Skip to content

Commit b9ca41e

Browse files
committed
Merge main into fix/pre-parse-json-optional-str
2 parents 9173fe8 + 2118f14 commit b9ca41e

1,276 files changed

Lines changed: 125620 additions & 9216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/ISSUE_TEMPLATE/bug.yaml‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,26 @@ body:
1111
id: checks
1212
attributes:
1313
label: Initial Checks
14-
description: Just making sure you're using the latest version of MCP Python SDK.
14+
description: >
15+
Both the 2.x stable line and the 1.x maintenance line are supported, and only
16+
the newest release of each line receives fixes.
1517
options:
16-
- label: I confirm that I'm using the latest version of MCP Python SDK
18+
- label: I confirm that I'm using the newest release of my line (the latest 2.x, or the latest 1.x if I'm still on v1)
1719
required: true
1820
- label: I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
1921
required: true
2022

23+
- type: dropdown
24+
id: release-line
25+
attributes:
26+
label: Release line
27+
description: Which major version of the SDK are you using?
28+
options:
29+
- 2.x (current stable)
30+
- 1.x (maintenance line, v1.x branch)
31+
validations:
32+
required: true
33+
2134
- type: textarea
2235
id: description
2336
attributes:

‎.github/ISSUE_TEMPLATE/v2-feedback.yaml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
name: v2 feedback
22
description: Bugs, API friction, or docs gaps in v2 of the SDK
33
title: "[v2] "
4-
labels: ["v2-alpha"]
4+
labels: ["v2"]
55

66
body:
77
- type: markdown
88
attributes:
99
value: |
10-
Thanks for trying v2. Anything that broke, surprised you, or slowed you down is useful — API feedback is explicitly welcome while v2 is in pre-release.
10+
Anything in v2 that broke, surprised you, or slowed you down is useful, including API friction and docs gaps.
1111
12-
Docs: https://py.sdk.modelcontextprotocol.io/v2/ · Migration from v1: https://py.sdk.modelcontextprotocol.io/v2/migration/
12+
Docs: https://py.sdk.modelcontextprotocol.io/ · Migration from v1: https://py.sdk.modelcontextprotocol.io/migration/
1313
1414
- type: textarea
1515
id: what

‎.github/actions/conformance/client.py‎

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111
--spec-version is omitted the harness picks per-scenario (LATEST_SPEC_VERSION
1212
for active scenarios, DRAFT_PROTOCOL_VERSION for draft-only ones).
1313
- Server URL as last CLI argument (sys.argv[1])
14-
- Must exit 0 within 30 seconds
14+
- Must exit 0 within the harness --timeout (CI passes 60s; the default is 30s)
1515
1616
Scenarios:
1717
initialize - Connect, initialize, list tools, close
1818
tools_call - Connect, call add_numbers(a=5, b=3), close
1919
sse-retry - Connect, call test_reconnection, close
2020
json-schema-ref-no-deref - Connect, list tools (no $ref deref)
21+
json-schema-2020-12-preservation - List tools, echo the focal inputSchema back verbatim
2122
request-metadata - Connect with all callbacks; client stamps _meta
22-
http-standard-headers - Connect, call a tool (Mcp-* headers checked)
23+
http-standard-headers - Tool, resource and prompt round-trips (Mcp-* headers checked)
2324
http-invalid-tool-headers - List tools, call every surfaced tool (x-mcp-header filter)
25+
http-custom-headers - Replay the harness's toolCalls (x-mcp-header -> Mcp-Param-*)
2426
elicitation-sep1034-client-defaults - Elicitation with default accept callback
2527
sep-2322-client-request-state - Drive the MRTR auto-loop (SEP-2322)
2628
auth/client-credentials-jwt - Client credentials with private_key_jwt
@@ -38,7 +40,7 @@
3840
from typing import Any, cast
3941
from urllib.parse import parse_qs, urlparse
4042

41-
import httpx
43+
import httpx2
4244
import mcp_types as types
4345
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
4446
from pydantic import AnyUrl
@@ -151,7 +153,7 @@ async def handle_redirect(self, authorization_url: str) -> None:
151153
"""Fetch the authorization URL and extract the auth code from the redirect."""
152154
logger.debug(f"Fetching authorization URL: {authorization_url}")
153155

154-
async with httpx.AsyncClient() as client:
156+
async with httpx2.AsyncClient() as client:
155157
response = await client.get(
156158
authorization_url,
157159
follow_redirects=False,
@@ -252,6 +254,21 @@ async def run_json_schema_ref_no_deref(server_url: str) -> None:
252254
await client.list_tools()
253255

254256

257+
@register("json-schema-2020-12-preservation")
258+
async def run_json_schema_2020_12_preservation(server_url: str) -> None:
259+
"""List tools, then echo the focal tool's inputSchema back verbatim (SEP-1613 / SEP-2106).
260+
261+
The harness diffs what the client round-trips through `json_schema_echo` against its
262+
fixture to detect 2020-12 keywords ($schema, $defs, $anchor, additionalProperties,
263+
allOf/anyOf, if/then/else) being stripped while parsing tools/list. Unlike
264+
json-schema-ref-no-deref, this mock is version-aware, so client_mode() applies.
265+
"""
266+
async with Client(server_url, mode=client_mode()) as client:
267+
listed = await client.list_tools()
268+
focal = next(tool for tool in listed.tools if tool.name == "json_schema_2020_12_tool")
269+
await client.call_tool("json_schema_echo", {"schema": focal.input_schema})
270+
271+
255272
@register("tools_call")
256273
async def run_tools_call(server_url: str) -> None:
257274
"""Connect, list tools, call add_numbers(a=5, b=3), close."""
@@ -294,11 +311,24 @@ async def run_request_metadata(server_url: str) -> None:
294311

295312
@register("http-standard-headers")
296313
async def run_http_standard_headers(server_url: str) -> None:
297-
"""Connect on the modern path so Mcp-Method / Mcp-Name / MCP-Protocol-Version are sent (SEP-2243)."""
314+
"""Touch tools, resources and prompts on the modern path so each standard header is checked (SEP-2243).
315+
316+
The scenario inspects Mcp-Method on each request and Mcp-Name on tools/call,
317+
resources/read and prompts/get, and reports methods the client never sent as
318+
SKIPPED rather than failed, so exercise one of each. initialize and
319+
notifications/initialized stay SKIPPED: the modern path discovers via
320+
server/discover and never sends them.
321+
"""
298322
async with Client(server_url, mode=client_mode()) as client:
299-
await client.list_tools()
300-
result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
301-
logger.debug(f"add_numbers result: {result}")
323+
tools = await client.list_tools()
324+
if tools.tools:
325+
await client.call_tool(tools.tools[0].name, _stub_required_args(tools.tools[0].input_schema))
326+
resources = await client.list_resources()
327+
if resources.resources:
328+
await client.read_resource(resources.resources[0].uri)
329+
prompts = await client.list_prompts()
330+
if prompts.prompts:
331+
await client.get_prompt(prompts.prompts[0].name)
302332

303333

304334
def _stub_required_args(input_schema: dict[str, Any]) -> dict[str, Any]:
@@ -486,13 +516,13 @@ async def run_enterprise_managed_authorization(server_url: str) -> None:
486516
# learn it from the harness's PRM document (RFC 9728); production
487517
# deployments would supply it as static configuration instead.
488518
prm_url = build_protected_resource_metadata_discovery_urls(None, server_url)[0]
489-
async with httpx.AsyncClient(timeout=30.0) as http:
519+
async with httpx2.AsyncClient(timeout=30.0) as http:
490520
prm = (await http.get(prm_url)).raise_for_status().json()
491521
as_issuer = prm["authorization_servers"][0]
492522

493523
async def fetch_id_jag(audience: str, resource: str) -> str:
494524
"""Leg 1 - RFC 8693 token-exchange at the enterprise IdP."""
495-
async with httpx.AsyncClient(timeout=30.0) as http:
525+
async with httpx2.AsyncClient(timeout=30.0) as http:
496526
resp = await http.post(
497527
idp_token_endpoint,
498528
data={
@@ -563,9 +593,9 @@ async def run_auth_code_client(server_url: str) -> None:
563593
await _run_auth_session(server_url, oauth_auth)
564594

565595

566-
async def _run_auth_session(server_url: str, oauth_auth: httpx.Auth) -> None:
596+
async def _run_auth_session(server_url: str, oauth_auth: httpx2.Auth) -> None:
567597
"""Common session logic for all OAuth flows."""
568-
http_client = httpx.AsyncClient(auth=oauth_auth, timeout=30.0)
598+
http_client = httpx2.AsyncClient(auth=oauth_auth, timeout=30.0)
569599
transport = streamable_http_client(url=server_url, http_client=http_client)
570600
async with Client(transport, mode=client_mode(), elicitation_callback=default_elicitation_callback) as client:
571601
logger.debug("Initialized successfully")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Expected failures at the 2025-11-25 wire, read by the `--suite all
2+
# --spec-version 2025-11-25` server and client legs: every scenario the pinned
3+
# harness marks applicable at 2025-11-25, run over the stateful handshake.
4+
#
5+
# That selection is a superset of the frozen 2025-11-25 requirement set (`npx
6+
# $CONFORMANCE_PKG list --requirements 2025-11-25`), so an entry here for a
7+
# scenario in that set means `conformance tier-check` scores the SDK below 100%
8+
# for 2025-11-25 even though CI is green; link the tracking issue next to any
9+
# such entry.
10+
#
11+
# Kept separate from expected-failures.yml even though the bare `--suite all`
12+
# legs currently run these same scenarios at 2025-11-25 too: the harness's
13+
# default wire is due to move (modelcontextprotocol/conformance#426) and this
14+
# file must keep meaning 2025-11-25. Until then a failure here also needs an
15+
# entry there. The file is passed even while empty: run-client.sh keys its solo
16+
# re-verification off the harness's baseline output.
17+
#
18+
# Baseline established against the harness pinned via CONFORMANCE_PKG in
19+
# .github/workflows/conformance.yml. New conformance releases are adopted by
20+
# deliberately bumping that pin and reconciling all three expected-failures
21+
# files in the same change. Entries MUST be removed as they start passing (the
22+
# runner fails on stale entries).
23+
24+
client: []
25+
26+
server: []

‎.github/actions/conformance/expected-failures.2026-07-28.yml‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
# Expected failures for the carried-forward 2026-07-28 legs
2-
# (`--suite all --spec-version 2026-07-28` for both server and client).
1+
# Expected failures at the 2026-07-28 wire, read by the `--suite all
2+
# --spec-version 2026-07-28` server and client legs: every scenario the pinned
3+
# harness marks applicable at 2026-07-28, run stateless with per-request _meta.
4+
#
5+
# That selection is a superset of the frozen 2026-07-28 requirement set (`npx
6+
# $CONFORMANCE_PKG list --requirements 2026-07-28`), so an entry here for a
7+
# scenario in that set means `conformance tier-check` scores the SDK below 100%
8+
# for 2026-07-28 even though CI is green; link the tracking issue next to any
9+
# such entry.
310
#
411
# This baseline is separate from expected-failures.yml because entries are
5-
# keyed by scenario name only: a scenario that passes at its default version
6-
# in the 2025 legs but fails when forced to 2026-07-28 (or vice versa) cannot
7-
# be expressed in a shared file (the passing leg would flag the entry as
8-
# stale). Like expected-failures.yml, this single file covers both
9-
# directions: the client 2026 leg reads the `client:` section and the server
10-
# 2026 leg reads the `server:` section. Both burn down independently of the
11-
# 2025 legs.
12+
# keyed by scenario name only: a scenario that passes at the harness default
13+
# wire in the bare `--suite all` legs but fails when forced to 2026-07-28 (or
14+
# vice versa) cannot be expressed in a shared file. Where the bare legs already
15+
# default a scenario to 2026-07-28, a failure here also needs an entry there.
1216
#
1317
# Baseline established against the harness pinned via CONFORMANCE_PKG in
1418
# .github/workflows/conformance.yml. New conformance releases are adopted by
15-
# deliberately bumping that pin and reconciling both this file and
16-
# expected-failures.yml in the same change.
19+
# deliberately bumping that pin and reconciling all three expected-failures
20+
# files in the same change.
1721
#
1822
# Entries are grouped by what unblocks them. As each gap closes the
1923
# corresponding scenarios start passing and MUST be removed from this list

‎.github/actions/conformance/expected-failures.yml‎

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
1-
# Conformance scenarios not yet passing against the Python SDK on main.
2-
# CI exits 0 if only these fail, exits 1 on unexpected failures or stale entries.
1+
# Conformance scenarios not yet passing against the Python SDK on main, read by
2+
# the bare `--suite all` server and client legs (every scenario shipped with the
3+
# pinned harness, each at the harness's default wire for it). CI exits 0 if only
4+
# these fail, exits 1 on unexpected failures or stale entries.
5+
#
6+
# The wire-pinned legs read expected-failures.2025-11-25.yml and
7+
# expected-failures.2026-07-28.yml instead; see the header of
8+
# .github/workflows/conformance.yml for the leg layout.
39
#
410
# Baseline established against the harness pinned via CONFORMANCE_PKG in
511
# .github/workflows/conformance.yml. New conformance releases are adopted by
6-
# deliberately bumping that pin and reconciling both this file and
7-
# expected-failures.2026-07-28.yml in the same change.
12+
# deliberately bumping that pin and reconciling all three expected-failures
13+
# files in the same change.
814
#
915
# Entries are grouped by SEP. As each SEP lands in the SDK the corresponding
1016
# scenarios start passing and MUST be removed from this list (the runner fails
1117
# on stale entries), so the baseline burns down per milestone.
1218

13-
client: []
19+
client:
20+
# SEP-1932 (DPoP): the SDK's OAuth client does not implement DPoP proofs.
21+
# The entries are per-check (conformance #406) because both scenarios
22+
# pass their non-DPoP checks (discovery, token acquisition, request
23+
# flow) live.
24+
- auth/dpop:sep-1932-client-token-request-proof
25+
- auth/dpop:sep-1932-client-dpop-auth-scheme
26+
- auth/dpop:sep-1932-client-fresh-proof
27+
- auth/dpop-nonce:sep-1932-client-token-request-proof
28+
- auth/dpop-nonce:sep-1932-client-dpop-auth-scheme
29+
- auth/dpop-nonce:sep-1932-client-fresh-proof
30+
- auth/dpop-nonce:sep-1932-client-as-nonce
31+
- auth/dpop-nonce:sep-1932-client-rs-nonce
32+
# Workload identity federation: the OAuth client does not implement the
33+
# urn:ietf:params:oauth:grant-type:jwt-bearer grant (it answers with
34+
# authorization_code). Per-check for the same reason.
35+
- auth/wif-jwt-bearer:wif-grant-type
1436

1537
server:
1638
# SEP-2663 (io.modelcontextprotocol/tasks): the SDK does not implement the
17-
# tasks extension yet. These extension-tagged scenarios are selected only by
18-
# the bare `--suite all` leg — extension scenarios never match a
19-
# --spec-version filter and the active/draft suites exclude them — so these
20-
# entries are inert for the other legs that read this file.
39+
# tasks extension yet. The bare `--suite all` leg is the only one that
40+
# reaches these extension-tagged scenarios (they never match a
41+
# --spec-version filter), so these entries live here only.
2142
#
2243
# `tasks-status-notifications` is intentionally NOT listed: the harness
2344
# skips it unconditionally (pending its rewrite against subscriptions/

‎.github/actions/conformance/run-client.sh‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22
# Run a client conformance suite, re-verifying unexpected failures solo.
3-
# Concurrent suite runs on a 2-vCPU runner can push scenarios with real-time
3+
# Concurrent suite runs on a hosted runner can push scenarios with real-time
44
# waits past tolerance; solo, a real failure fails again while a contention
55
# artifact passes. Failures that only reproduce under concurrency are excused.
66
set -uo pipefail
@@ -101,4 +101,8 @@ if [ -n "$output_dir" ]; then
101101
printf '%s\n' "${scenarios[@]}" > "$output_dir/FLAKE_RESCUED"
102102
fi
103103
echo "All ${#scenarios[@]} unexpected failure(s) passed when re-run solo; the suite failures were parallel-run contention."
104+
# Surface the rescue on the run summary; the log line above and the
105+
# FLAKE_RESCUED marker in the uploaded results are otherwise easy to miss on a
106+
# green run.
107+
echo "::warning title=Conformance flake rescued::${scenarios[*]} failed under suite concurrency and passed solo${output_dir:+ (see ${output_dir}-solo and FLAKE_RESCUED in the uploaded results)}."
104108
exit 0

‎.github/dependabot.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ updates:
1010
python-packages:
1111
patterns:
1212
- "*"
13+
python-security:
14+
applies-to: security-updates
15+
patterns:
16+
- "*"
1317
- package-ecosystem: "github-actions"
1418
directory: "/"
1519
schedule:

‎.github/pull_request_template.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!--
2+
Pull requests from outside the maintainer team need to link an open issue that
3+
a maintainer has assigned to you (or one labeled `help wanted`); others are
4+
closed automatically until that's in place. See CONTRIBUTING.md for details.
5+
-->
6+
7+
Fixes #
8+
9+
<!-- Provide a brief summary of your changes -->
10+
11+
## Motivation and Context
12+
<!-- Why is this change needed? What problem does it solve? -->
13+
14+
## How Has This Been Tested?
15+
<!-- Have you tested this in a real application? Which scenarios were tested? -->
16+
17+
## Breaking Changes
18+
<!-- Will users need to update their code or configurations? -->
19+
20+
## Types of changes
21+
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
22+
- [ ] Bug fix (non-breaking change which fixes an issue)
23+
- [ ] New feature (non-breaking change which adds functionality)
24+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
25+
- [ ] Documentation update
26+
27+
## Checklist
28+
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
29+
- [ ] I am assigned to the linked issue (or it is labeled `help wanted`, or I'm a maintainer)
30+
- [ ] I have disclosed any AI assistance and can explain the change in my own words
31+
- [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io)
32+
- [ ] My code follows the repository's style guidelines
33+
- [ ] New and existing tests pass locally
34+
- [ ] I have added appropriate error handling
35+
- [ ] I have added or updated documentation as needed
36+
37+
## Additional context
38+
<!-- Add any other context, implementation notes, or design decisions -->

0 commit comments

Comments
 (0)