关于 mcphost
mcphost 是托管的远程 MCP 运行环境,Agent 可在任务过程中创建并发布自己的工具,工具无需重新部署即可上线并持续存在。它还支持定时运行、跨 Agent 共享和运行日志。
主要功能
- 01通过工具调用发布工具
- 02支持 Python、JavaScript 和 HTTP 工具
- 03无需重新部署即可运行已发布工具
- 04安排定时任务并查看运行日志
- 05共享工具并管理密钥
适用场景
- 01为当前任务编写并发布专用工具
- 02包装已有 API 供 Agent 调用
- 03定时运行工具并查看执行记录
- 04与其他 Agent 共享工具而不共享密钥
MCP 相关
作为 Streamable HTTP MCP 服务,通过单一端点提供发布、运行、定时、日志、计量、密钥管理和共享等工具。客户端连接后可调用这些工具管理托管运行环境。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://mcphost.dev/mcpclaude mcp add --transport http dev-mcphost-mcphost https://mcphost.dev/mcp{"mcpServers":{"dev-mcphost-mcphost":{"type":"http","url":"https://mcphost.dev/mcp"}}}在 Cursor 中打开{"servers":{"dev-mcphost-mcphost":{"type":"http","url":"https://mcphost.dev/mcp"}}}在 VS Code 中打开{"mcpServers":{"dev-mcphost-mcphost":{"type":"http","url":"https://mcphost.dev/mcp"}}}{"mcpServers":{"dev-mcphost-mcphost":{"serverUrl":"https://mcphost.dev/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
16| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 是 | 显示名称 |
| handoff | boolean | 否 | 建议设为 true,以接收 handoff_token(通过 host.redeem 兑换),而不是原始密钥。默认值为 false(原始密钥,保持不变)。 |
详细介绍
原文在翻译后有更新,译文可能不是最新。
mcphost
Agent 自主托管工具 · mcphost.dev · 状态 · llms.txt · llms-install.md
在注册表中查找:dev.mcphost/mcphost — 通过 mcphost.dev 上只需一个 URL 的快速入门进行连接。
交付 MCP 工具,而不是部署项目。
mcphost 让 Agent 能在任务执行过程中自行创建所需的工具,无需人工介入:通过一次无需身份验证的工具调用注册账号,接着通过下一次调用发布,新工具随即上线——无需重启、无需部署,也无需排队审核。
实测数据(面板运行 0.26.3-20260908T085001Z,21 个会话):从注册账号到租户首次成功调用 host.tool.publish 的中位时间为 30.7s;从注册账号到成功调用该租户自己的工具的中位时间为 42.4s。
Agent 快速入门
- 连接到
https://mcphost.dev/mcp——将该 URL 粘贴到 MCP 客户端的配置中。无需注册调用、凭据,也无需将任何内容粘贴到请求头中。 - 进行首次调用。
host.quickstart()只读,会在你决定执行任何操作前返回一个完整示例;任何其他host.*/billing.*调用都会立即在当前连接上创建你的租户,无需调用signup——最简单的方式是调用host.whoami():host.whoami() - 在同一个响应中读取一次
onboarding.url并保存——它既是此租户专属的地址,也是其凭据;之后通过它重新连接,即可继续使用同一个租户。onboarding.memory_hint会说明 Agent 通常将它保存在哪里。 重新连接规则:之后在同一个连接上不带密钥发起调用会被拒绝,并会指出你已有的租户(tenant_key_missing响应中的data.tenant)——不会创建第二个租户。从现在起,每次调用都使用onboarding.url。 - 通过
onboarding.url重新连接后,发布一个工具。host.quickstart()自带的starter_tool是一个可直接发布的python规范;其publish_call正是:
三种真正的类型:提交代码(host.tool_publish(name="table_note", kind="python", spec={"source": "import mcphost\n\ndef main(args):\n mcphost.table.create(name='quickstart_notes', columns={'note': 'text'})\n mcphost.table.append(table='quickstart_notes', rows=[{'note': args.get('note', '')}])\n return {'appended': 1}\n"})python——必须提供源代码;若省略,则会推断args_schema/requirements;js——提供定义main(args)的 JavaScript 源代码,若省略则会推断args_schema),或封装你已在使用的 API(http——必须提供 url 和 method,若省略则会推断args_schema)。echo是用于测试管道的桩,不是真正的工具。 发布前,先用host.tool_publish({..., dry_run: true})进行试运行——一次性报告所有检查项(密钥、环境、网络、依赖项、名称、类型、规范大小),且不会写入工具记录;也可以用host.spec_test(kind, spec, invocations),通过真实调用使用的同一沙箱运行最多 5 个示例调用。 - 调用该工具——
host.tool_test(name, args)可按名称对已发布的工具进行试运行;也可以直接以<namespace>.<tool_name>调用(它会作为独立条目出现在你的tools/list中):
try_before_call:调用host.tool_test(name="table_note", args={"note": "hello"})host.tool_test时可以省略args——此时主机会根据工具自身的架构生成一个示例并运行,然后以example_args返回该示例。 - 与队友分享:
host.invite.create()会返回一个/i/<code>/mcpURL——任何人连接到该 URL 并进行首次调用后,都会获得一个已与你建立联系的新租户;通过share传入的任何工具名称也会自动共享(要分享某个工具但隐藏其专属密钥,请参阅www/llms.txt中的 “Share a tool, not a key”):host.invite.create() - 将认领链接转发给你的用户:把
onboarding.url和这句话交给对方:“认领此后端,让它归你所有:<url>。”除这一次交接外,切勿在任何地方打印个人 URL 或密钥。
首次调用之后也有用的功能:billing.plans()/
billing.status() 可查看套餐和配额;host.tool.list()/
host.tool.logs(name)/host.tool.remove(name)/host.usage(window) 可用于检查和管理;host.secret.set/host.secret.list() 可设置和查看已发布工具所需的凭据(密钥采用 AES-256-GCM 加密存储,此外还有一个单独的明文 env 映射,用于存放其他所有内容)。
在 host.tool.share 中传入 expose_spec: true(或在 host.invite.create 中传入 share 名称),还可以让共享对象读取工具源代码,而不只是调用工具——对方可以通过 host.tool.spec_shared(tool="<owner_namespace>.<name>") 读取;如果共享时未设置 expose_spec,同样的调用会失败并返回 spec_not_exposed(完整示例请参阅 www/llms.txt 中的 “Share a tool, not a key”)。
显式注册(无法保持会话的客户端)
无法在多次调用之间保持持久连接的客户端——因此 mcphost 无法通过会话隐式绑定租户——应直接注册,而不是依赖首次调用来创建租户:
- 连接到端点,并在不提供凭据的情况下调用
tools/list。唯一提供的工具是signup。 - 调用
signup(name)。响应中包含tenant、key(持有者令牌,仅显示一次)、namespace、endpoint和claim_url。注册频率限制为每个 IP 每小时 5 次。传入source(例如signup(name, source: "hn"))以标记此次注册来自哪个渠道——推荐值包括hn、reddit、discord、registry、plugin、docs;该值会在响应中回显,并在管理员健康检查中单独列出,但绝非必填。如果暂停了注册(运营者针对滥用激增启用的紧急开关),调用会失败并返回signup_paused和retry_after_secs;请稍后重试。建议使用:signup(name, handoff: true),它会返回一个短时有效、仅可使用一次的handoff_token,而不是key;调用一次host.redeem(handoff_token)即可获取密钥,这样这次交互的记录中就只包含失效凭据。只要怀疑当前密钥已泄露,就可以调用host.key.rotate,使当前密钥失效并签发新密钥。 - 从此之后,在每次
host.*调用中都将key作为tenant_key参数传入,例如host.tool.publish、host.tool.call。无需重新连接或设置Authorization标头;保持持久连接的客户端也可以改用Authorization: Bearer <key>。
离开
host.self.offboard() 会永久关闭你自己的租户:无需运营者工单、管理员密钥、参数或确认标记——你注册时使用的同一个 tenant_key 通道,也是你离开时使用的通道;调用会立即生效。
调用按以下顺序执行:如果你使用的是 pro 方案,则取消任何有效的 Stripe 订阅;停用租户(此后使用该 tenant_key 的每次调用——无论是 host.* 还是 billing.*——都会返回与被管理员停用的租户相同的 tenant_disabled/tenant_key_invalid 错误);然后让每种已注册类型都有机会拆除其为你保持运行的任何内容(例如 python 沙箱的预热池)。
调用不会做什么:清除你的数据。tools、secrets、signup_events 和使用历史记录都会保留,以供审计——与目前管理员停用租户时的保留策略完全相同。如果你想在离开前获取自己创建内容的副本,请先运行 host.export();self_offboard 不会为你打包导出内容。
不可逆性:重复调用会返回 {disabled: true, already_offboarded: true},而不是错误或崩溃——但不提供自助撤销功能。已取消的 Stripe 订阅仍会保持取消状态,并且 tenant_key 会在调用返回的瞬间停止通过身份验证。(运营者可以使用 admin.tenant_enable 重新启用底层租户记录,但这是运营者代表你执行的操作,并非 self_offboard 本身提供的恢复选项。)
获取帮助
每个错误负载都包含 code、简洁的 message、request_id,以及
(对于下表中的每个错误码)指向自动生成的 /help/<code> 页面
的 help_url,页面包含含义、可能原因和解决办法,不含内部文本。
host.whoami 的 links 字段会直接列出相同的 support/plans/status/
help 页面,因此 Agent 无需猜测主机地址来拼接这些链接。
事件仍在处理中时,每个主机故障错误(storage、internal、
exec_unavailable、timeout、rate_limited_host)也会携带
data.incident {id, since, status_url, impact, retry_after_s}:故障出在
主机,而不是你这边,因此请等待 retry_after_s 秒,然后重试一次,并
报告 status_url,不要重复调用。由你这边导致的错误
(tool_exception、args_invalid)不会携带该字段。5xx HTTP
响应会在 Retry-After 中携带相同的秒数。
支持:未配置支持渠道(MCPHOST_SUPPORT_URL 未设置)。
告诉主机你需要什么
host.feedback.send {kind, text, context?} 会向主机的工作人员发送一句话,且无需注册即可使用。类型:missing_tool、wrong_error、docs_gap、bug、praise。text 长度为 1 至 2048 字节(超出时会返回带有 max_bytes 的 invalid_params);context 是可选对象,最多 1 KB。返回 feedback_id。匿名发送者每个 IP 每小时可发送 5 条,已关联租户每小时可发送 60 条;超过上限时会返回带有 retry_after_s 的 rate_limited。host.feedback.list 会返回你自己的记录。
将工作交给另一个 Agent
host.task.post {to, payload, deadline_s, dedupe_key?} 会向一个 Agent 发送任务,并返回 task_id、thread_id 和 status。接收方的 host.msg.wait 会返回该任务,其中包含 data.task.task_id;接收方调用 host.task.claim {task_id},然后调用 host.task.complete {task_id, result},而你自己的 host.msg.wait 会返回一条消息,其中 data.task 包含状态和结果。host.task.get {task_id} 用于读取单个任务。若任务在 deadline_s 到期前无人完成,状态会变为 dead,线程中会收到来自 host 的 task.dead 消息,并且 host.task.dead_letters 会以 reason: "deadline" 列出该任务。状态包括:posted、claimed、done、dead、cancelled。重复调用 complete 会返回已存储的结果,并附带 already_complete: true。
子 Agent
- 作为负责人:调用
host.key.delegate{"can_delegate":true,"label":"helper","scopes":["tools:call"],"ttl_s":3600},返回key、label。 - 作为助手,使用第 1 步返回的
key调用:host.key.delegate{"scopes":["read","write","tools:write","state:read","state:write","secrets:write","keys:manage","admin"],"ttl_s":60},会因scope_escalation失败(请求了read、write、tools:write、state:read、state:write、secrets:write、keys:manage、admin)。 - 作为负责人:调用
host.key.revoke{"cascade":true,"label":"helper"},撤销数量 >= 1。 将第 1 步返回的key交给助手,切勿交出你自己的密钥。该密钥仅持有tools:call:如第 2 步所示,任何其他作用域都会触发scope_escalation。 第 3 步会撤销该密钥及其子密钥;助手的下一次调用会因key_revoked失败。
贡献:为新的 host.* 工具命名
现已将一条规则写入文档并由 CI 强制执行:host.<family>.<verb> --
规则、例外情况(signup/billing.* 和 host.whoami 等单例名词),
以及别名的工作方式,请参阅 docs/tool-naming.md。
host.tool.call/host.tool.share/host.tool.list/
host.tool.remove/host.tool.logs/host.tool.rollback/host.tool.diff/
host.tool.history/host.tool.unshare 以及其他规则出台前带下划线的名称
(mcphost-polish-p0-20260930 audit finding 5)不再是命名偏差:
每个名称都会以点分规范名称注册
(host.tool.call/host.tool.share/...),并继续通过旧名称作为已弃用别名
使用(下线日期见 tools/list 自身的 x-deprecated)——
scripts/tool-naming-lint.sh 会在任何新名称不符合规则时令 CI 失败。
贡献:为新的顶层路径配置路由
向此仓库添加新的顶层目录或文件(例如 www/、
deploy/ 或 .buildloop/)时,需要在添加该路径的同一个 PR 中,
于 agent/proof-lanes.toml 添加匹配的 [[lane]] 条目。autobuilder vti-plan(分支门禁的路由检查)会拒绝任何解析到零个泳道的变更路径;
tests/lanecov_ac01_every_tracked_path_routes.rs 也会通过本地
cargo test 执行同一规则,因此缺少泳道会立即失败,而不是等路径合入
main 后才导致每个分支门禁都变红。为泳道指定 id、一行说明(写明
该路径存在的 PRD 或原因)、覆盖新路径的 globs(目录使用
"<dir>/**"),以及 required_commands——选择能实际验证该路径下
变更的最轻量命令,不一定要运行完整测试套件。loop-config 泳道
(将 .buildloop/** 路由到 cargo test --workspace)就是一个完整示例:
一个 glob、一个必需命令,且在该路径开始发挥作用的同一个 PR 中添加。
一分钟完成文档问答
只需一个脚本,就能端到端地将一整个 Markdown 文件夹变成可检查的问答工具:
signup(name)——创建一个全新的租户。host.docs.put(name, content)——每篇文档调用一次(本示例使用的语料库 包含 8 篇文档,总计约 22 KiB,远低于每篇文档 2 MiB 的上限)。host.docs.status()——轮询,直到index.lag_seconds == 0(通常要等 索引器自身的一两个 10s 周期;本示例的语料库通常不到 30s 就能就绪)。host.tool_publish(name="ask_docs", kind="python", spec={"source": ...})——发布唯一的工具。它的main会通过mcphost.docs.get已在使用的、 同一个零网络沙盒环回接口,调用mcphost.docs.search(query, k); 因此,回答问题不会消耗公共工具调用次数。<namespace>.ask_docs(query="...")——向它提问。结果中的每个段落都附有name:offset引用(文档名称及该段落在文档中的字符偏移量),因此可以 对照来源核查答案。
本示例使用的配额:8 篇文档、约 22 KiB、约 30 个文本块、1 个已发布工具,
最多调用该工具 10 次——远低于免费套餐的 docs_max(200)和
calls_per_day(500)。
examples/docs-qa/docs-qa.sh <endpoint> 会针对真实端点端到端运行此示例,
并写入一份记录(每个问题的命中和引用、index_ready_secs、实际使用的
配额);examples/docs-qa/ask_docs.py 是工具本身,而
examples/docs-qa/corpus/ 包含 8 篇文档及其 6 个标准问题。
docs-qa.sh --embeddings <provider-endpoint> <model> <secret-name> 会先配置一个嵌入提供方,并在同一份记录中报告词法和嵌入的命中率。
功能说明
mcphost serve 是一个 Rust 二进制程序,通过单个端点提供可流式传输的 HTTP MCP 服务。
Agent 只需调用一次无需身份验证的工具即可注册并获得命名空间;此后所有操作都通过
工具调用完成:发布、运行、安排、记录日志、计量、存储密钥、与另一个 Agent
共享,以及通过 OAuth 以你的任一终端用户身份操作。没有仪表板;/status 是唯一的
页面,运营人员则通过同一端点上的 admin.* 工具进行操作。
实测数据,而非承诺:在 200 个并发调用下,p95 为 34.2 ms,且零错误;
结果与生成该数据的测试一起提交。公共网站是 mcphost.dev;
机器可读摘要 /llms.txt 与上方的快速入门指南
(docs/agent-quickstart.md、scripts/gen-agent-docs.sh)由同一来源生成。
连接
在客户端中粘贴一行配置,即可接入 mcphost。注册无需密钥——signup 是唯一无需身份验证的工具;此后所有操作都需要使用 signup 返回的 bearer key。
Claude Code
claude mcp add --transport http mcphost https://mcphost.dev/mcp
文档:https://code.claude.com/docs/en/mcp(检查于 2026-10-09)
Cursor
添加到 Cursor,或直接将 https://mcphost.dev/mcp 添加到 MCP 配置中。
文档:https://docs.cursor.com/en/tools/mcp(检查于 2026-10-09)
VS Code
添加到 VS Code,或直接将 https://mcphost.dev/mcp 添加到 MCP 配置中。
文档:https://code.visualstudio.com/api/extension-guides/ai/mcp(检查于 2026-10-09)
Claude.ai
- 打开“设置”,然后依次选择“连接器”和“添加自定义连接器”。
- 名称:mcphost
- 远程 MCP 服务 URL:https://mcphost.dev/login/mcp
- 保存,然后在聊天中启用该连接器以连接。
文档:https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp(检查于 2026-10-09)
ChatGPT
- 打开 ChatGPT,然后依次选择“插件”和“添加”。
- 选择“创建自定义 MCP 服务”。
- 名称:mcphost
- MCP 服务 URL:https://mcphost.dev/login/mcp
- 选择 OAuth,然后选择“创建”。在授权页面中,点击“创建新工作区”。
文档:https://platform.openai.com/docs/mcp(检查于 2026-10-09)
Codex CLI
codex mcp add mcphost --url https://mcphost.dev/mcp
文档:https://developers.openai.com/codex/mcp(检查于 2026-10-09)
Gemini CLI
gemini mcp add --transport http mcphost https://mcphost.dev/mcp
文档:https://geminicli.com/docs/tools/mcp-server/(检查于 2026-10-09)
OpenCode
opencode mcp add mcphost --url https://mcphost.dev/mcp
文档:https://opencode.ai/docs/mcp-servers/(检查于 2026-10-09)
Amp
amp mcp add mcphost https://mcphost.dev/mcp
文档:https://ampcode.com/manual#mcp(检查于 2026-10-09)
Goose
- 在 Goose 会话中输入 /extension。
- 选择“添加远程扩展”(Streamable HTTP)。
- 名称:mcphost
- 端点 URL:https://mcphost.dev/mcp
文档:https://goose-docs.ai/docs/getting-started/using-extensions/(检查于 2026-10-09)
Warp
- 在 Warp Agent 会话中输入 /agent-add-mcp。
- 粘贴此服务配置:{"mcphost":{"url":"https://mcphost.dev/mcp"}}
- 保存;Warp 会启动服务并列出其工具。
文档:https://docs.warp.dev/agents/capabilities/mcp/(检查于 2026-10-09)
Windsurf
{
"mcpServers": {
"mcphost": {
"url": "https://mcphost.dev/mcp"
}
}
}
文档:https://docs.windsurf.com/windsurf/cascade/mcp(检查于 2026-10-09)
Cline / Roo
{
"mcpServers": {
"mcphost": {
"type": "streamableHttp",
"url": "https://mcphost.dev/mcp"
}
}
}
文档:https://docs.cline.bot/mcp/configuring-mcp-servers(检查于 2026-10-09)
在将生产流量指向此服务之前,请查看实时状态页面和可接受使用政策。计划和限制:计划 / /plans.json(数值与 billing.plans 和 docs/plans.md 返回的相同)。每个错误载荷都包含一个 help_url,指向自动生成的 /help/<code> 页面,其中说明了相应错误。
支持:未配置支持渠道(MCPHOST_SUPPORT_URL 未设置)。
变更
当前版本:v0.64.0(2026-09-30);main 为 0.65.0。所有变更都记录在 CHANGELOG.md 和 git 标签中。
运行与贡献
以下内容适用于运行你自己的 mcphost 或修改此项目:安装、环境、类型、限制、计量、合成租户和验收套件。
安装
cargo install --path .
或在本地构建:
cargo build --release
./target/release/mcphost serve
环境约定
| 变量 | 含义 | 默认值 |
|---|---|---|
MCPHOST_DATA_DIR | 存放 mcphost.db(SQLite,WAL)的目录 | ./data |
MCPHOST_BIND | 要监听的 host:port | 127.0.0.1:8080 |
MCPHOST_PUBLIC_URL | signup 返回的端点 URL | http://<bind> |
MCPHOST_ADMIN_KEY | 用于解锁 admin.* 工具的 Bearer 密钥 | 未设置(无法访问 admin 工具) |
MCPHOST_SECRET_KEY | 用作租户密钥的密码短语,将通过 SHA-256 派生为 AES-256 密钥 | 开发环境默认值(生产环境请设置为真实密钥) |
MCPHOST_LOG_LEVEL | tracing 过滤器,例如 info | info |
MCPHOST_REGISTRY_URL | 启用 host.registry_publish(P1),并指定注册表 API 的基础 URL;mcphost serve --registry-url <url> 的优先级更高 | 未设置(禁用 registry-publish) |
MCPHOST_SIGNUP_RATE_LIMIT_PER_HOUR | 覆盖按来源 IP 计算的 signup 速率限制(PRD-mcphost-signup-rate-configurable)——从同一 IP 执行多会话测量时可调高;未设置或值非整数时使用默认值。生效值会在启动时记录一次。配置在 MCPHOST_FLEET_IPS 中的来源 IP 完全不受此限制 | 5 |
MCPHOST_EGRESS_PROXY | 运维人员出站 HTTP(S) 代理的 http(s)://host:port。pro 租户发布的 network: "public" 或 "egress" python/wasm 工具若要获得任何沙箱网络访问权限,此项为必需——见下文“出口代理” | 未设置(pro 租户均无法访问出站网络) |
MCPHOST_FLEET_IPS | 以逗号分隔的 IPv4/IPv6 地址和/或 CIDR 网段列表(例如 46.225.110.44,178.105.64.66,10.0.0.0/8),表示此运维人员的自有集群注册时使用的地址。来源 IP 匹配的注册会被赋予 source_class: fleet(synthetic: harness:fleet-ip),即使没有 x-mcphost-synthetic 标头也一样——无效条目会被记录并跳过。若要回填此变量启用前的注册,请参阅 admin.reclassify_fleet_ips | 未设置(不会仅凭地址将任何 IP 归类为 fleet) |
mcphost migrate 会应用待处理的 SQL 迁移,然后退出。mcphost version
会打印版本号,然后退出。mcphost serve --registry-url <url> 是上述
MCPHOST_REGISTRY_URL 的 CLI 标志形式。
出口代理(network: "public" / "egress")
free 租户永远无法发布 network: "public" 或 "egress" 的工具——
两种写法授予相同的沙箱访问权限,且都会在 host.tool_publish 处被拒绝,
返回 plan_required,并指出 pro 和 network 字段。pro 租户可以
发布此类工具,但除非在此主机上配置了 $MCPHOST_EGRESS_PROXY,否则
沙箱在调用时仍无法访问出站网络——未设置该变量时,调用会在启动任何
沙箱进程之前失败,并返回 egress_unavailable。如果某个现有工具的
所有者为 pro 时声明了 public/egress,那么该租户降为 free 后,
此工具就会开始返回 plan_required(而不会静默降级为无网络);错误会
指出可通过重新发布并设置 network: "none" 修复,或升级回 pro。
配置代理后,pro 租户的出口调用会使用 --share-net(其出站网络与
主机自身的网络命名空间共享),并在沙箱进程的环境中将
http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY 全部设为
$MCPHOST_EGRESS_PROXY,同时设置 no_proxy="",确保运维人员环境中
残留的 NO_PROXY 无法让沙箱进程绕过代理。本 crate 不会随附代理
(该配置由 mcphost-deploy 负责)——它只保证“没有代理就没有网络”。
部署的代理必须对其转发的每个连接实施私有/链路本地地址拒绝列表,
这些范围与 http 类型自身的 VettingResolver 对渲染后的请求 URL
拒绝的范围相同:
- RFC 1918 私有网段(
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16) 169.254.0.0/16(链路本地,包括云元数据地址169.254.169.254)127.0.0.0/8(环回地址)和::1fc00::/7(IPv6 唯一本地地址)
Registry 发布(P1)
默认关闭。一旦 --registry-url / $MCPHOST_REGISTRY_URL 指定了某个注册表 API 基础 URL(即注册表自身的基础 URL):
- 运营方通过自己信任的任意方式验证某个租户的域名命名空间(PRD 将验证方法本身——DNS 还是 HTTP 记录——留作开放问题,由 Joe 负责;此 crate 不实现验证方法),并使用
admin.tenant_verify_namespace记录验证结果:admin.tenant_verify_namespace(tenant="t_xxxxxxxx", domain_namespace="io.github.example.myserver")。 - 该租户随后便可调用
host.registry_publish()(不带参数):它会将server.json文档(name/description/version/remotes: [{type: "streamable-http", url}])POST 到<registry-url>/v0/publish,同一文档也可通过GET /.well-known/mcp/<namespace>/server.json免认证获取。 - 如果出现以下情况,
host.registry_publish会以data.error_code中不同且机器可读的错误拒绝请求:registry_disabled(标志关闭)、namespace_unverified(该租户尚未完成步骤 1),或registry_rejected(注册表 API 返回非 2xx)。
此主机自身的注册表条目
这与上面的“Registry 发布(P1)”不同,后者是面向租户、用于发布租户自身命名空间的工具;这里指的是 mcphost 自身的条目。
mcphost registry-manifest 根据 Cargo.toml(DNS 验证的 dev.mcphost 命名空间下的名称、描述、版本、许可证)以及 $MCPHOST_PUBLIC_URL(默认值为 https://mcphost.dev)+ /mcp 作为 streamable-http 远程服务,构建公共 MCP 注册表的 server.json 条目:
- 不带标志:将条目打印到标准输出。
--write <path>:写入条目(用于重新生成已提交的registry/server.json,不得手动编辑)。--check:将已提交的文件(默认为registry/server.json)与最新生成的内容进行比较;如果有差异,则输出差异并以非零状态码退出,但会忽略version字段。一次发布落地时会提交registry/server.json,之后才会更新Cargo.toml中的版本号,因此,在 PRD 落地后到下一个标签发布前,已提交文件中的版本号会有意保持过时;否则,按设计,--check会仅因该文件版本号不匹配而导致每次此类发布落地都失败。其他任何差异(描述、名称、许可证、仓库、远程服务)仍会像以前一样导致--check失败。
.github/workflows/registry.yml 会在每次推送时运行 --check,并根据固定版本的 registry/schema.json 验证架构;在 v* 标签上,它会先使用 --write 重新生成 registry/server.json(因此,发布的条目会包含刚打标签的真实版本,即使仓库中已提交的副本仍保留过时的版本号),然后通过仓库密钥 MCPHOST_REGISTRY_PRIVATE_KEY 将其发布到注册表(该密钥是供注册表发布 CLI 使用的 dev.mcphost 域名命名空间 DNS 登录密钥)。
类型
下方列出的每种已注册类型的最简示例规范,以及 host.tool_publish 在线传输的说明(注册前可通过 tools/list 查看),都由相同的 docs/kinds/*.md 文件生成(PRD-mcphost-publish-first-try 要求 6)——tests/publishfirsttry_ac06_docs_shared_source.rs 会从这些文件重新生成本节;如果生成内容与下方已提交的内容不一致,CI 就会失败。调用 host.quickstart(kind) 可获取相同示例,并自动填入你自己的命名空间。
echo
spec.schema 可以是任意 JSON Schema;调用时会回显传入的参数,并根据该架构进行验证。
示例规范:
{
"schema": {
"properties": {
"msg": {
"type": "string"
}
},
"required": [
"msg"
],
"type": "object"
}
}
示例调用参数:
{
"msg": "hi"
}
http
url 必须是绝对 https URL;method 和 url 是仅有的必填字段——如果未提供 args_schema,则会根据 url/header/body 模板推断。
示例规范:
{
"method": "GET",
"url": "https://api.example.com/items/{{id}}"
}
示例调用参数:
{
"id": "123"
}
python
只需提供 source——args_schema 和 requirements 都会根据它推断(tool-infer,v0.4.0);source 必须定义 main(args)。
示例规范:
{
"source": "def main(args):\n return {\"doubled\": args[\"n\"] * 2}\n"
}
示例调用参数:
{
"n": 3
}
js
只需提供 source——系统会根据其中对 args.<name> 的读取(以及任何 JSDoc @param {type} args.<name>)推断 args_schema;source 必须定义 function main(args)(或 export default),且须为普通 JavaScript(ES2020),由 QuickJS 在与 python 相同的沙箱中运行。
规格示例:
{
"source": "function main(args) {\n return { total: args.items.reduce((a, b) => a + b.price, 0) };\n}\n"
}
调用参数示例:
{
"items": [
{
"price": 2
},
{
"price": 5
}
]
}
wasm
component 是经过 base64 编码的 WebAssembly component(component-model,而非 core module),导出 call: func(args: string) -> result<string, string>;args_schema 为可选项(默认接受任意对象)。
规格示例:
{
"component": "AGFzbQEAAAAA"
}
调用参数示例:
{
"msg": "hi"
}
Python 规格语言说明
PRD-mcphost-python-kind-runtime(AC6):用于门控 host.tool_publish 的 AST 检查通常接受赋值表达式(:=,PEP 572)——CPython 从 3.8 起就能解析它们,而且 mcphost 的发布时检查和工具自身的运行时都会使用相同的 CPython 语法规则编译 source,因此 mcphost 并未额外施加需要放宽的限制。唯一会被拒绝的是 Python 自身语法规则强制执行的一项限制:赋值表达式的目标必须是普通名称。(obj.attr := 1) 和 (d[key] := 1) 都是无效的 Python 语法(cannot use assignment expressions with attribute / ...with subscript),无论 mcphost 是否先验证它们,都会以相同方式失败——工具自身的 main(args) 根本无法解析。由于这是执行器层面的问题,而非验证器层面的问题,因此 mcphost 没有什么可放宽的;此处的修复是,发布时的拒绝提示现在会用一句话指出具体结构和可接受的替代写法(先赋值给普通名称,再在单独的语句中设置属性或下标),而不是任由 CPython 原样输出语法错误。
调用限制
PRD-mcphost-call-limits-honest:此处的每项限制都与代码实际执行的限制一致——tests/limits_ac06_quickstart_docs_match_constants.rs 会检查本节和 www/llms.txt 的“限制和定价”一节是否与 host.quickstart 的 limits 对象读取的同一组常量一致。
- 调用超时:默认 30 s,或使用你自己的
timeout_s,最长为 60 s ——声明了timeout_s的 python 规格会获得恰好为该值的截止时间 (但受 60 s 的主机上限约束),不会在后台被静默套用更短的时间。call_timeout表示实际生效的截止时间。 - 输出大小:工具输出最多为 1 MiB。超过上限时会返回
tool_output_too_large,并指出limit_bytes和实际生成的actual_bytes,绝不会仅返回一个笼统的tool_output_invalid解析失败。 - 请求正文:最多为 1 MiB(超出时返回 HTTP 413——参见
tests/ac16_request_body_too_large.rs;该 AC 自身说明中的“2 MiB”是用于 证明 1 MiB 上限的超大测试负载,并非上限本身)。 - 并发数:主机范围内最多同时进行 20 次调用;每个租户在免费套餐下最多 4 次
(专业版为 10 次)。超过你所在租户自身上限时会拒绝请求,返回
concurrency_exceeded,并包含in_flight、limit、scope: "tenant"和retry_after_ms;超过主机范围上限时,scope为"host"。 - 沙箱进程上限:python 工具的沙箱最多允许 64 个存活进程;
超出此限制的 fork 会以结构化错误
tool_process_limit失败,而不会静默挂起或返回含糊的操作系统错误。
按量超额计费(billing emit-meter)
PRD-mcphost-metered-overage:专业版租户每月超过套餐内含的 50,000 次调用后,成功调用的费用会通过 Stripe 的 mcphost_tool_calls 计量器及其阶梯式按量计费价格单独结算。根据 ~/.config/mcphost/stripe-objects.json 设置以下环境变量(未设置时行为与 v0.14.0 相同——不进行计量,也没有 meter_lag):
MCPHOST_STRIPE_METERED_PRICE_ID——billing.checkout附加在基础价格旁的按量计费价格 ID。MCPHOST_STRIPE_METER_EVENT_NAME—— 默认为mcphost_tool_calls。
然后定时运行 mcphost billing emit-meter(已提供的默认频率为每五分钟一次):它读取专业版租户尚未发送的 ok 调用,按每个租户向 Stripe POST 一条计量事件(每个请求分批发送,最多 100 条事件),为每个批次记账,并且只有在本次运行中的所有事件均已被接受后,才推进自身的最高水位线——因此在崩溃或 POST 失败后重新运行也是安全的(重放/幂等性约定请参阅 src/metering.rs 的文档注释)。
安装随附的 systemd 用户单元(~/.config/systemd/user/,与此主机上的其他 mcphost-* 单元一致):
cp deploy/mcphost-emit-meter.service deploy/mcphost-emit-meter.timer \
~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mcphost-emit-meter.timer
mcphost-emit-meter.service 从 ~/.config/mcphost/emit-meter.env 读取环境变量(通过 EnvironmentFile=-,因此文件缺失不会报错),包括 MCPHOST_DATA_DIR/MCPHOST_STRIPE_SECRET_KEY/上述两个变量。
两个单元文件均通过 systemd-analyze verify --user 验证(AC9;tests/metering_ac09_deploy_units_verify.rs)。
/healthz 的 meter_lag 字段(仅当设置了 MCPHOST_STRIPE_METERED_PRICE_ID 时存在)表示仍高于最高水位线的专业版租户 ok 调用数量——可通过该字段快速查看发送状态。
合成租户(admin.*_synthetic)
PRD-mcphost-synthetic-flag:测试框架创建的租户从注册开始就带有自由格式的 synthetic 标签(例如 synthorg:<run_id>);框架在 signup 调用中发送 x-mcphost-synthetic: <label> 来设置此标签——不会改变行为,仅添加元数据。/healthz 中的 tenants_real/tenants_synthetic 划分,以及 admin.tenants 的 synthetic 筛选项(true/false/all,默认为 all),均读取此列,因此 synthorg candidates --measure 可以排除面板流量,不将其作为“真实租户”证据。
回填现有租户清单(此列添加前已有的所有租户,在打标签前其值均为 synthetic: null):使用 admin.tenants_set_synthetic,先通过 dry_run: true 预览,再设置 dry_run: false 应用更改。此主机自身租户清单采用的操作步骤:
admin.tenants_set_synthetic(name_like: 'joe-%', label: 'operator', dry_run: true)
admin.tenants_set_synthetic(name_like: 'joe-%', label: 'operator', dry_run: false)
admin.tenants_set_synthetic(name_like: '%', label: 'synthorg:backfill-20260906', dry_run: true)
admin.tenants_set_synthetic(name_like: '%', label: 'synthorg:backfill-20260906', dry_run: false)
先运行 joe-* 批次——否则,第二次调用中范围更广的 % 模式也会覆盖这些行的标签,因为被后续调用重新标记的租户会直接使用后续标签(这是有意设计的,不设“已有标签则跳过”保护:重新标记是修正标签的唯一方式)。任何时候都可以单独修正某个租户:使用 admin.tenant_set_synthetic(tenant, label)(label: null 会清除标签)。
验收
每项 P0 验收标准都对应一个真实的 cargo test(tests/ 下的集成测试会在临时 $MCPHOST_DATA_DIR 上通过临时端口启动服务),但依赖硬件的 AC11 除外;其冒烟测试结果记录如下。
沙盒套件:用户命名空间要求
python 类型的沙箱测试套件(tests/sandboxready_*、python_ac*、
infer_ac*、warmpool_ac*、ac17_kind_conformance)会启动真正的 bwrap/
unshare 隔离环境,因此需要非特权用户命名空间
(unshare --user --map-root-user -- true 必须成功)才能实际运行。如果
你的机器不允许这样做(某些内核上的 Ubuntu 默认 AppArmor 策略、某些容器运行时),
在 CI 之外运行 cargo test 会按设计明确报错,并指出修复方法:较旧内核上使用
sysctl kernel.unprivileged_userns_clone=1,或 Ubuntu 24.04+ 上使用
sysctl kernel.apparmor_restrict_unprivileged_userns=0。完整约定请参阅
sandbox::require_user_namespaces_or_ci_skip 的文档注释;至于托管 CI 运行器如何授予
相同能力(PRD-mcphost-ci-sandbox-coverage),而不是静默跳过,请参阅
.github/workflows/ci.yml。
CI 会将这些测试套件作为独立的 sandbox 作业运行,与核心作业并行
(静态分析使用 core-static,其余测试使用 core 分片矩阵,还有一个名为
“静态分析 + 核心测试套件”的 gate 汇总作业;见下文“核心测试套件也会分片”)——这些测试套件停止
跳过后,单个 cargo test --workspace 步骤耗时 313–336 秒,超过了 300 秒的预算。哪些测试套件二进制文件
应放在哪里,是通过规则推导出来的,而非手动列出:scripts/ci-test-partition.sh core|sandbox
会根据每个 tests/*.rs 文件是否涉及沙箱执行
部分,将其分类(PRD-mcphost-test-suite-consolidation 将单元 cargo 链接从
“每个文件对应一个二进制文件”改为少量 tests/suite_<core|sandbox>_NN.rs
二进制文件——见下文“添加测试”——因此分区现在是文件→测试套件,
而非文件→二进制文件),并且 check 会验证两个层级上的划分既完整又互斥。两个作业都会在日志中发现任何能力跳过时失败,因此文件
被分到错误的一半会导致 CI 变红,而非空跑通过。
sandbox 作业使用按耗时平衡而非按位置划分的分片矩阵
(PRD-mcphost-ci-sandbox-shards-balanced):scripts/ci-suite-weights.json
记录每个沙箱测试套件二进制文件以及 --lib 的耗时(秒),数据取自最新一次通过的 CI 运行中的
finished in 行;ci-test-partition.sh
会将最耗时的测试套件优先放入当前耗时最短的分片。分片数量只在一处定义
(SANDBOX_SHARDS,由 ci-test-partition.sh shards 打印);工作流的
plan 作业会据此生成矩阵和作业名称。要更新权重,请从分片步骤的
suite=<name> measured=<s>s recorded=<s>s 行(当分片运行耗时达到
240 秒时会打印)复制每个测试套件的 finished in 秒数。ci-test-partition.sh check 会校验该文件:沙箱测试套件缺少权重,或预测最耗时分片超过 240 秒,都会导致
校验失败,并指出测试套件和文件。每个分片都会在步骤摘要中报告 build wall 和 run wall;只有运行耗时计入预算(240 秒时警告,超过 300 秒时失败)。
核心测试套件也已分片处理(PRD-mcphost-ci-core-suites-sharded):必需检查“静态分析 + 核心测试套件”原本是一个串行任务,仅核心步骤就要运行 13 到 16 分钟,因此 main 的运行耗时达到 20 到 30 分钟,逼近 30 分钟上限。现在,core-static 执行静态步骤(check、clippy、契约测试套件、文档测试、deny、audit),core 则是按权重均衡分片的矩阵(ci-test-partition.sh core-shard <n> <of>,使用与 sandbox 分片相同的 scripts/ci-suite-weights.json 和优先放置最重测试套件的策略);任务 gate 保留原名,作为一个小型汇总任务,只有两者都成功才会通过,因此分支保护和 .github/required-checks.toml 均未更改(静态任务和分片任务带有 # required-checks: exclude 标记)。核心分片数只定义一次(CORE_SHARDS,由 ci-test-partition.sh shards core 输出);如果某个核心测试套件没有权重,或预测的最重核心分片超过 420 s,check 就会报错并指出该测试套件。每个分片都会在步骤摘要中输出 build wall、run wall,以及每个测试套件对应的一行 suite=<name> measured=<s>s recorded=<s>s(运行耗时达到 540 s 时警告,超过 720 s 时失败;在 10-17 上运行缓慢的托管 Runner 实测 suite_core_08 耗时 559 s,而记录值为 375 s);如果测试失败,则会输出 core shard N/M FAILED <test>,方便通过 gh run view --log-failed 查看。
添加测试
tests/*.rs 不再是 Cargo 发现测试二进制的单位(PRD-mcphost-test-suite-consolidation,2026-09-12):在 Cargo.toml 中设置 autotests = false,再加上少量生成的 tests/suite_<core|sandbox>_NN.rs 文件,通过 #[path] 引入实际文件,从而避免 target/debug/deps 中为每个测试文件都存放一个约 280 MB 的二进制文件。每个测试仍保留自己的文件、名称和 AC 配对方式——改变的只有它链接到哪个 BINARY。
添加测试:照常新建 tests/<name>.rs(命名约定不变:<prefix>_ac<N>_<description>.rs;如果需要共享测试框架,则添加 mod common;),然后运行 scripts/gen-test-suites.sh 将其归入某个测试套件(也可以等 CI 提醒你——scripts/gen-test-suites.sh --check 已接入 ci-test-partition.sh check;如果忘记运行生成器,它会报错并指出确切文件)。生成器按文件名前缀分组,先将需要 sandbox 的文件与仅需 core 的文件分开(因此不会有测试套件混用两者——见上文),并将新文件中独立的顶层 mod common;/mod ci_sandbox_support; 行改写为 use crate::common;/use crate::ci_sandbox_support;(这些模块现在每个测试套件只编译一次,而不是每个文件编译一次)——不改动其他行。切勿手动编辑 tests/suite_*.rs 文件;这些文件会被完全重新生成。
现在按名称运行单个测试需要多加一个参数:cargo test --test suite_core_01 my_test_file:: -- --nocapture(也可以使用 cargo nextest run -E 'test(my_test_file::)',无需指定测试套件名称)。单独运行 cargo test --test my_test_file 将不再解析成功——该文件不再是独立的 Cargo 目标。
| AC | 要求 | 测试 |
|---|---|---|
| 1 (P0) | 未认证的 tools/list 仅显示 signup;响应包含 MCP-Protocol-Version | tests/ac01_unauthenticated_lists_signup.rs |
| 2 (P0) | signup 返回密钥/命名空间/端点;密钥仅以哈希形式存储 | tests/ac02_signup_creates_hashed_tenant.rs |
| 3 (P0) | 租户的 tools/list 显示 host.*,且不显示其他租户的工具 | tests/ac03_tenant_lists_control_plane_only.rs |
| 4 (P0) | 发布、列出,然后调用,流程往返成功 | tests/ac04_publish_list_and_call.rs |
| 5 (P0) | 跨租户隔离:B 无法查看或调用 A 的工具 | tests/ac05_cross_tenant_isolation.rs |
| 6 (P0) | 移除工具:该工具不再出现在列表中,调用时返回 tool_not_found | tests/ac06_remove_tool.rs |
| 7 (P0) | host.usage/admin.usage 报告调用次数及 p50/p95 | tests/ac07_usage_metering.rs |
| 8 (P0) | admin.tenant_disable 会锁定密钥;租户密钥调用 admin.tenants 时返回 forbidden | tests/ac08_admin_disable_and_forbidden.rs |
| 9 (P0) | 每小时每个 IP 的第 6 次注册返回 rate_limited,且不会创建租户 | tests/ac09_signup_rate_limit.rs |
| 10 (P0) | 未注册的类型、无效名称、超大规格分别报错,且不会写入任何内容 | tests/ac10_publish_validation_errors.rs |
| 11 (P0,非功能性) | 200 个并发 echo 调用,p95 < 50ms,错误数为 0,RSS < 100MiB | tests/ac11_load_smoke.rs(已标记 #[ignore] — 依赖硬件;使用 cargo test --release --test suite_core_01 ac11_load_smoke:: -- --ignored --nocapture 运行)。在构建机上的测量结果:p95 = 34.20ms,错误数为 0,RSS = 37.3MiB |
| 12 (P0) | synthorg consume --preflight <url> 以状态码 0 退出 | tests/ac12_preflight.rs — 始终运行的进程内部分会执行 run_preflight 发出的同样两个请求;如果可用,第二部分会启动真实的 mcphost 二进制文件和真实的 synthorg CLI(直接运行二进制文件或通过 uv run --project),并断言退出状态码为 0 |
| 13 (P0) | Mcp-Name 请求头与请求正文不匹配时,按正文中的名称记录并标记 | tests/ac13_mcp_name_mismatch_metering.rs |
| 14 (P0) | 数据库不可写:返回 storage 错误,/healthz 显示 db_ok: false,进程保持运行 | tests/ac14_storage_unwritable.rs |
| 15 (P0) | 永不完成的调用会在截止时间超时,并丢弃 future | tests/ac15_call_timeout.rs |
| 16 (P0) | 超过 1 MiB 上限的请求正文(通过 2MiB 正文验证)会被拒绝,并返回 HTTP 413 | tests/ac16_request_body_too_large.rs |
| 17 (P0) | Kind 一致性测试套件通过 echo 测试;对于错误的 schema,describe 测试失败并指出名称 | tests/ac17_kind_conformance.rs(可复用的检查器位于 mcphost::kinds::conformance) |
| 18 (P1) | tools/list 包含 ttlMs/cacheScope;发布后的 60s 内 ttlMs: 0 | tests/ac18_tools_list_ttl.rs |
| 19 (P1) | host.registry_publish() + /.well-known/mcp/<ns>/server.json | tests/ac19_registry_publish.rs(使用 wiremock 模拟注册表 API;参见上文“注册表发布(P1)” — 命名空间验证方法不在范围内,“已验证”是由管理员设置的布尔值) |
相关服务群工作
mcp-core(私有仓库,无法公开链接)— 可复用的 stdio JSON-RPC 2.0 MCP 服务核心(Tooltrait +serve_stdio),其他 wintermute MCP 服务均基于此构建。此处未复用:mcphost是一个可流式传输的 HTTP 服务(rmcp),而非 stdio 服务;其工具面是动态的(按租户划分,由数据库 支持),而不是由mcp-core封装的静态Tooltrait。根据 PRD 的技术考量, 此处将其列为相关代码,而非共享代码。
许可证
采用 MIT OR Apache-2.0 双重许可 — 详见 LICENSE-MIT 和
LICENSE-APACHE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。