
Gateway
io.github.bolyraPublished by GitHub account bolyrav0.2.1Updated Oct 24, 2026
MCP auth gateway: verify agent identity, enforce per-tool permissions, block replays, emit receipts.
Overview
A local reverse-proxy gateway that authenticates agents and enforces per-tool permissions before forwarding MCP tool calls to an upstream server.
- What you can do
- It sits in front of an upstream MCP server and intercepts JSON-RPC traffic: non-tools/call methods are forwarded unauthenticated, while tools/call requests are verified first. Verification covers agent proof bundles, per-tool permission bitmasks, delegation chain depth, credential binding, and nonce-based replay protection. Every allow and deny decision produces an ES256K-signed receipt, optionally hash-chained, written to files, stdout, or a webhook. Verified requests are forwarded with X-Bolyra-* headers describing the agent DID, score, permissions, and receipt id.
- When to use it
- Use it when several agents or clients call one MCP server and you want a single enforcement point for tool-level authorization, replay protection, and an auditable record of every tool call. It is also useful when an upstream server should stay on localhost or a private network while only the gateway is exposed.
- Requirements
- Node.js for the npm package, or Docker for the container image. A target upstream MCP server URL is required. Optional: a gateway config file, a credentials file, a Redis URL for shared nonce storage, and a secp256k1 receipt signing key supplied via the BOLYRA_RECEIPT_KEY environment variable or config. No accounts or API keys are declared.
Installation
In SourceWeft
- Open Gateway in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
@bolyra/gateway
Bolyra MCP Auth Gateway -- standalone reverse proxy that verifies agent ZKP credentials before forwarding requests to upstream MCP servers.
Put @bolyra/gateway in front of your MCP server; it verifies agent authority, prevents replay, enforces delegated scopes, and gives you signed audit logs for every tool call.
Quickstart
Or run directly with CLI flags:
How It Works
Request routing:
GET /healthz-- returns gateway status (intercepted, not proxied)- JSON-RPC method !=
tools/call-- forwarded to upstream without auth - JSON-RPC method ==
tools/call-- auth verified, then forwarded if authorized
CLI Reference
CLI flags override config file values.
Config File Reference
Environment variables are substituted in string values using ${VAR_NAME} syntax.
Library API
For embedding the gateway middleware in your own server:
Exports
Redis Nonce Store (v0.2.0+)
For multi-instance deployments, use Redis for shared nonce replay protection:
Library usage:
Fail-closed behavior: If Redis is unreachable, the gateway returns HTTP 500, never passes requests through. Monitor Redis availability as a critical dependency.
X-Bolyra-* Headers
When a request passes verification, the gateway injects these headers into the proxied request:
The upstream server can use these headers for logging/auditing without importing Bolyra.
Signed Receipts (v0.3.0+)
Every tools/call decision — allow and deny, dev mode and production —
produces an ES256K-signed receipt (SignedReceipt from
@bolyra/receipts: secp256k1 + keccak256,
Ethereum-compatible recovery). This includes rejections with no usable proof
bundle at all: a missing or malformed Authorization header still leaves a
signed anonymous deny receipt, so the audit trail has no unsigned gaps.
Each receipt carries the verdict (decision.allowed), a human-readable
reasonCode (which tool, which permissions were required vs. held), the agent
DID when known, score, permission bitmask, delegation chain depth, hashes of
the presented proof material, and the decision timestamp. Any edit to a
receipt breaks its signature.
Signing key resolution:
receiptSigneroption (library embedders)receipts.privateKeyfrom the config (recommended for production)- Otherwise an ephemeral key is generated at startup. Receipts remain
independently verifiable — the signer address is recoverable from every
signature — but the address rotates on restart. The CLI prints the signer
address in the startup banner and, in
fileoutput mode, persists it to{receipt-dir}/signer.jsonso auditors can pin the trust anchor.
Independent verification needs only the receipt (plus, optionally, the pinned signer address) — no gateway, no database:
Hash-Chained Receipts (v0.5.0+)
Signatures make each receipt tamper-evident; hash-chaining makes the receipt
stream tamper-evident. Every receipt the gateway signs carries
chain: { seq, prevReceiptHash } inside the signed payload (seq is 0-based
per gateway process; the first receipt links to the 32-zero-byte genesis
sentinel) plus a top-level receiptHash that the next receipt links to.
Deleting, reordering, or inserting receipts in a collected log breaks chain
verification even though every remaining signature stays individually valid:
Notes for auditors:
- One chain per gateway process. A restart starts a new chain at seq 0 —
rotate the collected log per process run (or verify each run's segment
separately); a mid-file restart is reported as
chain-restart. - File mode writes one JSON file per receipt; the chain fields still order and link them (concatenate in seq order to verify).
- Tail truncation is not detectable from the log alone. Chain
verification proves nothing was deleted, reordered, or edited within the
log, but a log cut after any receipt is still internally consistent. Pin
the head hash / receipt count that
verify-chainprints and re-verify with--expect-head/--expect-count. How and how often you anchor those checkpoints (object-lock storage, transparency log, chain) is enterprise-configurable deployment policy. - Receipts predating 0.5.0 have no chain fields;
--allow-unchainedaccepts them as a leading prefix (signatures still verified) while chain-verifying the rest. A chain-less receipt after a chained one always fails — it could be an inserted line.
Receipt Output Modes
All modes are non-blocking. Write and signing failures are logged but never
interrupt request processing. The signing key is probe-validated at startup;
in the exceptional case that signing still fails at runtime, the decision is
recorded as a raw JSON record explicitly tagged "unsigned": true (with the
signing error), so audit consumers can detect the gap — an unsigned record
can never masquerade as a signed receipt.
Receipts always record the gateway's final decision: a request that authenticates successfully but fails tool policy gets a deny receipt. A bundle that parses as JSON but is missing proof material entirely is denied fail-closed (HTTP 401) with a signed anonymous receipt.
Credential Binding (v0.4.0+)
In Core mode (--dev) proofs are mocked, so the permission mask inside a
bundle is asserted by the caller. Registering credentials turns that claim
into an enforced check — the same registered-credential pattern as
examples/verified-actions-demo,
now packaged:
- unknown commitment → 401 + signed deny receipt (
credential_unknown) - claimed mask ≠ registered grant (forged bundle) → 401 + signed deny
receipt (
credential_mismatch) - delegation chain expanding beyond the grant → 401 (
credential_mismatch— scope narrowing is one-way) - registered expiry passed → 401 + signed deny receipt
(
credential_expired)
All denials are fail-closed and receipted like every other decision.
Registering a dev credential. A commitment in Core mode is an opaque identifier the platform assigns (production commitments are Poseidon hashes of the credential). Generate one and register it:
Hand the commitment (and its granted mask) to the agent; its proof bundles
must carry exactly that identity and mask. The --credentials file can be a
bare map (above) or the full config-section shape (type: static, map:);
the credentials: section in gateway.yaml is equivalent.
Without registered credentials, Core mode stays frictionless for
tutorials: any claim passes (current behavior), but the gateway warns at
startup and every allow receipt is flagged
[credential-binding: none — permission claims self-asserted] so audit
consumers can see the tradeoff — the same visibility pattern as the
ephemeral receipt-signing key.
In production mode the same credentials section is compiled into a
resolveCredential implementation, engaging verifyBundle's cryptographic
binding: the verifier recomputes Poseidon3(permissionBitmask, commitment, expiryTimestamp) from the registered credential and requires it to equal
the proof's public scopeCommitment output — a forged mask cannot produce
a valid proof. expiryTimestamp is therefore required per entry in
production. Library embedders can still pass resolveCredential directly;
it takes precedence over the config section.
Security Model
The gateway is the trust boundary. Agents present proof bundles; the gateway verifies them before forwarding.
Deployment guidance:
- Bind your upstream MCP server to localhost or a private network
- Only expose the gateway to the internet
- Use HTTPS between clients and the gateway
- Configure HMAC signing if you need to verify X-Bolyra-* headers were set by the gateway
Threats addressed:
- Replay attacks (nonce store)
- Credential substitution (production: Poseidon3 scope commitment binding)
- Forged permission claims in Core mode (registered-credential binding, when credentials are configured)
- Delegation escalation (one-way scope narrowing, circuit-enforced)
- Scope bypass (per-tool bitmask enforcement)
- Header injection (optional HMAC signing)
- Audit evasion (ES256K-signed receipts for every decision — allow and deny, including anonymous rejections; tamper-evident via
verifyReceipt)
Permission Bitmask Reference
Higher tiers imply lower (e.g., FINANCIAL_MEDIUM implies FINANCIAL_SMALL).
Docker
Run the gateway as a container with zero Node.js setup required.
Pull from GHCR
Quick Start
Production
Build Locally
Override Port
Image Details
- Base:
node:22-alpine - User:
bolyra(UID 1001, non-root) - Port: 4100 (default)
- Healthcheck:
GET /healthzevery 30s - Config mount:
/etc/bolyra/gateway.yaml - Receipt dir:
/app/receipts/(writable) - Architectures:
linux/amd64,linux/arm64
CLI flags passed after the image name override config file values. See CLI Reference above.
License
Apache-2.0
Source: integrations/gateway/README.md at commit 5e9de85
Tools
0Version history
1- v0.2.1LatestSep 16, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts