fastapi-gql-mcp vs fastapi-mcp

fastapi-gql-mcp 0.3.0(本仓库) FastAPI → GraphQL → MCP  ·  fastapi-mcp 0.4.0(tadata-org,⭐12k) FastAPI → MCP 直暴露
同一应用双框架接入实测 · 2026-10-04 · macOS Apple Silicon · 全部数字来自 bench/results.json(进程内基准)

0 · 三十秒结论

11k tok
fastapi-mcp:100 端点的工具目录体积
随端点数线性增长,agent 每次会话都要吞下
1,281 tok
fastapi-gql-mcp:渐进式发现的上下文
50 / 100 端点下数值不变 —— 恒定上下文
1 vs 2
组合任务的 agent 轮次(查询+统计)
GraphQL 单次组合 vs 每端点一调
4.7×
响应体积差(字段投影后)
610 B vs 2,875 B(同 20 条笔记)
0.91 ms
fastapi-mcp 单调用 p50(更快)
无 GraphQL 执行层;诚实计入
✕
两库无法共存同一环境
mcp 2.x 签名变更,fastapi-mcp 0.4.0 无上界依赖被打破
定位差异是根源:fastapi-mcp 把「端点」变成工具(最小心智负担,小 API 下它的简单就是优势——5 端点时它的目录反而更省:685 vs 796 tok); fastapi-gql-mcp 把「API」变成一张类型化查询图(schema 即契约),上下文经济性、组合、投影、写门禁、OAuth 登录体验都由此派生。

1 · 架构对照

fastapi-mcp:端点 = 工具
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
描述链:OpenAPI summary/description + 自动生成的示例响应;每个工具自带完整参数 JSON Schema。
fastapi-gql-mcp:schema = 契约
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 服务人类"]
描述链:模型 docstring → 类型、Field(description) → 字段、端点 docstring → 字段、Query() → 参数。

2 · 上下文经济性(核心分水岭)

agent 可用之前必须吞下「它有什么可调」。测法:tools/list 载荷 JSON 字节(fastapi-gql-mcp 另计 get_schema 的 SDL, 渐进式另计单域发现路径全程),token 估算 = 字节 ÷ 4。

端点数 fastapi-mcp 工具数 fastapi-mcp 目录 (tok) gql-mcp simple 全量 (tok) gql-mcp 渐进式单域 (tok)
读法:fastapi-mcp 线性增长,100 端点 ≈ 11k token 只算目录;simple 模式缓增(SDL 紧凑); 渐进式模式上下文恒定(≈1.28k tok,与 API 规模无关——agent 按域按需取 SDL 片段)。 诚实反例:≤5 端点时 fastapi-mcp 更省(685 vs 796 tok)——没有组合需求的小 API,直暴露的简单就是最优解。

3 · 组合与往返(agent 真实的成本单位)

任务:过滤笔记 + 统计
1 轮
fastapi-gql-mcp:一次 graphql_query 组合两个域
进程内均值 2.01 ms
同一任务
2 轮
fastapi-mcp:list_notes + stats 两次工具调用
进程内均值 1.42 ms
为什么轮次比毫秒重要
秒 ≫ ms
每个工具调用是 agent 的一个决策轮(含 LLM 推理,秒级); 1ms 级的执行差距被轮次差完全淹没

响应体积:字段投影

4 · 延迟(进程内微基准,200 次迭代)

诚实计入:单次平凡调用 fastapi-mcp 更快(0.91 vs 1.36 ms p50)——GraphQL 执行层有代价。 其代价换来上文的组合与投影能力;且两者都是 ASGI 进程内调用,绝对值都在 1–2 ms 区间,远低于一次网络往返。

5 · 错误语义:整调用失败 vs 字段级隔离

同一场景:一次请求里既有好数据(stats)又有坏请求(不存在的 note 9999)。

fastapi-mcp:工具级失败
❌ Error calling get_note_api_notes__note_id__get.
   Status code: 404.
   Response: {"detail":"note not found"}

— 整个工具调用标记为错误;
— 想拿 stats 需要再发一次调用;
— 失败原因与结果混在一行文本里。
fastapi-gql-mcp:字段级隔离
✅ {
  "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 }     ← 结构化错误码
  }]
}

6 · 契约保真:同一个 list_notes,agent 看到什么

fastapi-gql-mcp:类型化 SDL(可组合)
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 { … }   ← 类型只定义一次,处处复用
fastapi-mcp:工具描述 + JSON Schema
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 拼接路径与方法)。

7 · 能力矩阵

维度fastapi-mcp 0.4.0fastapi-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;不验证 tokenauth= 完整 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)

8 · 认证模型对照

环节fastapi-mcpfastapi-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 完整客户端
实证:本仓库 examples/notes_oauth 用 Claude Code 走通了完整 OAuth 登录(consent → GitHub → 引用型 token → 以本人身份 CRUD)。

9 · 生态与工程化

实测撞出的兼容性事实:fastmcp 4 要求 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 且逐版本端到端验证。

10 · 规范演进:MCP 的 lazy 机制与本对照的关系

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 两个普通工具今天可用(纯工具实现,任意客户端)省
对结论的诚实修正: ① fastapi-mcp 的目录线性增长是开箱现状而非死刑 —— 可自行实现 search 折叠(裸 mcp 1.x 无现成件)或等 Tool Search 扩展普及; ② 我们的渐进式披露与 lazy 模式同型且不依赖扩展,今天在任何客户端可用; ③ 即便工具目录问题被扩展彻底解决,组合查询、字段投影、字段级错误隔离、写门禁仍只有 GraphQL 路线能给 —— 目录体积是本对照的一个维度,不是全部; ④ fastmcp 的 SearchTransform 是我们的备用弹药(我们目录恒定 2–6 个工具,无需折叠)。

11 · 选型建议

选 fastapi-mcp,当…
· API 小(~10 端点内)且稳定
· 只给 agent 用,没有组合查询需求
· 想零配置最快接入(pip install 即用)
· 认证已有 FastAPI Depends 兜底
· 需要 stdio / 分离部署形态
选 fastapi-gql-mcp,当…
· API 会长大(上下文预算随规模恒定)
· agent 需要一次拿到组合视图(省轮次)
· 需要字段投影控制响应体积
· 需要细粒度写操作门禁
· 需要 OAuth 登录的完整体验(consent/PKCE/端点门禁)
· 顺便想让人类也用 GraphQL(GraphiQL)

12 · 方法与复现

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 差距)稳定。