# @epoch-agent/plugin-mcp 中文文档

规范地址：https://npmcn.com/package/%40epoch-agent/plugin-mcp/

版本：6\.2\.0
npm 元数据更新时间：2026-11-13T08:29:26.656Z
中文译文更新时间：2026-11-13T19:50:35.133Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40epoch-agent/plugin-mcp
原文对照：https://npmcn.com/package/%40epoch-agent/plugin-mcp/?lang=original
项目主页：https://github.com/Ddbor/epoch-agent#readme
源码仓库：https://github.com/Ddbor/epoch-agent

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：132 kB，5 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.0\.0
- 运行依赖：4 个
- 首次发布：2026-08-19T10:31:54.140Z
- 最新版本发布：2026-11-13T08:29:26.430Z
- 近 30 天下载量：4583（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：57（近 12 个月 57 个）

## 中文简介

时代特派员 MCP

## 安装

```sh
npm install @epoch-agent/plugin-mcp
```

也可以使用：`pnpm add @epoch-agent/plugin-mcp`、`yarn add @epoch-agent/plugin-mcp`、`bun add @epoch-agent/plugin-mcp`

## README

# @epoch-agent/plugin-mcp

MCP (Model Context Protocol) 客户端插件。连接外部 MCP server 扩展工具集。

配置文件位置由 `@epoch-agent/infra` 的 `mcpConfigPath()` 决定 （认 `EPOCH_HOME` / `EPOCH_PROFILE`），默认 `~/.epoch/mcp.json`。

## 支持 Transport

| Transport | 配置 | 场景 |
| --- | --- | --- |
| Stdio | `command` + `args` | 本地 MCP server 进程 |
| SSE | `url` | 远程 MCP server |
| Streamable HTTP | `url` + `transport: "http"`（或 `streamable`） | 远程 HTTP 流式 |

> ⚠️ **stdio 那档要机器上有 Node。** `command` 是从 **PATH** 解析的 （SDK 用 cross-spawn、`shell: false`），而官方配置几乎都是 `npx -y @modelcontextprotocol/server-xxx` —— PATH 上没有 `npx` / `node` 就是 ENOENT， 那台 server 起不来（stderr 里那行会进日志）。 CLI 用户天然有 Node；**嵌入宿主的用户没有**，那边要宿主自己把 ship 的 node 目录 prepend 到 `process.env.PATH`，见 [EMBEDDING.md](https://npmcn.com/docs/EMBEDDING.md) 的 「用户机器上没装 Node」一节。HTTP / SSE 那两档不起子进程，不受这条影响。

`transport` 不写时按 `url` 有无推断：有 `url` 走 SSE，没有走 stdio。 **`type` 是 `transport` 的别名**（MCP 生态里多数客户端的 mcp.json 用的是 `type`）， 两个都写时听 `transport` 的。收它不是为了顺手：不收的话 `{"type":"http","url":…}` 会掉进上面那条「按 `url` 推断」的路，一个 Streamable HTTP server 被**静默连成 SSE**。

## 配置示例

`servers` 是**以 server 名为键的对象**，不是数组：

> **这份文件现在有两条写法：手写，或者 `epoch web` 的连接器页那枚「添加 server」** （2026-08-18，`POST /api/mcp`）。
> 
> 界面那条只收三样（传输类型 / 命令或 URL / 环境变量）—— 下面那几格 （`timeout` / `oauth` / `headers` …）**仍然只能手写**，所以它加完会把 「写进了哪个文件」印出来，让你接着去补。
> 
> ⚠️ 界面那条**往原文里插一段**，你的注释、键序、缩进、以及 `connect_timeout` / `type` 这类别名一个字节都不动（它不是「读进来 → 改 → 整份写回」）。 判据全文在 [runtime/src/mcp-config-write.ts](https://npmcn.com/package/runtime/src/mcp-config-write.ts)。
> 
> ⚠️ 界面那条给的 server 名**收得比这份文件严**（只许字母数字和 `_` `-`）—— 手写的不受这条管。理由同下面「插件带的名字为什么更严」：名字会原样进工具名。

```json
{
  "servers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
      "env": { "LOG_LEVEL": "warn" }
    },
    "remote": {
      "transport": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer xxx" }
    }
  }
}
```

### 字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `transport` / `type` | `stdio|sse|http` | 不写按 `url` 推断；互为别名 |
| `command` / `args` | `string` / `string[]` | stdio 必填 |
| `url` | `string` | sse / http 必填 |
| `streamable` | `boolean` | 远程用 Streamable HTTP 而非 SSE |
| `headers` | `Record<string,string>` | 远程请求头（如 auth） |
| `env` | `Record<string,string>` | 子进程环境变量 |
| `timeout` | 秒 | 单次请求超时，默认 300 |
| `connect_timeout` | 秒 | 建连（含握手）超时，默认 60 |
| `keepalive_interval` | 秒 | 心跳间隔，默认 180 |
| `idle_timeout_seconds` | 秒 | 空闲自动断开，0 表示不断 |
| `max_lifetime_seconds` | 秒 | 连接最大寿命，0 表示不限 |
| `supports_parallel` | `boolean` | 是否允许并行调用 |
| `skip_preflight` | `boolean` | 跳过启动预检 |

snake\_case 和 camelCase 两种写法都收。**未知字段会进启动诊断**（多半是拼错了）， 配置写错不再静默变成「没有 MCP」—— 每条问题都带文件名 + 字段路径。

⚠️ **`source` 不是这张表里的字段，写在 `mcp.json` 里不生效。** 那一格在 `McpServerStatus` 上（不在配置上），由**调用方**在 `connectAll(configs, source)` 那一下填死。分开是刻意的：来源要是配置的一个字段，用户就能给自己那台贴上 「宿主注入」的标 —— 同角色那条「命名空间由装配层拼，宿主自己写的不算数」。

## server 是从哪儿来的：`McpServerStatus.source`

三档：

| `source` | 谁带来的 | 名字长什么样 |
| --- | --- | --- |
| `user` | 上面那份 `~/.epoch/mcp.json` | 你自己写的那个 |
| `host` | 嵌入宿主 `buildRuntime({hostCapabilities: {mcpServers}})` | `<namespace>__<server>` |
| `plugin` | 已装插件根下那份 `mcp.json`（方案 44 PR-3） | `<插件名>__<server>` |

前两档一个字节都不落盘 / 落在插件目录里，能力页的连接器那一栏就是按这一格分组的。

⚠️ **加一档要连着改能力页那张 `MCP_GROUPS` 数组**（`web/src/capability/connectors.tsx`） —— 那是 `satisfies readonly {…}[]`，多一格**不会**编译红，而页面按组过滤后渲染， 漏改的表现是那一档一行都不显示、也没有任何报错。PR-2 实测复现过一次。

**后两档的名字都由装配层拼**，用**两个下划线**，而不是专家 / 技能那条路上的 `<namespace>:`。这不是两套风格：

-   server 名会原样变成工具名的一部分（`prefixedName()` 拼的 `mcp__<server>__<tool>`），而工具名是**真的发给 provider** 的；
-   2026-08-17 实测：带 `:` 的工具名被硬拒 —— `tools.0.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$'`， 同一发换成 `mcp__gh__search_issues` 就 200 正常回；
-   角色名 / 技能名**不上网线进 provider**，所以那边拼 `:` 一直没事。

判据全文（含「为什么这两批的名字收得比 `mcp.json` 严」、两个前缀的**来路差别** （宿主那个是 `buildRuntime` 的参数、装配时校验；插件那个是插件名、**安装时**就被 `PLUGIN_NAME_PATTERN` 卡过了）、以及为什么这一格是必填）在 [types.ts](https://npmcn.com/package/%40epoch-agent/plugin-mcp/src/types.ts) 的 `McpServerStatus.source` 上。

## 功能

-   三 transport（stdio / SSE / Streamable HTTP）
-   工具名前缀 `mcp__<server>__<tool>`，撞名交 `ToolRegistry` 处理
-   配置校验：语法错误 / 类型错误 / 缺必填字段都进诊断，逐 server 隔离
-   `tools/list` annotations + title 透传到 `EpochTool.annotations`（权限系统消费）
-   schema 缓存：内存优先，磁盘（0600）作降级；按连接配置指纹失效
-   看门狗式重连：断连**不阻塞**工具调用，重连在后台按 1s/2s/4s 退避
-   **取消得掉**：`ToolContext.signal` 一路递到 SDK 的 `RequestOptions.signal`， 用户按停时 server 当场收到 `notifications/cancelled`（见下面那一节）
-   **`timeout` 对每一条请求都算数**：`tools/call` / `tools/list` / `resources/*` / `prompts/*` 用的是同一个 `McpClient.requestOptions()`。 ⚠️ `resources/list` 这种翻页的是**逐页**给 —— SDK 的超时按请求算，只给第一页的话 第二页起又落回它那个 60 秒默认值
-   keepalive 心跳、idle 超时断开、最大生命周期
-   **OAuth**（`oauth/`）：`epoch mcp login` / `logout` 的后端，本机回调 server + token 刷新（带 skew）+ 0600 存储。认证过不去时置 `needsLogin`——重连解决不了 这个问题，只有重新登录能
-   **resources**：包成每个 server 一对工具 `mcp__<server>__list_resources` / `mcp__<server>__read_resource`
-   **prompts**：`McpRegistry.listPrompts()` / `getPrompt()`
-   **一台一台地断 / 问它此刻按的哪份配置**（2026-08-18）： `McpRegistry.disconnect(name)` / `configOf(name)`。在这之前只有 `disconnectAll()` —— 而「让**一台** server 在这个进程里消失」是装配层那条「应用 `mcp.json` 的改动」 必须做到的事（runtime 的 `applyMcpConfig`）：全断再全连会把宿主注入的和插件带来的 那两批也连带断掉，而它们一个字节都不在那份文件里。 ⚠️ `disconnect()` **连带失效 schema 缓存**（同 `reconnect()`），但**不碰工具表** —— 把 `mcp:<名字>` 那一组工具从 `ToolRegistry` 里摘掉是装配层的事

resources 和 prompts 都**先看 capability 再问**（server 没声明就别问，问了多半得到 `-32601 Method not found`），都**翻页**（`nextCursor`，上限 50 页），都把 method-not-found **当空**处理——有些 server 声明了 capability 却没实现， 「没有 resources」比「MCP 初始化失败」更贴近事实。

resources 包成工具而不是注入上下文：一个 server 挂几十份文档是常态，全量注入等于 每一轮都为「模型多半用不到的东西」付 token；而工具是按需调用的，天然懒加载。

## ⚠️ 写 MCP server 的人请处理 `notifications/cancelled`

一次 `tools/call` 有两种结束不掉的方式，**两种我们都会发那条通知**：

-   用户按停（`ToolContext.signal` → SDK 的 `RequestOptions.signal`）
-   `timeout` 到了（上面那张表里的 `timeout` 秒）

通知的形状是 JSON-RPC 规范里那一条：

```jsonc
{ "method": "notifications/cancelled", "params": { "requestId": 2, "reason": "…" } }
```

**不认它的 server 会继续把活干完**，而客户端这侧已经没人在等了。实测过一趟真跑的 代价：4 次 `invoke` → 后端 4 条「收流结束」一条不少，两份跑完的完整报告被扔掉， 外加一次白烧的后端算力。用 SDK 写的 server 自动就有这个行为；**手写 JSON-RPC 的 要自己接**（按 `requestId` 记一张在飞表，收到通知就中断那次活，且**取消之后 result 和 error 都不发**）。

⚠️ 这件事**不因为 `timeout` 配得多长而消失**：只要还有超时，「客户端放弃了而 server 还在跑」这个窗口就一直在。

## 还没做

`notifications/tools/list_changed` 收得到，但**不是热更新**：`AgentLoop` 的工具集是 构造参数，本进程内换不掉。registry 只把「工具表已经不是启动时那份了」冒泡给宿主， 真正换掉要重启。

## 明确不做的三条（**带判据，不是「以后再说」**）

-   **sampling / elicitation**（server 反过来请求 client：帮我调一次模型 / 帮我问用户 一句）。它要一个新的 `AgentEvent` 变体加一道权限闸门 —— 也就是**动 protocol**， 而那是全仓最贵的改动面。硬门槛在权限那一侧而不是协议那一侧：一个 server 能让我们 代它调模型，等于它能花我们的钱、并且把内容塞进我们的上下文，那需要先有一套 「这次反向请求要不要问用户」的语义。**要做就先定那套语义**，别从 SDK 那头往回接。
-   **MCP server 的目录 / 市场**（对标 hermes 的 `mcp_catalog.py` + `mcp_picker.py`）。 ⚠️ **别和[方案 32](https://npmcn.com/docs/verify/VERIFY_RECORD-32-runtime-plugins.md) 的插件市场 混为一谈**：32 管的是**我们自己的插件**（`~/.epoch/plugins/`），这一条管的是 「上哪儿找第三方 MCP server」。两件事，两套来源，两套信任模型。
-   **MCP server 模式**（把 epoch 自己暴露成 MCP server 给别的 agent 用）。 hermes 有 `mcp_serve.py`、codex 有整个 `codex-mcp/` crate，但那是「被集成」 而不是「集成」。更强的版本连同一条**永久红线**（真做也只暴露 `epoch_run`， 不把内置工具逐个转出去 —— 那等于让对方绕过我们的权限层）写在 [方案 28 验收记录第五章](https://npmcn.com/docs/verify/VERIFY_RECORD-28-headless-protocol.md)（那一版 `epoch mcp serve` 已经砍掉，红线连同重开的条件一起记在那儿）。

## 依赖

-   `@modelcontextprotocol/sdk`
-   `zod`（配置校验，走 infra 的 `parseLenient`）
-   [protocol](https://npmcn.com/package/protocol) 和 [infra](https://npmcn.com/package/infra) —— **四个都是 `dependencies`** （本包**没有 `peerDependencies`** —— 2026-11-03 核）

## 开发

```bash
pnpm --filter @epoch-agent/plugin-mcp test
```

`__tests__/orphan-reap.test.ts` 真起 server、真查残留，钉的是 `closeTransportAndReap()` 那三步（拍快照 → 优雅关 → 清扫快照）。**Windows 和 macOS 都跑**，改 disconnect 路径必须跑它。

⚠️ 用例里那个「启动器」中间层**必须是 shell**（Windows 生成一个 `.cmd`，POSIX 用 `/bin/sh -c`），换成 node 写的启动器用例就是空的：Windows 上 node 起的非 detached 子进程会被 libuv 的全局 Job Object 连坐收掉，父进程一死孙进程自动没，把清扫整段 删掉照样全绿。shell 也更接近真实形态——`npx -y mcp-remote …` 在 Windows 上就是被 cross-spawn 改写成 `cmd.exe /c npx.cmd …` 的。细节记在 [验收记录](https://npmcn.com/docs/verify/VERIFY_RECORD-16-process-tree.md) 的「追加二」。

## 常见问题

### @epoch-agent/plugin-mcp 是什么？

时代特派员 MCP

### 如何安装 @epoch-agent/plugin-mcp？

在项目目录运行 npm install @epoch-agent/plugin-mcp，或使用 pnpm add / yarn add / bun add。

### @epoch-agent/plugin-mcp 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @epoch-agent/plugin-mcp 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @epoch-agent/plugin-mcp 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @epoch-agent/plugin-mcp 可以商用吗？

@epoch-agent/plugin-mcp 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @epoch-agent/plugin-mcp 的下载量有多少？

根据 npm 官方下载统计，@epoch-agent/plugin-mcp 在 2026/10/14 – 2026/11/12 这 30 天内被下载 4,583 次，日均约 153 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @epoch-agent/plugin-mcp 还在维护吗？

最新版本 6\.2\.0 发布于 2026/11/13（今天）；至今共发布 57 个版本，其中过去 12 个月发布了 57 个；首次发布于 2026/08/19。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

