Production integration
registerMcpApps() is the production seam. It adds MCP Apps resources, linked
tools, capability negotiation, and text fallback to an existing, unconnected
McpServer.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { readMcpAppManifest } from "mountly-mcp/artifact";import { registerMcpApps } from "mountly-mcp/server";
const server = new McpServer( { name: "weather", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } },);const { artifacts } = await readMcpAppManifest("dist/mountly-mcp.manifest.json");
const apps = await registerMcpApps(server, { views: artifacts.map((artifact) => ({ artifact })), tools: [ { name: "get_weather", resourceUri: "ui://weather/dashboard", config: { description: "Show current weather", inputSchema: { type: "object", properties: { location: { type: "string" } }, required: ["location"], }, }, handler: async ({ location }: { location: string }) => ({ structuredContent: { location, temperature: 22 }, }), }, ],});Call this before server.connect(). You keep auth, transport, prompts,
ordinary tools, shutdown, telemetry, scaling, and deploy.
ChatGPT extensions
Section titled “ChatGPT extensions”pnpm add @openai/mcp-extensionsimport { enableOpenAiExtensions, openaiUiToolMeta,} from "mountly-mcp/openai/server";
enableOpenAiExtensions(server);// tool config._meta: { ...openaiUiToolMeta({ entrypoints: [{ type: "global" }] }) }Pass openaiUi on the Vite View. In the View:
import { useOpenAIExtensions } from "mountly-mcp/react/openai".
Serve HTTPS Streamable HTTP at /mcp for ChatGPT Developer mode.
Existing server (beside-package)
Section titled “Existing server (beside-package)”Keep the server package. Add a View app beside it (or under views/):
npx mountly-mcp create weather-views --framework react(or wire Vite by hand).- Point
mountlyMcpViewsURIs at your namespace (ui://weather/…). - Build →
dist/mountly-mcp.manifest.json. - In the server module:
readMcpAppManifest+registerMcpAppsbeforeconnect. - Leave ordinary
server.tool(...)registrations alone. - Develop with
npx mountly-mcp dev --server ./path-to-unconnected-factory.mjs.
Default-export an unconnected McpServer (or a factory that returns one).
Connected exports fail with mountly-mcp/bad-server-export.
Skill: add-app-to-server.
Views and tools are independent
Section titled “Views and tools are independent”A View is declared once and tools link to it with resourceUri. This supports:
- several tools opening the same View;
- a View with model-visible and app-only tools;
- a resource with no tool yet;
- independently replacing tool handlers and View artifacts.
Registration rejects a tool whose URI does not match a declared View, and rejects duplicate View names or URIs before the server connects.
Tool visibility
Section titled “Tool visibility”By default, a linked tool is available to both the model and the View. Use a visibility override for UI-only actions:
{ name: "refresh_weather", resourceUri: "ui://weather/dashboard", visibility: "app", handler: async ({ location }) => ({ structuredContent: await refreshWeather(location), }),}visibility accepts one value or an array. App-only tools are callable from
the View but are not exposed to the model. On clients without MCP Apps support,
Mountly removes app-only tools and UI resources while preserving model-visible
tools without their UI metadata.
Schemas and metadata
Section titled “Schemas and metadata”config.inputSchema and config.outputSchema accept documented JSON Schema as
well as schema-library values supported by the MCP SDK. Other official tool
configuration is passed through. Existing _meta is preserved; Mountly merges
the official _meta.ui.resourceUri and visibility fields it owns.
Handlers may return content, structuredContent, and isError. If
structuredContent exists and content does not, Mountly adds a JSON text
block for model and text-only compatibility.
Remove an installation
Section titled “Remove an installation”The return value owns exactly what that call installed:
apps.remove();This removes every View resource and tool from the registration, which is useful in tests and controlled reconfiguration.
Cloudflare Workers
Section titled “Cloudflare Workers”A Worker has no filesystem, so register the Views from the
views module
instead of from paths. mountly-mcp/server resolves to a Workers build through
the workerd and worker export conditions, so it needs no nodejs_compat.
Pair it with the MCP SDK’s web-standard transport:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";import { registerMcpApps } from "mountly-mcp/server";import views from "./dist/mountly-mcp.views.js";
export default { async fetch(request: Request): Promise<Response> { const server = new McpServer({ name: "tools", version: "1.0.0" }); await registerMcpApps(server, { views: views.map((artifact) => ({ artifact })), tools: [{ name: "lookup", resourceUri: "ui://tools/lookup", handler: lookup }], }); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); await server.connect(transport); return transport.handleRequest(request); },};The Workers build has no serveStdio. Registering a View by path there throws,
and the error says to use the views module.
Downloads
Section titled “Downloads”Most hosts block <a download> inside a View. useDownload() (React and Vue)
and downloadFile(app, file) from mountly-mcp ask a host that supports
ui/download-file to save the file, which the host confirms with the user.
Anywhere else they fall back to <a download>:
const download = useDownload();await download({ name: "matches.csv", data: csv, mimeType: "text/csv" });data is a string or a Blob. The promise resolves false when the host
declined or the user cancelled. mountly-mcp dev implements
ui/download-file, so the host path runs locally too.
Convenience server adapters
Section titled “Convenience server adapters”createMcpAppServer() and serveStdio() declare Views and tools with exactly
the same views and tools vocabulary: they only own the McpServer and,
for serveStdio(), the transport. Reach for them in examples and small
standalone processes; production systems should keep server and transport
ownership and install Mountly at the registration seam above.