③ 问答
Mirobody MCP Server
/mcp 端点:本地客户端、远程 HTTPS 访问、个人 URL 与 OAuth
Mirobody 把自己的工具以 JSON-RPC 2.0 暴露在 /mcp。这与引擎自己的 agent 读的是同一份注册表,因此 Claude Desktop、Cursor 这类客户端调用到的,
和你的 agent 调用的是同一批工具,包括你自己加的那些。
服务监听 HTTP_HOST:HTTP_PORT,在发行版 compose.yaml 里是 0.0.0.0:18060。
本页所有 URL 都按这个端口写。
同时作为 server 与 client
Section titled “同时作为 server 与 client”Mirobody 既是 MCP 服务端,也有 MCP 客户端的一侧,这是两套独立的机制:
| 方向 | 是什么 |
|---|---|
| 向外给你 | 被发现的那些工具,暴露在 /mcp |
| 向内给 Mirobody | 按用户维护的外部 MCP 服务器注册表,用 tools/list 拉取并转成函数描述 |
用户的外部服务器按账号存储,由三个 JSON 端点管理:GET/POST /api/user/mcp 读整张表,
POST /api/user/mcp/set 新增或替换一项,POST /api/user/mcp/delete 删除一项。
每一项以名字为键:
{ "my-server": { "url": "https://example.com/mcp", "token": "…可选的 bearer token…", "enabled": true, "order": 0 }}加载器遍历这张表,跳过 enabled: false 或 url 为空的项,对其余每一项以
Authorization: Bearer <token> 发出 tools/list(没配单独 token 时回退用调用者自己的
JWT),再把返回的每个工具按其 name、description、inputSchema 转成一条函数描述。
一共注册了三条路由,区别只在于怎么识别调用者:
| 路由 | 靠什么识别调用者 | 用途 |
|---|---|---|
POST /mcp | Authorization: Bearer <JWT> | 常规端点。对不需要身份的工具即匿名可用。 |
POST /mcp/{secret} | 路径里的 secret | 个人 URL,给发不了请求头的客户端用。 |
POST /personal/mcp | Authorization: Bearer <JWT> | 它本身不是 MCP:用来生成并返回你的个人 URL。 |
服务端实现的方法
Section titled “服务端实现的方法”| 方法 | 行为 |
|---|---|
initialize | 协商协议版本(见下),并报告取自 HTTP_SERVER_NAME 的 serverInfo 名称。 |
server/discover | 2026-07-28 的无状态发现方法。它公布的能力与 initialize 完全一致:两者共用同一份声明,所以不可能漂移。 |
notifications/initialized | 接受,返回空 200。 |
ping | 返回空结果。 |
tools/list | 全部被发现的工具,减去这个账号没有对应数据的那些。不需要凭据。 |
tools/call | 按 name 执行一个工具,参数放在 arguments 对象里。 |
resources/list / resources/read | MCP_RESOURCE_DIRS 里找到的 resources。读取时服务端会把当前服务地址与调用者的 token 替换进 resource 文本。 |
prompts/list | 永远是空列表,引擎不提供 MCP prompts。 |
服务端说的是 MCP 2026-07-28,也就是当前这版无状态修订:per-request _meta、server/discover、必填的 resultType、确定性的工具排序,以及不再有 session id。在这版修订下根本没有握手,所以客户端的第一个请求就可以是 tools/list。
它会为更老的客户端向下协商,接受 2026-07-28、2025-11-25、2025-06-18、2025-03-26 与 2024-11-05。遇到它不认识的版本,就用自己最新的那个回答;遇到明确不支持的,返回规范定义的 -32022。
按数据可用性门控
Section titled “按数据可用性门控”tools/list 是按账号如实回答的。列出之前,服务端会探测解析出来的调用者是否确实持有每一类数据:没有健康记录行就隐藏 query_health_indicators,没有基因型数据行就隐藏 get_genetic_data。一个只能回答「你没有数据」的工具,不值得让每个客户端都背一份它的 schema。
这个探测失败时按放开处理:数据库抖一下,什么都不隐藏,因为把一个确实有数据的用户的工具面缩掉,是更糟的那种失败。
其它方法一律返回 JSON-RPC -32601(方法未找到)。请求体解析不了是 -32700,没有 method
是 -32600;tools/call 缺 params、缺工具名、或者给了服务端没有的工具名,都是 -32602。
调用者的解析方式
Section titled “调用者的解析方式”只有声明了 user_info 参数的工具才需要身份。对这些工具,tools/call 会依次尝试三件事,
都不成才放弃:
-
Authorization请求头 校验这个 JWT,把它的sub声明当作user_id - URL 里的 secret 先查临时 secret(它还可能带着一个会话和一个 agent 名),再查永久的个人 secret
- 都没有,那就请用户去登录 这次调用返回一个指向
/mcplogin的授权 URL,外加一组轮询参数
当 URL 里的 secret 带着 agent 名时,tools/list 会先过该 agent 的 ALLOWED_TOOLS_{NAME}
白名单,再过它的 DISALLOWED_TOOLS_{NAME} 黑名单。注意这条过滤路径与常规路径不是同一段
代码:两个键都没配时它返回的是空列表,所以带 agent 的 URL 至少要配上其中一个。
接一个本地客户端
Section titled “接一个本地客户端”Claude Desktop 与 Cursor 走 stdio 说 MCP,所以要用一个代理把它们桥到 HTTP 端点上。 把下面这段写进你客户端的 MCP 配置文件:
{ "mcpServers": { "mirobody_mcp": { "command": "npx", "args": [ "-y", "universal-mcp-proxy" ], "env": { "UMCP_ENDPOINT": "http://localhost:18060/mcp" } } }}先确认服务应答
curl -sX POST http://localhost:18060/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'返回一个带 tools 数组的 JSON-RPC 结果,说明端点通了、注册表也加载上了。
加配置
把上面那段 JSON 写进客户端的 MCP 配置文件,然后把客户端完全重启: 多数客户端只在启动时读这个文件。
确认工具出现了
Mirobody 应该出现在客户端的 MCP 服务器列表里,下面挂着 tools/list 里的那些工具名。
问一句需要用工具的话
「在我的健康指标里搜 HbA1c。」第一次需要身份的调用会触发上面讲的登录流程; 此后每次调用都解析到同一个账号。
http://localhost:18060/mcp 只对同一台机器上的客户端有效。远程客户端(云上部署、
一个 ChatGPT App、同事的笔记本)需要在 config.{env}.yaml 里把 MCP_PUBLIC_URL
设成一个公网可达的 HTTPS 源:
MCP_PUBLIC_URL: 'https://yourdomain.com'当引擎必须把一个绝对 URL 交给进程之外的组件时,用的就是这个值;启动横幅里让你打开的地址, 也是它。
个人 MCP URL
Section titled “个人 MCP URL”个人 URL 把身份写在路径里,给带不了 Authorization 请求头的客户端用。用你的 JWT 去要一个:
curl -sX POST http://localhost:18060/personal/mcp \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{}'# → {"code":0,"data":{"url":"http://localhost:18060/mcp/<secret>"}}这个 secret 是 96 字节的 URL-safe 随机串,在 Redis 里缓存一年,过期之前再要还是同一个 URL。
把空请求体换成 {"user_id": "…"},可以为另一个账号生成 URL:仅当那个账号把对话权限
分享给了你才允许,服务端会先核对这层关系。
引擎内部还会生成短命的变体:十分钟过期,并带上一个会话 id 与一个 agent 名,
正是它把 tools/list 收窄到某一个 agent 的工具集。
对于走完整 OAuth 握手的客户端,服务端在三个 well-known 路径上发布同一份元数据文档:
/.well-known/oauth-authorization-server、同一路径后面加 /mcp、以及
/.well-known/mcp-configuration。三者内容完全相同,并且注册在服务根路径上,
因此 HTTP_URI_PREFIX 对它们不生效。
这份文档声明了端点与整个流程的形状:
| 字段 | 值 |
|---|---|
authorization_endpoint | /oauth/authorize |
token_endpoint | /oauth/token |
registration_endpoint | /oauth/register(动态客户端注册是开放的) |
introspection_endpoint | /oauth/introspect |
grant_types_supported | authorization_code、refresh_token、client_credentials |
code_challenge_methods_supported | S256(只支持 PKCE) |
scopes_supported | openid、profile、email、offline_access,外加 mcp:read、mcp:write、mcp:tools、mcp:admin、mcp:connect |
另有一个 /oauth2/authorize 别名,以及 /oauth2/check_state/{state}:
设备式登录的轮询循环,就是靠它来确认浏览器那一步做完了没有。
用 curl 调用
Section titled “用 curl 调用”tools/list 不需要凭据,因此这是看清一台服务提供了什么的最快办法:
curl -sX POST http://localhost:18060/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'对声明了 user_info 的工具做 tools/call 就需要身份:这里用 bearer JWT,
或者把常规路径换成个人 URL:
curl -sX POST http://localhost:18060/mcp \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "query_health_indicators", "arguments": { "keywords": ["HbA1c", "Hemoglobin A1c"], "aggregate": "stats" } } }'返回结果里有一个 content 数组,装着 JSON 编码后的工具输出;一个 isError 标记,
取自工具自己的 success 字段;输出是结构化的时候,还会多一份 structuredContent。
tools/list 会给你列出什么
把你自己的工具挂到这个端点上
agent、工具与 skills 是怎么拼在一起的
MCP_PUBLIC_URL、HTTP_PORT 与配置的层次