Skip to content

Participants and boundaries

  • Lesson 2
  • 50 min
  • Module 1: Understand the integration problem
Lesson 2 of 22 · MCP: Connect AI Applications to Tools and DataParticipants and boundaries

What you will be able to do

Name the three parts of every MCP connection: the host, the client and the server. Run the smallest MCP server, read the six messages that its client and server exchange, and decide which part is responsible for each check.

Before you startWhat to know and to have readyAbout 50 minutes

What you need to know

Your project

  • ~/projects/support-mcp from the last lesson, with the virtual environment active: python -m pytest -q prints 8 passed.
  • The labs repository in ~/projects/nextia-learning-labs, from the last lesson.
  • An internet connection for one install: the SDK and its packages take about 30 MB on disk. No account and no key.

Think of a food hall. You sit at a table, and one waiter serves you. Behind the waiter are three small kitchens: pizza, noodles and salads. Each kitchen has its own menu.

The waiter keeps a separate order pad for each kitchen. A noodle order goes on the noodle pad, in the form that the noodle kitchen expects. The waiter also decides what to pass on. If a guest asks for “the kitchen’s keys”, the waiter does not write it down.

Each kitchen cooks only what is on its own menu. It also checks every slip that arrives. If a slip asks for a dish that is not on the menu, the kitchen refuses it, even if the waiter wrote it neatly. The ingredients are in a pantry behind the kitchen, and the guests never see the pantry.

Four roles, four kinds of decision. The waiter decides what to pass on. The order pad carries the order in the kitchen’s form. The kitchen decides what it will cook. The pantry holds what exists.

The problem: “the AI read the wrong ticket”

Section titled “The problem: “the AI read the wrong ticket””

Tomás brings a worry to the team. “Imagine the editor assistant shows Sam a Bramble Books ticket. Where is the bug? In the model, in the editor, in MCP, or in our code?”

Nobody can answer yet, because the team has no names for the parts. Tomás suggests a quick fix: “Add a line to the model’s instructions: never read the other shop’s tickets.”

That fix is in the wrong place. The model only writes text, and the text can be wrong. A ticket can even contain words that try to change what the model does, as AI Security, Privacy and Responsible Design showed. If the only protection is a sentence in the model’s instructions, nothing guarantees it.

You need names for the parts, and a rule for which part must make each check. That is what this lesson gives you.

An MCP connection always has the same three participants. They match the food hall.

  • The host is the AI application that a person uses: the help desk assistant, the editor assistant or the partner’s app. It holds the conversation and talks to the model. It decides which of the model’s proposed calls really run. It is the waiter.
  • The client is a part inside the host that speaks MCP to one server. A host that uses three servers holds three clients. The client asks the server what it offers, sends requests, and returns the results to the host. It is the order pad: it carries requests, and it decides nothing.
  • The server is a program that offers capabilities: things that a client can discover and use. In Module 2 you meet the three kinds: tools, resources and prompts. The server checks every request that it receives. It is the kitchen.

Under the server is the business service: the code and the data that already existed. For Larkfield, that is knowledge.py and the data/ folder. It is not part of MCP at all. It is the pantry.

Asking a server what it offers has a name: capability discovery. The client does it before it calls anything, so the host can show the tools to the model.

Three hosts on the left: the help desk assistant, the editor assistant and the partner's app. Each host contains an MCP client. The help desk assistant also talks to a model; each host has its own model. All three clients send MCP messages (JSON-RPC) to one server, support-mcp, which will offer the tools search_knowledge and get_ticket, the resources policy://larkfield/… and the prompt draft_reply. The server makes Python calls to knowledge.py, the business service, which reads data/: 14 documents and 21 tickets. Four boxes below say what each part does. Host, the AI application: talks to the person and to the model, decides which tool calls really run. MCP client, inside the host: one connection to one server, discovers what it offers, sends requests, gets results. MCP server, support-mcp: offers tools, resources and prompts, checks the caller on every request. Business service: the rules and the data, knows nothing about MCP or about models.
Larkfield's plan: three hosts, each with its own MCP client, reach one server. The server sits in front of the business service that already exists.
Text description of the diagram

Three hosts, the help desk assistant, the editor assistant and the partner’s app, each contain an MCP client. Each host has its own model; the picture shows it for the first one. All three clients send MCP messages, written in JSON-RPC, to one server, support-mcp. The server will offer the tools search_knowledge and get_ticket, the resources policy://larkfield/… and the prompt draft_reply. It makes ordinary Python calls to knowledge.py, the business service, which reads data/: 14 documents and 21 tickets.

  • Host, the AI application: talks to the person and to the model, and decides which tool calls really run.
  • MCP client, inside the host: one connection to one server. It discovers what the server offers, sends requests and gets results.
  • MCP server, support-mcp: offers tools, resources and prompts, and checks the caller on every request.
  • Business service: the rules and the data. It knows nothing about MCP or about models.

The server’s capabilities in the picture are a plan. You design them in Module 2 and build them in Module 3. Today you run a much smaller server, to see the three participants work.

This course uses MCP specification revision 2026-07-28 with the official MCP Python SDK 2.3.0. An SDK, a software development kit, is a library that does the protocol work for you. The next lesson explains what these two version numbers mean, and how to check them.

Here is the smallest useful server. It is in examples/hello_server.py of the snapshot end-of-m01:

examples/hello_server.py
"""The smallest MCP server: one tool. Run it with: python -m examples.hello_server (stdio)."""
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("hello-server", version="1.0.0")
@mcp.tool()
def hello(name: str) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run("stdio")

Read it from the top. MCPServer("hello-server", version="1.0.0") makes a server with a name and a version. The decorator @mcp.tool() turns an ordinary function into a tool. The SDK takes three things from the function. The function’s name, hello, becomes the tool’s name. The docstring becomes the description that a model reads. The type hint name: str becomes the input schema: one required argument, a string.

The last line starts the server on stdio: it reads messages from its standard input and writes answers to its standard output. A host starts a local server like this as a child process, and the client talks to it through those two streams. Module 3 explains stdio in detail.

The host is scripts/participants.py. It is a script, so it has no model and no person. That is enough to see the three participants:

scripts/participants.py (the main part)
server = StdioServerParameters(command=sys.executable, args=["-m", "examples.hello_server"])
print(f"host: this script (process {os.getpid()})")
async with Client(server) as client:
print(f"client: the SDK's Client, protocol {client.protocol_version}")
info = client.server_info
print(f"server: {info.name} {info.version}, a child process that talks on stdin/stdout")
tools = await client.list_tools()
for t in tools.tools:
print(f" tool {t.name}: {t.description}")
result = await client.call_tool("hello", {"name": "Amira"})
print(f"call: hello(name='Amira') -> {result.content[0].text}")

StdioServerParameters says how to start the server: this Python, with -m examples.hello_server. Client(server) starts it and connects. Then the host asks for the tools, and calls one.

  1. Install the SDK, at exactly this version:

    Terminal window
    python -m pip install "mcp==2.3.0"

    It also installs 27 packages that the SDK needs, such as pydantic, anyio and starlette. In the next lesson, you write the version into requirements.txt.

  2. Copy this lesson’s files from the snapshot end-of-m01:

    Terminal window
    S=../nextia-learning-labs/C28/snapshots/end-of-m01
    cp -R "$S/examples" .
    mkdir scripts
    cp "$S/scripts/__init__.py" "$S/scripts/participants.py" scripts/
    cp "$S/tests/test_participants.py" tests/
  3. Run the host:

    Terminal window
    python -m scripts.participants
    host: this script (process 24484)
    client: the SDK's Client, protocol 2026-07-28
    server: hello-server 1.0.0, a child process that talks on stdin/stdout
    tool hello: Say hello to someone.
    call: hello(name='Amira') -> Hello, Amira!

    Your process number differs. Every other line is the same.

  4. Run the tests:

    Terminal window
    python -m pytest -q

    The last line is 10 passed: the 8 tests of the business service, and 2 new ones.

  5. Start the server alone, without a host:

    Terminal window
    python -m examples.hello_server

    Nothing happens. The server waits for messages on its input. Type hello and press Enter. Still nothing comes back, because hello is not an MCP message.

  6. Stop the server. On macOS and Linux, press Ctrl+D: it closes the input, and the server stops. On Windows, press Ctrl+Z and then Enter. The Windows keys are not tested by the course.

Step 5 shows something important. A server does not start a conversation. It waits for a client, and it answers only messages in the protocol’s form.

The first test in tests/test_participants.py shows one more thing. It passes the server object itself to Client(hello_server). The client then talks to the server in the same process, with no child process. The messages are the same, so tests can be fast.

The host printed five lines. Under them, the client and the server exchanged six messages: three requests and three answers. The course team recorded them with a small program that sits between the two and copies every line. In Module 3, you read these messages yourself, from your own server.

A sequence between three participants: the host, participants.py; the client, the SDK's Client; the server, hello_server.py. The host creates the Client. The client starts the child process python -m examples.hello_server, then sends server/discover with id 1 and _meta 2026-07-28; the server answers supportedVersions 2026-07-28, hello-server 1.0.0. The host calls list_tools; the client sends tools/list with id 2; the server answers with 1 tool, hello(name). The host calls call_tool hello with name Amira; the client sends tools/call with id 3; the server runs hello('Amira') and answers 'Hello, Amira!', isError false; the client returns the result to the host. A note: 3 requests and 3 answers, 6 lines of JSON on the child's stdin and stdout.
The run of participants.py, message by message. The labels are shortened from the recorded messages.
Text description of the diagram

The host creates the Client. The client starts the child process python -m examples.hello_server. Then three requests and answers follow:

  1. The client sends server/discover (id 1), with _meta naming revision 2026-07-28. The server answers: supported versions 2026-07-28, server hello-server 1.0.0.
  2. The host calls list_tools(). The client sends tools/list (id 2). The server answers with one tool, hello(name).
  3. The host calls call_tool("hello", name="Amira"). The client sends tools/call (id 3). The server runs hello("Amira") and answers "Hello, Amira!" with isError: false. The client returns the result to the host.

That makes six lines of JSON on the child’s standard input and output.

Here are the six recorded messages, with their time after the start, their direction and their size. An arrow -> goes from the client to the server, and <- comes back.

Time Direction Message Bytes
0.2 ms -> server/discover, id 1 246
301.6 ms <- result 1: supportedVersions ["2026-07-28"], serverInfo hello-server 1.0.0 344
302.4 ms -> tools/list, id 2 241
303.0 ms <- result 2: one tool, hello, with its input schema 515
303.8 ms -> tools/call, id 3, {"name": "hello", "arguments": {"name": "Amira"}} 285
304.6 ms <- result 3: "Hello, Amira!", isError: false 263

The first message is capability discovery. The answer to tools/list holds what the decorator made from the function:

{"description": "Say hello to someone.",
"inputSchema": {"type": "object", "properties": {"name": {"title": "Name", "type": "string"}},
"required": ["name"], "title": "helloArguments"},
"name": "hello",
"outputSchema": {"properties": {"result": {"title": "Result", "type": "string"}},
"required": ["result"], "title": "helloOutput", "type": "object"}}

This is the part of the answer that describes the tool, laid out on several lines. A host shows this description and schema to its model, so the model can propose a call. The outputSchema comes from the return type, -> str.

Look at the times. The first answer came about 300 ms after the start. Most of that time is the child process starting: Python, then the SDK. The recorder in between adds a little more. After that, each answer took less than 1 ms. The course’s measurement of the full Larkfield server agrees. Starting a stdio server and listing its tools took a median of 296.5 ms over 10 starts, on the course team’s laptop. Your computer gives other numbers.

Now answer Tomás. Give each check to the part that can make it reliably.

Part At Larkfield Responsible for Not responsible for
Person Sam, a support agent asking, and approving actions knowing the protocol
Model the assistant’s model proposing calls and writing text any check: its text is not a guarantee
Host the help desk assistant the conversation; which proposed calls run; asking the person deciding what the server allows
Client the SDK’s Client in the host discovery, requests, results for one server any decision
Server support-mcp its capabilities; checking every request and the caller choosing which tool the model wants
Business service knowledge.py the data, and the rule “inside one organization” the protocol

Two lines in this table are boundaries of trust. The host must not trust the model’s proposal: it checks it, as you did in Building Reliable Applications with LLM APIs. And the server must not trust the host. The server cannot see what the host checked, and a partner’s host is not even Larkfield’s code. So every request that reaches the server is checked there again.

The answer to Tomás is now clear. “Never read the other shop’s tickets” belongs in the server and in the business service. A sentence for the model is a hint, not a control. In Module 3, the server learns the organization from how it was started, never from the model. So a model cannot even ask for the other shop.

The split has one more benefit. A host can change without any change to the server. Sam’s editor assistant and the help desk assistant use different code and different models, but they reach the same server with the same messages.

The split helps whenever more than one application uses the same capabilities, as the last lesson showed. It also helps with tests. You can test the server with a plain client, with no model at all, as test_participants.py does. And you can test a host against a server with recorded answers.

It also has costs:

  • A process start. A local server is a child process. Here, the first answer needed about 300 ms. For a host that starts the server once and keeps it, that is fine. For a host that starts it again for every question, it adds up.
  • Two programs to debug. An error can be in the host, in the server or in the messages between them. Module 3 gives you tools to see each message.
  • One more thing to protect. The server is now a door to the business service. Everything that reaches it must be checked there, for every host.

Common mistake

No module named 'anyio' (or 'mcp')

Symptom: python -m scripts.participants stops at once:

File "…/support-mcp/scripts/participants.py", line 10, in <module>
import anyio
ModuleNotFoundError: No module named 'anyio'

Cause: the Python that runs the script does not have the SDK. Either step 1 was skipped, or the virtual environment is not active. anyio comes with the SDK, so it is the first missing import.

Fix: activate the virtual environment, then run python -m pip install "mcp==2.3.0" again.

Common mistake

The server seems to hang

Symptom: python -m examples.hello_server prints nothing and never returns to the prompt.

Cause: this is correct. A stdio server waits for a client’s messages on its input, and it never prints a greeting. Text on its output would break the protocol, because the client reads that output as messages.

Fix: stop it with Ctrl+D (macOS and Linux) or Ctrl+Z and Enter (Windows). Start it through a host, such as python -m scripts.participants, instead.

Reset. This lesson adds files and one package, and changes no data. If your project is broken, copy the snapshot end-of-m01 as in Where MCP fits, with end-of-m01 instead of start. That snapshot also has the next lesson’s files.

Check your understanding

Name the part (person, model, host, client, server or business service) that is responsible for each one: (a) deciding whether a proposed get_ticket call runs; (b) sending tools/list; (c) refusing a Bramble Books ticket to a Larkfield caller; (d) choosing which tool to propose.

Show the answer

(a) The host. It checks the model’s proposal and decides whether it runs, and it may ask the person. (b) The client, inside the host: it does capability discovery. (c) The server and the business service. The server checks every request, and knowledge.py looks up tickets inside one organization only. The host may also hide things, but the server must not rely on it. (d) The model proposes. It is responsible for no guarantee, which is why (a) and (c) exist.

Check your understanding

Predict: you change the docstring of hello to 'Greet a person by name.' and run participants.py again. Which printed line changes, and which recorded message carries the new text to the client?

Show the answer

The line tool hello: Say hello to someone. becomes tool hello: Greet a person by name.. The new text travels in the answer to tools/list (id 2), in the tool’s description. The host would show that description to its model. The tool’s name and its input schema do not change, because the function name and the type hint did not change.

Check your understanding

Practical check: run python -m scripts.participants. Which line shows the client, and which line shows that the server is a separate process?

Show the answer

client: the SDK's Client, protocol 2026-07-28 shows the client and the protocol revision that it agreed with the server. server: hello-server 1.0.0, a child process that talks on stdin/stdout shows the server, started by the client as a child process. The host: line prints the host’s own process number, which differs on every run.

Recap

  • Host: the AI application. It talks to the person and the model, and decides which calls run.
  • Client: inside the host, one per server. It discovers capabilities, sends requests and returns results. It decides nothing.
  • Server: offers capabilities and checks every request. Under it, the business service keeps the data and its rules, outside MCP.
  • The smallest server is 15 lines. Its run is 6 messages: server/discover, tools/list, tools/call and their answers. The first answer waits about 300 ms for the process to start.
  • Every check belongs to a part that can make it reliably: never to the model, and never only to the host.

Your project now has examples/hello_server.py and scripts/participants.py, and python -m pytest -q prints 10 passed. Next, you write down the versions that the project depends on.

Next: Version awareness