mcpField
G
gateway

gateway

本地服务
github.com/bolyra安全04 天前更新

关于 gateway

gateway 是用于 MCP 服务前端的身份验证反向代理,面向需要限制 Agent 工具调用并记录审计信息的部署。它验证 ZKP 凭证、执行工具权限和防重放检查,并生成签名收据。

主要功能

  1. 01验证 Agent ZKP 凭证
  2. 02按工具执行权限策略
  3. 03检查 nonce 重放
  4. 04生成签名及哈希链收据
  5. 05支持 Redis 共享 nonce 存储

适用场景

  1. 01在 MCP 服务前拦截未授权工具调用
  2. 02将收据输出到文件、标准输出或 webhook
  3. 03通过 Redis 在多实例间共享重放保护
  4. 04独立核验已收集的签名收据

MCP 相关

作为 MCP 服务的反向代理,gateway 验证 tools/call 请求并在授权后转发;其他 JSON-RPC 方法不经身份验证直接转发。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"bolyra-gateway": {
"command": "npx",
"args": [
"-y",
"@bolyra/gateway"
]
}
}
}
启动命令npx -y @bolyra/gateway

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@bolyra/gateway0.2.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

@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    |
                  +----------------------------------+

请求路由:

  1. GET /healthz —— 返回网关状态(由网关拦截,不会代理)
  2. JSON-RPC 方法 != tools/call —— 不进行身份验证,直接转发到上游
  3. 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-Verifiedtrue
X-Bolyra-DIDdid:bolyra:{network}:{commitment}
X-Bolyra-Score验证得分(0-100)
X-Bolyra-Permissions有效权限位掩码(十进制)
X-Bolyra-Chain-Depth委派链深度(0 = 直接委派)
X-Bolyra-Receipt-ID此决策的签名回执 ID(启用回执时)
X-Bolyra-HMACHMAC-SHA256 签名(配置 HMAC 时)

上游服务无需导入 Bolyra,即可使用这些标头进行日志记录和审计。

已签名回执(v0.3.0+)

每个 tools/call 决策——允许和拒绝、开发模式和生产模式——都会生成一份使用 ES256K 签名的回执(SignedReceipt,来自 @bolyra/receipts:secp256k1 + keccak256, 支持与 Ethereum 兼容的恢复)。这也包括完全没有可用证明包的拒绝:即使缺少或格式错误的 Authorization 标头,仍会留下已签名的匿名拒绝回执,因此审计记录不会出现未签名的空档。

每份回执都包含判定结果(decision.allowed)、可读的 reasonCode(涉及哪个工具、需要哪些权限以及持有哪些权限)、已知时的 Agent DID、评分、权限位掩码、委托链深度、所提供证明材料的哈希,以及决策时间戳。任何对回执的修改都会导致其签名失效。

签名密钥的解析顺序:

  1. receiptSigner 选项(供库的嵌入方使用)
  2. 配置中的 receipts.privateKey(生产环境推荐)
  3. 否则,启动时会生成一个临时密钥。回执仍可独立验证——每个签名都能恢复出签名者地址——但该地址会在重启后轮换。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 验证篡改痕迹)

权限位掩码参考

位权限值
0READ_DATA1
1WRITE_DATA2
2FINANCIAL_SMALL (<$100)4
3FINANCIAL_MEDIUM (<$10K)8
4FINANCIAL_UNLIMITED16
5SIGN_ON_BEHALF32
6SUB_DELEGATE64
7ACCESS_PII128

较高层级包含较低层级的权限(例如,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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。