Skip to content

Update python-mcp-client sample code for MCP SDK 2.x - #825

Open
lpozo wants to merge 3 commits into
masterfrom
update-mcp-client-sdk-v2
Open

lpozo wants to merge 3 commits into
masterfrom
update-mcp-client-sdk-v2

Conversation

@lpozo

@lpozo lpozo commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

The MCP Python SDK released 2.0 on 2026-07-28 (2.2.0 is current), renaming FastMCP to MCPServer and moving mcp.server.fastmcp.* to mcp.server.mcpserver.*. This folder's pyproject.toml allowed mcp>=1.20.0 with no upper bound, so a fresh uv sync today resolves to 2.x and the sample server dies on its first import with ModuleNotFoundError: No module named 'mcp.server.fastmcp'.

This also fixes a Windows bug that two readers reported on Build a Python MCP Client to Test Servers From Your Terminal (2025-11-24 and 2026-05-03) and that Bartosz diagnosed in the thread. The client wrapped the server process in a POSIX shell, sh -c "{sys.executable} {server_path} 2>/dev/null", purely to silence the server's stderr. On Windows, sh isn't on PATH and /dev/null doesn't exist, so stdio_client() can't spawn the subprocess and the app dumps a stacktrace. Running sys.executable directly and passing the SDK's own errlog parameter behaves the same on Windows, macOS, and Linux.

What changed

  • mcp_server/mcp_server.py: FastMCPMCPServer
  • mcp_client/mcp_client.py: dropped the sh -c wrapper for sys.executable + errlog, added import os
  • pyproject.toml: mcp>=1.20.0mcp>=2.2.0,<3
  • uv.lock: regenerated
package was now
mcp 1.18.0 2.2.0
openai 2.6.0 3.11.0

Only the server's import and constructor needed changing. Every client-side API the tutorial teaches — ClientSession, StdioServerParameters, stdio_client(), .list_tools() / .list_prompts() / .list_resources(), and .call_tool() with result.content[0].text — is unchanged in 2.x, and the migration guide's client-side breaks (the removed cursor param, timedelta → float timeouts) don't apply because this code never used either.

Two incidental fixes: the lockfile was already out of sync before this change, pinning mcp>=1.17.0 against a pyproject that said >=1.20.0; and greeting_prompt() returned "Great {name} kindly." where the tutorial says "Greet". openai moves to 3.11.0 in the regenerated lock — I checked handlers.py's OpenAI() and .chat.completions.create() usage against it and no code change was needed.

Testing

uv run python -m mcp_client mcp_server/mcp_server.py --members lists the server's tool, prompt, and resource correctly on mcp 2.2.0, and call_tool() round-trips. The Windows path wasn't tested on a Windows machine — the fix removes the POSIX-only dependency, which is what broke.

Ran ruff format --check, ruff check, and dircheck.py locally with ruff 0.14.1 and all pass.

Related

Matches the tutorial update in CMS post 2355, a clone of 2015 that isn't published yet. Python MCP Server: Connect LLMs to Your Data has the same 2.x break with a larger blast radius, since FastMCP is that tutorial's subject — out of scope here, still needs its own fix.

🤖 Generated with Claude Code

lpozo and others added 2 commits September 9, 2026 16:56
The MCP Python SDK released 2.0 on 2026-07-28, which renamed FastMCP to
MCPServer and moved mcp.server.fastmcp.* to mcp.server.mcpserver.*. The
sample code's pyproject.toml allowed mcp>=1.20.0, so a fresh `uv sync`
resolved to 2.x and the server failed on its first import with
ModuleNotFoundError.

Also fixes a Windows incompatibility that readers reported in the
tutorial comments. The client wrapped the server process in a POSIX
shell (`sh -c "... 2>/dev/null"`) purely to silence server stderr. On
Windows, `sh` isn't on PATH and /dev/null doesn't exist, so
stdio_client() failed to spawn the subprocess. Launching sys.executable
directly and routing stderr through the SDK's own errlog parameter works
identically on Windows, macOS, and Linux.

Changes:

- mcp_server.py: FastMCP -> MCPServer
- mcp_client.py: drop the `sh -c` wrapper for sys.executable + errlog
- pyproject.toml: pin mcp>=2.2.0,<3
- uv.lock: regenerate (mcp 1.18.0 -> 2.2.0, openai 2.6.0 -> 3.11.0)

The lockfile was already out of sync before this change, pinning
mcp>=1.17.0 against a pyproject that specified >=1.20.0.

Verified with `uv run python -m mcp_client mcp_server/mcp_server.py
--members`, which lists the server's tool, prompt, and resource
correctly on mcp 2.2.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@realpython-bot

Copy link
Copy Markdown
Collaborator

Holding off on merging this — Final QA on the companion tutorial (CMS post 2355) didn't run, and the card is back with the author: https://trello.com/c/PTZubVff

The blocker is the openai line in python-mcp-client/source-code-final/pyproject.toml, which this PR leaves at openai>=2.6.1. Unbounded, it resolved to 3.11.0 in the regenerated uv.lock — while the tutorial caps readers at openai>=2.6.1,<3.0 and the CMS dependencies field still says openai==2.6.1. Readers get 2.x, the materials they download run 3.x.

The mcp specifier here (mcp>=2.2.0,<3) is also cosmetically off from the tutorial's mcp>=2.2.0,<3.0 — same meaning, but the article shows a pyproject.toml that isn't byte-identical to this one.

Resolution agreed on the card is to move to the latest openai SDK across the tutorial, this folder, and the CMS field, then regenerate the lock. Leaving this open for that push.

The mcp migration and the Windows fix themselves look right to me — no objection to either.

- openai>=2.6.1 (unbounded) resolved to 3.11.0 in uv.lock, drifting from
  the article's pinned 2.6.1 and causing a version mismatch reviewers
  flagged. Pin to openai>=3.16.2,<4.0 to match the article and regenerate
  uv.lock.
- Bump MODEL to gpt-5.4-mini (gpt-4o-mini's mini-tier successor) since
  gpt-4o-mini's currency was already flagged as unverified.
- gpt-5.4-mini rejects the max_tokens param; switch both
  chat.completions.create() calls to max_completion_tokens.
- mcp 2.x renamed Tool.inputSchema to Tool.input_schema. _get_tools()
  still read the old camelCase name via getattr(), which silently fell
  back to an empty schema and dropped every tool's parameters. Fixed to
  input_schema and verified live that tool-call arguments are populated
  correctly again.
- Sync the mcp specifier's upper bound (<3 -> <3.0) with the article.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants