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
1. Understand the integration problem
3. Build and inspect a local server
4. Connect an AI application
5. Remote access and authorization
6. Protect tools and user control
7. Operate and maintain the integration
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 ready
What you need to know
- The three ways to connect an application, and what MCP standardizes, from Where MCP fits.
- A Python decorator, such as
@app.get(...)in Build and Deploy Your First API.
Your project
~/projects/support-mcpfrom the last lesson, with the virtual environment active:python -m pytest -qprints8 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.
A food hall with three kitchens
Section titled “A food hall with three kitchens”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.
The idea: host, client and server
Section titled “The idea: host, client and server”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.
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.
A tiny example: the smallest server
Section titled “A tiny example: the smallest server”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:
"""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:
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.
Run it
Section titled “Run it”-
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,anyioandstarlette. In the next lesson, you write the version intorequirements.txt. -
Copy this lesson’s files from the snapshot
end-of-m01:Terminal window S=../nextia-learning-labs/C28/snapshots/end-of-m01cp -R "$S/examples" .mkdir scriptscp "$S/scripts/__init__.py" "$S/scripts/participants.py" scripts/cp "$S/tests/test_participants.py" tests/Terminal window $S = "..\nextia-learning-labs\C28\snapshots\end-of-m01"Copy-Item -Recurse "$S\examples" .mkdir scriptsCopy-Item "$S\scripts\__init__.py", "$S\scripts\participants.py" scripts\Copy-Item "$S\tests\test_participants.py" tests\ -
Run the host:
Terminal window python -m scripts.participantshost: this script (process 24484)client: the SDK's Client, protocol 2026-07-28server: hello-server 1.0.0, a child process that talks on stdin/stdouttool hello: Say hello to someone.call: hello(name='Amira') -> Hello, Amira!Your process number differs. Every other line is the same.
-
Run the tests:
Terminal window python -m pytest -qThe last line is
10 passed: the 8 tests of the business service, and 2 new ones. -
Start the server alone, without a host:
Terminal window python -m examples.hello_serverNothing happens. The server waits for messages on its input. Type
helloand press Enter. Still nothing comes back, becausehellois not an MCP message. -
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.
What travelled between them
Section titled “What travelled between them”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.
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:
- The client sends
server/discover(id 1), with_metanaming revision 2026-07-28. The server answers: supported versions 2026-07-28, serverhello-server1.0.0. - The host calls
list_tools(). The client sendstools/list(id 2). The server answers with one tool,hello(name). - The host calls
call_tool("hello", name="Amira"). The client sendstools/call(id 3). The server runshello("Amira")and answers"Hello, Amira!"withisError: 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.
Larkfield: who checks what
Section titled “Larkfield: who checks what”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.
When the split helps, and what it costs
Section titled “When the split helps, and what it costs”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 mistakes
Section titled “Common mistakes”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 anyioModuleNotFoundError: 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.
Knowledge check
Section titled “Knowledge check”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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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/calland 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