Delegus

ReceiptsLaunch

Any web-facing MCP server, in any language

An MCP server receives a tool call from an AI agent it has never met. The one-line check for Node (Express-style) MCP servers asks, before the tool runs, whether the organization behind the calling agent allowed this call, and keeps a signed receipt either way. The next question is obvious: what about a server that isn't Node, or one you didn't write?

Team Delegus

  • 3 min read

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.

bash
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/call is 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-Id and Delegus-Decision headers 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 -32040 and 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_REVOKED and 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.

Read the MCP docs.