Repository navigation
pkg_version() in Server.create_initialization_options doesn't guard against importlib.metadata.version() returning None #3487
Description
Activity
Confirmed against
v1.25.0(src/mcp/server/lowlevel/server.py,create_initialization_options→ the localpkg_version()helper, ~line 171):def pkg_version(package: str) -> str: try: from importlib.metadata import version return version(package) except Exception: # pragma: no cover pass return "unknown" # pragma: no cover
importlib.metadata.version()returningNone(rather than raising or returning a string) skips theexceptentirely, soNonereachesInitializationOptions(server_version=...), which is typedstr— pydantic raisesValidationErrorbefore the handshake completes, and the client just sees "Connection closed" with nothing actionable.One thing worth confirming first: this helper doesn't exist in the same form on
main(v2) — the version handling looks like it was restructured there. Is this issue specifically for the 1.x maintenance line, or is there an equivalent path on v2 I should check too?Proposed fix, scoped to 1.x: guard the return value, not just the exception —
def pkg_version(package: str) -> str: try: from importlib.metadata import version v = version(package) return v if v else "unknown" except Exception: # pragma: no cover pass return "unknown" # pragma: no cover
Happy to add a regression test (mocking
importlib.metadata.versionto returnNone) and open a PR against the 1.x branch if that approach looks right and someone can assign this to me.I hit this too and traced the root cause. In
Server.create_initialization_options,server_versionfalls back to the localpkg_version("mcp")helper, which handlesimportlib.metadata.version()raising but not the case where it returnsNone. SinceInitializationOptions.server_versionis typedstr, thatNonereaches the pydantic model and raisesValidationError, so the server dies before the stdio handshake and the client only sees a bare "Connection closed" with no useful diagnostic.It is v1.x-only.
main(v2) setsserver_version=self.versiondirectly and does not go throughpkg_version.Minimal fix that stays consistent with the helper's existing "return
unknownon failure" contract:return version(package) or "unknown"
I have this plus a regression test ready on a
v1.x-based branch (fails before, passes after; full suite and 100% coverage green locally). Happy to open a PR if a maintainer wants an outside fix for this, though of course @j-brumback has first call as the reporter.- @arpankernel that matches what I saw, and your read of the root cause is right: pkg_version only guards against version() raising, not against it returning None, so the None hits the typed server_version field and the server dies before the handshake with nothing useful surfaced to the client. Agreed it's v1.x-only. I only wanted to raise the issue, I'm not planning to fix it. Please go ahead and open the PR, `return version(package) or "unknown"` plus the regression test is the fix I'd want too. [cid:fe2791c8-cc4b-4378-8787-73dcc0bd319f] Joshua Brumback Senior System Consultant Office 513-353-1800 Address 11857 Kemper Springs Drive, Cincinnati, Ohio 45240…________________________________ From: ARPAN MONDAL ***@***.***> Sent: Tuesday, September 15, 2026 11:23 AM To: modelcontextprotocol/python-sdk ***@***.***> Cc: Josh Brumback ***@***.***>; Mention ***@***.***> Subject: Re: [modelcontextprotocol/python-sdk] pkg_version() in Server.create_initialization_options doesn't guard against importlib.metadata.version() returning None (Issue #3487) [https://avatars.githubusercontent.com/u/66848339?s=20&v=4]arpankernel left a comment (modelcontextprotocol/python-sdk#3487)<#3487 (comment)> I hit this too and traced the root cause. In Server.create_initialization_options, server_version falls back to the local pkg_version("mcp") helper, which handles importlib.metadata.version() raising but not the case where it returns None. Since InitializationOptions.server_version is typed str, that None reaches the pydantic model and raises ValidationError, so the server dies before the stdio handshake and the client only sees a bare "Connection closed" — no useful diagnostic. It is v1.x-only: main (v2) sets server_version=self.version directly and does not go through pkg_version. Minimal fix that stays consistent with the helper's existing "return unknown on failure" contract: return version(package) or "unknown" I have this plus a regression test ready on a v1.x-based branch (fails before / passes after; full suite + 100% coverage green locally). Happy to open a PR if a maintainer wants an outside fix for this — though of course @j-brumback<https://github.com/j-brumback> has first call as the reporter. — Reply to this email directly, view it on GitHub<#3487?email_source=notifications&email_token=BQ2DYNDMQD5JYDKJO62VIY35PFNG7A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNRYGI4TIMZXGI2KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5682943724>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/BQ2DYNGJGR62CVIGL65NJJ35PFNG7AVCNFSNUABFKJSXA33TNF2G64TZHM4DMMRVHA2DAMJYHNEXG43VMU5TKNBQGM3DINJTGI32C5QC>. You are receiving this because you were mentioned.Message ID: ***@***.***>
Thanks for the detailed report, and for tracking it down to
pkg_version(). I'm going to close this as not planned.This only affects 1.x. On 2.x the helper is gone: the server reports whatever
version=you give it (an empty string if you give none) and never looks up its own package metadata at startup. The 1.x line only takes critical and security fixes now, and this one is below that bar, since it needs an environment whereimportlib.metadata.version("mcp")returnsNoneand setting the version explicitly gets around it.For anyone else who lands here on 1.x, the workaround from the report is to set
mcp._mcp_server.versionafter constructingFastMCP(...).@arpankernel thanks for putting #3507 together. It will stay closed for the same reason.
If you still see something like this after moving to 2.x, a new issue is very welcome.
Generated by Claude Code
Summary
Server.create_initialization_optionsinmcp/server/lowlevel/server.py:183falls through topkg_version("mcp")whenself.versionis falsy. The fallback function catches exceptions and returns"unknown", but doesn't guard against the (rare but real) case whereimportlib.metadata.version()returnsNoneinstead of raising or returning a string.When that happens,
Noneflows through toInitializationOptions.server_version— which is typed asstron the pydantic model — and pydantic raisesValidationError. The server dies before the stdio handshake completes; the MCP client sees a bare"Connection closed"with no useful diagnostic.Reproduction
Environment where I hit this: Python 3.12 embedded distribution installed via WiX MSI, with
mcp>=1.26,<2pip-installed into the embedded Python'ssite-packagesvia the standardpython.exe -m pip installcommand.Probe from the affected Python:
The
mcp-1.30.0.dist-info/METADATAfile on disk correctly declaresVersion: 1.30.0. Root cause of theNonereturn appears to be a downstream bug in the embedded-Python distribution or its pip metadata setup — a separate concern I'm investigating on my side.Impact
Any FastMCP server that (a) doesn't set an explicit version (FastMCP doesn't accept a
version=kwarg anyway) and (b) runs on a Python whereimportlib.metadata.version("mcp")returnsNoneinstead of a string, crashes on stdio startup:Full traceback from an affected install:
Suggested fix
Guard the return of
pkg_versionagainstNone— matches the existing "returns'unknown'on failure" contract implied by the type annotation and the# pragma: no coverfallback:Doesn't change happy-path behavior for well-formed metadata; catches the pathological
Nonecase that pydantic then rejects.Workaround
Setting
mcp._mcp_server.versionexplicitly on the underlying server afterFastMCP(...)construction bypasses the fallback entirely. That's what I ended up shipping in my own server.Version
mcp:1.30.03.12embedded distribution on Windows Server 2022