Skip to content
GitHub

MCP Apps in the Web SDK

Show the interactive views your own MCP servers return (MCP Apps) inside the Runbear chat widget, what you need to turn them on, what a view can do, and the limits.


On this page

An MCP App is an interactive view that an MCP server returns with a tool result, as defined by the MCP Apps extension of the Model Context Protocol. When the agent calls a tool that has a view, the widget shows that view under the reply: a dashboard, a form, a map, whatever the server built. The visitor uses it in place, without another turn.

Use it when an MCP server you connect already ships views, or when the visitor should explore what a tool returned rather than read a summary of it.

What you need#

  • MCP Apps enabled for your organization.
  • An agent on the Anthropic runtime. Claude Agent SDK agents don't show views yet, and an agent in HIPAA data handling never does.
  • A Custom MCP server on Streamable HTTP or SSE, whose tools declare a view. Its authentication must be Static or None. Views from OAuth connections, shared or per-user, are not shown yet.
  • Web SDK 0.7.0 or later.

Set up#

The widget needs no new option: from 0.7.0 it renders views on its own. Check two things on your side:

  1. If your page sends a Content-Security-Policy that restricts frames, add https://runbear-mcp-app-sandbox.runbear.workers.dev to frame-src. That is the sandbox Runbear hosts, where every view runs.
  2. In proxy mode, forward the two MCP Apps routes listed under Proxy mode.

config.mcpApps.sandboxUrl points the widget at another sandbox. You only need it to test one of your own. A sandbox on the same origin as your page is refused, and each view then shows as its text: Interactive view: <tool name>.

How a view behaves#

Where it shows. The view appears under the reply as soon as the tool returns, so it can arrive while the agent is still writing. It starts 320 px tall and grows with its content, up to 2,000 px.

What it can reach. The view runs in a sandboxed iframe with no origin of its own, inside a sandbox page on another domain. It can't read your page, its cookies or the visitor's session, and it can't use cookies or localStorage itself. It receives the tool's input and result, and it can load from or connect to only the domains its MCP server declares for it.

Calling tools. A view can call tools on its own MCP server: a tool whose declared visibility includes app, or a tool that has a view of its own and declares no visibility. A tool declared visible to the model only can't be called from a view. The call runs on Runbear's servers over the agent's connection to that server, and the arguments are checked against the tool's input schema. It doesn't start an agent turn and doesn't reach the model. A tool whose visibility is app alone is hidden from the model.

Sending a message. A view can post text as the visitor's next message, exactly as if the visitor had typed it, so it costs a turn. It can send at most one message a second, and only while the widget is idle, not while a reply is streaming. Unlike a component's options, which go inert once answered, a view keeps sending after later messages, so a visitor can keep working in it.

Telling the model what it shows. A view can report its current state, for example the filters the visitor set. The widget keeps the latest report of each view and sends the reports of the four most recently updated views with the visitor's next message. The model reads it for that turn only, as data from the view, not as instructions. It isn't stored in the conversation, so after a reload the model sees it again only once the view reports again.

Links. A view can open an http or https link. It opens in a new tab.

After a reload. Views come back with the conversation. The widget loads the view from the MCP server again and hands it the original tool input and result, so the view's own code is the server's current version.

When a view can't load. If the MCP server can't be reached, needs to be reconnected, returns a view over the size limit, or the view doesn't start within 15 seconds, the widget shows the component's text instead. Saving the MCP server connection's settings, such as its URL or its key, has the same effect on every view shown before the save: a view only ever reaches the connection exactly as it was when the view was shown.

To stop this widget rendering views, add "mcp-app" to config.components.disabled. The agent still calls the tool.

Where views don't appear#

Views appear only in conversations through the Web SDK and the public API. In Slack, Teams and every other channel, and in scheduled runs, the agent calls the same tool and reads its result, but no view is produced. A client that doesn't read components sees Interactive view: <tool name> at the end of the reply instead, like any other unrecognized component.

The agent's response component settings don't govern views: the Component frequency dial and the component switches never turn them on or off. A view does count toward the four components a message can carry.

Limits#

WhatLimit
Views per messageAt most 4, counted together with every other response component
Tool input and result carried by the view65,536 characters of JSON together. A larger result produces no view
View HTML5 MB
Arguments of a tool call from a view65,536 characters of JSON
Result of a tool call from a view1,048,576 characters of JSON
Views reported to the model4 per message, the most recently updated
One view's report16,384 characters of JSON and 20 content blocks. A larger report is refused to the view
Messages a view sends1 per second
View loads and tool callsThe same per-agent window as chat requests, counted separately from them. Over it, a 429 with Retry-After
Time for a view to start15 seconds
View height2,000 px

An agent with PreToolUse or PostToolUse hooks still shows views, but a view can't call tools on it: those calls are refused with 409 mcp_app_tool_hooks_unsupported, because they would skip the hooks.

On the API#

If you render the API directly rather than through the SDK, a view reaches you as a component named mcp-app (see Components on the API). Its props carry integrationId, connectionVersion, toolName, resourceUri, toolInput and toolResult. Load and run it with two endpoints, both accepted with an API key or with the conversation's session pass:

EndpointBodyReturns
POST /v1/threads/{threadId}/mcp-apps/resources/read{ integrationId, connectionVersion, resourceUri }{ resourceUri, mimeType, html, meta }, the view's HTML and its _meta
POST /v1/threads/{threadId}/mcp-apps/tools/call{ integrationId, connectionVersion, name, arguments }The MCP CallToolResult, unchanged

Render html only inside a sandboxed iframe on an origin other than your page's, as the MCP Apps specification requires. To tell the model what the views show, send their latest reports on the next run as config.mcpAppContexts: up to 4 entries of { componentId, toolName, content?, structuredContent? }, with at most 20 blocks in content. A run with more is refused with 400.

A 404 covers a thread that doesn't exist, worded like any other missing thread. It also covers a connection, view or tool that doesn't exist, a connection that never showed a view in this thread or whose settings were saved since, and an organization without MCP Apps, with the code mcp_app_not_found; those cases are deliberately indistinguishable. So both endpoints serve only an MCP server whose view this conversation actually showed. A 409 carries one of these codes:

CodeMeaning
mcp_app_auth_requiredThe MCP server needs to be reconnected
mcp_app_unsupported_connectionThe connection uses OAuth
mcp_app_too_largeThe view's HTML is over 5 MB
mcp_app_result_too_largeThe tool result is over the limit
mcp_app_tool_hooks_unsupportedThe agent has tool hooks
mcp_app_server_unavailableThe MCP server couldn't be reached
mcp_app_server_errorThe MCP server failed the request

A 403 with organization_suspended means the organization is suspended; no view request runs until it is reinstated. A 400 is invalid_resource_uri for a URI that isn't ui://, arguments_too_large, or invalid_arguments when the tool's input schema rejects the arguments. Full schemas are in the OpenAPI reference.