Web组件沙盒框架

用于在沙盒化iframe中加载MCP相关UI内容的轻量级Web组件框架,支持资源解析、主题系统和MCP应用兼容性。

  • by mcp4h
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 mcp4h/mcp-ui

使用说明

MCP用户界面

使用srcdoc和基于消息的资源/工具调用桥将MCP相关UI内容加载到沙盒iframe中的最小web组件主机。

特性

  • 本机web组件(无框架依赖关系)。
  • <mcp-view> 解决 ui:// 通过解析器获取资源。
  • 默认情况下,HTML会被重写以使用 mcp-* 用于资源加载的组件。
  • 可选的 resource-base 模式重写 ui:// 将资源转换为本地浏览器可加载的URL。
  • Iframe引导一个小型桥接脚本和实用程序CSS。
  • 主题系统 --mcp- 变量和内置 .mcp-* 公用事业类。
  • 对可选https/http资源的远程分配列表支持。
  • 可选的自动高度模式,用于内容驱动的尺寸调整。

安装

npm install mcp-view

基本用法(ESM)

<script type="module">
	import "mcp-view";
</script>
<mcp-view
	src="ui://index.html"
	base="/mcp/resources/"
	style="display:block; width: 100%; height: 420px;"></mcp-view>

基本用法(UMD)

<script src="./dist/mcp-view.umd.min.js"></script>
<mcp-view
	src="ui://index.html"
	base="/mcp/resources/"
	style="display:block; width: 100%; height: 420px;"></mcp-view>

默认的获取解析器使用 base (或 /mcp/resources/ 当省略时)。

使用加载本地资源 resource-base

默认情况下, mcp-ui 负载 ui:// 通过它的信息桥,脚本、风格和媒体。

如果你提供 resource-base, mcp-ui 保持本机标签如下 <script>, <link>,以及 <img> 在iframe文档中重写 ui:// URL改为浏览器可加载的URL。如果没有自定义 resolver 设置, resource-base 也用作根的默认解析器 src 文件。

<mcp-view
	src="ui://index.html"
	resource-base="/mcp/resources?uri={uri}"></mcp-view>

当您希望与现有的MCP应用程序更好地兼容时,这是推荐的模式,特别是对于正常的脚本加载和模块导入。

resource-base 支持与相同的URL模式 base:

  • {path} 替换已剥离的 ui:// 路径
  • {uri} 替换完整编码 ui:// 统一资源标识符
  • 如果该值包含 ?, &uri= 已附加
  • 否则,剥离的路径将附加到基础路径

如果 resource-base 未设置, mcp-ui 保留了传统的基于网桥的加载器,以实现向后兼容性。

延迟初始渲染

如果主机需要设置以下属性 css, csp, resolver,或 toolCaller 在加载iframe之前,您可以推迟初始渲染。

<mcp-view
	defer
	src="ui://review/index.html"
	resource-base="/mcp/resources?uri={uri}"></mcp-view>

然后,在设置属性后,调用 render() 一次:

const view = document.querySelector("mcp-view");
view.css = { "--mcp-color-bg": "#111" };
view.csp = "default-src 'none'; style-src 'unsafe-inline'";
view.resolver = async(uri) => fetch(`/mcp/resources?uri=${encodeURIComponent(uri)}`);
view.render();

主题化

该组件首先注入默认的CSS变量和实用程序类,然后是您的覆盖。

主题URL(属性)

<mcp-view theme="https://example.com/theme.css"></mcp-view>

theme 作为样式表链接加载。给 ui:// 默认情况下,它使用传统解析器桥的URL,或 resource-base 当启用本地资源加载时。对于http/https,它始终是允许的,不需要手动分配。相对URL解析主机页面。

CSS覆盖(属性)

view.css = `.mcp-button { border-radius: 999px; }`;

CSS变量(对象)

view.css = {
  "--mcp-color-primary": "#2d5bff",
  "--mcp-color-bg": "#ffffff",
};

不支持以下功能 css;传递字符串或变量对象。

语法变量

默认语法高亮标记作为CSS变量提供:

  • --mcp-syntax-comment
  • --mcp-syntax-constant
  • --mcp-syntax-keyword
  • --mcp-syntax-entity
  • --mcp-syntax-tag
  • --mcp-syntax-variable
  • --mcp-syntax-string
  • --mcp-syntax-number
  • --mcp-syntax-operator
  • --mcp-syntax-punctuation

工具栏实用程序

<div class="mcp-toolbar">
	<button class="mcp-button mcp-button-secondary">One</button>
	<button class="mcp-button mcp-button-secondary active" aria-pressed="true">Two</button>
	<button class="mcp-button mcp-button-secondary">Three</button>
</div>

将数据传递到iframe

设置 data 主机上的财产。iframe可以访问 window.mcpData 并倾听 mcp-data 事件。

view.data = { input: "hello", output: null };

将完成的工具结果传递给iframe

如果主机已经有一个完整的MCP工具结果,并希望在工具调用后呈现UI,请设置 toolResult 主机上的财产。iframe通过用于实时工具调用的相同客户机端结果处理程序接收它:

  • window.mcp.ontoolresult
  • app.ontoolresult
view.toolResult = {
  content: [{ type: "text", text: "Rendered in the app" }],
  structuredContent: { items: [] },
  _meta: { some: "value" },
  isError: false,
};

这是为了实现嵌入式应用程序的MCP应用程序兼容性:应用程序在工具调用后收到与正常情况下相同的结果包络形状,而不需要从iframe内部重放工具调用。

这还不是完全符合有线级MCP Apps协议。在内部, mcp-ui 仍然使用自己的网桥消息,例如 mcp:tool-result 而不是发出确切的MCP Apps JSON-RPC通知名称。

自动高度

允许iframe报告其内容高度,以便host元素调整自身大小:

<mcp-view src="ui://index.html" auto-height="true"></mcp-view>

你可以用以下方式限制高度 max-height (像素):

<mcp-view src="ui://index.html" auto-height="true" max-height="800"></mcp-view>

注意:如果您的内容设置 html, body { height: 100%; } 或 min-height: 100%,报告的高度可能至少保持在视口高度。

MCP应用程序兼容性

iframe桥现在公开了一个MCP应用程序风格的来宾API。

重要区别:

  • mcp-ui 旨在在应用程序边界实现实用的MCP应用程序兼容性:嵌入式应用程序可以使用MCP应用程序风格的API并接收MCP风格的结果信封,包括 structuredContent.
  • mcp-ui 尚未声称完全符合有线级MCP Apps协议。内部主机/iframe网桥仍在使用 postMessage 消息类型,例如 mcp:tool-result, mcp:tool-input,以及 mcp:connect.

当前的目标是使iframe中现有的MCP Apps兼容客户端代码能够正确运行。如果需要,稍后可以调整确切的协议和通知命名。

iframe桥现在公开了一个MCP应用程序风格的来宾API:

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

支持的客户端API:

  • app.connect()
  • app.callServerTool(...)
  • app.sendMessage(...)
  • app.ontoolinput
  • app.ontoolresult
  • app.onhostcontextchanged
  • app.getHostContext()

iframe和包导出中也提供了帮助函数:

  • applyHostStyleVariables(...)
  • applyHostFonts(...)
  • applyDocumentTheme(...)

年长者 window.mcp.* API仍然作为兼容层存在,但新客户端应该更喜欢 App API

宿主样式和CSS变量

mcp-ui 现在支持通过主机上下文进行MCP Apps风格的主机主题化:

  • hostContext.theme
  • hostContext.styles.variables
  • hostContext.styles.css.fonts

在iframe内部,应用程序可以这样应用它们:

const app = new App({ name: "My App", version: "1.0.0" });
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);
	}
};
await app.connect();
const ctx = app.getHostContext();
if (ctx?.styles?.variables) {
	applyHostStyleVariables(ctx.styles.variables);
}

为了向后兼容性, mcp-ui 还将MCP Apps令牌映射到较旧的 --mcp-* 现有应用程序使用的变量。这意味着较旧的应用程序可以继续使用 --mcp-color-*, --mcp-font-*, --mcp-radius-*,以及 --mcp-shadow-* 而较新的应用程序可以使用MCP应用程序变量,例如 --color-background-primary, --font-sans,以及 --border-radius-md 直接。

工具调用

在iframe内部,引导程序注入了两个遗留内容 window.mcp.callTool(params) 助手和MCP Apps兼容 App 桥。工具响应保留了MCP结果包络,因此 structuredContent 直接在iframe中可用。

主机侧:

const view = document.querySelector("mcp-view");
view.toolCaller = async (params) => {
  return await runTool(params);
};

Iframe侧:

const result = await window.mcp.callTool({
  name: "read_file",
  arguments: { path: "src/main.ts" },
});

// If the host tool returns an MCP Apps-style envelope, the iframe receives it.
console.log(result.structuredContent);

如果主机端 toolCaller 返回一个普通值legacy callTool() 用法仍然直接解析该值。如果它返回一个形状像MCP工具结果的对象,例如:

{
  content: [{ type: "text", text: "Rendered in the app" }],
  structuredContent: { items: [] },
  _meta: { some: "value" },
  isError: false,
}

然后,该信封将原封不动地转发到iframe。

图层顺序

默认情况下,级联为:defaults→ ui://内容→ 应用程序覆盖。您可以通过以下方式覆盖图层顺序 layers 财产。

view.layers = ["mcp-default", "mcp-content", "mcp-user"];

将自定义图层作为目标 ui:// 样式表,添加 layer="custom" 关于原作 <link>:

<link
	rel="stylesheet"
	href="ui://application/app.css"
	layer="bob">

远程allowlist

默认情况下,远程资源被阻止。通过以下任一方式启用它们:

view.allowedOrigins = ["https://example.com", "cdn.example.com"];

或

view.allowedRemote = (url) => url.startsWith("https://cdn.example.com/");

解析器返回类型

解析器可以返回:

  • Response
  • Blob
  • string
  • ArrayBuffer
  • Uint8Array

构建

npm run build

捆绑使用(UMD/IIFE)

<script src="./dist/mcp-view.umd.js"></script>
<script>
	McpView.defineMcpView();
	const view = document.querySelector("mcp-view");
	view.src = "ui://application/index.html";
	view.resolver = async() => "<html><body>...</body></html>";
</script>

精简版本可在以下网址获得:

  • dist/mcp-view.esm.min.js
  • dist/mcp-view.umd.min.js

例子

  • UMD: examples/basic.html
  • ESM: examples/basic-esm.html (需要通过HTTP提供服务)

要在本地运行ESM示例:

npx serve

属性与特性

  • 属性仅为字符串,适用于HTML标记。
  • 属性是JavaScript值(函数、对象、数组)。

使用 src 对于根文档。 使用 css 用于内联重写(字符串或变量对象)。 使用 resolver (属性)用于自定义解析。 使用 base 启用默认的fetch解析器。它解析相对于主页的相对URL。

解析器(属性)

view.resolver = (uri) => fetch(`/mcp-ui?uri=${encodeURIComponent(uri)}`);

默认解析器(属性)

如果你设置 base,组件将解析 ui:// 使用这些规则的URI(按顺序):

  • {path} 被剥离的路径所取代(否 ui://).
  • {uri} 被替换为完整的URI(URL编码)。
  • 如果 base 包含 ?,它附加 &uri= 带有完整的URI(URL编码)。
  • 否则,它会将剥离的路径附加到基础。
<mcp-view src="ui://index.html" base="/mcp/resources/"></mcp-view>
<mcp-view src="ui://index.html" base="/mcp/resources/{path}"></mcp-view>
<mcp-view src="ui://index.html" base="/mcp/resources?uri="></mcp-view>
<mcp-view src="ui://index.html" base="/mcp/resources/{uri}"></mcp-view>

如果没有提供解析器 base 已设置,默认为 /mcp/resources/. ui:// 构造时去掉前缀 {path}.