Problem class: python-mcp-sdk-serverinfo-version-fallback
Verified end-to-end in a clean Python 3.11 venv with mcp==1.28.1: reproduced the 1.28.1 fallback, applied the one-line stamp, watched the e2e test go RED→GREEN. Solution written to ~/SOLUTION-mcp-serverinfo-version.md.
FastMCP serverInfo version fallback (mcp 1.28.1)Problem class: python-mcp-sdk-serverinfo-version-fallback
Environment: Python 3.11 · mcp 1.28.1 (stdio server, FastMCP API) · chimera 0.2.6
An MCP server built with FastMCP("chimera") advertised the mcp SDK's own distribution version in its initialize handshake:
{"serverInfo": {"name": "chimera", "version": "1.28.1"}}
even though the package's real version was 0.2.6 (chimera --version printed 0.2.6, pyproject.toml said 0.2.6). Clients could not tell which build they had reached. Packaging is fine; only the MCP handshake is wrong.
FastMCP.__init__ on mcp 1.28.1 accepts no version argument and its Settings model has no version field — there is no public seam to pass one:
$ python -c "import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))"
# no `version=` parameter anywhere in the signature
Chain of custody:
mcp/server/fastmcp/server.py:205 — FastMCP builds the low-level server with no version:
python
self._mcp_server = MCPServer(
name=name or "FastMCP",
instructions=instructions,
...
)mcp/server/lowlevel/server.py:151 — the low-level server stores whatever it got (here None):
python
self.version = versionmcp/server/lowlevel/server.py:183 — the initialize handler silently falls back to the mcp distribution's version:
python
server_version=self.version if self.version else pkg_version("mcp"),Every transport (stdio, SSE, streamable HTTP) reads this same low-level Server object, so the wrong value is inherited uniformly.
# 1. Is the fallback in play? No `version` parameter == yes.
python -c "import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))"
# 2. Every place the SDK silently borrows its own version:
grep -rn 'pkg_version("mcp")' "$(python -c 'import mcp, os; print(os.path.dirname(mcp.__file__))')"
# 3. Compare surfaces. If the REST/FastAPI surface reports the package version
# but the MCP handshake does not, it is this class — the wheel is fine.
Stamp the package version onto the low-level server immediately after constructing FastMCP, with a note to remove the private access when the SDK grows a real seam.
src/chimera/server.py:
from mcp.server.fastmcp import FastMCP
from . import __version__ # 0.2.6, read from installed distribution metadata
def build_server() -> FastMCP:
server = FastMCP("chimera")
# mcp 1.28.1 has no public seam for the server version: FastMCP.__init__
# takes no `version` kwarg and its pydantic Settings model has no version
# field, so the low-level server falls back to pkg_version("mcp") in its
# initialize handler (mcp/server/lowlevel/server.py:183). Stamp ours here.
# SWITCH TO THE KWARG when the mcp pin is bumped (mcp 2.x or any SDK that
# grows a `version` kwarg / Settings field) and delete this line.
server._mcp_server.version = __version__
return server
server = build_server()
@server.tool()
def ping() -> str:
"""Liveness probe."""
return "pong"
def main() -> None:
server.run(transport="stdio")
Version source must be the installed distribution, not a duplicated literal:
# src/chimera/__init__.py
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version("chimera")
except PackageNotFoundError: # pragma: no cover - source checkout
__version__ = "0.2.6"
The chimera mcp click subcommand inherits the fix automatically as long as it calls the same build_server() (or imports the module-level server), because all transports share that low-level Server.
Unit tests pin the wiring; a subprocess test over stdio pins the wire.
import json, subprocess, sys
from importlib.metadata import version
from pathlib import Path
import pytest
from chimera import __version__
from chimera.server import build_server
def _initialize_request() -> str:
return json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "probe", "version": "0"},
},
}) + "\n"
def test_builder_pins_lowlevel_version() -> None:
"""Wiring."""
assert build_server()._mcp_server.version == __version__
def _handshake(argv: list[str]) -> dict:
proc = subprocess.run(argv, input=_initialize_request(),
capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, proc.stderr
line = next(ln for ln in proc.stdout.splitlines() if '"result"' in ln)
return json.loads(line)["result"]
def test_real_mcp_entry_point_reports_package_version() -> None:
"""Wire: the shipped console script must not leak the mcp SDK version."""
exe = Path(sys.executable).parent / "chimera-mcp"
info = _handshake([str(exe)])["serverInfo"]
assert info["name"] == "chimera"
assert info["version"] == __version__
assert info["version"] != version("mcp") # explicit: never the SDK fallback
--expected-versionimport argparse, json, subprocess
from importlib.metadata import version
parser = argparse.ArgumentParser()
parser.add_argument("--expected-version")
args = parser.parse_args()
req = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "probe", "version": "0"}}}) + "\n"
proc = subprocess.run(["chimera-mcp"], input=req, capture_output=True, text=True)
info = json.loads(next(l for l in proc.stdout.splitlines() if '"result"' in l))["result"]["serverInfo"]
expected = args.expected_version or version("chimera")
assert info["version"] == expected, f"handshake {info['version']} != released {expected}"
assert info["version"] != version("mcp"), "serverInfo fell back to the mcp SDK version"
print(f"OK serverInfo={info['name']}@{info['version']}")
Wire into CI as python tools/probe_handshake.py --expected-version="$(cat VERSION)".
Reproduced in a clean Python 3.11 venv with mcp==1.28.1.
Before (wiring removed):
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera-mcp
... "serverInfo":{"name":"chimera","version":"1.28.1"}
$ .venv/bin/python -m pytest tests/test_mcp.py::test_real_mcp_entry_point_reports_package_version -q
> assert info["version"] == __version__
E AssertionError: assert '1.28.1' == '0.2.6'
1 failed
After (fix applied):
$ .venv/bin/chimera --version
chimera, version 0.2.6
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera-mcp
... "serverInfo":{"name":"chimera","version":"0.2.6"}
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera mcp
... "serverInfo":{"name":"chimera","version":"0.2.6"} # click path inherits the fix
$ .venv/bin/python -m pytest tests/ -q
3 passed, 1 skipped
Checks: serverInfo.version == __version__ (0.2.6), != pkg_version("mcp") (1.28.1), and both the console script and chimera mcp agree.
When bumping the mcp pin, re-run diagnostic #1. The moment version appears as a FastMCP.__init__ parameter (or Settings field, e.g. mcp 2.x), switch to:
server = FastMCP("chimera", version=__version__)
and delete the server._mcp_server.version = ... line. The subprocess regression test stays in place either way.
# Evidence - Problem class: python-mcp-sdk-serverinfo-version-fallback - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T17:19:28.757Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: an MCP server built with `FastMCP(\"chimera\")` advertised the mcp SDK's own distribution version in its initialize handshake: serverInfo = {\"name\": \"chimera\", \"version\": \"1.28.1\"} for a package whose real version was 0.2.6 (`chimera --version` printed 0.2.6, pyproject version = 0.2.6). MCP clients could not tell which build they had reached.\n\nROOT CAUSE (trace, not guess): `FastMCP.__init__` on mcp 1.28.1 takes no `version` parameter at all (verified with inspect.signature) and its pydantic Settings model has no version field, so there is no public seam to pass one. FastMCP then constructs the low-level server without a version (mcp/server/fastmcp/server.py:205, `self._mcp_server = MCPServer(`). The low-level server stores it at mcp/server/lowlevel/server.py:151 (`self.version = version`) and the initialize handler at line 183 uses `server_version=self.version if self.version else pkg_version(\"mcp\")` \u2014 so an unset version silently falls back to the mcp DISTRIBUTION's version, which is exactly what clients see.\n\nFIX: in the server builder, immediately after `server = FastMCP(\"chimera\")`, set `server._mcp_server.version = __version__` (the package version), with a comment naming the SDK gap and instructing the next maintainer to switch to the kwarg when the mcp pin is bumped (mcp 2.x or any SDK that grows a version kwarg / Settings field). Verified live: the real console script now answers {\"name\":\"chimera\",\"version\":\"0.2.6\"} and the click path (`chimera mcp`) inherits it, because every transport reads the same low-level server object.\n\nDIAGNOSTIC SHORTCUTS: (1) `python -c \"import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))\"` \u2014 if there is no `version` parameter, the fallback is in play. (2) grep the SDK for `pkg_version(\"mcp\")`: anywhere that string appears is a place where a missing setting silently inherits the library's own version. (3) Compare surfaces: if the REST/FastAPI surface reports the package version but the MCP handshake does not, it is this class, not a packaging bug \u2014 the wheel is fine.\n\nREGRESSION GUARD: a test that spawns the REAL MCP console script, writes one initialize request to stdin and asserts result.serverInfo.version == the package __version__ (and, explicitly, is NOT the mcp distribution version). Unit tests that only call the builder pin the wiring; only the subprocess test pins the wire. Add a `--expected-version=X` mode to any release-surface probe so a tag pipeline fails loudly when the handshake version drifts from the released version.", "environment": "Python 3.11, mcp python-sdk 1.28.1 (stdio MCP server), FastMCP API", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "python-mcp-sdk-serverinfo-version-fallback", "provider": "openrouter", "solved_at": "2026-09-17T17:19:28.757Z", "version": "chimera-deliberation 0.2.6 / mcp 1.28.1"}Verified end-to-end in a clean Python 3.11 venv with mcp==1.28.1: reproduced the 1.28.1 fallback, applied the one-line stamp, watched the e2e test go RED→GREEN. Solution written to ~/SOLUTION-mcp-serverinfo-version.md.
FastMCP serverInfo version fallback (mcp 1.28.1)Problem class: python-mcp-sdk-serverinfo-version-fallback
Environment: Python 3.11 · mcp 1.28.1 (stdio server, FastMCP API) · chimera 0.2.6
An MCP server built with FastMCP("chimera") advertised the mcp SDK's own distribution version in its initialize handshake:
{"serverInfo": {"name": "chimera", "version": "1.28.1"}}
even though the package's real version was 0.2.6 (chimera --version printed 0.2.6, pyproject.toml said 0.2.6). Clients could not tell which build they had reached. Packaging is fine; only the MCP handshake is wrong.
FastMCP.__init__ on mcp 1.28.1 accepts no version argument and its Settings model has no version field — there is no public seam to pass one:
$ python -c "import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))"
# no `version=` parameter anywhere in the signature
Chain of custody:
mcp/server/fastmcp/server.py:205 — FastMCP builds the low-level server with no version:
python
self._mcp_server = MCPServer(
name=name or "FastMCP",
instructions=instructions,
...
)mcp/server/lowlevel/server.py:151 — the low-level server stores whatever it got (here None):
python
self.version = versionmcp/server/lowlevel/server.py:183 — the initialize handler silently falls back to the mcp distribution's version:
python
server_version=self.version if self.version else pkg_version("mcp"),Every transport (stdio, SSE, streamable HTTP) reads this same low-level Server object, so the wrong value is inherited uniformly.
# 1. Is the fallback in play? No `version` parameter == yes.
python -c "import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))"
# 2. Every place the SDK silently borrows its own version:
grep -rn 'pkg_version("mcp")' "$(python -c 'import mcp, os; print(os.path.dirname(mcp.__file__))')"
# 3. Compare surfaces. If the REST/FastAPI surface reports the package version
# but the MCP handshake does not, it is this class — the wheel is fine.
Stamp the package version onto the low-level server immediately after constructing FastMCP, with a note to remove the private access when the SDK grows a real seam.
src/chimera/server.py:
from mcp.server.fastmcp import FastMCP
from . import __version__ # 0.2.6, read from installed distribution metadata
def build_server() -> FastMCP:
server = FastMCP("chimera")
# mcp 1.28.1 has no public seam for the server version: FastMCP.__init__
# takes no `version` kwarg and its pydantic Settings model has no version
# field, so the low-level server falls back to pkg_version("mcp") in its
# initialize handler (mcp/server/lowlevel/server.py:183). Stamp ours here.
# SWITCH TO THE KWARG when the mcp pin is bumped (mcp 2.x or any SDK that
# grows a `version` kwarg / Settings field) and delete this line.
server._mcp_server.version = __version__
return server
server = build_server()
@server.tool()
def ping() -> str:
"""Liveness probe."""
return "pong"
def main() -> None:
server.run(transport="stdio")
Version source must be the installed distribution, not a duplicated literal:
# src/chimera/__init__.py
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version("chimera")
except PackageNotFoundError: # pragma: no cover - source checkout
__version__ = "0.2.6"
The chimera mcp click subcommand inherits the fix automatically as long as it calls the same build_server() (or imports the module-level server), because all transports share that low-level Server.
Unit tests pin the wiring; a subprocess test over stdio pins the wire.
import json, subprocess, sys
from importlib.metadata import version
from pathlib import Path
import pytest
from chimera import __version__
from chimera.server import build_server
def _initialize_request() -> str:
return json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "probe", "version": "0"},
},
}) + "\n"
def test_builder_pins_lowlevel_version() -> None:
"""Wiring."""
assert build_server()._mcp_server.version == __version__
def _handshake(argv: list[str]) -> dict:
proc = subprocess.run(argv, input=_initialize_request(),
capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, proc.stderr
line = next(ln for ln in proc.stdout.splitlines() if '"result"' in ln)
return json.loads(line)["result"]
def test_real_mcp_entry_point_reports_package_version() -> None:
"""Wire: the shipped console script must not leak the mcp SDK version."""
exe = Path(sys.executable).parent / "chimera-mcp"
info = _handshake([str(exe)])["serverInfo"]
assert info["name"] == "chimera"
assert info["version"] == __version__
assert info["version"] != version("mcp") # explicit: never the SDK fallback
--expected-versionimport argparse, json, subprocess
from importlib.metadata import version
parser = argparse.ArgumentParser()
parser.add_argument("--expected-version")
args = parser.parse_args()
req = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "probe", "version": "0"}}}) + "\n"
proc = subprocess.run(["chimera-mcp"], input=req, capture_output=True, text=True)
info = json.loads(next(l for l in proc.stdout.splitlines() if '"result"' in l))["result"]["serverInfo"]
expected = args.expected_version or version("chimera")
assert info["version"] == expected, f"handshake {info['version']} != released {expected}"
assert info["version"] != version("mcp"), "serverInfo fell back to the mcp SDK version"
print(f"OK serverInfo={info['name']}@{info['version']}")
Wire into CI as python tools/probe_handshake.py --expected-version="$(cat VERSION)".
Reproduced in a clean Python 3.11 venv with mcp==1.28.1.
Before (wiring removed):
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera-mcp
... "serverInfo":{"name":"chimera","version":"1.28.1"}
$ .venv/bin/python -m pytest tests/test_mcp.py::test_real_mcp_entry_point_reports_package_version -q
> assert info["version"] == __version__
E AssertionError: assert '1.28.1' == '0.2.6'
1 failed
After (fix applied):
$ .venv/bin/chimera --version
chimera, version 0.2.6
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera-mcp
... "serverInfo":{"name":"chimera","version":"0.2.6"}
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}\n' | .venv/bin/chimera mcp
... "serverInfo":{"name":"chimera","version":"0.2.6"} # click path inherits the fix
$ .venv/bin/python -m pytest tests/ -q
3 passed, 1 skipped
Checks: serverInfo.version == __version__ (0.2.6), != pkg_version("mcp") (1.28.1), and both the console script and chimera mcp agree.
When bumping the mcp pin, re-run diagnostic #1. The moment version appears as a FastMCP.__init__ parameter (or Settings field, e.g. mcp 2.x), switch to:
server = FastMCP("chimera", version=__version__)
and delete the server._mcp_server.version = ... line. The subprocess regression test stays in place either way.
# Evidence - Problem class: python-mcp-sdk-serverinfo-version-fallback - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T17:19:28.757Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: an MCP server built with `FastMCP(\"chimera\")` advertised the mcp SDK's own distribution version in its initialize handshake: serverInfo = {\"name\": \"chimera\", \"version\": \"1.28.1\"} for a package whose real version was 0.2.6 (`chimera --version` printed 0.2.6, pyproject version = 0.2.6). MCP clients could not tell which build they had reached.\n\nROOT CAUSE (trace, not guess): `FastMCP.__init__` on mcp 1.28.1 takes no `version` parameter at all (verified with inspect.signature) and its pydantic Settings model has no version field, so there is no public seam to pass one. FastMCP then constructs the low-level server without a version (mcp/server/fastmcp/server.py:205, `self._mcp_server = MCPServer(`). The low-level server stores it at mcp/server/lowlevel/server.py:151 (`self.version = version`) and the initialize handler at line 183 uses `server_version=self.version if self.version else pkg_version(\"mcp\")` \u2014 so an unset version silently falls back to the mcp DISTRIBUTION's version, which is exactly what clients see.\n\nFIX: in the server builder, immediately after `server = FastMCP(\"chimera\")`, set `server._mcp_server.version = __version__` (the package version), with a comment naming the SDK gap and instructing the next maintainer to switch to the kwarg when the mcp pin is bumped (mcp 2.x or any SDK that grows a version kwarg / Settings field). Verified live: the real console script now answers {\"name\":\"chimera\",\"version\":\"0.2.6\"} and the click path (`chimera mcp`) inherits it, because every transport reads the same low-level server object.\n\nDIAGNOSTIC SHORTCUTS: (1) `python -c \"import inspect, mcp.server.fastmcp.server as s; print(inspect.signature(s.FastMCP.__init__))\"` \u2014 if there is no `version` parameter, the fallback is in play. (2) grep the SDK for `pkg_version(\"mcp\")`: anywhere that string appears is a place where a missing setting silently inherits the library's own version. (3) Compare surfaces: if the REST/FastAPI surface reports the package version but the MCP handshake does not, it is this class, not a packaging bug \u2014 the wheel is fine.\n\nREGRESSION GUARD: a test that spawns the REAL MCP console script, writes one initialize request to stdin and asserts result.serverInfo.version == the package __version__ (and, explicitly, is NOT the mcp distribution version). Unit tests that only call the builder pin the wiring; only the subprocess test pins the wire. Add a `--expected-version=X` mode to any release-surface probe so a tag pipeline fails loudly when the handshake version drifts from the released version.", "environment": "Python 3.11, mcp python-sdk 1.28.1 (stdio MCP server), FastMCP API", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "python-mcp-sdk-serverinfo-version-fallback", "provider": "openrouter", "solved_at": "2026-09-17T17:19:28.757Z", "version": "chimera-deliberation 0.2.6 / mcp 1.28.1"}