From c00695ceb2d78e2d34c86a0843da423e71d7b867 Mon Sep 17 00:00:00 2001 From: Steven J Hatzakis Date: Mon, 2 Feb 2026 18:26:50 -0500 Subject: [PATCH] Add cross-host portability guide for MCP Apps This guide covers cross-host portability issues for MCP Apps, including endpoint requirements, metadata formats, size notifications, and debugging tips. --- docs/portability.md | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/portability.md diff --git a/docs/portability.md b/docs/portability.md new file mode 100644 index 00000000..9d52c2aa --- /dev/null +++ b/docs/portability.md @@ -0,0 +1,75 @@ +# Cross-Host Portability + +This guide covers issues that arise when running the same MCP App across multiple +hosts (Claude, ChatGPT, custom hosts). For migrating from OpenAI Apps SDK, see +the [Migration Guide](./migrate_from_openai_apps.md). + +## All Endpoints Need resources/* + +If your server exposes multiple MCP endpoints (authenticated routes, magic links, +proxies), **each one** must handle `resources/list` and `resources/read`—not +just the primary endpoint. + +Claude fetches widget HTML via `resources/read`. An endpoint that handles +`initialize`, `tools/list`, and `tools/call` but returns 404 for `resources/*` +will cause: + +``` +MCP error 32600: Session terminated +``` + +ChatGPT doesn't hit this because it uses `openai/outputTemplate` URLs directly. + +## Dual-Format Metadata + +To support both Claude and ChatGPT without separate codepaths, include both +metadata formats: + +```json +{ + "name": "get_chart", + "_meta": { + "ui": { + "resourceUri": "ui://widget/chart.html", + "csp": { "connectDomains": ["https://api.example.com"] } + } + }, + "openai/outputTemplate": "https://example.com/widgets/chart.html" +} +``` + +ChatGPT ignores `_meta.ui`; Claude ignores `openai/*`. Both get what they need. + +Note: MCP Apps CSP is an object; OpenAI CSP is a string. + +## Throttle Size Notifications + +Widgets sending `ui/notifications/size-changed` on every resize flood the console. +Debounce: + +```js +let timeout; +new ResizeObserver(() => { + clearTimeout(timeout); + timeout = setTimeout(() => { + app.notifySizeChanged({ height: document.body.scrollHeight }); + }, 100); +}).observe(document.body); +``` + +## Debugging + +| Symptom | Cause | +|---------|-------| +| Tools work, widgets blank | Endpoint missing `resources/read` | +| "MCP error 32600: Session terminated" | `resources/*` returned error | +| Works in ChatGPT, blank in Claude | Only `openai/*` metadata, no `_meta.ui` | +| Console spam with "size-changed" | Resize notifications not debounced | + +## Related Resources + +- [migrate_from_openai_apps.md](https://github.com/modelcontextprotocol/ext-apps/blob/main/docs/migrate_from_openai_apps.md) — OpenAI to MCP Apps migration +- [migrate-oai-app skill](https://github.com/modelcontextprotocol/ext-apps/tree/main/plugins/mcp-apps/skills/migrate-oai-app) — Agent-assisted migration +- [patterns.md](https://github.com/modelcontextprotocol/ext-apps/blob/main/docs/patterns.md) — Common MCP Apps patterns +- [testing-mcp-apps.md](https://github.com/modelcontextprotocol/ext-apps/blob/main/docs/testing-mcp-apps.md) — Testing with basic-host and real hosts +- [apps.mdx](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx) — Full protocol spec