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 runrelay-core start. The daemon serves the Web UI by default;relay-core statusprints the URL. Turn the page off with--no-webor[daemon] webui = false. set_ruletakes{ "rule": <Rule> }.actionsis an array of{type, config}objects. One action object is rejected, and the error names the field (at actions). Prefermock_urlfor a fixed response. It ends the exchange at the request-header stage and does not connect upstream, so response-stage rules andonResponseHeaders/onResponsedo not run. Put further changes in the mock body and headers. Read theapi://rulesresource before writing a rule. It lists every filter and action.capture_excludematches destinationhost:port. The control API and MCP ports stay excluded. The proxy port is excluded only while the proxy is listening there.upstreamhasproxy_url, optionalauth,bypass_hosts, andfail_open. Abypass_hostsentry is an IP, acidr:network, or a hostname glob, and a matching host is dialled directly.get_policyshows the write immediately. A new proxy URL, auth,fail_open, orbypass_hostsapplies to later requests. If the new proxy URL cannot be resolved andfail_openis false, the previous connector stays in use.onWebSocketMessage(context, flow, message)returns the message,"DROP", or nothing. A WebSocket flow's layer type isWebSocket. A throw tags only that flow;set_scriptreplaces the hook. Read theapi://scriptresource before writing a script. It is the hook, return-value, andflowcontract.onRequestandonResponsetake(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:
| Flag | Env | Default | Description |
|---|---|---|---|
| — | RELAY_DATA_DIR | ~/.relay-core | Which daemon to attach to |
--mcp-url <URL> | RELAY_MCP_URL | discovered | Skip discovery and use this MCP endpoint |
--token <T> | RELAY_MCP_TOKEN | from manifest | Bearer token for that endpoint |
--no-autostart | RELAY_MCP_NO_AUTOSTART=1 | auto-start on | Refuse 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
Filter captured flows by host / path / method / status (most recent first).
{
"host": "api.example.com",
"path_contains": "/v1/",
"method": "GET",
"limit": 10
} Full flow by UUID (headers, body, timing, tags).
{
"id": "b9c84d96-2e49-43fe-9064-58d953e15279"
} Runtime metrics: flow totals, in-memory count, pending intercepts, rule errors.
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
One-shot breakpoint; the next request matching the URL pattern pauses.
{
"url_pattern": "api.example.com/login",
"phase": "request"
} List currently pending intercepts (HTTP and WebSocket).
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
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 currently loaded.
Delete a rule by id.
{
"id": "probe-mock-…"
} 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
Read the currently loaded Deno script source, if any.
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 one or more flows as HAR 1.2.
{
"host": "example.com",
"limit": 50
} 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
Current policy, including redaction and retention.
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.
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 flowsrules://— Active rulesproxy://status— Proxy status and metricsaudit://recent— Recent audit eventsca://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