add-app-to-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Add UI to MCP Server

为MCP服务器添加UI

Enrich an existing MCP server's tools with interactive UIs using the MCP Apps SDK (
@modelcontextprotocol/ext-apps
).
使用MCP Apps SDK(
@modelcontextprotocol/ext-apps
)为现有MCP服务器的工具添加交互式UI,丰富其功能。

How It Works

工作原理

Existing tools get paired with HTML resources that render inline in the host's conversation. The tool continues to work for text-only clients — UI is an enhancement, not a replacement. Each tool that benefits from UI gets linked to a resource via
_meta.ui.resourceUri
, and the host renders that resource in a sandboxed iframe when the tool is called.
现有工具会与在宿主对话中内嵌渲染的HTML资源配对。该工具仍可在纯文本客户端中正常工作——UI只是增强功能,而非替代方案。每个可从UI中获益的工具都会通过
_meta.ui.resourceUri
链接到一个资源,当工具被调用时,宿主会在沙箱化的iframe中渲染该资源。

Getting Reference Code

获取参考代码

Clone the SDK repository for working examples and API documentation:
bash
git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps
克隆SDK仓库获取可用示例和API文档:
bash
git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps

API Reference (Source Files)

API参考(源文件)

Read JSDoc documentation directly from
/tmp/mcp-ext-apps/src/
:
FileContents
src/app.ts
App
class, handlers (
ontoolinput
,
ontoolresult
,
onhostcontextchanged
,
onteardown
), lifecycle
src/server/index.ts
registerAppTool
,
registerAppResource
,
getUiCapability
, tool visibility options
src/spec.types.ts
All type definitions:
McpUiHostContext
, CSS variable keys, display modes
src/styles.ts
applyDocumentTheme
,
applyHostStyleVariables
,
applyHostFonts
src/react/useApp.tsx
useApp
hook for React apps
src/react/useHostStyles.ts
useHostStyles
,
useHostStyleVariables
,
useHostFonts
hooks
直接从
/tmp/mcp-ext-apps/src/
读取JSDoc文档:
文件内容
src/app.ts
App
类、处理器(
ontoolinput
ontoolresult
onhostcontextchanged
onteardown
)、生命周期
src/server/index.ts
registerAppTool
registerAppResource
getUiCapability
、工具可见性选项
src/spec.types.ts
所有类型定义:
McpUiHostContext
、CSS变量键、显示模式
src/styles.ts
applyDocumentTheme
applyHostStyleVariables
applyHostFonts
src/react/useApp.tsx
用于React应用的
useApp
钩子
src/react/useHostStyles.ts
useHostStyles
useHostStyleVariables
useHostFonts
钩子

Key Examples (Mixed Tool Patterns)

核心示例(混合工具模式)

These examples demonstrate servers with both App-enhanced and plain tools — the exact pattern you're adding:
ExamplePattern
examples/map-server/
show-map
(App tool) +
geocode
(plain tool)
examples/pdf-server/
display_pdf
(App tool) +
list_pdfs
(plain tool) +
read_pdf_bytes
(app-only tool)
examples/system-monitor-server/
get-system-info
(App tool) +
poll-system-stats
(app-only polling tool)
这些示例展示了同时包含App增强型工具和普通工具的服务器——与你要实现的扩展模式完全一致:
示例模式
examples/map-server/
show-map
(App工具) +
geocode
(普通工具)
examples/pdf-server/
display_pdf
(App工具) +
list_pdfs
(普通工具) +
read_pdf_bytes
(仅App工具)
examples/system-monitor-server/
get-system-info
(App工具) +
poll-system-stats
(仅App轮询工具)

Framework Templates

框架模板

Learn and adapt from
/tmp/mcp-ext-apps/examples/basic-server-{framework}/
:
TemplateKey Files
basic-server-vanillajs/
server.ts
,
src/mcp-app.ts
,
mcp-app.html
basic-server-react/
server.ts
,
src/mcp-app.tsx
(uses
useApp
hook)
basic-server-vue/
server.ts
,
src/App.vue
basic-server-svelte/
server.ts
,
src/App.svelte
basic-server-preact/
server.ts
,
src/mcp-app.tsx
basic-server-solid/
server.ts
,
src/mcp-app.tsx
/tmp/mcp-ext-apps/examples/basic-server-{framework}/
中学习并适配:
模板核心文件
basic-server-vanillajs/
server.ts
,
src/mcp-app.ts
,
mcp-app.html
basic-server-react/
server.ts
,
src/mcp-app.tsx
(使用
useApp
钩子)
basic-server-vue/
server.ts
,
src/App.vue
basic-server-svelte/
server.ts
,
src/App.svelte
basic-server-preact/
server.ts
,
src/mcp-app.tsx
basic-server-solid/
server.ts
,
src/mcp-app.tsx

Step 1: Analyze Existing Tools

步骤1:分析现有工具

Before writing any code, analyze the server's existing tools and determine which ones benefit from UI.
  1. Read the server source and list all registered tools
  2. For each tool, assess whether it would benefit from UI (returns data that could be visualized, involves user interaction, etc.) vs. is fine as text-only (simple lookups, utility functions)
  3. Identify tools that could become app-only helpers (data the UI needs to poll/fetch but the model doesn't need to call directly)
  4. Present the analysis to the user and confirm which tools to enhance
在编写任何代码之前,先分析服务器的现有工具,确定哪些工具可从UI中获益。
  1. 阅读服务器源码,列出所有已注册的工具
  2. 针对每个工具,评估其是否能从UI中获益(返回可可视化的数据、涉及用户交互等),还是保持纯文本形式即可(简单查询、工具函数)
  3. 识别可成为仅App辅助工具的工具(UI需要轮询/获取但模型无需直接调用的数据)
  4. 向用户展示分析结果,确认要增强的工具

Decision Framework

决策框架

Tool output typeUI benefitExample
Structured data / lists / tablesHigh — interactive table, search, filteringList of items, search results
Metrics / numbers over timeHigh — charts, gauges, dashboardsSystem stats, analytics
Media / rich contentHigh — viewer, player, rendererMaps, PDFs, images, video
Simple text / confirmationsLow — text is fine"File created", "Setting updated"
Data for other toolsConsider app-onlyPolling endpoints, chunk loaders
工具输出类型UI收益示例
结构化数据/列表/表格高——交互式表格、搜索、筛选项目列表、搜索结果
时序指标/数值高——图表、仪表盘、计量器系统统计数据、分析数据
媒体/富内容高——查看器、播放器、渲染器地图、PDF、图片、视频
简单文本/确认信息低——纯文本即可"文件已创建"、"设置已更新"
供其他工具使用的数据考虑设为仅App工具轮询端点、分块加载器

Step 2: Add Dependencies

步骤2:添加依赖

bash
npm install @modelcontextprotocol/ext-apps
npm install -D vite vite-plugin-singlefile
Plus framework-specific dependencies if needed (e.g.,
react
,
react-dom
,
@vitejs/plugin-react
for React).
Use
npm install
to add dependencies rather than manually writing version numbers. This lets npm resolve the latest compatible versions. Never specify version numbers from memory.
bash
pm install @modelcontextprotocol/ext-apps
npm install -D vite vite-plugin-singlefile
如需使用特定框架,还需添加对应依赖(例如React项目需安装
react
react-dom
@vitejs/plugin-react
)。
使用
npm install
命令添加依赖,而非手动编写版本号。这样npm会自动解析最新的兼容版本,切勿凭记忆指定版本号。

Step 3: Set Up the Build Pipeline

步骤3:构建构建流水线

Vite Configuration

Vite配置

Create
vite.config.ts
with
vite-plugin-singlefile
to bundle the UI into a single HTML file:
typescript
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";

export default defineConfig({
  plugins: [viteSingleFile()],
  build: {
    outDir: "dist",
    rollupOptions: {
      input: "mcp-app.html", // one per UI, or one shared entry
    },
  },
});
创建
vite.config.ts
并配置
vite-plugin-singlefile
,将UI打包为单个HTML文件:
typescript
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";

export default defineConfig({
  plugins: [viteSingleFile()],
  build: {
    outDir: "dist",
    rollupOptions: {
      input: "mcp-app.html", // 每个UI对应一个入口,或使用共享入口
    },
  },
});

HTML Entry Point

HTML入口文件

Create
mcp-app.html
(or one per distinct UI if tools need different views):
html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MCP App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./src/mcp-app.ts"></script>
  </body>
</html>
创建
mcp-app.html
(如果不同工具需要不同视图,可为每个工具创建单独的入口):
html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MCP App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./src/mcp-app.ts"></script>
  </body>
</html>

Build Scripts

构建脚本

Add build scripts to
package.json
. The UI must be built before the server code bundles it:
json
{
  "scripts": {
    "build:ui": "vite build",
    "build:server": "tsc",
    "build": "npm run build:ui && npm run build:server",
    "serve": "tsx server.ts"
  }
}
package.json
中添加构建脚本。必须先构建UI,再让服务器代码打包UI资源:
json
{
  "scripts": {
    "build:ui": "vite build",
    "build:server": "tsc",
    "build": "npm run build:ui && npm run build:server",
    "serve": "tsx server.ts"
  }
}

Step 4: Convert Tools to App Tools

步骤4:将工具转换为App工具

Transform plain MCP tools into App tools with UI.
Before (plain MCP tool):
typescript
server.tool("my-tool", { param: z.string() }, async (args) => {
  const data = await fetchData(args.param);
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
});
After (App tool with UI):
typescript
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";

const resourceUri = "ui://my-tool/mcp-app.html";

registerAppTool(server, "my-tool", {
  description: "Shows data with an interactive UI",
  inputSchema: { param: z.string() },
  _meta: { ui: { resourceUri } },
}, async (args) => {
  const data = await fetchData(args.param);
  return {
    content: [{ type: "text", text: JSON.stringify(data) }],   // text fallback for non-UI hosts
    structuredContent: { data },                                 // structured data for the UI
  };
});
Key guidance:
  • Always keep the
    content
    array
    with a text fallback for text-only clients
  • Add
    structuredContent
    for data the UI needs to render
  • Link the tool to its resource via
    _meta.ui.resourceUri
  • Leave tools that don't benefit from UI unchanged — they stay as plain tools
将普通MCP工具转换为带UI的App工具。
转换前(普通MCP工具):
typescript
server.tool("my-tool", { param: z.string() }, async (args) => {
  const data = await fetchData(args.param);
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
});
转换后(带UI的App工具):
typescript
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";

const resourceUri = "ui://my-tool/mcp-app.html";

registerAppTool(server, "my-tool", {
  description: "以交互式UI展示数据",
  inputSchema: { param: z.string() },
  _meta: { ui: { resourceUri } },
}, async (args) => {
  const data = await fetchData(args.param);
  return {
    content: [{ type: "text", text: JSON.stringify(data) }],   // 为不支持UI的宿主提供文本回退
    structuredContent: { data },                                 // 供UI渲染使用的结构化数据
  };
});
核心指导:
  • 始终保留
    content
    数组
    ,为纯文本客户端提供文本回退
  • 添加
    structuredContent
    字段,提供UI所需的渲染数据
  • 通过
    _meta.ui.resourceUri
    将工具与对应的资源关联
  • 无需增强的工具保持不变——继续作为普通工具使用

Step 5: Register Resources

步骤5:注册资源

Register the HTML resource so the host can fetch it:
typescript
import fs from "node:fs/promises";
import path from "node:path";

const resourceUri = "ui://my-tool/mcp-app.html";

registerAppResource(server, {
  uri: resourceUri,
  name: "My Tool UI",
  mimeType: RESOURCE_MIME_TYPE,
}, async () => {
  const html = await fs.readFile(
    path.resolve(import.meta.dirname, "dist", "mcp-app.html"),
    "utf-8",
  );
  return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] };
});
If multiple tools share the same UI, they can reference the same
resourceUri
and the same resource registration.
注册HTML资源,使宿主能够获取该资源:
typescript
import fs from "node:fs/promises";
import path from "node:path";

const resourceUri = "ui://my-tool/mcp-app.html";

registerAppResource(server, {
  uri: resourceUri,
  name: "我的工具UI",
  mimeType: RESOURCE_MIME_TYPE,
}, async () => {
  const html = await fs.readFile(
    path.resolve(import.meta.dirname, "dist", "mcp-app.html"),
    "utf-8",
  );
  return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] };
});
如果多个工具共享同一个UI,它们可以引用相同的
resourceUri
和资源注册信息。

Step 6: Build the UI

步骤6:构建UI

Handler Registration

处理器注册

Register ALL handlers BEFORE calling
app.connect()
:
typescript
import { App, PostMessageTransport, applyDocumentTheme, applyHostStyleVariables, applyHostFonts } from "@modelcontextprotocol/ext-apps";

const app = new App({ name: "My App", version: "1.0.0" });

app.ontoolinput = (params) => {
  // Render the UI using params.arguments and/or params.structuredContent
};

app.ontoolresult = (result) => {
  // Update UI with final tool result
};

app.onhostcontextchanged = (ctx) => {
  if (ctx.theme) applyDocumentTheme(ctx.theme);
  if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
  if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
  if (ctx.safeAreaInsets) {
    const { top, right, bottom, left } = ctx.safeAreaInsets;
    document.body.style.padding = `${top}px ${right}px ${bottom}px ${left}px`;
  }
};

app.onteardown = async () => {
  return {};
};

await app.connect(new PostMessageTransport());
在调用
app.connect()
之前,必须注册所有处理器:
typescript
import { App, PostMessageTransport, applyDocumentTheme, applyHostStyleVariables, applyHostFonts } from "@modelcontextprotocol/ext-apps";

const app = new App({ name: "My App", version: "1.0.0" });

app.ontoolinput = (params) => {
  // 使用params.arguments和/或params.structuredContent渲染UI
};

app.ontoolresult = (result) => {
  // 使用最终的工具结果更新UI
};

app.onhostcontextchanged = (ctx) => {
  if (ctx.theme) applyDocumentTheme(ctx.theme);
  if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
  if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
  if (ctx.safeAreaInsets) {
    const { top, right, bottom, left } = ctx.safeAreaInsets;
    document.body.style.padding = `${top}px ${right}px ${bottom}px ${left}px`;
  }
};

app.onteardown = async () => {
  return {};
};

await app.connect(new PostMessageTransport());

Host Styling

宿主样式适配

Use host CSS variables for theme integration:
css
.container {
  background: var(--color-background-secondary);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  border-radius: var(--border-radius-md);
}
Key variable groups:
--color-background-*
,
--color-text-*
,
--color-border-*
,
--font-sans
,
--font-mono
,
--font-text-*-size
,
--font-heading-*-size
,
--border-radius-*
. See
src/spec.types.ts
for the full list.
For React apps, use the
useApp
and
useHostStyles
hooks instead — see
basic-server-react/
for the pattern.
使用宿主CSS变量实现主题集成:
css
.container {
  background: var(--color-background-secondary);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  border-radius: var(--border-radius-md);
}
核心变量组:
--color-background-*
--color-text-*
--color-border-*
--font-sans
--font-mono
--font-text-*-size
--font-heading-*-size
--border-radius-*
。完整变量列表请查看
src/spec.types.ts
对于React应用,请使用
useApp
useHostStyles
钩子替代上述方式——可参考
basic-server-react/
中的实现模式。

Optional Enhancements

可选增强功能

App-Only Helper Tools

仅App辅助工具

Tools the UI calls but the model doesn't need to invoke directly (polling, pagination, chunk loading):
typescript
registerAppTool(server, "poll-data", {
  description: "Polls latest data for the UI",
  _meta: { ui: { resourceUri, visibility: ["app"] } },
}, async () => {
  const data = await getLatestData();
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
});
The UI calls these via
app.callServerTool("poll-data", {})
.
供UI调用但模型无需直接触发的工具(轮询、分页、分块加载):
typescript
registerAppTool(server, "poll-data", {
  description: "为UI轮询最新数据",
  _meta: { ui: { resourceUri, visibility: ["app"] } },
}, async () => {
  const data = await getLatestData();
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
});
UI可通过
app.callServerTool("poll-data", {})
调用这些工具。

CSP Configuration

CSP配置

If the UI needs to load external resources (fonts, APIs, CDNs), declare the domains:
typescript
registerAppResource(server, {
  uri: resourceUri,
  name: "My Tool UI",
  mimeType: RESOURCE_MIME_TYPE,
  _meta: {
    ui: {
      connectDomains: ["api.example.com"],      // fetch/XHR targets
      resourceDomains: ["cdn.example.com"],      // scripts, styles, images
      frameDomains: ["embed.example.com"],        // nested iframes
    },
  },
}, async () => { /* ... */ });
如果UI需要加载外部资源(字体、API、CDN),请声明对应的域名:
typescript
registerAppResource(server, {
  uri: resourceUri,
  name: "我的工具UI",
  mimeType: RESOURCE_MIME_TYPE,
  _meta: {
    ui: {
      connectDomains: ["api.example.com"],      // fetch/XHR目标域名
      resourceDomains: ["cdn.example.com"],      // 脚本、样式、图片资源域名
      frameDomains: ["embed.example.com"],        // 嵌套iframe域名
    },
  },
}, async () => { /* ... */ });

Streaming Partial Input

流式部分输入

For large tool inputs, show progress during LLM generation:
typescript
app.ontoolinputpartial = (params) => {
  const args = params.arguments; // Healed partial JSON - always valid
  // Render preview with partial data
};

app.ontoolinput = (params) => {
  // Final complete input - switch to full render
};
对于大型工具输入,在LLM生成过程中展示进度:
typescript
app.ontoolinputpartial = (params) => {
  const args = params.arguments; // 修复后的部分JSON——始终有效
  // 使用部分数据渲染预览
};

app.ontoolinput = (params) => {
  // 最终完整输入——切换为完整渲染
};

Graceful Degradation with
getUiCapability()

使用
getUiCapability()
实现优雅降级

Conditionally register App tools only when the client supports UI, falling back to text-only tools:
typescript
import { getUiCapability, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";

server.server.oninitialized = () => {
  const clientCapabilities = server.server.getClientCapabilities();
  const uiCap = getUiCapability(clientCapabilities);

  if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
    // Client supports UI — register App tool
    registerAppTool(server, "my-tool", {
      description: "Shows data with interactive UI",
      _meta: { ui: { resourceUri } },
    }, appToolHandler);
  } else {
    // Text-only client — register plain tool
    server.tool("my-tool", "Shows data", { param: z.string() }, plainToolHandler);
  }
};
仅当客户端支持UI时才注册App工具,否则回退到纯文本工具:
typescript
import { getUiCapability, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";

server.server.oninitialized = () => {
  const clientCapabilities = server.server.getClientCapabilities();
  const uiCap = getUiCapability(clientCapabilities);

  if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
    // 客户端支持UI——注册App工具
    registerAppTool(server, "my-tool", {
      description: "以交互式UI展示数据",
      _meta: { ui: { resourceUri } },
    }, appToolHandler);
  } else {
    // 纯文本客户端——注册普通工具
    server.tool("my-tool", "展示数据", { param: z.string() }, plainToolHandler);
  }
};

Fullscreen Mode

全屏模式

Allow the UI to expand to fullscreen:
typescript
app.onhostcontextchanged = (ctx) => {
  if (ctx.availableDisplayModes?.includes("fullscreen")) {
    fullscreenBtn.style.display = "block";
  }
  if (ctx.displayMode) {
    container.classList.toggle("fullscreen", ctx.displayMode === "fullscreen");
  }
};

async function toggleFullscreen() {
  const newMode = currentMode === "fullscreen" ? "inline" : "fullscreen";
  const result = await app.requestDisplayMode({ mode: newMode });
  currentMode = result.mode;
}
允许UI切换至全屏模式:
typescript
app.onhostcontextchanged = (ctx) => {
  if (ctx.availableDisplayModes?.includes("fullscreen")) {
    fullscreenBtn.style.display = "block";
  }
  if (ctx.displayMode) {
    container.classList.toggle("fullscreen", ctx.displayMode === "fullscreen");
  }
};

async function toggleFullscreen() {
  const newMode = currentMode === "fullscreen" ? "inline" : "fullscreen";
  const result = await app.requestDisplayMode({ mode: newMode });
  currentMode = result.mode;
}

Common Mistakes to Avoid

需避免的常见错误

  1. Forgetting text
    content
    fallback
    — Always include
    content
    array with text for non-UI hosts
  2. Registering handlers after
    connect()
    — Register ALL handlers BEFORE calling
    app.connect()
  3. Missing
    vite-plugin-singlefile
    — Without it, assets won't load in the sandboxed iframe
  4. Forgetting resource registration — The tool references a
    resourceUri
    that must have a matching resource
  5. Hardcoding styles — Use host CSS variables (
    var(--color-*)
    ) for theme integration
  6. Not handling safe area insets — Always apply
    ctx.safeAreaInsets
    in
    onhostcontextchanged
  1. 遗漏文本回退
    content
    ——始终为不支持UI的宿主保留
    content
    数组和文本内容
  2. connect()
    之后注册处理器
    ——必须在调用
    app.connect()
    之前注册所有处理器
  3. 未使用
    vite-plugin-singlefile
    ——缺少该插件,资源将无法在沙箱iframe中加载
  4. 未注册资源——工具引用的
    resourceUri
    必须有对应的资源注册
  5. 硬编码样式——使用宿主CSS变量(
    var(--color-*)
    )实现主题集成
  6. 未处理安全区域内边距——必须在
    onhostcontextchanged
    中应用
    ctx.safeAreaInsets

Testing

测试

Using basic-host

使用basic-host测试

Test the enhanced server with the basic-host example:
bash
undefined
使用basic-host示例测试增强后的服务器:
bash
undefined

Terminal 1: Build and run your server

终端1:构建并运行你的服务器

npm run build && npm run serve
npm run build && npm run serve

Terminal 2: Run basic-host (from cloned repo)

终端2:运行basic-host(从克隆的仓库中)

cd /tmp/mcp-ext-apps/examples/basic-host npm install SERVERS='["http://localhost:3001/mcp"]' npm run start
cd /tmp/mcp-ext-apps/examples/basic-host npm install SERVERS='["http://localhost:3001/mcp"]' npm run start

Configure `SERVERS` with a JSON array of your server URLs (default: `http://localhost:3001/mcp`).

使用JSON数组配置`SERVERS`,填入你的服务器URL(默认值:`http://localhost:3001/mcp`)。

Verify

验证项

  1. Plain tools still work and return text output
  2. App tools render their UI in the iframe
  3. ontoolinput
    handler fires with tool arguments
  4. ontoolresult
    handler fires with tool result
  5. Host styling (theme, fonts, colors) applies correctly
  1. 普通工具仍可正常工作并返回文本输出
  2. App工具可在iframe中渲染对应的UI
  3. ontoolinput
    处理器可接收工具参数并触发
  4. ontoolresult
    处理器可接收工具结果并触发
  5. 宿主样式(主题、字体、颜色)可正确应用