Lesson 2 - Step 5 of 5

Building an Echo Server

Put it all together with a complete JSON echo server.

The Complete Echo Server

Let's build a complete server that echoes back JSON-RPC style messages. This is a stepping stone to a real MCP server.

python
"""
echo_server.py - A JSON-RPC style echo server.
This is a precursor to our MCP server.
"""

import json
from http.server import HTTPServer, BaseHTTPRequestHandler


class EchoHandler(BaseHTTPRequestHandler):
    """Handle JSON-RPC style requests by echoing them back."""

    def log_message(self, format, *args):
        """Suppress default logging."""
        pass

    def read_json(self):
        """Read and parse JSON from request body."""
        content_length = int(self.headers.get("Content-Length", 0))
        if content_length == 0:
            return None
        body = self.rfile.read(content_length)
        return json.loads(body.decode("utf-8"))

    def send_json(self, data, status=200):
        """Send a JSON response."""
        body = json.dumps(data, indent=2).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_POST(self):
        """Handle POST requests."""
        # Validate content type
        content_type = self.headers.get("Content-Type", "")
        if "application/json" not in content_type:
            self.send_json(
                {"error": "Content-Type must be application/json"},
                status=415
            )
            return

        # Parse JSON
        try:
            request = self.read_json()
        except json.JSONDecodeError as e:
            self.send_json(
                {"error": f"Invalid JSON: {e}"},
                status=400
            )
            return

        if request is None:
            self.send_json({"error": "Empty body"}, status=400)
            return

        # Validate JSON-RPC structure
        if not isinstance(request, dict):
            self.send_json(
                {"error": "Expected JSON object"},
                status=400
            )
            return

        if request.get("jsonrpc") != "2.0":
            self.send_json(
                {"error": "jsonrpc must be '2.0'"},
                status=400
            )
            return

        if "method" not in request:
            self.send_json(
                {"error": "Missing 'method' field"},
                status=400
            )
            return

        # Echo the request back as the result
        response = {
            "jsonrpc": "2.0",
            "id": request.get("id"),
            "result": {
                "echo": {
                    "method": request.get("method"),
                    "params": request.get("params", {})
                }
            }
        }

        print(f"[Echo] {request.get('method')}")
        self.send_json(response)


def run_server(host="localhost", port=8080):
    """Start the echo server."""
    server = HTTPServer((host, port), EchoHandler)
    print(f"Echo server running on http://{host}:{port}")
    print("Press Ctrl+C to stop")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
        server.shutdown()


if __name__ == "__main__":
    run_server()

Test the Echo Server

Run the server:

bash
python echo_server.py

Test with curl:

bash
# Valid request
curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

# Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "echo": {
      "method": "tools/list",
      "params": {}
    }
  }
}

What You've Built

Your echo server now:

  • Handles POST requests with JSON bodies
  • Validates JSON-RPC 2.0 message structure
  • Returns properly formatted JSON responses
  • Handles errors gracefully
Ready for MCP!

This echo server has all the HTTP machinery you need for MCP. In the next lessons, we'll add JSON-RPC routing and MCP-specific handlers.

What's Next?

In Lesson 3, we'll dive deeper into JSON-RPC 2.0 — the protocol that MCP builds on. You'll learn about request/response matching, error handling, and notifications.