MCP Server

MCP is provided by relay-core-probe (npm package @relay-core/mcp), which is a bridge: it forwards MCP messages between stdin/stdout and the daemon's /mcp endpoint.

Connecting an MCP client starts no engine. The proxy, the flow history, the rules and the intercept queue all belong to the daemon, so the CLI, your editor and the Web UI see one state rather than one each. The daemon also serves the MCP endpoint over HTTP, so a client that can be configured with a remote MCP server can skip the bridge entirely.

For agents

A short index of the shapes below is at /llms.txt. The command is relay-core. There is no relay command and no relay-core set_rule subcommand.

  • Connecting MCP does not start the proxy. Call proxy_start, or run relay-core start. The daemon serves the Web UI by default; relay-core status prints the URL. Turn the page off with --no-web or [daemon] webui = false.
  • set_rule takes { "rule": <Rule> }. actions is an array of {type, config} objects. One action object is rejected, and the error names the field (at actions). Prefer mock_url for a fixed response. It ends the exchange at the request-header stage and does not connect upstream, so response-stage rules and onResponseHeaders / onResponse do not run. Put further changes in the mock body and headers. Read the api://rules resource before writing a rule. It lists every filter and action.
  • capture_exclude matches destination host:port. The control API and MCP ports stay excluded. The proxy port is excluded only while the proxy is listening there.
  • upstream has proxy_url, optional auth, bypass_hosts, and fail_open. A bypass_hosts entry is an IP, a cidr: network, or a hostname glob, and a matching host is dialled directly. get_policy shows the write immediately. A new proxy URL, auth, fail_open, or bypass_hosts applies to later requests. If the new proxy URL cannot be resolved and fail_open is false, the previous connector stays in use.
  • onWebSocketMessage(context, flow, message) returns the message, "DROP", or nothing. A WebSocket flow's layer type is WebSocket. A throw tags only that flow; set_script replaces the hook. Read the api://script resource before writing a script. It is the hook, return-value, and flow contract. onRequest and onResponse take (body, flow).

Prerequisites

npx @relay-core/cli ca generate
npx @relay-core/cli ca install   # macOS helper; see ca status on other platforms
npx @relay-core/cli start        # daemon + proxy (optional on first run: the bridge starts one)

Configure your MCP client

In Cursor, Claude Desktop, or any MCP client:

{
  "mcpServers": {
    "relay-core": {
      "command": "npx",
      "args": ["-y", "@relay-core/mcp"]
    }
  }
}

When no daemon is running, the bridge starts one (detached, so closing your editor does not take it with it). It does not start a proxy: that stays an explicit command — relay-core start / stop, or the proxy_start / proxy_stop tools. Set RELAY_MCP_NO_AUTOSTART=1, or [client] autostart_daemon = false in the config file, to require starting the daemon yourself.

Bridge flags

The bridge accepts a small set of flags and env vars:

FlagEnvDefaultDescription
—RELAY_DATA_DIR~/.relay-coreWhich daemon to attach to
--mcp-url <URL>RELAY_MCP_URLdiscoveredSkip discovery and use this MCP endpoint
--token <T>RELAY_MCP_TOKENfrom manifestBearer token for that endpoint
--no-autostartRELAY_MCP_NO_AUTOSTART=1auto-start onRefuse to start a daemon

Lifecycle

proxy_status reports whether the proxy is running, on which port, for how long, and why it failed if it did. proxy_start starts it — idempotent, and a taken port answers start_failed rather than success for a proxy that never bound. proxy_stop stops the proxy and keeps the daemon and its history. All three carry read-only / destructive / idempotent annotations and are listed first.

When no proxy is running, traffic tools answer a structured proxy_not_running error (in the error's data.code) instead of an empty list that reads as "no traffic". History that was already captured is still returned while the proxy is stopped.

Tools

Observe

search_flows

Filter captured flows by host / path / method / status (most recent first).

{
  "host": "api.example.com",
  "path_contains": "/v1/",
  "method": "GET",
  "limit": 10
}
get_flow

Full flow by UUID (headers, body, timing, tags).

{
  "id": "b9c84d96-2e49-43fe-9064-58d953e15279"
}
get_metrics

Runtime metrics: flow totals, in-memory count, pending intercepts, rule errors.

clear_flows

Delete captured flows and summaries from memory and the database. Rules, policy, and the audit log stay. Idempotent when history is already empty. Between clears, default retention (at most 5000 flows or 7 days) keeps a long-running proxy bounded.

Control

set_intercept

One-shot breakpoint; the next request matching the URL pattern pauses.

{
  "url_pattern": "api.example.com/login",
  "phase": "request"
}
get_pending_intercepts

List currently pending intercepts (HTTP and WebSocket).

resume_flow

Resume a paused flow. Use request_header_upserts to add/overwrite headers without dropping the rest; request_headers replaces the entire map.

{
  "key": "<flow_id>:request_headers",
  "action": "continue",
  "request_header_upserts": { "X-Debug": "1" }
}

Rules

set_rule

Create or replace one rule. actions must be an array. Filters and actions are {type, config}; a string match is {mode, value}; a text body is {type: "Text", value}. constraints may be null. Field reference: Rule engine.

{
  "rule": {
    "id": "r1",
    "name": "r1",
    "active": true,
    "stage": "RequestHeaders",
    "priority": 200,
    "termination": "Stop",
    "filter": {"type": "Url", "config": {"mode": "Contains", "value": "example"}},
    "actions": [{"type": "AddRequestHeader", "config": {"name": "X-Test", "value": "1"}}],
    "constraints": null
  }
}
list_rules

List rules currently loaded.

delete_rule

Delete a rule by id.

{
  "id": "probe-mock-…"
}
mock_url

Quickly create a MockResponse rule. It ends the exchange at the request-header stage and does not connect upstream, so response-stage rules and scripts do not run. Put further changes in this body and these headers.

{
  "url_pattern": "api.example.com/v1/health",
  "status": 200,
  "body": "{\"ok\":true}",
  "content_type": "application/json"
}

Scripts

get_script

Read the currently loaded Deno script source, if any.

set_script

Hot-load a Deno script. Returning a flow from a header hook still streams the upstream body; body reads are budgeted and timed out so a hung hook cannot stall the engine. See Scripting API.

{
  "script": "globalThis.onResponseHeaders = (ctx, flow) => {\n  flow.layer.data.response.headers.push([\"X-Relay\", \"1\"]);\n  return flow;\n};"
}

Analyze

export_har

Export one or more flows as HAR 1.2.

{
  "host": "example.com",
  "limit": 50
}
replay_flow

Re-send a captured HTTP request through the running proxy. It is captured as a new flow. The call fails when the proxy is not running.

{
  "id": "b9c84d96-2e49-43fe-9064-58d953e15279"
}

Policy

get_policy

Current policy, including redaction and retention.

update_policy

Replace the full policy. A new proxy URL, auth, fail_open, or bypass_hosts applies to later requests. A host matching bypass_hosts is dialled directly. If the new proxy URL cannot be resolved and fail_open is false, the previous connector stays in use.

patch_policy

Partial update. Accepts redaction / upstream / retention; unknown fields are rejected rather than silently ignored. Each upstream.bypass_hosts entry is an IP, a cidr: network, or a hostname glob, and a matching host is dialled directly. get_policy shows the write immediately, and a new proxy URL, auth, fail_open, or bypass_hosts applies to later requests. If the new proxy URL cannot be resolved and fail_open is false, the previous connector stays in use.

{
  "patch": {
    "retention": { "max_flows": 2000, "max_age_secs": 86400 },
    "redaction": { "enabled": true }
  }
}

Result shape

Every tool returns structuredContent (typed fields an agent reads directly, with a declared outputSchema where the shape is stable) plus the same JSON as text, so old and new clients cannot disagree about what a call returned. Writes return { ok, message, ... }: success is a field, not a phrase to match. search_flows returns { count, flows }. Tool contract version: 2.

Resources

MCP also exposes read-only resources:

  • flows:// — Recent flows
  • rules:// — Active rules
  • proxy://status — Proxy status and metrics
  • audit://recent — Recent audit events
  • ca://install — CA install guide with default paths

Subscriptions

The server declares the resources-subscribe capability and accepts a subscription for exactly the URIs it pushes updates for — flows://, flows://{id}, rules://, proxy://status and audit://recent — so a client that subscribes is a client that will actually be notified. Any other URI is refused with an error rather than silently accepted, because a subscription that can never produce an update only tells a client to wait. unsubscribe is accepted; the server broadcasts to whoever is connected and does not keep per-subscriber state.

Note that redaction is on by default: an agent reading flows sees masked sensitive headers and query values unless the policy is changed (see API § policy).

Use Cases

  • AI-powered API testing and debugging
  • Automated traffic analysis
  • Dynamic rule generation based on traffic patterns
  • Integration with CI/CD pipelines