mcpproxy

module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT

README

mcpproxy

A security gateway for MCP (Model Context Protocol) servers. It terminates streamable-HTTP MCP on the front, inspects every JSON-RPC message in both directions, and bridges to one or more backends: local stdio subprocesses or remote MCP endpoints.

MCP client ──HTTP──▶ mcpproxyd ──▶ stdio child (npx some-mcp-server)
             auth      │ inspect       └─▶ remote MCP (https://…/mcp)
                       │ policy
                       │ audit

Why

Your agent calls MCP servers with real credentials, and those servers are third-party code you did not write. Put mcpproxy on that path and you get:

  • Policy: allow/deny tools by glob. mcpproxy strips denied tools from tools/list, so the model never sees them.
  • Approvals: hold flagged tool calls until a human approves them over the REST API.
  • Rug-pull detection: fingerprint every tool (name, description, input schema). If a fingerprint changes mid-session, mcpproxy kills the session or raises an alert.
  • Server-request gating: the gateway denies sampling/createMessage and elicitation/create from the server by default, whether the server asks directly or embeds the ask in a 2026-07-28 input_required result.
  • Budgets: max calls per session, per-tool rate limits, result-size truncation.
  • Audit: structured events (mcp.tool_call, mcp.tool_denied, mcp.tools_changed, …) to logs and per-session JSONL replay files.
  • Auth, all three planes: inbound (who may talk to the gateway: OIDC, GitHub, AWS STS, static token), outbound (how the gateway auths to MCP servers: static headers, passthrough, full OAuth with discovery + DCR + PKCE + refresh, RFC 8693 token exchange), and an embedded OAuth authorization server so MCP clients can OAuth against the gateway itself.
  • Aggregation: several backends behind one endpoint. mcpproxy prefixes tools <backend>_<tool> and routes each call to its backend.
  • Telemetry: Prometheus metrics and OTLP traces per message.

Quick start

go build -o bin/mcpproxyd ./cmd/mcpproxyd
go build -o bin/mcpsmoke  ./cmd/mcpsmoke

bin/mcpproxyd -config examples/01-minimal-stdio.yaml

# in another terminal: initialize → tools/list → tools/call echo
bin/mcpsmoke -url http://127.0.0.1:8000/mcp \
  -tool echo -args '{"message":"hello"}'

Point any MCP client (Claude Code, Cursor, MCP Inspector) at http://127.0.0.1:8000/mcp as a streamable-HTTP server.

Configuration

One YAML file. mcpproxy expands ${ENV_VAR} references at load. Full schema in config/config.go; examples/ covers every option:

Example Shows
01-minimal-stdio smallest useful config: one stdio backend
02-remote-static-auth remote MCP with an API-key/bearer header
03-policy allow/deny globs, budgets, rug-pull mode, catalog overrides
04-approvals human review of flagged tool calls + curl walkthrough
05-aggregation many backends, one endpoint, per-backend policy
06-inbound-oidc callers authenticate with IdP-issued JWTs
07-inbound-github-awssts GitHub-token and AWS-STS inbound modes
08-outbound-oauth OAuth to a remote MCP: discovery, DCR, PKCE, refresh
09-outbound-passthrough per-user upstream credential supplied by the client
10-auth-server embedded OAuth AS + upstream IdP federation + token swap
11-token-exchange RFC 8693: exchange the caller's token for an upstream one
12-observability Prometheus, OTLP traces, session WAL, optimizer
13-kitchen-sink everything at once, commented

Public remote MCP servers (Linear, Notion, GitHub, Stripe, Sentry, …) with ready-to-run configs: examples/remote/.

Architecture, auth planes, pipeline internals, operations, and embedding guide: docs/architectural/.

Deployment

Running it outside hoop, on GCP or AWS: docs/deployment/. Start with the constraints page — the daemon keeps sessions, approvals, and auth-server state in memory, so it is single-replica by construction and a round-robin load balancer in front of two instances answers 404 unknown session. That page also covers the container image, TLS termination, state volumes, and the exposure checklist;

HTTP surface

Endpoint Purpose
POST /mcp one JSON-RPC message (initialize starts a session; later messages carry the Mcp-Session-Id header)
GET /mcp SSE stream of server-initiated messages for the session
DELETE /mcp end the session
GET /approvals/ pending held tool calls (bearer: approvals.api_token)
POST /approvals/{id}/approve | /reject resolve a held call
GET /healthz liveness
GET /metrics Prometheus (when telemetry.prometheus_path set)
/.well-known/*, /authorize, /token, /register, /callback embedded auth server (when auth_server set)

Protocol revisions

mcpproxy serves the session-based MCP revisions through 2025-11-25.

It recognizes 2026-07-28 (the stateless revision) and refuses it with a conformant -32022 naming 2025-11-25, so a dual-era client retries on the session path instead of failing. A request whose mirrored Mcp-Method / Mcp-Name / MCP-Protocol-Version headers disagree with its body is rejected with -32020: an intermediary routing on the header and the gateway executing the body would otherwise act on different requests.

The refusal is deliberate. Several controls — rug-pull detection, per-session call budgets, and override un-renaming — decide by comparing across requests in a session. Serving them statelessly today would not weaken them, it would silently disable them, because each no-ops when its state is empty. The list lives in Gateway.statelessBlockers; when it empties, the stateless path can be served.

Session recording

With wal_dir set, mcpproxy writes one append-only JSONL file per session, one line per audit event, replayable for review:

cat sessions/<sid>.jsonl | jq -r '[.time,.event,.tool // "",.reason // ""] | @tsv'

State

mcpproxy stores outbound OAuth tokens under $MCPPROXY_STATE_DIR (default ~/.mcpproxy), encrypted with AES-256-GCM under a generated keyfile. DCR client registrations are not persisted, so a restart re-registers. The embedded auth server keeps its sessions in memory.

Embedding

The daemon is thin wiring over the library packages. Hosts such as hoop embed the gateway and supply their own implementations of:

  • inspect.Hooks: guardrails, data masking, AI analysis callbacks
  • audit.Sink: event stream consumer
  • gateway.HeldStore: approval workflow backend
  • gateway.IdentityResolver: inbound identity (skip auth/inbound when the host already authenticated the caller)
  • gateway.Observer: telemetry

Import it the way any Go dependency is imported:

go get github.com/hoophq/mcpproxy@v0.1.0

Releases

Releases are cut by CI from merged PRs; nobody tags by hand. Every PR needs exactly one label, which PR Release Label Check enforces before merge:

Label Effect on merge to main
major vX.Y.Zv(X+1).0.0
minor vX.Y.ZvX.(Y+1).0
patch vX.Y.ZvX.Y.(Z+1)
skip-release no tag, no release (docs, CI, refactors)

Auto Release then creates the vX.Y.Z tag and GitHub release — that tag is what go get github.com/hoophq/mcpproxy@vX.Y.Z resolves. Binary Release chains off it and attaches mcpproxy_X.Y.Z_<os>_<arch>.tar.gz (.zip on Windows) for darwin/linux/windows on amd64 and arm64, plus checksums.txt. Each archive holds all four commands, README.md, config.example.yaml and examples/. Re-publish binaries for an existing tag with the Binary Release workflow's manual trigger.

Going to v2 requires the major suffix in the module path (github.com/hoophq/mcpproxy/v2); Auto Release refuses to tag until go.mod matches.

Directories

Path Synopsis
Package approval implements the hold-and-review parking lot: the place a tool call waits while a human decides whether it may run.
Package approval implements the hold-and-review parking lot: the place a tool call waits while a human decides whether it may run.
Package audit defines the structured event stream the gateway emits: one event per protocol-level occurrence.
Package audit defines the structured event stream the gateway emits: one event per protocol-level occurrence.
auth
authserver
Package authserver embeds an OAuth 2.1 authorization server in the gateway so MCP clients authenticate against the gateway itself instead of needing a separate IdP registration per client.
Package authserver embeds an OAuth 2.1 authorization server in the gateway so MCP clients authenticate against the gateway itself instead of needing a separate IdP registration per client.
inbound
Package inbound implements the gateway's inbound authentication: turning an incoming HTTP request into an inspect.Identity.
Package inbound implements the gateway's inbound authentication: turning an incoming HTTP request into an inspect.Identity.
outbound
Package outbound provides token sources for gateway→MCP-server authentication.
Package outbound provides token sources for gateway→MCP-server authentication.
outbound/internal/core
Package core holds the value types shared by auth/outbound and its oauth subpackage.
Package core holds the value types shared by auth/outbound and its oauth subpackage.
outbound/oauth
Package oauth implements the outbound OAuth machinery: RFC 9728/8414 metadata discovery, RFC 7591 dynamic client registration, the PKCE authorization-code flow, refresh-token renewal, and RFC 8693 token exchange.
Package oauth implements the outbound OAuth machinery: RFC 9728/8414 metadata discovery, RFC 7591 dynamic client registration, the PKCE authorization-code flow, refresh-token renewal, and RFC 8693 token exchange.
Package backend defines the transport-agnostic connection to one MCP server (stdio subprocess or remote streamable-http endpoint).
Package backend defines the transport-agnostic connection to one MCP server (stdio subprocess or remote streamable-http endpoint).
Package catalog ships a built-in list of publicly hosted remote MCP servers and converts entries into gateway backend configurations.
Package catalog ships a built-in list of publicly hosted remote MCP servers and converts entries into gateway backend configurations.
Package checks implements every stage of the gateway's inspection pipeline: policy enforcement, approvals, server-request gating, rug-pull detection, catalog overrides, budgets, guardrail hooks, redaction, and the terminal audit tap.
Package checks implements every stage of the gateway's inspection pipeline: policy enforcement, approvals, server-request gating, rug-pull detection, catalog overrides, budgets, guardrail hooks, redaction, and the terminal audit tap.
cmd
configcheck command
Command configcheck validates mcpproxyd configuration files without starting the daemon: YAML syntax, ${ENV} expansion, and structural invariants (config.Validate).
Command configcheck validates mcpproxyd configuration files without starting the daemon: YAML syntax, ${ENV} expansion, and structural invariants (config.Validate).
import-catalog command
Command import-catalog generates mcpproxyd backend configuration from a catalog of remote MCP servers.
Command import-catalog generates mcpproxyd backend configuration from a catalog of remote MCP servers.
mcpproxyd command
Command mcpproxyd is the standalone MCP gateway daemon: one process serving one or many MCP backends behind /mcp with inbound auth, policy checks, approvals, telemetry, and session recording.
Command mcpproxyd is the standalone MCP gateway daemon: one process serving one or many MCP backends behind /mcp with inbound auth, policy checks, approvals, telemetry, and session recording.
mcpsmoke command
Command mcpsmoke is a scripted MCP client for smoke-testing the gateway.
Command mcpsmoke is a scripted MCP client for smoke-testing the gateway.
Package config defines the gateway configuration schema (YAML for the standalone daemon; hoop later constructs these structs from connection opts).
Package config defines the gateway configuration schema (YAML for the standalone daemon; hoop later constructs these structs from connection opts).
Package gateway is the MCP gateway core.
Package gateway is the MCP gateway core.
Package inspect defines the gateway's message-inspection pipeline: every JSON-RPC message crossing the gateway, in either direction, flows through an ordered chain of Checks that may allow, mutate, deny, hold, or kill.
Package inspect defines the gateway's message-inspection pipeline: every JSON-RPC message crossing the gateway, in either direction, flows through an ordered chain of Checks that may allow, mutate, deny, hold, or kill.
internal
testmcp
Package testmcp is a minimal, controllable MCP server for tests.
Package testmcp is a minimal, controllable MCP server for tests.
testmcp/cmd command
Command testmcp runs the testmcp server as a real stdio MCP binary, so tests can exercise the full spawn path (`go run ./internal/testmcp/cmd`) rather than in-process pipes alone.
Command testmcp runs the testmcp server as a real stdio MCP binary, so tests can exercise the full spawn path (`go run ./internal/testmcp/cmd`) rather than in-process pipes alone.
Package jsonrpc implements a tolerant JSON-RPC 2.0 envelope used by the MCP gateway.
Package jsonrpc implements a tolerant JSON-RPC 2.0 envelope used by the MCP gateway.
Package mcp holds the minimal MCP protocol vocabulary the gateway inspects.
Package mcp holds the minimal MCP protocol vocabulary the gateway inspects.
Package optimizer trims a tools/list response down to the tools that matter for the session's stated purpose.
Package optimizer trims a tools/list response down to the tools that matter for the session's stated purpose.
Package session holds per-MCP-session protocol state: request/response correlation, the observed tool catalog with fingerprints, identity, and arbitrary per-check state.
Package session holds per-MCP-session protocol state: request/response correlation, the observed tool catalog with fingerprints, identity, and arbitrary per-check state.
Package telemetry implements the gateway's observability seam: a Prometheus registry for per-message counters/histograms and an optional OTLP trace exporter.
Package telemetry implements the gateway's observability seam: a Prometheus registry for per-message counters/histograms and an optional OTLP trace exporter.
Package wal persists the audit stream as one append-only JSONL file per MCP session, giving post-hoc session review a durable record even if the gateway crashes mid-session.
Package wal persists the audit stream as one append-only JSONL file per MCP session, giving post-hoc session review a durable record even if the gateway crashes mid-session.

Jump to

Keyboard shortcuts

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