{"slug": "headless-xcode-from-prompt-to-simulator-with-mcp", "title": "Headless Xcode: From Prompt to Simulator with MCP", "summary": "Apple's Xcode 27 beta 5 introduces `xcrun mcp-server`, a headless MCP server that exposes Xcode's build, preview, and simulator tools to external coding agents without opening the Xcode UI. Combined with Apple's exportable agent skills, it enables an agent like Claude Code to create, build, and verify a project entirely from the command line, with permissions gated by per-agent approval and `sudo`-enabled settings.", "body_md": "Xcode's MCP server lets a coding agent create files, build, render previews, and drive the simulator. Until now, those tools disappeared when you quit Xcode.\n\nXcode 27 beta 5 adds `xcrun mcp-server`\n\n, which exposes the same tools without\nopening the Xcode UI. Combined with Apple's exportable agent skills, it lets an\nexternal agent create a project, build it, render previews, and verify\ninteractions in the simulator. I'll use Claude Code to do exactly that with a\nsmall reading list app.\n\nEverything here was run on Xcode 27.0 beta 5 (build 27A5237l) with Claude Code as the MCP client. You need that Xcode build, Claude Code, and an administrator account to enable the service. This is beta software and the CLI surface may change.\n\nQuit Xcode. We're getting started.\n\n## Starting the server without the Xcode UI\n\nHeadless mode is off until you turn it on, and every command that changes\npermissions needs `sudo`\n\n:\n\n`enable`\n\nflips the switch; `start`\n\nlaunches the service and is a no-op if it is\nalready up. `unsafeAlwaysAllowAllAgents`\n\nis the one thing `enable`\n\nlets you\nchange, and `false`\n\n— the default — means every agent gets approved\nindividually. More on that later.\n\n`xcrun mcp-server status`\n\nreports where things stand:\n\n## Wiring the project: MCP server and Apple's skills\n\nTwo things have to be in place before an agent is any use here, and both belong in the project repo rather than in your machine's global config: the server registration, which is how the agent reaches Xcode at all, and Apple's skills, which are how it knows what to do once it gets there.\n\n### Registering the MCP server in the repo\n\nThe MCP server itself is a stdio bridge. Pinning `DEVELOPER_DIR`\n\nin the\nregistration keeps the beta scoped to the agent and leaves the stable Xcode\nselected for day-to-day work:\n\nEverything after `--`\n\nis the command the bridge runs, so the flags belong to\n`claude`\n\nrather than to `xcrun`\n\n. `--scope project`\n\nwrites a `.mcp.json`\n\nat the\nrepository root. The registration travels with the repository, but every\ndeveloper still approves it locally. The resulting file is small enough to\nhand-edit later:\n\nPoint `DEVELOPER_DIR`\n\nat wherever the beta actually lives. You can omit the\nvariable entirely to use the Xcode selected by `xcode-select`\n\n. When set,\n`DEVELOPER_DIR`\n\ntakes precedence without changing the selection for other\nterminals.\n\n### Exporting Apple's skills into the repo\n\nTools are verbs: build this, render that. Skills tell the agent when to use\nthem and how Apple expects SwiftUI code to be structured. One command copies\nApple's skills next to `.mcp.json`\n\n:\n\nXcode launches and shows its window while exporting the skills — its only\non-screen appearance in this entire headless workflow. The skills live inside\nXcode rather than the CLI, which is why the command prints `Launching Xcode...`\n\n.\nExisting skills are skipped rather than overwritten, so pass\n`--replace-existing`\n\nwhen you re-export after an update.\n\nPass `--output-dir`\n\nan **absolute** path. The export is performed by Xcode\nover XPC (Apple's system for communication between processes), not by your\nshell, so a relative path may resolve outside your project and fail.\n\nEach skill is a plain `SKILL.md`\n\nwith `name`\n\nand `description`\n\nfrontmatter,\nmost with a `references/`\n\ndirectory of deeper material and occasionally a\n`scripts/`\n\ndirectory:\n\nPutting them under `.claude/skills/`\n\nis what makes Claude Code discover them\nautomatically, and discovery is relative to the directory the agent was started\nin — so the agent has to be started in the project, the same place the server\nregistration lives.\n\n## Understanding the two permission gates\n\nAfter you start an agent, its first request to the MCP server waits while the headless service authorizes two things independently.\n\nThe first gate is the **agent** itself. This prompt appears:\n\nThe prompt comes from the headless service, not from Claude Code. The grant is\nkeyed to the code signature rather than the app name, which is why the dialog\nsays *agents from Anthropic PBC*. Approving it gives the agent every tool the\nserver exposes: *build, test, and modify your code*, as the dialog says.\n\nAn Xcode icon appears in the menu bar as the only visible trace of the setup. Click it to open the Background Activity window:\n\nIt lists approved agents, their current activity, and any open workspaces.\n`Quit`\n\nshuts down the service, just like `xcrun mcp-server stop`\n\n.\n\nA **folder** grant is the second gate, and it gets its own prompt the moment the\nagent tries to open or create something:\n\nThe phrase *Any Xcode project inside this folder* matters. The grant is\nrecursive and does not expire unless you pick *Allow for 24 Hours*.\n\n`status`\n\nshows both gates now:\n\nBoth grants carry an id, and that id is how you revoke them later.\n\n### Skipping the prompts\n\nOne unsafe flag skips both prompts:\n\nThe Xcode item in the menu bar normally collapses to a hammer with no background. When an agent connects, it briefly expands into a colored badge: blue for individual approvals and red for unsafe mode. The red badge is a security warning, not an error.\n\nWhile Xcode is handling an agent request, the hammer changes into rotating arrows — for example, while building and launching the app on a simulator. The color still reflects the permission mode; the animation is the activity indicator.\n\nThe flag is named after agents, but it bypasses the folder gate too. It trusts\nevery process on the machine with every Xcode project it can reach, leaving no\nindividual grant in `status`\n\nto revoke. Disable and re-enable the service\nwithout the flag to return to individual prompts. To remove any stored grants,\nrun:\n\nThe next connection asks for approval again.\n\n## Creating the project from a prompt\n\nWith the setup complete, describe the project:\n\nCreate a new multiplatform SwiftUI app called ReadingListExample in a subfolder, no storage, Swift Testing.\n\nThe agent discovers what the templates actually offer instead of guessing at\nidentifiers. The `XcodeListTemplates`\n\ntool with `kind: \"project\"`\n\nreturns each\ntemplate along with its option schema — for the standard App template that is\n`storageType`\n\n, `hostInCloudKit`\n\n, and `testingSystem`\n\n. Then the\n`XcodeNewProject`\n\ntool instantiates it:\n\nThat JSON is the tool call, not something you write. Claude Code collapses tool calls to a one-line summary; ⌃ + O shows the parameters and response.\n\n`destinationPath`\n\nis the **parent** directory. Asking for a subfolder keeps\n`.mcp.json`\n\nand the skills at the repository root while Xcode creates\n`<productName>/`\n\nbeneath it. The `XcodeNewProject`\n\ntool writes the project to\ndisk; the `XcodeOpenWorkspace`\n\ntool then opens the generated `.xcodeproj`\n\nand\nreturns the workspace identifier used by subsequent tools.\n\n## Generating the app with Apple's SwiftUI skill\n\nThe app is deliberately small: a list of books grouped into *Currently Reading*\nand *Finished*, a detail screen, and a sheet for adding a book.\n\nBuild the reading list screen. A\n\n`Book`\n\nmodel with title, author, page count, current page and a reading/finished status. An`@Observable`\n\nstore that holds a loading/loaded/failed state and exposes the two sections. A`List`\n\ngrouped into Currently Reading and Finished, pushing to a detail screen with a \"Mark as Finished\" button, plus a sheet for adding a book. Log every mutation with`OSLog`\n\nunder the`ReadingList`\n\ncategory.\n\nLogging each mutation lets the simulator run verify the underlying state changes, not only the visible UI.\n\nThe generated project keeps the model, store, views, and logging extension in separate files:\n\nClaude loaded `swiftui-specialist`\n\nand its data-flow and structure references\nbefore editing. The generated store caches both sections and recomputes them\nafter mutations, while rows and sections remain separate views.\n\nThe `BuildProject`\n\ntool then verifies the generated project:\n\n## Rendering previews without a Canvas\n\nThe `RenderPreview`\n\ntool keeps visual verification inside the agent's workflow.\nIt builds a preview and returns the rendered PNG without opening the Canvas.\n\nCombine the previews in\n\n`ReadingListView.swift`\n\ninto one`#Preview(arguments:)`\n\nwith all four states — loading, empty, loaded, failed — and give each a readable name. Then render every variant and look at the PNGs.\n\nThe four states end up in a single\n[ #Preview(arguments:)](/blog/previewing-swiftui-states-with-preview-arguments),\none variant per case:\n\nThen the agent renders each variant, opens the resulting PNGs, and checks their\ncontents. The `RenderPreview`\n\ntool returns file paths, so it is important to ask\nthe agent to inspect the images rather than only confirm that rendering\nsucceeded.\n\nComparing four separate PNGs is cumbersome, so ask Claude to publish them as a single HTML artifact:\n\nPublish the renders as an artifact, one card per state.\n\nAn artifact is a standalone HTML page that Claude generates from the rendered files. Here it embeds each PNG in a labeled card, arranges the four states in a grid, and adds context such as the device, OS version, and appearance. The page can then be opened and shared as a single result instead of passing around four temporary files.\n\nSide by side, they reveal details a single snapshot hides: spacing that drifts between states, a title that sits differently, or a message that wraps.\n\n## Verifying a full app flow on the simulator\n\nPreviews show individual screens and states, but they don't cover navigation, text input, or how changes propagate across the app. Running the app on a simulator lets the agent verify these interactions as one continuous flow.\n\nThe prompt is the sentence you'd say to a colleague — no coordinates, no tool names, no mention of a simulator being booted:\n\nRun the app on the iPhone 17 Pro simulator and check that finishing a book moves it out of Currently Reading. Then add a book and confirm it shows up.\n\nThe agent opens an interaction session, boots a simulator through MCP, then builds, installs, and launches the app. This keeps verification in the same workflow as code generation without requiring manual interaction with the Simulator UI.\n\nEvery capture returns a PNG and the accessibility hierarchy, including each element's label, frame, and a precomputed point to tap. The agent uses this data to find controls and interact with them instead of guessing coordinates. These tools rely on the app's accessibility metadata, so clear labels benefit both automation and VoiceOver users.\n\nHere is the PNG returned for one capture:\n\nThe matching accessibility hierarchy included entries like these:\n\nThe output is reformatted here for readability. The frame is written as\n`{{x, y}, {width, height}}`\n\n, while `label`\n\nand `value`\n\ndescribe what is on\nscreen. `hitPoint`\n\ngives the agent a reliable coordinate for the next action:\nthe second entry becomes `t 201.0 264.7`\n\n.\n\nInteractions are a small command language, chained into one call — tap, wait, type, swipe, press a hardware button:\n\nHere `t x y`\n\ntaps, `w`\n\nwaits, and `sender keyboard kbd`\n\ntypes everything that\nfollows. Swipe and hardware-button commands can be chained in the same call.\nThe `device-interaction`\n\nskill documents this syntax and helps the agent turn\nthe accessibility output into reliable actions.\n\nUsing this loop, the agent can open a book, mark it as finished, return to the list, open the add-book sheet, enter the details, and submit the form. A final capture shows the result of the complete flow:\n\nThe screenshot confirms the visible result, while `OSLog`\n\nconfirms the\nunderlying state changes:\n\nTogether, the UI capture and logs provide stronger evidence than a successful build or preview alone. Closing the interaction session releases the simulator and its debug connection.\n\n## What headless costs you\n\nHeadless mode removes Xcode's visual debugging UI, not the debugger itself. You\nlose the Canvas with its live selection, the view debugger, the breakpoint\ngutter, and Organizer. Breakpoints still work through LLDB, but they are not\nvisible in an editor. The `RenderPreview`\n\ntool gives you a PNG per variant\ninstead of an interactive Canvas.\n\nBuilds, previews, tests, simulator runs, and console output remain available through tools. That covers most of an agent's build loop without a window stealing focus every time it renders something. You can still open the same project in Xcode whenever you need its visual tools.\n\n## Optional: inspecting the stored permissions\n\nYou don't need the implementation details below to use the server. They are useful when auditing or scripting the setup, but the location and schema are undocumented beta internals and may change.\n\nThe headless setting and folder grants are reflected in a JSON file inside Xcode's group container:\n\nThe file is one line on disk, formatted here for readability:\n\nRead it, but don't edit it: the service rewrites the file on every change. For\nscripts, `status --format json`\n\nis the better source because it also reports\nruntime state and the signed agent trust object:\n\nThe JSON format makes the unsafe-mode flag and agent trust data easier to inspect from scripts. A provisioning script can use them to verify the expected team identifier before a build.\n\n## Conclusion\n\nAfter the initial setup, Claude created the project, generated the app with Apple's skills, rendered four preview states, and verified a complete flow on the simulator without keeping the Xcode UI open.\n\nThe complete app, including the exported skills under `.claude/skills/`\n\n, is in\nthe private\n[ReadingListExample](https://github.com/artemnovichkov/ReadingListExample)\nrepository. My\n[Xcode Tools documentation](https://github.com/artemnovichkov/xcode-tools-docs)\ncovers the individual MCP tools in more detail.", "url": "https://wpnews.pro/news/headless-xcode-from-prompt-to-simulator-with-mcp", "canonical_source": "https://artemnovichkov.com/blog/headless-xcode-from-prompt-to-simulator-with-mcp", "published_at": "2026-08-13 17:04:16+00:00", "updated_at": "2026-08-13 17:13:56.495971+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["Apple", "Xcode 27 beta 5", "Claude Code", "MCP"], "alternates": {"html": "https://wpnews.pro/news/headless-xcode-from-prompt-to-simulator-with-mcp", "markdown": "https://wpnews.pro/news/headless-xcode-from-prompt-to-simulator-with-mcp.md", "text": "https://wpnews.pro/news/headless-xcode-from-prompt-to-simulator-with-mcp.txt", "jsonld": "https://wpnews.pro/news/headless-xcode-from-prompt-to-simulator-with-mcp.jsonld"}}