Skip to main content
Glama
Frz2004

MCP-ToolHub

by Frz2004
README.md
# MCP-ToolHub

**企业级 MCP 工具中台** —— 依据 MCP `2026-07-28` 规范自研:6 个 MCP Server + 无状态 MCP 网关 + 混合检索按需挂载 + OAuth 2.1 / RBAC / MRTR 安全体系 + 熔断/舱壁/重试韧性层 + DAG 工作流编排,为 AI Agent 提供统一、安全、可观测的工具接入层。

> 已开源 4 个 MCP Server(kb / db / repo / browser,terminal 与 ticket 为企业内部版演示实现,接口同构)。

## 架构

```
 Agent (LangGraph / 自研 Harness / IDE / MCP Client)
   │  Streamable HTTP (Mcp-Method / Mcp-Name 头路由)      │ stdio 桥(本地)
   ▼                                                       ▼
┌────────────────────── 无状态 MCP 网关 (水平扩容) ──────────────────────┐
│  OAuth 2.1 (issuer/JWKS) ─ RBAC 白名单 ─ 金丝雀版本路由                 │
│  MRTR 人工二次确认 ─ 速率配额(Redis) ─ 两级结果缓存(L1 LRU + L2 Redis)  │
│  韧性层: 熔断 + 舱壁 + 全抖动重试 ─ 凭证托管代理层                      │
│  截断/脱敏 ─ MCP Apps 富结果渲染 ─ 审计 JSONL ─ Prometheus             │
│  DAG 工作流引擎(拓扑并行 + 模板引用 + 检查点恢复)                        │
└──────┬──────────┬──────────┬──────────┬──────────┬──────────┬────────┘
   kb Server   db Server  repo Server  ticket Srv  browser Srv  terminal Srv
  知识库检索   只读SQL     代码仓库     工单系统     浏览器       沙箱终端
        (23 个原子工具 + 60 个实例级变体 = 83 工具目录)
```

## 核心特性

### 1. 无状态网关(Streamable HTTP)
- 按 MCP `2026-07-28` 新版规范:**无 initialize 握手、无 `Mcp-Session-Id`**,请求自携带协议版本(`Mcp-Protocol-Version`)与客户端能力
- 可部署在**普通轮询负载均衡**后水平扩容,无需会话粘滞与共享会话存储
- 网关直接解析 `Mcp-Method` / `Mcp-Name` 请求头完成**路由、限流与计量,无需解包 JSON-RPC 报文**;未带头的老客户端自动回退 body 解析(兼容模式)
- `tools/list` 返回 `ttlMs` + `catalogVersion`,客户端据此缓存工具目录,**冷启动拉取开销下降约 70%**
- 本地场景 `python -m clients.stdio_bridge` 一键切换 stdio 传输

### 2. 混合检索 + 按需挂载(解决工具规模膨胀)
83 工具全量注入 schema 约占 5k token(len(json)/4 估算)。按需挂载机制:

1. 每轮按用户意图经 **BM25 + 512 维稳定哈希向量 + 加权 RRF 融合**(k=60)检索 Top-K 工具(默认 8 个)
2. 中英混合分词(英文按词 / 中文二元组)+ ~90 条领域同义词词典("提交记录"→log、谁动过配置→log)+ 泛化/点名双加成
3. 仅将选中工具 schema 注入上下文,其余保留为可检索目录(常驻元工具 `toolhub.search_tools` 支持换装)

在 105 条标注评测集上的真实测量(`scripts/benchmark.py retrieval` 复现):

| 检索器 | accuracy@1 | recall@3 | MRR |
|---|---|---|---|
| 两阶段(v1.0) | 20.9% | 43.8% | 0.339 |
| **混合 RRF(v1.1)** | **95.2%** | **99.1%** | **0.968** |

单查询延迟 mean 1.8ms / p95 2.0ms(纯 Python、零第三方依赖);
按需挂载实测 **5.1k → 0.5k token(-89%)**。完整迭代过程与方法论见 `docs/benchmarks.md`。

### 3. 网关韧性层(熔断 / 舱壁 / 重试)
- **熔断器**:按工具粒度,CLOSED → OPEN(连续上游故障)→ HALF_OPEN(恢复窗口后探测);OPEN 时快速失败 `43001` 并附 `retryAfterMs`
- **错误分类**:业务错误(校验/未找到)是确定性失败,**不熔断不重试**;`UpstreamError`/超时按瞬时故障处理
- **舱壁隔离**:per-tool 信号量,槽满立即拒绝 `43002` 不排队,单个工具打挂不拖垮整池
- **全抖动重试**:仅幂等工具,`sleep = random(0, base·2^attempt)`,防止重试风暴同步化
- `/admin/resilience` 实时内省熔断与缓存状态

### 4. 两级结果缓存 + 目录 ETag
- **tools/call 结果缓存**:L1 进程内 LRU(1024 条)+ L2 Redis(`redis.asyncio` 协议注入),键 = `sha256(工具+参数+目录版本)`;仅缓存幂等只读工具
- **stale-while-revalidate**:条目过期但仍在 stale 窗口内时立即返回旧值并后台刷新,P99 不为缓存刷新买单
- **tools/list ETag/304**:目录指纹(含金丝雀路由状态)不变则 304,客户端继续用本地缓存
- 命中率 / stale 服务数经 `/admin/resilience` 与 Prometheus `tcache_events` 暴露

### 5. 安全体系
| 层 | 机制 |
|---|---|
| 认证 | OAuth 2.1:issuer 校验、JWKS 验签(缓存 + 失败刷新)、exp/aud/nbf |
| 授权 | RBAC 租户级工具白名单(通配符)+ scope 校验 |
| 凭证 | 统一托管在**代理层**,由 MCP Server 代取注入 `ToolContext`,**模型全程不接触明文密钥**;出口兜底脱敏 |
| 人审 | 危险工具(`danger=high`)经 **MRTR** 返回 `input_required` + HMAC 确认令牌(绑定 工具+参数哈希+用户+TTL),人工批准后带令牌重放才执行 |
| 审计 | 全量 tool call 写 JSONL 审计日志(含 redacted 参数、延迟、重试、截断信息) |

### 6. DAG 工作流编排(固定套路不再逐工具决策)
Agent 逐工具决策在稳定流程上是浪费。声明式工作流(`mcp_toolhub/orchestration/`):

- **拓扑执行**:节点声明 `depends_on`,无依赖节点 `asyncio.gather` 并行,延迟取决于关键路径
- **输出模板引用**:参数可写 `{{node.path.to.value}}`(整串保留原类型 / 嵌入做内插),上游输出跨节点注入
- **MRTR 感知**:高危节点返回 `input_required` 时整个流暂停,状态序列化为检查点;人工确认后从断点恢复,**不重跑已完成节点**
- **检查点跨副本**:FlowState 纯 JSON,恢复无需粘滞到原网关副本(无状态原则:状态在调用方)

内置示例流:`incident_triage`(知识库检索 ∥ 失败调用聚合 → 自动建单)、`terminal_debug`(跨服务模板 + MRTR 暂停恢复):
```bash
curl -X POST http://127.0.0.1:8080/admin/flows/incident_triage/run \
  -H "Authorization: Bearer dev-token-1"
```

### 7. 工具版本金丝雀 + MCP Apps 富结果
- **金丝雀发布**:同一工具名注册多实现版本(`register_version` / `promote` / `rollback`),按权重路由;目录指纹包含版本路由状态,切流瞬间客户端 ETag 与结果缓存全部自动失效。路由分布经 Prometheus `tool_version_calls` 打点
- **MCP Apps 富结果**:纯函数把 tool result 渲染为 resourceLinks / 列表卡片 / 表格 / 操作按钮(按钮 = 带参数预填的合法 tools/call),声明式、可随缓存复用、危险操作按钮联动 MRTR

### 8. 可观测
- 每次 tool call 记录**延迟、成功率、重试、缓存命中、版本路由**,Prometheus 指标 + Grafana 看板(`deploy/grafana-dashboard.json`)
- `/metrics` 指标;`/healthz`、`/admin/tools`、`/admin/resilience`、`/admin/flows` 运维端点

### 9. 工具设计约定(6 Server 通用)
- **统一命名**:`<server>.<action>[_<instance>]`
- **原子性**:一个工具只做一件事,组合交给上层 Agent
- **正交性**:工具间不重复覆盖动作空间;错误码分层(网关 40001-40999 / 工具 41001-41999 / MRTR 42001)
- **输出可截断**:verbose 字段丢弃 → 文本窗口截断 → 硬截断 + 续读 token,**避免长结果撑爆上下文**

## 6 个 MCP Server

| Server | 工具(原子) | 说明 |
|---|---|---|
| `kb` | search / fetch / list_sources / related | 内部知识库检索(后端协议化,内置内存实现) |
| `db` | query / list_tables / explain | 只读 SQL:强制 SELECT-only、行数上限 |
| `repo` | search_code / read_file / list_dir / log | 代码仓库(本地目录 / Git Provider 适配) |
| `ticket` | search / get / create / update | 工单系统(写操作 danger=medium,入审计) |
| `browser` | navigate / extract_links / extract_text / find_in_page | 网页抓取,SSRF 防护 + 站点白名单 |
| `terminal` | exec / write_file / read_file / list_dir | 沙箱终端:命令 allowlist、超时、danger=high 走 MRTR |

实例级变体由 `mcp_toolhub/servers/variants.json` 配置驱动(如 `db.query_analytics`、`repo.search_code_gateway`),热加载、复用底层 handler。

## 快速开始

```bash
# 安装
pip install -e ".[test]"          # LangGraph 集成: pip install ".[langgraph]"

# 启动网关(dev 模式自带静态令牌)
cp .env.example .env
python -m uvicorn mcp_toolhub.gateway.app:app --port 8080

# 头路由调用(不解包 JSON-RPC)
curl -s http://127.0.0.1:8080/mcp \
  -H "Authorization: Bearer dev-token-1" \
  -H "Mcp-Method: tools/call" -H "Mcp-Name: kb.search" \
  -H "Mcp-Protocol-Version: 2026-07-28" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"kb.search","arguments":{"query":"oauth"}}}'

# 工具目录(带 ttlMs 缓存约定)
curl -s http://127.0.0.1:8080/mcp \
  -H "Authorization: Bearer dev-token-1" -H "Mcp-Method: tools/list" -d '{}'

# 本地 stdio 一键切换(给 IDE / Claude Desktop 用)
python -m clients.stdio_bridge --url http://127.0.0.1:8080 --token dev-token-1

# 测试
pytest -q
```

## Agent 接入

### LangGraph

```python
from mcp_toolhub.adapters.gateway_client import GatewayClient
from mcp_toolhub.adapters.tool_mounting import ToolMounter
from mcp_toolhub.adapters.langgraph_adapter import to_langchain_tools
from mcp_toolhub.servers.catalog import build_catalog

mounter = ToolMounter(build_catalog(), top_k=8, token_budget=2048)
client = GatewayClient("http://127.0.0.1:8080", token="dev-token-1")

# 只挂载与当前意图相关的 Top-K 工具(而不是 80+ 全量)
tools = to_langchain_tools(mounter, client, query="查询数据库表结构")
# tools 直接喂给 langgraph.prebuilt.ToolNode
```

### 自研 Harness

```python
from mcp_toolhub.adapters.harness_adapter import Harness, EchoModelClient

harness = Harness(mounter, client, model=MyModelClient(), max_steps=8)
run = await harness.run("统计上周工具调用成功率")
if run.paused_for_confirmation:      # MRTR: 等人工确认
    run = await harness.resume(run, confirmationToken=approved_token)
print(run.to_dict())
```

## 部署

```bash
docker compose up -d   # gateway x2(无状态,轮询LB) + redis + prometheus + grafana
```

生产检查清单:
1. `DEV_MODE=0`,配置 `OAUTH_ISSUER`(JWKS 端点)
2. `MRTR_SECRET` 换成强随机值;凭证接 Vault/KMS(实现 `CredentialSource` 协议)
3. `REDIS_URL` 指向共享 Redis(限流与审计 Stream)
4. Grafana 导入 `deploy/grafana-dashboard.json`

## 项目结构

```
mcp_toolhub/
├── common/        统一错误码 / 输出截断 / Tool 抽象
├── servers/       6 个 MCP Server + variants.json 目录扩展
├── gateway/       无状态网关: app / router / registry(金丝雀) / resilience(熔断舱壁重试) / cache(两级) / apps(富结果) / ratelimit / metering / audit / credentials
├── auth/          OAuth 2.1 校验 / RBAC 策略
├── mrtr/          人工二次确认(HMAC 令牌)
├── retrieval/     混合检索: BM25 + 哈希向量 + 加权 RRF + 评测集(105 case)
├── orchestration/ DAG 工作流引擎 + 内置流
└── adapters/      工具按需挂载 / GatewayClient / LangGraph / Harness
clients/           stdio 桥
scripts/           benchmark.py(本 README 与 docs/benchmarks.md 数据来源) / smoke
tests/             pytest 82 用例(检索评测门/韧性/缓存/编排/金丝雀/网关端到端)
deploy/            prometheus / grafana 看板
```

## License

MIT