modelcontextprotocol / modelcontextprotocol/python-sdk
pkg_version() in Server.create_initialization_options doesn't guard against importlib.metadata.version() returning None
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.3k
- Forks
- 4k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 31
Description
Summary
Server.create_initialization_options in mcp/server/lowlevel/server.py:183 falls through to pkg_version("mcp") when self.version is falsy. The fallback function catches exceptions and returns "unknown", but doesn't guard against the (rare but real) case where importlib.metadata.version() returns None instead of raising or returning a string.
When that happens, None flows through to InitializationOptions.server_version — which is typed as str on the pydantic model — and pydantic raises ValidationError. 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,<2 pip-installed into the embedded Python's site-packages via the standard python.exe -m pip install command.
Probe from the affected Python:
from importlib.metadata import version
print(repr(version("mcp"))) # prints: None
The mcp-1.30.0.dist-info/METADATA file on disk correctly declares Version: 1.30.0. Root cause of the None return 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 where importlib.metadata.version("mcp") returns None instead of a string, crashes on stdio startup:
Exception Group Traceback (most recent call last):
File "…/mcp/server/fastmcp/server.py", line 775, in run_stdio_async
self._mcp_server.create_initialization_options(),
File "…/mcp/server/lowlevel/server.py", line 181, in create_initialization_options
return InitializationOptions(
File "…/pydantic/main.py", line 263, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for InitializationOptions
server_version
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
Full traceback from an affected install:
2026-09-09 14:44:00,841 [INFO] __main__: Starting GRAccess MCP server (stdio transport)
+ Exception Group Traceback (most recent call last):
| File "<frozen runpy>", line 198, in _run_module_as_main
| File "…/mcp/server/fastmcp/server.py", line 312, in run
| anyio.run(self.run_stdio_async)
| File "…/mcp/server/fastmcp/server.py", line 771, in run_stdio_async
| async with stdio_server() as (read_stream, write_stream):
| File "…/mcp/server/lowlevel/server.py", line 181, in create_initialization_options
| return InitializationOptions(
| File "…/pydantic/main.py", line 263, in __init__
| validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
| pydantic_core._pydantic_core.ValidationError: 1 validation error for InitializationOptions
| server_version
| Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
Suggested fix
Guard the return of pkg_version against None — matches the existing "returns 'unknown' on failure" contract implied by the type annotation and the # pragma: no cover fallback:
def pkg_version(package: str) -> str:
try:
from importlib.metadata import version
v = version(package)
if v is not None:
return v
except Exception: # pragma: no cover
pass
return "unknown" # pragma: no cover
Doesn't change happy-path behavior for well-formed metadata; catches the pathological None case that pydantic then rejects.
Workaround
Setting mcp._mcp_server.version explicitly on the underlying server after FastMCP(...) construction bypasses the fallback entirely. That's what I ended up shipping in my own server.
Version
mcp:1.30.0- Python:
3.12embedded distribution on Windows Server 2022
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in mcp/server/lowlevel/server.py at Server.create_initialization_options and the pkg_version fallback it calls. Reproduce the None return from importlib.metadata.version("mcp"), then verify that initialization produces the existing "unknown" fallback instead of a Pydantic ValidationError and that normal version strings remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100