delegus-mcp
Delegus for Python MCP servers. An ASGI middleware checks every MCP
tools/call against the calling agent's delegated authority before your tool
runs. It works with the official Python MCP SDK's MCPServer over streamable
HTTP, with Starlette, and with FastAPI. It follows the same rules as the Node
middleware, delegus.mcp() in @delegus/sdk.
pip install delegus-mcp
import os
from mcp.server.mcpserver import MCPServer
from delegus_mcp import DelegusMCPMiddleware
mcp = MCPServer("tickets")
@mcp.tool()
def create_ticket(title: str) -> str:
return f"created ticket: {title}"
app = DelegusMCPMiddleware(
mcp.streamable_http_app(),
api_key=os.environ["DELEGUS_RP_KEY"],
public_base_url="https://tools.example.com",
)
Run it with any ASGI server, for example uvicorn server:app.
public_base_url is the address agents call and sign for; behind a proxy,
it's the public one. For FastAPI or Starlette, wrap the app the same way, or
use app.add_middleware(DelegusMCPMiddleware, api_key=…, public_base_url=…).
Options:
tools: a list or a predicate, to enforce only some tools (others pass unchecked);relying_party: skips a lookup when credentials are missing;on_decision: called with each decision, for logs;max_body_bytes: default 1 MiB; a larger body is refused;api_base_url;timeout_seconds;protocol:"auto"(the default),"v0.2"or"v0.3"(see below);requires: v0.3 only, the prior decisions every checked call must rest on, as[{"fingerprint": …, "mode": …}].
Every receipt is checked
Each relying party receives receipts in its own profile's format: v0.2
(delegus-base-v1) or v0.3 (delegus-base-v3, whose receipts carry the
matched permission as a commitment, with the permission and the key to open it
returned beside the receipt). With the default, protocol="auto", the
middleware accepts either, chosen by the version the receipt was signed with,
and checks it with delegus-core before the tool runs:
- the signature, against Delegus's published receipt keys
(
<api_base_url>/.well-known/did.json, which must namedid:web:delegus.aiin production), fetched once and reused; - that the receipt is for this request: its Grant, Proof and action hashes match what was sent;
- that the decision, the reason and the individual checks agree;
- for v0.3, that the permission opens its commitment, and that the dependency
results and
requiresmatch.
protocol="v0.2" or "v0.3" pins one format. An answer that fails any check,
or comes in the other format under a pin, is refused with
SERVICE_UNAVAILABLE, and on_decision gets an error field naming the
cause. There is one check per call, never a retry: a second check with the
same Proof would be refused as a replay.
What happens to each request
-
tools/call: the agent'sDelegus-GrantandDelegus-Proofheaders are checked, including that the Proof was signed for this tool at this address.- On ALLOW, the tool runs. The receipt is in
scope["state"]["delegus"](in Starlette,request.state.delegus) and the response carriesDelegus-Receipt-Id. - On DENY, the middleware answers HTTP 403 with JSON-RPC error
-32040and the Delegus reason, and the tool never runs.
- On ALLOW, the tool runs. The receipt is in
-
Anything it can't read is refused:
- a missing, unparseable or oversized body, or one with duplicate keys
(
REQUEST_UNREADABLE); - a JSON-RPC batch carrying a checked tool call (
BATCH_NOT_SUPPORTED); - a tool call without a string tool name (
TOOL_NAME_INVALID).
These are the middleware's own refusals and carry no signed receipt.
- a missing, unparseable or oversized body, or one with duplicate keys
(
-
Delegus can't be reached, or its answer doesn't check: 403
SERVICE_UNAVAILABLE. It fails closed. -
Everything that can't run a tool passes through:
initialize,tools/list,ping, notifications, JSON-RPC responses,GET(the SSE stream),HEAD,OPTIONS, and aDELETEthat ends a session. -
The app receives exactly the bytes that were checked.
Limits
- No stdio. It checks HTTP requests, so it covers MCP servers served over HTTP, not stdio-only servers.
- No argument inspection. Delegus binds the tool, not its arguments
(v0.2 and v0.3 alike): a permission can allow
create_ticket, not "tickets under a priority". Validate arguments in the tool. - Server-to-client messages aren't checked. Requests your server sends to the client on an SSE stream (sampling, elicitation) pass as they are.
- Only requests that reach this app are checked. If the same tools are exposed some other way, those routes aren't protected.
Tested against
The shared MCP request-shape suite in @delegus/conformance: the same rows
the Node middleware and the Delegus MCP gateway run. It asserts that a refused
request never reaches the tool, and that a forwarded one arrives byte for
byte. End to end, with the official Python MCP SDK (mcp 2.x) and a local
Delegus, for a relying party on each profile:
- a delegated call runs with a receipt;
- after a revoke, the same call is refused with
AUTHORITY_REVOKED; - a batch is refused.
Python 3.9 or later. Apache-2.0.
Release files for delegus-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| delegus_mcp-0.2.0.tar.gz | 19.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| delegus_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.9 kB
Release files / delegus_mcp-0.2.0.tar.gz
| Download URL | delegus_mcp-0.2.0.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b7d8e57460c2dfffff92879009f5e907ee1ef7eb167327b7a4b6f1fa8f589e14
|
|
BLAKE2b-256 checksum How to use checksums |
3aa9f69cfafb12c735334a4861398f34cacb43b62f3e81f729bfb0b8d43a8656
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / delegus_mcp-0.2.0-py3-none-any.whl
| Download URL | delegus_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e7a699ce1df3ed04794740d3f80208916215772ece18f8772336c0a902a8baf6
|
|
BLAKE2b-256 checksum How to use checksums |
d78818ee190aca317892f90c8114bbc2b4a24f61d7878d00c0a9e497465fb785
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|