English
← 返回 Agent Rule

用 MCP(模型上下文协议)为 Hermes Agent 接入外部工具 ✓ 已验证

2026-08-16 · 16 分钟 · MCP · Hermes · Docker · sun-port · Git

一个 Agent 的能力上限,取决于它的工具。你可以为每一个集成手写一个专用工具——也可以采用 MCP,即模型上下文协议,这个开放标准让一个 Agent 只需一行配置,就能与成千上万个现成的工具服务器对话。Hermes 内置原生 MCP 客户端:往配置里加一个服务器、重启,它的工具就会和 terminalread_file 一起,作为一等可调用工具出现——无需桥接 CLI,也无需胶水代码。本教程接入 GitHub文件系统和一个自定义 HTTP 服务器,把它们跑在 Docker 里,用 sun-port 前置,并用 Git 为整套东西做版本管理——外加那些阻止不受信任服务器泄漏密钥的安全控制。

本文中的每一条命令都在本站所记录的 Hermes 真实部署上运行过——MCP 服务器以子进程方式启动、配置提交到 Git、HTTP 服务器经 sun-port 暴露。✓ 已验证 徽章意味着真实的执行,而非从 README 里复制粘贴。

1. MCP 是什么(以及不是什么)

MCP 把 AI 应用与它所能调用的工具之间的连接方式标准化。在 MCP 之前,每一个集成都是定制的一次性工作:你得给 GitHub API 写一个包装器,给数据库再写一个,给内部服务又写一个——每个都带着各自的认证、各自的错误处理、各自的提示词描述。MCP 用一个 JSON-RPC 协议和两种传输方式,取代了所有这一切。

MCP 不是模型,不是向量数据库,也不是对你 Agent 自身逻辑的替代。它是管道——可靠、乏味、标准化的管道——而这恰恰是它有用的原因:一个客户端就能与每一个合规的服务器对话。

2. Hermes 的原生 MCP 客户端

Hermes 把客户端内置其中。启动时,它从 ~/.hermes/config.yaml 读取 mcp_servers 配置块,在一个专属的后台事件循环里连接每个服务器,调用 list_tools() 发现能力,并把每个工具注册到共享工具注册表中。从那以后,这些工具在每一段对话里都可用——无需逐会话设置,也无需热重载(新增服务器仍然需要重启)。

两种传输方式对应两种配置形态:

# stdio 传输——Hermes 把服务器作为子进程启动
mcp_servers:
  filesystem:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]

# HTTP 传输——通过网络连接远程或共享服务器
mcp_servers:
  company_api:
    url: "https://mcp.mycompany.com/v1/mcp"
    headers:
      Authorization: "Bearer sk-..."

服务器配置要么携带 command(stdio),要么携带 url(HTTP),绝不会两者兼有。每个选项都有合理的默认值:timeout 是每次工具调用 120s,connect_timeout 是初次握手 60s。

3. 前置条件

$ pip install mcp          # MCP SDK——没有它,MCP 支持会被静默禁用
$ node --version          # npx 类服务器需要它
v20.11.0
$ uv --version            # uvx 类(Python)服务器需要它
uv 0.4.0

mcp 这个 Python 包是一个可选依赖——如果缺失,Hermes 会带着一条警告跳过 MCP 发现,所以先装上它,再留意启动日志,确认客户端确实生效了。

4. 快速上手——一个时间服务器

最小可能的胜利:一个报时间的 stdio 服务器。

# ~/.hermes/config.yaml
mcp_servers:
  time:
    command: "uvx"
    args: ["mcp-server-time"]

重启 Hermes。启动时它会连接服务器、发现 get_current_time,并把它注册为 mcp_time_get_current_time。命名约定是可预测的:mcp_{server}_{tool},连字符和点号会被替换成下划线。这种可预测性很重要——当两个服务器暴露同名工具时,你正是靠它来判断哪个服务器拥有哪个工具。

5. 一个真实工具——经 stdio 接入 GitHub

GitHub 服务器把整个 gh 风格的操作面——issue、PR、release——变成 Agent 可调用的工具:

# ~/.hermes/config.yaml
mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xx...xxxx"
    timeout: 60

现在 Agent 可以按名称列出 issue、创建 PR、审查 diff:

$ # 在 Hermes 会话里,Agent 可以调用:
mcp_github_list_issues(owner="im-sun", repo="agent-rule")
mcp_github_create_pull_request(owner="im-sun", repo="agent-rule", title="...")
为什么用 env 而不是你的 shell?Hermes 刻意把完整环境变量传给 MCP 子进程。只有一份安全的允许列表会被继承——PATHHOMEUSERLANGLC_ALLTERMSHELLTMPDIR,以及所有 XDG_* 变量。其他的一切——API 密钥、令牌、秘密——都会被排除,除非你在 env 里显式点名。这正是 env 键的全部意义:上面那个 GitHub 令牌,是该子进程唯一能看到的凭据。

6. 文件系统与其他 stdio 服务器

文件系统服务器是本地自动化的主力——在你显式圈定的目录里读、写、列出文件:

# ~/.hermes/config.yaml
mcp_servers:
  filesystem:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/documents"]
    timeout: 30

作用域被烤进了 args 里——这个服务器只能碰 /home/user/documents,所以即便是一个被提示词注入的 Agent,也没法溜进 ~/.ssh。这是一个反复出现的主题:当你给 MCP 服务器一个最窄的、刚好够用的根目录或作用域时,它才最安全——这正是我们密钥管理教程里的最小权限纪律。

7. 在 Docker 里运行 MCP 服务器

npxuvx 启动的 stdio 服务器会在启动时下载包,这在生产环境里很脆弱——一个缺失的 npm 源、一次版本升级、一个坏掉的缓存,都会让工具下线。把服务器跑在 Docker 里,就能把运行时、版本和依赖钉死在一个不可变镜像上:

# 自定义 MCP 服务器的 Dockerfile
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
CMD ["node", "src/server.js"]

但 Hermes 的 stdio 传输启动的是一个命令,而不是一个容器。干净的做法是改经 HTTP 传输暴露容器化的服务器,然后让容器自身的生命周期(Docker 重启策略、健康检查)来管理可用性:

# docker-compose.yml
services:
  mcp-github:
    build: ./mcp-github
    restart: unless-stopped
    environment:
      GITHUB_PERSONAL_ACCESS_TOKEN_FILE: /run/secrets/gh_token
    secrets:
      - gh_token
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
    ports:
      - "127.0.0.1:8080:8080"    # 仅回环——见第 9 节

secrets:
  gh_token:
    file: ./secrets/gh_token.txt

秘密通过 Compose secrets 以文件形式送达(绝不会出现在 docker inspect 里),端口绑定到 127.0.0.1,这样公网上没有任何东西能直接访问它。在 Hermes 这一侧,把客户端指向它:

# ~/.hermes/config.yaml
mcp_servers:
  github:
    url: "http://127.0.0.1:8080/mcp"
    timeout: 180
    connect_timeout: 30

8. 经 sun-port 暴露 HTTP MCP 服务器

仅回环对单台主机够用,但一个多 Agent 集群——或其他机器需要的共享 MCP 服务器——就得跨网络。这正是 sun-port 的用武之地,它是我们在反向代理教程里介绍过的、基于 Pingora 的反向代理。它在边缘终结 TLS,并经回环转发到容器,于是 MCP 服务器保持不暴露,而客户端能通过 HTTPS 访问它:

# sun-port 路由:公网 HTTPS -> 内部 MCP 服务器
upstream mcp_github {
  server 127.0.0.1:8080;
}

server {
  listen 443 ssl;
  server_name mcp.agent-rule.com;

  ssl_certificate     /etc/letsencrypt/live/mcp.agent-rule.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mcp.agent-rule.com/privkey.pem;

  location /mcp {
    proxy_pass http://mcp_github;
    proxy_set_header Authorization $http_authorization;
  }
}

然后,Hermes 客户端(在任意能访问 mcp.agent-rule.com 的主机上)通过 TLS 连接:

mcp_servers:
  github:
    url: "https://mcp.agent-rule.com/mcp"
    headers:
      Authorization: "Bearer sk-..."
如果你安装的 mcp 版本不提供 HTTP 客户端传输,该服务器会以一个指明 mcp.client.streamable_httpImportError 失败——而其他服务器仍能继续工作。用 pip install --upgrade mcp 修复。别让一个坏掉的服务器掩盖其他服务器:去启动日志里查看每个服务器的状态,而不是默认这是一次无声的全局失败。

9. 安全——Hermes 为你做了什么

MCP 服务器是拿着你的凭据运行的第三方代码,所以 Hermes 叠加了三层内置防护,否则这些都得你自己手写:

# 为一个不受信任的服务器禁用采样
mcp_servers:
  third_party:
    command: "npx"
    args: ["-y", "some-untrusted-server"]
    sampling:
      enabled: false

10. 工具命名、冲突与生命周期

每个 MCP 工具都以 mcp_{server}_{tool} 的名称注册,所以两个服务器可以各自暴露一个 read_file 而不冲突——它们会变成 mcp_filesystem_read_filemcp_github_read_file。有三个生命周期事实值得牢记:

11. 故障排查

# "MCP SDK 不可用——跳过 MCP 工具发现"
$ pip install mcp

# "无法连接到 MCP 服务器 'X'"
#  - 命令不在 PATH 上(安装 npx / uvx)
#  - npx 包需要在 args 里加 -y 才能自动安装
#  - 服务器启动太慢 -> 调高 connect_timeout

# "MCP 服务器 'X' 需要 HTTP 传输,但 streamable_http 不可用"
$ pip install --upgrade mcp

# 工具不出现:检查配置键是 mcp_servers(不是 mcp 或 servers),
# 确认 YAML 缩进,并在启动日志里 grep 工具前缀

12. 常见坑

13. 核心要点

MCP 的价值不在于任何一个单独的工具——而在于,下一个工具的成本是一行配置,而不是一个周末。加一个服务器、重启,你的 Agent 就获得了一项能力,它拥有与它此前一切能力相同的信任边界、相同的命名方案和相同的安全姿态。对于一个职责是编排 im-bot 房间、Git 仓库、PostgreSQL 后端和 Docker 工作负载的 Agent 来说,这正是「一件工具」与「一整套工具箱」之间的差别。