关于 gateway
gateway 是用于 MCP 服务前端的身份验证反向代理,面向需要限制 Agent 工具调用并记录审计信息的部署。它验证 ZKP 凭证、执行工具权限和防重放检查,并生成签名收据。
主要功能
- 01验证 Agent ZKP 凭证
- 02按工具执行权限策略
- 03检查 nonce 重放
- 04生成签名及哈希链收据
- 05支持 Redis 共享 nonce 存储
适用场景
- 01在 MCP 服务前拦截未授权工具调用
- 02将收据输出到文件、标准输出或 webhook
- 03通过 Redis 在多实例间共享重放保护
- 04独立核验已收集的签名收据
MCP 相关
作为 MCP 服务的反向代理,gateway 验证 tools/call 请求并在授权后转发;其他 JSON-RPC 方法不经身份验证直接转发。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
@bolyra/gateway
Bolyra MCP Auth Gateway —— 独立的反向代理,可在将请求转发到上游 MCP 服务前验证 Agent 的 ZKP 凭证。
将 @bolyra/gateway 放在 MCP 服务前面;它会验证 Agent 权限、防止重放、强制执行委派的作用域,并为每次工具调用生成签名审计日志。
快速开始
# Install
npm install @bolyra/gateway
# Create a config file (optional)
cat > gateway.yaml << 'EOF'
target: http://localhost:3000/mcp
port: 4100
devMode: true
EOF
# Run
npx @bolyra/gateway --config ./gateway.yaml
也可以直接通过 CLI 标志运行:
npx @bolyra/gateway --target http://localhost:3000/mcp --dev --port 4100
工作原理
+----------------------------------+
| @bolyra/gateway |
Agent -------->| |--------> Upstream MCP Server
(with proof | 1. Parse Authorization header | (unmodified)
bundle) | 2. verifyBundle() from @bolyra/mcp|
| 3. checkToolPolicy() |
| 4. Nonce replay check |
| 5. Emit signed receipt |
| 6. Proxy request + X-Bolyra-* hdrs|
| |
| Config: gateway.yaml |
| Receipts: ./receipts/ or stdout |
+----------------------------------+
请求路由:
GET /healthz—— 返回网关状态(由网关拦截,不会代理)- JSON-RPC 方法 !=
tools/call—— 不进行身份验证,直接转发到上游 - JSON-RPC 方法 ==
tools/call—— 验证身份后,仅在获得授权时转发
CLI 参考
npx @bolyra/gateway [options]
Options:
--target <url> Upstream MCP server URL (required unless in config)
--port <number> Gateway listen port (default: 4100)
--config <path> Path to gateway config file (default: ./gateway.yaml)
--dev Enable Bolyra Core mode (classical checks: tool policy,
nonce replay, signed receipts, credential binding
against registered credentials; ZK proof verification
off. Without registered credentials, permission claims
are self-asserted — see Credential Binding below)
--credentials <path> Credentials file (YAML/JSON map: commitment ->
{ permissionBitmask, expiryTimestamp? }); overrides
the config's credentials section
--receipt-dir <path> Directory for receipt JSON files (default: ./receipts/)
--receipt-stdout Write receipts to stdout (NDJSON)
--no-receipts Disable receipt generation
--network <name> Bolyra network (default: base-sepolia)
--help Show help
--version Show version
CLI 标志会覆盖配置文件中的值。
配置文件参考
# gateway.yaml
target: http://localhost:3000/mcp
port: 4100
network: base-sepolia
# Bolyra Core mode: classical checks only, ZK proof verification skipped
devMode: false
# Registered credentials (see "Credential Binding" below).
# Core mode (devMode: true): claims are checked against this registry —
# unknown commitments, forged masks, and expired credentials are denied.
# Production mode: this map becomes the resolveCredential source for
# verifyBundle's Poseidon3 scopeCommitment binding (expiryTimestamp
# required per entry).
# type: registry (on-chain resolution) is not supported by the packaged
# gateway yet and fails validation — use the library resolveCredential
# option for custom resolution.
credentials:
type: static
map:
"48201394857102938471029384": # credential commitment (decimal)
permissionBitmask: 3 # READ_DATA | WRITE_DATA
expiryTimestamp: "1893456000" # unix seconds; optional in Core mode
# Per-tool policies
tools:
write_file:
requireBitmask: 2 # WRITE_DATA (0b10)
delete_file:
requireBitmask: 6 # WRITE_DATA + FINANCIAL_SMALL (0b110)
maxChainDepth: 0 # Direct credentials only
transfer_funds:
requireBitmask: 28 # FINANCIAL_* (0b11100)
minScore: 90
# Nonce replay protection
nonce:
store: memory # "memory" (default) or "redis"
maxProofAge: 300 # seconds
# Redis config (required when store: redis)
# redis:
# url: ${REDIS_URL} # required
# keyPrefix: "bolyra:nonce:" # optional, default shown
# connectTimeout: 5000 # optional, ms
# Receipt signing — every allow/deny decision is ES256K-signed (see
# "Signed Receipts" below). Without privateKey an ephemeral key is
# generated at startup; set it for a stable, pinnable signer address.
receipts:
enabled: true
issuer: "my-gateway"
keyId: "k1"
privateKey: "${BOLYRA_RECEIPT_KEY}" # 32-byte hex secp256k1 key, env var substitution
output: file # "file", "stdout", or "webhook"
dir: ./receipts/
# Health check
health:
enabled: true
path: /healthz
# Optional HMAC signing for X-Bolyra-* headers
# hmac:
# secret: "hex-encoded-shared-secret"
字符串值中的环境变量会使用 ${VAR_NAME} 语法进行替换。
库 API
如需在自己的服务中嵌入网关中间件:
import {
createGatewayMiddleware,
createGatewayProxy,
loadConfig,
injectBolyraHeaders,
computeHmac,
verifyHmac,
createReceiptWriter,
createHealthHandler,
} from '@bolyra/gateway';
import type {
GatewayConfig,
GatewayMiddlewareOptions,
GatewayRequest,
} from '@bolyra/gateway';
// Option 1: Full proxy server
const config = loadConfig({ target: 'http://localhost:3000/mcp', dev: true });
const server = createGatewayProxy({ config });
server.listen(4100);
// Option 2: Just the middleware (for custom servers)
const middleware = createGatewayMiddleware({ config });
// Use in your request handler:
// const authorized = await middleware(req, res, toolName);
导出项
| 导出项 | 说明 |
|---|---|
createGatewayProxy(options) | 创建完整的反向代理 HTTP 服务 |
createGatewayMiddleware(options) | 仅创建身份验证中间件 |
loadConfig(flags?) | 加载并验证网关配置 |
injectBolyraHeaders(authCtx, receiptId?) | 构建 X-Bolyra-* 标头 |
computeHmac(headers, secret) | 对 X-Bolyra-* 标头进行 HMAC-SHA256 签名 |
verifyHmac(headers, secret, hmac) | 验证 X-Bolyra-* 标头上的 HMAC |
createReceiptWriter(config) | 创建可插拔的回执输出 |
createGatewayReceiptSigner(config, override?) | 解析 ES256K 回执签名器(使用已配置密钥或临时密钥) |
createHealthHandler(config) | 创建 /healthz 端点处理程序 |
extractToolName(body) | 从 JSON-RPC 请求体中提取工具名称 |
loadCredentialsFile(path) | 加载 --credentials 文件(仅映射或完整区段) |
buildCredentialRegistry(credentials) | 将静态凭证区段编译为查找注册表 |
checkCredentialBinding(bundle, authCtx, registry) | 核心模式下检查已注册的凭证(参见凭证绑定) |
createStaticCredentialResolver(credentials) | 用于生产环境 scopeCommitment 绑定的静态 resolveCredential |
hasStaticCredentials(credentials) | 配置了可用静态凭证映射时返回 True |
RedisNonceStore | 用于多实例部署、基于 Redis 的 NonceStore |
Redis Nonce Store(v0.2.0+)
对于多实例部署,可使用 Redis 共享 nonce 重放保护:
# gateway.yaml
nonce:
store: redis
maxProofAge: 300
redis:
url: ${REDIS_URL}
keyPrefix: "bolyra:nonce:" # optional
库用法:
import { createGatewayMiddleware, RedisNonceStore } from '@bolyra/gateway';
const store = new RedisNonceStore({ url: 'redis://localhost:6379' });
const middleware = createGatewayMiddleware({ config, nonceStore: store });
// Graceful shutdown
process.on('SIGTERM', () => store.close());
故障时拒绝放行: 如果无法连接 Redis,网关会返回 HTTP 500,绝不会让请求绕过验证。请将 Redis 可用性作为关键依赖进行监控。
X-Bolyra-* 标头
请求通过验证后,网关会向代理请求注入以下标头:
| 标头 | 值 |
|---|---|
X-Bolyra-Verified | true |
X-Bolyra-DID | did:bolyra:{network}:{commitment} |
X-Bolyra-Score | 验证得分(0-100) |
X-Bolyra-Permissions | 有效权限位掩码(十进制) |
X-Bolyra-Chain-Depth | 委派链深度(0 = 直接委派) |
X-Bolyra-Receipt-ID | 此决策的签名回执 ID(启用回执时) |
X-Bolyra-HMAC | HMAC-SHA256 签名(配置 HMAC 时) |
上游服务无需导入 Bolyra,即可使用这些标头进行日志记录和审计。
已签名回执(v0.3.0+)
每个 tools/call 决策——允许和拒绝、开发模式和生产模式——都会生成一份使用 ES256K 签名的回执(SignedReceipt,来自
@bolyra/receipts:secp256k1 + keccak256,
支持与 Ethereum 兼容的恢复)。这也包括完全没有可用证明包的拒绝:即使缺少或格式错误的 Authorization 标头,仍会留下已签名的匿名拒绝回执,因此审计记录不会出现未签名的空档。
每份回执都包含判定结果(decision.allowed)、可读的 reasonCode(涉及哪个工具、需要哪些权限以及持有哪些权限)、已知时的 Agent DID、评分、权限位掩码、委托链深度、所提供证明材料的哈希,以及决策时间戳。任何对回执的修改都会导致其签名失效。
签名密钥的解析顺序:
receiptSigner选项(供库的嵌入方使用)- 配置中的
receipts.privateKey(生产环境推荐) - 否则,启动时会生成一个临时密钥。回执仍可独立验证——每个签名都能恢复出签名者地址——但该地址会在重启后轮换。CLI 会在启动横幅中打印签名者地址;在
file输出模式下,还会将其写入{receipt-dir}/signer.json,以便审计人员固定信任锚点。
独立验证只需要回执(以及可选的固定签名者地址)——无需网关或数据库:
import { verifyReceipt } from '@bolyra/receipts';
const receipt = JSON.parse(fs.readFileSync('receipts/2026-07-10/....json', 'utf8'));
verifyReceipt(receipt); // true — signature + payload hash check
verifyReceipt(receipt, pinnedSigner); // true — also pins the signer address
哈希链回执(v0.5.0+)
签名让每份回执具有防篡改可验证性;哈希链则让回执流具有防篡改可验证性。网关签署的每份回执都会在已签名载荷中携带 chain: { seq, prevReceiptHash }(seq 在每个网关进程内从 0 开始;第一份回执链接到由 32 个零字节组成的创世哨兵值),并在顶层包含 receiptHash,供下一份回执链接。即使剩余的每份签名仍单独有效,在收集的日志中删除、重新排序或插入回执,也会导致链验证失败:
# stdout mode: collect NDJSON to a file, verify signatures + chain
bolyra receipt verify-chain receipts.ndjson --signer 0xPinnedSigner
审计人员须知:
- 每个网关进程有一条链。 重启会从 seq 0 开始新链——请按进程运行划分收集的日志(或分别验证每次运行的片段);文件中途重启会报告为
chain-restart。 - 文件模式会为每份回执写入一个 JSON 文件;链字段仍会对这些文件进行排序和链接(按 seq 顺序拼接后即可验证)。
- 仅凭日志无法检测尾部截断。 链验证可以证明日志内部没有内容被删除、重新排序或修改,但在任意一份回执后截断的日志仍然内部一致。请固定
verify-chain打印的头部哈希/回执数量,并使用--expect-head/--expect-count重新验证。如何以及多久锚定一次这些检查点(对象锁定存储、透明日志、区块链),属于可按企业需求配置的部署策略。 - 0.5.0 之前的回执没有链字段;
--allow-unchained会接受它们作为前导部分(仍会验证签名),同时对其余回执验证哈希链。链中已有带链回执后再出现不带链的回执,则一定会验证失败——它可能是插入的日志行。
回执输出模式
| 模式 | 描述 |
|---|---|
file | 按日期轮换目录中的 JSON 文件:receipts/2026-06-18/{timestamp}-{receipt-id}.json,以及包含固定签名者地址的 signer.json |
stdout | 输出到 stdout 的 NDJSON(每条回执一行 JSON) |
webhook | 向配置的 URL 发送 HTTP POST,可选附带身份验证标头 |
所有模式均为非阻塞模式。写入和签名失败会记录日志,但不会中断请求处理。启动时会对签名密钥进行探测验证;在极少数情况下,如果签名在运行时仍然失败,则会将决策记录为原始 JSON 记录,并明确标记为 "unsigned": true(附带签名错误),以便审计使用方检测这一缺口——未签名记录绝不会伪装成已签名回执。
回执始终记录网关的最终决策:请求即使通过身份验证,但未通过工具策略,也会生成 deny 回执。能够解析为 JSON、但完全缺少证明材料的捆绑包会按失败关闭方式拒绝(HTTP 401),并生成匿名签名回执。
凭据绑定(v0.4.0+)
在核心模式(--dev)下,证明是模拟的,因此捆绑包中的权限掩码由调用方声明。注册凭据后,这项声明会转变为强制检查——采用与 examples/verified-actions-demo 相同的已注册凭据模式,现已打包:
- 未知承诺 → 401 + 签名拒绝回执(
credential_unknown) - 声明的掩码 ≠ 已注册的授权(伪造的捆绑包)→ 401 + 签名拒绝回执(
credential_mismatch) - 委派链超出授权范围 → 401(
credential_mismatch——范围只能单向收窄) - 已注册凭据过期 → 401 + 签名拒绝回执(
credential_expired)
所有拒绝均按失败关闭方式处理,并像其他所有决策一样生成回执。
注册开发凭据。 在核心模式下,承诺是平台分配的不透明标识符(生产环境中的承诺是凭据的 Poseidon 哈希)。生成并注册一个承诺:
# 1. Generate a commitment
node -e "console.log(BigInt('0x' + require('crypto').randomBytes(16).toString('hex')).toString())"
# 2. Register it (credentials.yaml)
cat > credentials.yaml << 'EOF'
"48201394857102938471029384":
permissionBitmask: 3 # READ_DATA | WRITE_DATA
EOF
# 3. Run with binding enforced
npx @bolyra/gateway --target http://localhost:3000/mcp --dev --credentials ./credentials.yaml
将承诺(及其授权掩码)提供给 Agent;其证明捆绑包必须携带完全一致的身份和掩码。--credentials 文件可以是简单映射(如上所示),也可以采用完整的配置段结构(type: static、map:);gateway.yaml 中的 credentials: 段与之等效。
未注册凭据时,核心模式仍可在教程中轻松使用:任何声明都会通过(当前行为),但网关会在启动时发出警告,并在每条 allow 回执中标记 [credential-binding: none — permission claims self-asserted],以便审计使用方了解这一取舍——这与临时回执签名密钥采用相同的可见性模式。
在生产模式下,同一个 credentials 段会编译为 resolveCredential 实现,启用 verifyBundle 的密码学绑定:验证器会根据已注册凭据重新计算 Poseidon3(permissionBitmask, commitment, expiryTimestamp),并要求其等于证明的公开 scopeCommitment 输出——伪造的掩码无法生成有效证明。因此,生产环境中的每个条目都必须包含 expiryTimestamp。库的嵌入者仍可直接传入 resolveCredential;它的优先级高于配置段。
安全模型
网关是信任边界。Agent 提供证明包;网关会先验证这些证明,再进行转发。
部署建议:
- 将上游 MCP 服务绑定到 localhost 或专用网络
- 仅将网关暴露到互联网
- 在客户端与网关之间使用 HTTPS
- 如果需要验证 X-Bolyra-* 标头是否由网关设置,请配置 HMAC 签名
应对的威胁:
- 重放攻击(nonce 存储)
- 凭据替换(生产环境:Poseidon3 范围承诺绑定)
- Core 模式下伪造权限声明(配置了凭据时,绑定已注册的凭据)
- 委托权限升级(单向收窄范围,由电路强制执行)
- 绕过范围限制(按工具执行位掩码)
- 标头注入(可选 HMAC 签名)
- 规避审计(每项决策——允许和拒绝,包括匿名请求被拒——均生成 ES256K 签名回执;可通过
verifyReceipt验证篡改痕迹)
权限位掩码参考
| 位 | 权限 | 值 |
|---|---|---|
| 0 | READ_DATA | 1 |
| 1 | WRITE_DATA | 2 |
| 2 | FINANCIAL_SMALL (<$100) | 4 |
| 3 | FINANCIAL_MEDIUM (<$10K) | 8 |
| 4 | FINANCIAL_UNLIMITED | 16 |
| 5 | SIGN_ON_BEHALF | 32 |
| 6 | SUB_DELEGATE | 64 |
| 7 | ACCESS_PII | 128 |
较高层级包含较低层级的权限(例如,FINANCIAL_MEDIUM 包含 FINANCIAL_SMALL)。
Docker
将网关作为容器运行,无需配置 Node.js。
从 GHCR 拉取
docker pull ghcr.io/bolyra/gateway:latest
快速开始
# Bolyra Core mode -- classical checks, no circuits, no config file needed
docker run --rm -p 4100:4100 ghcr.io/bolyra/gateway \
--target http://host.docker.internal:3000/mcp --dev
生产环境
# Mount config file + set env vars
docker run -d \
-v $(pwd)/gateway.yaml:/etc/bolyra/gateway.yaml:ro \
-e REDIS_URL=redis://redis:6379 \
-e BOLYRA_RECEIPT_KEY=hex-encoded-key \
-p 4100:4100 \
ghcr.io/bolyra/gateway
本地构建
# From repo root
docker build -f Dockerfile.gateway -t bolyra-gateway:local .
# Run with local build
docker run --rm -p 4100:4100 bolyra-gateway:local \
--target http://host.docker.internal:3000/mcp --dev
# Verify health
curl http://localhost:4100/healthz
覆盖端口
docker run --rm -p 8080:8080 ghcr.io/bolyra/gateway \
--target http://upstream:3000/mcp --port 8080 --dev
镜像详情
- 基础镜像:
node:22-alpine - 用户:
bolyra(UID 1001,非 root) - 端口: 4100(默认)
- 健康检查: 每 30s 执行一次
GET /healthz - 配置挂载:
/etc/bolyra/gateway.yaml - 回执目录:
/app/receipts/(可写) - 架构:
linux/amd64、linux/arm64
镜像名称后传入的 CLI 标志会覆盖配置文件中的值。请参阅上文的 CLI 参考。
许可证
Apache-2.0
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。