Skip to main content
Views render in a sandboxed iframe under a strict Content Security Policy: it blocks every cross-origin request the view hasn’t declared. Your server’s own origin is allowed automatically, so a view that only talks to its server needs no configuration. For anything else, list the origin on the tool’s config view.csp.
server.ts
server.registerTool(
  {
    name: "search-products",
    inputSchema: { query: z.string() },
    view: {
      component: "carousel",
      csp: {
        connectDomains: ["https://api.myshop.com"],
        resourceDomains: ["https://cdn.myshop.com"],
        frameDomains: ["https://www.youtube.com"],
        redirectDomains: ["https://checkout.myshop.com"],
      },
    },
  },
  async ({ query }) => {
    /* … */
  },
);
Each field maps to one kind of request the iframe would otherwise block. The sections below cover the four you’ll reach for, what’s allowed without any config, and how the policy differs per host.

Call an External API

connectDomains lists the origins the view may reach with fetch or XHR. Without it, a direct request from the view is blocked.
server.ts
server.registerTool(
  {
    name: "search-products",
    inputSchema: { query: z.string() },
    view: {
      component: "carousel",
      csp: {
        connectDomains: ["https://api.myshop.com"],
        resourceDomains: ["https://cdn.myshop.com"],
        frameDomains: ["https://www.youtube.com"],
        redirectDomains: ["https://checkout.myshop.com"],
      },
    },
  },
  async ({ query }) => {
    /* … */
  },
);
The carousel checks live stock from api.myshop.com, so that origin is listed.
Your server’s origin is already in connectDomains, so same-origin requests need no entry. In development, the dev server’s WebSocket origin is added too, for hot reload.

Load External Assets

resourceDomains covers the static assets the browser loads from another origin: images, fonts, scripts, and stylesheets, whether from a CDN, a fonts provider, or any other host.
server.ts
server.registerTool(
  {
    name: "search-products",
    inputSchema: { query: z.string() },
    view: {
      component: "carousel",
      csp: {
        connectDomains: ["https://api.myshop.com"],
        resourceDomains: ["https://cdn.myshop.com"],
        frameDomains: ["https://www.youtube.com"],
        redirectDomains: ["https://checkout.myshop.com"],
      },
    },
  },
  async ({ query }) => {
    /* … */
  },
);
The carousel’s product images come from cdn.myshop.com, so that origin has to be listed or they won’t render.
Your server’s origin is already in resourceDomains, so assets served from it need no entry.

Embed an Iframe

frameDomains lists the origins the view may embed in a nested iframe, like a map or a video player. Third-party iframes opt your app into stricter review when you submit it to a directory.
server.ts
server.registerTool(
  {
    name: "search-products",
    inputSchema: { query: z.string() },
    view: {
      component: "carousel",
      csp: {
        connectDomains: ["https://api.myshop.com"],
        resourceDomains: ["https://cdn.myshop.com"],
        frameDomains: ["https://www.youtube.com"],
        redirectDomains: ["https://checkout.myshop.com"],
      },
    },
  },
  async ({ query }) => {
    /* … */
  },
);
The carousel embeds a product video from www.youtube.com, so that origin is listed.

Redirect Off-App

redirectDomains lists the origins useOpenExternal can send the user to without the host’s safe-link confirmation modal.
server.ts
server.registerTool(
  {
    name: "search-products",
    inputSchema: { query: z.string() },
    view: {
      component: "carousel",
      csp: {
        connectDomains: ["https://api.myshop.com"],
        resourceDomains: ["https://cdn.myshop.com"],
        frameDomains: ["https://www.youtube.com"],
        redirectDomains: ["https://checkout.myshop.com"],
      },
    },
  },
  async ({ query }) => {
    /* … */
  },
);
The carousel hands off to checkout.myshop.com to take payment; listing it skips the interstitial on the way out.

Verify Your CSP

To check your CSP, run your app in a real host through the tunnel, or let the Audit verify it before you submit.
DevTools runs a loose policy, so CSP violations don’t surface locally: a view that loads in the emulator can still have its requests blocked in production.
For the full field reference, see registerTool.

Go Further

Create Views

Craft interactive UIs rendered in conversation

UX Design

Account for what makes an MCP App different

Audit

Validate before submission