{"slug": "your-app-should-ship-an-mcp-server", "title": "Your App Should Ship an MCP Server", "summary": "A developer building a native desktop editor for fiction writers embedded a full MCP server inside the application binary to enable AI agents to drive the app programmatically, solving the problem of opaque GUI apps. The MCP server allows the AI to rebuild, launch, and test the app without human intervention, dramatically accelerating the development loop.", "body_md": "I’m building a native desktop editor for fiction writers. It’s written in Rust on top of [gpui](https://www.gpui.rs), by the Zed team. Under the hood, it generates a fiction specific AST, runs ~120 prose-craft analyzers and uses a multi-task ONNX transformer model against your manuscript in real time, surfacing things like show-don’t-tell violations, passive voice, pacing issues, and much more.\n\nI started out using [gpui-component](https://github.com/longbridge/gpui-component), but the available `Input`\n\ncomponent was not sufficient for building the complex UI I wanted with a CRDT backing, so I ended up with my own buffer based system and building the entire text editing UX from scratch. **This is not recommended!**\n\nTo solve for this complexity, I embedded a full [MCP](https://modelcontextprotocol.io/) server directly inside the application binary. It has since become the single most impactful architectural decision I’ve made on this project, not for users, but for *how I Claude builds the product itself*.\n\nHere’s the case for why your application should do the same.\n\n## The Problem: GUI Apps Are Opaque to AI Agents\n\nIf you’re building a native desktop application in 2026, you’ve probably noticed a gap. Your AI coding assistant can read your source code, run your tests, and even propose edits. However, your AI cannot *always* see your running application. It can’t click a button, type into a text field, verify that a diagnostic tooltip rendered correctly, or confirm that a scrollbar stopped at the right position.\n\nFor web apps, this is a solved problem with headless browsers, Playwright, Chrome MCP, etc. For native apps, especially those built on GPU-accelerated frameworks like gpui, you’re largely on your own. There’s no DOM to query. There’s no accessibility tree you can trivially script against. The rendered output is just a texture.\n\nI spent too long in a loop that looked like this:\n\n- Read the source code\n- Make a change\n`cargo build`\n\n- Manually launch the app\n- Manually paste in test prose\n- Squint at the screen\n- Screenshot it myself\n- Paste the screenshot into the AI interface\n- Repeat\n\nSteps 4 through 8 are the bottleneck, and no amount of faster builds fixes that. The feedback loop is human-gated.\n\n## The Solution: Make the App Speak MCP\n\nThe [Model Context Protocol](https://modelcontextprotocol.io/) is essentially a standardized JSON-RPC interface that AI agents already know how to speak. If your app exposes MCP tools, any MCP-compatible client can drive your application programmatically.\n\nMy implementation has two pieces:\n\n### 1. The In-App MCP Server\n\nWhen launched with `--mcp`\n\n, the app starts a background thread that reads newline-delimited JSON-RPC from stdin and writes responses to stdout. Commands are dispatched into the gpui event loop.\n\nThis is ~200 lines of Rust. No external dependencies beyond `serde_json`\n\n. The protocol surface is minimal: `initialize`\n\n, `tools/list`\n\n, and `tools/call`\n\n. That’s it (for now).\n\n### 2. The Lifecycle Wrapper\n\nThis is a separate binary that *manages* the app process. It:\n\n- Builds the app from source on startup\n- Launches it with\n`--mcp`\n\n- Proxies all JSON-RPC between the MCP client and the app\n- Intercepts a special\n`rebuild`\n\ntool call to stop the app, run`cargo build`\n\n, and relaunch, without dropping the MCP connection\n\nThe wrapper feels like a hack and there is probably a cleaner solution. When the agent edits Rust source and calls `rebuild`\n\n, the app restarts with the new binary and the agent’s MCP session continues uninterrupted.\n\n### The SDLC Loop\n\nHere’s what the development loop looks like with the MCP server in place compared to without:\n\n```\nBefore (human-gated):\n\n  ╭─► Edit .rs             Agent\n  │   cargo build          Agent\n  │   Launch App           Agent\n  │   Paste Prose          Human\n  │   Squint               Human\n  │   Screenshot           Human\n  │   Describe to AI       Human\n  ╰───────────╯\n\nAfter (agent-driven):\n\n  ╭─► Edit .rs             Agent\n  │   rebuild              Agent\n  │   set_text             Agent\n  │   wait_idle            Agent\n  │   screenshot           Agent\n  ╰───────────╯\n```\n\nThe “before” loop requires a human at every step past the build. The “after” loop is fully autonomous and the agent drives the entire cycle in ~10-second iterations. Sceeenshots are expensive, you can expose other tools.\n\n## What Tools Does the Server Expose?\n\nHere’s my current tool surface. Claude can quickly iterate on the available tools as it adds features too!\n\n| Tool | What it does |\n|---|---|\n`set_text` / `type_text` | Load prose or type at cursor |\n`press_key` | Simulate any keystroke (enter, backspace, Cmd+B, etc.) |\n`click` / `double_click` / `triple_click` | Click at pixel coordinates |\n`drag_select` | Click-drag selection |\n`screenshot` | Capture the window to PNG |\n`get_state` | Return cursor position, selection, text content, word count |\n`get_diagnostics` | Return structured analysis results (message, severity, source, byte range) |\n`wait_idle` | Block until both fast and semantic analysis stages complete |\n`set_view_mode` | Switch between Draft, Review, and Analyze modes |\n`set_nav_pane` | Switch sidebar panes (editor, outline, find, diagnostics, settings) |\n`list_elements` | Enumerate UI elements with rendered positions |\n`hover_diagnostic` | Programmatically hover a diagnostic card |\n`format_state` | Query which inline/block formats are active at cursor |\n`rebuild` | Stop → `cargo build` → relaunch (wrapper-only) |\n\nThe total is around 30 tools. The marginal cost of adding a new tool is about 15 minutes; write a match arm, call an existing editor method, return JSON.\n\nI haven’t attempted to expose dynamic tools based upon the current view.\n\n## What This Actually Enables\n\n### AI-Driven Iteration and Verification\n\nThe agent can now verify what it built. It edits `paint.rs`\n\n, calls `rebuild`\n\n, calls `set_text`\n\nwith sample prose, calls `wait_idle`\n\nto let the analyzers finish, and calls `screenshot`\n\nto capture the result. It reads the PNG, evaluates whether the margin notes rendered correctly, and iterates. No human in the loop.\n\n### Structured Test Authoring\n\nInstead of asserting against internal state (which couples tests to implementation), the agent can write behavioral tests:\n\n```\nset_text(\"She felt very sad about what happened.\")\nwait_idle()\ndiagnostics = get_diagnostics()\nassert any(d.source == \"show_tell\" for d in diagnostics)\nassert any(d.source == \"redundancy\" for d in diagnostics)\n```\n\nThis tests what the *user* would experience. If I refactor the analyzer pipeline — change AST nodes, rename modules, swap out models — these tests still pass because they’re testing the product surface, not the implementation. Too many tests at these lower levels just add friction and churn especially with AI coding tools.\n\n### The “Rebuild” Pattern\n\nThis is another useful pattern. The agent can:\n\n- Edit a\n`.rs`\n\nfile - Call\n`rebuild`\n\n(wrapper stops the app, runs`cargo build`\n\n, relaunches) - Immediately verify the new build\n- Evaluate and iterate\n\nThe rebuild starts (shouldn’t take too long with Rust’s incremental compilation). The MCP session stays connected. The agent can do edit-verify cycles quicker than I can switch windows.\n\n## The Broader Principle\n\nThere’s a deeper pattern here. We’re entering a period where the *audience for your application’s API* is not just other programmers — it’s AI agents. And agents don’t need the same things programmers need. They don’t need beautiful documentation, clever abstractions, or versioned REST endpoints. They need:\n\n- A way to\n**do things** - A way to\n**wait for things** - A way to\n**verify things**\n\nMCP in your app gives you a standardized way to expose all three. The protocol handles capability negotiation, tool discovery, and structured responses. Your job is just to wire the tools to your application’s internals.\n\nI started the MCP server to speed up my own development loop. You might want to also use the same MCP server for your power users!\n\nOpinions are my own and not the views of my employer.\n\n© 2026 by Justin Poehnelt is licensed under CC BY-SA 4.0", "url": "https://wpnews.pro/news/your-app-should-ship-an-mcp-server", "canonical_source": "https://justin.poehnelt.com/posts/ship-mcp-server-native-app/", "published_at": "2026-06-24 11:31:47+00:00", "updated_at": "2026-06-24 11:40:47.967894+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["MCP", "Model Context Protocol", "gpui", "Zed", "Rust", "ONNX", "CRDT", "Claude"], "alternates": {"html": "https://wpnews.pro/news/your-app-should-ship-an-mcp-server", "markdown": "https://wpnews.pro/news/your-app-should-ship-an-mcp-server.md", "text": "https://wpnews.pro/news/your-app-should-ship-an-mcp-server.txt", "jsonld": "https://wpnews.pro/news/your-app-should-ship-an-mcp-server.jsonld"}}