你的网站来了新访客:浏览器里的 AI Agent,替人办事。它现在靠读 DOM、猜按钮含义来操作你的页面——CSS 一改就崩,还浪费算力。Google 与 Microsoft 共同推进的 WebMCP(Chrome 149 起开放试用)让网页主动声明「我能做的动作」为结构化工具,Agent 直接调用、拿结构化结果。本教程两种落地方式都走一遍。
Step 1:特征检测与环境确认
WebMCP 目前是 Chrome 149+ / Edge 147+ 的开放试用特性,只在受支持的浏览器里存在。代码里先检测,不在就走降级。
// 入口对象可能在 navigator 或 document 上,按当前规范探测
const mc = navigator.modelContext || document.modelContext;
if (!mc) {
console.warn("当前浏览器不支持 WebMCP,降级为普通页面交互");
} else {
console.log("WebMCP 可用,开始注册工具");
}
规范仍在演进:API 命名在 2026 年有过迁移(从 navigator.modelContext 切到 document.modelContext 的版本有过反复),上线前请以 Chrome 官方 WebMCP 文档的当前形态为准。上面同时探测两者就是为兼容差异。
Step 2:声明式——给表单加两个属性
对已有的 HTML 表单,无需写 JS:加 toolname 与 tooldescription 两个属性,浏览器自动把表单合成一个工具。人照样能填,Agent 也能调。
<form toolname="searchProducts" tooldescription="按关键词搜索商品,返回匹配结果列表">
<input name="query" placeholder="输入商品名" />
<button type="submit">搜索</button>
</form>
tooldescription 写「什么时候用这个工具」,而不是字段说明——字段类型由表单自动推导。描述越清楚,Agent 越知道何时调用。Step 3:命令式——用 registerTool 暴露动态动作
表单表达不了的逻辑(React 弹窗、AJAX 动作)用 registerTool。核心是 name + description + inputSchema(JSON Schema)+ execute(调用你已有的业务函数)。
const mc = navigator.modelContext || document.modelContext;
if (mc) {
mc.registerTool({
name: "addToCart",
description: "把指定 SKU 的商品加入购物车",
inputSchema: {
type: "object",
properties: {
sku: { type: "string", description: "商品 SKU,例如 SHOE-42" },
quantity: { type: "integer", description: "加入数量", default: 1 }
},
required: ["sku"]
},
execute: async (params) => {
const result = await addToCart(params.sku, params.quantity || 1);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
});
}
execute 调用你页面本就有的业务函数。你是在「暴露已有能力」,不是在写第二套集成。Step 4:返回正确结构 + 状态感知工具
execute 返回 MCP 内容块结构,Agent 才能拿到可用结果。工具要随页面状态出现/消失:登录前只暴露登录,登录后注册订单类工具,卸载时 unregisterTool 回收。
// 组件挂载时注册
mc.registerTool({ name: "viewOrders", description: "查看当前用户订单", inputSchema: { type: "object", properties: {} }, execute: async () => ({ content: [{ type: "text", text: JSON.stringify(await getOrders()) }] }) });
// 组件卸载时回收,避免暴露用户看不到的工具
function onUnmount() {
if (mc) mc.unregisterTool("viewOrders");
}
不要暴露用户看不到的工具:组件卸载后没回收的 registerTool 既是糟糕体验,也是潜在滥用面。把注册/注销和组件生命周期绑在一起。
Step 5:兜底与安全闸
不支持的浏览器要降级,危险动作要人工确认。给写操作(下单、支付、删除)加显式确认;即便 Agent 调用,也走和用户点击一样的确认流程。
// 降级:不支持 WebMCP 时,用 JSON-LD / 语义 HTML 让传统爬虫与脚本仍能拿到结构化信息
if (!mc) {
injectJsonLd({ "@type": "WebSite", name: "我的商城", potentialAction: "searchProducts" });
}
// 危险动作:复用页面既有的确认流程,不绕过人工闸
execute: async (params) => {
const ok = await confirmWithUser("确认把该商品加入购物车?");
if (!ok) return { content: [{ type: "text", text: "用户取消了操作" }] };
return { content: [{ type: "text", text: JSON.stringify(await addToCart(params.sku, params.quantity)) }] };
}
浏览器支持是硬约束:当前仅 Chrome / Edge 的试用通道可达,Firefox / Safari 还在跟进。WebMCP 面向「人在回路的本地浏览器会话」,无法被无头服务端 Agent 调用,也没有统一发现机制——Agent 要到你的页面后才「发现」工具。
常见问题
| 现象 | 处理 |
|---|---|
| 页面上报 navigator.modelContext 不存在 | 浏览器不在支持列表,或规范已迁移到 document.modelContext;两种都探测即可。 |
| 声明式工具装了却「没反应」 | 确认属性名是 toolname / tooldescription,且表单能被正常提交。 |
| Agent 调错工具 / 填错参数 | 把 description 和字段 description 写具体,含糊描述导致误调用。 |
| 要不要为服务端 Agent 也开 WebMCP | 不必。服务端 Agent 走标准 MCP 连后端;WebMCP 只服务浏览器内会话。 |