{"slug": "building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse", "title": "Building Production Model Context Protocol (MCP) Servers in Pure Python: Stdio, SSE & Pydantic V2", "summary": "A developer published an architectural guide for building production-grade Model Context Protocol (MCP) servers in pure Python using asyncio, json, and Pydantic V2. The guide covers JSON-RPC 2.0 wire format, Pydantic data contracts for type-safe schema validation, automatic tool-schema extraction from Python type hints and docstrings, and stdio-based transport for clients like Claude Desktop and Cursor.", "body_md": "In the rapidly evolving landscape of agentic AI, connecting Large Language Models to real-world data and execution environments has historically been plagued by fragmentation. Every developer was writing bespoke function-calling wrappers, brittle regex parsers, and custom HTTP endpoints.\n\nThe **Model Context Protocol (MCP)**, open-sourced by Anthropic and rapidly adopted by **Claude Desktop**, **Cursor**, and **Antigravity**, has emerged as the universal standard—the **USB-C of Artificial Intelligence**.\n\nIn this architectural guide, we build a production-grade, asynchronous MCP server from scratch in pure Python using `asyncio`, `json`, and `pydantic`.\n\nAt the wire level, MCP communicates using pure **JSON-RPC 2.0**. Every message transmitted between client and server is a newline-delimited JSON payload.\n\n```\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"execute_sqlite_query\",\n    \"arguments\": {\n      \"db_path\": \"production.db\",\n      \"query\": \"SELECT id, email, status FROM customers WHERE active = 1;\"\n    }\n  }\n}\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"result\": {\n    \"content\": [\n      {\n        \"type\": \"text\",\n        \"text\": \"[{\\\"id\\\": 101, \\\"email\\\": \\\"alex@example.com\\\", \\\"status\\\": \\\"active\\\"}]\"\n      }\n    ],\n    \"isError\": false\n  }\n}\n```\n\nTo maintain 100% type safety and automatic schema validation, we define our core protocol data contracts:\n\n``` python\nfrom typing import Any, Dict, List, Optional, Union\nfrom pydantic import BaseModel, Field\n\nclass JSONRPCRequest(BaseModel):\n    jsonrpc: str = \"2.0\"\n    id: Optional[Union[str, int]] = None\n    method: str\n    params: Optional[Dict[str, Any]] = None\n\nclass JSONRPCResponse(BaseModel):\n    jsonrpc: str = \"2.0\"\n    id: Optional[Union[str, int]] = None\n    result: Optional[Any] = None\n    error: Optional[Dict[str, Any]] = None\n\nclass ToolInputSchema(BaseModel):\n    type: str = \"object\"\n    properties: Dict[str, Any] = Field(default_factory=dict)\n    required: List[str] = Field(default_factory=list)\n\nclass ToolDefinition(BaseModel):\n    name: str\n    description: str\n    inputSchema: ToolInputSchema\n\nclass CallToolResult(BaseModel):\n    content: List[Dict[str, str]] = Field(default_factory=list)\n    isError: bool = False\n```\n\nRather than maintaining duplicate JSON schemas by hand, our server inspects Python functions, extracts docstrings, and derives parameters directly from native type hints:\n\n``` python\nimport inspect\nfrom typing import Callable, get_type_hints\n\ndef extract_schema(func: Callable) -> ToolDefinition:\n    sig = inspect.signature(func)\n    type_hints = get_type_hints(func)\n\n    properties = {}\n    required = []\n\n    type_map = {\n        int: \"integer\",\n        float: \"number\",\n        str: \"string\",\n        bool: \"boolean\",\n        list: \"array\",\n        dict: \"object\"\n    }\n\n    for param_name, param in sig.parameters.items():\n        if param_name in (\"self\", \"cls\"):\n            continue\n        py_type = type_hints.get(param_name, str)\n        json_type = type_map.get(py_type, \"string\")\n\n        properties[param_name] = {\"type\": json_type, \"description\": f\"Parameter {param_name}\"}\n        if param.default is inspect.Parameter.empty:\n            required.append(param_name)\n\n    return ToolDefinition(\n        name=func.__name__,\n        description=(func.__doc__ or \"\").strip(),\n        inputSchema=ToolInputSchema(properties=properties, required=required)\n    )\n```\n\nFor local tools configured in Claude Desktop or Cursor, communication happens over standard I/O streams (`sys.stdin` and `sys.stdout`). Stream flushing is critical:\n\n``` python\nimport sys\nimport json\nimport asyncio\n\nasync def run_stdio_server(server):\n    loop = asyncio.get_event_loop()\n    reader = asyncio.StreamReader()\n    protocol = asyncio.StreamReaderProtocol(reader)\n    await loop.connect_read_pipe(lambda: protocol, sys.stdin)\n\n    while True:\n        line = await reader.readline()\n        if not line:\n            break\n        try:\n            req_json = json.loads(line.decode(\"utf-8\").strip())\n            resp = await server.handle_request(req_json)\n            if resp is not None:\n                sys.stdout.write(json.dumps(resp) + \"\\n\")\n                sys.stdout.flush()\n        except Exception as e:\n            err_resp = {\"jsonrpc\": \"2.0\", \"id\": None, \"error\": {\"code\": -32700, \"message\": str(e)}}\n            sys.stdout.write(json.dumps(err_resp) + \"\\n\")\n            sys.stdout.flush()\n```\n\n**Warning:** Never use unbuffered `print()` statements inside an MCP Stdio server. Any rogue stdout logging will break the JSON-RPC stream parser. Always direct internal server logs to `sys.stderr`.\n\nOnce your Python script is ready, add it to your `claude_desktop_config.json`:\n\n```\n{\n  \"mcpServers\": {\n    \"my_python_tools\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"mcpmaster_pro.server_cli\"]\n    }\n  }\n}\n```\n\nNow, Claude Desktop natively sees your custom tools, knows their exact parameter requirements, and executes them with full local privacy and $0 ongoing cloud API subscription bills.\n\nWant the complete, production-ready implementation?\n\nOur new flagship guide and commercial repository is now live:\n\n🚀 [Production Model Context Protocol (MCP) with Python](https://antigone16.gumroad.com/l/production-mcp-python)\n\nIncludes:\n\n`mcpmaster_pro`` LAUNCH20`\nOr get the complete 4-book + 8-repository bundle:\n\n🏛️ [The Sovereign Software Architect Quad-Vault](https://antigone16.gumroad.com/l/sovereign-architect-quad-vault)", "url": "https://wpnews.pro/news/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse", "canonical_source": "https://dev.to/antigravitydev/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse-pydantic-v2-ia4", "published_at": "2026-11-03 03:38:19+00:00", "updated_at": "2026-11-03 03:52:01.268241+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools", "large-language-models"], "entities": ["Anthropic", "Model Context Protocol", "Claude Desktop", "Cursor", "Pydantic", "Antigravity"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse", "markdown": "https://wpnews.pro/news/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse.md", "text": "https://wpnews.pro/news/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse.txt", "jsonld": "https://wpnews.pro/news/building-production-model-context-protocol-mcp-servers-in-pure-python-stdio-sse.jsonld"}}