Skip to main content
MCP Apps SDK

Overview

Every MCP App tool uses _meta.ui to link the tool to an HTML View and control who can access it. This metadata is set in the tool config passed to registerAppTool.
registerAppTool(server, "get-weather", {
  description: "Get current weather for a location",
  _meta: {
    ui: {
      resourceUri: "ui://weather/view.html",
      visibility: ["model", "app"],
    },
  },
}, handler);

Fields

_meta.ui.resourceUri

resourceUri
string
URI of the HTML resource to render when this tool is called. Uses the ui:// protocol.
_meta: { ui: { resourceUri: "ui://weather/view.html" } }
Must match a URI registered via registerAppResource. When the host invokes this tool, it calls resources/read with this URI to fetch the HTML content, then renders it in a sandboxed iframe.

_meta.ui.visibility

visibility
McpUiToolVisibility[]
default:"[\"model\", \"app\"]"
Who can discover and call this tool. An array containing "model", "app", or both.
ValueMeaning
"model"Tool is included in the agent’s tool list and callable by the LLM
"app"Tool is callable by the View (your UI) via app.callServerTool() on the same server connection
Default: ["model", "app"] — both model and app can access.

Visibility Examples

Model + App (default)

Both the LLM and the View can call this tool. This is the default when visibility is omitted.
registerAppTool(server, "show-cart", {
  description: "Display the user's shopping cart",
  _meta: {
    ui: { resourceUri: "ui://shop/cart.html" },
  },
}, handler);

App-only (hidden from model)

The View can call this tool, but it’s hidden from the agent’s tool list. Use this for UI-driven server interactions — polling, pagination, form submissions, or any action the user triggers directly in the View.
registerAppTool(server, "update-quantity", {
  description: "Update item quantity in cart",
  inputSchema: { itemId: z.string(), quantity: z.number() },
  _meta: {
    ui: {
      resourceUri: "ui://shop/cart.html",
      visibility: ["app"],
    },
  },
}, async ({ itemId, quantity }) => {
  const cart = await updateCartItem(itemId, quantity);
  return { content: [{ type: "text", text: JSON.stringify(cart) }] };
});

Model-only

The model can call this tool, but the View cannot. Use this when the tool should only be triggered by the LLM in conversation, never by the UI directly.
registerAppTool(server, "show-cart", {
  description: "Display the user's shopping cart",
  _meta: {
    ui: {
      resourceUri: "ui://shop/cart.html",
      visibility: ["model"],
    },
  },
}, handler);

Host Behavior

Hosts enforce visibility rules:
  • The host MUST NOT include tools with visibility: ["app"] in the agent’s tool list.
  • The host MUST reject tools/call requests from Views for tools that don’t include "app" in visibility.
  • Cross-server tool calls are always blocked for app-only tools — a View can only call app-visible tools on its own MCP server.

Metadata Normalization

registerAppTool automatically normalizes between the current format (_meta.ui.resourceUri) and the deprecated flat key (_meta["ui/resourceUri"]) for backward compatibility with older hosts. You don’t need to set both.

TypeScript Types

import type { McpUiToolMeta, McpUiToolVisibility } from "@modelcontextprotocol/ext-apps";

type McpUiToolVisibility = "model" | "app";

interface McpUiToolMeta {
  resourceUri?: string;
  visibility?: McpUiToolVisibility[];
}
The sunpeak framework provides useAppTools for calling app-visible tools from React components, and the Tool File Reference for defining tool metadata.