Skip to main content
Glama
Romumrn

ViromeChat MCP server

by Romumrn

Я(?)——等等,这是翻译任务。我只需输出译文。开始。

ViromeChat MCP 服务器(ViromeChat MCP 服务器)

一个 FastMCP 服务器,负责并拥有 Viromech@t 的所有数据集访问、外部 API 调用和业务逻辑。客户端(位于独立 viromechat 仓库中的 FastAPI 后端 / React 前端)从不直接接触 dataframe、S3 凭据或列名——它只通过 MCP/HTTP 与该服务器进行泛化交流,即读取它当前发布的任何工具和资源。

本仓库是该服务器的独立所在。它不依赖 app 仓库;两者之间的唯一契约是下文记录的那组 MCP 工具/资源,由后端通过其 MCP_SERVER_URL 环境变量来消费。


运行方式

前置要求: taxonomy 数据集(data/TAXONOMY.csv,约 327 MB)通过 Git LFS 存储。克隆前在每台机器上运行一次 git lfs install,或者克隆后运行 git lfs pull,以将其物化。

本地(Python)

git lfs pull                      # fetch data/TAXONOMY.csv
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env              # fill in your S3 credentials
python server_mcp.py

Docker

cp .env.example .env              # fill in your S3 credentials
docker compose up --build

无论哪种方式,它都会在 0.0.0.0:8000 上启动一个 HTTP 服务器,MCP 端点为 /mcp (http://localhost:8000/mcp —— 这是后端 MCP_SERVER_URL 指向的地址)。启动时它:

  1. 将 data/TAXONOMY.csv 完整加载进内存,作为 df_taxo。

  2. 加载构建下面两个 MCP 资源所需的两个列描述文件(data/v@_columns_description.csv 和 data/TAXONOMY_columns_description.json)。

  3. 打开一个进程内 DuckDB 连接,安装 httpfs 和 spatial 扩展,并在 S3 Parquet 数据集之上注册一个 host 视图 — Parquet 文件从不加载进内存;每一次 query_host_sql 调用都由 DuckDB 下推到 S3(列剪枝 / 行组剪枝)。

测试

pip install pytest
pytest

辅助测试会覆盖纯函数(_ok/_fail、图表/表格构建器、SQL 防护),并且不需要真实的 S3 连接。


Related MCP server: Alma Atlas

集成客户端

任何 MCP 客户端的都可以消费这个服务器。Viromech@t 后端通过 fastmcp.Client 完成:

from fastmcp import Client

async with Client("http://localhost:8000/mcp") as mcp:
    tools = await mcp.list_tools()
    result = await mcp.call_tool("wikipedia_search", {"search_term": "Lentivirus"})

客户端应该动态地发现工具和资源(list_tools() / list_resources()),并分配对 artifact["type"] 进行分发——绝不硬编码工具名称或列知识。这正是两个仓库保持解耦的原因:在这里新增一个复用现有 artifact 类型的工具,不需要客户端做任何改动。


资源

资源是静态的、读取一次的知识 — 不是像工具那样被 LLM “调用”的东西。客户端的每个对话读取一次,并将其内容并入系统提示。

URI

内容

来源

resource://datasets/host/schema

host 表的每一列的 JSON 映射 {column_name: {description, Type}}

data/v@_columns_description.csv

resource://datasets/taxonomy/schema

df_taxo 的完整 JSON 模式(名称、描述、列、方主键、行定义)

data/TAXONOMY_columns_description.json

再加一个新资源(例如第三个数据集)不要求任何客户端改动:客户端通过 list_resources() 发现资源,并统一地读取每一个资源。


响应契约

每个工具都返回这个形状,无论它做什么:

{
  "success": true,           // or false
  "content": "human-readable text — this is what the LLM reads back as the tool result",
  "artifacts": [ ... ]        // structured extras the client can render; [] if none
}

失败时,content 持有错误消息(尽可能附重试指引),artifacts 为空。server_mcp.py 顶部的两个辅助函数 _ok(content, artifacts) / _fail(content) 用于构建这个形状—请始终使用它们,而不是手动构造一个 dict。

Artifact 类型

type

由以下产生

结构

客户端消费为

url

wikipedia_search

{"type": "url", "url": "..."}

“Sources” 面板中的 Wikipedia 链接

pubmed

pubmed_search

{"type": "pubmed", "pmids": [123, 456]}

PubMed 链接 + 供给发生幻觉防护的 PMID 白名单

ncbi_taxonomy

ncbi_taxonomy_search

{"type": "ncbi_taxonomy", "url": "...", "tax_id": "..."}

“Sources” 面板中的 NCBI Taxonomy 链接

table

query_host_sql, query_dataframe

{"type": "table", "rows": [...], "columns": [...], "total_rows": N}

在 “Sources” 中作为已执行 SQL/代码追踪;rows 以 preview_rows 为上限

plotly

create_visualization, create_map

{"type": "plotly", "figure": {...}}(来自 fig.to_json(),已被解析回 dict)

渲染出的 plotly 图表

客户端完全根据 artifact["type"] 分发——绝不做任何标识对工具的名称。添加一个复用现有 artifact 类型的工具(例如另一个返回 "table" 的工具)不需要客户端做任何改动。


工具集合

wikipedia_search(search_term: str, wikipedia_limit: int = 4000) -> dict

在 Wikipedia 上查找某个页面;如果没有完全匹配的标题,就回退到最接近的全文搜索匹配(内容中标为“模糊匹配”提示)。返回一个 url artifact。

pubmed_search(query: str, max_results: int = 5) -> dict

在 PubMed(NCBI E-utilities esearch + efetch,db=pubmed)中搜索,并返回每条命中的标题、作者、期刊、年份、摘要、DOI 和 PMID。返回一个包含所有真实 PMID 的 pubmed artifact——这是客并端 PMID 幻觉防护的权威唯一来源。

ncbi_taxonomy_search(name: str) -> dict

把任意生物物种名称——缩写、常用名或学名——解析到 NCBI Taxonomy 数据库(E-utilities,db=taxonomy)。返回每个匹配结果:学名、分类层级(species/genus/family/……)、门(division)、完整谱系学及已行知同义/缩写。这就是把 HIV 变成 Human immunodeficiency virus 1 / 属 Lentivirus,或者检查某个名称究竟是一个属还是一个科,而不依赖 Wikipedia 措辞的权威途径。对最佳匹配返回一个 ncbi_taxonomy artifact。

实现说明:NCBI 的 efetch XML 在每个结果的 <LineageEx> 内为每个祖先级别嵌套一个 <Taxon>。解析器只遍历 root.findall(" entirely, that(直接子元素就好了)—— 如果使用.//Taxon` 会把每个祖先循环都当作是另一个独立匹配项。

query_host_sql(sql: str, preview_rows: int = 50) -> dict

针对 host 视图执行一个只读的 SELECT 语句(S3 Parquet 数据集),返回一个 table artifact。这必要的第一步必须先执行,然后 query_dataframe、create_visualization 或 create_map 才能使用 df_host —— 这些工具操作的是最近的**一次 query_host_sql 调用的结果(ctx.last_host_result),而不是整个数据集。

执行前后强制执行以下保护网:

  • 只能一条 SELECT 语句——INSERT/UPDATE/DELETE/DDL/PRAGMA/... 会被 _FORBIDDEN_SQL_KEYWORDS 拒绝。

  • 单点 SELECT * 会被直接拒绝。 host 约有 65 列,其中包含一个较重的 geometry blob;在该防护存在之前,从 S3 拉取每个匹配行的全部列正导致了多分钟超时。调用者必须只投影他们需要的列。

  • 坐标位于本地的 GEOMETRY 点列中,而不是普通字段普通 lat/lon —— 请用 ST_X(geometry) AS lon, ST_Y(geometry) AS lat 提取它们(spatial 扩展在启动时已加载)。

query_dataframe(code: str, preview_rows: int = 50) -> dict

执行 pandas 代码,作用域中包含 df_taxo、原型末 frame(不可用,等于 ctx.last_host_client,如果尚未调用 query_host_sql 则返回明确错误)、pd 和 np。必须将 DataFrame 指派给 result。返回一个 table artifact。

create_visualization(code: str) -> dict

与 query_dataframe 相同的执行环境不同点在于多了 px/go。必须把 Figure 赋值给 fig。空图(0 个数据点)会带指引信息地被拒绝,而不是静默返回一张空图表发出一个 plotly artifact。

create_map(code: str) -> dict

与 create_visualization 相同,但强制只能使用 px.scatter_mapbox(...)(不可用 scatter_map),并强制之前的 query_host_sql 调用已从 geometry 提取出 longitude/latitude。返回一个 plotly artifact。

必需样本标识符:除非 primary_id(BioSample 登录号)出现在 hover_data 中,否则图形会被拒绝——每个绘制点必须能够追溯到具体的样本。这一条在代码中强制实现(_check_hover_has_column(fig, "primary_id")),不只是写在 docstring 中——缺少该列的 map 会无可争辩地 _fail(...)。


扩展服务器

要添加一个新工具,请按照以下步骤:

  1. 把它写成普通函数,用 @mcp.tool 装饰,返回 _ok(content, artifacts) 或 _fail(content)——不要手写 dict。

  2. 如果它得出可供客户端特殊渲染的输出(链接、表格、图形),就要复用上表中已有的 artifact 类型,只要其结构相符即可——这可以使客户端改动为零。如果结构确实是新出现,才发明新 type(并把接入到客户端的在分布循环中)。

  3. 把每条使用规则、注意事项和示例写在 docstring 中。工具的描述会原样发给 LLM——这是唯一应该存储该数据集特有指引的地方。

  4. 如果某工具需要一个可配置的 UI 默认值(如 preview_rows 或 wikipedia_limit),就一直给参数命名;客户端会将匹配的专家设置作用到 JSON schema 中声明了同名参数的任意工具。


配置

server_mcp.py 在 import 时通过 mcp_config.py 的 load_env_file() 读取 .env(另有 .env.example):

变量

必需

默认值

含义

ENDPOINT

是

—

S3 兼容性 endpoint 主机名

ACCESS_KEY

是

—

S3 访问密钥

SECRET_KEY

是

—

S3 私密密钥

BUCKET

是

—

S3 存储桶名称

VIRAL_HOST_DATASET

是

*.parquet

Bucket 中 Parquet 数据的对象键

REGION

否

fr

S3 区域

S3_URL_STYLE

否

path

DuckDB s3_url_style 设置

TAXO_DB_PATH

否

data/TAXONOMY.csv

taxonomy CSV 的本地路径

非敏感设置位于 mcp_config.py 中。

Related MCP Connectors

Related MCP Servers