MCP-native least-agency gateway for tool-using AI agents.
This MVP implements the customer-support account recovery demo from docs/:
- Scan an MCP-shaped tool manifest.
- Run a deterministic unsafe baseline agent — or a live/scripted agent loop where every tool call is mediated by the gateway.
- Route the same workflow through AgentGate policy enforcement:
allow,allow_with_transform(redaction/limit/arg-narrowing),require_approval,deny, andquarantine. - Approve/deny gated calls and resume the original tool call when approved.
- Log runtime decisions and audit events; export them as OpenTelemetry (OTLP/JSON) traces.
- Run baseline/protected eval suites against final world state (security failures are distinguished by scenario tag).
- Export blocked incidents as regression YAML and re-run them through the eval runner (Story 6.4 round-trip).
- Edit or upload the active policy from the dashboard or API.
- Drive everything from a CLI and gate CI with the protected eval.
pnpm install
pnpm db:seed
pnpm upstream:local
pnpm devOpen http://localhost:3000.
Run pnpm upstream:local in a second terminal. It starts the local upstream
executor that the Live Gateway defaults call at http://127.0.0.1:4000/tool
and http://127.0.0.1:4000/mcp.
The first screen is Live Gateway. It is the real execution surface:
- Enter a tool executor URL.
- Select a registered tool.
- Edit the JSON arguments.
- Set identity and approval state for high-risk calls.
- Click
Send Real Tool Call. - Inspect whether AgentGate allowed the call and whether the upstream was called.
- Resolve pending runtime approvals in the Approval Queue.
The Demo Lab tab is only for the seeded hackathon scenario and eval loop.
For the verified production preview:
pnpm build
pnpm startpnpm scan:tools
pnpm demo:baseline
pnpm demo:protected
pnpm eval:run --mode=baseline
pnpm eval:run --mode=protected
pnpm regression:export --decision=dec_001The protected path should show:
auth.reset_passwordreturnsrequire_approval.email.send_externalreturnsdeny.password_reset_performedremainsfalse.- External outbox stays empty.
- A pending approval is created.
The app exposes both Next API routes and canonical rewrites:
POST /v1/tools/scanPOST /v1/policy/evaluateGET /v1/policy·POST /v1/policy(upload/edit the active policy YAML)POST /v1/tool-callPOST /v1/mcpPOST /v1/approvals(approve/deny and resume the original call)POST /v1/evals/runPOST /v1/regressions/exportPOST /v1/regressions/run(re-run exported regression YAML — Story 6.4)GET /v1/telemetry(OTLP/JSON trace export of every decision)GET /v1/state
All request bodies are validated with zod; malformed input returns a 400 with structured issues. Mutating endpoints serialize their read-modify-write through an in-process lock with atomic file replacement. Set AGENTGATE_API_TOKEN to require Authorization: Bearer <token> (or x-agentgate-token) on mutating routes; unset leaves the demo open.
Demo mode calls the local fake world inside AgentGate. External mode calls a separate upstream process. For local verification, start the included executor:
pnpm upstream:localThen call AgentGate with execution_mode: "external".
curl -s http://localhost:3000/v1/tool-call \
-H 'Content-Type: application/json' \
-d '{
"execution_mode": "external",
"upstream_url": "http://localhost:4000/tool",
"tool_call": {
"name": "crm.lookup_customer",
"args": {"email": "real_customer@example.com"}
},
"input_email_trust_level": "untrusted"
}'AgentGate evaluates policy first. Only allow and allow_with_transform calls are
forwarded. Denied, pending approval-gated, and quarantined calls do not reach the
upstream. High-risk calls such as password reset can forward after the required
runtime state is present, for example identity_verification_status: "verified"
and approval_status: "approved".
The upstream receives:
{
"tool": "crm.lookup_customer",
"args": {"email": "real_customer@example.com"},
"decision": {
"decision_id": "dec_001",
"matched_policy_ids": ["support.allow_customer_lookup_for_active_ticket"],
"risk_score": 25,
"reason": "Customer lookup is allowed for active support tickets."
}
}Allowed upstream origins default to common localhost ports. For non-local upstreams:
AGENTGATE_ALLOWED_UPSTREAM_ORIGINS=https://tools.example.com pnpm startYou can also set a default tool executor:
AGENTGATE_UPSTREAM_TOOL_URL=http://localhost:4000/tool pnpm startPoint an MCP Streamable HTTP client at:
http://localhost:3000/v1/mcp
AgentGate handles initialize, tools/list, and tools/call.
To forward allowed tools/call requests to a real MCP server:
AGENTGATE_MCP_UPSTREAM_URL=http://localhost:4000/mcp pnpm startBlocked MCP tool calls return a JSON-RPC error with the AgentGate decision in
error.data.
A single CLI wraps the gateway for scripts and CI:
pnpm agentgate scan # risk-scan the tool manifest
pnpm agentgate eval --mode protected # run the eval suite
pnpm agentgate regression:export # export the latest blocked incident
pnpm agentgate regression:run # re-run exported regression YAML (Story 6.4)
pnpm agentgate policy:validate policy.yaml
pnpm agentgate agent --injection # drive an agent loop through the gateway
pnpm agentgate telemetry # print OTLP/JSON traces
pnpm agentgate ci-gate --mode protected # exit non-zero if the release is BLOCKEDscripts/agentgate-cli.ts is also exposed as the agentgate bin.
runAgent() lets a real or scripted agent propose tool calls; each one is mediated by the
gateway and the decision is fed back as an observation, so the model cannot take an action the
policy forbids. The default client is deterministic (ScriptedLlmClient). Set ANTHROPIC_API_KEY
and pass --live to use the Claude Messages API (AnthropicLlmClient, with prompt caching).
.github/workflows/agentgate.yml runs typecheck, tests, build, and the AgentGate release gate.
The repo also ships a reusable composite action (action.yml) that fails a job when the protected
eval recommends block:
- uses: your-org/agentgate@v1
with:
mode: protectedThe hackathon demo uses a local file-backed runtime store at .agentgate/runtime.json
(override the directory with AGENTGATE_RUNTIME_DIR). Writes are atomic (temp file + rename)
and serialized through an in-process lock. It preserves the documented entities for the local
loop: tools, policy, scans, decisions, audit events, eval runs/results, world state, approvals,
quarantine status, regression exports, and regression runs.