MCP Protocol
The Smarter Project MCP server: the Model Context Protocol, over Streamable HTTP, in Django.
SmarterProjectMCPServer answers the JSON-RPC messages of one POST request, and
SmarterProjectMCPServerView serves it. It speaks the
initialize handshake of every protocol version that the MCP Python SDK negotiates
(mcp_types.version.HANDSHAKE_PROTOCOL_VERSIONS), and implements these methods:
initialize: the server’s identity, its tools capability and its instructions.notifications/initializedand every other notification: accepted, and ignored.ping: an empty result.tools/list: the tools ofsmarter.apps.mcpserver.tools.tools/call: runs a tool. A tool’s own errors, e.g. a file that does not exist, are returned as a result withisErrorset, so that the LLM can correct its call.
Why not the MCP SDK’s server. The SDK’s server runs as an ASGI application, with an
anyio task group for its lifetime, and calls its tools as coroutines. Smarter’s views and
ORM are synchronous, and the platform runs under both the Django development server and
uvicorn. Every message of this server is answered from the database, with no state between
requests, so the server is a plain Django view that uses the SDK’s own Pydantic models,
mcp_types, to validate the requests and to build the responses.
Stateless. The server answers every POST with application/json, and does not issue
an Mcp-Session-Id. Clients therefore open no GET stream, and send no DELETE, and any
of the platform’s pods can answer any request.
Note
Experimental. The MCPServer was designed and coded by Claude Code (Anthropic’s Claude Opus 5.5), with Lawrence McDaniel as co-author. It is experimental, and will be documented.
- class smarter.apps.mcpserver.server.SmarterProjectMCPServer(repository=None)[source]
Bases:
objectThe MCP server of one repository of The Smarter Project, or of all of them.
- Parameters:
repository (
ProjectRepository|None) – The repository, or None for all of them.
Example usage:
server = SmarterProjectMCPServer(get_repository("smarter-cli")) response = server.handle({"jsonrpc": "2.0", "id": 1, "method": "tools/list"})
- call_tool(params)[source]
Run a tool.
- Parameters:
params (
CallToolRequestParams) – The tool’s name and arguments.- Return type:
CallToolResult- Returns:
The tool’s result, as text. A tool error is a result with
is_errorset.- Raises:
LookupError – If there is no such tool.
- dispatch(method, params)[source]
Run one JSON-RPC method.
- Parameters:
- Return type:
Result- Returns:
Its result.
- Raises:
LookupError – If the server does not implement the method, or the tool does not exist.
ValidationError – If the params are not valid.
- static dump(response)[source]
Return a response as JSON.
An error keeps its
id, which JSON-RPC requires, even when it is null.- Return type:
TypeAliasType
- static error(request_id, code, message)[source]
Return a JSON-RPC error response.
- Return type:
JSONRPCError
- handle(payload)[source]
Answer the JSON-RPC message, or batch of messages, of one POST request.
- Parameters:
payload (
TypeAliasType) – The request body, parsed.- Return type:
TypeAliasType|None- Returns:
The response body, or None if no message needs a response, e.g. a notification.
- handle_message(message)[source]
Answer one JSON-RPC message.
- Parameters:
message (
TypeAliasType) – The message.- Return type:
JSONRPCResponse|JSONRPCError|None- Returns:
The response, or None for a notification, or for a response from the client.
- initialize(params)[source]
Answer the initialize handshake.
- Parameters:
params (
InitializeRequestParams) – The client’s offer.- Return type:
InitializeResult- Returns:
The client’s protocol version, if this server speaks it, and otherwise the latest that it speaks, which the client may refuse.