CL
Clausgap
/Docs

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

HTTPdefault :8443

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

yaml
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"
FieldTypeDescription
routes_file*stringPath to routes.yaml
hmac_secret*stringShared secret for X-Clausgap-Context HMAC verification. Headers must be v2 — signed for one request (method, path, body) and usable once
event_buffer_sizeintIn-memory event buffer capacity before drain. Default: 10000
static_roles[]objectMap source IPs to agent roles — allows policy governance without SDK integration
upstream_headersmapHeaders 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_untilstringRFC3339 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.
tlsobjectIn-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

clausgap
allow call tool "web_search"
deny  call tool "delete_records"
escrow when confidence < 0.8
escrow when tool = "send_email"

PostgreSQL listener

PGdefault :5433

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

yaml
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"
FieldTypeDescription
upstream_host*stringPostgreSQL host
upstream_portintPostgreSQL port. Default: 5432
upstream_dbname*stringDatabase name to connect to on the upstream
upstream_userstringUsername for upstream connection. Default: "postgres"
upstream_passwordstringPassword for upstream connection
event_buffer_sizeintIn-memory event buffer capacity. Default: 10000
metrics_addrstringPrometheus metrics bind address. Default: ":9091"
keys[]KeyEntryClient 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.
tlsobjectIn-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

clausgap
# 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

MCPdefault :3000

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

yaml
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"
FieldTypeDescription
upstream*stringMCP server base URL
discover_toolsboolCall tools/list on startup to log the tool inventory. Default: false
propagate_decisionsboolStamp X-Clausgap-Decision: approved on forwarded requests to prevent double-evaluation by downstream HTTP/PG listeners. Default: false
agent_rolestringRole for callers that don't send a signed context header. If unset, those callers are refused with 403.
hmac_secretstringVerifies signed X-Clausgap-Context headers — the only way for a caller to identify as a specific role. Supports $ENV expansion.
tlsobjectIn-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:

ALLOW

Proxied to upstream, response returned as-is

DENY

JSON-RPC error object: {"code": -32603, "message": "denied by policy", "data": {"decision_id", "reason"}}

ESCROW

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:

clausgap
allow call tool "read_file"
deny  call tool "delete_file"
escrow when tool = "execute_code" and confidence < 0.9

TLS

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.

FieldTypeDescription
cert_file*stringPEM server certificate (chain)
key_file*stringPEM private key for cert_file
client_ca_filestringPEM CA bundle. When set, the listener requires and verifies a client certificate against it (mutual TLS).
min_versionstringMinimum TLS version: "1.2" (default) or "1.3".
yaml
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"