We shipped Model Context Protocol (MCP) support in Laravel in September, 2025. You define tools, register them on a server, and the model calls them. It's clean, fast, and genuinely easy to set up.
But the more tools we built, the more a specific frustration surfaced. Every single response came back as text. The model would get structured data (a list of links, a set of metrics, a queue of items to review) and convert it into a paragraph. Then the user would read the paragraph. Then ask for a follow-up. Then the model would fetch again.
Round and round, all text, all the time.
It's a strange regression. Web developers have spent 15 years building interfaces precisely because text is a bad way to present interactive data. Dashboards, forms, live feeds, drill-down views. Now all of that disappears when you go through an AI chat interface.
We wanted to fix that. So we built MCP Apps support in our MCP package, a way for MCP tools to return interactive HTML interfaces instead of text, rendered inline inside the conversation.
MCP Apps: An Interactive UI Inside Your AI Chat
Picture this: a user asks Claude to show them the trending links from their MCP server. Instead of reading a numbered list in the chat, a live card feed appears right inside the conversation. They scroll through it, click to summarize any link, and filter by category. The UI is just there, inside Claude, as part of the conversation, without the need to open a new tab or log in separately.
With MCP Apps, the host renders it in a sandboxed iframe, inline in the chat. The app can call server tools, push messages back to the model, and open links, all through the existing MCP connection, with no separate API or authentication layer to build.
Claude, Claude Desktop, VS Code Copilot, and Goose all support it.
Why Not Just Send a Link?
It’s a good question. You could build a web app, return a URL, and tell the user to open it. Plenty of tools do exactly that.
The difference is what the app has access to inside the host.
An MCP App runs inside the conversation. It can call any tool on your MCP server directly, with no new backend or auth. It can send messages back to the model: "Summarize this URL," "What does this metric mean," "Draft an email about this." The host pushes tool results to the app automatically when a tool is called. And because the app lives in the chat, the user never loses context.
A standalone web app gets none of that because isolated. MCP Apps are integrated.
The Protocol Is Not Simple to Implement
Once we decided to build MCP App support, we read the spec carefully. The protocol itself is well-designed. But implementing it from scratch takes longer than you'd expect:
- You need to advertise the
io.modelcontextprotocol/uicapability during the MCP handshake. - You need a
ui://URI scheme with the exact MIME typetext/html;profile=mcp-app. You need to bundle and ship a JavaScript SDK into the iframe HTML, which is the postMessage bridge that makescreateMcpAppwork. - You need iframe CSP headers permissive enough to load libraries but locked down enough to satisfy the sandbox.
- You need Tailwind's
darkModeconfigured to['selector', '[data-theme="dark"]']or your app renders incorrectly in dark mode because the host setsdata-theme, not a class. - And you need a way to mark certain tools as invisible to the model but callable only from the UI.
None of these are hard in isolation. Together, they're the kind of yak-shaving that kills momentum before you've written a line of product code.
laravel/mcp exists so you don't have to think about any of it.
What Building an MCP App Actually Looks Like
One command to scaffold:
A PHP class and a Blade view. That's your app. The PHP class handles resource registration; the Blade view is where the entire UI lives.
The <x-mcp::app> component inlines the JS SDK automatically, so no npm, no Vite, and no CDN dependency:
Want Tailwind and Alpine? One attribute on the resource class:
The Library enum injects the correct script tags, sets up Alpine's [x-cloak] style, configures Tailwind dark mode to match the host's data-theme attribute, and adds the CDN domains to the iframe's CSP. All automatically. The dark mode piece alone is the kind of thing you'd only discover by shipping a broken UI into Claude Desktop and wondering why your colors look wrong.
Then link a tool to the app:
The #[RendersApp] attribute injects _meta.ui.resourceUri into the tool listing. The server advertises the io.modelcontextprotocol/ui capability automatically. All the plumbing described above: you never touch it.
Keeping Tools Out of the Model's Sight
Once you have a UI, you'll want tools that the UI calls directly that the model should never see. A "get current page data" tool isn't a meaningful tool for the model to reason about. It's an implementation detail of the UI.
Visibility::App removes the tool from the model's listing entirely. Only the iframe can call it via app.callServerTool(...). Three options total: Model, App, or both (the default).
Locket
Locket is our open-source Laravel MCP server, and we built two MCP Apps on it as a real-world test of everything above.
The first is TrendingLinksApp: a visual card feed of the day's most bookmarked links. Users can open any link in their browser, bookmark it from the UI, or hit "Summarize" to delegate back to the model via app.sendMessage. It adapts to dark mode automatically.
The second is UnreadQueueApp: a personal reading queue showing the authenticated user's unread bookmarks, with one-click "Start reading" buttons. Because it requires authentication, the tool that renders it returns an error for unauthenticated requests before the app ever loads.
Both PHP resource classes are around 12 lines each. The entire app logic, HTML, CSS, and Alpine wiring lives in a single Blade file per app, with no build tooling at all.
The Even Faster Path
If you have Laravel Boost installed, the mcp-development skill scaffolds the resource class, Blade view, and linked tool from a plain description of what you want to build. It knows the conventions, the Library options, and the visibility patterns. Describe your app, and it generates the files.
Start Building
MCP changed how AI models call your code. MCP Apps change what those calls can return.
Instead of forcing structured data through a text-shaped hole, you can ship a real UI that's scrollable, filterable, interactive, and have it live inside the conversation.
Read the full MCP Apps documentation or clone Locket to see two working examples.
