Tool generation
How 0-mcp turns your existing resources into agent-callable tools โ names, schemas, custom routes, registry inspection.
CRUD verb mapping
For each resource, 0-mcp emits agent-callable tools that mirror the REST verbs. Tool names are verb-first, lowercase:
| Resource verb | REST | MCP tool |
|---|---|---|
| list | GET /spaces | list_space |
| get | GET /spaces/{id} | get_space |
| create | POST /spaces | create_space |
| update | PATCH /spaces/{id} | update_space |
| delete | DELETE /spaces/{id} | delete_space |
Tool name = <verb>_<slug(summary)> โ summary is slugified (whitespace and punctuation collapsed to _, lowercased). When summary is unset, falls back to a snake_case version of the class name (SpaceResource โ space).
What drives the tool
class SpaceResource(BaseResource):
summary = 'space' # โ tool name suffix
description = 'Workspaces.' # โ tool description
create_schema = SpaceCreate # โ MCP inputSchema (POST)
update_schema = SpaceUpdate # โ MCP inputSchema (PATCH)
list_schema = SpaceOut # โ MCP outputSchema
filter_fields = ['active'] # โ list_space input properties
search_fields = ['name'] # โ list_space.search
order_fields = ['created_at'] # โ list_space.order_byNo second declaration. Every property the REST API uses, MCP uses too.
inputSchema
How 0-mcp builds it, in priority order:
- Pydantic schema present โ
model.model_json_schema()is emitted as JSON Schema directly. - No Pydantic schema โ Django field introspection: emits an object schema with the columns from
create_fields/update_fields/list_fields, types mapped from Django field types (IntegerFieldโ integer,EmailFieldโ string with format=email,JSONFieldโ object, etc.). - Resource without
modeland no schema โ open object ({type: 'object', additionalProperties: true}). Dispatch handles it.
Example โ generated from Django introspection
{
"name": "create_space",
"description": "Workspaces.",
"inputSchema": {
"type": "object",
"properties": {
"name": {"type": "string", "maxLength": 100},
"description": {"type": "string"}
},
"required": ["name"]
}
}Example โ generated from a Pydantic schema
class SpaceCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
description: str = ''
class SpaceResource(BaseResource):
create_schema = SpaceCreateBecomes:
{
"name": "create_space",
"inputSchema": {
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1, "maxLength": 100},
"description": {"type": "string", "default": ""}
},
"required": ["name"]
}
}outputSchema
Driven by list_schema when set. For list tools, wrapped in {meta, objects}. For detail/create/update, the schema is used directly. When neither list_schema nor edit_fields is set, falls back to model introspection.
Custom routes
Custom routes decorated with @openapi(...) are exposed automatically:
from zeromcp import openapi
from pydantic import BaseModel
class SearchInput(BaseModel):
query: str
limit: int = 10
class SearchOutput(BaseModel):
results: list[dict]
class UserResource(BaseResource):
summary = 'user'
routes = [{'path': r'/search$', 'func': 'search', 'allowed_methods': ['post']}]
@openapi(
summary='Semantic user search',
description='Hybrid keyword + vector search. Returns ranked results.',
request=SearchInput,
response=SearchOutput,
)
async def search(self, request, match=None, body=None):
...โ Tool search_user with description, input and output schemas straight from the decorator.
Registry inspection โ built-in routes
When MCP is enabled (get_routes(endpoints, mcp=True)), two inspection routes are registered alongside the JSON-RPC endpoint:
| Route | Returns |
|---|---|
GET /mcp/tools.json | Machine-readable list of tool definitions (the same as the agent sees via tools/list) |
GET /mcp/tools | Human-readable HTML page โ name, description, collapsible input/output schemas |
Both honour the same auth as /docs: session cookie, X-Api-Key, or Authorization: Bearer when configured. REQUIRE_VALID_BEARER strict mode applies here too. Pass docs_public=True to get_routes for anonymous access.
Programmatic access
from zeromcp.mcp import list_tools_public
for tool in list_tools_public(endpoints):
print(tool['name'], 'โ', tool['description'])Returns the same definitions the agent receives via tools/list, minus the internal metadata that drives dispatch.
When you need the internal metadata (writing tests, custom dispatchers):
from zeromcp.mcp import list_tools
tools = list_tools(endpoints)
# [{'name': 'list_space', 'inputSchema': ..., 'mcp_internal': {...}}, ...]The registry is derived, not maintained. Add a field to a Pydantic schema, the tool's inputSchema updates. Add a custom route with @openapi, a new tool appears. Nothing to register, nothing to wire up.
0-mcp by Stamatios Stamou Jr โ github.com/ssjunior/0-mcp