Skip to content

Quick start

This runs InterLock and a small sample shop database with Docker Compose, then walks one agent from nothing to a governed, redacted, audited query. Everything after the first build happens in the admin console and one terminal.

You need:

  • Docker with Compose 2.24 or later (docker compose version), running.
  • git and curl.

You do not need psql (a copy inside the stack is used below), and you do not need an API key for any model provider: the optional Anthropic and Mistral keys only enable document summaries and OCR during ingestion.

Terminal window
git clone https://github.com/ContextData/interlock
cd interlock
docker compose --profile quickstart up -d --wait

The first run builds the images, which takes a few minutes. It starts the gateway, the admin console, two workers, PostgreSQL and Redis for InterLock itself, and sample-postgres, a database named shop with customers and orders tables. Every port binds to 127.0.0.1 only:

Service Address
Admin console http://127.0.0.1:9090
Gateway, HTTP and MCP http://127.0.0.1:3001
Gateway, PostgreSQL wire 127.0.0.1:5434

If one of those ports is already in use, create docker-compose.override.yml next to docker-compose.yml with other host ports, run the same up command again, and use your ports wherever this guide says 9090, 3001 or 5434:

services:
admin:
ports: !override
- "127.0.0.1:9190:9090"
gateway:
ports: !override
- "127.0.0.1:3101:3000"
- "127.0.0.1:5534:5432"

Open http://127.0.0.1:9090 and sign in as admin with the password admin. The console asks for a new password straight away (at least 12 characters) and opens nothing else until you set one.

Open Data Sources and choose + New Source. The wizard has five steps:

  1. Display name Sample shop, Connector PostgreSQL. Leave Create default source role templates ticked. Choose Next.

  2. Connection details:

    • Host sample-postgres, Port 5432, Database shop.
    • User shop_reader, Password shop-reader-dev-only.
    • TLS mode disable: the sample database has no TLS. Production refuses anything but verify-full or verify-ca.
    • Tick Allow a private network address. sample-postgres is a Compose service on a private address, and InterLock refuses private addresses unless a source opts in.

    Choose Test Connection; it reports Connected in a few milliseconds. Then choose Next.

  3. The schema preview reports Found 2 table(s) in shop and pre-ticks the columns that look like personal data, which records them as PII in the source catalog. Choose Next.

  4. Keep the cache settings. Choose Next.

  5. Review and choose Save Source.

The console opens the new source’s page. It was created as sample_shop: the ID is generated from the name, and agents use it to name the source.

The Sample shop source page, showing its ID, connector and the policy that applies to it.The Sample shop source page, showing its ID, connector and the policy that applies to it.

A source role lists what an agent granted it may do on this source. The wizard created five from the PostgreSQL templates, listed under Source Roles on the Sample shop page:

Role Allows
Read (read) list schemas, describe tables, read rows in any table, search discovery
Analyst (analyst) the same as Read, for analysis agents
Write (write) Read, plus inserting and updating rows
Owner (owner) everything
Blocked (blocked) nothing: an explicit deny

The agent in this guide gets Read. It is what an agent needs to explore and query a SQL source: interlock_describe_source needs db.table.describe and interlock_query needs db.table.select for a read. The MCP tools reference lists the action each tool needs. To narrow a role to certain tables or columns, open it and choose Edit.

Policies are checked after roles, in priority order (higher first); the first rule that matches decides, and a request no rule matches is denied. Open Policies, choose + New Policy, and create:

  • Name allow-sample-shop-reads, Priority 10.
  • Source ID sample_shop, Operation read.
  • Effect allow.

Choose Save.

Open Identities, choose + New Identity:

  • Name quickstart-agent, Agent type claude_code.
  • Under Source access, choose Sample shop and the Read role.
  • Leave Auto-generate API key ticked and choose Save.

The key is shown once. Copy it into a shell variable:

Terminal window
API_KEY=paste-the-key-here

The same statement over both protocols. First the PostgreSQL wire protocol, using the psql inside the stack’s own PostgreSQL container: the database name picks the source and the API key is the password.

Terminal window
docker compose exec -e PGPASSWORD="$API_KEY" postgres \
psql "host=gateway port=5432 user=agent dbname=sample_shop sslmode=disable" \
-c "SELECT name, email, plan FROM customers ORDER BY id LIMIT 3"
name | email | plan
-------------+------------------+------------
Ada Park | [REDACTED:EMAIL] | team
Ben Okafor | [REDACTED:EMAIL] | free
Chloe Varga | [REDACTED:EMAIL] | enterprise
(3 rows)

The email addresses left the database and were redacted on the way out. If you have psql on your machine, the same query works from there with host=127.0.0.1 port=5434.

Now over MCP:

Terminal window
curl -sS http://127.0.0.1:3001/mcp \
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-11-25' \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "interlock_query",
"arguments": {"source_id": "sample_shop", "sql": "SELECT name, email, plan FROM customers ORDER BY id LIMIT 3"}}}'

The result’s text is the same three rows as JSON, with the same [REDACTED:EMAIL] values.

Now a write the Read role does not allow, over MCP:

Terminal window
curl -sS http://127.0.0.1:3001/mcp \
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-11-25' \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "interlock_query",
"arguments": {"source_id": "sample_shop", "sql": "DELETE FROM orders WHERE id = 1"}}}'

The result has "isError": true and the text Source role denied: .... Nothing reached the database.

Open Audit & Costs. Each request is a row: the identity, the source, the protocol, whether it was allowed, and whether PII was redacted. The refused delete is there too, marked denied. Rows are written in batches, so a request can take a few seconds to appear; refresh if it is not there yet. Filter by Protocol to compare the PostgreSQL and MCP requests.

Audit and Costs: requests counted by outcome, with one denied, and the audit log below.Audit and Costs: requests counted by outcome, with one denied, and the audit log below.