Quickstart¶
Five minutes from install to your first proxied tool call.
Install the Binary¶
$ curl -sSf https://raw.githubusercontent.com/samanthaci/arbiter-mcp-firewall/main/install.sh | sh
This downloads the latest Arbiter binary for your platform (Linux or macOS, amd64 or arm64), verifies its SHA256 checksum, and installs it to ~/.arbiter/bin. To pin a version:
$ ARBITER_VERSION=v0.5.0 curl -sSf https://raw.githubusercontent.com/samanthaci/arbiter-mcp-firewall/main/install.sh | sh
Or: Run with Docker Compose¶
If you prefer Docker, this gets a full gateway running with a mock MCP server and Keycloak as the identity provider.
Prerequisites¶
Docker and Docker Compose v2
curland optionallyjqfor readability
$ git clone https://github.com/samanthaci/arbiter-mcp-firewall.git
$ cd arbiter
$ docker compose up --build -d
This starts four services:
Service |
Port |
Role |
|---|---|---|
Arbiter proxy |
8080 |
Gateway; where agents send MCP traffic |
Arbiter admin |
3000 |
Lifecycle API (agent registration, sessions, tokens) |
mcp-echo |
8081 |
Mock MCP upstream that echoes requests back |
Keycloak |
9090 |
OAuth 2.0 identity provider |
Wait for everything to come up:
$ docker compose ps
Verify the Proxy¶
$ curl http://localhost:8080/health
OK
If you get OK, the proxy is running and connected to the upstream.
Register an Agent¶
Every agent needs to be registered before it can make tool calls through Arbiter. Registration happens through the admin API:
$ curl -s -X POST http://localhost:3000/agents \
-H "Content-Type: application/json" \
-H "x-api-key: arbiter-dev-key" \
-d '{
"owner": "user:alice",
"model": "gpt-4",
"capabilities": ["read", "write"],
"trust_level": "basic"
}' | jq .
You’ll get back an agent ID and a short-lived JWT:
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Save both. You’ll need them in the next steps.
$ export AGENT_ID="550e8400-e29b-41d4-a716-446655440000"
Create a Task Session¶
Sessions scope what an agent can do during a specific task. They carry a declared intent, a tool whitelist, a time limit, and a call budget:
$ curl -s -X POST http://localhost:3000/sessions \
-H "Content-Type: application/json" \
-H "x-api-key: arbiter-dev-key" \
-d '{
"agent_id": "'$AGENT_ID'",
"declared_intent": "analyze transactions and generate reports",
"authorized_tools": ["query_transactions", "generate_risk_report"],
"time_limit_secs": 1800,
"call_budget": 50
}' | jq .
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
$ export SESSION_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Send a Proxied MCP Request¶
Now send an actual MCP tool call through the proxy. The request includes the agent ID and session ID as headers:
$ curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "x-agent-id: $AGENT_ID" \
-H "x-arbiter-session: $SESSION_ID" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "query_transactions",
"arguments": { "account": "ACC-2847", "period": "2025-Q4" }
}
}' | jq .
The echo server returns the request back as a JSON-RPC response. Behind the scenes, Arbiter validated the session, checked the tool whitelist, evaluated policies, ran anomaly detection, and logged an audit entry.
Check the Audit Log¶
$ docker compose exec arbiter cat /var/log/arbiter/audit.jsonl | jq .
Each line is a structured JSON entry with the request ID, agent ID, tool called, authorization decision, latency, and any anomaly flags.
Check Metrics¶
$ curl -s http://localhost:8080/metrics
Prometheus-format metrics: requests_total (by decision), tool_calls_total (by tool), and request duration histograms.
Try Getting Denied¶
Send a request for a tool that isn’t on the session’s whitelist:
$ curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "x-agent-id: $AGENT_ID" \
-H "x-arbiter-session: $SESSION_ID" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "delete_account",
"arguments": { "account": "ACC-2847" }
}
}' | jq .
You’ll get a 403 with an error explaining the tool isn’t authorized for this session. That’s deny-by-default at work.
Explore the Admin API¶
# List all registered agents
$ curl -s http://localhost:3000/agents \
-H "x-api-key: arbiter-dev-key" | jq .
# Get details for a specific agent
$ curl -s http://localhost:3000/agents/$AGENT_ID \
-H "x-api-key: arbiter-dev-key" | jq .
# Issue a fresh short-lived token
$ curl -s -X POST http://localhost:3000/agents/$AGENT_ID/token \
-H "x-api-key: arbiter-dev-key" \
-H "Content-Type: application/json" \
-d '{"expiry_seconds": 300}' | jq .
Stop the Stack¶
$ docker compose down -v
Next Steps¶
You’ve seen Arbiter running. Now go deeper:
Your First Policy: write your first authorization policy from scratch
Your First Session: understand session scoping, budgets, and intent
Architecture: how the middleware chain works under the hood