MCP 与托管工具
让你的 agent 用 SciRouter 搜网页、读网页、查文献、核对标识符、找先例:MCP 服务端怎么接(Claude Code / Cursor / Codex,以及 claude.ai / ChatGPT 的 OAuth 连接器),/v1 里怎么声明托管工具让代理替你跑工具循环,限额、错误码与数据流向。
SciRouter 把「联网」做成了两个出口,用的是同一份工具、同一套闸:
- MCP 服务端
https://api.scirouter.cn/mcp——你自己的 agent(Claude Code、Cursor、Codex CLI,或任何 MCP 客户端)拿 API Key 接上,就能搜索网页、读网页、查文献、核对序列 / 结构 / 化合物的标识符、找先例;claude.ai 与 ChatGPT 的连接器走 OAuth 登录(见下文「在 claude.ai / ChatGPT 里连接」)。 /v1/chat/completions的托管工具——在请求的tools里声明一次,代理自己执行工具、多轮调模型,把最终答案交回给你。适合「直接对接模型」的场景:你不用写工具循环。
两边的安全闸完全一样:只读公网的 http / https;私网地址、平台自己的站点、拒绝名单里的站点会被拒绝;robots.txt 只认写给 SciRouterBot 的规则;
每个站点有并发与频率的闸;网页正文有字数上限。
工具免费,不扣配额;只有模型调用计费。
五个工具
| 工具 | 入参 | 出参 |
|---|---|---|
web_search | { query(1–300 字), lang?: "zh" | "en", kind?: "web" | "scholar" | "news" } | { kind, items: [{ n, title, url, site, date?, snippet }], related: string[], cached } |
fetch_page | { url } | { url, finalUrl, title, site, date?, text, chars, truncated, cached } |
search_literature | { query(1–300 字), limit?: 1–10(默认 5) } | { items: [{ n, title, authors[], year?, venue?, doi?, arxiv?, url, abstract?, source? }] } |
resolve_identifier | { kind: "sequence" | "structure" | "compound", identifier } | 按 kind 不同,见下表 |
find_precedents | { kind: "accession" | "gene" | "compound", accession?, gene?, organism?, cid?, inchiKey? } | { basis, basisKey, source, total, papers: [{ pmid, doi, title, journal, year, firstAuthor, chapter, url }], ambiguous, fetchedAt, cached } |
web_search的scholar/news要平台开了对应的搜索种类才有;没开时得到的是一条isError结果「这种搜索没有开」,不是协议错误。fetch_page也能读 PDF(平台配了文档解析时);truncated为 true 说明正文超过了字数上限、被截掉了后面的部分。search_literature合并 arXiv / CrossRef / Semantic Scholar 的结果并去重,source说明这一条来自哪里。resolve_identifier与find_precedents是站内证据层的两个能力(消息里的核对徽章、找先例)开给 agent 用,出参与站内同形。
每个工具的结果都有两份:给人或模型读的文字(MCP 的 content[0].text)与结构化 JSON(MCP 的 structuredContent),两份同源。
工具「跑了但没成」——网站打不开、被策略拒绝、种类没开——是 isError: true 的正常结果,附一句原因;JSON-RPC 的错误只用于协议层面的问题(见下文错误码表)。
证据层的两个工具
resolve_identifier 拿一个标识符去公共库取回记录,出参按 kind:
kind | 标识符长什么样 | 出参 |
|---|---|---|
sequence | NCBI 登录号,可带区间:NC_012920.1:648-1601 | accession, source, molecule, start, stop(整条为 null), header, length, sequence(最多 10000 位,超出则截断、truncated=true), release, sourceUrl, fetchedAt, cached |
structure | pdb:1HEL、pubchem:2519 | ref, source, id, format(pdb / sdf), bytes, sourceUrl, pageUrl, fetchedAt, cached——不含文件本身,要文件按 sourceUrl 自己取 |
compound | PubChem CID 或化合物名 | cid, title, iupacName, formula, molecularWeight, inchiKey, smiles, ambiguous(按名称查到多条时取第一条), sourceUrl, fetchedAt, cached |
find_precedents 按登录号 / 基因(可加物种)/ 化合物(CID 或 InChIKey)在 PubMed 找先例:basis 与 basisKey 说明是按什么找的,total 是库里的总数,papers 最多几十条;
chapter 为 true 的是书籍章节(GeneReviews、StatPearls),那时 journal 是书名、year 是最近一次修订的年份;ambiguous 说明基因名或化合物名对上了不止一条。
- 两个工具的结果只是「库里有这条记录、记录长这样」,不替你判断回答对不对。
- 「库里没有」是
isError: true的结论文本(「NCBI 里没有这个登录号」),不是协议错误;入参形状不对(不认识的kind、缺对应字段)才是 JSON-RPC-32602。 - 次数与站内证据层共用同一份桶:核对每用户每分钟 60 次、找先例每用户每小时 30 次;超了是
RATE_LIMITED,按错误返回(MCP 里是 JSON-RPC-32000,data.retryAfterSeconds说等几秒),不是isError结果。/v1托管工具里限流不报错:这一轮的工具结果是失败态(tool_calls里该条status: "failed",文本「调用太频繁,这次没有执行:稍后再试或换个办法」),循环照常继续,由模型自己决定重试还是换办法。 - 团队项目下的 Key 看团队的「外部检索」档位(见下文);发出去的只有标识符,不含对话内容。
- 工具免费,与另外三个一样。
接 MCP 服务端
鉴权
Authorization: Bearer YOUR_API_KEY,与 /v1 是同一把 Key,在控制台的 API Keys 页面新建。
鉴权失败是 HTTP 401 / 403(错误体是 OpenAI 形状,同错误码那一页),不是 JSON-RPC 错误。
协议
- Streamable HTTP:单个端点、只收
POST;GET/DELETE回 405。 - 实现 MCP 规范 2026-07-28(无状态、
server/discover),同时接受 2025-03-26 / 2025-06-18 / 2025-11-25 的客户端:initialize握手照答,不发 session id。 旧版客户端不需要带MCP-Protocol-Version头;按 2026-07-28 写的请求要带MCP-Protocol-Version: 2026-07-28、Mcp-Method,tools/call还要带Mcp-Name,且与正文一致。 - 只支持 tools;不支持 prompts / resources / sampling。
- 响应一律
application/json(工具都在几秒到几十秒内完成,不开 SSE 流)。
各客户端怎么配
Claude Code:
bashclaude mcp add --transport http scirouter https://api.scirouter.cn/mcp --header "Authorization: Bearer YOUR_API_KEY"Cursor / Codex CLI(写进它们的 MCP 配置文件):
json{
"mcpServers": {
"scirouter": {
"url": "https://api.scirouter.cn/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}Anthropic Messages API(请求带 beta 头 anthropic-beta: mcp-client-2025-11-20):
json{
"mcp_servers": [
{ "type": "url", "url": "https://api.scirouter.cn/mcp", "name": "scirouter", "authorization_token": "YOUR_API_KEY" }
]
}OpenAI Responses API:
json{
"tools": [
{ "type": "mcp", "server_label": "scirouter", "server_url": "https://api.scirouter.cn/mcp", "authorization": "YOUR_API_KEY", "require_approval": "never" }
]
}后两种是让厂商的云来连我们的 MCP:要求 MCP 端点对厂商的公网可达,并且你把 Key 交给了厂商转发。能用,但不是我们保证的路径——出了问题先看厂商那边的日志。
claude.ai / ChatGPT 网页端的「连接器」不填 Key、走 OAuth 登录:见下文「在 claude.ai / ChatGPT 里连接(OAuth)」。
手工调一次
bash# 列工具(旧版写法:不带协议版本头也行)
curl https://api.scirouter.cn/mcp \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# 调工具(2026-07-28 写法:协议版本头 + Mcp-Method / Mcp-Name 与正文一致)
curl https://api.scirouter.cn/mcp \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: web_search" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"web_search","arguments":{"query":"钙钛矿 封装 稳定性","lang":"zh"}}}'成功的响应:
json{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "1. … — example.org(2026-08-01)\n …" }],
"structuredContent": { "kind": "web", "items": [ { "n": 1, "title": "…", "url": "https://example.org/…", "site": "example.org", "snippet": "…" } ], "related": ["…"], "cached": false },
"isError": false
}
}工具跑了但没成时,result.isError 是 true、content[0].text 是原因(例如「读网页失败:不允许的地址」);HTTP 状态仍是 200。
限额与错误码
每把 Key 每分钟 120 次工具调用;超了是 JSON-RPC 错误 -32000,data.retryAfterSeconds 说等几秒。
| 错误码 | 含义 |
|---|---|
-32700 | JSON 坏了 |
-32600 | 不是合法的 JSON-RPC 请求;批量(数组)不支持 |
-32601 | 方法不支持 |
-32602 | 入参不合法;没有这个工具 |
-32603 | 内部错误 |
-32000 | 被限频,data.retryAfterSeconds 说等几秒 |
-32020 | 头与正文不一致(Mcp-Method / Mcp-Name 与 method / params.name 对不上),HTTP 400 |
-32022 | 协议版本不支持,data.supported 列出支持的版本,HTTP 400 |
json{ "jsonrpc": "2.0", "id": 2, "error": { "code": -32000, "message": "…", "data": { "retryAfterSeconds": 12 } } }团队项目下的 Key
Key 归属于团队项目时,工具受团队的「外部检索」档位约束(三档:核对 + 相似性 all、只核对 verify、全部关闭 off):
resolve_identifier只发编号,「只核对」及以上就能用(verify/all);- 其余四个(
web_search、fetch_page、search_literature、find_precedents)要「核对 + 相似性」(all)。
档位不够时工具返回 isError「团队策略不允许联网检索」。个人 Key 不受这条影响。
在 claude.ai / ChatGPT 里连接(OAuth)
claude.ai 与 ChatGPT 的「连接器」不让你填 API Key,只走 OAuth 2.1:你在它们那里填 MCP 地址,它们自动发现 SciRouter 的授权服务器,把你带到 SciRouter 的授权页; 你在页面上选一把 Key 授给它,跳回去就连上了。它拿到的令牌等价于那把 Key:计费、模型白名单、团队档位都按那把 Key 算。
- claude.ai:设置 → 连接器 → 添加自定义连接器,远程 MCP 服务器地址填
https://api.scirouter.cn/mcp;client id / secret 留空。 - ChatGPT:设置 → 连接器 → 创建(要先开开发者模式),MCP 服务器地址填同一个
https://api.scirouter.cn/mcp,鉴权选 OAuth;同样不用填 client id / secret。
两家都会自动读 MCP 地址所在源的 /.well-known/oauth-protected-resource/mcp 找到授权服务器,然后:
- 把你带到
https://www.scirouter.cn/console/oauth/authorize——SciRouter 的授权页(没登录先登录,登录完自动回来); - 页面上写着是谁在请求(客户端名与主页)、要什么权限、授权后跳回哪里;你选一把 Key,点「授权」或「拒绝」;
- 跳回客户端,它换到令牌,之后每次调工具都带着这枚令牌。
权限(scope)只有两种:tools(调用 MCP 的工具)、offline_access(拿刷新令牌,长期保持连接)。
令牌有效期:访问令牌 1 小时;刷新令牌 30 天,每次刷新轮换(旧的立刻作废)。
撤销:控制台 API Keys 页的「已连接的应用」,每条一个「撤销」——刷新令牌立刻作废,已签出的访问令牌最多再活到过期(1 小时内)。吊销那把 Key 也会让它的令牌失效。
自己写 OAuth 客户端
按 MCP 规范 2026-07-28 的授权章节实现即可。元数据与端点都挂在 MCP 地址所在的源上(把 https://api.scirouter.cn/mcp 末尾的 /mcp 换成下面的路径就是完整地址):
| 元数据 | 路径 |
|---|---|
| 受保护资源(RFC 9728) | /.well-known/oauth-protected-resource/mcp——列出授权服务器 |
| 授权服务器(RFC 8414) | /.well-known/oauth-authorization-server——授权、令牌、注册端点与支持的 PKCE 方法都在里面,以它为准,不要写死端点 |
| 客户端注册 | 元数据里的 registration_endpoint(RFC 7591 动态注册) |
- 客户端标识两种都支持:Client ID Metadata Documents(
client_id是一个 https 地址,指向你公开的客户端元数据文档;claude.ai 这类)、动态注册(RFC 7591;ChatGPT 这类)。 - PKCE
S256必须;response_type=code;resource参数填https://api.scirouter.cn/mcp(RFC 8707);redirect_uri要与登记的一致(loopback 地址允许)。 - 拿到的访问令牌当
Authorization: Bearer <access_token>用,与 API Key 的用法一样;刷新令牌每次轮换,用旧的会失败。 - 授权请求本身有问题(不认识的
client_id、回跳地址不在名单、缺 PKCE)时授权页不会跳回你,只在页面上说明——回跳地址本身可能就是错的。
/v1 里的托管工具
不想自己写工具循环时,让代理替你跑:
httpPOST https://api.scirouter.cn/v1/chat/completionsjson{
"model": "deepseek/deepseek-v3",
"messages": [{ "role": "user", "content": "近一年钙钛矿封装稳定性有哪些新进展?给出处。" }],
"tools": [
{ "type": "mcp", "server_label": "scirouter", "allowed_tools": ["web_search", "fetch_page"], "require_approval": "never" }
],
"scirouter": { "max_tool_rounds": 6 }
}请求
tools里加一项type: "mcp"(借 OpenAI Responses 的形状,官方 SDK 不会拒绝):server_label只认"scirouter";allowed_tools可省略,省略 = 五个都给模型;- 不接受
server_url(要连外部 MCP 服务器,用上一节的方式让厂商去连); require_approval只能是"never"或不填。
- 顶层可选
"scirouter": { "max_tool_rounds": 6 }:带工具调模型的轮数上限,缺省 6、最多 30。到了上限,最后再调一次不带工具,让模型作答。 - 你自己的
function工具可以同时存在。
行为
- 模型要了托管工具 → 代理执行 → 结果以
role: "tool"消息接进上下文 → 再调模型。 - 模型要了你自己的函数 → 循环结束,那一轮原样交回你(你执行后再发下一轮,与今天一样)。
- 同一轮既要了托管工具又要了你的函数:交回你的函数,托管的那一轮不执行(扩展字段里
status: "skipped");你回复之后模型会重新要。 - 模型不支持函数调用时上游会报错,照常回 4xx,不退回文本协议。
响应
仍是 OpenAI 形状;顶层 scirouter 扩展字段(见可观测性)多两样:
json{
"choices": [ … ],
"usage": { … },
"scirouter": {
"request_id": "req_…",
"cost_quota": 23180,
"tool_rounds": 3,
"tool_calls": [
{ "round": 0, "id": "call_…", "name": "web_search", "status": "done", "summary": "web 8 条", "ms": 1320 },
{ "round": 1, "id": "call_…", "name": "fetch_page", "status": "failed", "summary": "读网页失败:不允许的地址", "ms": 45 }
]
}
}tool_rounds:调了几次模型;tool_calls每条是一次工具调用(round从 0 起,与请求日志的v1_tool_round标签同一口径;status是done/failed/skipped),不含入参与正文。cost_quota是各轮之和;控制台里每轮一条请求日志,标签v1_tool_round。- 流式:中间轮的正文照常流出;托管工具的
tool_calls增量不下发;每次工具开始 / 结束各发一个choices: []的分片,scirouter.tool里是那一条工具调用(开始那条status为空、ms为 0;结束那条带status/summary/ms)——与可观测性那一页的扩展分片同一机制,官方 SDK 会跳过;[DONE]之前的收尾分片带各轮合计。这条流还没有任何分片时(模型第一轮一个字没吐就要了工具)开始那条不发。
textdata: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"tool":{"round":0,"id":"call_…","name":"web_search","status":"","ms":0}}}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"tool":{"round":0,"id":"call_…","name":"web_search","status":"done","summary":"web 8 条","ms":1320}}}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"近一年…"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"request_id":"req_…","tool_rounds":3,"cost_quota":23180,"tool_calls":[…]}}
data: [DONE]- 带托管工具的请求不进响应缓存(结果随网页变),
Idempotency-Key照常。
计费
每一轮按正常的模型调用计费,工具免费。想控制花费就调小 max_tool_rounds,或用 allowed_tools 只放需要的工具。
数据流向与限制
- 检索词会发给搜索服务商,见子处理者清单。
- 读网页以
SciRouterBot的 UA 访问、遵守 robots.txt;网页正文不落库。 - MCP 调用日志只记 Key、工具名、耗时与一句摘要,不记正文。
- 只读公网 http / https:私网地址、平台自己的站点、拒绝名单里的站点一律拒绝;每个站点有并发与频率的闸;正文有字数上限。
常见问题
为什么读不了某个链接? 五种情况:地址是私网 / 本机的;是平台自己的站点;在拒绝名单里;站点的 robots.txt 明确拒绝了 SciRouterBot;页面太大超过体积上限。
结果的 isError 文字里写着是哪一种。
为什么 kind: "scholar" 没有结果? 平台没开学术搜索——web_search 会以 isError 告诉你「这种搜索没有开」。查文献用 search_literature,它不依赖这个开关。
/v1 的托管工具用什么模型? 任何会函数调用的模型都行;带 tools 的请求只会发往支持工具调用的线路(见对话补全)。模型不会函数调用时上游报错、照常回 4xx。
轮数用完了怎么办? 到 max_tool_rounds 后代理最后再调一次不带工具的模型,让它用已有的资料作答,所以你总能拿到一个回答。想多查几轮就调大它(最多 30)。
工具调用扣配额吗? 不扣。MCP 与 /v1 托管工具都免费;/v1 只按每轮的模型调用计费。
在 claude.ai 里连上之后能撤掉吗? 能:API Keys 页的「已连接的应用」里撤销;或者直接吊销那把 Key。撤销后应用要再用就得重新走一遍授权。
相关
- Key 的边界与权限:认证与 API Key
scirouter扩展字段与流式分片:可观测性- 工具调用只走支持的线路:对话补全