Version awareness
- Lesson 3
- 45 min
- Module 1: Understand the integration problem
Lesson 3 of 22 · MCP: Connect AI Applications to Tools and DataVersion awareness
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
MCP changes fast, and old examples are everywhere. Write down the protocol revision, the SDK version and the hosts that your project depends on. Recognize older code and messages, and make a test fail when the installed SDK is not the one you chose.
Before you startWhat to know and to have ready
What you need to know
- The host, the client and the server, and the six messages of the hello server, from Participants and boundaries.
- A pinned package version in
requirements.txt, such asfastapi==…, from Build and Deploy Your First API.
Your project
~/projects/support-mcpfrom the last lesson, with the virtual environment active:python -m pytest -qprints10 passed.- The labs repository in
~/projects/nextia-learning-labs. No account and no key.
A recipe from an old cookbook
Section titled “A recipe from an old cookbook”Think of a recipe card from an old family cookbook. It says “a cup of flour” and “a moderate oven, gas mark 4”. You follow it in a new kitchen with an electric oven and metric cups. The cake comes out flat.
Nothing was wrong with the recipe. It was written for another kitchen, in another time, and the card did not say so. A careful cook writes the details on the card: which oven, which cup, which year’s flour. Then the next cook knows what to change.
Code examples work the same way. An example that worked for its author can fail for you, because it was written for another version. If the example does not say which version, you find out by failing.
The problem: Tomás copies a tutorial
Section titled “The problem: Tomás copies a tutorial”Tomás wants a server quickly. He finds a tutorial and copies its code:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("hello-server")
@mcp.tool()def hello(name: str) -> str: """Say hello to someone.""" return f"Hello, {name}!"
if __name__ == "__main__": mcp.run()He runs it in the project’s virtual environment, with SDK 2.3.0 installed:
Traceback (most recent call last): File "…/tutorial_server.py", line 1, in <module> from mcp.server.fastmcp import FastMCP File "…/.venv/lib/python3.12/site-packages/mcp/server/fastmcp.py", line 16, in <module> raise ModuleNotFoundError(_MESSAGE, name=__name__)ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.This failure is the lucky kind. It is loud, and the message says what changed. Other differences are quieter. A client and a server can disagree about the messages themselves. Then a request fails with an error that does not mention versions at all, as you see below. To understand both kinds, you need to know which version numbers exist.
The idea: three things have versions
Section titled “The idea: three things have versions”An MCP connection depends on three versioned things. Each one has its own number.
- The specification revision. The specification is the document that defines MCP’s messages and rules. Each revision is named by its release date. This course uses 2026-07-28, the newest stable revision. The earlier ones are 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25. A date makes the order clear: a later date is a newer protocol.
- The SDK version. The SDK is the library that writes and reads the messages for you. This course uses the official MCP Python SDK 2.3.0, the
pippackagemcp. Its first number, 2, is the major version. A new major version may rename things, as Tomás just saw:FastMCPbecameMCPServer. The 1.x line still gets critical bug fixes and security fixes, but no new features. - The host and its client. Each host brings its own client, and each client speaks some revisions and not others. In this course you use three clients: the SDK’s
Client, MCP Inspector 2.10.1 in Module 3, and the course’s own host in Module 4.
So the course says it in one sentence: this course uses MCP specification revision 2026-07-28 with the official MCP Python SDK 2.3.0. Write such a sentence for every MCP project that you build.
Text description of the diagram
Five specification revisions, oldest first: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28.
- The first four (legacy): a connection starts with the
initializehandshake. Old tutorials use SDK 1.x code, withFastMCPandMcpError; SDK 1.x gets only critical bug fixes and security fixes. The revision 2024-11-05 also had a remote transport called HTTP+SSE, which this course does not teach. - 2026-07-28 (current): no handshake. Discovery is a request,
server/discover, and every request carries_meta. - SDK 2.3.0 (this course):
MCPServerandMCPError. It speaks 2026-07-28, and it still accepts the handshake of the four older revisions from older clients.
A tiny example: the same server, two eras
Section titled “A tiny example: the same server, two eras”The newest revision changed how a connection starts. The course team sent both forms by hand to your hello server, one JSON line at a time, and recorded the answers. In Module 3, you write such messages yourself.
An older client (revision 2025-06-18) starts with a handshake: a first exchange that agrees on the revision before any other request.
-> {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "old-tutorial-client", "version": "1.0"}}}<- {"jsonrpc":"2.0","id":1,"result":{"capabilities":{…},"protocolVersion":"2025-06-18","serverInfo":{"name":"hello-server","version":"1.0.0"}}}-> {"jsonrpc": "2.0", "method": "notifications/initialized"}-> {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}<- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"description":"Say hello to someone.", … "name":"hello", …}]}}The client proposes 2025-06-18, and the server agrees. The client confirms with notifications/initialized. A notification is a message that gets no answer: it has no id. After the handshake, tools/list carries no version at all. The agreement holds for the whole connection. That shared state is called a session.
A current client (revision 2026-07-28) has no handshake and no session. Every request carries the version itself, in a field called _meta:
-> {"jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": {"name": "by-hand", "version": "1.0"}, "io.modelcontextprotocol/clientCapabilities": {}}}}<- {"jsonrpc":"2.0","id":1,"result":{"cacheScope":"private","capabilities":{…},"resultType":"complete","supportedVersions":["2026-07-28"],"ttlMs":0,"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"hello-server","version":"1.0.0"}}}}-> {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {"_meta": {…the same _meta…}}}<- {"jsonrpc":"2.0","id":2,"result":{"cacheScope":"private","resultType":"complete","tools":[{"description":"Say hello to someone.", … "name":"hello", …}],"ttlMs":0,"_meta":{…}}}Discovery is now an ordinary request, server/discover. Its answer lists the revisions that the server supports. Each later request repeats the _meta, so each one stands alone.
A request in neither form gets an error. This is tools/list with no handshake before it, and no _meta:
-> {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}<- {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters","data":""}}The lines are shortened with … where the content repeats. Here are the three cases side by side:
Text description of the diagram
An older client, revision 2025-06-18 (legacy):
- Client to server:
initialize(id 1),protocolVersion: 2025-06-18. - Server to client: result (id 1),
protocolVersion 2025-06-18,serverInfo: hello-server 1.0.0. - Client to server:
notifications/initialized, a notification with no answer. - Client to server:
tools/list(id 2), with no_meta. - Server to client: result (id 2), one tool,
hello.
A current client, revision 2026-07-28:
- Client to server:
server/discover(id 1), with_meta:protocolVersion 2026-07-28,clientInfo,clientCapabilities. - Server to client: result (id 1),
supportedVersions ["2026-07-28"],hello-server 1.0.0. - Client to server:
tools/list(id 2), with the same_meta. - Server to client: result (id 2), one tool,
hello.
Neither form: tools/list (id 1) with no handshake and no _meta gets error −32602, “Invalid request parameters”.
Why does one server answer both? SDK 2.3.0 speaks 2026-07-28, and it still accepts the handshake from older clients. The client side is similar: the SDK’s Client first tries server/discover. If the server is older and does not know it, the client falls back to initialize. So participants.py printed protocol 2026-07-28: both sides spoke the newest revision.
Larkfield: write the versions down
Section titled “Larkfield: write the versions down”Amira does three things for support-mcp. She pins the SDK, writes the versions in the code, and adds a test that fails when they disagree.
-
Copy this lesson’s files from the snapshot
end-of-m01:Terminal window S=../nextia-learning-labs/C28/snapshots/end-of-m01cp "$S/requirements.txt" "$S/pyproject.toml" "$S/README.md" .cp "$S/support_mcp/__init__.py" support_mcp/cp "$S/scripts/versions.py" scripts/cp "$S/tests/test_versions.py" "$S/tests/conftest.py" tests/Terminal window $S = "..\nextia-learning-labs\C28\snapshots\end-of-m01"Copy-Item "$S\requirements.txt", "$S\pyproject.toml", "$S\README.md" .Copy-Item "$S\support_mcp\__init__.py" support_mcp\Copy-Item "$S\scripts\versions.py" scripts\Copy-Item "$S\tests\test_versions.py", "$S\tests\conftest.py" tests\The new
conftest.pyadds two test helpers for Module 3. They change nothing yet. -
Open
requirements.txt. It pins exact versions, with==:requirements.txt # The MCP Python SDK. It brings Starlette, uvicorn, httpx2, pydantic, PyJWT and cryptography.mcp==2.3.0PyJWT[crypto]==2.15.1PyJWT is for the sign-in of Module 5.
pyproject.tomllists the same two pins. -
Open
support_mcp/__init__.py. The versions are now part of the code:support_mcp/__init__.py __version__ = "1.0.0"SERVER_NAME = "support-mcp"# Versions are part of the design: MCP changes fast. This project is built and tested with:SPEC_REVISION = "2026-07-28" # the MCP specification revision (the latest stable one)SDK_VERSION = "2.3.0" # the official MCP Python SDK (pip package "mcp") -
Install the pinned packages:
Terminal window python -m pip install -r requirements.txtMost of them are installed already, so
pipreports them as satisfied. -
Print the version report:
Terminal window python -m scripts.versionsspecification revision (this project): 2026-07-28MCP Python SDK (pinned): 2.3.0MCP Python SDK (installed): 2.3.0newest revision the SDK speaks: 2026-07-28older revisions it still accepts (initialize handshake): 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25Python: 3.12.13Your Python version can differ. It must be 3.12 or later.
-
Run the tests:
Terminal window python -m pytest -qThe last line is
13 passed. Your project now equals the snapshotend-of-m01.
Each line of the report comes from a different place, and that is the point. “Pinned” comes from SDK_VERSION, the project’s decision. “Installed” comes from the package that pip really installed. “Newest revision” and “older revisions” come from the SDK’s own constants, so they say what the code really speaks. When a line disagrees with another, something changed without a decision.
tests/test_versions.py turns this into three tests. The first fails when the installed SDK is not the pinned one, or when requirements.txt does not pin it. The second fails when the SDK’s newest revision is not 2026-07-28. The third checks that the report names the older revisions. So a silent upgrade becomes a red test, not a surprise in a lesson three modules later.
Recognize legacy in examples
Section titled “Recognize legacy in examples”You will read many MCP examples. Use these signs to place an example in time before you copy it.
| In the example you see | It is written for | What to do |
|---|---|---|
from mcp.server.fastmcp import FastMCP |
SDK 1.x | Use MCPServer from mcp.server.mcpserver, as in hello_server.py. |
McpError |
SDK 1.x | The 2.x name is MCPError. |
camelCase attributes, such as result.serverInfo |
SDK 1.x | 2.x uses snake_case, such as client.server_info in participants.py. |
initialize, then notifications/initialized |
revisions 2025-11-25 or older | Recognize it. SDK 2.3.0 still accepts it from older clients. Current clients send server/discover. |
| A session ID kept between requests | revisions 2025-11-25 or older | 2026-07-28 has no session. Module 5 shows the current HTTP headers. |
| The HTTP+SSE transport | revision 2024-11-05 | Legacy. Module 5 uses Streamable HTTP. |
If an example names no version at all, look at its date and at its imports. Then decide: port it to the current names, or do not use it. Never mix 1.x and 2.x code in one project.
Which hosts you can promise
Section titled “Which hosts you can promise”A version record also says which clients you tested. “It works with MCP” is not a useful promise, because each host has its own client. Here is the record for this course:
| Participant | Version | Tested by the course |
|---|---|---|
| Specification | revision 2026-07-28 | yes |
| Server and client library | official MCP Python SDK 2.3.0 | yes |
| Older clients with the handshake | revision 2025-11-25 | yes, in Module 7; the three older revisions are accepted by the SDK, but the course does not test them |
| MCP Inspector | 2.10.1, needs Node.js 22.19 or later | yes, in Module 3 |
| The course’s own host | the project’s host/, Module 4 |
yes |
| Desktop assistants and code editors | their own | no: the course shows their configuration only |
| Operating system | Python 3.12 on macOS | yes; Windows and Linux commands are written, not tested |
Module 7 turns this table into a compatibility matrix with real test results.
When to pin, and what it costs
Section titled “When to pin, and what it costs”Pin exact versions in every project that other people run: a course, a team’s service, a partner integration. Then every install gets the same code, and every message in the lessons matches yours.
Pinning has a cost. A pinned version does not get fixes by itself. So plan an upgrade as a decision: read the SDK’s changelog, change the pin and the constant, run the tests, and read what fails. The test that you added today is the first alarm in that process.
Note one limit. This project pins its two direct packages exactly, but it has no lock file. The 27 packages that the SDK brings can still get newer versions on a later install. For a course project that is acceptable. For a production service, a lock file pins every package.
Common mistakes
Section titled “Common mistakes”Common mistake
The installed SDK is not the pinned one
Symptom: after a separate install of another version, python -m scripts.versions ends with a warning, and one test fails:
MCP Python SDK (pinned): 2.3.0MCP Python SDK (installed): 2.2.0…WARNING: the installed SDK is not the pinned one. Run: pip install -r requirements.txt> assert version("mcp") == support_mcp.SDK_VERSIONE AssertionError: assert '2.2.0' == '2.3.0'…FAILED tests/test_versions.py::test_the_installed_sdk_is_the_pinned_one - Ass...1 failed, 12 passed in 1.68sCause: something installed another SDK version into the virtual environment, for example pip install mcp==2.2.0 copied from an old note. The course team made this failure on purpose, in a second environment.
Fix: run python -m pip install -r requirements.txt. The test passes again, and the report shows 2.3.0 twice.
Common mistake
No module named 'mcp.server.fastmcp'
Symptom: code copied from an older tutorial stops at its first import, with the message at the top of this lesson.
Cause: the code was written for SDK 1.x. The project uses SDK 2.3.0, where FastMCP became MCPServer.
Fix: change the import to from mcp.server.mcpserver import MCPServer, and FastMCP(…) to MCPServer(…). Then check the rest against the table above. Do not change the pin to mcp<2: that would break every other file of the project.
Reset. If your project is broken, copy the snapshot end-of-m01 as in Where MCP fits, with end-of-m01 instead of start. Then make the virtual environment and install requirements-dev.txt.
Module practice: map an assistant’s integrations
Section titled “Module practice: map an assistant’s integrations”Guided example. docs/integration-map.md is the worked example of this module. Amira made it in four steps. First, list the AI applications and who uses them. Second, list what each one needs from the service. Third, write how each one connects today, without MCP. Fourth, write what changes with MCP, and the cases where MCP is not needed. The last table of the file says what MCP standardizes and what stays with Larkfield. Read it once more before the task.
Your turn. Harbour Cycles is a made-up bike rental shop. Its website chat assistant needs five things:
- Check bike availability in the shop’s booking system.
- Read the rental terms and the safety guide.
- Get the weather forecast from a public weather API.
- Refund a customer’s deposit.
- The mechanics’ phone app and the staff’s editor assistant also want the same availability lookup.
Write a short integration map for Harbour Cycles. For each need, decide: a direct API call, a tool in one application, or a capability of an MCP server. For each MCP capability, name one decision that stays with Harbour Cycles.
Show a worked solution
| Need | Choice | Why | What stays with Harbour Cycles |
|---|---|---|---|
| 1. Availability | MCP server, a read-only tool | Three applications need it: the chat assistant, the phone app and the editor assistant. | Which bikes and dates a caller may see; limits on the size of a result. |
| 2. Terms and safety guide | The same MCP server, as readable documents (resources, Module 2) | The same three applications quote them. | Which version is in force; staff-only documents. |
| 3. Weather | A tool in the chat assistant, or a direct API call | Only one application needs it, and the public API is already simple. A server adds a program to run for no reuse. | The weather API’s key and its terms of use. |
| 4. Deposit refund | Not yet. Later, at most a proposal that a person approves (Module 6) | It moves money. A model must never decide it alone. | Who may approve, the amount limit, and the record of each approval. |
| 5. Phone and editor | Their own MCP clients, to the same server as need 1 | That is the reason for the server: one capability, three hosts. | Sign-in and permissions for each app’s users (Module 5). |
Count: without MCP, needs 1 and 2 in three applications make 3 × 2 = 6 custom integrations. With MCP, they make 1 server. Your table can differ in the details. It must not give the refund to a model alone, and it must keep the permission checks in the shop’s own code.
Knowledge check
Section titled “Knowledge check”Check your understanding
Name the three things in an MCP connection that have their own version, and give the values that this course uses.
Show the answer
The specification revision: 2026-07-28, a date that names the protocol’s messages and rules. The SDK version: the official MCP Python SDK 2.3.0, the library that writes and reads the messages. The host and its client: each host has its own client. In this course, they are the SDK’s Client, MCP Inspector 2.10.1 and the course’s own host. Python 3.12 is a fourth version worth writing down.
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: an older client sends initialize with protocolVersion 2025-11-25 to the hello server, then notifications/initialized, then tools/list without _meta. What does it get? And a client that sends only tools/list, with no handshake and no _meta?
Show the answer
The first client gets a normal answer, because SDK 2.3.0 accepts the handshake of the older revisions. The server answers initialize with protocolVersion 2025-11-25. The notification gets no answer, and tools/list returns the hello tool. The second client gets error −32602, “Invalid request parameters”. Its request is in neither form: there was no handshake, and the request has no _meta.
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.versions and python -m pytest -q. What do the lines 'pinned' and 'installed' say, and how many tests pass?
Show the answer
Both say 2.3.0: MCP Python SDK (pinned): 2.3.0 and MCP Python SDK (installed): 2.3.0. The report also names revision 2026-07-28 and the four older revisions. python -m pytest -q prints 13 passed. If the two lines differ, the report ends with a warning, and test_the_installed_sdk_is_the_pinned_one fails.
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
Module completion check: explain what MCP standardizes, and what remains the responsibility of the application and the server. Use Larkfield's support-mcp as the example.
Show the answer
MCP standardizes five things:
- how a client discovers what a server offers (
server/discover,tools/list); - the format of the messages (JSON-RPC 2.0);
- the transports (stdio and Streamable HTTP);
- how a call is made, and how a result or an error comes back;
- how a remote server says where to get a token.
It also names its revisions, so both sides can agree on one.
The application and the server still decide everything about meaning and permission. The server decides which capabilities exist and how narrow they are, for example get_ticket instead of a database query tool. The server and the business service check who the caller is and what they may read. For example, knowledge.py answers Not found. for a Bramble Books ticket on a Larkfield request. The host decides which proposed calls run and when a person must approve, and the project decides its limits, logs and pinned versions. MCP is not an agent framework, and it does not make a tool safe.
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
- Three things have versions: the specification revision (2026-07-28), the SDK (the official MCP Python SDK 2.3.0) and each host’s client. Write them down in one sentence for every project.
- Legacy, to recognize: the
initializehandshake and its session, SDK 1.x names such asFastMCPandMcpError, and the HTTP+SSE transport. Current:server/discoverand a_metaon every request. - SDK 2.3.0 answers both eras. A request in neither form gets −32602.
scripts/versions.pyandtests/test_versions.pyturn a silent version change into a warning and a failed test.
Module recap. You can now explain where MCP fits, and when a direct API call or a tool in one application is enough. You can name the host, the client and the server, and say which part must make each check. You ran the smallest server and read its six messages. And you wrote down the versions that your project depends on. Your project equals the snapshot end-of-m01: 13 passed.
Next, you design support-mcp’s capabilities before you build them, starting with its tools.
Next: Tools