Skip to content

Build1 publisher3 min readPublished

Renaming FastMCP to MCPServer closes stdio before an unpinned server finishes its handshake

An AWS EC2 rig with mcp unbounded in requirements.txt picked up mcp 2.2.0 and stopped importing. The server edit is two lines; the pip resolver and the test suite's camelCase attribute reads are where the work sits.

The Engineer · Build desk

Illustration accompanying Renaming FastMCP to MCPServer closes stdio before an unpinned server finishes its handshake

What happened

  • A boto3 EC2 MCP server that listed mcp with no version bound in requirements.txt stopped importing once the host Python moved to mcp 2.2.0, failing on mcp.server.fastmcp.
  • The migration touched two lines of server.py, the import and the constructor, for a server with 15 tools on stdio transport.
  • Upgrading the shared system Python drew a pip warning that strands-agents 1.55.0 requires mcp<2.2,>=1.23.0, and pip installed mcp 2.2.0 regardless.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure Operators whose clients spawn unpinned MCP servers lose the tool list with no traceback in view, so diagnosis has to start by importing the module by hand.
  • constraint One shared interpreter makes another package's ceiling yours: the strands-agents bound decides which mcp release the server can hold, and pip will not stop you crossing it.
  • decision Because v1.x still gets security patches, the pin-or-migrate call turns on how your interpreters are laid out.
  • cost The rename costs two lines of server code; the unbudgeted work sits in test code that reads annotation fields by their camelCase names.

The import failure is legible only when you run the module yourself. `python3 -c "import server"` prints ModuleNotFoundError: No module named 'mcp.server.fastmcp', and the message goes on to name both fixes: import MCPServer from mcp.server.mcpserver, or pin 'mcp<2' to keep running v1 code [5]. Under an MCP client that text goes to a pipe nobody reads. The server launches, dies on the import, and stdio closes before the handshake [4].

The edit itself is two lines in server.py: the import, and the constructor call [6]. Nothing else moves, because the instance keeps the name `mcp` and all 15 tools are declared with `@mcp.tool()` [7][1]. For that two-line figure to transfer to another server, this rig's shape has to hold: stdio transport, no client code, 15 handlers that are all async, and no call to `get_running_loop` or `asyncio.run` anywhere in the file [7][8]. The rig reads MCP_SERVER_NAME with `os.getenv` and passes it in as the server's name, so v2 dropping MCP_* environment variables from server settings changes nothing here [9].

Make both line changes in one edit. The linter is correct about the unused import and unhelpful about the timing: an editor running `ruff check --fix` on save deletes it, and the next edit leaves MCPServer undefined [10].

The three ToolAnnotations(...) lines keep their camelCase keywords, and the author calls that deliberate and safe [11]. Reading those same names back is a different matter. The test suite contains two camelCase attribute reads, `tool.annotations.destructiveHint` and `inputSchema["properties"]` [12], and unittest errors on the first with AttributeError: 'ToolAnnotations' object has no attribute 'destructiveHint' [13]. The grep that finds them looks in tests/*.py, and the article says it is the one a FastMCP-only checklist misses [12].

On the shared interpreter, the upgrade printed a resolver complaint and then installed anyway: strands-agents 1.55.0 requires mcp<2.2,>=1.23.0, but you have mcp 2.2.0 which is incompatible, followed by Successfully installed mcp-2.2.0 mcp-types-2.2.0 [14]. pip warns and proceeds [15]. Read the specifier and the conflict narrows: it excludes 2.2.0 while admitting the 2.0 and 2.1 series, so the bound is not an objection to the rename [2]. The article's advice is to check that bound before choosing a 2.x release, or to give the two projects separate interpreters [15].

Pinning remains a working configuration. The migration guide says the v1.x line keeps receiving critical bug fixes and security patches [16], and this rig migrated for a layout reason instead: every project on the host installs into one system Python with no virtualenvs, and the deployment image uses the same layout [17]. mcp 2.x declares Requires-Python >=3.10 [18]. Reproducing any of this needs no AWS credentials, since the tests are offline by design and the stdio check only lists tools without running a handler [20]. On mcp 1.30.0 the unchanged code passes all 27 of its tests [19].

What to watch

  • Whether strands-agents raises its mcp<2.2 bound; until it does, one interpreter cannot hold both it and mcp 2.2.0.
  • Whether later 2.x releases keep accepting camelCase keywords in ToolAnnotations while attribute access stays snake_case.
  • How long the v1.x line keeps getting the security patches the migration guide promises, since no end date is given.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories