内容
# nats-mcp-gateway
通过 NATS 网络连接多个 MCP 服务器。原本只能在本地运行的 stdio 服务器现在可以作为网络服务;NATS 账号和主题权限成为了 MCP 的授权平面;网关注入后端凭证,确保终端用户不会持有这些凭证。
> **状态:正在积极开发中。** 这还不是一个稳定的版本。
> 数据传输(主题、帧、头部)版本为 `Mcp-Wire: 1`,但可能会不兼容地更改,
> 而且 MCP 2026-07-28 — 数据传输所携带的修订版 — 直到 2026-07-28 才最终确定。
> 请锁定提交版本,预期会有破坏性更改,并在升级前阅读 git 日志。
```
[MCP 客户端] [网关集群,队列组] [真正的 MCP 服务器]
2025-11-25 2026-07-28 在 NATS 上 2025-11-25
│ stdio │ │
▼ ▼ ▼
┌─────────┐ ═══ NATS ═══> ┌─────────┐ ── stdio 子进程 ──> github-mcp
│ 代理 │ ║ │ 网关 │ ── 流式 HTTP ───> weather
└─────────┘ ║ └─────────┘
兼容翼 ║ 兼容翼
║
║ {租户}.{用户}-作用域主题
╚═══════> ┌───────────────────────────────────┐
│ 按用户划分的容器 │
│ 网关(作用域) ── stdio ──> aws │
└───────────────────────────────────┘
```
一个二进制文件,`natsmcp`,三个子命令:
- `natsmcp gateway --config gateway.json` — 通过 NATS 连接配置的 MCP 服务器
(一个 `micro` 服务;运行副本以实现高可用,它们是队列组)。
- `natsmcp shim --server NAME` — 客户端边缘;位于 `.mcp.json`
中,桥接 MCP 客户端的 stdio 到线路。
- `natsmcp call --server NAME --method M [--params JSON]` — 调试 CLI,打印原始回复帧。
## 线路(v1)
NATS 线路**仅承载 MCP 2026-07-28**。该修订版废除了服务器发起的请求、初始化握手和协议会话,
这将传输合同减少到 *一个请求 → 零个或多个通知的流 → 一个精确的响应* —
正是 NATS 请求/回复与流式回复 inbox 的结合。无状态性使得网关副本可以在队列组中运行而无需协调。
今天的 2025-11-25 客户端和服务器在边缘进行桥接(`pkg/shim/legacyclient.go`,`pkg/backend/legacy`),
而非在总线上。
```
请求 mcp.v1.req.{租户}.{用户}.{服务器}.{方法标记}.{名称}
回复 每个请求的唯一 inbox;帧标记为 Mcp-Frame: msg|end|err|ka
控制 {回复}.ctl — 取消
```
`{用户}` 携带调用者的身份用于归属(见下文的“用户归属”);当部署不使用按用户身份验证时,它为 `_`。
帧:`msg` 携带一个 JSON-RPC 通知;`end` 携带响应(空正文 = 已取消,无响应存在);
`err` 携带网关合成的错误;`ka` 是一个 15 秒的心跳,以便将空闲流与已死亡的流区分开。
一个损坏的流被报告为错误 `-32010`,客户端重新发出请求 —
恰好是 2026-07-28 规范要求的。
## 大结果(声明检查)
每个消息都受到 NATS 服务器的 `max_payload` 限制,网关从连接握手(`nc.MaxPayload()`)中得知这一点 —
没有需要同步的大小配置。一个不能正常适配的响应会以易读的 `-32012` 失败。
启用 **声明检查** 后,网关改为将超大正文存入 JetStream 对象存储,并发送一个引用(`Mcp-Claim` 头部在空 `end` 帧上);
客户端获取、验证并删除它,然后 MCP 客户端才能看到任何内容:
```
网关:正文 > max_payload → put MCP_CLAIMS_{租户}/{随机 ID} → end + Mcp-Claim: ID
客户端: end + Mcp-Claim → 获取(SHA-256 验证)→ 删除 → 直接交付
```
双方选择加入 — 该功能**默认关闭**,需要 JetStream:
```
natsmcp gateway --config gw.json # 文件模式:添加 "claimCheck": {"maxAge": "5m"}
natsmcp gateway --config-subject … --claim-check # 获取模式
natsmcp shim --server aws --accept-claims # 客户端(也是:natsmcp call)
```
网关仅为携带 `Mcp-Accept-Claim: 1` 的请求进行声明(如果一个声明帧的空正文将被无感知客户端读取为“已取消”),
因此混合集群是安全的。一个 bucket 每个租户(`MCP_CLAIMS_{租户}`)保留 fencing 在 NATS 权限中,
与请求主题相同 — 调用者授予客户端在其自己的 bucket 上读取权限:
```
allow: ["$O.MCP_CLAIMS_acme.>"] # 对象存储主题为租户 acme
```
对象生存直到被获取(客户端主动删除)或直到 bucket TTL(`maxAge`,默认为 5 分钟)回收它们 —
在获取过程中死亡的客户端可以重新发出请求并仍然成功。`maxBytes`(默认为 1GiB)限制每个租户的 bucket;
单个对象限制在 64MiB。每个失败都降级为状态 quo:存储在 put 时间不可达 → `-32012`;获取失败 → `-32010`,
重新发出。 在一个租户中,声明保密性依赖于不可猜测的随机 ID 和回复 inbox 的隐私。成本:一个声明的结果大约多一个磁盘往返
在 JetStream 节点上(~300ms 对于 8MB)— 保持 `max_payload` 在 8MB 以便声明停留在罕见的尾部,而不是稳定的路径。
## 授权平面是 NATS
网关**不包含授权代码**。主题权限决定一切:
```
# 该用户可以列出工具并调用一个工具在一个服务器上
# (“_” 是用户令牌 — 参见用户归属)
发布:[
"mcp.v1.req.acme._.github.tools.list._",
"mcp.v1.req.acme._.github.tools.call.get_issue",
"_INBOX.>"
]
订阅:["_INBOX.>"]
```
两件事使其真实:
1. **完整性检查**(`pkg/proxy/integrity.go`):网关证明 NATS 授权的主题、镜像头部和 JSON-RPC 正文都一致,
并拒绝任何不一致的情况,返回 `-32020`。如果没有它,调用者可以将 `delete_repo` 正文发布到 `get_issue` 主题。
2. **租户令牌由 NATS 强制执行**:用户不能发布到另一个租户的主题,因此网关信任它解析的租户。
后端进程按 `(服务器, 租户, 凭证)` 池化,**从不跨租户共享**。
不是主题令牌安全的工具名称(`A-Za-z0-9_-`)映射到 `_` 令牌;这些工具的权限是全有或全无,
而完整性检查防止令牌安全名称隐藏在 `_` 下。
## 用户归属
`{用户}` 主题令牌记录了**谁**发出了每个请求。由于 `{租户}` 的相同原因,它是值得信赖的:
核心 NATS 从不告诉网关谁发布了消息,但它*确实*强制执行连接可以发布的主题。
因此,归属骑在主题上,并由 NATS 验证,而网关中没有加密。
预期的设置是 **NATS 认证调用**:您的调用服务认证调用者并生成一个 NATS 用户 JWT,
限定在 `mcp.v1.req.{租户}.{用户}.>`。调用者然后不能以其他用户的 `{用户}` 令牌发布,
而网关信任它解析的令牌 — 在每个请求上记录它,并且因为作用域是按用户的,
所以您可以免费编写按用户的工具权限:
```
# 调用者为用户 u_9f3a 在租户 acme 中生成:
发布:[
"mcp.v1.req.acme.u_9f3a.>", # 以此用户身份执行所有操作,或更细粒度:
"mcp.v1.req.acme.u_9f3a.github.tools.call.get_issue",
"_INBOX_acme_u_9f3a.>"
]
```
客户端通过 `--user` / `NATSMCP_USER`(代理,调用)传递其用户令牌。用户 ID 必须是主题令牌安全(`A-Za-z0-9_-`),
因此将电子邮件等映射到调用者的稳定句柄或 UUID。没有按用户身份验证的部署使用 `_` 占位符(“无归属”),
而网关记录 `user=_`。
当凭证是按用户身份提供时,归属是隔离的:具有静态服务器时,池键保持 `(服务器, 租户)`,
而进程按租户共享,无论用户;具有按用户身份 `auth` 模式(如下)的服务器将其后端按 `(服务器, 租户, 用户, 凭证生成)` 键控,
因此用户从不共享进程或凭证。
## 配置
```json
{
"nats": { "url": "nats://gw:pw@nats:4222", "credsFile": "" },
"servers": {
"github": {
"protocol": "2025-11-25",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
},
"weather": {
"protocol": "2026-07-28",
"transport": "http",
"url": "https://weather.internal/mcp",
"headers": { "Authorization": "Bearer ${WEATHER_TOKEN}" }
}
}
}
```
`protocol` 默认值为 `2025-11-25`(即现存的协议)。
`${VAR}` 从网关的环境中扩展 — 凭证注入点;客户端从不看到后端机密。
在 NATS 服务器上设置 `max_payload: 8MB`:MCP 结果携带 base64 块,
而超大消息以易读的 `-32012` 失败,而不是挂起。
### 缓存提示
2026-07-28 需要 `ttlMs` 和 `cacheScope` 在每个可缓存的结果上
(`server/discover`,`*/list` 方法,`resources/read`)。对于遗留后端,
网关提供两者,因为 2025-11-25 服务器不能:
```json
"github": { "discoverTtlMs": 300000, "cacheScope": "private" }
```
`cacheScope` 默认值为 **`private`**,您通常应该将其保留在此处。
后端按 `(服务器, 租户[, 用户, 凭证生成])` 池化,而凭证按调用者注入,
因此结果通常不适合共享缓存传递给不同的授权上下文 — 这正是 `public` 许可的内容。
仅当服务器的列表对于每个调用者都完全相同且有证明时,才设置 `public`。
未识别的值在配置加载时被拒绝,而不是被默认,以便 typo 不能悄悄扩大其范围。
### 工具参数镜像到头部
后端可能使用 `x-mcp-header` 注释工具参数,要求其值也作为 `Mcp-Param-{Name}` 头部传输。
网关遵循此操作,用于 HTTP 后端,而不放弃其模式盲性:
它从通过的 `tools/list` 响应中了解注释,如果 `tools/call` 以 `-32020` 拒绝,
在它看到一个之前,它会重新读取模式并重试调用一次 —
规范为此特定情况定义了恢复。注释无效的工具从 `tools/list` 中被删除,并带有警告,
因此一个格式错误的定义不能使服务器失去整个工具集。
## 按用户身份的后端凭证
默认情况下,每个服务器都运行在来自其配置 `env`/`headers` 的**一个共享凭证**。
对于精选的第三方服务器(AWS,Grafana,GitHub ...),调用者不应共享身份:
一个服务器的 `auth` 块选择如何**按(租户,用户,服务器)解析凭证**并注入 —
作为 HTTP 请求的 `headers` 或作为生成的子进程的 `env`,合并在配置的静态值之上(配置保留诸如 `AWS_REGION` 之类的非机密信息;
解析的凭证在冲突时获胜)。终端用户从未看到凭证,而按用户身份的模式将机密完全排除在配置之外。
```json
"grafana": { "transport": "http", "url": "https://grafana.internal/mcp",
"auth": { "mode": "nats" } },
"jira": { "transport": "http", "url": "https://jira.internal/mcp",
"auth": { "mode": "oauth-token-exchange",
"tokenUrl": "https://idp/token", "audience": "jira",
"clientId": "${OAUTH_CLIENT_ID}", "clientSecret": "${OAUTH_CLIENT_SECRET}",
"subjectTokenFile": "/var/run/assertions/{user}.jwt" } },
"aws": { "transport": "stdio", "command": "uvx", "args": ["awslabs.aws-api-mcp-server"],
"auth": { "mode": "exec", "command": "aws-cred-helper" } }
```
| 模式 | 粒度 | 通过以下方式解析 |
|---|---|---|
| `static`(默认) | 共享 | 配置 `env`/`headers` — 今天的行为 |
| `file` | 共享,或按用户身份(如果路径包含 `{user}`) | 挂载/旋转文件(`path`;可选 `ttl`,默认为 1 分钟,如果文件带有 `expiresAt`) |
| `oauth-client-credentials` | 共享 | RFC 6749 client_credentials(`tokenUrl`, `clientId`, `clientSecret`; 可选 `scope`, `audience`) |
| `exec` | 按用户身份 | 凭证助手命令(`command`; 可选 `args`, `env` 传通)— 通用适配器,以下 |
| `oauth-token-exchange` | 按用户身份 | RFC 8693(`tokenUrl`, `subjectTokenFile`; 可选 `clientId`, `clientSecret`, `scope`, `audience`, `subjectTokenType` — 默认 `urn:ietf:params:oauth:token-type:access_token`) |
| `oauth-refresh` | 按用户身份 | 刷新令牌授权(`tokenUrl`, `clientId`, `refreshTokenFile`; 旋转的刷新令牌被写回) |
| `nats` | 按用户身份 | 请求/回复到控制器(如下;主题前缀默认为 `{subjectPrefix}.cred`,而 `subject` 重载它) |
`file-path` 字段(`path`, `subjectTokenFile`, `refreshTokenFile`)接受
`{tenant}`, `{user}`, `{server}` 占位符,而 `file` 读取相同的
`{"headers"|"env": {...}, "expiresAt": "RFC3339"}` JSON,`exec` 助手打印。
按用户身份的服务器按 `(服务器, 租户, 用户, 凭证生成)` 池化:
一个服务器的两个用户获得单独的进程,具有他们自己的凭证,一个刷新的凭证排干旧后端,
而池在凭证过期之前回收后端(在过期时进行的中断调用失败,返回 `-32010`,而客户端重新发出)—
过期仅当解析器实际返回 `env`/`headers` 时才绑定到进程,因此带有空回复的回复不产生新的进程。
HTTP 服务器在每个请求上获得当前的承载令牌,因此在生命周期中途刷新不需要重新连接;
在 `401` 时,网关丢弃缓存的凭证,重新解析并重试一次 —
对任何方法都安全,因为 `401` 在服务器执行请求之前拒绝请求。凭证解析失败表现为 **`-32014`**;
消息指示是否重试(解析器不可达)或不重试(源拒绝),而失败被记住,具有短暂的退避(1 秒加倍至 30 秒),
因此解析器中断降级为快速,易读的错误,而不是按请求速率敲打源。注意 `pool.maxProcsPerTenant`
(默认为 16):按用户身份的 stdio 服务器将每个 `(用户, 服务器)` 进程计入其中,
因此调整大小以大致等于用户 × 每个租户的 stdio 服务器。一个租户范围的部署通过一个池连接整个组织,
因此它是可能需要提高的最可能的结构。池限制是启动时固定的:文件和内联源从文档的 `pool` 块读取它们,
而获取源 — 其配置在池构建后到达 — 从 `--pool-max-procs-per-tenant`, `--pool-max-concurrent`, `--pool-idle-ttl` 和 `--pool-max-lifetime`(或它们的 `NATSMCP_POOL_*` 环境变量)中获取。
添加凭证源与配置重载的优先级相同(`pkg/backend/cred`,最便宜的第一):
1. **使用内置的** — 上述模式,包括 RFC 8693。
2. **用 `exec` 包装脚本** — 助手在清理环境中运行,并带有 `NATSMCP_CRED_{TENANT,USER,SERVER}`,并打印
`{"headers"|"env": {...}, "expiresAt": "RFC3339"}`。任何凭证系统都集成在 ~20 行中;例如,在网关中没有 AWS SDK 的 AWS STS:
```sh
#!/bin/sh
aws sts assume-role-with-web-identity \
--role-arn "arn:aws:iam::123456789:role/mcp-${NATSMCP_CRED_USER}" \
--role-session-name "$NATSMCP_CRED_USER" \
--web-identity-token "$(cat /var/run/assertions/$NATSMCP_CRED_USER.jwt)" \
--query 'Credentials' --output json |
jq '{env: {AWS_ACCESS_KEY_ID: .AccessKeyId, AWS_SECRET_ACCESS_KEY: .SecretAccessKey,
AWS_SESSION_TOKEN: .SessionToken}, expiresAt: .Expiration}'
```
3. **实现 `cred.Resolver`** — 一个方法;将其包装在 `cred.Cached` 中,用于 TTL 缓存、单航班和每个模式获得的故障退避。
## 本地和远程服务器
网关以两种形式运行MCP服务器;MCP服务器本身是普通的,不感知网关:
- **本地(进程内):** 上述形式的网关创建的标准输入/输出子进程,网关调用的HTTP端点。适用于单二进制部署和托管的HTTP服务器,它们将用户凭证作为请求头。
- **远程限定实例:** 标准输入/输出服务器无法联网,因此要在**外部**运行一个与中心网关(其自己的Pod,具有自己的资源)相同的二进制文件,作为容器入口点,**限定**到主题空间的一部分。`nats.tenant`/`nats.user`配置字段(文件源)或`--scope-tenant`/`--scope-user` / 它们的env(获取和内联源)选择三个范围之一:
| 范围 | 绑定 | 服务 | 用例 |
|-------|-------|--------|-----|
| *未设置* | `mcp.v1.req.*.*.{server}.>` | 所有调用者 | 中央舰队 |
| 仅限租户 | `mcp.v1.req.{tenant}.*.{server}.>` | 某个租户的所有用户 | 按组织部署 |
| 租户 + 用户 | `mcp.v1.req.{tenant}.{user}.{server}.>` | 一个调用者 | 按用户Pod |
设置`user`而没有`tenant`是无效的 — 用户令牌跨越每个租户。下面的示例是按用户Pod:
```json
{ "nats": { "url": "nats://nats:4222", "tenant": "acme", "user": "u_9f3a" },
"servers": { "aws": { "transport": "stdio", "command": "uvx",
"args": ["awslabs.aws-api-mcp-server"],
"env": { "AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}",
"AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}",
"AWS_SESSION_TOKEN": "${AWS_SESSION_TOKEN}" } } } }
```
它绑定`mcp.v1.req.acme.u_9f3a.{server}.>`,因此NATS将该用户的流量路由到Pod — 没有入口的Service、DNS或ingress; Pod只建立连接。创建Pod的任何内容都将用户凭证注入其环境中,并且Pod的生命周期绑定到凭证的生命周期。删除`user`字段使其成为按组织部署的实例,为整个租户提供相同的服务器和一组共享凭证。
对于任何限定实例,都有三条规则:
1. 给定的服务器名称必须由**要么**中央网关**或**限定实例提供服务,而不能同时提供服务 — 它们将竞争重叠的主题。因此,限定实例默认为其自己的队列组,`mcpgw.{tenant}`或`mcpgw.{tenant}.{user}`。
2. 出于同样的原因,**不要在同一服务器名称上运行租户限定实例和按用户实例**。 `{tenant}.*`和`{tenant}.{user}`重叠,由于它们的默认队列组不同,NATS将该用户的请求传递给两者 — 调用在两个后端执行两次,只保留第一个回复。每个服务器名称选择一个粒度。
3. Pod的NATS身份应该限制在其自己的主题 — **包括其收件箱**。限定实例应该设置`--inbox-prefix` / `NATSMCP_INBOX_PREFIX`(文件源:`nats.inboxPrefix`),这会将进程发布的所有请求/回复 — 配置获取、`nats`凭证解析器、JetStream — 移到该前缀下:
```
# 而不是订阅:["_INBOX.>"] — 整个帐户的回复
publish: ["mcp.v1.req.acme.u_9f3a.>", "mcp.v1.cfg.request", "mcp.v1.cred.acme.u_9f3a.>"]
subscribe: ["_INBOX_acme_u_9f3a.>"]
```
如果没有它,身份需要 `_INBOX.>` 才能接收自己的回复,这授予了整个帐户范围的回复命名空间。对于运行**第三方MCP服务器代码**的按组织Pod尤其如此:一个子进程可以提升连接凭证,可以订阅并读取其他租户的凭证响应。值必须是字面主题前缀 — 没有空格,没有 `*`/`>`,没有尾随点 — 否则在启动时将被拒绝。
上述文档是文件源表单。没有文件挂载的Pod会携带内联表单 — `NATSMCP_CONFIG_JSON` 保存一个纯粹的 `{"servers":{…}}` 文档(其中不允许`nats`块,因此范围永远不会被静默删除),而作用域和带有令牌的NATS URL作为环境变量到达(参见[配置源](#配置源--热重载))。
## Docker 镜像
发布版本会发布多架构(amd64/arm64)镜像:一个~20MB静态二进制文件位于 `gcr.io/distroless/static-debian12:nonroot` — 没有shell,没有包管理器,CA证书和可写的`/tmp`(stdio后端的workdir)。`make image` 构建一个本地单架构 `natsmcp:develop`。
```
docker run --rm -v ./gateway.json:/etc/natsmcp.json:ro \
ghcr.io/code-cargo/natsmcp:latest gateway --config /etc/natsmcp.json
```
同一个镜像适用于中央网关、按用户Pod的入口点和shim/call CLI。精选的MCP服务器镜像(需要自己的Python/Node运行时)应该**复制二进制文件**而不是从中构建 `FROM`:
```dockerfile
COPY --from=ghcr.io/code-cargo/natsmcp:v1.2.3 /usr/local/bin/natsmcp /usr/local/bin/natsmcp
ENTRYPOINT ["/usr/local/bin/natsmcp", "gateway", "--config", "/etc/natsmcp.json"]
```
然后网关升级是每个精选镜像的标签更新,镜像的出处按照发布进行证明
(`gh attestation verify oci://ghcr.io/code-cargo/natsmcp:vX --repo code-cargo/nats-mcp-gateway`)。
## 配置源 / 热重载
**服务器集在运行时重新加载** — 添加、删除或重新认证前端的MCP服务器,而无需重启网关。(NATS连接、主题前缀和队列组在启动时固定;只有服务器重新加载。)重新加载是安全的:格式错误的修订版被拒绝,最后一个好的配置继续提供服务,删除的服务器停止响应(客户端得到 `-32011` 并重新发出请求),更改的服务器的池进程被驱逐,以便下一个调用从新定义中生成。正在删除/更改服务器上的未完成调用会失败并可重试(`-32010`);未更改的服务器永远不会受到干扰。
配置来自一个**源**,由标志选择:
```
natsmcp gateway --config /etc/natsmcp.json # 文件;SIGHUP 或轮询重载
natsmcp gateway --config-subject mcp.v1.cfg.request # 通过 NATS 请求/回复获取
NATSMCP_CONFIG_JSON='{"servers":{…}}' natsmcp gateway # 内联;进程生命周期固定
```
- **文件** (`--config`, `--reload-interval` 默认 10s):在更改和 `SIGHUP` 时重新读取。适用于演示和简单部署。在 K8s 上,ConfigMap 挂载会原子更新,因此轮询会捕捉到它。
- **NATS 获取** (`--config-subject`, `--config-events-subject`, `--config-refetch`):通过 NATS 请求配置 JSON,并在更改事件发布时重新获取(带有周期性重新获取作为遗漏事件的安全网)。机密存储在控制器中,仅通过身份验证的 NATS 连接传递 — ConfigMap 或 KV 存储桶中没有静态存储。**控制器合约:** 用相同的配置 JSON 响应请求主题,并在更改时发布任意消息到事件主题;NATS 权限将两个主题限制在控制器范围内。
- **内联** (`--config-json` / `NATSMCP_CONFIG_JSON`):整个配置文档作为字符串应用,一次且不重新加载 — 适用于没有文件挂载和配置响应器的 Pod(作用域 stdio 部署通过此方式注入其服务器)。连接和作用域来自获取源标志/env,而不是文档(它是纯文本,因此 NATS URL — 而不是配置 — 携带凭证);配置更改是新的 Pod,而不是热重载。
注意:只有 **文件** 源从文档的 `nats` 块中读取连接设置(URL、前缀、队列组、收件箱前缀、作用域)。获取和内联源从标志/env中获取这些设置 — 文档仅提供服务器集。
`--inbox-prefix` / `NATSMCP_INBOX_PREFIX`(文件源:`nats.inboxPrefix`)设置此进程的请求/回复收件箱前缀 — 配置获取、`nats`凭证解析器和JetStream都从连接派生它们的回复主题,因此一个设置涵盖了它们。作用域实例**应该**设置它:没有它,网关的回复落在 `_INBOX.>` 下,它的NATS身份必须授予整个命名空间(参见[本地和远程服务器](#本地和远程服务器))。
### 从其他内容重新加载
重载机制是一个小型构建块,而不是一对固定的模式。整个扩展表面是 `pkg/configsource` 中的一个接口:
```go
type Source interface { Watch(ctx context.Context) <-chan Update }
```
从新的后端热重载的三种方式,按从廉价到贵的顺序排列:
1. **使用内置** — `configsource.NewFile(...)` 或 `&configsource.NATS{...}`。
2. **包装获取函数** 与 `configsource.Poll` — 用于 HTTP、Vault、KV 存储、S3、数据库。您编写一个获取;变更检测、初始加载和生命周期免费:
```go
src := configsource.Poll(30*time.Second, func(ctx context.Context) (*config.Config, error) {
raw, err := fetchFromVault(ctx)
if err != nil { return nil, err }
return config.Parse(raw)
})
```
3. **实现 `Source`** — 用于推送系统(Kubernetes informer、webhook)已经知道配置何时更改。将其包装在 `configsource.Dedup` 中以抑制无操作排放。
`pkg/reconcile.Reconciler` 是与源无关的引擎,它将配置应用于正在运行的线路 + 池;`configsource.Run` 是消费循环,保持最后一个良好的配置在错误时生效。嵌入者可以驱动 `Reconciler.Apply` 从其自己的控制循环中,并完全跳过 `Source`。
客户端(`.mcp.json):
```json
{
"mcpServers": {
"github": {
"command": "natsmcp",
"args": ["shim", "--server", "github"],
"env": { "NATSMCP_NATS_URL": "nats://...", "NATSMCP_TENANT": "acme" }
}
}
}
```
## 演示
`make demo` (需要 `nats-server` 和 `npx` 在PATH上)遍历整个故事:工具/列表和通过真正的 `server-everything` 子进程的工具调用,然后是对限制权限的用户调用失败 **且没有网关代码运行**。在 `demo/run.sh` 中查看 `demo/README-steps`。
## 已知 v1 限制
- 采样、诱导和根节点未桥接:网关未向旧后端宣传这些功能,因此符合规范的服务器不会使用它们(自 2026-07-28 起已弃用)。服务器发起的请求会收到 `-32601` 而不是阻塞子进程。
- `notifications/message` (日志)在多路复用进程中没有每个请求的关联性;它会进入网关自己的日志中,并使用租户和服务器标记,从不转发。
- 除非启用声明检查(参见“大结果”),否则有效载荷受 NATS `max_payload` 的限制,并且单个结果上限为 64MiB。请求方向的正文(大上传)始终受 `max_payload` 的限制 — 声明检查仅涵盖响应。
- `resourceSubscriptions` 在 `subscriptions/listen` 内未桥接到旧后端(`*/list_changed` 事件不是)。订阅确认仅报告已遵守的内容,因此请求的客户端会立即收到通知,而不是等待永远不会到来的更新。
- `x-mcp-header` 仅适用于 **HTTP后端** — 规范使其成为 Streamable HTTP 的客户端要求,并允许其他传输忽略它,而 stdio 子进程没有要映射的标头。
- 过期后端的凭证会在下次使用时被回收,回收器也会回收,但前提是它们处于空闲状态:一个长寿命的未完成调用(`subscriptions/listen` 流)可以将其进程锁定在其凭证过期之前,直到它完成。
## 开发
`make ci` = 格式检查 + staticcheck + 构建 + 测试。测试在任何 Go 支持的环境中运行(嵌入式 NATS 服务器;假的 MCP 服务器是重新执行的测试二进制文件)。`go test ./... -race` 是干净的。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器