Skip to content

CLI/MCP parity (feature branch)#97

Closed
obj-p wants to merge 2 commits intomainfrom
daemon-foundation
Closed

CLI/MCP parity (feature branch)#97
obj-p wants to merge 2 commits intomainfrom
daemon-foundation

Conversation

@obj-p
Copy link
Copy Markdown
Owner

@obj-p obj-p commented Apr 14, 2026

Feature branch — do not merge until all child PRs land

This is the umbrella feature branch for the CLI/MCP parity work described in docs/cli-mcp-parity-spec.md.

Workflow:

  • Each step from the spec's implementation plan ships as a child PR targeting cli-mcp-parity (not main).
  • When all child PRs have landed and verification is complete, this branch merges to main as a single feature.

Current contents (PR 1 of plan)

Daemon foundation — landed as the initial commit on this branch:

  • previewsmcp serve --daemon — runs MCP server on ~/.previewsmcp/serve.sock
  • previewsmcp status — reports daemon liveness
  • previewsmcp kill-daemon — graceful SIGTERM
  • UDS transport via Apple Network framework (NWEndpoint.unix)
  • One MCP.Server per connection, sharing module-level state + a single Compiler
  • Startup race fix (authoritative socket-connect liveness probe)

Includes 7 daemon lifecycle integration tests. All MCP stdio paths (Claude/Cursor integration) unchanged and still passing.

Pending child PRs

Per the spec's stacked plan:

  • PR 2 — DaemonClient + run migration (+ --detach)
  • PR 3 — snapshot magical reuse
  • PR 4 — configure
  • PR 5 — switch
  • PR 6 — elements (iOS)
  • PR 7 — touch (iOS)
  • PR 8 — simulators
  • PR 9 — stop
  • PR 10 — variants migration

Related

🤖 Generated with Claude Code

obj-p and others added 2 commits April 13, 2026 21:24
Introduces `previewsmcp serve --daemon`, which runs the MCP server on a
Unix domain socket at ~/.previewsmcp/serve.sock. Multiple CLI clients can
connect concurrently; each connection gets its own MCP.Server instance
sharing module-level state (IOSState, ConfigCache) for consistent session
visibility.

New commands:
- `serve --daemon` — starts the daemon listener
- `status` — reports daemon liveness via socket connect
- `kill-daemon` — graceful SIGTERM with stale PID cleanup

Supporting infrastructure:
- DaemonPaths — filesystem constants (~/.previewsmcp/{serve.sock, serve.pid})
- DaemonLifecycle — PID file management + signal handlers
- DaemonListener — NWListener on UDS, accepts connections, spawns per-conn servers

Existing `serve` (stdio mode, used by Claude/Cursor MCP integration) is
unchanged and its integration tests still pass.

First PR in the stack for the CLI/MCP parity spec (docs/cli-mcp-parity-spec.md).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changes from self-review of PR 97:

- Critical: fix startup race where a second `serve --daemon` could unlink
  the first daemon's socket and clobber it. Previous check was PID-based,
  which misses the window between bind and PID-write (and also misses the
  case where someone deletes the PID file manually). Replaces with a
  socket `connect()` probe via new DaemonProbe module — authoritative
  since the kernel tracks socket-to-fd associations atomically.

- Share Compiler across daemon connections. `configureMCPServer` now
  accepts `sharedCompiler:`; DaemonListener builds one at startup and
  passes it to each accepted connection, avoiding ~seconds of per-client
  xcrun/SDK resolution cost. Stdio mode still creates its own.

- Extract `DaemonProbe.canConnect()` — shared by ServeCommand (startup
  guard) and StatusCommand (liveness report). Previously duplicated.

- Extract `DaemonLifecycle.daemonRunningPID()` — returns PID only if
  alive, replaces two-step readPID/isProcessAlive calls at three sites.

- Readability:
  - Remove unneeded @mainactor from DaemonListener.start
  - Replace clever AsyncStream-based ready-signaling with
    withCheckedThrowingContinuation
  - Remove dead runForever() (NSApp.run() handles this)

- Add regression test secondDaemonRefusesWithMissingPIDFile that would
  hang without the fix (second daemon rebinds and runs forever). Uses a
  bounded wait so it fails fast if the race returns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@obj-p obj-p closed this Apr 14, 2026
@obj-p obj-p deleted the daemon-foundation branch April 14, 2026 11:28
@obj-p obj-p changed the title Add daemon foundation for CLI/MCP parity CLI/MCP parity (feature branch) Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant