Source: https://jtpck.com/blog/creating-an-mcp-server-for-a-mac-app

# Creating an MCP Server For A Mac App

How we built an MCP server for WRFRMS, our Mac wireframing app in development, by exposing the app's native document mutations, layout logic, and file-watching workflow to agents.

By JTPCK Team · Apr 20, 2026

WRFRMS, our Mac wireframing app still in development, is built around frames, devices, a palette of UI primitives, and files that serialize to JSON. When we started thinking about exposing it to agents, a few properties made it an unusually clean fit for MCP. The domain is small and well-typed, with about thirty element kinds and a dozen device presets. Every user action is a pure mutation on a document, and the document is already a single file on disk. That meant we could expose the app's own verbs like `add_element`, `move_element`, `resize_element`, and `clear_frame` as MCP tools without inventing a new abstraction layer. An agent that can read the document and call those tools has exactly the same leverage over a wireframe as a human with a mouse.

<video controls playsinline preload="metadata" class="w-full rounded-xl my-8">
  <source src="/blog/mcp.mp4" type="video/mp4" />
  Your browser does not support the video tag.
</video>

## Keeping the Server Close to the App

We implemented the server in TypeScript on top of `@modelcontextprotocol/sdk`. The tools are thin wrappers over a small core loop: `readProject`, then mutate, then `writeProject`, with atomic renames and an mtime check that rejects writes if the app saved underneath us.

The important detail was not the transport. It was behavior. We ported the app's own layout logic, including the snap-into-device rules that anchor nav bars to the top, footers to the bottom, and heroes full-width, directly into the server in `src/element-factory.ts`. That way an element placed by MCP lands exactly where the UI would have placed it. On the Swift side, `DocumentViewModel` file-watches the open project and hot-reloads when the server writes, briefly suppressing the watcher around its own atomic writes so save-reload-save loops do not thrash.

## The Mac File System Is the Hard Part

A few Mac-specific sharp edges showed up quickly in practice. `NSSavePanel` appends `.json` because we declared `allowedContentTypes = [.json]`, so a file the user types as `mcp.wrfrms` lands on disk as `mcp.wrfrms.json`. The app's default save directory is `~/Documents/WRFRMS` rather than wherever the user's project source happens to live.

Figuring out which file the agent is actually editing mattered more than we expected. In theory MCP is about tools and structured calls. In practice, a large part of reliability comes down to whether the path on disk is the path the human thinks is open in the app.

## Shipping Instructions with the Server

The single highest-leverage addition was passing an `AGENT_INSTRUCTIONS` string to the server's `instructions` init field. Every MCP client forwards that to the agent on connect, so Claude Code, Cursor, or anything else gets a condensed operator brief immediately: tool workflow, coordinate conventions, default element sizes, snap behavior, and known limitations.

That removed a surprising amount of friction. Before that change, users would burn a large part of a session teaching the model that `x` and `y` are element centers rather than top-left coordinates, that `add_element` for `type=deviceFrame` hardcodes `deviceKind=iPhone`, or that the file on disk might not be the file the app currently has open. Once those details shipped as part of the server itself, the agent started informed instead of spending the first half of a session reconstructing invariants from trial and error.

## Lessons for Other Mac Apps

If you are building MCP into your own Mac app, expose the verbs your app already has instead of inventing new ones. Agents reason better about "add a button at `(x, y)`" than about an abstract CRUD schema disconnected from the product's actual language.

Port any non-trivial domain logic like layout, validation, or snapping into the server instead of approximating it. Identical behavior on both sides is what makes hot-reload feel seamless instead of uncanny.

Ship instructions from day one, and put the non-obvious parts there: file-location quirks, sharp edges, and limitations you have not fixed yet. Budget specifically for the Mac file-system corner cases too, especially `NSSavePanel` extension munging, `~/Documents` versus project-folder confusion, and the coordination dance between file-watching and your own atomic writes.

An mtime check plus atomic rename plus a short watcher-suppression window is maybe fifty lines of code, and it saves you from an entire class of "the app saved over my edit" bugs. Users will not thank you for the work they never see, but they will absolutely notice when their wireframe silently disappears.
