Skip to main content
MCP Apps SDK An MCP App View runs inside a host iframe. The View does not talk directly to the MCP server or the chat UI. It uses the SDK App instance, and the host decides which requests it supports. Use this guide when you are choosing which client API to call from the View.

Quick Choice

What the View needs to doUseWhy
Fetch fresh server data or run a server actioncallServerTool()The host proxies a normal tools/call to the originating MCP server.
Load static or binary server contentreadServerResource()Resources are better for files, templates, media, and named content.
Discover server resourceslistServerResources()Use it before showing a picker for server-registered resources.
Share UI state with the next model turnupdateModelContext()It updates context without adding a chat message or starting a response.
Ask the host to continue the conversation nowsendMessage()It adds a user message and may trigger model work.
Ask for a local model completion from the ViewcreateSamplingMessage()Use it for View-local drafting, labeling, or summarization when the host supports sampling.
Let the host call into current UI stateApp-side toolsUse this when the answer lives in the DOM, form, canvas, or local View state.
Open a browser pageopenLink()Host-mediated link opening works from sandboxed iframes.
Save a generated filedownloadFile()Host-mediated download avoids iframe download restrictions.
Change presentation sizerequestDisplayMode()The host can approve inline, fullscreen, or PiP mode based on support.
Report diagnosticssendLog()Logs go to the host’s developer surface, not the conversation.
Ask to close the ViewrequestTeardown()The host may then send ui/resource-teardown before unmounting.

Check Host Capabilities First

Hosts may implement only part of the MCP Apps API. Read capabilities after app.connect(), then hide or disable UI that depends on missing host support.
await app.connect();

const caps = app.getHostCapabilities();

const canCallTools = Boolean(caps?.serverTools);
const canReadResources = Boolean(caps?.serverResources);
const canSendMessages = Boolean(caps?.message?.text);
const canShareTextContext = Boolean(caps?.updateModelContext?.text);
const canUseSampling = Boolean(caps?.sampling);
const canDownload = Boolean(caps?.downloadFile);
Use modality fields when sending content. For example, message.text does not mean the host accepts images in sendMessage(), and updateModelContext.image does not mean it accepts structured content.

Server Tool or Server Resource?

Use a server tool when the View needs computation, authorization, mutation, search, filtering, pagination, or fresh data:
const result = await app.callServerTool({
  name: 'search-orders',
  arguments: { status: 'open', page: 2 },
});

if (!result.isError) {
  renderOrders(result.structuredContent);
}
Use a server resource when the View needs named content that exists apart from an action, such as a PDF, image, transcript, saved report, or static config:
const resource = await app.readServerResource({
  uri: 'reports://quarterly-summary.pdf',
});

const first = resource.contents[0];
if (first && 'blob' in first) {
  const bytes = Uint8Array.from(atob(first.blob), (char) => char.charCodeAt(0));
  showPdf(new Blob([bytes], { type: first.mimeType }));
}
If a button mutates server state, make it an app-visible tool and call it with callServerTool(). If a menu lists files, call listServerResources() and then readServerResource().

Model Context or Chat Message?

Use updateModelContext() when the model should know what the user is viewing next time, but should not respond yet.
await app.updateModelContext({
  content: [
    {
      type: 'text',
      text: `The user selected order ${selectedOrder.id} with status ${selectedOrder.status}.`,
    },
  ],
  structuredContent: { selectedOrderId: selectedOrder.id },
});
Use sendMessage() when a user action should continue the conversation immediately.
await app.sendMessage({
  role: 'user',
  content: [{ type: 'text', text: 'Draft a reply for the selected order.' }],
});
For large state, update model context first, then send a short message. This keeps the user-visible chat clean while still giving the model the facts it needs.

Sampling or Conversation?

Use createSamplingMessage() for work that belongs inside the View, such as generating a short label, grouping visible rows, or summarizing a selected passage before the user commits it.
if (app.getHostCapabilities()?.sampling) {
  const result = await app.createSamplingMessage({
    messages: [
      {
        role: 'user',
        content: { type: 'text', text: 'Write a short label for these selected rows.' },
      },
    ],
    maxTokens: 80,
  });

  applySuggestedLabel(result.content);
}
Use sendMessage() when the output should become part of the chat transcript or when the model should decide which server tools to call next.

App-side Tool or Server Tool?

Use an app-side tool when the host needs information that only exists inside the rendered View.
app.registerTool(
  'get-selected-order',
  {
    description: 'Return the order currently selected in the View.',
    inputSchema: z.object({}),
  },
  () => ({
    content: [{ type: 'text', text: JSON.stringify(selectedOrder) }],
    structuredContent: selectedOrder,
  })
);
Use a server tool when the action needs backend data, credentials, audit logging, writes, or any source of truth outside the iframe. App-side tools can read UI state, but they should not become a hidden backend.

Errors and Fallbacks

Tool execution errors come back as results, so check isError. Protocol, transport, and host rejection failures may throw.
try {
  const result = await app.callServerTool({
    name: 'save-order-note',
    arguments: { orderId, note },
  });

  if (result.isError) {
    showError(result.content);
    return;
  }

  showSaved();
} catch (error) {
  showError('The host could not reach the server.');
}
For host-mediated actions such as links, messages, and downloads, also check returned isError when the method returns it.
const result = await app.openLink({ url: docsUrl });
if (result.isError) {
  showError('The host blocked this link.');
}

Requests

Full method signatures for View-to-host requests.

Event Handlers

Incoming notifications and host-to-View requests.

App Tools

Expose View-local tools to the host and model.

Core Types

Host capabilities, context, metadata, display modes, and content types.