Skip to content

Commit d04ce69

Browse files
committed
Support Python 3.14
Add the 3.14 trove classifier, add 3.14 to the CI test matrix, and make the dependency floors installable there: - pydantic >=2.12.0 and starlette >=0.48.0 on 3.14 only (older floors pin pydantic-core 2.33.0 / predate 3.14 support and have no cp314 wheels); floors for <3.14 are unchanged - pywin32 >=311 on 3.14 only (310 ships no cp314 wheels) - mkdocs-material[imaging] >=9.6.19: the 9.5.45 imaging extra pins pillow~=10.2, which has no cp314 wheels and fails to install on the 3.14 lowest-direct lane; 9.6.19 allows pillow 11.x - backport the coverage workarounds from #1834 (coveragepy#1987 branch misreporting on 3.14), plus one no-branch pragma for a nested async-with arc in test_sse_security.py - regenerate uv.lock with the 3.14 resolution fork The README badge lists supported versions from the classifiers of the latest PyPI release, so the badge updates at the next v1.x release. Update CONTRIBUTING.md to say 3.10 through 3.14.
1 parent 6213787 commit d04ce69

13 files changed

Lines changed: 224 additions & 42 deletions

File tree

.github/workflows/shared.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
continue-on-error: true
3636
strategy:
3737
matrix:
38-
python-version: ["3.10", "3.11", "3.12", "3.13"]
38+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
3939
dep-resolution:
4040
- name: lowest-direct
4141
install-flags: "--upgrade --resolution lowest-direct"

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ When bumping a dependency version manually, update the constraint in `pyproject.
7272

7373
Security-relevant dependency updates (P0) are applied within 7 days of public disclosure and backported to active release branches.
7474

75-
The SDK currently supports Python 3.10 through 3.13. New CPython releases are supported within one minor SDK release of their stable release date.
75+
The SDK currently supports Python 3.10 through 3.14. New CPython releases are supported within one minor SDK release of their stable release date.
7676

7777
## Triage Process
7878

pyproject.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,23 @@ classifiers = [
2020
"Programming Language :: Python :: 3.11",
2121
"Programming Language :: Python :: 3.12",
2222
"Programming Language :: Python :: 3.13",
23+
"Programming Language :: Python :: 3.14",
2324
]
2425
dependencies = [
2526
"anyio>=4.5",
2627
"httpx>=0.27.1,<1.0.0",
2728
"httpx-sse>=0.4",
28-
"pydantic>=2.11.0,<3.0.0",
29-
"starlette>=0.27",
29+
"pydantic>=2.12.0,<3.0.0; python_version >= '3.14'",
30+
"pydantic>=2.11.0,<3.0.0; python_version < '3.14'",
31+
"starlette>=0.48.0; python_version >= '3.14'",
32+
"starlette>=0.27; python_version < '3.14'",
3033
"python-multipart>=0.0.9",
3134
"sse-starlette>=1.6.1",
3235
"pydantic-settings>=2.5.2",
3336
"uvicorn>=0.31.1; sys_platform != 'emscripten'",
3437
"jsonschema>=4.20.0",
35-
"pywin32>=310; sys_platform == 'win32'",
38+
"pywin32>=311; sys_platform == 'win32' and python_version >= '3.14'",
39+
"pywin32>=310; sys_platform == 'win32' and python_version < '3.14'",
3640
"pyjwt[crypto]>=2.10.1",
3741
"typing-extensions>=4.9.0",
3842
"typing-inspection>=0.4.1",
@@ -67,7 +71,7 @@ dev = [
6771
docs = [
6872
"mkdocs>=1.6.1",
6973
"mkdocs-glightbox>=0.4.0",
70-
"mkdocs-material[imaging]>=9.5.45",
74+
"mkdocs-material[imaging]>=9.6.19",
7175
"mkdocstrings-python>=1.12.2",
7276
]
7377

src/mcp/server/fastmcp/utilities/context_injection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def find_context_parameter(fn: Callable[..., Any]) -> str | None:
2525
# Get type hints to properly resolve string annotations
2626
try:
2727
hints = typing.get_type_hints(fn)
28-
except Exception:
28+
except Exception: # pragma: no cover
2929
# If we can't resolve type hints, we can't find the context parameter
3030
return None
3131

src/mcp/server/session.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,5 @@ async def _handle_incoming(self, req: ServerRequestResponder) -> None:
685685
await self._incoming_message_stream_writer.send(req)
686686

687687
@property
688-
def incoming_messages(
689-
self,
690-
) -> MemoryObjectReceiveStream[ServerRequestResponder]:
688+
def incoming_messages(self) -> MemoryObjectReceiveStream[ServerRequestResponder]:
691689
return self._incoming_message_stream_reader

tests/experimental/tasks/server/test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ async def run_server():
312312
async with anyio.create_task_group() as tg:
313313

314314
async def handle_messages():
315-
async for message in server_session.incoming_messages:
315+
async for message in server_session.incoming_messages: # pragma: no cover
316316
await server._handle_message(message, server_session, {}, False)
317317

318318
tg.start_soon(handle_messages)
@@ -392,7 +392,7 @@ async def run_server():
392392
) as server_session:
393393
async with anyio.create_task_group() as tg:
394394

395-
async def handle_messages():
395+
async def handle_messages(): # pragma: no cover
396396
async for message in server_session.incoming_messages:
397397
await server._handle_message(message, server_session, {}, False)
398398

tests/server/test_lowlevel_input_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def run_server():
7070
async with anyio.create_task_group() as tg:
7171

7272
async def handle_messages():
73-
async for message in server_session.incoming_messages:
73+
async for message in server_session.incoming_messages: # pragma: no cover
7474
await server._handle_message(message, server_session, {}, False)
7575

7676
tg.start_soon(handle_messages)

tests/server/test_lowlevel_output_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async def run_server():
7171
async with anyio.create_task_group() as tg:
7272

7373
async def handle_messages():
74-
async for message in server_session.incoming_messages:
74+
async for message in server_session.incoming_messages: # pragma: no cover
7575
await server._handle_message(message, server_session, {}, False)
7676

7777
tg.start_soon(handle_messages)

tests/server/test_lowlevel_tool_annotations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ async def run_server():
6767
async with anyio.create_task_group() as tg:
6868

6969
async def handle_messages():
70-
async for message in server_session.incoming_messages:
70+
async for message in server_session.incoming_messages: # pragma: no cover
7171
await server._handle_message(message, server_session, {}, False)
7272

7373
tg.start_soon(handle_messages)

tests/server/test_session.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,8 @@ async def test_create_message_tool_result_validation():
410410

411411
# Case 8: empty messages list - skips validation entirely
412412
# Covers the `if messages:` branch (line 280->302)
413-
with anyio.move_on_after(0.01):
414-
await session.create_message(
415-
messages=[],
416-
max_tokens=100,
417-
)
413+
with anyio.move_on_after(0.01): # pragma: no cover
414+
await session.create_message(messages=[], max_tokens=100)
418415

419416

420417
@pytest.mark.anyio

0 commit comments

Comments
 (0)