MCP Gateway
MCP Gateway — 阶段 1(KLIP,只读)
一个单一的安全服务,让经授权的 Energi-Up 员工通过 Claude 以自然语言查询 KLIP, 而无需打开应用程序。严格只读。
部署目标(PRD Q3,现已关闭):<gateway-hostname> -> <gateway-public-ip>
(ECS-MCP,ap-southeast-5)。请注意主机名是 mcp-gw,而不是 v0.9 文档所假设的
mcp.example.com;PRD/TSD 应相应更新。
文档: PRD v0.9 · TSD v0.9 · 实施指南 · 设计评审 · 部署手册
1. 固定版本(T-1)
MCP 规范修订版和 SDK 版本在此固定。当 SDK 自己的文档与设计文档不一致时,以 SDK 为准——它定义了确切的 API。
组件 | 固定版本 | 备注 |
| 1.30.0(精确版本,无脱字符) | 发布于 2026 年 7 月 27 日。提供 OAuth AS、Streamable HTTP 传输和 |
MCP 协议修订版 | 2025-11-25 已实现;线格式与 | 修订版 |
Node.js | 22 LTS ( | |
TypeScript | 5.9.3, | |
express | 5.2.1 | 从 TSD 的 4.x 提升而来:SDK 依赖 |
zod | 4.4.3 | 从 TSD 的 3.x 提升而来:SDK 的类型定义面向 zod 4。注意 |
jose | 6.2.9 | RS256 网关令牌。 |
| 2.1.0 | 替代 |
axios | 1.19.0 | KLIP 客户端,位于方法守卫之后。 |
pg | 8.23.0 | |
PostgreSQL | 16 (container) | |
nginx | nginx.org mainline | 配置位于 |
Related MCP server: Snowflake MCP Server
2. 布局
src/
core/ config, logger, db, audit, cache, rateLimit, semaphore, migrate, errors
adapters/klip/ routes(APPENDIX A) · fields(APPENDIX A) · client(guard) · session · paginate · normalize
tools/klip/ 9 tool definitions + shared parameter plumbing
mcp/ server, envelope, runner
auth/ keys, hub(OIDC RP), users, clients, tokens, provider, loginPage
http/ app, consent(Hub + break-glass), health, origin, clientIp
migrations/ idempotent SQL (001 schema, 002 Hub OIDC)
deploy/ nginx config, backup sidecar
test/ 120 tests + mock KLIP and mock Hub fixtures分层规则(T-3):tools → adapters → core。除入口点外,没有任何模块导入 http/。所有业务规范化都位于 adapters/klip/normalize.ts 中,该文件不导入任何内容,因此可以独立进行单元测试。
3. 身份验证 — Downstream Hub OIDC
试点用户使用 Downstream Hub(OIDC)登录。网关仍然是 Claude 与之通信的授权服务器;Hub 是其自身 /authorize 流程中的一个步骤。
我们特意不使用 SDK 的 ProxyOAuthServerProvider。代理会把 Hub 令牌交给 Claude,这会破坏 RFC 8707 的受众绑定,让 Claude 获得比 klip:read 更广泛的 Hub 作用域,并将令牌签发移出我们的控制,从而使 S8 紧急停止开关无法再使活动会话失效。
Claude ──/authorize──▶ gateway ──302──▶ Downstream Hub ──302──▶ /authorize/hub/callback
│ │
│ validate id_token (sig/iss/aud/nonce) │
│ check the pilot ALLOWLIST │
◀──────────────────────────────────────────────┘
└──302 code──▶ Claude ──/token──▶ gateway token (klip:read)身份验证不是授权。 Hub 证明某人的身份;users 表决定他们是否可以使用连接器。阶段 1 使用一个共享的 KLIP 服务账户,因此每个被接纳的用户都可以读取 MCP_READONLY 能读取的所有内容(评审 H8)——试点成员资格就是数据访问控制。不在列表中的 Hub 账户会收到 403,<= 15 的上限由 user:add 强制执行。
该流程中运行两次 PKCE 交换;不要混淆它们。Claude 自己的 code_challenge 保护 Claude→网关这一段(由 SDK 处理);网关在服务端保存的单独验证器保护网关→Hub 这一段。
使用哪个 Hub 客户端?网关自己的——而不是 KLIP 的
为 MCP Gateway 注册一个新的 OIDC 客户端。不要复用 KLIP 的 Hub 注册,即使 KLIP 已经在测试 DWS Hub 中注册。
Hub 只涉及两个信任边界之一:
信任边界 | 凭据 | 是否涉及 Hub? |
Claude → gateway(谁在提问) | 网关自己的 Hub 客户端 + 试点允许列表 | 是 |
gateway → KLIP(读取数据) |
| 否——PRD §7 明确排除 |
复用 KLIP 的客户端会以具体方式破坏第一个边界。网关根据其自身的 HUB_CLIENT_ID 验证 ID 令牌的 aud;共享 KLIP 的客户端 ID 意味着在 KLIP 登录期间签发的 ID 令牌会被网关接受,这正是 confused-deputy 的形态。独立的客户端才能使两个依赖方可区分。这还使重定向 URI 允许列表、客户端密钥、轮换计划、Hub SSO 审计条目和禁用开关相互独立——关闭连接器的 Hub 客户端绝不能导致 KLIP 登录中断。
这些都不需要在 KLIP 侧做任何更改。K1–K4 不受影响。
两个 Hub 实例,两次注册
在每个 Hub 中分别注册网关,并将其与匹配的 KLIP 配对:
阶段 | KLIP_ENV | HUB_ISSUER | 客户端 |
4–6(构建、暂存 UAT) |
| testing DWS Hub | testing Hub 中的网关客户端 |
7+(生产切换) |
| production Hub | production Hub 中一个独立的网关客户端 |
该配对在启动时强制执行,因为在一个方向上弄错是危险的,而不仅仅是混乱:
KLIP_ENV=production+ testingHUB_ISSUER→ 网关拒绝启动。 否则,任何能够创建测试 Hub 账户的人都可能接触到真实的商业数据。KLIP_ENV=staging+ productionHUB_ISSUER→ 发出警告并继续。
hub:check 会打印它检测到的配对,因此切换是可验证的:
pairing: KLIP staging <-> Hub testingDWS Hub 的要求(它不是标准 OIDC)
依据 Docs/SSO-TARGET-APP-INTEGRATION.md。其中四项与 OIDC 客户端库默认假设不同,三项会直接失败:
DWS Hub | |
客户端类型 | public,PKCE S256 — |
发现 |
|
令牌正文 | JSON;表单编码返回 |
作用域 | 仅 |
| 在令牌请求中为必填,且必须逐字节完全匹配 |
因此,HUB_DISCOVERY_URL 被显式配置,HUB_CLIENT_SECRET 是可选的,HUB_TOKEN_BODY 默认为 json,并在 unsupported_grant_type 时一次性回退到表单(记录哪种方式有效,以便固定下来)。
设置
在 Hub 中将网关注册为 OIDC 客户端,重定向 URI 为
<PUBLIC_URL>/authorize/hub/callback。它是一个公共客户端——不要索取密钥。将
HUB_ISSUER、HUB_DISCOVERY_URL和HUB_CLIENT_ID放入/opt/mcp/.env。在任何试点用户尝试之前进行验证:
docker compose exec -T gateway node dist/cli.js hub:check这会打印要注册的重定向 URI,运行发现,并在 Hub 未公布 S256 PKCE 时发出警告。网关还会在启动时探测发现,并在 /healthz 中将其报告为 hub_oidc。
添加试点用户(无密码——由 Hub 进行身份验证):
docker compose exec -T gateway node dist/cli.js user:add someone@example.com "Their Name"应急账户
只允许一个本地密码账户,用于 Hub 宕机或配置错误时:
docker compose exec -T gateway node dist/cli.js user:add-break-glass it-emergency@example.com它隐藏在登录页面的披露项后面,首次使用时强制更改密码,并且每次通过它登录都会以高严重级别记录审计,标记为 break_glass: true。Hub 认证的用户完全无法使用密码路径,因此关闭 Hub 并不是回退到无人设置的密码的方式。
一旦 Hub 路径在生产环境中得到验证,请设置 BREAK_GLASS_ENABLED=false,以彻底移除密码面。
可选组门控 — DWS Hub 上不可用
HUB_REQUIRED_GROUP 会针对 groups 声明增加第二重检查。DWS Hub 不签发该声明(它只通告 openid profile email),因此设置它将拒绝所有用户。hub:check 会在您这样做时发出警告。users 表中的试点白名单仍然是授权控制手段。
IdP 发起的登录
Hub 可以从其仪表板磁贴将用户直接推送到回调。这对连接器来说行不通:回调的存在是为了完成 Claude 发起的授权请求,因此没有授权请求到达时,就没有可据以签发 code 的对象。网关会检测到这种情况并提示"请从 Claude 开始",而不是以"登录已过期"的方式失败。
4. 快速开始(本地)
npm ci
docker run -d --name mcpgw-devdb -e POSTGRES_DB=gateway -e POSTGRES_USER=gateway \
-e POSTGRES_PASSWORD=devpassword -p 127.0.0.1:55432:5432 postgres:16-alpine
cp .env.example .env.dev # then edit: PUBLIC_URL=http://localhost:8787, DATABASE_URL=...55432...
npx tsx test/fixtures/mockKlip.ts 5099 & # mock KLIP, behaves like the real one
set -a; . ./.env.dev; set +a
npm run migrate
npx tsx src/index.ts创建试点用户(适用于 TTY 或管道输入):
printf 'a-strong-password\na-strong-password\n' | npx tsx src/cli.ts user:add you@example.com "Your Name"5. 测试
npm test测试套件 | 覆盖范围 |
| Incoterm × 状态 × null 矩阵、kg→MT、舍入顺序、负未结数量、WIB 时间戳 |
| T-6 的穷举方法/路径表、目录穿越与源逃逸 |
| T-5 信封、截断 |
| 有界抓取发布 |
| 针对模拟 KLIP 的全部 9 个工具;非 GET 请求绝不触达 KLIP;401 重新登录; |
| RFC 8707 受众绑定:仅接受此服务器的规范资源 |
| S5 脱敏 — 对字符串激进,对数字无作用 |
| 针对模拟提供方的 Hub OIDC:发现文档签发者不匹配、外部签名密钥、错误的 issuer/audience、过期令牌、缺失和重放的 nonce、无 email 声明 |
|
|
| 生产 KLIP 配测试 Hub 拒绝启动;正常配对不受影响 |
| 从发现文档中选择令牌端点认证方法,包括仅 POST 和公共客户端 Hub |
| 精确建模 DWS Hub: |
模拟 Hub 是一个可工作的迷你 OIDC 提供方 — 真实的发现文档、真实的 JWKS、真实的 RS256 ID 令牌,以及授权码上的 PKCE 验证 — 并配备伪造坏令牌所需的每一个旋钮,因为负面用例才是重点。
模拟 KLIP 夹具刻意复现了真实系统的怪癖:以 MT 标注的千克、混合语言状态、标准四种之外的 incoterm、null 数量、超量交付的合同、被静默钳制为 100 的 limit,以及携带提示注入载荷的合同备注。
6. 部署
完整的主机特定流程,包含真实 IP、主机名和安全组表:deploy/RUNBOOK.md。
# on ECS-MCP
cd /opt/mcp && git pull
docker compose build gateway && docker compose up -d
curl -fsS http://127.0.0.1:8787/healthz管理 CLI — 请注意它在容器内部运行,因为主机仅安装 Docker,没有 Node.js:
docker compose exec -T gateway node dist/cli.js user:list
docker compose exec -T gateway node dist/cli.js audit:summary --days 7
docker compose exec -T gateway node dist/cli.js audit:export --from 2026-08-01 --to 2026-09-01 --out /tmp/audit.csv
docker compose exec -T gateway node dist/cli.js routes:verify # probes KLIP, reports Appendix A gaps紧急开关(S8) — 目标在 5 分钟内:
docker compose exec -T gateway node dist/cli.js tokens:revoke-all --reason "incident 2026-xx"
docker compose stop gateway应用容器不健康时的破窗操作:
docker compose exec -T db psql -U gateway -d gateway -c "UPDATE oauth_tokens SET revoked_at=now() WHERE revoked_at IS NULL;"7. 附录 A 是硬性门禁
src/adapters/klip/routes.ts 和 src/adapters/klip/fields.ts 保存着适配器所依赖的每一个 KLIP 路径、查询参数名、页大小上限、响应字段名和枚举值。当前每一项均未验证。
该门禁是可执行的,而非文书性的:当 KLIP_ENV=production 时,只要有任何路由未验证,进程拒绝启动。针对 staging 运行 routes:verify,记录结果,为每条路由设置 verified: true 并设置 enums.verified = true。
有两个字段比其他字段更重要:
maxLimit— KLIP 实际接受的最大limit。如果它静默钳制为 100,那么KLIP_PAGE_SIZE为 1000 时一页会变成十页,从而破坏延迟目标。enums.*— 规范的状态和 incoterm 值。任何未映射的值都会从总计中排除并附上数据质量说明,绝不使用默认值。
8. 与 TSD v0.9 的偏差
每一项都来自设计评审,并在其调用点处有注释。
# | 变更 | 原因 |
B3 | 仅当 Origin 存在且无效时才拒绝 | 规范仅要求对存在且无效的 Origin 返回 403。Claude 以服务器对服务器方式调用连接器,可能不发送 Origin;若对缺失的 Origin 也拒绝,则每次工具调用都会返回 403。 |
B4 |
| RFC 8707 audience 绑定。在 PRM 文档中增加了 |
B5 | 无状态传输;身份信息来自每个请求中的令牌 | 2026-07-28 修订版移除了协议会话。T-4 通过构造即满足——不存在可劫持的会话。 |
B6 | 熔断开关运行 | 宿主机没有 Node.js,因此 |
B7 | nginx 是反滥用基线;按用户限流以 OAuth | 所有流量均来自 Anthropic 的共享出口网段,因此按 IP 限流会把整个试点项目放入同一个桶中。增加了 |
H1 | OAuth 基于 SDK 的 | SDK 自带 AS,包括吊销和默认限流。只有存储和用户认证是我们自己的。 |
H2 | 下游 Hub OIDC 是登录路径,另设一个应急本地账户 | 从阶段 2 提前引入。Hub 负责认证;用户表仍作为试点白名单。 |
H3 | 为 Anthropic 的 | Anthropic 发布稳定的出口网段并建议白名单; |
H4 | 截断的结果发布 | 四条不同的路径都可能导致一个看似正确实则错误的数字。 |
H5 | 短 TTL 缓存;第 2..N 页并发获取;页大小限制为 | 十次串行往返无法满足 P95 ≤ 5 s。 |
H6 | 新增第 9 个工具 | 没有它,拼写错误的工厂名称会返回空集合,而这会被解读为"没有任何 outstanding"。 |
H7 |
| 规范中硬编码的 "KLIP production" 字符串会导致每次预演 UAT 的答案都声称自己是生产环境。 |
H9 |
| 通过删除分区进行保留是仅追加触发器唯一允许的方式;U5 的导出没有实现;客户端 IP 原本都会记录为 127.0.0.1。 |
H10 | 备份侧车、容器健康检查、 | 在 TSD 中有规定,但指南中从未实现,因此上线时不会存在。 |
— | 未知工具参数通过 | PRD 8.1 要求拒绝;普通的 |
— | 工具返回针对 | 类型化数据而非嵌入散文中的 JSON —— 减少转写错误,这正是 M1 所衡量的。 |
9. 仍待处理事项
附录 A 对账 (P1) —— 阻止投产,启动时强制检查。
KLIP 侧 K1–K4 ——
MCP_READONLY角色、svc-mcp账户、安全组规则。离机备份和经过测试的恢复 —— 侧车目前仅写入本地。
deploy/nginx/mcp.conf中的真实企业出口 CIDR,然后启用两行被注释掉的return 403行。负载测试,按 30 并发用户容量 NFR 执行。
Hub 客户端注册 —— 网关自身的注册,先在测试 DWS Hub 中:
HUB_ISSUER、客户端 ID 和密钥,并在 Hub 侧注册重定向 URI<PUBLIC_URL>/authorize/hub/callback。确认 Hub 的 email 和 groups 声明名称,然后运行hub:check。阶段 7 需要在生产 Hub 中进行第二次独立注册。决定是否设置
HUB_REQUIRED_GROUP,以及在 Hub 路径在生产环境中验证后是否将BREAK_GLASS_ENABLED=false。
This server cannot be deployed
Maintenance
Related MCP Connectors
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Give Claude only the Google Drive files you choose. Every action logged.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables read-only querying of the gong-nl-db Postgres database through natural language via Claude Desktop.9Academic Free v1.1
- AlicenseNot gradedqualityDmaintenanceEnables natural language queries against Snowflake Gold-layer tables through Claude Desktop, allowing users to ask business questions in plain English without SQL knowledge.MIT
- FlicenseNot gradedqualityCmaintenanceEnables natural language interaction with Kintone data via Claude, allowing listing apps, field definitions, querying and modifying records.-
- FlicenseAqualityCmaintenanceEnables querying AgroClimate company data via natural language through ChatGPT or Claude.7-