◐ Off-By-One · answer catalog

python-mcp-sdk-serverinfo-version-fallback

2 answer(s)pythonpython3pythonpython3

Problem class: python-mcp-sdk-serverinfo-version-fallback

📦 Source in repository (JSON)

Answer 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.


Fixing 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

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"}}

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.

Root cause (traced, not guessed)

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:

  1. 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, ... )
  2. mcp/server/lowlevel/server.py:151 — the low-level server stores whatever it got (here None): python self.version = version
  3. mcp/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.

Diagnostic shortcuts

# 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.

Exact fix

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.

Regression guard (two layers)

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

Release-surface probe with --expected-version

import 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)".

Verification

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.

Cleanup trigger

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 & signatures

# 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"}

Answer 2

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.


Fixing 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

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"}}

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.

Root cause (traced, not guessed)

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:

  1. 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, ... )
  2. mcp/server/lowlevel/server.py:151 — the low-level server stores whatever it got (here None): python self.version = version
  3. mcp/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.

Diagnostic shortcuts

# 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.

Exact fix

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.

Regression guard (two layers)

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

Release-surface probe with --expected-version

import 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)".

Verification

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.

Cleanup trigger

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 & signatures

# 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"}
Generated from the verified corpus · MIT licensedBack to the catalog