Conversation
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>
This was referenced Apr 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
cli-mcp-parity(notmain).mainas 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.sockpreviewsmcp status— reports daemon livenesspreviewsmcp kill-daemon— graceful SIGTERMNWEndpoint.unix)MCP.Serverper connection, sharing module-level state + a singleCompilerIncludes 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:
DaemonClient+runmigration (+--detach)snapshotmagical reuseconfigureswitchelements(iOS)touch(iOS)simulatorsstopvariantsmigrationRelated
runcommand has no way to capture a snapshot of the running preview #92, CLI/MCP parity: run command lacks interactive capabilities #94runsessions #95 (interactive REPL)🤖 Generated with Claude Code