Roadmap / Model Context Protocol

✓

A Minimal MCP Tool Server

mediummcptoolsprotocols

The problem

An MCP (Model Context Protocol) server offers tools to a language model. You will build a minimal one: a class that stores tools, and answers the three methods of the protocol: server/discover (who are you?), tools/list (what tools do you have?) and tools/call (run this one). It is stateless: each request is handled on its own.

The class

class ToolServer(name, version, supported=["2026-07-28"]):

  • add_tool(self, name, description, input_schema, fn): register a tool, whose function is fn. Tools are listed in registration order.
  • handle(self, message): process one parsed JSON-RPC message and return the response dict, or None when no response must be sent.

handle, in this order

  1. A message without "id" (a notification): return None.
  2. A method other than server/discover, tools/list, tools/call: error -32601, message "Method not found".
  3. check_meta (given, from the previous exercise) fails: return its error response.
  4. Otherwise return {"jsonrpc": "2.0", "id": <id>, "result": <result>} where <result> always has "resultType": "complete", plus:
    • server/discover: "supportedVersions" (the list), "capabilities": {"tools": {}}, and "_meta" holding {"name": ..., "version": ...} under the key serverInfo prefixed with io.modelcontextprotocol/;
    • tools/list: "tools", a list of {"name", "description", "inputSchema"}, plus the caching hints "ttlMs": 300000 and "cacheScope": "public";
    • tools/call: look up params["name"]. An unknown tool is a protocol error: error -32602, message "Unknown tool: <name>". Otherwise call fn(**params.get("arguments", {})). On success the result has "content": [{"type": "text", "text": str(value)}] and "isError": False. If the tool raises, it is a tool execution error: same shape, with the exception's text and "isError": True.

Two kinds of failure, on purpose: a protocol error means the request itself was wrong (the tool does not exist), and is a JSON-RPC error. A tool execution error means the request was fine but the tool failed: it is an ordinary result with isError: true, and the text tells the model what went wrong, so that it can correct itself and try again.

Example. A server ToolServer("demo", "0.1") with the tools add (adds a and b) and boom (always raises ValueError("city not found")). Every request below carries a valid _meta, and every result has "resultType": "complete".

  • server/discover: result with supportedVersions ["2026-07-28"], capabilities {"tools": {}}, and _meta holding the server's name demo and version 0.1 under its serverInfo key.
  • tools/list: result with tools (add and boom, each with name, description and inputSchema), ttlMs 300000 and cacheScope "public".
  • tools/call add with a=2, b=3: content with the text 5, and isError false.
  • tools/call boom: content with the text city not found, and isError true.
  • tools/call nope: error with code -32602 and message "Unknown tool: nope".
  • resources/list: error with code -32601 and message "Method not found".
  • Any notification (no id): None.

What to implement

ToolServer with add_tool and handle.

Requirements

  • The server is stateless: it keeps no information between requests besides its registered tools.
solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.