bench/results.json(进程内基准)
flowchart LR A[FastAPI app] -->|"get_openapi()"| O[OpenAPI schema] O -->|每个 operation 一个工具| T["N 个工具
(name = operationId)"] T --> C1[工具调用 1] T --> C2[工具调用 2] T --> C3[工具调用 3] C1 -.->|还原 HTTP 请求
ASGI 进程内| A C2 -.-> A C3 -.-> A
flowchart LR A[FastAPI app] -->|tag 域树 + 函数名字段| G[GraphQL schema SDL] G --> T["固定 2–6 个工具
get_schema / graphql_query
graphql_mutation / 渐进式发现"] T -->|一次组合查询| Q["{ a… b… c… }
多域多字段"] Q -->|GraphQL 执行| A G --> H["GraphiQL + POST /graphql
同一 schema 服务人类"]
agent 可用之前必须吞下「它有什么可调」。测法:tools/list 载荷 JSON 字节(fastapi-gql-mcp 另计 get_schema 的 SDL, 渐进式另计单域发现路径全程),token 估算 = 字节 ÷ 4。
| 端点数 | fastapi-mcp 工具数 | fastapi-mcp 目录 (tok) | gql-mcp simple 全量 (tok) | gql-mcp 渐进式单域 (tok) |
|---|
同一场景:一次请求里既有好数据(stats)又有坏请求(不存在的 note 9999)。
❌ Error calling get_note_api_notes__note_id__get.
Status code: 404.
Response: {"detail":"note not found"}
— 整个工具调用标记为错误;
— 想拿 stats 需要再发一次调用;
— 失败原因与结果混在一行文本里。
✅ {
"data": {
"meta": { "stats": { "notes": 20 } }, ← 好数据照常返回
"notes": { "mine": { "get_note": null } } ← 坏字段置空
},
"errors": [{
"message": "GET /api/notes/{note_id} -> 404: …",
"path": ["notes","mine","get_note"],
"extensions": { "code": "HTTP_404",
"http_status": 404 } ← 结构化错误码
}]
}
type NotesMineQuery {
"""
List YOUR notes.
``q`` filters by substring over title and body.
"""
list_notes(
"""substring over title/body"""
q: String = null
active: Boolean = true
): [NoteOut!]
"""Fetch one of YOUR notes by id."""
get_note(note_id: Int!): NoteOut
}
"""A note owned by the logged-in user."""
type NoteOut { … } ← 类型只定义一次,处处复用
List Notes
List YOUR notes.
``q`` filters by substring over title and body.
### Responses:
**200**: Successful Response
Content-Type: application/json
**Example Response:**
```json
[{"id":1,"title":"Title","body":"Body","owner":"Owner"}]
```
inputSchema: { "properties": { "q": {"type":"string", …},
"active": {"type":"boolean"} }, … }
— NoteOut 的形状在每个相关工具里以示例形式重复出现
list_notes(函数名)vs list_notes_api_notes_get(FastAPI operationId 拼接路径与方法)。| 维度 | fastapi-mcp 0.4.0 | fastapi-gql-mcp 0.3.0 |
|---|---|---|
| 工具模型 | 每端点一工具(N 个) | 固定 2–6 个 + schema 即契约 |
| 规模控制 | include/exclude operations / tags(扁平) | 域树 + 渐进式发现(auto > 25 路由自动启用) |
| 组合查询 | 无 | 一次调用组合多域多字段、别名、投影 |
| 字段投影 | 无(全量 JSON,indent=2) | 按需选择字段 |
| 错误语义 | HTTP ≥400 → 整工具失败 | 字段级置空 + extensions.code,兄弟字段存活 |
| 写操作门禁 | 无概念(全部暴露) | allow_mutation + mutation_include + 工具级操作类型守卫 |
| 凭据透传 | headers 白名单(默认 authorization) | passthrough_headers 白名单(默认 authorization,可加 cookie 等) |
| MCP 端点认证 | OAuth 发现/授权代理 + 假 DCR;端点防护交给自带 FastAPI Depends;不验证 token | auth= 完整 OAuth 2.1 代理:DCR + PKCE + consent + 引用型 token + 端点门禁 |
| 传输 | SSE / streamable HTTP / stdio,支持分离部署(真 HTTP) | streamable HTTP(按调用者凭据需要 HTTP 上下文,stdio 已移除) |
| 人类入口 | 无 | GraphiQL + POST /graphql,同一 schema 服务人机 |
| 依赖锚定 | mcp>=1.12 无上界,mcp 2.x 下崩溃 | fastmcp<5 锁上界,4.0.10 端到端验证 |
| 代码规模 | ~2.0k LOC(直连 mcp SDK) | ~2.6k LOC(graphql-core + fastmcp) |
| 环节 | fastapi-mcp | fastapi-gql-mcp(auth=) |
|---|---|---|
| 发现(RFC 9728/8414) | 代理你自己的 IdP metadata,替换 authorize 地址 | fastmcp provider 自带 401 挑战 + well-known 全套 |
| 客户端注册 | 「假」DCR:回显预注册的 client_id/secret | 真 DCR(动态注册) |
| 授权 | 302 重定向到你的 IdP authorize(无 consent 页) | consent 确认页 → IdP(PKCE 全程) |
| token | 由你的 IdP 直接发给客户端,MCP 层不管 | 代理签发引用型 token(identity 留在服务端存储) |
| 端点门禁 | 不验 token —— 靠你在 mount 上加 Depends | 每次请求验签 + JTI 映射 + 上游 token 校验 |
| 路由侧身份 | headers 白名单透传(与我们同思路) | passthrough_headers 透传(与我们同思路) |
| 设计取向 | 复用既有 IdP,最少自建组件;面向 npx mcp-remote 生态 | 自包含授权服务器代理;面向 Claude Code 等 OAuth 完整客户端 |
mcp>=2,<3;fastapi-mcp 0.4.0 声明
mcp>=1.12(无上界)但其 Server(name, description)
位置参数在 mcp 2.x 已改为关键字参数 —— 同一环境装两库直接崩溃,本基准被迫拆成双环境(bench/env_ours、bench/env_theirs)。
上游无上界依赖是消费者最常踩的坑;fastapi-gql-mcp 对 fastmcp 锁 <5 且逐版本端到端验证。
2026-10 核实 · 依据本地 mcp SDK 2.3.0 内嵌协议类型(最新修订 2026-07-28)+ 生态检索。 "lazy 省 context"常被混为一谈,实际分四层:
| 层 | 机制 | 状态 | 省不省 agent 上下文 |
|---|---|---|---|
| 核心规范 | tools/list 分页(nextCursor,2025-06-18 起) | 已发布 | 不省 —— 分页只是传输分块,agent 选工具仍需全量目录 |
| 核心规范 | server/discover + cacheScope(2026-07-28 修订) | 已发布 | 不减体积 —— 帮的是 prompt cache 复用(public/private 缓存语义) |
| 规范扩展 | Tool Search 扩展(tools/search:只留名称/摘要,按需拉取完整定义) | draft,需双边支持,客户端采纳进行中 | 省 —— 真正的"lazy 工具加载" |
| 库层 | fastmcp 4.x SearchTransform(Regex/BM25):目录折叠成 search_tools + call_tool 两个普通工具 | 今天可用(纯工具实现,任意客户端) | 省 |
· API 小(~10 端点内)且稳定 · 只给 agent 用,没有组合查询需求 · 想零配置最快接入(pip install 即用) · 认证已有 FastAPI Depends 兜底 · 需要 stdio / 分离部署形态
· API 会长大(上下文预算随规模恒定) · agent 需要一次拿到组合视图(省轮次) · 需要字段投影控制响应体积 · 需要细粒度写操作门禁 · 需要 OAuth 登录的完整体验(consent/PKCE/端点门禁) · 顺便想让人类也用 GraphQL(GraphiQL)
Comparison/bench/
├── shared_app.py # 同一 notes 应用(5 业务端点 + 可扩容 filler),接入两个框架 —— 桥接代码本体
├── env_ours/ # fastapi-gql-mcp[mcp] 环境(fastmcp 4 / mcp 2)
├── env_theirs/ # fastapi-mcp 环境(mcp 钉 <2 才能运行)
├── run_ours.py # 本库侧测量 → results_ours.json
├── run_theirs.py # fastapi-mcp 侧测量 → results_theirs.json
├── merge_results.py # 合并 → results.json
└── results.json # 本报告全部数字的来源
复现:gh repo clone tadata-org/fastapi_mcp ../fastapi-mcp # 前提:对照组源码克隆在本仓库旁
cd Comparison/bench
uv run --project env_ours python run_ours.py
uv run --project env_theirs python run_theirs.py
python3 merge_results.py
测量口径:工具目录 = tools/list 载荷 JSON 字节;token 估算 = 字节÷4;延迟为进程内内存会话(无网络), 本库侧用 fastmcp Client、对方用 mcp SDK ClientSession(各自原生客户端栈); 延迟数字的绝对值受本机状态影响,相对关系(≈0.5ms 差距)稳定。