Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
dep-resolution:
- name: lowest-direct
install-flags: "--upgrade --resolution lowest-direct"
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ When bumping a dependency version manually, update the constraint in `pyproject.

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

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

## Triage Process

Expand Down
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"anyio>=4.5",
"httpx>=0.27.1,<1.0.0",
"httpx-sse>=0.4",
"pydantic>=2.11.0,<3.0.0",
"starlette>=0.27",
"pydantic>=2.12.0,<3.0.0; python_version >= '3.14'",
"pydantic>=2.11.0,<3.0.0; python_version < '3.14'",
"starlette>=0.48.0; python_version >= '3.14'",
"starlette>=0.27; python_version < '3.14'",
"python-multipart>=0.0.9",
"sse-starlette>=1.6.1",
"pydantic-settings>=2.5.2",
"uvicorn>=0.31.1; sys_platform != 'emscripten'",
"jsonschema>=4.20.0",
"pywin32>=310; sys_platform == 'win32'",
"pywin32>=311; sys_platform == 'win32' and python_version >= '3.14'",
"pywin32>=310; sys_platform == 'win32' and python_version < '3.14'",
"pyjwt[crypto]>=2.10.1",
"typing-extensions>=4.9.0",
"typing-inspection>=0.4.1",
Expand Down Expand Up @@ -67,7 +71,7 @@ dev = [
docs = [
"mkdocs>=1.6.1",
"mkdocs-glightbox>=0.4.0",
"mkdocs-material[imaging]>=9.5.45",
"mkdocs-material[imaging]>=9.6.19",
"mkdocstrings-python>=1.12.2",
]

Expand Down
2 changes: 1 addition & 1 deletion src/mcp/server/fastmcp/utilities/context_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def find_context_parameter(fn: Callable[..., Any]) -> str | None:
# Get type hints to properly resolve string annotations
try:
hints = typing.get_type_hints(fn)
except Exception:
except Exception: # pragma: no cover
# If we can't resolve type hints, we can't find the context parameter
return None

Expand Down
4 changes: 1 addition & 3 deletions src/mcp/server/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,5 @@ async def _handle_incoming(self, req: ServerRequestResponder) -> None:
await self._incoming_message_stream_writer.send(req)

@property
def incoming_messages(
self,
) -> MemoryObjectReceiveStream[ServerRequestResponder]:
def incoming_messages(self) -> MemoryObjectReceiveStream[ServerRequestResponder]:
return self._incoming_message_stream_reader
4 changes: 2 additions & 2 deletions tests/experimental/tasks/server/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ async def run_server():
async with anyio.create_task_group() as tg:

async def handle_messages():
async for message in server_session.incoming_messages:
async for message in server_session.incoming_messages: # pragma: no cover
await server._handle_message(message, server_session, {}, False)

tg.start_soon(handle_messages)
Expand Down Expand Up @@ -392,7 +392,7 @@ async def run_server():
) as server_session:
async with anyio.create_task_group() as tg:

async def handle_messages():
async def handle_messages(): # pragma: no cover
async for message in server_session.incoming_messages:
await server._handle_message(message, server_session, {}, False)

Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_lowlevel_input_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def run_server():
async with anyio.create_task_group() as tg:

async def handle_messages():
async for message in server_session.incoming_messages:
async for message in server_session.incoming_messages: # pragma: no cover
await server._handle_message(message, server_session, {}, False)

tg.start_soon(handle_messages)
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_lowlevel_output_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def run_server():
async with anyio.create_task_group() as tg:

async def handle_messages():
async for message in server_session.incoming_messages:
async for message in server_session.incoming_messages: # pragma: no cover
await server._handle_message(message, server_session, {}, False)

tg.start_soon(handle_messages)
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_lowlevel_tool_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async def run_server():
async with anyio.create_task_group() as tg:

async def handle_messages():
async for message in server_session.incoming_messages:
async for message in server_session.incoming_messages: # pragma: no cover
await server._handle_message(message, server_session, {}, False)

tg.start_soon(handle_messages)
Expand Down
7 changes: 2 additions & 5 deletions tests/server/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,8 @@ async def test_create_message_tool_result_validation():

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


@pytest.mark.anyio
Expand Down
4 changes: 3 additions & 1 deletion tests/server/test_sse_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ async def hold_sse_connection() -> None:
scope = _sse_scope("GET", "/sse", creator_user)
with anyio.fail_after(5):
async with transport.connect_sse(scope, get_receive, get_send) as (read_stream, write_stream):
async with read_stream, write_stream:
async with read_stream, write_stream: # pragma: no branch
# ^ coverage.py misses the ->exit arc on 3.11+ when the body
# is nested inside multiple async with blocks
async for _ in read_stream:
pass

Expand Down
12 changes: 6 additions & 6 deletions tests/shared/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async def make_request(client_session: ClientSession):
)

# Give cancellation time to process
with anyio.fail_after(1):
with anyio.fail_after(1): # pragma: no cover
await ev_cancelled.wait()


Expand Down Expand Up @@ -176,7 +176,7 @@ async def make_request(client_session: ClientSession):
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)

with anyio.fail_after(2):
with anyio.fail_after(2): # pragma: no cover
await ev_response_received.wait()

assert len(result_holder) == 1
Expand Down Expand Up @@ -232,7 +232,7 @@ async def make_request(client_session: ClientSession):
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)

with anyio.fail_after(2):
with anyio.fail_after(2): # pragma: no cover
await ev_error_received.wait()

assert len(error_holder) == 1
Expand Down Expand Up @@ -289,7 +289,7 @@ async def make_request(client_session: ClientSession):
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)

with anyio.fail_after(2):
with anyio.fail_after(2): # pragma: no cover
await ev_timeout.wait()


Expand Down Expand Up @@ -335,7 +335,7 @@ async def mock_server():
tg.start_soon(make_request, client_session)
tg.start_soon(mock_server)

with anyio.fail_after(1):
with anyio.fail_after(1): # pragma: no cover
await ev_closed.wait()
with anyio.fail_after(1):
with anyio.fail_after(1): # pragma: no cover
await ev_response.wait()
Loading
Loading