Labs / Lab 03

Build a tiny protocol adapter

Let a host discover tools and call them by schema instead of by memorized shell details.

What you'll build

A toy protocol server that lists tools and dispatches calls.

By the end of this lab you will have a tiny JSON-RPC-style server that tells a host what tool it offers, what arguments that tool expects, and how to call it by name. Instead of memorizing CLI flags, the caller sends a structured request and gets a structured response back.

This is the conceptual move that matters. A protocol adapter gives the rest of the stack a stable surface: discover first, then invoke through the protocol boundary. That is the same basic shape real hosts use with MCP.

Run it

cd ai_ecosystem_labs
printf '%s\n' '{"id":1,"method":"tools/list"}' | python3 03-protocol-adapter/protocol_server.py
Starting here? Quick setup
git clone https://github.com/BanditF/ai_ecosystem_labs
cd ai_ecosystem_labs
printf '%s\n' '{"id":1,"method":"tools/list"}' | python3 03-protocol-adapter/protocol_server.py

Requires Python 3.8+. No additional packages needed for this lab.

Time guide. Setup: ~2 min. Working through it: 20–35 min because there are a few more moving parts than the earlier CLI labs.

Why this piece exists

Hosts need a way to discover and call tools without knowing implementation trivia.

If every host has to remember that one tool wants --term, another wants positional arguments, and a third prints half-human output, integration gets brittle fast. A protocol adapter fixes that by putting one predictable request/response shape in front of the tool.

A good real-world analog is MCP. The host does not care that your tool happens to be Python, or that it reads files with pathlib. It cares that it can discover the tool, inspect its schema, and call it reliably. This toy server teaches that shape before the real SDK adds transport, auth, and the rest.

This is a toy, not real MCP

This lab teaches the conceptual shape of a protocol adapter using a toy JSON-RPC implementation. It does not implement the real MCP SDK. For a real working MCP server, see Lab 03b →.

The code

protocol_server.py

Walk through it

Four things worth noticing.

Tool discovery comes first

The server answers "tools/list" with a tool definition that includes name, description, and input_schema. That is the key idea: the protocol tells the host what exists before the host tries to call it. Real hosts use that schema to decide what tools are available and how to format the arguments.

Structured dispatch beats memorized flags

A call comes in as {"method":"tools/call","params":{"name":"term_count","arguments":...}}. The server looks at params.name and routes to term_count(). That is a cleaner boundary than saying "remember to run this exact shell command with these flags in this order."

Request and response framing matters

Every exchange includes an id. When the server returns {"id": request.get("id"), ...}, the host can match the response to the request that caused it. That sounds small, but once multiple calls are in flight, correlation is part of the protocol, not an optional extra.

Why this stays a toy

This file teaches discovery, framing, and dispatch. It does not try to teach everything the real SDK handles. MCP adds transport conventions, authentication patterns, streaming, schema validation, lifecycle handling, and host integration. Lab 03b is the real version. This lab is here so the shape is easy to see all at once.

Expected output

What a successful exchange looks like.

A tools/list request returns the tool schema:

{
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "term_count",
        "description": "Count how often a term appears across local text files.",
        "input_schema": {
          "type": "object",
          "required": ["term", "files"],
          "properties": {
            "term": {"type": "string"},
            "files": {"type": "array", "items": {"type": "string"}}
          }
        }
      }
    ]
  }
}

A tools/call request returns structured results for the named tool:

{
  "id": 2,
  "result": {
    "ok": true,
    "items": [
      {"file": "sample_docs/agents.txt", "count": 2},
      {"file": "sample_docs/protocols.txt", "count": 0}
    ],
    "summary": {"total": 2},
    "errors": []
  }
}

If your output has this shape, the lab is working. Notice that both responses are JSON and both carry the request id back.

Try this

Three things to try before moving on.

  1. Call tools/list and inspect the schema field. Look closely at input_schema. That is what a real host reads to know how to call the tool and what argument types it should send.
  2. Send the wrong argument types. Try a request where term is a number or files is a string instead of a list. You should get an error-shaped result with invalid_arguments.
  3. Read Lab 03b next to this file. Compare this toy server to Lab 03b and identify one thing the real MCP SDK handles that this toy skips. Good answers include transport details, schema generation, validation, streaming, or host integration.

Concepts behind this

This lab makes the protocols concept page concrete: discovery first, then structured invocation across a boundary the host can understand.

When you are ready to build the real version, go straight to Lab 03b: a real MCP server.