For the policy YAML format, see POLICY.md.
The root command runs Intercept as a transparent proxy in front of an MCP server, enforcing policy rules on every tool call.
intercept -c <policy-file> [flags] -- <upstream-command...>
intercept -c <policy-file> [flags] --upstream <url>
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--config |
-c |
string | (required) | Path to the policy YAML file |
--state-dir |
string | ~/.intercept/state |
Directory for persistent state (SQLite database) | |
--state-dsn |
string | Shared state backend DSN (e.g. redis://host:6379/0) |
||
--state-prefix |
string | Key prefix for state counters (e.g. stripe-prod:) |
||
--state-fail-mode |
string | closed |
Behavior when state backend is unreachable: open or closed |
|
--log-level |
string | info |
Logging verbosity: debug, info, warn, error |
|
--transport |
string | (auto-detect) | Transport mode: stdio, sse, http |
|
--upstream |
string | Upstream server URL | ||
--header |
string[] | Custom headers for upstream requests (e.g. "Authorization: Bearer tok") |
||
--bind |
string | 127.0.0.1 |
Bind address for HTTP/SSE listener | |
--port |
int | 0 (auto) |
Port for HTTP/SSE listener | |
--enable-admin-api |
bool | false |
Start the local admin API for managing approvals | |
--admin-addr |
string | 127.0.0.1:9111 |
Bind address for the admin API server |
--state-dir and --state-dsn are mutually exclusive.
Intercept supports three transport modes. The mode is auto-detected from the flags provided, or can be set explicitly with --transport.
Stdio with child process (activates when a command follows --):
Intercept launches the upstream MCP server as a child process and proxies stdin/stdout. JSON-RPC messages are intercepted and evaluated. Signals (SIGTERM, SIGINT) are forwarded to the child process. If the child exits, Intercept exits with the same status.
intercept -c policy.yaml -- npx -y @modelcontextprotocol/server-githubStdio with upstream URL (default when --upstream is provided):
Intercept reads JSON-RPC from stdin and forwards requests over HTTP to the upstream MCP server. Responses are written back to stdout as newline-delimited JSON. This is the default when --upstream is provided, allowing MCP clients to spawn intercept as a command while the upstream is a remote HTTP server.
intercept -c policy.yaml --upstream https://mcp.stripe.com
intercept -c policy.yaml --upstream https://mcp.stripe.com --header "Authorization: Bearer tok"HTTP/SSE (activates with --transport http or --transport sse):
Intercept runs a local HTTP server and proxies requests to the upstream MCP server URL. On startup, it prints the local endpoint URL to stderr (e.g., Intercept listening on http://127.0.0.1:4821). When --port is 0, an available port is selected automatically.
intercept -c policy.yaml --transport http --upstream https://mcp.example.com
intercept -c policy.yaml --transport sse --upstream https://mcp.example.com/sse --port 8080# Stdio proxy with debug logging
intercept -c policy.yaml --log-level debug -- npx -y @modelcontextprotocol/server-github
# Stdio bridge to remote HTTP server with auth header
intercept -c policy.yaml --upstream https://mcp.stripe.com --header "Authorization: Bearer tok"
# HTTP proxy with explicit port
intercept -c policy.yaml --transport http --upstream https://mcp.example.com --port 9000
# Shared Redis state with key prefix
intercept -c policy.yaml --state-dsn redis://localhost:6379/0 --state-prefix github: -- npx server-githubConnects to an MCP server, discovers all available tools, and generates a policy scaffold.
intercept scan [flags] -- <upstream-command...>
intercept scan [flags] --upstream <url>
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--upstream |
string | Upstream server URL | ||
--header |
string[] | Custom headers for upstream requests (e.g. "Authorization: Bearer tok") |
||
--output |
-o |
string | stdout | Write output to file instead of stdout |
--timeout |
duration | 30s |
Max time to wait for server startup and tool listing |
The generated YAML lists every tool alphabetically with descriptions and parameter summaries as comments, and a commented-out global rate limit wildcard example at the end. Edit the rules: [] entries to add your policy rules, and uncomment the wildcard section for a global rate limit.
# Scan a stdio server, print to stdout
intercept scan -- npx -y @modelcontextprotocol/server-github
# Scan an HTTP server
intercept scan --upstream https://mcp.example.com
# Scan with auth header
intercept scan --upstream https://mcp.stripe.com --header "Authorization: Bearer tok"
# Write to file
intercept scan -o policy.yaml -- npx -y @modelcontextprotocol/server-githubChecks a policy file for errors without starting the proxy.
intercept validate -c <policy-file>
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--config |
-c |
string | (required) | Path to the policy YAML file |
Prints Policy file is valid. on success, or lists all validation errors. See POLICY.md, Validation errors for the full list.
$ intercept validate -c policy.yaml
Policy file is valid.Shows a summary of Intercept proxy instances and recent activity by reading event logs.
intercept status
No flags. The command reads event files from ~/.intercept/events/ and prints:
- Instances: table of all known proxy instances with ID, server, PID, state backend, fail mode, status (alive/dead/unknown), and last seen time.
- Stats: total tool calls, denied count and percentage, calls per minute (over last 5 minutes).
- Top Deny Rules: the 10 most frequently triggered deny rules.
- Recent Denied Calls: the last 10 denied tool calls with timestamp, tool name, and rule.
Sections with no data are omitted.
Manages pending approval requests created by require_approval rules. See POLICY.md, Approval rules for the policy format.
intercept approvals <subcommand> [flags]
| Subcommand | Description |
|---|---|
list |
List all pending approval requests |
show [id] |
Show full details of a single approval |
approve [id] |
Approve a pending request (allows the next matching call) |
deny [id] |
Deny a pending request |
expire --all-stale |
Expire all approvals past their timeout |
| Flag | Subcommand | Type | Default | Description |
|---|---|---|---|---|
--state-dir |
all | string | ~/.intercept/state |
Directory for persistent state |
--actor |
approve |
string | Identity of the approver | |
--reason |
deny |
string | Reason for denial | |
--all-stale |
expire |
bool | false |
Required flag to confirm expiry |
# List pending approvals
intercept approvals list
# Show details of a specific approval
intercept approvals show apr_abc123
# Approve a request
intercept approvals approve apr_abc123 --actor "liad"
# Deny with a reason
intercept approvals deny apr_abc123 --reason "amount too high"
# Expire stale approvals
intercept approvals expire --all-staleWhen --enable-admin-api is set, the proxy exposes a local HTTP API for approval management:
| Method | Path | Description |
|---|---|---|
GET |
/api/pending |
List all pending approvals |
GET |
/api/pending/{id} |
Get a single approval by ID |
POST |
/api/approve/{id} |
Approve (body: {"actor": "..."}) |
POST |
/api/deny/{id} |
Deny (body: {"reason": "..."}) |
Expired approvals return 409 Conflict. Already-decided approvals return the current state (idempotent).
To use Intercept with Claude Code or any MCP client that reads .mcp.json, point the server's command at Intercept.
With a child process (stdio):
{
"mcpServers": {
"github": {
"command": "intercept",
"args": [
"-c", "/path/to/policy.yaml",
"--",
"npx", "-y", "@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "..."
}
}
}
}Environment variables defined in env are passed through to the upstream server process.
With a remote HTTP server:
{
"mcpServers": {
"stripe": {
"command": "intercept",
"args": [
"-c", "/path/to/policy.yaml",
"--upstream", "https://mcp.stripe.com",
"--header", "Authorization: Bearer tok"
]
}
}
}Intercept bridges stdio (from the MCP client) to HTTP (to the upstream server). The AI agent interacts with Intercept exactly as it would with the upstream server; Intercept is fully transparent aside from policy enforcement.
Intercept watches the policy file for changes using filesystem notifications. Edits are debounced (100ms) to handle editors that write files in multiple steps.
On reload:
- New rule definitions take effect immediately for subsequent tool calls.
- In-flight calls (already past policy evaluation) are not affected.
- Stateful counters are not reset. Counters persist independently of rule definitions to prevent gaming limits by editing config.
- If the new file is invalid YAML or fails validation, the reload is rejected and the previous config remains active. A warning is logged.
- A log message is emitted on successful reload.
Intercept supports two state backends for stateful counters. Only one can be active at a time.
Used when --state-dsn is not set. Stores counters in a SQLite database at <state-dir>/intercept.sqlite (default: ~/.intercept/state/intercept.sqlite). Runs in WAL mode for safe multi-process access.
Used when --state-dsn is set to a Redis URL.
intercept -c policy.yaml --state-dsn redis://localhost:6379/0 -- npx serverDSN format follows standard Redis URLs: redis://[user:password@]host:port[/db].
The --state-fail-mode flag controls behavior when Redis is unreachable:
| Mode | Behavior |
|---|---|
closed (default) |
Deny tool calls that require state evaluation |
open |
Allow tool calls, treating counters as zero |
The --state-prefix flag prepends a string to all counter keys, allowing multiple Intercept instances to share a Redis database with isolated namespaces:
intercept -c policy.yaml --state-dsn redis://localhost:6379/0 --state-prefix stripe-prod: -- npx stripe-serverEach Intercept proxy instance appends structured events to ~/.intercept/events/<instance-id>.jsonl in newline-delimited JSON format.
| Type | Description |
|---|---|
startup |
Proxy started, includes server name, PID, config path, state backend |
tool_call |
Tool call processed, includes tool name, result (allowed/denied/idempotent_reuse), rule name if denied |
config_reload |
Policy file reloaded, includes status (success/failure) |
heartbeat |
Periodic (60s) snapshot of counter values |
spend |
Spend decision on MPP -32042 response, includes amount, currency, and decision (allowed/denied/shadow_denied/approval_required) |
shutdown |
Proxy shutting down |
Spend events include additional fields:
| Field | Description |
|---|---|
spend_amount |
Amount in microdollars (int64) |
spend_currency |
Currency code (e.g. "usd") |
spend_decision |
Decision: allowed, denied, shadow_denied, approval_required |
Tool call arguments are not logged by default for privacy. Only a SHA-256 hash of the arguments is recorded for correlation.
Event files older than 7 days are automatically pruned on proxy startup and when running intercept status.
| Path | Description |
|---|---|
~/.intercept/state/ |
Default state directory (contains intercept.sqlite) |
~/.intercept/events/ |
Event log directory (contains <instance-id>.jsonl files) |