Skip to content

Commit 3ffe142

Browse files
authored
Support Python 3.14 (#1834)
1 parent 1fd557a commit 3ffe142

File tree

11 files changed

+427
-323
lines changed

11 files changed

+427
-323
lines changed

.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"

pyproject.toml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@ 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",
2728
"httpx-sse>=0.4",
28-
"pydantic>=2.11.0,<3.0.0",
29-
"starlette>=0.27",
29+
"pydantic>=2.12.0; python_version >= '3.14'",
30+
"pydantic>=2.11.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'",
3639
"pyjwt[crypto]>=2.10.1",
3740
"typing-extensions>=4.9.0",
3841
"typing-inspection>=0.4.1",
@@ -62,13 +65,14 @@ dev = [
6265
"pytest-pretty>=1.2.0",
6366
"inline-snapshot>=0.23.0",
6467
"dirty-equals>=0.9.0",
65-
"coverage[toml]==7.10.7",
68+
"coverage[toml]>=7.13.1",
69+
"pillow>=12.0",
6670
]
6771
docs = [
6872
"mkdocs>=1.6.1",
6973
"mkdocs-glightbox>=0.4.0",
70-
"mkdocs-material[imaging]>=9.5.45",
71-
"mkdocstrings-python>=1.12.2",
74+
"mkdocs-material>=9.5.45",
75+
"mkdocstrings-python>=2.0.1",
7276
]
7377

7478
[build-system]

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
@@ -704,7 +704,5 @@ async def _handle_incoming(self, req: ServerRequestResponder) -> None:
704704
await self._incoming_message_stream_writer.send(req)
705705

706706
@property
707-
def incoming_messages(
708-
self,
709-
) -> MemoryObjectReceiveStream[ServerRequestResponder]:
707+
def incoming_messages(self) -> MemoryObjectReceiveStream[ServerRequestResponder]:
710708
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

tests/shared/test_session.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ async def make_request(client_session: ClientSession):
124124
)
125125

126126
# Give cancellation time to process
127-
with anyio.fail_after(1):
127+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
128+
with anyio.fail_after(1): # pragma: no cover
128129
await ev_cancelled.wait()
129130

130131

@@ -176,7 +177,8 @@ async def make_request(client_session: ClientSession):
176177
tg.start_soon(mock_server)
177178
tg.start_soon(make_request, client_session)
178179

179-
with anyio.fail_after(2):
180+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
181+
with anyio.fail_after(2): # pragma: no cover
180182
await ev_response_received.wait()
181183

182184
assert len(result_holder) == 1
@@ -232,7 +234,8 @@ async def make_request(client_session: ClientSession):
232234
tg.start_soon(mock_server)
233235
tg.start_soon(make_request, client_session)
234236

235-
with anyio.fail_after(2):
237+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
238+
with anyio.fail_after(2): # pragma: no cover
236239
await ev_error_received.wait()
237240

238241
assert len(error_holder) == 1
@@ -287,7 +290,8 @@ async def make_request(client_session: ClientSession):
287290
tg.start_soon(mock_server)
288291
tg.start_soon(make_request, client_session)
289292

290-
with anyio.fail_after(2):
293+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
294+
with anyio.fail_after(2): # pragma: no cover
291295
await ev_timeout.wait()
292296

293297

@@ -333,7 +337,8 @@ async def mock_server():
333337
tg.start_soon(make_request, client_session)
334338
tg.start_soon(mock_server)
335339

336-
with anyio.fail_after(1):
340+
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
341+
with anyio.fail_after(1): # pragma: no cover
337342
await ev_closed.wait()
338-
with anyio.fail_after(1):
343+
with anyio.fail_after(1): # pragma: no cover
339344
await ev_response.wait()

0 commit comments

Comments
 (0)