From c541bb8603b966ebf92f593b0a267140198a2c26 Mon Sep 17 00:00:00 2001 From: Arpan Mondal Date: Tue, 15 Sep 2026 20:50:25 +0530 Subject: [PATCH] [v1.x] Fall back to "unknown" when package version metadata is None `Server.create_initialization_options` fills `server_version` from `pkg_version("mcp")` when no explicit version is set. That helper caught exceptions from `importlib.metadata.version` but not the case where it returns None instead of a string, which happens on some environments (an embedded Python whose package metadata is present but unreadable). Because `InitializationOptions.server_version` is typed `str`, the None propagated into the model and raised a ValidationError, killing the server before the stdio handshake and surfacing to the client as a bare "Connection closed". Fall back to "unknown" for a None (or empty) version, matching the helper's existing behaviour on failure. Add a regression test. Github-Issue: #3487 Reported-by: j-brumback --- src/mcp/server/lowlevel/server.py | 6 +++++- .../server/test_lowlevel_input_validation.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 25a8fde37c..0c25ac9b05 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -172,7 +172,11 @@ def pkg_version(package: str) -> str: try: from importlib.metadata import version - return version(package) + # `version()` can return None (not just raise) on some + # environments, e.g. an embedded Python whose package metadata + # is present but unreadable; `server_version` is typed `str`, + # so fall back to "unknown" rather than let None crash init. + return version(package) or "unknown" except Exception: # pragma: no cover pass diff --git a/tests/server/test_lowlevel_input_validation.py b/tests/server/test_lowlevel_input_validation.py index 0614ad7c46..f317925935 100644 --- a/tests/server/test_lowlevel_input_validation.py +++ b/tests/server/test_lowlevel_input_validation.py @@ -309,3 +309,22 @@ async def test_callback(client_session: ClientSession) -> CallToolResult: assert any( "Tool 'unknown_tool' not listed, no validation will be performed" in record.message for record in caplog.records ) + + +def test_create_initialization_options_falls_back_when_package_version_is_none( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A None from importlib.metadata.version does not crash create_initialization_options. + + On some environments (e.g. an embedded Python whose package metadata is present but + unreadable) importlib.metadata.version returns None instead of raising. server_version is + typed str, so without a guard that None reaches InitializationOptions and raises a + ValidationError, killing the server before the stdio handshake. It must fall back to + "unknown" instead. + """ + monkeypatch.setattr("importlib.metadata.version", lambda package: None) + + # No explicit version, so create_initialization_options falls back to pkg_version("mcp"). + options = Server("test-server").create_initialization_options() + + assert options.server_version == "unknown"