跳转到内容
快速开始

③ 问答

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 都按这个端口写。

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: falseurl 为空的项,对其余每一项以 Authorization: Bearer <token> 发出 tools/list(没配单独 token 时回退用调用者自己的 JWT),再把返回的每个工具按其 namedescriptioninputSchema 转成一条函数描述。

一共注册了三条路由,区别只在于怎么识别调用者:

路由靠什么识别调用者用途
POST /mcpAuthorization: Bearer <JWT>常规端点。对不需要身份的工具即匿名可用。
POST /mcp/{secret}路径里的 secret个人 URL,给发不了请求头的客户端用。
POST /personal/mcpAuthorization: Bearer <JWT>它本身不是 MCP:用来生成并返回你的个人 URL。
方法行为
initialize协商协议版本(见下),并报告取自 HTTP_SERVER_NAMEserverInfo 名称。
server/discover2026-07-28 的无状态发现方法。它公布的能力与 initialize 完全一致:两者共用同一份声明,所以不可能漂移。
notifications/initialized接受,返回空 200
ping返回空结果。
tools/list全部被发现的工具,减去这个账号没有对应数据的那些。不需要凭据。
tools/callname 执行一个工具,参数放在 arguments 对象里。
resources/list / resources/readMCP_RESOURCE_DIRS 里找到的 resources。读取时服务端会把当前服务地址与调用者的 token 替换进 resource 文本。
prompts/list永远是空列表,引擎不提供 MCP prompts。

服务端说的是 MCP 2026-07-28,也就是当前这版无状态修订:per-request _metaserver/discover、必填的 resultType、确定性的工具排序,以及不再有 session id。在这版修订下根本没有握手,所以客户端的第一个请求就可以是 tools/list

它会为更老的客户端向下协商,接受 2026-07-282025-11-252025-06-182025-03-262024-11-05。遇到它不认识的版本,就用自己最新的那个回答;遇到明确不支持的,返回规范定义的 -32022

tools/list 是按账号如实回答的。列出之前,服务端会探测解析出来的调用者是否确实持有每一类数据:没有健康记录行就隐藏 query_health_indicators,没有基因型数据行就隐藏 get_genetic_data。一个只能回答「你没有数据」的工具,不值得让每个客户端都背一份它的 schema。

这个探测失败时按放开处理:数据库抖一下,什么都不隐藏,因为把一个确实有数据的用户的工具面缩掉,是更糟的那种失败。

其它方法一律返回 JSON-RPC -32601(方法未找到)。请求体解析不了是 -32700,没有 method-32600tools/callparams、缺工具名、或者给了服务端没有的工具名,都是 -32602

只有声明了 user_info 参数的工具才需要身份。对这些工具,tools/call 会依次尝试三件事, 都不成才放弃:

  1. Authorization 请求头 校验这个 JWT,把它的 sub 声明当作 user_id
  2. URL 里的 secret 先查临时 secret(它还可能带着一个会话和一个 agent 名),再查永久的个人 secret
  3. 都没有,那就请用户去登录 这次调用返回一个指向 /mcplogin 的授权 URL,外加一组轮询参数

当 URL 里的 secret 带着 agent 名时,tools/list 会先过该 agent 的 ALLOWED_TOOLS_{NAME} 白名单,再过它的 DISALLOWED_TOOLS_{NAME} 黑名单。注意这条过滤路径与常规路径不是同一段 代码:两个键都没配时它返回的是空列表,所以带 agent 的 URL 至少要配上其中一个。

Claude Desktop 与 Cursor 走 stdio 说 MCP,所以要用一个代理把它们桥到 HTTP 端点上。 把下面这段写进你客户端的 MCP 配置文件:

{
"mcpServers": {
"mirobody_mcp": {
"command": "npx",
"args": [
"-y",
"universal-mcp-proxy"
],
"env": {
"UMCP_ENDPOINT": "http://localhost:18060/mcp"
}
}
}
}

先确认服务应答

Terminal window
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 源:

config.localdb.yaml
MCP_PUBLIC_URL: 'https://yourdomain.com'

当引擎必须把一个绝对 URL 交给进程之外的组件时,用的就是这个值;启动横幅里让你打开的地址, 也是它。

个人 URL 把身份写在路径里,给带不了 Authorization 请求头的客户端用。用你的 JWT 去要一个:

Terminal window
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_supportedauthorization_coderefresh_tokenclient_credentials
code_challenge_methods_supportedS256(只支持 PKCE)
scopes_supportedopenidprofileemailoffline_access,外加 mcp:readmcp:writemcp:toolsmcp:adminmcp:connect

另有一个 /oauth2/authorize 别名,以及 /oauth2/check_state/{state}: 设备式登录的轮询循环,就是靠它来确认浏览器那一步做完了没有。

tools/list 不需要凭据,因此这是看清一台服务提供了什么的最快办法:

Terminal window
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:

Terminal window
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