内容
# 基因发现路由器
[](https://www.python.org/)
[](https://github.com/jlowin/fastmcp)
[](https://github.com/astral-sh/uv)
[](https://github.com/astral-sh/ruff)
[](https://mypy-lang.org/)
[](#develop--test)
[](#how-discovery-works)
[](LICENSE)
一个薄的 **FastMCP 3.x 聚合器**,它将 GeneFoundry 的 `*-link` MCP 船队聚合到一个单一的 Streamable-HTTP 端点。一个主机(Claude、Cursor、Gemini 等)添加一个服务器——`genefoundry`——并透明地获取所有后端,并具有无冲突的 `<namespace>_<tool>` 命名和基于搜索的发现,而不是一个大约 200 工具的墙。路由器是每个后端的 **客户端** 和主机端的 **服务器**;它命名和塑造表面,但从不重写后端的任何数据。
> ⚕️ **仅用于研究。不是临床决策支持。** 镜像每个后端的免责声明。
**托管端点:** 实时路由器托管在 **`https://genefoundry.org/mcp`** (健康检查在 [`/health`](https://genefoundry.org/health))。将其添加到任何 MCP 主机 - 无需安装:
```bash
claude mcp add --transport http genefoundry https://genefoundry.org/mcp
```
## MCP 服务
后端在提交的 [`servers.yaml`](servers.yaml) 中声明(URL 在 gitignored `.env` 中)。**21 个后端,280 个工具**,每个都以命名空间的形式呈现 - 例如 `gnomad_search_genes`。
| 命名空间 | 领域 | 数据源 | 工具 | 仓库 |
|-----------|--------|-------------|------:|------|
| `pubtator` | 文献和实体注释 | [PubTator3](https://www.ncbi.nlm.nih.gov/research/pubtator3/) | 43 | [pubtator-link](https://github.com/berntpopp/pubtator-link) |
| `gnomad` | 变体/基因/群体频率 | [gnomAD](https://gnomad.broadinstitute.org/) | 22 | [gnomad-link](https://github.com/berntpopp/gnomad-link) |
| `orphanet` | 罕见病理学本体和关联 | [Orphadata](https://www.orphadata.com/) | 19 | [orphanet-link](https://github.com/berntpopp/orphanet-link) |
| `clingen` | 基因-疾病整理 | [ClinGen](https://clinicalgenome.org/) | 17 | [clingen-link](https://github.com/berntpopp/clingen-link) |
| `hpo` | 表型本体和关联 | [人类表型本体](https://hpo.jax.org/) | 17 | [hpo-link](https://github.com/berntpopp/hpo-link) |
| `mavedb` | 变体效应测定分数 | [MaveDB](https://www.mavedb.org/) | 15 | [mavedb-link](https://github.com/berntpopp/mavedb-link) |
| `uniprot` | 蛋白质功能 | [UniProt](https://www.uniprot.org/) | 15 | [uniprot-link](https://github.com/berntpopp/uniprot-link) |
| `mgi` | 小鼠表型和模型 | [MGI](https://www.informatics.jax.org/) | 13 | [mgi-link](https://github.com/berntpopp/mgi-link) |
| `genereviews` | 基因-疾病文献 | [GeneReviews](https://www.ncbi.nlm.nih.gov/books/NBK1116/) | 13 | [genereviews-link](https://github.com/berntpopp/genereviews-link) |
| `mondo` | 疾病本体/交叉引用 | [Mondo](https://mondo.monarchinitiative.org/) | 13 | [mondo-link](https://github.com/berntpopp/mondo-link) |
| `gencc` | 基因-疾病整理 | [GenCC](https://thegencc.org/) | 12 | [gencc-link](https://github.com/berntpopp/gencc-link) |
| `metadome` | 蛋白质耐受性景观 | [MetaDome](https://stuart.radboudumc.nl/metadome/) | 11 | [metadome-link](https://github.com/berntpopp/metadome-link) |
| `stringdb` | 蛋白质-蛋白质相互作用网络 | [STRING](https://string-db.org/) | 10 | [stringdb-link](https://github.com/berntpopp/stringdb-link) |
| `gtex` | 组织表达 | [GTEx 门户](https://gtexportal.org/) | 9 | [gtex-link](https://github.com/berntpopp/gtex-link) |
| `hgnc` | 基因命名 | [HGNC](https://www.genenames.org/) | 9 | [hgnc-link](https://github.com/berntpopp/hgnc-link) |
| `panelapp` | 诊断基因面板和整理 | [PanelApp](https://panelapp.genomicsengland.co.uk/) | 9 | [panelapp-link](https://github.com/berntpopp/panelapp-link) |
| `autopvs1` | 变体 ACMG PVS1 | [AutoPVS1](https://autopvs1.bgi.com/) | 7 | [autopvs1-link](https://github.com/berntpopp/autopvs1-link) |
| `spliceai` | 剪接预测 | [SpliceAI 查找](https://spliceailookup.broadinstitute.org/) | 7 | [spliceailookup-link](https://github.com/berntpopp/spliceailookup-link) |
| `vep` | 变体注释/后果 | [Ensembl VEP](https://rest.ensembl.org/) | 7 | [vep-link](https://github.com/berntpopp/vep-link) |
| `clinvar` | 变体临床意义 | [ClinVar](https://www.ncbi.nlm.nih.gov/clinvar/) | 6 | [clinvar-link](https://github.com/berntpopp/clinvar-link) |
| `litvar` | 变体文献 | [LitVar2](https://www.ncbi.nlm.nih.gov/research/litvar2/) | 6 | [litvar-link](https://github.com/berntpopp/litvar-link) |
## 快速开始
```bash
# 1. 安装(Python 3.12+,uv)
uv sync --group dev
# 2. 配置:复制模板,设置 GF_*_URL 后端 URL 和 GF_AUTH_MODE
cp .env.example .env
# 3. 通过 Streamable HTTP 运行
uv run genefoundry-router run --host 127.0.0.1 --port 8000
# 4. 验证后端可达性
curl -s localhost:8000/health | python -m json.tool
```
然后将您的 MCP 主机指向本地实例:
```bash
claude mcp add --transport http genefoundry http://127.0.0.1:8000/mcp
```
## 发现机制
`genefoundry` 是一个 **元路由器**,而不是一个扁平的工具服务器。列出大约 200 个工具对于模型来说是不可行的,因此路由器公开了一个 **搜索表面** 而不是完整的目录:
- **`search_tools`** — 对整个联合目录进行相关性搜索。
- **`call_tool`** — 按其 `<namespace>_<tool>` 名称调用一个命中。
- 一小部分固定的 **规范入口点** — 每个后端的门户工具(一个自由文本→ID 解析器和/或主要查询),通过 `entrypoints:` 在 `servers.yaml` 中声明,并生成到固定的 `always_visible` 集 *和* 服务器 `instructions` 映射中。固定使得每个领域的规范工具可以通过 **确定性** 发现,而不是通过相关性运气——这是 FastMCP 平面 BM25 索引的解决方法(无字段加权,无词干提取),它让一个简洁的规范工具输给一个包含关键词的冗长工具。
**所有其他内容都通过 `search_tools` → `call_tool` **(也可以通过完整名称直接调用)。典型的流程:
```text
search_tools(query="剪接预测") # → hit: name="spliceai_predict_splicing", inputSchema, returns
call_tool(name="spliceai_predict_splicing", arguments={...})
```
模型通过 MCP **`instructions`** 字段(在服务器上设置)加上 `search_tools`/`call_tool` 描述来定位此两层模型。路由器还 **改进了搜索本身**:其 `CompactBM25SearchTransform` 将工具名称/叶和标签折叠到索引中,并对文档和查询都进行词干提取,因此单词形式不匹配(`expressed`↔`expression`)和关键词填充的文章不会再隐藏正确的工具。联合名称也适用于 Gemini Remote MCP(蛇形, `[a-z0-9_]`,≤64 个字符)。
可发现性是 **经过衡量,而不是假设的**:`make bench-discoverability` 评估了在目录快照中,大约 50 个现实意图通过此确切表面达到其规范工具的可靠性(目前 **9.79/10**,100% 在前 5 名中可达)。该标准在 CI 中强制执行(`tests/discoverability/`),因此调整固定、搜索或描述保持证据驱动。
> **两个需要避免的陷阱**(参见 [#3](https://github.com/berntpopp/genefoundry-router/issues/3))。您的 **主机/客户端工具列表中缺少的功能并不意味着它不存在** — 主机只看到 `search_tools`、`call_tool` 和固定的入口点;调用 `search_tools` 在得出工具不存在的结论之前;并且 `search_tools` 返回 *数据*,因此您 **不要重新运行主机工具搜索来调用命中** — 只需调用 `call_tool`。`Unknown tool: call_tool` 表示您的客户端已将其删除;重新运行 `search_tools` 以重新发现并继续(可恢复,而不是路由器故障)。
## 配置
结构位于提交的 `servers.yaml` 中;URL/秘密位于 gitignored `.env` 中(复制 `.env.example`)。关键环境变量(前缀 `GF_`):
| 变量 | 默认值 | 目的 |
|----------|---------|---------|
| `GF_HOST` / `GF_PORT` | `127.0.0.1` / `8000` | 绑定地址 |
| `GF_MCP_PATH` | `/mcp` | MCP 安装路径 |
| `GF_SERVERS_FILE` | `servers.yaml` | 后端注册表 |
| `GF_AUTH_MODE` | `none` | `none` \| `jwt` \| `oauth`(在生产中使用 jwt/oauth) |
| `GF_ALLOW_INSECURE` | `false` | 选择在非环回绑定上以 `auth=none` 提供服务(仅限 PoC;路由器拒绝否则) |
| `GF_PUBLIC_BASE_URL` | _(unset)_ | 路由器的规范公共 URL — OAuth 资源 URI + 受保护资源元数据 |
| `GF_JWT_ISSUER` | _(unset)_ | jwt/oauth:令牌发行者 URL(例如 `https://auth.example.org/realms/genefoundry`) |
| `GF_JWT_JWKS_URL` | _(unset)_ | jwt/oauth:发行者 JWKS 端点(签名验证密钥) |
| `GF_JWT_AUDIENCE` | _(unset)_ | jwt/oauth:所需的令牌 `aud`(必须匹配;受众绑定) |
| `GF_OAUTH_CLIENT_ID` / `GF_OAUTH_CLIENT_SECRET` | _(unset)_ | oauth:上游提供商客户端凭证 |
| `GF_OAUTH_AUTHORIZE_URL` / `GF_OAUTH_TOKEN_URL` | _(unset)_ | oauth:上游提供商授权/令牌端点 |
| `GF_ALLOWED_ORIGINS` | _(empty)_ | CSV `Origin` 允许列表(DNS 重新绑定防御) |
| `GF_<NAME>_URL` | _(unset)_ | 每个后端的 `/mcp` URL(例如 `GF_GNOMAD_URL`) |
未设置的后端 URL 或 `enabled: false` 的后端将被跳过并发出警告;路由器仍将启动。
**无令牌传递:** 调用者在边缘进行身份验证,并且他们的令牌从不转发到后端(混淆委托防御)。
### 身份验证
路由器拒绝在非环回绑定上以 `auth=none` 启动,除非 `GF_ALLOW_INSECURE=true`(显式、记录的逃生阀,用于故意公开的 PoC)。对于生产,请启用两种 **资源服务器** 模式之一 — 路由器 **验证** 来自身份提供者的令牌,它不颁发令牌,因此需要 IdP(例如自托管的 Keycloak):
- **`oauth`** — 适用于交互式/浏览器 MCP 客户端(claude.ai、Cursor)。路由器提供受保护的资源元数据(RFC 9728)+ `WWW-Authenticate`,并代理上游提供商,以便客户端可以完成登录流程;访问令牌根据 `GF_JWT_JWKS_URL` 和受众绑定 (`GF_JWT_AUDIENCE`) 进行验证。路由器的 OAuthProxy **是** 动态客户端注册的外观 — 它提供自己的 `/register`,因此 Keycloak 的 DCR 保持关闭;在 Keycloak 客户端上,将路由器的回调 `https://genefoundry.org/auth/callback` 列为有效重定向 URI。
- **`jwt`** — 机器对机器:验证来自 `GF_JWT_ISSUER`(JWKS + 受众)的承载 JWT,没有交互式登录界面。
示例(自托管 Keycloak 在 `auth.example.org`,领域 `genefoundry`):
```bash
GF_AUTH_MODE=oauth
GF_OAUTH_CLIENT_ID=genefoundry-router
GF_OAUTH_CLIENT_SECRET=… # 机密;设置在服务器环境中,从不提交
GF_OAUTH_AUTHORIZE_URL=https://auth.example.org/realms/genefoundry/protocol/openid-connect/auth
GF_OAUTH_TOKEN_URL=https://auth.example.org/realms/genefoundry/protocol/openid-connect/token
GF_JWT_ISSUER=https://auth.example.org/realms/genefoundry
GF_JWT_JWKS_URL=https://auth.example.org/realms/genefoundry/protocol/openid-connect/certs
GF_JWT_AUDIENCE=https://genefoundry.org/mcp # Keycloak 必须将此值印章到令牌 `aud`
GF_PUBLIC_BASE_URL=https://genefoundry.org # 裸公共源,没有路径 — OAuth 路由在根目录
```
`GF_PUBLIC_BASE_URL` 是裸公共源:OAuth 端点(`/authorize`,`/token`,`/register`,`/.well-known/*`)在根目录提供,MCP 端点是 `GF_PUBLIC_BASE_URL` + `GF_MCP_PATH`(→ `https://genefoundry.org/mcp`),这也是 OAuth 资源/令牌受众。将路径放在此处(`…/mcp`)会错误地宣传 OAuth 端点为 `…/mcp/authorize` 并使受保护的资源元数据 URL 数量增加一倍。
验证:未经身份验证的 `POST /mcp` 返回 `401` + `WWW-Authenticate`;带有有效颁发者签名、正确受众的令牌的请求返回 `200`。参见 `.env.docker.example` 获取所有三种模式。
## 命令行界面
```bash
genefoundry-router run --host 0.0.0.0 --port 8000 # 通过 Streamable HTTP 提供服务
genefoundry-router validate # 检查 servers.yaml + 环境变量,报告缺失的 URL
genefoundry-router list-tools [--namespace gnomad] # 枚举联合工具,标记 >64 个字符的名称
genefoundry-router doctor [--strict-naming] # ping 后端;审计叶名称与 Tool-Naming v1
genefoundry-router drift [--manifest ci/fleet-baseline.json] # 工具定义拉格-拉格 tripwire;退出 0/1/2
```
## 漂移检测(计划的 CI)
后端可以在审查时提供一个干净的工具,并在稍后更改其 **描述或输入模式** — 这是 *拉格拉格* /*工具中毒* 的渠道,因为工具描述是指导模型读取的指令。`genefoundry-router drift` 指纹每个后端的工具 `{name, description, inputSchema}`(SHA-256)并将 **实时** 机队与固定的基线进行比较。`.github/workflows/drift.yml` 每 6 小时运行一次(选择加入)并通过 GitHub 问题和健康检查.io 死人开关提醒。
**退出代码:** `0` 无漂移,所有可达 · `1` 可达后端中的漂移(警报)·
`2` 无漂移但 ≥1 个后端不可达(可用性警告,**非** 警报)。安全性胜过可用性:漂移 + 中断仍退出 `1`。
**两个提交的(非机密)文件:**
| 文件 | 内容 | 保持同步 |
|------|------|--------------|
| `ci/fleet-urls.env` | 公共 `GF_*_URL=https://<name>-link.genefoundry.org/mcp` 用于每个启用的后端 — CI 探针的 URL | `make` 测试 `test_ci_fleet_urls.py` 断言它与 `servers.yaml` 完全匹配 |
| `ci/fleet-baseline.json` | 固定的 **实时** 工具定义基线(`source:"live"`,所有启用的后端) | `make snapshot-baseline` 重新固定;`test_ci_fleet_baseline.py` 断言实时 + 完整覆盖 |
**重新固定规则:** 当漂移因 **合法** 的上游更改而触发时,使用 `make snapshot-baseline` 刷新基线并通过 **审查的 PR** 合并(将工具差异视为与安全相关的)。CI 中永远不要自动刷新 — 那将无声地宽恕一次拉格拉格。
**启用它(一次,在默认分支)。** GitHub → *设置 → 机密和变量 → 操作* 中配置了所有三个设置:
| 设置 | 类型 | 是否需要? | 值 / 获取位置 |
|---------|------|-----------|-------------------------|
| `DRIFT_ENABLED` | 存储库 **变量** | 启用计划运行 | `true`。未设置/`false` ⇒ 计划运行是一个无操作(分支保持关闭);`workflow_dispatch` 始终运行 |
| `DRIFT_HEARTBEAT_URL` | 存储库 **机密** | 可选(心跳) | [healthchecks.io](https://healthchecks.io) 检查的 ping URL(创建检查:周期 **6 小时**,宽限期 **~45 分钟**)。未设置 ⇒ 心跳步骤跳过 |
| `DRIFT_OPEN_ISSUE` | 存储库 **变量** | 可选 | `false` 依赖仅 red 运行 + 所有者电子邮件,而不是自动问题(默认为开启) |
```bash
gh variable set DRIFT_ENABLED --body true
gh secret set DRIFT_HEARTBEAT_URL --body 'https://hc-ping.com/<your-uuid>'
gh workflow run drift.yml # 烟雾测试;预期退出 0 和 healthchecks.io 检查绿色
```
`drift` 命令行界面可独立运行于您自己的 cron/CI:
`genefoundry-router drift --servers-file servers.yaml --manifest ci/fleet-baseline.json`
(从环境中读取 `GF_*_URL` — `set -a; . ./.env; set +a` 本地,或加载
`ci/fleet-urls.env`)。
## 开发和测试
```bash
make ci-local # 格式检查,lint,600-LOC 预算,mypy,单元 + 集成测试
make test # 仅限单元 + 集成测试
make bench-discoverability # 在目录快照上评估工具可发现性(离线)
make run # 在实时机队上提供服务(导出 .env)
```
离线假机队(`make dev-fleet` + `make run-dev`,或一次性 `make test-e2e`)在模拟后端上运行真正的路由器,通过实时 Streamable-HTTP — 无 Docker,无网络。
## 文档
- 设计规格 — [`docs/specs/2026-06-13-genefoundry-router-design.md`](docs/specs/2026-06-13-genefoundry-router-design.md)
- 代理人工程规格 — [`docs/specs/2026-06-16-router-agentic-ergonomics-design.md`](docs/specs/2026-06-16-router-agentic-ergonomics-design.md)
- 工具命名标准 v1 — [`docs/TOOL-NAMING-STANDARD-v1.md`](docs/TOOL-NAMING-STANDARD-v1.md)
- 响应信封标准 v1 — [`docs/RESPONSE-ENVELOPE-STANDARD-v1.md`](docs/RESPONSE-ENVELOPE-STANDARD-v1.md)
- 容器和部署强化标准 v1 — [`docs/CONTAINER-HARDENING-STANDARD-v1.md`](docs/CONTAINER-HARDENING-STANDARD-v1.md)
- 贡献者指南 — [`AGENTS.md`](AGENTS.md)
## 许可证
[MIT](LICENSE) 由 Bernt Popp 著作
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器