# MCP Apps Through Executor Gateway
Executor can already act as a gateway to downstream MCP servers for tools. MCP
Apps support should preserve that same gateway property: add Executor once, and
UI-capable tools from downstream MCPs should keep working through the Executor
MCP server.
This note is about the Executor work, not the GitHub issue demo app.
## Goals
- Proxy downstream MCP Apps without degrading the direct MCP server experience.
- Make UI-capable tools discoverable through Executor's normal MCP surface.
- Support app template/resource loading through `resources/list`,
`resources/templates/list`, and `resources/read`.
- Keep tool invocation and resource loading consistent across direct MCP,
Executor gateway MCP, and future generated UI.
- Leave room for generated React UIs to embed existing MCP Apps as components.
## Terms
MCP Apps are not a separate top-level MCP object. In practice they are
UI-capable tools: a tool descriptor includes metadata pointing to a UI resource,
and the client reads that resource to render the app.
There are two compatibility tracks to care about:
- MCP Apps / ext-apps metadata: `_meta.ui.resourceUri`, often flattened as
`_meta["ui/resourceUri"]`, with a `ui://...` resource whose MIME type is
commonly `text/html;profile=mcp-app`.
- ChatGPT Apps SDK / Skybridge metadata: `_meta["openai/outputTemplate"]`,
usually pointing to a `ui://widget/...` HTML resource with
`text/html+skybridge`.
Executor should preserve both when present. It should not assume all hosts have
converged on one metadata key or MIME type yet.
## Resource Gateway
Executor needs a generic resource surface in addition to the current tool
surface.
Suggested executor-side APIs:
```ts
executor.resources.list({ source?: string, cursor?: string })
executor.resources.templates.list({ source?: string, cursor?: string })
executor.resources.read({ uri: string })
```
The MCP plugin should implement these by forwarding to downstream MCP
`resources/list`, `resources/templates/list`, and `resources/read`.
The host MCP server should expose the same through protocol handlers:
```txt
resources/list
resources/templates/list
resources/read
notifications/resources/list_changed
notifications/resources/updated
```
Resource reads must not go through tool calls. The app renderer expects a
resource read path that can fetch the template URI returned in tool metadata.
## URI Proxying
Executor can prefix downstream resource URIs and strip that prefix when it
forwards a request to the source MCP server.
Example:
```txt
downstream: ui://app/show_github_issue.html
executor: executor+mcp://{sourceId}/ui/app/show_github_issue.html
```
or:
```txt
downstream: ui://app/show_github_issue.html
executor: ui://executor/{sourceId}/app/show_github_issue.html
```
The first form is less likely to collide with app code that has assumptions
about the `ui://` authority/path shape. The second form may be friendlier to
hosts that only recognize `ui://`.
The important invariant is:
- every resource URI exposed by Executor must be globally unique inside the
gateway MCP server
- Executor must maintain a reversible mapping back to `{ sourceId, uri }`
- tool metadata, tool results, resource listings, and resource contents should
all use the same rewritten URI
## Response Body Rewriting
URI rewriting in metadata is not enough. HTML and JavaScript inside app
templates may contain baked-in references to their own `ui://...` resources or
may call host APIs with those URIs.
Executor probably needs a conservative response-rewrite layer for proxied app
resources:
- rewrite exact downstream resource URIs inside text resources
- rewrite known metadata keys in embedded JSON
- avoid broad string replacement that can corrupt unrelated content
- do not rewrite binary resources unless we add a format-specific rewriter
This is especially relevant for app HTML and JS. A downstream app might call
`window.openai.readResource("ui://...")`, use an MCP Apps host bridge resource
read method, or import a sibling resource by URI. If Executor has exposed the
app under a prefixed URI, those calls must use the prefixed form at the host
boundary and the stripped form at the downstream boundary.
## Iframe Shape
Gateway mode should not introduce a double app iframe.
The expected shape is:
```txt
ChatGPT / host
iframe for downstream app template served through Executor
```
Executor should own the MCP transport/resource proxying, not wrap every
downstream app in an Executor app shell. A shell iframe is appropriate for
Executor's own generated UI feature, but direct downstream MCP Apps should render
as if the client connected to the downstream MCP server directly.
## Host Bridge Interception
The `client` or `window.openai` object used by the app belongs to the host/app
iframe runtime, not the agent model. Executor can influence it only through:
- the tool descriptor metadata it exposes
- the resource body it serves
- the resource URI mapping it applies
- any app shell it intentionally provides for Executor-owned generated UI
For proxied downstream apps, avoid injecting a custom client unless required for
compatibility. Injection creates a new compatibility surface and risks changing
how existing apps behave.
For Executor-owned generated UI, a shell can provide controlled APIs:
```ts
tools.github.issues.create(...)
tools.run(...)
UI.SomeDownstreamApp(...)
```
That is a separate feature from transparent gatewaying.
## Generated UI Integration
The open generative UI PR adds an Executor app shell that can render React
generated by the model and proxy tool calls back through Executor. That shell is
the right place to experiment with treating UI-capable tools as components.
Possible future convention:
```tsx
```
The generated UI shell would:
- resolve `UI.*` to a UI-capable tool
- call the tool or reuse supplied tool output
- load the tool's app template resource
- mount the downstream MCP App within a component boundary
This should be built on top of the same resource gateway. Generated UI should
not need a bespoke resource loader that bypasses Executor's MCP resource proxy.
## Compatibility Notes From Spike
- ChatGPT normalized a tool call to `show_github_issue`; an xmcp tool registered
as `show-github-issue` failed with `Tool show_github_issue not found`.
Gateway code should avoid changing tool names after discovery, and demo/tools
intended for ChatGPT should prefer identifier-safe names.
- ChatGPT developer mode showed the widget template metadata separately from
tool response metadata. Both descriptor metadata and tool-result metadata
matter.
- Adding `_meta["openai/outputTemplate"]` alongside `_meta["ui/resourceUri"]`
improved host compatibility.
- A host may say "Failed to fetch template" even when `tools/call` succeeds.
Debug that as a resource-read/template metadata problem, not as a tool
invocation problem.
## Open Questions
- Which URI prefix shape should Executor standardize on for proxied resources?
- Should Executor expose both original and rewritten URI in hidden metadata for
debugging?
- Do we need resource body rewriting in v1, or can v1 document that apps must
use host-provided current template state rather than hard-coded sibling URIs?
- How do we handle resource subscriptions through a gateway?
- Should `tool.search` include "has UI" / "template URI" hints so agents can
intentionally choose UI-capable tools?
- Should generated UI expose MCP Apps as components directly, or only via a
generic `` primitive at first?
## References
- MCP resources specification:
https://modelcontextprotocol.io/specification/draft/server/resources
- MCP schema reference for `resources/list` and `resources/read`:
https://modelcontextprotocol.io/specification/2025-06-18/schema
- MCP Apps `RESOURCE_URI_META_KEY` docs:
https://apps.extensions.modelcontextprotocol.io/api/variables/app.RESOURCE_URI_META_KEY.html
- xmcp resource docs:
https://xmcp.dev/docs/core-concepts/resources
- xmcp MCP UI integration docs:
https://xmcp.dev/docs/integrations/mcp-ui
- xmcp MCP Apps metadata docs:
https://xmcp.dev/docs/core-concepts/tools#mcp-apps-metadata
- OpenAI Apps SDK / Skybridge compatibility docs:
https://docs.skybridge.tech/fundamentals/apps-sdk
- Skybridge `registerWidget` API:
https://docs.skybridge.tech/api-reference/register-widget
- Executor generative UI PR:
https://github.com/RhysSullivan/executor/pull/263