Transports
Two ways for agents to talk to your MCP server. Both share the same handle_rpc dispatch underneath โ only the wire is different.
HTTP โ for production and browser-launched agents
Setup
# urls.py โ the one-liner urlpatterns = get_routes(endpoints, mcp=True) # โ POST /mcp
Or with custom behaviour:
from zeromcp import MCPResource
class MyMCP(MCPResource):
endpoints = my_endpoints
summary = 'agent-tools'
urlpatterns = [path('mcp/', MyMCP.as_view())]Wire format
Single endpoint, JSON-RPC 2.0 over HTTP POST. Each request body is one message:
POST /mcp HTTP/1.1
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}Response:
HTTP/1.1 200 OK
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "result": {"tools": [...]}}Use it for
- Production deployments (same ASGI process as your API)
- Browser-launched agents (cookie session works automatically)
- HTTP-based copilots and integrations
- Anything that already speaks HTTP
stdio โ for desktop agents
Setup
MCP_API_KEY="<your-token>" python manage.py mcp_serve myapp.urls.endpoints
The first argument is the dotted path to your endpoints registry. The command reads newline-delimited JSON-RPC from stdin, dispatches via handle_rpc, writes responses to stdout.
Use it for
- Claude Desktop (
mcpServersconfig inclaude_desktop_config.json) - Cursor (custom MCP servers)
- Any agent that launches a subprocess and pipes JSON-RPC
Example agent config
{
"mcpServers": {
"myapp": {
"command": "python",
"args": ["manage.py", "mcp_serve", "myapp.urls.endpoints"],
"cwd": "/path/to/your/django/project",
"env": {
"MCP_API_KEY": "your-token-here",
"DJANGO_SETTINGS_MODULE": "myapp.settings"
}
}
}
}Authentication
Both transports use 0-mcp's existing auth โ no new code path. The MCP bridge attaches credentials to a synthetic HttpRequest that goes through the same _authenticate you use for REST.
API key
The agent attaches a X-Api-Key header (HTTP) or sets MCP_API_KEY (stdio). 0-mcp's _authenticate resolves the key and switches to the matching tenant DB.
How keys are issued is up to your project โ 0-mcp does not enforce a particular format. See Authentication for the resolution flow 0-mcp uses, and Sessions for issuing keys.
Session cookie
HTTP transport accepts the session cookie like any other request. Useful when the agent runs inside a web app that already has a session.
Anonymous access
Set authenticated = False on your MCPResource subclass for unauthenticated tool calls. Use carefully โ the agent will run with no user, so anything that depends on self.user will need explicit handling.
Comparison
| Transport | Auth | Process model | Best for |
|---|---|---|---|
| HTTP | X-Api-Key header or session cookie | Inside your ASGI server | Production, browsers, services |
| stdio | MCP_API_KEY env var | Subprocess launched by the agent | Claude Desktop, Cursor, local agents |
Direct dispatch โ no HTTP loopback
Even the HTTP transport does not make a real network call to itself when running a tool. The bridge builds a synthetic HttpRequest, wraps view_cls.as_view() with the project's settings.MIDDLEWARE (async-capable only), and calls it directly:
- Zero round-trip latency
- No port juggling
SecurityMiddleware,AuthMiddleware,ExceptionMiddlewareand any custom async middleware run exactly as on a REST hit โ agents go through the same gate- Sync-only middleware is skipped โ mark it async-capable or enforce the equivalent invariant inside
dispatch
HTTP and stdio share the same handle_rpc and bridge.call_tool. Adding a third transport is mostly an IO-loop change โ the protocol layer stays untouched.
0-mcp by Stamatios Stamou Jr โ github.com/ssjunior/0-mcp