Build a small, purpose-built CLI for Xcode's MCP bridge that:
- talks to
xcrun mcpbridge - keeps a single long-lived client identity
- uses a per-user daemon so Xcode permission can be granted once to that daemon-owned client
This is intentionally a narrow tool for Xcode, not a generic MCP framework.
A custom tool can be:
- smaller
- easier to debug
- easier to distribute as a stable native binary
- more explicit about Xcode-specific commands and errors
- The project will target Swift as the primary implementation language.
- The build system will be Swift Package Manager.
- The system will be split into two executables:
xcode-toolsandxcode-toolsd. - The daemon will own the only long-lived connection to
xcrun mcpbridge. - The CLI will talk only to the daemon over a local Unix domain socket.
- The daemon API will be a small app-specific RPC protocol, not raw MCP.
- V1 will focus on read/build/test flows and daemon lifecycle, not full Xcode tool coverage.
The tool should consist of two binaries:
-
xcode-toolsThe user-facing CLI. -
xcode-toolsdA per-user background daemon that owns a long-livedxcrun mcpbridgesubprocess.
The daemon is the only process that should directly communicate with xcrun mcpbridge.
The CLI should never talk to xcrun mcpbridge directly.
Instead:
xcode-tools -> unix domain socket -> xcode-toolsd -> stdio -> xcrun mcpbridge -> Xcode
That gives us:
- one stable client identity from Xcode's point of view
- persistence across multiple CLI invocations
- a place to handle reconnects, logs, and health checks
Ship only the essential workflow:
list-windowsbuild <tab-id>test <tab-id>test-list <tab-id>read <file> <tab-id>grep <pattern> <tab-id> [path]issues <tab-id>build-log <tab-id>statusstoprestart
Defer these until the daemon and permission model are proven:
writeupdatermmvmkdir- previews and snippet execution
- full dynamic tool discovery
- multi-user or remote access
- building a generic MCP client framework
- supporting arbitrary MCP servers beyond Xcode
- exposing the daemon over the network
- implementing collaborative or multi-user access
- optimizing for parallel tool execution in v1
- shipping full write/mutation coverage before the permission model is validated
Use Swift with Swift Package Manager.
Why Swift:
- native macOS binary with a stable process identity
- clean fit for
Process, pipes, file APIs, and launchd integration - straightforward future path for packaging and signing
- no external runtime dependency once built
- strong alignment with an Apple-platform-only tool
Go would also work well, but this version assumes we are explicitly choosing tighter Apple-native integration over lighter tooling.
xcode-tools is responsible for:
- parsing commands
- connecting to the daemon socket
- auto-starting the daemon if missing
- sending local RPC requests
- formatting output for humans
It should not:
- speak MCP directly
- manage the bridge subprocess
- own reconnect logic beyond "start daemon if unavailable"
xcode-toolsd is responsible for:
- listening on a per-user Unix domain socket
- starting
xcrun mcpbridge - performing MCP initialization
- sending
tools/listandtools/call - caching known tool metadata
- restarting the bridge when it exits unexpectedly
- exposing health and lifecycle commands
The daemon should wrap the MCP bridge as a managed child process:
- executable:
xcrun - args:
mcpbridge - transport: stdio
The daemon should own:
- stdin writer
- stdout reader
- stderr log capture
- request/response correlation
Do not expose raw MCP as the daemon API.
Use a tiny app-specific JSON protocol over a Unix socket.
Example requests:
{"id":"1","method":"status"}
{"id":"2","method":"stop"}
{"id":"3","method":"restart"}
{"id":"4","method":"callTool","params":{"tool":"BuildProject","arguments":{"tabIdentifier":"windowtab1"}}}Example responses:
{"id":"1","ok":true,"result":{"daemon":"running","bridge":"healthy"}}
{"id":"4","ok":true,"result":{"content":[{"type":"text","text":"Build succeeded"}]}}
{"id":"4","ok":false,"error":{"message":"Xcode is not running"}}This keeps the daemon API stable even if internal MCP details evolve.
The daemon should implement a minimal MCP stdio client:
- send
initialize - optionally send
initialized - call
tools/list - call
tools/call - correlate responses by
id
V1 does not need a complete generic MCP abstraction. It only needs enough to talk to the Xcode bridge reliably.
- User runs
xcode-tools list-windows - CLI does not find the daemon socket
- CLI starts
xcode-toolsd - Daemon launches
xcrun mcpbridge - Daemon initializes MCP
- Xcode prompts for permission
- User clicks Allow
- Subsequent CLI calls reuse the same daemon-managed connection
- CLI connects to daemon
- CLI sends request
- Daemon forwards to the bridge
- Daemon returns result
If the bridge exits or becomes unresponsive:
- daemon marks bridge unhealthy
- daemon restarts
xcrun mcpbridge - daemon re-initializes the MCP session
- daemon retries the original request once when safe
If the daemon itself is gone:
- CLI detects socket failure
- CLI starts daemon again
- CLI retries once
The key product assumption is:
Xcode permission should stick to the daemon-owned client identity, not to each short-lived CLI invocation.
This must be validated early.
The entire project is worth doing only if this assumption holds reliably enough in real use.
- Does Xcode reliably treat the daemon-owned client as a stable identity across multiple CLI invocations?
- Is explicit
tabIdentifierenough for the first release, or isMCP_XCODE_PIDsupport needed immediately? - Should the daemon auto-start only on demand, or should the project also ship an optional
launchdagent for always-on behavior? - Should v1 support only human-readable CLI output, or also a machine-readable JSON mode?
- Should bridge retry behavior be limited to one retry globally, or vary by command type?
- How much tool metadata should the daemon cache after
tools/list?
Recommended per-user paths:
~/Library/Application Support/xcode-tools/
daemon.sock
daemon.pid
daemon.log
state.json
The daemon should ensure the directory exists and use user-only permissions.
V1 logging should be simple:
- daemon stdout/stderr to
daemon.log - bridge stderr appended to
daemon.log - optional request summaries:
- timestamp
- tool name
- elapsed time
- success/failure
Avoid logging full file contents by default.
Suggested SwiftPM layout:
Package.swift
Sources/
xcode-tools/
main.swift
xcode-toolsd/
main.swift
CLI/
Commands.swift
OutputFormatter.swift
Daemon/
DaemonServer.swift
SocketServer.swift
BridgeProcess.swift
MCPClient.swift
StateStore.swift
Protocol/
LocalRPC.swift
MCPMessages.swift
Errors.swift
LogSupport/
Logger.swift
Launchd/
com.example.xcode-toolsd.plist
Tests/
CLITests/
DaemonTests/
ProtocolTests/
Responsibilities:
- launch
xcrun mcpbridge - manage stdin/stdout/stderr pipes
- detect child exit
- expose restart hooks
- coordinate daemon lifecycle and health
Likely files:
DaemonServer.swiftBridgeProcess.swiftSocketServer.swiftStateStore.swift
Responsibilities:
- encode JSON-RPC requests
- decode JSON-RPC responses
- map request ids to pending continuations or callbacks
- implement
initialize,tools/list, andtools/call
Likely files:
LocalRPC.swiftMCPMessages.swiftErrors.swift
Responsibilities:
- map CLI verbs to Xcode tool names
- validate command arguments
- build local RPC requests
- present output in a stable human-friendly way
Likely files:
Commands.swiftOutputFormatter.swift
Responsibilities:
- provide lightweight logging helpers
- keep daemon and bridge logging consistent
- avoid unnecessary dependencies in v1
Keep v1 simple.
- accept multiple CLI clients
- serialize bridge calls through one actor or one dedicated request queue
- do not attempt parallel MCP calls initially
This avoids subtle ordering and correlation bugs while the core system is being proven.
- errors should mention Xcode explicitly when relevant
list-windowsshould be the default discovery path fortabIdentifier- command help should be concise
- daemon commands should be obvious:
status,stop,restart - failures should be honest and nonzero
- local RPC encoding/decoding
- CLI argument parsing
- MCP request/response parsing
- bridge restart state transitions
Use a fake MCP stdio server first:
- daemon starts bridge
- multiple CLI calls reuse the same daemon-owned process
- daemon restarts bridge after forced exit
- CLI auto-starts daemon when socket is missing
With real Xcode:
- enable Xcode MCP
- start daemon
- verify permission prompt appears once
- run multiple CLI invocations
- verify prompt does not reappear on each call
- restart CLI but keep daemon alive
- verify access still works
The main risk is that Xcode may not treat the daemon identity the way we expect.
This is the first thing to validate.
xcrun mcpbridge may hang, exit, or change behavior across Xcode releases.
The daemon must tolerate restarts.
V1 should avoid smart guessing.
Use tabIdentifier explicitly and add MCP_XCODE_PID support later if needed.
Apple may add or rename tools.
V1 should hardcode only the small set we depend on.
- build a tiny daemon
- connect to
xcrun mcpbridge - support
statusandlist-windows - confirm Xcode permission behavior
- add build, test, read, grep, issues
- add restart logic
- add logging
- launchd support
- better output formatting
- packaging and install story
- prototype: 1 day
- solid internal v1: 3 to 5 days
- polished distributable version: 1 to 2 weeks
Proceed only if Milestone 1 confirms the permission model.
If that works, a custom daemonized CLI is a very reasonable standalone alternative for an Xcode-specific workflow.