Skip to content

Commit 473c92f

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 473c92f

13 files changed

Lines changed: 236 additions & 43 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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
29+
except Exception: # pragma: no cover
2930
# If we can't resolve type hints, we can't find the context parameter
3031
return None
3132

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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
316+
async for message in server_session.incoming_messages: # pragma: no cover
316317
await server._handle_message(message, server_session, {}, False)
317318

318319
tg.start_soon(handle_messages)
@@ -391,8 +392,8 @@ async def run_server():
391392
),
392393
) as server_session:
393394
async with anyio.create_task_group() as tg:
394-
395-
async def handle_messages():
395+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
396+
async def handle_messages(): # pragma: no cover
396397
async for message in server_session.incoming_messages:
397398
await server._handle_message(message, server_session, {}, False)
398399

tests/server/test_lowlevel_input_validation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
74+
async for message in server_session.incoming_messages: # pragma: no cover
7475
await server._handle_message(message, server_session, {}, False)
7576

7677
tg.start_soon(handle_messages)

tests/server/test_lowlevel_output_validation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
75+
async for message in server_session.incoming_messages: # pragma: no cover
7576
await server._handle_message(message, server_session, {}, False)
7677

7778
tg.start_soon(handle_messages)

tests/server/test_lowlevel_tool_annotations.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
71+
async for message in server_session.incoming_messages: # pragma: no cover
7172
await server._handle_message(message, server_session, {}, False)
7273

7374
tg.start_soon(handle_messages)

tests/server/test_session.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,9 @@ 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+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
414+
with anyio.move_on_after(0.01): # pragma: no cover
415+
await session.create_message(messages=[], max_tokens=100)
418416

419417

420418
@pytest.mark.anyio

0 commit comments

Comments
 (0)