Protocol Reference
clausgap-proxy supports three wire protocols in a single binary. Each listener is declared in the listeners: array of proxy.yaml and shares the same policy engine, bundle cache, and event buffer.
HTTP listener
The HTTP listener is a reverse proxy that intercepts REST API calls, webhook deliveries, and tool invocations from your agents. It evaluates every inbound request against your policy bundle before forwarding to the upstream.
Agents identify themselves with an X-Clausgap-Context header (HMAC-SHA256 signed JWT carrying agent_role, session_id, confidence, etc.). See Context Signing for details.
Tool names are declared in routes.yaml and referenced in policy rules as allow call tool "tool_name".
Configuration
listeners:
- protocol: http
addr: ":8443"
http:
routes_file: "./routes.yaml"
hmac_secret: "$HMAC_SECRET"
event_buffer_size: 10000 # optional, default 10000
static_roles: # optional — no SDK needed
- source_ip: "10.0.0.5"
agent_role: "internal_runner"
upstream_headers: # optional — the tool's own credentials
X-Api-Key: "$UPSTREAM_API_KEY"
# legacy_context_until: "2026-12-31T00:00:00Z" # temporarily accept v1 SDK headers
# tls: # optional in-process TLS (ListenAndServeTLS)
# cert_file: "/etc/clausgap/tls/http.crt"
# key_file: "/etc/clausgap/tls/http.key"
# client_ca_file: "/etc/clausgap/tls/ca.crt" # require client certs (mTLS)
# min_version: "1.2"| Field | Type | Description |
|---|---|---|
| routes_file* | string | Path to routes.yaml |
| hmac_secret* | string | Shared secret for X-Clausgap-Context HMAC verification. Headers must be v2 — signed for one request (method, path, body) and usable once |
| event_buffer_size | int | In-memory event buffer capacity before drain. Default: 10000 |
| static_roles | []object | Map source IPs to agent roles — allows policy governance without SDK integration |
| upstream_headers | map | Headers added to every forwarded request — how a tool gets its own credentials. Values support $ENV expansion. The agent's Clausgap credentials (X-Clausgap-Context, and the Authorization / X-API-Key header that carried a keyless credential) are always removed before forwarding. |
| legacy_context_until | string | RFC3339 time until which v1 context headers (not bound to a request) are also accepted, while agents upgrade their SDK. Unset: v1 is refused. Once the time passes the proxy refuses to start until it's removed. |
| tls | object | In-process TLS (ListenAndServeTLS). Fields: cert_file, key_file, client_ca_file (set to require + verify client certs — mTLS), min_version ("1.2" default | "1.3"). |
Supported targets
Any HTTP or HTTPS upstream: OpenAI, Anthropic, custom tool microservices, SaaS APIs (Salesforce, HubSpot, Zendesk), and MCP servers via the protocol: jsonrpc route option.
Policy rules
allow call tool "web_search"
deny call tool "delete_records"
escrow when confidence < 0.8
escrow when tool = "send_email"PostgreSQL listener
The PostgreSQL listener intercepts the PG wire protocol (v3). Point your agent's database connection at the proxy instead of the upstream database — no application code changes required.
Agents connect like any PostgreSQL client (psql, drivers, BI tools): the password is a key from the keys list, which sets the tenant and the agent_role; the user must be that role (any other user name is refused, so a client can't pick its own role); and application_name (if set) becomes the agent_id. The proxy then evaluates each SQL statement against your policy bundle before forwarding.
The password is exchanged the standard PostgreSQL way, in cleartext within the connection — configure tls on the listener so it's encrypted on the wire. The listener currently supports the simple query protocol (what psql uses); drivers that send parameterized queries through the extended protocol aren't supported yet.
To guarantee table, column and row policy is enforced, the listener only runs query shapes it can fully check: single-table statements without UNION / INTERSECT / EXCEPT, joins, subqueries, CTEs, comments or dollar-quoted strings. Columns a role can't read can't be used in WHERE, ORDER BY or GROUP BY, and computed columns (functions, expressions) aren't accepted on tables with column rules — casts and aliases of readable columns are fine. Anything else is refused with an explanation rather than run.
Configuration
listeners:
- protocol: pg
addr: ":5433"
pg:
upstream_host: "postgres"
upstream_port: 5432
upstream_dbname: "mydb"
upstream_user: "clausgap_proxy"
upstream_password: "$PG_PASSWORD"
event_buffer_size: 10000
metrics_addr: ":9091" # Prometheus metrics for this listener
keys: # Client credential → tenant + role
- key_id: "$PG_CLIENT_KEY_1" # presented as the PG password
tenant_id: "my-org"
agent_role: "analyst" # connect with user=analyst
# tls: # optional in-process TLS
# cert_file: "/etc/clausgap/tls/pg.crt"
# key_file: "/etc/clausgap/tls/pg.key"
# client_ca_file: "/etc/clausgap/tls/ca.crt" # require client certs (mTLS)
# min_version: "1.2"| Field | Type | Description |
|---|---|---|
| upstream_host* | string | PostgreSQL host |
| upstream_port | int | PostgreSQL port. Default: 5432 |
| upstream_dbname* | string | Database name to connect to on the upstream |
| upstream_user | string | Username for upstream connection. Default: "postgres" |
| upstream_password | string | Password for upstream connection |
| event_buffer_size | int | In-memory event buffer capacity. Default: 10000 |
| metrics_addr | string | Prometheus metrics bind address. Default: ":9091" |
| keys | []KeyEntry | Client credentials. Each entry: key_id (the value the client presents as the PostgreSQL password), tenant_id, and agent_role (the role the key grants; the connection's user must match it). agent_role is required: the listener refuses to start with a key that has none. |
| tls | object | In-process TLS. Fields: cert_file, key_file, client_ca_file (set to require + verify client certs — mTLS), min_version ("1.2" default | "1.3"). The listener answers 'S' to the PG SSLRequest and upgrades the connection in place. With tls set, plain-text logins are refused: clients must connect with sslmode=require or verify-full. |
Supported targets
PostgreSQL 12+, Amazon Redshift, Google AlloyDB, ClickHouse (PG-compatible mode), CockroachDB, Neon, Supabase.
Policy rules
# Table-level read/write control
allow read on table "orders" columns ["id", "status", "total"]
deny read on table "users" columns ["ssn", "credit_card"]
deny write on all tables
# Row-level filter (injected as WHERE clause)
filter on table "orders" where "region = 'EMEA'"Column masking returns NULL for denied columns rather than blocking the query, so agents can still process allowed columns without code changes.
MCP listener
The Model Context Protocol (MCP) listener is an HTTP proxy that intercepts JSON-RPC 2.0 traffic between LLM runtimes and MCP tool servers. It only intercepts tools/call requests — all other methods (tools/list, resources/read, prompts/get) pass through without policy evaluation.
Every POST body is parsed in full and the parsed message is what gets forwarded, so the upstream always runs exactly what was evaluated. Bodies that can't be parsed, are over 1 MB or are compressed are refused, as is a tools/call inside a JSON-RPC batch.
Agent identity
A caller identifies as a role by sending a signed X-Clausgap-Context header (v2 — see context signing), verified with the listener's hmac_secret. Callers without one are evaluated as the listener's agent_role. The unsigned X-Agent-Role header is ignored — anyone could set it.
On startup, if discover_tools: true is set, the proxy calls tools/list on the upstream and logs the discovered tool inventory. This also pre-warms the tool catalogue for policy matching.
Configuration
listeners:
- protocol: mcp
addr: ":3000"
mcp:
upstream: "http://mcp-server:5000"
discover_tools: true # Call tools/list on startup
propagate_decisions: true # Add X-Clausgap-Decision: approved header
agent_role: "mcp_agent" # role for callers without a signed header
hmac_secret: "${CLAUSGAP_HMAC_SECRET}" # verifies signed context headers
# tls: # optional in-process TLS (ListenAndServeTLS)
# cert_file: "/etc/clausgap/tls/mcp.crt"
# key_file: "/etc/clausgap/tls/mcp.key"
# client_ca_file: "/etc/clausgap/tls/ca.crt" # require client certs (mTLS)
# min_version: "1.2"| Field | Type | Description |
|---|---|---|
| upstream* | string | MCP server base URL |
| discover_tools | bool | Call tools/list on startup to log the tool inventory. Default: false |
| propagate_decisions | bool | Stamp X-Clausgap-Decision: approved on forwarded requests to prevent double-evaluation by downstream HTTP/PG listeners. Default: false |
| agent_role | string | Role for callers that don't send a signed context header. If unset, those callers are refused with 403. |
| hmac_secret | string | Verifies signed X-Clausgap-Context headers — the only way for a caller to identify as a specific role. Supports $ENV expansion. |
| tls | object | In-process TLS (ListenAndServeTLS). Fields: cert_file, key_file, client_ca_file (set to require + verify client certs — mTLS), min_version ("1.2" default | "1.3"). |
Decision outcomes
The MCP listener maps Clausgap verdicts to MCP-native responses:
Proxied to upstream, response returned as-is
JSON-RPC error object: {"code": -32603, "message": "denied by policy", "data": {"decision_id", "reason"}}
JSON-RPC result: {"status": "held", "escrow_id", "poll_url"}
Only ALLOW/AllowWithMasking, DENY, and ESCROW have an explicit mapping. Any other decision — including a rate-limited request — currently falls through to the default case and is proxied to the upstream anyway, rather than blocked. Don't rely on rate_limit being enforced over MCP yet.
Policy rules
MCP tool names are matched using the same call tool syntax as the HTTP listener:
allow call tool "read_file"
deny call tool "delete_file"
escrow when tool = "execute_code" and confidence < 0.9TLS
Every listener accepts an optional tls: block inside its protocol sub-config. TLS is terminated in-process by clausgap-proxy itself — not a sidecar — because the PostgreSQL wire protocol negotiates TLS in-band (the client sends an SSLRequest, the listener answers 'S' and upgrades the connection in place), which a generic TCP/HTTP reverse proxy cannot do. The HTTP and MCP listeners use standard ListenAndServeTLS.
| Field | Type | Description |
|---|---|---|
| cert_file* | string | PEM server certificate (chain) |
| key_file* | string | PEM private key for cert_file |
| client_ca_file | string | PEM CA bundle. When set, the listener requires and verifies a client certificate against it (mutual TLS). |
| min_version | string | Minimum TLS version: "1.2" (default) or "1.3". |
listeners:
- protocol: pg
addr: ":5433"
pg:
upstream_host: "postgres"
upstream_dbname: "mydb"
tls:
cert_file: "/etc/clausgap/tls/pg.crt"
key_file: "/etc/clausgap/tls/pg.key"
client_ca_file: "/etc/clausgap/tls/ca.crt" # optional — enables mTLS
min_version: "1.3"