Skip to content

Commit 48b3cc9

Browse files
authored
Merge branch 'main' into fix/prevent-session-hang
2 parents c35e749 + a7ddfda commit 48b3cc9

File tree

93 files changed

+2304
-2254
lines changed

Some content is hidden

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

93 files changed

+2304
-2254
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Source: https://github.com/anthropics/claude-code-action/blob/main/docs/code-review.md
2+
name: Claude Code Review
3+
4+
on:
5+
pull_request:
6+
types: [opened, synchronize, ready_for_review, reopened]
7+
8+
jobs:
9+
claude-review:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
pull-requests: read
14+
issues: read
15+
id-token: write
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 1
22+
23+
- name: Run Claude Code Review
24+
id: claude-review
25+
uses: anthropics/claude-code-action@v1
26+
with:
27+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
28+
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
29+
plugins: "code-review@claude-code-plugins"
30+
prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"

.github/workflows/claude.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Source: https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
2+
name: Claude Code
3+
4+
on:
5+
issue_comment:
6+
types: [created]
7+
pull_request_review_comment:
8+
types: [created]
9+
issues:
10+
types: [opened, assigned]
11+
pull_request_review:
12+
types: [submitted]
13+
14+
jobs:
15+
claude:
16+
if: |
17+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
19+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
20+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: read
25+
issues: read
26+
id-token: write
27+
actions: read # Required for Claude to read CI results on PRs
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 1
33+
34+
- name: Run Claude Code
35+
id: claude
36+
uses: anthropics/claude-code-action@v1
37+
with:
38+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
39+
use_commit_signing: true
40+
additional_permissions: |
41+
actions: read

.github/workflows/shared.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ jobs:
6262
uv run --frozen --no-sync coverage combine
6363
uv run --frozen --no-sync coverage report
6464
65+
- name: Check for unnecessary no cover pragmas
66+
if: runner.os != 'Windows'
67+
run: uv run --frozen --no-sync strict-no-cover
68+
6569
readme-snippets:
6670
runs-on: ubuntu-latest
6771
steps:

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,8 +2119,6 @@ uv run client
21192119
import asyncio
21202120
import os
21212121

2122-
from pydantic import AnyUrl
2123-
21242122
from mcp import ClientSession, StdioServerParameters, types
21252123
from mcp.client.stdio import stdio_client
21262124
from mcp.shared.context import RequestContext
@@ -2173,7 +2171,7 @@ async def run():
21732171
print(f"Available tools: {[t.name for t in tools.tools]}")
21742172

21752173
# Read a resource (greeting resource from fastmcp_quickstart)
2176-
resource_content = await session.read_resource(AnyUrl("greeting://World"))
2174+
resource_content = await session.read_resource("greeting://World")
21772175
content_block = resource_content.contents[0]
21782176
if isinstance(content_block, types.TextContent):
21792177
print(f"Resource content: {content_block.text}")

docs/migration.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,35 @@ notification = server_notification_adapter.validate_python(data)
233233

234234
All adapters are exported from `mcp.types`.
235235

236+
### `RequestParams.Meta` replaced with `RequestParamsMeta` TypedDict
237+
238+
The nested `RequestParams.Meta` Pydantic model class has been replaced with a top-level `RequestParamsMeta` TypedDict. This affects the `ctx.meta` field in request handlers and any code that imports or references this type.
239+
240+
**Key changes:**
241+
242+
- `RequestParams.Meta` (Pydantic model) → `RequestParamsMeta` (TypedDict)
243+
- Attribute access (`meta.progress_token`) → Dictionary access (`meta.get("progress_token")`)
244+
- `progress_token` field changed from `ProgressToken | None = None` to `NotRequired[ProgressToken]`
245+
`
246+
247+
**In request context handlers:**
248+
249+
```python
250+
# Before (v1)
251+
@server.call_tool()
252+
async def handle_tool(name: str, arguments: dict) -> list[TextContent]:
253+
ctx = server.request_context
254+
if ctx.meta and ctx.meta.progress_token:
255+
await ctx.session.send_progress_notification(ctx.meta.progress_token, 0.5, 100)
256+
257+
# After (v2)
258+
@server.call_tool()
259+
async def handle_tool(name: str, arguments: dict) -> list[TextContent]:
260+
ctx = server.request_context
261+
if ctx.meta and "progress_token" in ctx.meta:
262+
await ctx.session.send_progress_notification(ctx.meta["progress_token"], 0.5, 100)
263+
```
264+
236265
### Resource URI type changed from `AnyUrl` to `str`
237266

238267
The `uri` field on resource-related types now uses `str` instead of Pydantic's `AnyUrl`. This aligns with the [MCP specification schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) which defines URIs as plain strings (`uri: string`) without strict URL validation. This change allows relative paths like `users/me` that were previously rejected.
@@ -274,12 +303,48 @@ Affected types:
274303
- `UnsubscribeRequestParams.uri`
275304
- `ResourceUpdatedNotificationParams.uri`
276305

277-
The `ClientSession.read_resource()`, `subscribe_resource()`, and `unsubscribe_resource()` methods now accept both `str` and `AnyUrl` for backwards compatibility.
306+
The `Client` and `ClientSession` methods `read_resource()`, `subscribe_resource()`, and `unsubscribe_resource()` now only accept `str` for the `uri` parameter. If you were passing `AnyUrl` objects, convert them to strings:
307+
308+
```python
309+
# Before (v1)
310+
from pydantic import AnyUrl
311+
312+
await client.read_resource(AnyUrl("test://resource"))
313+
314+
# After (v2)
315+
await client.read_resource("test://resource")
316+
# Or if you have an AnyUrl from elsewhere:
317+
await client.read_resource(str(my_any_url))
318+
```
278319

279320
## Deprecations
280321

281322
<!-- Add deprecations below -->
282323

324+
## Bug Fixes
325+
326+
### Extra fields no longer allowed on top-level MCP types
327+
328+
MCP protocol types no longer accept arbitrary extra fields at the top level. This matches the MCP specification which only allows extra fields within `_meta` objects, not on the types themselves.
329+
330+
```python
331+
# This will now raise a validation error
332+
from mcp.types import CallToolRequestParams
333+
334+
params = CallToolRequestParams(
335+
name="my_tool",
336+
arguments={},
337+
unknown_field="value", # ValidationError: extra fields not permitted
338+
)
339+
340+
# Extra fields are still allowed in _meta
341+
params = CallToolRequestParams(
342+
name="my_tool",
343+
arguments={},
344+
_meta={"progressToken": "tok", "customField": "value"}, # OK
345+
)
346+
```
347+
283348
## New Features
284349

285350
### `streamable_http_app()` available on lowlevel Server

examples/clients/sse-polling-client/mcp_sse_polling_client/main.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
from mcp import ClientSession
2222
from mcp.client.streamable_http import streamable_http_client
2323

24-
logger = logging.getLogger(__name__)
25-
2624

2725
async def run_demo(url: str, items: int, checkpoint_every: int) -> None:
2826
"""Run the SSE polling demo."""

examples/servers/everything-server/mcp_everything_server/server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ async def test_tool_with_progress(ctx: Context[ServerSession, None]) -> str:
161161
await ctx.report_progress(progress=100, total=100, message="Completed step 100 of 100")
162162

163163
# Return progress token as string
164-
progress_token = ctx.request_context.meta.progress_token if ctx.request_context and ctx.request_context.meta else 0
164+
progress_token = (
165+
ctx.request_context.meta.get("progress_token") if ctx.request_context and ctx.request_context.meta else 0
166+
)
165167
return str(progress_token)
166168

167169

examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
99
"""
1010

11-
import logging
1211
import secrets
1312
import time
1413
from typing import Any
@@ -29,8 +28,6 @@
2928
)
3029
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
3130

32-
logger = logging.getLogger(__name__)
33-
3431

3532
class SimpleAuthSettings(BaseSettings):
3633
"""Simple OAuth settings for demo purposes."""

examples/snippets/clients/stdio_client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import asyncio
66
import os
77

8-
from pydantic import AnyUrl
9-
108
from mcp import ClientSession, StdioServerParameters, types
119
from mcp.client.stdio import stdio_client
1210
from mcp.shared.context import RequestContext
@@ -59,7 +57,7 @@ async def run():
5957
print(f"Available tools: {[t.name for t in tools.tools]}")
6058

6159
# Read a resource (greeting resource from fastmcp_quickstart)
62-
resource_content = await session.read_resource(AnyUrl("greeting://World"))
60+
resource_content = await session.read_resource("greeting://World")
6361
content_block = resource_content.contents[0]
6462
if isinstance(content_block, types.TextContent):
6563
print(f"Resource content: {content_block.text}")

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ dependencies = [
2626
"anyio>=4.5",
2727
"httpx>=0.27.1",
2828
"httpx-sse>=0.4",
29-
"pydantic>=2.12.0; python_version >= '3.14'",
30-
"pydantic>=2.11.0; python_version < '3.14'",
29+
"pydantic>=2.12.0",
3130
"starlette>=0.48.0; python_version >= '3.14'",
3231
"starlette>=0.27; python_version < '3.14'",
3332
"python-multipart>=0.0.9",
@@ -37,7 +36,7 @@ dependencies = [
3736
"jsonschema>=4.20.0",
3837
"pywin32>=311; sys_platform == 'win32'",
3938
"pyjwt[crypto]>=2.10.1",
40-
"typing-extensions>=4.9.0",
39+
"typing-extensions>=4.13.0",
4140
"typing-inspection>=0.4.1",
4241
]
4342

@@ -67,8 +66,9 @@ dev = [
6766
"pytest-pretty>=1.2.0",
6867
"inline-snapshot>=0.23.0",
6968
"dirty-equals>=0.9.0",
70-
"coverage[toml]>=7.13.1",
69+
"coverage[toml]>=7.10.7,<=7.13",
7170
"pillow>=12.0",
71+
"strict-no-cover",
7272
]
7373
docs = [
7474
"mkdocs>=1.6.1",
@@ -164,6 +164,7 @@ members = ["examples/clients/*", "examples/servers/*", "examples/snippets"]
164164

165165
[tool.uv.sources]
166166
mcp = { workspace = true }
167+
strict-no-cover = { git = "https://github.com/pydantic/strict-no-cover" }
167168

168169
[tool.pytest.ini_options]
169170
log_cli = true
@@ -199,7 +200,6 @@ branch = true
199200
patch = ["subprocess"]
200201
concurrency = ["multiprocessing", "thread"]
201202
source = ["src", "tests"]
202-
relative_files = true
203203
omit = [
204204
"src/mcp/client/__main__.py",
205205
"src/mcp/server/__main__.py",
@@ -216,6 +216,7 @@ ignore_errors = true
216216
precision = 2
217217
exclude_lines = [
218218
"pragma: no cover",
219+
"pragma: lax no cover",
219220
"if TYPE_CHECKING:",
220221
"@overload",
221222
"raise NotImplementedError",

0 commit comments

Comments
 (0)