modelcontextprotocol / modelcontextprotocol/python-sdk

pkg_version() in Server.create_initialization_options doesn't guard against importlib.metadata.version() returning None

Aperta Adatta ai principianti
#3,487 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

v1
Lingua principale
Python
Stelle
24.3k
Fork
4k
Merge medio
1g 1h
PR unite (30g)
31

Descrizione

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.12 embedded distribution on Windows Server 2022

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia in mcp/server/lowlevel/server.py, in Server.create_initialization_options e nel fallback pkg_version che richiama. Riproduci la restituzione di None da parte di importlib.metadata.version("mcp"), quindi verifica che l'inizializzazione produca il fallback esistente "unknown" invece di un Pydantic ValidationError e che le normali stringhe di versione rimangano invariate.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
backend
Tipo di issue
Bug
Difficoltà
2/5
Tempo stimato
1-3 ore
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
78/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.