Add CSP and CORS section to patterns docs

Adds a new section to the patterns guide explaining how to configure
Content Security Policy and CORS for MCP Apps that make network requests:

- CSP via `_meta.ui.csp` controls browser-side permissions (`connectDomains`,
  `resourceDomains`)
- CORS via `_meta.ui.domain` provides a stable origin for API servers to
  allowlist

Includes a type-checked example showing `computeAppDomainForClaude()` helper
that hashes an MCP server URL to create a stable `*.claudemcpcontent.com`
subdomain. Also adds `@see` links to `McpUiResourceMeta` and `McpUiResourceCsp`
in `registerAppResource` JSDoc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-30 13:50:53 -06:00
commit 13b6d06399
No known key found for this signature in database
GPG key ID: 0DA40AFD21043691

View file

@ -296,6 +296,65 @@ videoEl.src = `data:${content.mimeType!};base64,${content.blob}`;
> [!NOTE]
> For a full example that implements this pattern, see: [`examples/video-resource-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server).
## Configuring CSP and CORS
Unlike regular web apps, MCP Apps HTML is served as an MCP resource and runs in a sandboxed iframe with no same-origin server. Any app that makes network requests must configure Content Security Policy (CSP) and possibly CORS.
**CSP** controls what the _browser_ allows. You must declare _all_ origins in {@link types!McpUiResourceMeta.csp `_meta.ui.csp`} ({@link types!McpUiResourceCsp `McpUiResourceCsp`}) — including `localhost` during development. Declare `connectDomains` for fetch/XHR/WebSocket requests and `resourceDomains` for scripts, stylesheets, images, and fonts.
**CORS** controls what the _API server_ allows. Public APIs that respond with `Access-Control-Allow-Origin: *` or use API key authentication work without CORS configuration. For APIs that allowlist specific origins, use {@link types!McpUiResourceMeta.domain `_meta.ui.domain`} to give the app a stable origin that the API server can allowlist. The format is host-specific, so check each host's documentation for its supported format.
<!-- prettier-ignore -->
```ts source="../src/server/index.examples.ts#registerAppResource_withDomain"
// Computes a stable origin from an MCP server URL for hosting in Claude.
function computeAppDomainForClaude(mcpServerUrl: string): string {
const hash = crypto
.createHash("sha256")
.update(mcpServerUrl)
.digest("hex")
.slice(0, 32);
return `${hash}.claudemcpcontent.com`;
}
const APP_DOMAIN = computeAppDomainForClaude("https://example.com/mcp");
registerAppResource(
server,
"Company Dashboard",
"ui://dashboard/view.html",
{
description: "Internal dashboard with company data",
},
async () => ({
contents: [
{
uri: "ui://dashboard/view.html",
mimeType: RESOURCE_MIME_TYPE,
text: dashboardHtml,
_meta: {
ui: {
// CSP: tell browser the app is allowed to make requests
csp: {
connectDomains: ["https://api.example.com"],
},
// CORS: give app a stable origin for the API server to allowlist
//
// (Public APIs that use `Access-Control-Allow-Origin: *` or API
// key auth don't need this.)
domain: APP_DOMAIN,
},
},
},
],
}),
);
```
Note that `_meta.ui.csp` and `_meta.ui.domain` are set in the `contents[]` objects returned by the resource read callback, not in `registerAppResource()`'s config object.
> [!NOTE]
> For full examples that configures CSP, see: [`examples/sheet-music-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) (`connectDomains`) and [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) (`connectDomains` and `resourceDomains`).
## Adapting to host context (theme, styling, fonts, and safe areas)
The host provides context about its environment via {@link types!McpUiHostContext `McpUiHostContext`}. Use this to adapt your app's appearance and layout: