modelcontextprotocol / modelcontextprotocol/ext-apps

Sandbox proxy does not apply _meta.ui.csp from the UI resource, so resourceDomains has no effect

Open
#761 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.9k
Forks
387
Avg merge
3h 21m
Merged PRs (30d)
6

Description

A server that declares _meta.ui.csp.resourceDomains on its ui:// resource still cannot load images from those origins in claude.ai. The declaration appears to be dropped rather than evaluated, so every external image in the view renders as a broken-image glyph.

What the spec says

McpUiResourceCsp.resourceDomains is documented as mapping to img-src (among others), and McpUiSandboxProxyReadyNotification's sibling ui/notifications/sandbox-resource-ready defines a csp field on its params:

params: {
html: string;
sandbox?: string;
csp?: McpUiResourceCsp; // <- defined
permissions?: McpUiResourcePermissions;
}
registerAppResource's docs also say a resources/read content item's _meta.ui takes precedence over the listing-level value, implying both are read.

What we declared

On both the resource registration metadata and the resources/read result:

{ "_meta": { "ui": { "csp": { "resourceDomains": [
"https://storage.googleapis.com",
"https://firebasestorage.googleapis.com"
] } } } }
Verified on the wire by driving our published stdio server directly: both resources/list and the resources/read result carry it.

What we observed

The sandbox proxy document at https://.claudemcpcontent.com/mcp_apps?resource-src=... is served with a fixed response header:

content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data;
style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; ...
No server-declared origins appear in it.

The proxy builds the view's frame with document.write into an about:blank iframe, with an explanatory comment ("Use document.write() instead of srcdoc to preserve origin"). A document created that way inherits the creator's policy container, so the view runs under the same img-src.

The proxy's sandbox-resource-ready handler reads only two of the four defined params:

const {html, permissions} = data.params;
The string csp does not appear anywhere in that page, and neither does resourceDomains.

Reproduction of the effect

Serving a page with that exact header, building an about:blank iframe the same way, and writing one data: image and one https://storage.googleapis.com image into it:

data: image: renders, naturalWidth 1
https: image: blocked, naturalWidth 0
Console: Loading the image '' violates the following Content Security Policy directive: "img-src 'self' data: blob:". The action has been blocked.
Why this reading rather than "the host rejected our list"

Those two call for different fixes on our side. A rejection would mean our declaration is malformed. The absent handling in the proxy, plus a fixed header with no server-supplied origins, points at the field not being consumed at all.

We could be wrong about the mechanism: a text search over one bundle is not proof, the handling could live in another module, and minification can rename things. The blocked image and the console message are the observations; the rest is inference.

Impact

Any MCP App view that displays user content hosted outside the host's own origins cannot display it. The only workaround is inlining bytes as data: URIs in the tool result, which puts image data into the model's context window on every call and scales with the user's data.

Environment

@modelcontextprotocol/ext-apps 1.7.5
@modelcontextprotocol/sdk 1.30.0
claude.ai web, observed 2026-08-27
Resource mime text/html;profile=mcp-app
What would help

Either apply the declared resourceDomains to the inner frame's policy, or document that the field is not yet implemented on this host so servers do not ship a declaration that reads as a working guarantee.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the external-image failure in claude.ai and tracing the sandbox-resource-ready handler described in the report, especially its handling of html, permissions, and csp. Compare the fixed proxy Content-Security-Policy with the declared resourceDomains; done means those origins affect the inner frame's image policy, or the unsupported behavior is documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.