One library. REST API and MCP server. Same source of truth.

Every resource you ship as a REST endpoint also becomes a typed tool that LLM agents can call โ€” no second codebase, no schema drift, no auth duplication. Your summary, description, Pydantic schemas and field whitelists power both surfaces from the same class. Tool calls run through the same BaseResource.dispatch as REST. Auth, rate limit, security middleware, hooks โ€” all of it, automatically.

Why an MCP server alongside the API

Two years ago "expose your API" meant a REST docs page and an SDK. Today it also means agents โ€” Claude Desktop, Cursor, custom LangChain stacks, internal copilots โ€” that read tool definitions and call them by themselves. Every API will have an agent surface. The question is whether you write it from scratch or get it for free.

0-mcp gets it for free. We already generate OpenAPI 3.0.3 from your resources. We already have Pydantic schemas, field whitelists, auth, rate limiting and security middleware. The MCP layer reuses every bit of that โ€” no parallel handlers, no duplicated validation, no separate rate limits to forget.

Install

# MCP server is bundled in the base install โ€” no extras needed.

Brings jsonschema for fast-fail input validation. That's it.

Two ways to enable

One liner โ€” mcp=True

from zeromcp import get_routes

endpoints = {'spaces(.*)$': SpaceResource, 'users(.*)$': UserResource}

urlpatterns = get_routes(endpoints, mcp=True)

Adds POST /mcp automatically. Inherits the default MCPResource โ€” authenticated, no caching, full registry exposed.

Subclass โ€” full control

from zeromcp import MCPResource

class MyMCP(MCPResource):
    endpoints = my_endpoints
    summary = 'agent-tools'
    cache = True
    cache_ttl = 60          # cache tools/list responses

    async def post_process(self, response):
        await audit_log(self.user, self.body, response)
        return response

urlpatterns = [path('mcp/', MyMCP.as_view())]

MCPResource is just another BaseResource. Override pre_process, post_process, dispatch, set cache, cache_ttl, authenticated โ€” every 0-mcp attribute and hook applies.

Philosophy โ€” same dispatch, different transport

MCP is not a parallel codebase. It is another wire format that calls the same dispatch:

HTTP request    โ†’  dispatch  โ†’  response
JSON-RPC msg    โ†’  dispatch  โ†’  response

Adding a field to your Pydantic schema updates REST and MCP. Adding a dehydrate hook updates both. Adding a post_process hook updates both. Zero drift forever.

What's next

๐Ÿค–

Every API you ship is now also an agent API. No new code, no new auth, no new docs to maintain. Your Pydantic schema, your description, your rate limit, your dehydrate hook โ€” they all carry over. The agent and the human see the same source of truth.

0-mcp by Stamatios Stamou Jr โ€” github.com/ssjunior/0-mcp