关于 webweaver-mcp-server
webweaver-mcp-server 将 WebWeaver Nexus 的联系表单、产品介绍和联系方式提供给支持 MCP 的主机。联系表单以交互式 MCP App 呈现,并通过 MCP 桥接提交。
主要功能
- 01交互式联系表单
- 02提交表单至联系 API
- 03获取产品介绍
- 04获取联系信息
适用场景
- 01在 MCP 主机中填写并提交联系表单
- 02查询 WebWeaver Nexus 的产品信息
- 03查找 WebWeaver Nexus 的联系方式
MCP 相关
这是一个 MCP 服务,向 MCP 客户端提供联系表单、产品介绍和联系信息工具;表单作为 MCP App 呈现,提交通过 MCP 桥接调用服务器工具。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://webweaver-nexus-mcp.vercel.app/mcpclaude mcp add --transport http webweaver-nexus-webweaver https://webweaver-nexus-mcp.vercel.app/mcp{"mcpServers":{"webweaver-nexus-webweaver":{"type":"http","url":"https://webweaver-nexus-mcp.vercel.app/mcp"}}}在 Cursor 中打开{"servers":{"webweaver-nexus-webweaver":{"type":"http","url":"https://webweaver-nexus-mcp.vercel.app/mcp"}}}在 VS Code 中打开{"mcpServers":{"webweaver-nexus-webweaver":{"type":"http","url":"https://webweaver-nexus-mcp.vercel.app/mcp"}}}{"mcpServers":{"webweaver-nexus-webweaver":{"serverUrl":"https://webweaver-nexus-mcp.vercel.app/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
4此工具无需参数。
详细介绍
WebWeaver Nexus MCP 服务
一个 MCP 服务,向支持 MCP 的宿主(Claude Desktop、claude.ai、ChatGPT、Cursor、MCP Inspector、basic-host)提供 WebWeaver Nexus 服务——联系表单(以交互式 MCP App 的形式呈现)、产品概览和联系信息。
生产环境 URL: https://webweaver-nexus-mcp.vercel.app/mcp — Streamable HTTP,公开访问,无需身份验证。
根主机名提供静态导航页面(public/index.html)。只有 /mcp 会路由到函数,因此在浏览器中直接打开端点会返回 406 Not Acceptable——这是正确的内容协商行为,并非故障。
已发布到官方 MCP 注册表,名称为 io.github.webweaver-nexus/webweaver-mcp-server。请参阅发布到 MCP 注册表。
工具
| 工具 | 类型 | 描述 |
|---|---|---|
get_contact_form | MCP App(UI) | 在宿主内部原生呈现 WebWeaver Nexus 联系表单 |
get_product_overview | 普通工具 | 返回 WebWeaver Nexus 的功能说明 |
get_contact_info | 普通工具 | 返回联系方式和链接 |
submit_contact_form | 仅限 App | 提交表单。标记为 _meta.ui.visibility: ["app"],因此兼容的宿主会对模型隐藏此工具 |
表单在 App 自身的文档中呈现,不会发出外部请求。表单通过 MCP 桥接(app.callServerTool)提交给 submit_contact_form,后者使用 bearer 密钥向落地页的 /api/contact 发起服务器到服务器的 POST 请求。这就是它能在先前的 Tally iframe 无法运行的环境中正常工作的原因——请参阅已知限制。
前置条件
- Node.js 24.x(参见
package.json中的engines) - npm
engines特意指定为 24.x:Vercel 会根据它选择函数运行时。较新的本地 Node 主版本也能正常运行,但会导致 npm 打印EBADENGINE警告。
安装
npm install
构建
npm run build
此步骤使用 Vite + vite-plugin-singlefile 打包 MCP App UI,对客户端和服务端进行类型检查,并生成编译后的服务端。
Vite 步骤会先运行,这是构建必不可少的一环:构建插件会将打包后的 HTML 写入 generated/mcp-app-html.ts,server.ts 会导入该文件。若未先运行此步骤,服务端类型检查将失败。generated/ 是构建产物,已加入 Git 忽略列表。
运行
npm run dev时,监视构建会在每次客户端变更时重新生成该模块;但npm run serve只会在启动时加载一次,因此修改 UI 后需要重启服务端。
运行
# HTTP transport (default, port 3001)
npm run serve
# Custom port
PORT=4000 npm run serve
# stdio transport (for Claude Desktop local config)
npm run serve:stdio
# Dev mode (watch + serve)
npm run dev
服务端默认监听 http://localhost:3001/mcp。
配置
环境变量
有两个环境变量,都用于联系 API,且仅在 server.ts 中读取。本地开发时,请将 .env.example 复制为 .env。
| 变量 | 用途 |
|---|---|
CONTACT_FORM_SHARED_SECRET | 用于访问落地页 /api/contact 的 Bearer 令牌。必须与该项目中的值一致。 |
CONTACT_API_BASE_URL | 表单提交的目标地址。默认为 https://webweaver-nexus.vercel.app;末尾的斜杠会被去掉。 |
密钥绝不能进入客户端包。 src/mcp-app.ts 会被编译为约 246 KB 的单个 HTML 字符串,并提供给每位用户。每次构建后:
grep -c 'CONTACT_FORM_SHARED_SECRET' generated/mcp-app-html.ts # must print 0
通过 callServerTool 而不是从 App 直接请求 API,正是确保凭据留在服务端的关键。
开发时,请将 CONTACT_API_BASE_URL 指向本地落地页。 如果使用生产环境,每次测试提交都会向生产 Supabase 表写入真实数据行,并发送两封真实邮件。此服务的 Vercel 预览部署也会指向生产环境——请谨慎使用,并在之后清理数据行(这些数据行带有 source='mcp')。
npm run serve 会从工作目录加载 .env。stdio 入口的 cwd=/,无法找到该文件,因此 stdio 宿主配置需要有自己的 env 块——请参阅 Claude Desktop。
表单契约
落地页拥有表单契约,并通过 GET /api/contact/schema 发布。此仓库在 src/contact-contract.ts 中保存了一份已生成并提交的副本:
npm run sync:contract # regenerate from the endpoint
npm run check:contract # fail if the copy has drifted
切勿手动编辑该文件。8 个目标选项字符串也是落地页欢迎邮件中 GOAL_PARAGRAPHS 的键,因此哪怕只有一个字节不一致,也会悄无声息地丢失个性化段落,且不会在任何地方报错。提交这份副本而不是在构建时获取,是为了让 npm run build 能在离线环境和全新克隆的仓库中运行;check:contract 是发布步骤,不是构建步骤。
使用 MCP Inspector 测试(默认测试工具)
MCP Inspector 是官方 MCP 调试工具,几乎适用于此处的所有检查。已针对 v2.5.0 验证。
npx @modelcontextprotocol/inspector
Web UI 会在端口 6274 上打开。设置:
- 传输类型:
Streamable HTTP - 连接类型:
Via Proxy或Direct——两者都可用。Proxy 会通过 Inspector 的本地代理(端口6277)路由 JSON-RPC;Direct 则采用浏览器 → 服务的连接方式。 - URL:
http://localhost:3001/mcp(本地)或https://webweaver-nexus-mcp.vercel.app/mcp(生产环境)
点击 Connect,然后在各个选项卡中验证:
- 工具 — 会显示
get_contact_form、get_product_overview、get_contact_info和submit_contact_form。最后一个标记为仅应用可用;Inspector 会列出它,因为按visibility过滤是宿主的职责,而非服务的职责。调用两个普通工具时,它们会返回文本。 - 资源 — 会列出
ui://get-contact-form/mcp-app.html;读取后会返回约 246 KB 的打包 HTML,并带有_meta.ui.prefersBorder,完全没有csp键。 - 应用 — 选择
get_contact_form。表单会以可交互的实时控件呈现,且网络面板保持为空。Inspector 会移除allow-same-origin,因此这里的控件能正常工作,也证明旧的 Tally 故障源于 Tally 自身的存储访问,而非结构性的沙盒限制。
此外还提供了 CLI,可在无需浏览器的情况下将部署后的检查编写成脚本:
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url https://webweaver-nexus-mcp.vercel.app/mcp --method tools/list
# App metadata for a UI tool
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url https://webweaver-nexus-mcp.vercel.app/mcp \
--method tools/call --tool-name get_contact_form --app-info
**Inspector 无法做到的一件事:**接收 ui/update-model-context。截至 v2.5.0,它既不会注册 onupdatemodelcontext 处理程序,也不会声明该能力,因此 app.updateModelContext() 无处生效。只有这一项检查需要使用 basic-host。
使用 basic-host 测试(模型上下文更新)
MCP Apps 的 basic-host 测试工具副本已随仓库提供,位于 tools/basic-host/。它是唯一声明 updateModelContext 能力并会显示 📋 模型上下文面板的本地宿主,可用于确认表单在提交后确实会通知宿主模型。
# once
cd tools/basic-host && npm install && npm run build
# Terminal 1 — the server under test
npm run build && npm run serve
# Terminal 2 — the harness
cd tools/basic-host && SERVERS='["http://localhost:3001/mcp"]' npm run serve
# Open http://localhost:8080
改为针对生产环境测试:
cd tools/basic-host && SERVERS='["https://webweaver-nexus-mcp.vercel.app/mcp"]' npm run serve
SERVERS 是一个 JSON 数组,端口 8080/8081 实际上是固定的——有关其来源和完整注意事项,请参阅 tools/basic-host/README.md。
模型上下文面板会一直隐藏,直到收到第一次更新。对于
get_contact_form,这意味着必须实际提交表单——此操作会写入一行数据并发送通知邮件。请将CONTACT_API_BASE_URL指向本地落地页,并使用一次性信息。
何时使用哪种工具
| 需求 | 使用 |
|---|---|
| “服务能否访问?工具是否已列出?” | Inspector(脚本可使用 --cli) |
| “普通工具返回的文本是否正确?” | Inspector |
| “表单是否能正常呈现,控件是否可用?” | Inspector |
| “用户能否实际填写并提交表单?” | basic-host,或 Claude Desktop——两者都声明了 serverTools |
“app.updateModelContext() 能否到达宿主?” | basic-host——Inspector 无法做到 |
| “它能否在客户实际使用的宿主中正常工作?” | Claude Desktop,通过本地 stdio 配置 |
| 部署后最快的基本检查 | Inspector --cli |
经验证的宿主支持情况
每次发布时都应重新检查;以下是观察结果,并非保证。
| 宿主 | 表单可渲染 | 控件可用 | serverTools(提交) | 遵循 visibility: ["app"] |
|---|---|---|---|---|
| Claude Desktop | ✅ | ✅ | ✅ | ✅ |
tools/basic-host | ✅ | ✅ | ✅ | 不适用(显示所有工具) |
| MCP Inspector | ✅ | ✅ | ✅ | ❌ 会列出该工具 |
| claude.ai connector | ✅ | ✅ | ✅ | ✅ |
Claude Desktop 和 Inspector 已于 2026 年 10 月 5 日验证,并于 10 月 6 日在 v2.1.0 上重新验证;claude.ai 已于 10 月 6 日通过 cloudflared 隧道验证。Inspector 还无法接收 ui/update-model-context。
将本地开发服务暴露给 claude.ai(Cloudflare 隧道)
如果要针对 claude.ai 的自定义连接器 UI 在本地迭代服务器(该 UI 仅接受公共 HTTPS URL,不接受 localhost),可通过 Cloudflare 隧道暴露开发服务器:
# Terminal 1 — run the server
npm run build && npm run serve
# Terminal 2 — start the tunnel
npx cloudflared tunnel --url http://localhost:3001
从隧道输出中复制 https://*.trycloudflare.com URL。在 Claude 设置中,添加一个指向 https://<tunnel-url>/mcp 的自定义 MCP 连接器。
*.trycloudflare.com 已加入 api/mcp.ts 中 Host 标头检查的允许列表——cloudflared 转发的是公共主机名,而不是 localhost,因此如果没有该条目,所有经隧道转发的请求都会被拒绝,并返回 403 Invalid Host。
注意:
get_contact_form完全没有声明 CSP,因此阻止此前 Tally 嵌入的frameDomains问题不再适用。需要留意的是宿主是否声明了serverTools能力——如果没有,App 会显示“在网页上继续”的备用选项,而不是提交按钮。请参阅已知限制。
部署(Vercel)
MCP 服务使用 Express + Streamable HTTP 模式部署为 Vercel 无服务器函数。
工作原理:
vercel.json将流量路由到api/mcp.ts,该文件将 Express 应用导出为default。- Vite 打包的 MCP App UI 会编译为字符串常量(
generated/mcp-app-html.ts)并嵌入函数,因此运行时无需从磁盘读取文件。 - 函数运行在 Vercel 的 Node.js 24 运行时上,
maxDuration: 60(远超所需;工具调用会在毫秒内返回)。 - 无需环境变量——所有配置都硬编码在源代码中。
**重新部署:**推送到 main 分支。Vercel 会在推送后自动部署。
验证部署:
# Sanity check — should return a JSON-RPC tools list (~880 bytes)
curl -X POST https://webweaver-nexus-mcp.vercel.app/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
# Resource read — should return ~246 KB of bundled HTML.
# A response under a few KB means the unbundled source HTML was inlined
# instead of the bundle (the build guards against this — see git history).
curl -X POST https://webweaver-nexus-mcp.vercel.app/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"resources/read","params":{"uri":"ui://get-contact-form/mcp-app.html"},"id":1}' \
| wc -c
如需交互式的等效验证方式,可针对已部署的 URL 运行 MCP Inspector(参见上文使用 MCP Inspector 测试(默认测试工具))。建议在启动 basic-host 之前,先将其作为部署后的首项检查。
发布到 MCP 注册表
注册表条目为 io.github.webweaver-nexus/webweaver-mcp-server,定义在 server.json 中。发布只能手动触发——请在 Actions 选项卡中运行 Publish to MCP Registry 工作流。
# validate locally before dispatching
mcp-publisher validate
为何通过 CI 发布,而不是在本地电脑上发布。mcp-publisher login github 只有在 GitHub 通过 GET /user/memberships/orgs?state=active 将你识别为组织 Owner 时,才会授予组织命名空间(上游的 internal/api/handlers/v0/auth/github_at.go 中,注册表要求 role == "admin")。尽管该账户确实是 Owner、拥有公开成员身份,且组织的第三方策略并非原因,但此查询对该组织没有返回结果,因此个人登录只能获得 io.github.<user>/*,发布会因 403 失败。
GitHub Actions OIDC 可绕过这一问题:注册表会根据 repository_owner 声明推导命名空间;对于此仓库,该值为 webweaver-nexus,因此可直接授予 io.github.webweaver-nexus/*。该工作流需要 id-token: write。
触发发布前,请先递增 server.json 中的 version——注册表会拒绝重新发布已存在的版本。
在 MCP 客户端中安装
所有客户端都连接到同一个端点:
https://webweaver-nexus-mcp.vercel.app/mcp
传输方式为 Streamable HTTP。无需身份验证;所有已注册的工具都可公开调用,包括 submit_contact_form——请参阅 visibility 是提示,而非访问控制。
原生支持 Streamable HTTP 的主机(claude.ai、ChatGPT、MCP Inspector、basic-host)可直接使用该 URL。仅支持 stdio 的主机(目前包括 Claude Desktop、Cursor)需使用 mcp-remote npm 垫片——这是一个小型软件包,会以本地 stdio 进程的形式启动,并将 JSON-RPC 请求代理到远程 URL。
Claude Desktop
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json(macOS)或 %APPDATA%\Claude\claude_desktop_config.json(Windows):
{
"mcpServers": {
"webweaver-nexus": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://webweaver-nexus-mcp.vercel.app/mcp"]
}
}
}
重启 Claude Desktop;三个面向模型的工具会显示在工具菜单中。submit_contact_form 不会显示——Claude Desktop 会遵守 visibility: ["app"]。
若要将此服务作为本地 stdio 进程运行——这也是测试尚未发布的更改时的方式——请将配置指向构建后的入口点,并一并设置环境变量,因为 stdio 主机会以 cwd=/ 启动进程,导致无法找到 .env:
{
"mcpServers": {
"webweaver-nexus-local": {
"command": "node",
"args": ["/absolute/path/to/webweaver-mcp-server/dist/main.js", "--stdio"],
"env": {
"CONTACT_API_BASE_URL": "http://localhost:3000",
"CONTACT_FORM_SHARED_SECRET": "local-dev-secret"
}
}
}
}
上面的
mcp-remote配置通过 HTTP 代理到已部署的服务,因此不会触发 v1.0.1 修复的 stdiocwd问题——该问题仅影响这种本地 stdio 配置。
claude.ai(自定义连接器)
claude.ai 原生支持 Streamable HTTP,无需垫片。
- 打开 Settings → Connectors(或 Feature Preview → MCP,具体取决于分阶段推出情况)。
- 选择 Add custom connector → 粘贴
https://webweaver-nexus-mcp.vercel.app/mcp。 - 保存。
三个面向模型的工具都可使用。get_contact_form 能否提交,取决于 claude.ai 是否声明了 serverTools 能力——请参阅已知限制。
Cursor
编辑 ~/.cursor/mcp.json,或在 Cursor 界面中使用 Settings → MCP Servers:
{
"mcpServers": {
"webweaver-nexus": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://webweaver-nexus-mcp.vercel.app/mcp"]
}
}
}
重新加载 Cursor 窗口(Cmd+Shift+P → Reload Window)。
ChatGPT
适用于支持 MCP / 自定义连接器的订阅计划。
- Settings → Connectors(或 Apps)→ Add custom MCP server。
- 粘贴
https://webweaver-nexus-mcp.vercel.app/mcp。 - 在对话中启用。
验证连接(任意客户端)
向主机提出类似这样的请求:
使用 WebWeaver Nexus 连接器获取产品概览。
如果工具成功调用并返回文本,说明连接正常。对于 get_contact_form,已支持 MCP Apps 渲染的主机会直接在对话中显示表单——请参阅上文各客户端的注意事项。
已知限制
Claude Desktop stdio 集成 — 已在 v1.0.1 中修复
此前: join_waitlist(现为 get_contact_form)在 Claude Desktop 中会失败,并报错 ENOENT: no such file or directory, open '/dist/mcp-app.html'。Claude Desktop 在 macOS 上启动 MCP 服务进程时,会将工作目录设为 /,而 server.ts 通过 path.join(process.cwd(), "dist", "mcp-app.html") 定位打包后的 HTML。曾尝试在 claude_desktop_config.json 中设置 cwd 作为变通方案,但在测试的版本中该设置会被静默忽略。
修复: Vite 构建现在会将打包后的 HTML 写入 generated/mcp-app-html.ts,server.ts 将其作为字符串常量导入。运行时不再解析任何文件系统路径,因此 App 资源在 stdio、本地 HTTP 和 Vercel 环境中的行为完全一致。已通过从 / 运行服务并经由 stdio 读取资源进行验证。
此前的两次尝试保留在历史记录中,以供了解背景:一次路径探测(在 e73484f 中添加,在 32dc1aa 中撤销)在 Vercel 上解析到了未打包的源文件;以及一次将 Vite 8 降级到 7 的尝试(2e33c8e,在 c8ad36d 中撤销),其排查方向有误。现在,如果构建误将未打包的源 HTML 内联,构建流程会明确报错。
MCP Inspector 会剥除 allow-same-origin——不再导致表单失效
Inspector 的沙箱代理在自己的源代码中明确说明:App iframe 在沙箱中运行时不带 allow-same-origin,这个标志「始终会从服务器提供的值中剥除」。因此,App 运行在不透明(null)源上。
这过去会导致表单失效。旧版 Tally iframe 会继承不透明源(沙箱标志会由嵌套框架继承),其 JS 无法访问同源存储,导致下拉菜单和复选框失灵,而纯文本输入框仍可正常使用。当时通过仅更改该标志进行 A/B 测试,确认了这一点。
原生表单不受影响,因为原生控件不需要存储。但仍有一项限制必须遵守:在不透明源下,localStorage、sessionStorage 和 document.cookie 都会抛出 SecurityError,这就是为什么 src/mcp-app.ts 中的 RENDERED_AT 是模块作用域常量,而且必须保持如此。
不要用 _meta.ui.domain 来「修复」沙箱。 Inspector 确实会为声明该字段的 App 授予 allow-same-origin,但规范明确指出,其格式取决于具体主机({hash}.claudemcpcontent.com、www-example-com.oaiusercontent.com)。该值由各主机分配,因此猜一个值或许能满足 Inspector,却可能导致其他主机出问题。这里不需要它。
忽略 frameDomains 的主机——已在 v2.0.0 中通过移除依赖解决
frameDomains 的主机受影响的 claude.ai 自定义连接器和 Claude Desktop。 两者都能正确渲染 App 外壳,但嵌入的 Tally 表单却只显示一片空白:主机会忽略 _meta.ui.csp 中声明的 frameDomains,因此嵌套的 tally.so iframe 被拦截。上游问题 anthropics/claude-ai-mcp#40 仍处于开放状态。
为何现在不再适用。 表单现在直接在 App 自身的文档中原生渲染,且不声明任何 csp。我们不依赖的策略自然也就不会被忽略。保留这段说明,是因为它会限制未来的工作:绝不要在 MCP App 中放置第三方 iframe——如果我们把自己的表单放进 iframe,也会遇到完全相同的问题,因为问题出在嵌套框架这一形式,而非 Tally 本身。
Claude Desktop 使用相同的技术栈——其 initialize 会发送 clientInfo.name: "claude-ai (via mcp-remote …)"——因此也会受到同一问题影响。它确实声明支持 MCP Apps(extensions["io.modelcontextprotocol/ui"],值为 text/html;profile=mcp-app),而且 MCP 日志显示 resources/read 和 tools/call 都正常返回,因此这表现为渲染故障,而非协议故障。
已从 Claude Desktop 的 DevTools 控制台确认:
Framing 'https://tally.so/' violates the following Content Security Policy
directive: "frame-src 'self' blob: data:". The request has been blocked.
只有 frameDomains 被丢弃,另外两项声明都保留了下来。App 框架通过查询字符串加载了这些声明:
mcp_apps?connect-src=https%3A%2F%2Ftally.so&resource-src=https%3A%2F%2Ftally.so+https%3A%2F%2Fassets.claude.ai
没有 frame-src 参数,因此策略回退为 frame-src 'self' blob: data:。Tally 的加载器确实运行了,App 也请求了正确的嵌入 URL——是主机拦截了框架,上游环节没有问题。两种传输方式的结果相同:HTTP 上的 mcp-remote 和本地 stdio 配置皆如此,符合渲染器侧拦截的预期。
已确认 Claude Desktop 于 2026 年 10 月 5 日修复。 现在,相同的 App 框架 URL 显示为:
mcp_apps?stable-origin=true&resource-src=https%3A%2F%2Fassets.claude.ai&dev=true
resource-src 中不再有 tally.so,也完全没有 connect-src 参数,因为 App 两者都不需要。整个会话期间,DevTools 控制台没有 CSP 违规记录——唯一的策略提示是 Claude Desktop 自身的 Unrecognized Content-Security-Policy directive 'webrtc'。表单成功渲染,控件可正常交互,且提交内容已到达 Supabase,source='mcp'。
注意新增的 stable-origin=true 参数:Claude Desktop 现在会为 App 框架分配稳定源,这正是 _meta.ui.domain 背后的机制。如果这同时带来了 allow-same-origin,最终可能会在此主机上的 App 中使用浏览器存储。但不要依赖这一点——MCP Inspector 仍会剥除该标志,因此下文的不透明源规则依然适用。
/mcp 上的 GET 和 DELETE 返回 405
从 v2.1.0 开始,此端点通过 createMcpHandler 在同一个 URL 上同时支持 2025 协议时代和 2026-07-28 修订版。旧版流量以无状态方式处理——每个请求都会创建一个新的服务实例——因此没有可供 2025 版会话操作使用的会话,所以 GET 和 DELETE 会返回 405 Method not allowed。此前它们返回 200。
这是 SDK 文档中说明的无状态惯用模式,并非故障。已验证不会影响真实的 v1.30.0 SDK 客户端、tools/basic-host、Claude Desktop 或 claude.ai——它们都不需要该流来处理请求/响应。在两个 Claude 宿主中,表单均正常呈现并提交,且请求到达了 Supabase;两个控制台中都没有出现 405。POST 未作更改。
客户端默认使用 2025 握手,不会进行探测,因此客户端无需作任何更改。选择启用 versionNegotiation: { mode: 'auto' } 的客户端则会协商使用 2026-07-28;两者都能从同一个 URL 获取相同的工具。
不具备 serverTools 能力的宿主
app.callServerTool() 要求宿主声明 serverTools;如果没有该能力,App 就无法提交。Claude Desktop 已声明该能力——已于 2026 年 10 月 5 日验证,提交流程端到端正常完成。tools/basic-host 也已声明该能力。该能力在 claude.ai 和其他宿主上仍未经验证,而且宿主可能撤回该能力,因此保留了检查逻辑。
src/mcp-app.ts 会在加载时而非提交时探测此能力;如果该能力不存在,就会将表单替换为“在网页上继续”的入口,通过 app.openLink() 打开 /#contact(此调用本身受 openLinks 控制;如果也不具备该能力,则会显示可供选择的 URL 文本)。这里的顺序是有意安排的:提交按钮按下后悄无声息地无反应的表单,比没有表单更糟,因为它看起来像是正常工作了。
由于受测宿主都具备该能力,这一回退机制只是保险措施,尚未在真实宿主中实际使用过。 目前仅通过在本地模拟该能力进行了确认。如果要修改这部分逻辑,请重新检查。
visibility: ["app"] 是提示,而非访问控制
submit_contact_form 声明了 _meta.ui.visibility: ["app"],而且 Claude Desktop 遵守了这一设置:在被要求列出其工具时,模型只列出了 get_contact_form、get_contact_info 和 get_product_overview;此前它刚刚在 App 内调用过提交工具(已于 2026 年 10 月 5 日验证)。
这是宿主的自愿配合,并非强制执行。tools/list 会向每个客户端返回该工具——服务端不会进行筛选,规范将此责任交给宿主。忽略该标记的宿主会将工具暴露给其模型。此情况可以接受,但并非毫无风险:consentPrivacy 必须严格等于 true,且落地页会将此路径上的每次提交都记录为 source='mcp',因此任何合成内容都可以被识别。不要将此标记视为安全边界。
项目结构
├── api/ │ └── mcp.ts # Vercel 无服务器入口 — 导出 Express 应用 ├── public/ │ └── index.html # 在 / 提供的静态落地页(不属于 MCP 服务) ├── main.ts # 本地开发入口 — HTTP 和 stdio 传输 ├── server.ts # 工具和资源注册(共享) ├── mcp-app.html # 应用 UI 模板(Vite 入口,源文件) ├── src/ │ ├── mcp-app.ts # 客户端应用生命周期 + 原生联系表单 │ ├── contact-contract.ts # 已生成 — 从落地页引入的表单契约 │ ├── submit-outcome.ts # server.ts 和应用共享的结果类型 │ ├── mcp-app.css # 应用专属样式 │ └── global.css # 宿主变量回退值和重置样式 ├── scripts/ │ └── contract.ts # 同步/检查引入的契约 ├── tools/ │ └── basic-host/ # 引入的 MCP 宿主测试框架(模型上下文更新) ├── generated/ # 构建产物 — 作为 TS 常量的打包 HTML(由 git 忽略) ├── dist/ # 构建输出 — 函数代码和打包后的 mcp-app.html ├── vite.config.ts # Vite 和 singlefile 插件配置 ├── tsconfig.json # 客户端和共享类型检查 ├── tsconfig.server.json # 服务端声明生成 ├── tsconfig.scripts.json # scripts/ 的类型检查(noEmit) ├── .env.example # CONTACT_API_BASE_URL、CONTACT_FORM_SHARED_SECRET ├── vercel.json # Vercel 部署配置 └── package.json
版本历史
- v2.1.0 — 从单体版
@modelcontextprotocol/sdk迁移到 v2 拆分包(server/node/express/client/core,全部精确锁定版本),并将ext-apps从 1.7.5 升级到 2.0.3。现在,端点通过createMcpHandler在同一个 URL 上提供协议修订版 2026-07-28 以及 2025 年版本;客户端默认使用 2025 年握手,不进行探测,因此客户端无需做任何更改。现在,对/mcp执行GET和DELETE会返回405——请参阅已知限制。仅因 ext-apps 更精简,打包后的 App 就从 447 KB 缩减到 246 KB。tools/basic-host特意保留在 SDK v1 上,作为跨版本兼容性测试,并且测试通过。工具、架构和行为均未更改。 - v2.0.0 — 不兼容变更:
join_waitlist更名为get_contact_form,其资源 URI 现在为ui://get-contact-form/mcp-app.html;没有别名,因此缓存的客户端会报错,直到重新获取列表。Tally iframe 已替换为在 App 中原生渲染的表单,不会发出任何外部请求,因此无需声明_meta.ui.csp——这是针对忽略frameDomains的宿主的结构性修复。提交操作通过新的、仅供 App 使用的submit_contact_form工具(_meta.ui.visibility: ["app"])发送服务器到服务器的 POST 请求至落地页的/api/contact,因此共享密钥不会进入客户端包。新增仓库中的首批两个环境变量,以及通过npm run check:contract检查的 vendored 表单契约。还移除了一个没有来源检查、会信任任何声称提交了 Tally 表单的 iframe 的postMessage监听器。 - v1.0.2 — 修复了
join_waitlist在所有宿主中都渲染为空白面板的问题:App 加载了 Tally 的embed.js,却从未调用Tally.loadEmbeds(),因此表单 iframe 从未获得src。此外,现在仅当宿主具备相应能力时才调用updateModelContext,并将 basic-host 测试框架 vendored 到tools/basic-host/,因此不再需要克隆ext-apps。 - v1.0.1 —
join_waitlist现在可通过 stdio 工作(Claude Desktop)。App HTML 现在会编译到服务中,而不是从磁盘读取,从而移除了对process.cwd()的依赖。速率限制现在按客户端分别计算:此前未设置trust proxy,因此在 Vercel 代理后面的所有调用方共用一个每分钟 60 次的限流桶。宿主验证现在接受 Vercel 预览部署和*.trycloudflare.com,文档中介绍的隧道工作流需要这些域名。更新依赖以清除所有npm audit警告(MCP SDK 1.29 → 1.30)。 - v1.0.0 — 以
io.github.webweaver-nexus/webweaver-mcp-server名称发布到官方 MCP 注册表。通过 CORS 允许列表和速率限制强化端点安全性。 - v0.1.0 — 首次成功部署到生产环境。三个工具已上线;
join_waitlist可在 Streamable HTTP 宿主上使用。Claude Desktop stdio 集成推迟实现。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。