mcp-resile

module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2026 License: MIT

README

Go Reference License Go CI GitHub Stars


mcp-resile

mcp-resile is a resilience gateway for the Model Context Protocol that sits in front of your own MCP servers and protects them from autonomous-agent traffic, with zero backend code changes.

Quick Start · Policies · Auth & Telemetry · Docker · Errors · Benchmarks

  • Circuit breaking — a backend with a sustained failure rate gets taken out of rotation and fails fast, instead of every caller waiting out its own timeout.
  • Retries with full-jitter backoff — only for transient failures (network errors, 502/503/504/429); an application-level error from the backend is never retried.
  • Token-bucket rate limiting and min-deadline-threshold enforcement, per tool pattern.
  • Panic isolation — a panic anywhere in a call's dispatch path is recovered and returned as a clean JSON-RPC error, never a crashed process.
  • JSON Schema validation of tools/call arguments against each tool's own inputSchema, compiled and cached once per tool.
  • Response size clamping — an oversized backend response is truncated with an LLM-readable notice, rather than blowing up the caller.
  • Bearer token / API key authentication on every ingress request.
  • Prometheus metrics (/metrics) and structured slog logging for every request, and config validation that fails loudly at startup instead of silently misbehaving.

All of it is driven by one YAML file, matched per tool by glob pattern — no code, no SDK to integrate into your backend.

Table of Contents

Why mcp-resile?

MCP is how AI agents call tools, but an agent is a different kind of client than a human-built app: it can retry aggressively when confused, send malformed or oversized requests, and hammer a slow endpoint without backing off. There's no standard for auth or rate limiting in a typical hand-rolled MCP server, so every team that stands one up ends up either building the same defensive plumbing themselves, or not — until an agent takes their backend down.

mcp-resile is a reverse proxy, not a library: your backend doesn't change at all. Agents talk to mcp-resile instead of talking to your server directly, and it forwards calls through, layering resilience and safety controls on top:

  1. Zero reinvention. All resilience logic (circuit breaker, retries, rate limiting) comes from cinar/resile. All MCP protocol handling comes from modelcontextprotocol/go-sdk. mcp-resile's own code is routing, a schema firewall, output guardrails, and the glue between the two — not a reimplementation of either.
  2. Declarative, not code. Every policy is YAML matched by glob pattern against a tool name. There's no SDK to import into your backend and no code path to keep in sync with it.
  3. Honest about what's measured. BENCHMARKS.md publishes real go test -bench output, not estimated numbers, and this README doesn't claim a resilience feature works before it's been tested against a real backend process.

Out of scope for v1 (tracked, not forgotten): egress sidecar mode, SSE/stdio transports, speculative hedging, adaptive concurrency, priority bulkheads, full RBAC/OAuth 2.1, and a semantic idempotency cache.

Quick Start

You'll need Go 1.24+ and an MCP server of your own reachable over Streamable HTTP. This gets a gateway running in front of it in a couple of minutes.

1. Build

git clone https://github.com/cinar/mcp-resile.git
cd mcp-resile
make build           # -> bin/mcp-resile (CGO_ENABLED=0, static binary)

2. Write a config

Create mcp-resile.yaml, pointing backends[0].url at your own server:

version: "v1"

server:
  listen: "0.0.0.0:8080"

backends:
  - id: "my-service"
    url: "http://127.0.0.1:9999/mcp"   # <- your MCP server's Streamable HTTP endpoint

3. Run it

./bin/mcp-resile --config mcp-resile.yaml
# mcp-resile v1.0.0 listening on 0.0.0.0:8080, proxying to 1 backend(s)

4. Point a client at the gateway instead of your backend

Any Streamable HTTP MCP client works unchanged — mcp-resile is a transparent proxy at the protocol level. Swap your client's server URL from http://127.0.0.1:9999/mcp to http://127.0.0.1:8080, and every tools/list/tools/call now goes through the resilience pipeline before reaching your backend.

Resilience Policies

Policies match tools by glob pattern against tool_pattern (first match in the list wins) and layer any combination of circuit breaker, retries, rate limiting, and a minimum deadline threshold onto matching calls:

policies:
  - tool_pattern: "db_write_*"
    resilience:
      circuit_breaker:
        failure_rate: 40.0       # percent
        window_duration: "30s"
        reset_timeout: "15s"
      retries:
        max_attempts: 3
        base_delay: "150ms"
        max_delay: "2000ms"
        backoff: "full_jitter"   # the only backoff v1 supports
      rate_limit:
        rate: 100.0
        interval: "1s"
      min_deadline_threshold: "50ms"

A tool matching no policy still gets schema validation, response clamping, and panic recovery — those apply gateway-wide, not per policy.

Auth, Metrics, and Logging

auth:
  enabled: true
  header: "Authorization"        # or e.g. "X-API-Key"
  tokens: ["shared-secret-key-1"]

telemetry:
  metrics:
    enabled: true
    port: 9090
    path: "/metrics"
  logging:
    level: "info"                # debug, info, warn, error
    format: "json"                # json, text

Running with Docker

A prebuilt image is published to GitHub Container Registry on every vX.Y.Z tag:

docker run -p 8080:8080 \
  -v "$(pwd)/mcp-resile.yaml:/etc/mcp-resile/mcp-resile.yaml:ro" \
  ghcr.io/cinar/mcp-resile:latest

Or build it yourself:

docker build -t mcp-resile .
docker run -p 8080:8080 \
  -v "$(pwd)/mcp-resile.yaml:/etc/mcp-resile/mcp-resile.yaml:ro" \
  mcp-resile

The image is built on gcr.io/distroless/static:nonroot — no shell, no package manager, runs as a non-root user — and published for both linux/amd64 and linux/arm64. Pass --build-arg VERSION=$(git describe --tags --always --dirty) to stamp a real version into a locally built image; it defaults to dev.

Configuration Reference

Section Field Meaning
server listen, read_timeout, write_timeout, max_request_bytes, max_response_bytes Ingress HTTP server settings. transport is always "streamable-http" in v1.
auth enabled, header, tokens Bearer/API-key allow-list; disabled by default.
backends[] id, prefix, url One entry per MCP server. prefix namespaces tool names (querydb_query) and is required once you have more than one backend.
policies[] tool_pattern, resilience.* Glob-matched resilience config; see Resilience Policies.
telemetry.metrics enabled, port, path Prometheus endpoint, served on its own port.
telemetry.logging level, format Structured slog output.

An invalid config (a typo'd field, a malformed glob, a duplicate backend id or prefix) fails at startup with a message naming the offending field and the config file's path — never a generic parse error.

Error Responses

A rejected call comes back as a normal JSON-RPC error your agent can act on, not a bare connection failure:

Failure Code Notes
Schema validation -32602 Field-level detail so the caller can self-correct.
Unauthorized -32601 Deliberately indistinguishable from an unknown method.
Rate limit exceeded -33001 Includes retry_after_ms.
Circuit open -33002 Backend is being protected; stop retrying immediately.
Execution timeout -33003 Deadline exceeded before or during dispatch.
Response truncated (success) isError: false; a [TRUNCATED: ...] notice is appended.

Development

make build   # CGO_ENABLED=0 go build -o bin/mcp-resile ./cmd/mcp-resile
make test    # go test -race ./...
make lint    # go vet, plus golangci-lint if installed

See BENCHMARKS.md for measured (not estimated) performance numbers for the hot path.

Contributing to the Project

Issues and pull requests are welcome. If you're proposing a larger change, please open an issue first so the approach can be discussed before you put in the work.

License

mcp-resile is provided under the MIT License, reproduced below and also available in the LICENSE file.

MIT License

Copyright (c) 2026 Onur Cinar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Directories

Path Synopsis
cmd
mcp-resile command
Command mcp-resile is the resilience gateway/reverse proxy for the Model Context Protocol.
Command mcp-resile is the resilience gateway/reverse proxy for the Model Context Protocol.
internal
auth
Package auth enforces the gateway's bearer token/API key allow-list on every ingress HTTP request, per spec.md §5.4 and the FEATURE-019 acceptance criterion: a request without a valid token is rejected before it ever reaches MCP session handling, let alone a backend.
Package auth enforces the gateway's bearer token/API key allow-list on every ingress HTTP request, per spec.md §5.4 and the FEATURE-019 acceptance criterion: a request without a valid token is rejected before it ever reaches MCP session handling, let alone a backend.
config
Package config loads and validates mcp-resile.yaml, per spec.md §7.
Package config loads and validates mcp-resile.yaml, per spec.md §7.
egress
Package egress opens and drives MCP client sessions toward backend servers over HTTP, per spec.md §3.3.
Package egress opens and drives MCP client sessions toward backend servers over HTTP, per spec.md §3.3.
ingress
Package ingress builds the gateway's client-facing MCP server and serves it over Streamable HTTP, per spec.md §3.2.
Package ingress builds the gateway's client-facing MCP server and serves it over Streamable HTTP, per spec.md §3.2.
logging
Package logging builds the gateway's structured slog.Logger from telemetry.logging: config (spec.md §7), per the FEATURE-021 acceptance criterion: every request logs at info with tool name, backend, outcome, and latency as structured fields; errors log at warn/error with the mapped JSON-RPC code.
Package logging builds the gateway's structured slog.Logger from telemetry.logging: config (spec.md §7), per the FEATURE-021 acceptance criterion: every request logs at info with tool name, backend, outcome, and latency as structured fields; errors log at warn/error with the mapped JSON-RPC code.
metrics
Package metrics implements FEATURE-020's Prometheus /metrics endpoint: request count/latency by tool, circuit breaker state transitions, rate-limit rejections, and retry counts.
Package metrics implements FEATURE-020's Prometheus /metrics endpoint: request count/latency by tool, circuit breaker state transitions, rate-limit rejections, and retry counts.
policy
Package policy resolves a tool name to the policy that governs it, per spec.md §7 and the FEATURE-010 acceptance criterion: a tool name resolves to exactly one matching policy (or a documented default) before dispatch.
Package policy resolves a tool name to the policy that governs it, per spec.md §7 and the FEATURE-010 acceptance criterion: a tool name resolves to exactly one matching policy (or a documented default) before dispatch.
proxy
Package proxy wires the ingress MCP server to one or more egress backends, merging tools/list across them and routing tools/call by namespace prefix, per spec.md §5.1, and dispatching each tools/call through resile's resilience primitives per the policy governing it (FEATURE-010 onward).
Package proxy wires the ingress MCP server to one or more egress backends, merging tools/list across them and routing tools/call by namespace prefix, per spec.md §5.1, and dispatching each tools/call through resile's resilience primitives per the policy governing it (FEATURE-010 onward).
version
Package version holds the build-time version string for mcp-resile.
Package version holds the build-time version string for mcp-resile.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL