AgentStack
MCP verified MIT Self-run

Agentgate

mcp-selfradiance-agentgate · by selfradiance

Collateralized execution engine for AI agents: bond-and-slash accountability with Ed25519 identities, bounded exposure, and progressive trust tiers.

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add mcp-selfradiance-agentgate

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Agentgate? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

AgentGate

AgentGate is a collateralized execution engine for AI agents: Ed25519 identities, reusable bonds, bounded exposure, settlement and slashing, and progressive trust tiers at the point where agents attempt external actions.

[](https://github.com/selfradiance/agentgate/actions/workflows/ci.yml)

> [!IMPORTANT] > Where To Start > > AgentGate is the deeper accountability substrate underneath parts of this ecosystem. It is usually not the first repo a cold reader should start with. > > Recommended order for a first read: > 1. Governed WriteFile Demo for the narrowest end-to-end proof path > 2. MCP Firewall for the governed tool-call layer built on top > 3. AgentGate (this repo) for the underlying execution engine > > Start here when you want the engine itself: identity registration, bond lifecycle, bounded exposure accounting, settlement and slashing, and transport/security mechanics.

AgentGate is the bond-and-slash substrate itself. It requires signed identities, lets agents lock reusable bond capacity against declared exposure, and records settlement outcomes in durable audit state at the deterministic choke point between agents and external actions.

Visual Explainer

Prefer a short visual overview before reading? Watch the 4-part intro on X:

AgentGate explainer thread (4 short videos)

Covers:

  • what AgentGate is
  • why permissions are not enough
  • a concrete MCP Firewall example
  • why runtime accountability matters

What AgentGate Does

AgentGate sits between an autonomous agent and an external action such as an API call, bid, or financial operation. Before the action runs, the agent authenticates with an Ed25519 identity and locks bond capacity against a declared exposure.

If the action resolves cleanly, the reserved exposure is settled and released. Manual malicious resolution now requires two distinct eligible resolver identities before the slash finalizes, while expired-action sweeps still slash immediately. The system keeps that lifecycle in durable audit state and uses prior outcomes to gate larger bond sizes through progressive trust tiers.

This is a narrow runtime accountability mechanism, not a general AI safety or general agent security solution.

The actions table serves double duty: it's both a real-time enforcement log for slashing and a durable post-incident audit trail that can support disclosure to affected parties. The threat model doc covers this in more detail.

> [Threat Model →](docs/threat-model.md) — What AgentGate defends against, what it doesn't, and why.

Read the full story: [How I Built AgentGate](docs/manifesto.md)


Quick Integration

AgentGate works with any agent that can make HTTP requests. If you want the shortest outsider-readable proof, start with the Governed WriteFile Demo. If you want the governance layer that fronts MCP tools, see MCP Firewall. The flow below is the direct engine integration path: register an identity, lock a bond, execute an action against that bond, and resolve the outcome.

> Note: The curl examples below assume AGENTGATE_DEV_MODE=true for local development (auth key enforcement is skipped). In production, add -H 'x-agentgate-key: YOUR_KEY' to every POST request.

1. Register an identity

Identity registration requires proof-of-possession — the caller must sign the request with the private key matching the public key being registered. This uses the same signature headers as all other state-changing endpoints:

curl -s http://127.0.0.1:3000/v1/identities \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "publicKey": "" }'

Returns an identityId (e.g., id_abc123).

2. Lock a bond

All state-changing requests must be signed. Headers required on every request:

  • x-agentgate-timestamp — current time in epoch milliseconds
  • x-agentgate-signature — Ed25519 signature over sha256(nonce + method + path + timestamp + JSON.stringify(body))
  • x-nonce — a unique string per request (UUID recommended); bound into the signed message AND stored server-side — the server rejects duplicates per identity, providing replay protection on top of the timestamp window
curl -s http://127.0.0.1:3000/v1/bonds/lock \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "identityId": "id_abc123", "amountCents": 5000, "currency": "USD", "ttlSeconds": 300, "reason": "marketplace bid" }'

Returns a bondId.

3. Execute a bonded action

curl -s http://127.0.0.1:3000/v1/actions/execute \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "identityId": "id_abc123", "bondId": "bond_xyz", "actionType": "place-bid", "payload": { "item": "widget-42", "price": 1500 }, "exposure_cents": 1500 }'

Returns an actionId. The bond's available capacity is reduced by ceil(exposure_cents × 1.2).

4. Resolve the action

curl -s http://127.0.0.1:3000/v1/actions//resolve \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "outcome": "success", "resolverId": "id_resolver123" }'

Outcome must be one of: success, failed, or malicious. On success or failed, that action's reserved exposure is settled and released immediately. On manual malicious, the first eligible resolver vote returns a pending response with finalized: false, actionStatus: "open", maliciousVotes: 1, and maliciousVotesRequired: 2; the second distinct eligible resolver vote finalizes the slash. Sweeper auto-slash on expired actions remains immediate.

Common Errors

| Error | Cause | Fix | |---|---|---| | INVALID_SIGNATURE | Missing signature headers, stale timestamp, or signature doesn't match the signed payload | Verify you're signing sha256(nonce + method + path + timestamp + JSON.stringify(body)) with the correct private key and a fresh timestamp | | MISSING_NONCE | x-nonce header is missing | Send a fresh nonce on every POST request | | DUPLICATE_NONCE | Same nonce reused by the same identity | Generate a fresh UUID for every request | | DUPLICATE_MALICIOUS_VOTE | Same resolver tried to cast a second malicious vote for the same open action | Use a different eligible resolver identity for the second malicious vote | | TIER_BOND_CAP_EXCEEDED | Bond amount exceeds identity's trust tier cap | Build reputation with successful resolutions to unlock higher tiers | | INSUFFICIENT_BOND_CAPACITY | Bond doesn't have enough remaining capacity | Lock a larger bond or resolve outstanding actions to free capacity | | RATE_LIMIT_EXCEEDED | More than 10 executes in 60 seconds for this identity | Wait and retry, or spread actions across a longer window | | IDENTITY_BANNED | Identity has been banned (manually or after 3 malicious resolutions) | Contact the operator or use a different identity | | SERVER_MISCONFIGURED | Auth key not set and AGENTGATE_DEV_MODE is not true | Set the missing auth key or set AGENTGATE_DEV_MODE=true for local dev |

For the full security posture, see the [Threat Model](docs/threat-model.md).


Core Concepts

Identity

  • Ed25519 public key (raw 32-byte base64)
  • All state-changing endpoints require signed requests — including identity registration itself, which requires proof-of-possession (the caller must sign the request with the private key matching the public key being registered)
  • Public key uniqueness enforced at the database level — duplicate identity registration is rejected with 409 DUPLICATE_IDENTITY
  • Replay protection via timestamp validation (60-second window, 5-second future tolerance) AND nonce store (duplicate rejection per identity)
  • Named agent support: set AGENTGATE_AGENT_NAME env var to create separate identity files per agent (e.g., agent-identity-trader.json)

Signed message format: sha256(nonce + method + path + timestamp + JSON.stringify(body))

Required headers: x-agentgate-timestamp, x-agentgate-signature, x-nonce

Reusable Bond Model

Bonds are not single-use. Each bond represents reusable execution capacity.

  • Capacity rule: effective exposure = ceil(declared_exposure × 1.2)
  • Constraint: `outstandingexposurecents + effective_exposure /resolve \

-H 'content-type: application/json' \ -H 'x-agentgate-key: YOURKEY' \ -H "x-agentgate-timestamp: $TIMESTAMP" \ -H "x-agentgate-signature: $SIGNATURE" \ -H "x-nonce: $(uuidgen)" \ -d '{ "outcome": "yes", "resolverId": "idabc123" }'


**MCP tools:** `create_market` and `resolve_market` expose the same flow to Claude Desktop.

The dashboard shows a live Markets table with status color-coding (open → amber, resolved → green).

---

## Dashboard

AgentGate includes a real-time HTML dashboard at http://127.0.0.1:3000/dashboard. It shows:

- Summary bar with identity, bond, action, and market counts
- Per-identity reputation scores with color coding and trust tier labels (Tier 1 New / Tier 2 Established / Tier 3 Trusted)
- Tables for bonds, actions, identities, and markets with truncated IDs and status indicators
- Agent names displayed per identity (for multi-agent setups)
- `[BANNED]` tags on banned identities

The page auto-refreshes every 5 seconds. The server must be running.

---

## Health Check

GET /health


Returns `200 OK` with `{ "status": "ok", "timestamp": "" }`. No authentication required — designed for external uptime monitors (e.g., UptimeRobot).

---

## Running Locally

**Install:**

npm install


**Start server:**

npm run restart


This kills any old server process on port 3000 and starts fresh. Fastify REST API runs at http://127.0.0.1:3000, MCP HTTP server at http://127.0.0.1:3001/mcp, dashboard at http://127.0.0.1:3000/dashboard. The sweeper, nonce cleanup, and bucket cleanup logs appear every 60 seconds. Database file is created automatically at `data/agentgate.sqlite` on first run, with automatic backups to `data/backups/` on each startup (keeps the 5 most recent).

**Run tests:**

npm run test


123 tests across 12 test suites (API, MCP integration, prediction markets, sweeper, red team, outbound HTTP, dashboard, adapter).

---

## Security

### Nonce Replay Protection

All POST endpoints require an `x-nonce` header. The server stores each nonce per identity and rejects duplicates with a `409 DUPLICATE_NONCE` response. The `AgentAdapter` generates UUID nonces automatically. Expired nonces (older than 5 minutes) are cleaned up every 60 seconds alongside the sweeper. This provides replay protection on top of the 60-second timestamp window.

### MCP Endpoint Authentication

The MCP HTTP endpoint (port 3001) is protected by a shared-secret header.

- Set `AGENTGATE_MCP_KEY` in your `.env` file (loaded automatically via dotenv on startup)
- If the key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests are rejected with `500 SERVER_MISCONFIGURED`
- If the key is **not set** and `AGENTGATE_DEV_MODE=true`, auth is skipped (suitable for local dev)
- If the key **is set**, any request to `/mcp` without a matching `x-agentgate-key` header receives a `401 UNAUTHORIZED` response

`.env` entry:

AGENTGATEMCPKEY=your-long-random-secret


### REST API, Admin & Dashboard Authentication

Auth is split into three independent environment variables, each protecting a different surface:

| Variable | Protects | How it's checked |
|---|---|---|
| `AGENTGATE_REST_KEY` | All non-admin POST routes (bonds, actions, markets) | `x-agentgate-key` header |
| `AGENTGATE_ADMIN_KEY` | Admin endpoints (`/admin/ban-identity`, `/admin/unban-identity`) | `x-agentgate-key` header |
| `AGENTGATE_DASHBOARD_KEY` | Dashboard (`/dashboard`) | HTTP Basic Auth (username `admin`, password = key value) |

- **Auth is required by default.** If a key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests to that surface are rejected with `500 SERVER_MISCONFIGURED`
- If `AGENTGATE_DEV_MODE=true`, missing keys are allowed and auth is skipped for that surface (suitable for local dev)
- If a key **is set**, requests without a valid credential receive `401 UNAUTHORIZED`
- Each key can be rotated independently without affecting the others

AGENTGATEDEVMODE=true # skip auth enforcement for local dev (default: not set — auth required) AGENTGATERESTKEY=your-rest-secret AGENTGATEADMINKEY=your-admin-secret AGENTGATEDASHBOARDKEY=your-dashboard-secret


### Identity Governance

Operators can ban and unban identities via the admin API. Banned identities receive `403 IDENTITY_BANNED` on all `lockBond` and `executeAction` calls.

```bash
# Ban an identity
curl -s http://127.0.0.1:3000/admin/ban-identity \
  -H 'content-type: application/json' \
  -H 'x-agentgate-key: YOUR_KEY' \
  -d '{ "publicKey": "" }'

# Unban an identity
curl -s http://127.0.0.1:3000/admin/unban-identity \
  -H 'content-type: application/json' \
  -H 'x-agentgate-key: YOUR_KEY' \
  -d '{ "publicKey": "" }'

Auto-ban: an identity is automatically banned after 3 malicious action resolutions. The trigger logs an identity_auto_banned security event.

Security Event Logging

All security-relevant events are logged as structured JSON to stderr with an event field for easy filtering:

| event | Trigger | |---|---| | auth_failed | Wrong or missing x-agentgate-key on REST or MCP | | signature_failed | Missing headers, stale timestamp, or bad Ed25519 signature | | duplicate_nonce | Same nonce reused by the same identity | | bond_slashed | Action resolved as malicious (via API or sweeper) | | identity_auto_banned | Identity automatically banned after 3 malicious resolutions | | outbound_blocked | market.http action blocked by allowlist or protocol check |

Each entry includes relevant context: identityId (truncated), endpoint, reason, and requestId where available.


Security Hardening

AgentGate v0.2.0 was put through a structured red team process before release: 20 adversarial attack scenarios written as automated tests across 5 phases.

Invariant validator — every attack test ends by calling validateInvariants(db), which runs 8 SQL assertions against the live database: bond amounts never go negative, outstanding exposure never exceeds bond capacity, settlement is always consistent, and nonces are never duplicated. Any state corruption — however subtle — causes an immediate test failure with a precise diagnostic.

Attack phases:

| Phase | Focus | Tests | Findings | |---|---|---|---| | 1 | Bond/exposure math | 6 | Fixed: slashed_cents not written to DB on malicious resolution; negative exposure_cents bypassed service-layer guard | | 2 | Sweeper edge cases | 3 | Confirmed: resolve/sweep race is safe (SQLite serialization); double-slash prevented by open-action query | | 3 | Replay attacks | 4 | Confirmed: nonce check catches replays even within the 60-second timestamp window; parallel duplicate nonce via Promise.all safely rejected | | 4 | SQLite concurrency | 2 | Confirmed: better-sqlite3 synchronous transactions serialize concurrent requests correctly | | 5 | Outbound HTTP | 9 | Fixed: redirect bypass SSRF (allowlisted host could 302 to non-allowlisted target); IPv6 bracket allowlist bug ([::1] vs ::1) |

**Total: 3 logic bugs fixed, 1 SSRF vulnerability fixed, 29 red team

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.