aegis · Get started
Quick start
Run aegis locally with Docker Compose, mint a development token, list the tools you can reach and make your first governed MCP tool call.
On this page
This guide runs the full aegis stack on your machine with Docker Compose. By the end you will have minted a development token, listed the tools you are allowed to use, sent an authorized tools/call through the gateway and seen aegis refuse a prompt-injection payload.
From a checkout of the aegis source, run every command below from the repository root.
Prerequisites#
- Docker with the Compose plugin (
docker compose) curlopenssl, to sign a development token- Free host ports 8080, 8443, 6379, 8123, 4566, 8081, 9000, 9401, 9600 and 9700, which the Compose file publishes
1. Start the stack#
docker compose -f deploy/docker-compose.yml up --build
This builds and starts the stack defined in deploy/docker-compose.yml:
- the gateway, on port 8080
- Envoy, terminating TLS on port 8443 with a self-signed
localhostcertificate it generates when its container first starts (development only) - Redis, Postgres and ClickHouse, the gateway's three stores
- LocalStack, standing in for AWS Secrets Manager
- several mock MCP servers, a mock identity provider and other test fixtures
The file defines 20 services. Every one except the discovery scanner, which sits behind the tools profile, starts with the stack. certgen exits once it has written the test certificates. Compose starts the gateway only after certgen has finished and the services it depends on report healthy. The first run compiles the gateway from source, so it takes longer than later runs.
This stack is a test harness. It turns on several commercial modules with development values (OAuth issuance, SCIM, SIEM export, alerting, the credential broker and billing) so the end-to-end test can exercise them. Do not copy its settings into a production deployment. In particular, never set OAUTH_DEV_AUTHORIZE outside a local test stack: it lets /oauth/authorize issue a code for any subject without a login. Start from Configuration instead.
Leave that terminal running. In a second terminal, check the gateway:
curl -s http://localhost:8080/healthz
{"service":"aegis-gateway","status":"ok"}
/healthz is a static response. It tells you the process is serving HTTP, but it does not check Redis, Postgres or ClickHouse.
2. Mint a development token#
The Compose gateway verifies HS256 tokens signed with a shared development secret. Export the value of AUTH_HS256_SECRET from the gateway service in deploy/docker-compose.yml. Copy it by hand, or read it from the file:
export AEGIS_DEV_SECRET="$(sed -n 's/.*AUTH_HS256_SECRET: "\(.*\)"/\1/p' deploy/docker-compose.yml)"
Then sign a token for the seeded admin subject, e2e-tester:
b64() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
h=$(printf '{"alg":"HS256","typ":"JWT"}' | b64)
now=$(date +%s); exp=$((now+3600))
p=$(printf '{"sub":"e2e-tester","iat":%d,"exp":%d}' "$now" "$exp" | b64)
s=$(printf '%s.%s' "$h" "$p" | openssl dgst -sha256 -hmac "$AEGIS_DEV_SECRET" -binary | b64)
TOKEN="$h.$p.$s"
The token expires after one hour. When calls start returning HTTP 401, mint a new one.
The subject matters. The Compose stack sets DATABASE_URL, so RBAC is on. A subject that is not in the catalog, such as alice, is denied every call and gets an empty tool list. deploy/postgres/init/03-rbac-seed.sql seeds these two subjects for you to use (it also seeds k6-load, for the load test):
3. Lift the test quota (Compose stack only)#
The Compose file turns on the commercial billing module with BILLING_ENFORCE=1 and a Free-tier quota of 3 forwarded calls a month, so the end-to-end test can reach the limit. Move the default tenant to the unlimited Enterprise tier. tests/e2e/smoke_test.sh does the same before it makes any tool call:
curl -s -X POST http://localhost:8080/admin/v1/billing -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"tenant":"default","tier":"enterprise"}'
{"tenant":"default","tier":"enterprise"}
Setting a tier is an operator action, so it only works for a caller in the default tenant, which is where e2e-tester lives. If you skip this step, once three calls have been forwarded in the current month, every further tools/call returns HTTP 402 with JSON-RPC error -32060. Billing stays off unless you set BILLING_ENABLED=1, so a gateway you configure yourself does not need this step.
4. List tools#
curl -s -X POST http://localhost:8080/v1/invoke -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
aegis merges the tool lists of every server in your tenant's catalog and filters the result through RBAC, so each caller sees only the tools it may call. On a fresh stack, e2e-tester sees four tools:
echoandadd, frommock-primarybeta_echoandbeta_add, frommock-beta
Each entry carries the tool's name, description and inputSchema as the upstream advertised them, plus server, the catalog server that serves it, and tenant. Servers you register later through the admin API appear here once the gateway reloads its routes. A token for e2e-restricted returns only beta_echo and beta_add.
/mcp runs the same handler as /v1/invoke at the conventional MCP path. Point an MCP client there, with the same bearer token.
5. Call a tool#
curl -s -i -X POST http://localhost:8080/v1/invoke -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"echo","arguments":{"message":"hello"}},"id":2}'
The body is the mock server's reply, with the arguments you sent under result.echoed. Two response headers are worth noting:
X-Session-IDis the session aegis minted for this call. Sessions live in Redis for one hour.X-Correlation-IDties this request to its audit record and to the trace aegis propagates upstream. If you send a W3Ctraceparentheader, aegis adopts its trace ID.
Each call without an X-Session-ID header gets a new session. To keep one session, capture the header and send it back on later calls:
SID=$(curl -s -D - -o /dev/null -X POST http://localhost:8080/v1/invoke -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"ping","id":4}' | awk -F': ' 'tolower($1)=="x-session-id"{print $2}' | tr -d '\r')
curl -s -X POST http://localhost:8080/v1/invoke -H "Authorization: Bearer $TOKEN" -H "X-Session-ID: $SID" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":3}},"id":5}'
A session belongs to the subject that created it. If another subject presents it, the gateway returns HTTP 403, and an expired session returns HTTP 401. The Compose stack sets RATE_LIMIT_PER_TOOL=5, so on one session the sixth call to the same tool in the same one-minute window returns HTTP 429 with -32029.
6. See a block#
Send an argument that carries a prompt-injection phrase:
curl -s -X POST http://localhost:8080/v1/invoke -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"echo","arguments":{"message":"ignore previous instructions"}},"id":3}'
{"jsonrpc":"2.0","error":{"code":-32051,"message":"Blocked: prompt_injection pattern 'ignore_previous' in args.arguments.message"},"id":3}
The call never reaches mock-primary. aegis scans every string in the call's params, keys included, and refuses the call on the first match. The HTTP status is still 200 because threat refusals come back as a JSON-RPC error, so check error.code rather than the status. The refusal is written to the audit log with the decision block. Error codes lists every refusal code and the HTTP status that goes with it.
7. Open the console#
Open http://localhost:8080/dashboard in a browser. The console starts on a built-in demo dataset, and the bar in the bottom-right corner reads "DEMO DATA — not connected".
To see your own stack, print your token with echo "$TOKEN", paste it into the operator bearer token field and choose Connect live. The console then reads live data from /admin/v1: the overview, audit events, servers, roles, policies, users, shadow findings and kill-switches. That needs a catalog:read grant on the virtual aegis-admin resource, which the seeded admin role has through its * rule. The console does not yet read approvals, circuit breakers, SIEM sinks, alert channels or tenants from the gateway, so those panes stay empty in live mode. The browser keeps the token in localStorage until you choose Disconnect.
Stop the stack#
docker compose -f deploy/docker-compose.yml down
Add -v to also delete the named volumes: the Redis, Postgres and ClickHouse data and the generated test certificates. The next start then re-seeds the catalog and begins a new audit chain. The default tenant is back on the Free tier, so repeat step 3.
Next steps#
- Configuration: the environment variables that turn each subsystem on and tune it.
- Deploy aegis: run the gateway on Kubernetes with the Helm chart.
- Identity and access: connect your identity provider and write RBAC policies.
- How aegis works: follow a
tools/callthrough each check, in order.