Free lab Real Python 3. Zero installs. Your code stays in this browser. Open the playground

Build an MCP Server from Scratch

Implement the Model Context Protocol by hand: JSON-RPC messages, a tool registry that writes its own JSON Schema, and the dispatcher that lets an AI assistant like Claude call your Python functions.

Advanced 4 steps 200 XP ~75 min

How it works in the real world

The Model Context Protocol (MCP) is the open standard AI apps such as Claude use to call tools that other people write. An MCP server is a program that says "here are my tools, and here is how to call them"; the AI app is the client.

  1. Transport: client and server exchange JSON-RPC 2.0 messages, one JSON object per line over stdin/stdout (or over HTTP).
  2. Handshake: the client sends initialize; the server answers with its protocol version, capabilities and name.
  3. Discovery: tools/list returns every tool with a JSON Schema describing its arguments, so the model knows what it may send.
  4. Execution: tools/call runs a tool and returns content blocks; failures come back as errors the model can read and recover from.

Libraries such as the official mcp SDK hide all of this behind one decorator. Building it yourself first means you will know exactly what that decorator is doing.

Build progress0 / 4 steps
1

Speak JSON-RPC

Every MCP message is a JSON-RPC 2.0 object. Finish classify(line) so it returns request #ID METHOD when there is an id, notification METHOD when there is not, or invalid: parse error / invalid: wrong version / invalid: missing method.

Blank · autosaved
PYstep_1.py
Run your code to check this step…
2

A tool registry that writes its own schema

The model can only call a tool if it knows the arguments. Finish the @tool decorator: read the function's signature with inspect, map int/float/str/bool to JSON Schema types, mark parameters without defaults as required, and store the definition in TOOLS. Then print each definition as JSON.

Blank · autosaved
PYstep_2.py
Run your code to check this step…
3

The dispatcher: initialize, list, call

Write handle(msg) to answer three methods: initialize (protocol version 2025-06-18, {"tools": {}} capability, server name lwp-demo version 1.0.0), tools/list and tools/call, which returns the value as a text content block with isError: false.

Blank · autosaved
PYstep_3.py
Run your code to check this step…
4

Fail like a real server

Models make mistakes, so the server must answer them clearly. Wrap handle in serve(msg): notifications get no reply (None); an unknown method is error -32601; an unknown tool or wrong arguments is -32602; and a tool that raises becomes a normal result with isError: true so the model can read the message and try again.

Blank · autosaved
PYstep_4.py
Run your code to check this step…

Take it further

You have written the protocol by hand. Now run the real thing on your own machine and plug it into an AI assistant. Nothing below is graded: it runs on your own machine, not in this tab.

Stretch goals

  1. Add a tools/list pagination cursor so a server with hundreds of tools does not send them all at once.
  2. Read newline-delimited JSON from sys.stdin and write replies to sys.stdout: that is the whole stdio transport, and your step 4 code becomes a working server.
  3. Add a tool that reads a CSV from disk and returns summary statistics, then ask an assistant questions about your own data.
  4. Write tests with pytest that send malformed messages and assert on every error code.

server.py: the same server with the official MCP Python SDK

# pip install "mcp[cli]"
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("lwp-demo")


@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b


@mcp.tool()
def convert_temp(celsius: float, unit: str = "F") -> float:
    """Convert a Celsius temperature to F or K."""
    if unit == "F":
        return round(celsius * 9 / 5 + 32, 1)
    if unit == "K":
        return round(celsius + 273.15, 2)
    raise ValueError(f"unknown unit {unit}")


if __name__ == "__main__":
    mcp.run()  # stdio transport

# Try it:
#   npx @modelcontextprotocol/inspector python server.py
# Connect it to Claude Code:
#   claude mcp add lwp-demo -- python /full/path/to/server.py

Ask an AI to review your build

I built a Model Context Protocol server in Python by hand (JSON-RPC 2.0 dispatcher, a tool registry that derives JSON Schema from type hints, and error handling for -32601 and -32602). Here is my code: [paste it]. Review it like a senior engineer: point out protocol mistakes, missing edge cases (notifications, bad arguments, tools that raise), and one thing I should test next. Do not rewrite it for me; explain what to change and why.

Your next ten minutes

Write Python that does something useful.

Start free. No install, no card, no passive video marathon.

Start learning free → Explore the path