MCP 扩展:把外部工具接进来
本教程共 25 篇 · 第 19 篇 · 更新于 2026-07-26 · 约 15 分钟阅读
19. MCP 扩展:把外部工具接进来
本节目标:搞懂 MCP(Model Context Protocol)是什么、Hermes Agent 怎么通过它把外部工具接进来、怎么过滤要暴露给模型的工具、OAuth 怎么走、以及怎么反过来把 Hermes 自己当 MCP 服务器给别的 Agent 用。学完你能给 Hermes 接一个 GitHub 或文件系统的 MCP server,并按需裁剪工具表面。
MCP 是干什么的
先打个比方。你买了个万能遥控器,家里电视、空调、机顶盒各家有各家的协议,按理说你要为每个设备学一套按键逻辑。但遥控器支持一个统一协议,每个设备配一个适配器,遥控器只管发统一指令,适配器翻译成各家的话。
MCP(Model Context Protocol)就是 AI Agent 世界的这个统一协议。Hermes Agent 是遥控器(MCP client),外部那些工具服务器是设备(MCP server),它们之间用 MCP 协议通信。
具体来说,MCP 协议规定了三件事:
- 怎么发现工具:server 告诉 client「我有哪些工具」
- 怎么调用工具:client 发请求,server 返回结果
- 怎么传输:两种方式,stdio 管道 或 HTTP
对接之后,对 Hermes 来说,MCP 工具和 70+ 内置工具没有任何区别。模型调 mcp_github_list_issues 和调内置的 web_search 走的是同一套注册和分发机制,它根本不知道背后是一个外部进程在干活。
为什么不直接写内置工具
你可能会问:要个 GitHub 工具,我写个 github_tool.py 调 GitHub API 不就行了?
能用,但有三个问题。
每个 API 都要从头写。GitHub API 几十个端点,每个都要写 handler、定义 schema、处理错误、处理分页。而社区已经把这些做好打包成 MCP server 了,启动就能用。
工具和 Agent 绑死。内置工具跑在 Agent 进程里,要是工具依赖 Node.js(GitHub MCP server 是 Node 写的),你的 Python Agent 还得装 Node 运行时。工具崩了,Agent 跟着崩。MCP server 跑在独立进程,崩了不影响 Agent,重启就好。
没有统一发现机制。今天接 GitHub、明天接 Jira、后天接 Slack,每个 API、认证、schema 格式都不一样。MCP 把发现、调用、传输三件事统一了。
两种 MCP 服务器
Stdio 服务器
Stdio server 是本地子进程,通过 stdin/stdout 管道和 Hermes 通信。
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
适合:服务器装在本地、想低延迟访问本地资源、你跟着 MCP server 文档里写的 command/args/env 配。
HTTP 服务器
HTTP MCP server 是远程端点,Hermes 直接连过去。
mcp_servers:
remote_api:
url: "https://mcp.example.com/mcp"
headers:
Authorization: "Bearer ***"
适合:server 托管在别处、组织内部暴露 MCP 端点、你不想让 Hermes 在本地起子进程。
快速上手
MCP 支持随标准安装一起装好,不用额外步骤。
- 在
~/.hermes/config.yaml加一个 MCP server:
mcp_servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
- 启动 Hermes:
hermes chat
- 让 Hermes 用上这个能力:
List the files in /home/user/projects and summarize the repo structure.
Hermes 会发现这个 MCP server 的工具,像用普通工具一样用它。
工具命名规则
MCP 工具注册进 Hermes 时,名字会加 mcp_<server名>_ 前缀,避免和内置工具撞名:
mcp_<server_name>_<tool_name>
几个例子:
| Server | MCP 工具 | 注册名 |
|---|---|---|
filesystem | read_file | mcp_filesystem_read_file |
github | create-issue | mcp_github_create_issue |
my-api | query.data | mcp_my_api_query_data |
实际你不用手动调带前缀的名字,Hermes 看到工具在正常推理时会自己选。
Note名字清洗:server 名和工具名里的连字符(
-)和点(.)在注册前会被替换成下划线,保证名字是合法的 LLM 函数调用标识符。比如my-api暴露的list-items.v2会变成mcp_my_api_list_items_v2。写include/exclude过滤器时要用原始 MCP 工具名(带连字符/点),不是清洗后的版本。
工具发现与动态刷新
Hermes 在启动时发现 MCP server 并把工具注册进正常工具 registry。运行中如果 MCP server 的可用工具变了,server 会发 notifications/tools/list_changed 通知,Hermes 收到后自动重新拉取工具列表更新 registry,不用手动 /reload-mcp。
这对那种能力动态变化的 server 有用—比如加载了新数据库 schema 后多了一组工具,或服务下线后工具消失。刷新有锁保护,同一个 server 连发通知不会触发重叠刷新。
每个配置的 MCP server 只要贡献至少一个注册工具,就会创建一个运行时工具集(toolset)叫 mcp-<server>,方便在工具集层面统一管理。
工具过滤:少即是多
好的 MCP 用法不是「全连上」,而是「连对的,暴露最小的有用表面」。一个 MCP server 可能暴露 50 个工具,全注册进来模型的工具列表会很长、选择变难、token 消耗变大。
整个 server 禁用
mcp_servers:
legacy:
url: "https://mcp.legacy.internal"
enabled: false
enabled: false 时 Hermes 完全跳过这个 server,不连、不发现、不注册。配置留在那以后还能用。
白名单(推荐)
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
tools:
include: [create_issue, list_issues]
只注册列出来的工具。对敏感系统(财务、客户、破坏性操作)这是最安全的默认。
黑名单
mcp_servers:
stripe:
url: "https://mcp.stripe.com"
tools:
exclude: [delete_customer]
注册除列出来的之外的所有工具。
优先级
两个都写时 include 赢:
tools:
include: [create_issue]
exclude: [create_issue, delete_issue]
结果:create_issue 仍允许,delete_issue 因 include 优先被忽略。
关掉工具外的辅助包装
Hermes 还会给 MCP server 注册一些工具外的辅助工具:
- 资源类:
list_resources、read_resource - 提示词类:
list_prompts、get_prompt
不想让模型碰这些表面,可以单独关掉:
mcp_servers:
docs:
url: "https://mcp.docs.example.com"
tools:
prompts: false
resources: false
能力感知注册
即便你写 resources: true 或 prompts: true,Hermes 也只在 MCP 会话真的支持对应能力时才注册这些辅助工具。所以一个只暴露工具、不支持 resources/prompts 的 server,不会多出这些包装。这让工具列表保持诚实。
全过滤掉怎么办
如果配置过滤掉了所有可调用工具,又关掉了所有辅助包装,Hermes 不会为这个 server 创建一个空的运行时 MCP 工具集。工具列表保持干净。
改完配置怎么生效
/reload-mcp
这个斜杠命令从配置重新加载 MCP server、刷新可用工具列表。改了 include/exclude、enabled 标志、resources/prompts 开关、auth 头或 env 之后都要跑一下。
OAuth 认证
很多托管 MCP server(Linear、Sentry、Atlassian、Asana、Figma、Stripe 等)要求 OAuth 2.1,而不是静态 bearer token。配 auth: oauth,Hermes 通过 MCP Python SDK 处理发现、动态客户端注册、PKCE、token 交换、刷新、step-up auth:
mcp_servers:
linear:
url: "https://mcp.linear.app/mcp"
auth: oauth
第一次连接时,Hermes 打印一个授权 URL,能开浏览器就开,然后在本地 loopback 端口等 OAuth 回调。token 缓存在 ~/.hermes/mcp-tokens/<server>.json,权限 0o600,后续运行静默复用直到刷新失败。
远程 / 无头主机
Hermes 跑在另一台机器上、浏览器在你笔记本上时,loopback 回调够不到你。三种办法完成流程:
- 粘贴回填(零配置):交互式终端里 Hermes 会打印「Or paste the redirect URL here…」和授权 URL。在浏览器开 URL、批准、复制浏览器最终落到的那条 URL(重定向会报连接错误,正常),粘到提示符。裸
?code=…&state=…查询串也行 - SSH 端口转发:另开一个终端
ssh -N -L <port>:127.0.0.1:<port> user@host,然后正常走重定向流程 - 代理回调(
redirect_uri):当公共 HTTPS 端点能转发到主机(比如 Tailscale Funnel 或反代指向回调端口),设oauth.redirect_uri,浏览器重定向自己就能到 Hermes,不用隧道或粘贴:
mcp_servers:
myserver:
url: "https://mcp.example.com/mcp"
auth: oauth
oauth:
redirect_port: 8765
redirect_uri: "https://oauth.example.ts.net/callback"
完全无头的 Gateway(消息机器人、根本没有交互终端)可以用可选的 mcp-oauth-remote-gateway 技能,让 Agent 手动走完流程并把 token 写到 Hermes 期望的位置。
WarningWAF 拒绝
127.0.0.1重定向 URI:少数 provider 的授权服务器前面有 WAF,会 403 任何查询串里带字面127.0.0.1的授权请求(Reclaim.ai 的 AWS API Gateway 是已知例子)。设oauth.redirect_host: localhost改用http://localhost:<port>/callback就行。
Warning不支持自动注册的 provider(Google Drive、Atlassian):有些 server 拒绝裸
auth: oauth依赖的动态客户端注册步骤(RFC 7591)。Google 官方 Drive server 直接返回 400,没 OAuth client 被创建、没 token 被拿到。症状很隐蔽:这些 server 不带 auth 也能服务tools/list,所以hermes mcp login能列出工具看起来像成功了,但之后的真实工具调用全部超时。hermes mcp login现在能检测这个(会检查 token 真的落到磁盘了)并提示你自备 OAuth client。在 provider 控制台建一个,加到配置:
mcp_servers:
googledrive:
url: "https://drivemcp.googleapis.com/mcp/v1"
auth: oauth
oauth:
client_id: "<your-oauth-client-id>"
client_secret: "<your-oauth-client-secret>"
然后 hermes mcp login googledrive—有了预注册 client,Hermes 跳过注册、走正常浏览器授权。
Warning配置自动重载竞态:从运行中的 Hermes 会话里编辑
~/.hermes/config.yaml时,CLI 自动重载 MCP 连接有 30 秒超时,不够走交互式 OAuth。先把条目加上,再从新终端跑hermes mcp login <server>—它会等满 5 分钟让你完成认证。
mTLS / 客户端证书
要求双向 TLS(客户端证书认证)的远程 HTTP MCP server 通过 client_cert / client_key 支持。client_cert 接受三种形态:
单个合并 PEM 路径(一个文件同时含证书和私钥):
mcp_servers:
internal_api:
url: "https://mcp.internal.example.com/mcp"
client_cert: "~/.certs/mcp-client.pem"
[cert, key] 二元组(证书和私钥分文件,等价于设 client_cert + client_key):
mcp_servers:
internal_api:
url: "https://mcp.internal.example.com/mcp"
client_cert: ["~/.certs/mcp-client.crt", "~/.certs/mcp-client.key"]
[cert, key, password] 三元组(私钥加密时第三元素是口令):
mcp_servers:
internal_api:
url: "https://mcp.internal.example.com/mcp"
client_cert: ["~/.certs/mcp-client.crt", "~/.certs/mcp-client.key", "${MCP_KEY_PASSWORD}"]
路径支持 ~ 展开;文件缺失会抛清晰的、server 范围的错误,而不是晦涩的 TLS 握手失败。
Catalog:一键装 Nous Research 审过的 MCP
Hermes 自带一个精选 catalog,里面是 Nous Research 员工审过合并的 MCP server。默认全禁用,按需装:
hermes mcp # 交互式选择器(默认)
hermes mcp catalog # 纯文本列表,可脚本化
hermes mcp install n8n # 按名字装一个 catalog 条目
选择器每行显示当前状态:
n8n available Manage and inspect n8n workflows from Hermes
linear enabled Linear issue/project management (remote OAuth)
github installed (disabled) GitHub repo + PR tools
在某行回车可以装(并走必要的凭据配置)、启用、禁用或卸载。Catalog 条目存在 hermes-agent repo 的 optional-mcps/ 下—出现在那个目录就意味着过了 Nous Research 审批。没有社区提交层,靠合并 PR 加条目。
Catalog 条目可能要求:
- API key:Hermes 在安装时提示,把值写到
~/.hermes/.env。非敏感值(base URL)也写到同文件 - OAuth(远程 MCP):在配置里写成
auth: oauth,MCP 客户端首次连接时开浏览器 - OAuth(Google/GitHub 等第三方 provider):Hermes 指你到
hermes auth <provider>,如果你还没认证过
安装时选工具
凭据配好后,Hermes 探测 MCP server 列出它暴露的每个工具,给一个勾选清单:
Select tools for 'linear' (SPACE toggle, ENTER confirm)
[x] find_issues Find issues matching a query
[x] get_issue Get a single issue
[x] create_issue Create a new issue
[ ] delete_workspace Delete a Linear workspace
...
预勾选的行来自:
- 你之前装过这个条目的选择(重装保留你之前的,manifest 默认不覆盖)
- manifest 的
tools.default_enabled(有的 catalog 条目预裁剪掉修改性或罕用工具) - 都没有就全选
回车提交后,只有勾选的工具进 mcp_servers.<name>.tools.include。全选的话不写过滤器(最干净的配置形态,行为一样)。
Note探测失败(server 不可达、OAuth 还没完成、后端服务没跑)时安装仍会成功:manifest 的
tools.default_enabled直接应用(如果有声明),否则不写过滤器。等 server 可达了再跑hermes mcp configure <name>精修。
信任模型
装 catalog 条目会跑 manifest 指定的一切—git clone、条目的 bootstrap 命令(pip install、npm install 等)、最终是 MCP server 自己的代码。Manifest 靠 hermes-agent repo 的 PR 审核把关,Nous Research 在每个条目发布前都审过—但你装之前还是该读一下 manifest,特别是 source: 字段的仓库、install.bootstrap: 命令、任何 transport.command: 调用。
Manifest 在 GitHub 的 optional-mcps/<name>/manifest.yaml。选择器安装时也会打印 manifest 的 source: URL 方便你核对上游 repo。Web Dashboard 的 MCP 页面同样展示每个 catalog 条目的细节—传输、auth 类型、端点 URL(HTTP)或 command + args(stdio)、git 安装源/ref 和 bootstrap 命令、setup 说明—source: 渲染成可点链接,让你点 Install 前能看清这个条目连什么、跑什么。
运行时环境变量替换
条目的 transport.command、transport.args、transport.url、headers 里,${VAR} 占位符在 server 连接时从环境变量(包括 ~/.hermes/.env 里的一切)解析。这对 catalog 条目引用用户在别处配的值有用,比如 ${HOME}/foo 或 ${MY_PROVIDER_TOKEN}。
注意这跟 catalog manifest 里的 ${INSTALL_DIR} 不同—后者在安装时被替换成 catalog 把条目 repo clone 进的路径。
之后改工具选择
hermes mcp configure linear
重新打开同一个勾选清单,预勾选你当前的选择。想多启用些工具、或 server 加了新工具你想纳入时用。
更新 catalog manifest
MCP 永不自动更新。Hermes 升级后如果 manifest 版本变了,重跑 hermes mcp install <name> 刷新。
要给 catalog 加 MCP,往 optional-mcps/ 提 PR。
Manifest 版本兼容
Manifest 钉一个 manifest_version。Catalog 向前兼容:PR 加了个 manifest_version 比你装的 Hermes 理解的更新的条目时,选择器会给该条目显示警告(⚠ '<name>' requires a newer Hermes)而不是静默隐藏。看到就跑 hermes update 装最新 Hermes。
内置预设
对知名的 MCP server,hermes mcp add 接受一个 --preset 标志填好传输细节,省得你查 command 和 args。预设只提供默认值—同一命令行上再传别的(env、headers、过滤)仍然优先。
| 预设 | 装的是什么 |
|---|---|
codex | Codex CLI 的 MCP server(codex mcp-server 走 stdio)。需要 PATH 里有 codex CLI |
hermes mcp add codex --preset codex
这等价于写出:
mcp_servers:
codex:
command: "codex"
args: ["mcp-server"]
你可以挑任何本地名(hermes mcp add my-codex --preset codex 也行);预设只提供 command/args 默认值。
让 stdio server 回收内存
基于浏览器的 MCP server(比如 @playwright/mcp)首次工具调用后会留一个完整 Chromium 常驻—几百 MB 永不释放。开启自动回收,server 会在空闲/寿命限制后被拆掉,下次有工具调用时透明重启(工具全程保持注册):
mcp_servers:
playwright:
command: "npx"
args: ["-y", "@playwright/mcp@latest", "--headless"]
idle_timeout_seconds: 900 # 15 分钟无工具调用后回收
max_lifetime_seconds: 86400 # 不管怎样至少每天回收一次
并行工具调用
默认 MCP 工具顺序执行—一次一个。如果你的 MCP server 暴露的工具安全可并发(比如只读查询、独立 API 调用),可以开启并行执行:
mcp_servers:
docs:
command: "docs-server"
supports_parallel_tool_calls: true
开启后,Hermes 可能在单个工具调用批次里同时跑这个 server 的多个工具,就像对内置只读工具(web_search、read_file 等)那样。
Warning只对工具能同时安全跑的 MCP server 开启并行。如果工具读写共享状态、文件、数据库或外部资源,开启前先审视读写竞态。
MCP Sampling
MCP server 可以通过 sampling/createMessage 协议反过来请求 Hermes 做 LLM 推理。这让没有自己模型访问的 server 也能要 LLM 能力。
Sampling 对所有 MCP server 默认启用(MCP SDK 支持时)。按 server 在 sampling 键下配:
mcp_servers:
my_server:
command: "my-mcp-server"
sampling:
enabled: true # 启用 sampling(默认 true)
model: "openai/gpt-4o" # 覆盖 sampling 请求用的模型(可选)
max_tokens_cap: 4096 # 每次 sampling 响应最大 token(默认 4096)
timeout: 30 # 每次请求超时秒数(默认 30)
max_rpm: 10 # 限速:每分钟最大请求数(默认 10)
max_tool_rounds: 5 # sampling 循环里最大工具轮数(默认 5)
allowed_models: [] # server 可请求的模型名白名单(空 = 任意)
log_level: "info" # 审计日志级别:debug、info、warning(默认 info)
sampling 处理器带滑窗限速器、每请求超时、工具循环深度限制,防止失控使用。指标(请求数、错误数、用掉的 token)按 server 实例追踪。
给特定 server 关掉 sampling:
mcp_servers:
untrusted_server:
url: "https://mcp.example.com"
sampling:
enabled: false
反过来:把 Hermes 当 MCP 服务器
除了连MCP server,Hermes 也能当MCP server。这让其他支持 MCP 的 Agent(Claude Code、Cursor、Codex 或任何 MCP 客户端)能用 Hermes 的消息能力—列对话、读消息历史、跨所有已连平台发消息。
什么时候用
- 你想让 Claude Code、Cursor 或别的编码 Agent 通过 Hermes 收发 Telegram/Discord/Slack 消息
- 你想要一个 MCP server 一次性桥接到 Hermes 所有已连的消息平台
- 你已经有个跑着的 Hermes Gateway 连着各平台
快速开始
hermes mcp serve
启动一个 stdio MCP server。MCP 客户端(不是你)管进程生命周期。
MCP 客户端配置
把 Hermes 加到你的 MCP 客户端配置。比如 Claude Code 的 ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"hermes": {
"command": "hermes",
"args": ["mcp", "serve"]
}
}
}
装在特定位置时:
{
"mcpServers": {
"hermes": {
"command": "/home/user/.hermes/hermes-agent/venv/bin/hermes",
"args": ["mcp", "serve"]
}
}
}
暴露的工具
这个 MCP server 暴露 10 个工具:
| 工具 | 描述 |
|---|---|
conversations_list | 列活跃消息对话。按平台过滤或按名搜索 |
conversation_get | 按会话 key 取单个对话详情 |
messages_read | 读对话最近的消息历史 |
attachments_fetch | 从特定消息提取非文本附件(图、媒体) |
events_poll | 从游标位置轮询新对话事件 |
events_wait | 长轮询/阻塞直到下一个事件到达(近实时) |
messages_send | 通过平台(如 telegram:123456、discord:#general)发消息 |
channels_list | 列所有平台可用的消息目标 |
permissions_list_open | 列这次桥接会话期间观察到的待批准请求 |
permissions_respond | 批准或拒绝一个待批准请求 |
事件系统
MCP server 含一个实时事件桥,轮询 Hermes 的会话数据库找新消息。这让 MCP 客户端近实时感知到来的对话:
# 轮询新事件(非阻塞)
events_poll(after_cursor=0)
# 等下一个事件(阻塞到超时)
events_wait(after_cursor=42, timeout_ms=30000)
事件类型:message、approval_requested、approval_resolved。事件队列在内存里,桥连接时启动。更早的消息通过 messages_read 拿。
选项
hermes mcp serve # 正常模式
hermes mcp serve --verbose # 在 stderr 开调试日志
工作原理
MCP server 直接从 Hermes 的会话存储读对话数据(~/.hermes/sessions/sessions.json 和 SQLite 数据库)。后台线程轮询数据库找新消息,维护内存事件队列。发消息用和 cron 投递、hermes send CLI 同款的内部发送引擎(tools/send_message_tool.py)。
读操作(列对话、读历史、轮询事件)不需要 Gateway 跑着。发操作需要 Gateway 跑着,因为平台适配器要活跃连接。
当前限制
- 内置的
hermes mcp serve今天只暴露 stdio-only MCP server。要 HTTP MCP server,跑个独立适配器—或更常见,用 Hermes 的 MCP 客户端侧,它已经同时说 stdio 和 HTTP - 事件轮询约 200ms 间隔,靠 mtime 优化的 DB 轮询(文件没变就跳过活)
- 还没有
claude/channel推送通知协议 - 只发文本(
messages_send不能发媒体/附件)
安全模型
stdio env 过滤:对 stdio server,Hermes 不会盲目传你的整个 shell 环境。只传显式配置的 env 加一个安全基线。这减少意外的密钥泄露。
配置层面的暴露控制:过滤支持也是安全控制—禁用不想让模型看到的危险工具、给敏感 server 暴露最小白名单、不想要那个表面时关掉 resource/prompt 包装。
常见坑
把 LLM API key 传给 MCP server 的 env。env: { OPENAI_API_KEY: "..." } 把你的 LLM 密钥泄露给了外部进程。修:MCP server 的 env 只传它需要的(如 GITHUB_TOKEN)。Hermes 默认只传安全基础变量(PATH、HOME 等),用户显式配的才传。
不过滤工具全注册。一个 server 暴露 50 个工具全注册,模型工具列表变长、选择变难、token 消耗变大。修:用 tools.include 白名单只启用需要的。
MCP server 崩了不知道。stdio 子进程崩了,Agent 调用时才发现连接断了返回错误。Hermes 的 MCPServerTask 有自动重连—初始连接失败重试 3 次,运行中断线重试 5 次,指数退避。
MCP 工具和内置工具撞名。MCP server 提供的 read_file 和内置的 read_file 撞了。修:MCP 工具自动加 mcp_<server>_ 前缀。如果仍然撞了,内置工具优先,MCP 工具被跳过。
ssl_verify: false 用在真实服务上。这完全禁用服务器证书验证。别用在真实服务上。
改了配置不重载。改完 include/exclude、enabled、auth 头、env 之后忘了 /reload-mcp,老行为继续跑。
推荐的第一个 MCP
对大多数用户,好的第一个 server 是:filesystem、git、GitHub、fetch/文档 MCP server、一个窄的内部 API。
不好的第一个:带一堆破坏性动作又没法过滤的庞大业务系统、你不够了解没法约束的任何东西。