The answer is a gateway. You run it in front of an MCP server that's reached over HTTP (MCP's streamable HTTP transport), and make it the only way in. The server can be written in any language and needs no Delegus code.
DELEGUS_RP_KEY=dk_rp_… npx @delegus/mcp-gateway \
--upstream http://mcp:8080 --public-base-url https://tools.example.com--upstream is where your MCP server listens; keep it on a private network. --public-base-url is the gateway's public address, the one agents call and sign for.
It runs the same check as the Node middleware, because it is that middleware, in front of a streaming proxy. One implementation, so the two can't drift apart.
What happens to each request#
initialize,tools/list,ping, notifications, the event stream and session ends pass straight through.- A
tools/callis checked first. The agent sends its organization's signed permission and its own signature over this one call, tied to this tool at this address. - On ALLOW, your server receives the exact bytes that were checked, plus
Delegus-Receipt-IdandDelegus-Decisionheaders it can log. The gateway drops any copies of those headers a client sends, so the server can trust them. - On DENY, your server never sees the call. The agent gets HTTP 403 with JSON-RPC error
-32040and the reason, with the id of the signed receipt. - Anything the gateway can't read (a missing, unparseable or oversized body, a batch carrying a checked tool call, a tool call without a name) is refused, not forwarded. If Delegus can't be reached, the answer is no.
- Responses, including event streams, pass through as they arrive.
What we measured#
We ran the gateway container against a local Delegus, in front of three servers with no Delegus code in them: one built on the official Python MCP SDK, a hand-written Ruby server with no MCP SDK at all, and a Node server. All three gave the same results:
- a delegated tool call was allowed with a receipt, and the tool ran once;
- after a revoke, the same call was refused with
AUTHORITY_REVOKEDand a receipt, and the tool didn't run; - a batch carrying a tool call was refused, and the tool didn't run.
These were local runs, not production. Nothing here is a performance figure or a guarantee.
What it doesn't do#
- It only protects if it's the only way in. Keep your server on a private network; the gateway warns at startup if the upstream looks public.
- No stdio servers. There's no HTTP request to check.
- It checks which tool is called, not the tool's arguments. Validate arguments in the tool.
- Requests your server sends back on the stream (sampling, elicitation) aren't checked.
- Terminate TLS at the gateway or in front of it.
- It adds a network hop. We don't publish latency figures.
Try it#
@delegus/mcp-gateway 0.2.0 on npm, Apache-2.0, Node 22.18 or later. You need a Delegus API key; the sandbox is free, no card.
One deployment note: the gateway passes the original Host header through. If your server has DNS-rebinding protection (the official Python SDK turns it on for local hosts), allow your public host there, or run the gateway with --no-preserve-host.
On a Node server, the one-line middleware is still the shortest path. Python servers (MCPServer over streamable HTTP, Starlette, FastAPI) can use a middleware instead: pip install delegus-mcp.