Resilient MCP Server

The Resilient compiler ships a built-in Model Context Protocol (MCP) server that exposes the full compilation pipeline as tools. AI assistants that speak MCP (Claude Desktop, Cursor, VS Code with the MCP extension, …) can use these tools to write, check, and run Resilient code without leaving their chat session.


Quick start

# Activate the MCP server
rz mcp

The server reads one JSON-RPC message per line from stdin and writes one response per line to stdout (NDJSON — newline-delimited JSON, per the MCP 2024-11-05 spec). You typically don’t run it by hand; instead, register it in your MCP client’s config:

{
  "mcpServers": {
    "resilient": {
      "command": "rz",
      "args": ["mcp"]
    }
  }
}

For hosted integrations that cannot spawn a local stdio process, run the HTTP wrapper:

rz mcp --http-port 8080
curl http://127.0.0.1:8080/health
curl -s http://127.0.0.1:8080/mcp/call \
  -H 'content-type: application/json' \
  -d '{"tool":"rz_format","input":{"source":"fn f(int x)->int{x+1}"}}'

The wrapper exposes GET /health and POST /mcp/call. Tool names may use the hosted aliases from RES-3782 (rz_compile, rz_format, rz_verify, and related rz_* names) or the native MCP names (resilient_compile, resilient_format, resilient_verify, …).

Hardening (Phase 1, RES-3934/3935/3936/3938/3944)

The HTTP wrapper enforces three limits, all configurable via environment variables, all with sane defaults so a bare rz mcp --http-port stays safe out of the box:

Limit Env var Default Response on violation
Request body size cap RESILIENT_MCP_MAX_BODY_BYTES 10 MiB (10 * 1024 * 1024) 413 Payload Too Large
Per-request compute/compile timeout RESILIENT_MCP_TIMEOUT_SECS 10 seconds 504 Gateway Timeout
Per-IP rate limit RESILIENT_MCP_RATE_LIMIT_PER_MIN 100 requests/minute/IP 429 Too Many Requests

Concurrency and logging (Phase 1, RES-3934/3937/3941)

Behavior Env var Default
Bounded connection worker pool RESILIENT_MCP_MAX_CONNECTIONS 16 concurrent connections

Concurrency (RES-3937). run_http accepts connections on the main thread and hands each one to a bounded pool of worker threads over a sync_channel. Once every worker (and the channel’s queue slot) is busy, the accept loop backpressures — new connections wait rather than being dropped — instead of a single slow request blocking every other client, which is what a sequential accept loop does.

Access logging (RES-3941). Every HTTP request (including ones rejected for size/rate-limit reasons) emits one structured line to stderr:

ts_ms=1737331200000 peer=127.0.0.1 method=POST path=/mcp/call status=200 duration_ms=42 bytes=128

Fields: ts_ms (Unix epoch milliseconds), peer (client IP), method, path, status (HTTP status code), duration_ms (request handling time), bytes (request body size). key=value formatting keeps it both human-scannable in a terminal and easy for a log shipper to parse.

The body-size check inspects Content-Length (and the bytes actually read) before the payload is fully buffered, so an oversized request is rejected without allocating memory for the whole body. The compute timeout races tool execution (parsing, typechecking, running, verifying, …) against the configured wall-clock limit on a worker thread, so a pathological-but-syntactically-valid program cannot hang a connection past the deadline. The rate limiter is a token-bucket per source IP, implemented in the in-tree hardening module (no new dependencies).

Example: lower every limit for a locked-down deployment:

RESILIENT_MCP_MAX_BODY_BYTES=1048576 \
RESILIENT_MCP_TIMEOUT_SECS=5 \
RESILIENT_MCP_RATE_LIMIT_PER_MIN=30 \
rz mcp --http-port 8080

Tools

resilient_parse

Parse Resilient source and report syntax errors.

Input:

{ "source": "fn add(int a, int b) -> int { a + b }" }

Success output:

OK — parsed 1 top-level statement(s), no errors.

Error output:

Parse errors (1):
1:4: expected identifier

resilient_typecheck

Parse + type-check source. Returns all type diagnostics.

Input:

{ "source": "fn f() -> int { \"hello\" }" }

Error output:

Type error:
1:17: return type mismatch: expected int, got string

resilient_run

Execute Resilient source and capture stdout.

Input:

{ "source": "println(\"hello, world!\")" }

Success output:

Output:
hello, world!

resilient_lint

Run all Resilient lint passes and return warnings.

Includes: naming conventions, dead code, unsafe call patterns, safety-critical violations, AI-threat detection, and more.

Input:

{ "source": "fn F(int x) -> int { x }" }

resilient_format

Format / pretty-print Resilient source using the canonical formatter.

Input:

{ "source": "fn f(int x)->int{x+1}" }

Output:

fn f(int x) -> int {
  x + 1
}

resilient_check

Full pipeline: parse + typecheck + all lint passes. Fastest way to validate a snippet end-to-end.

Input:

{ "source": "fn add(int a, int b) -> int { a + b }" }

Success output:

OK — parse, typecheck, and lint all passed.

resilient_verify

Z3 SMT contract verification. Checks requires / ensures clauses on every function.

Note: Only available in builds compiled with --features z3. Without Z3, the tool returns a clear “not available” message.

Input:

{
  "source": "fn div(int x, int y) -> int\n  requires y != 0\n{ x / y }"
}

Protocol notes

  • Transport: stdio (NDJSON — one JSON object per line, flush after each)
  • Protocol version: 2024-11-05
  • No feature flags required: the MCP server is always available on native builds (it only depends on serde_json, which is already an unconditional dependency of the compiler).
  • wasm32: not available (same constraint as the REPL and watch mode).

Registry

The Resilient MCP server is published to the official MCP Registry under the namespace io.github.ericspencer00/resilient. The registration points at the multi-arch Docker image at ghcr.io/ericspencer00/resilient (both amd64 and arm64), so any MCP client that resolves servers from the registry can install Resilient with a single click without needing to clone or build from source.

server.json at the repo root is the source of truth for the registry entry; .github/workflows/mcp-publish.yml re-publishes it on every release: published event, syncing the version + OCI tag to the release.

Example session

→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}
← {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}},"protocolVersion":"2024-11-05","serverInfo":{"name":"resilient","version":"0.2.0"}}}

→ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
← {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"resilient_parse",...},...]}}

→ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"resilient_run","arguments":{"source":"println(42)"}}}
← {"jsonrpc":"2.0","id":3,"result":{"content":[{"text":"Output:\n42\n","type":"text"}],"isError":false}}