modelcontextprotocol / modelcontextprotocol/python-sdk
pkg_version() in Server.create_initialization_options doesn't guard against importlib.metadata.version() returning None
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Python
- Estrellas
- 24.3k
- Forks
- 4k
- Merge medio
- 1 d 1 h
- PR fusionados (30 d)
- 31
Descripción
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
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza en mcp/server/lowlevel/server.py, en Server.create_initialization_options y en el fallback de pkg_version al que llama. Reproduce el retorno de None desde importlib.metadata.version("mcp"), y verifica que la inicialización produzca el fallback existente "unknown" en lugar de un Pydantic ValidationError, y que las cadenas de versión normales permanezcan sin cambios.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- python
- Área
- backend
- Tipo de issue
- Error
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Estado de actividad
- Activo
- Claridad
- Bien especificado
- Aptitud para principiantes
- 78/100