Skip to content

Commit d9291c3

Browse files
authored
Merge branch 'main' into fix/reject-null-jsonrpc-id
2 parents 3162a4c + 62eb08e commit d9291c3

File tree

18 files changed

+209
-115
lines changed

18 files changed

+209
-115
lines changed

README.v2.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,12 @@ Tools can optionally receive a Context object by including a parameter with the
346346
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
347347
```python
348348
from mcp.server.mcpserver import Context, MCPServer
349-
from mcp.server.session import ServerSession
350349

351350
mcp = MCPServer(name="Progress Example")
352351

353352

354353
@mcp.tool()
355-
async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str:
354+
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
356355
"""Execute a task with progress updates."""
357356
await ctx.info(f"Starting: {task_name}")
358357

@@ -694,13 +693,12 @@ The Context object provides the following capabilities:
694693
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
695694
```python
696695
from mcp.server.mcpserver import Context, MCPServer
697-
from mcp.server.session import ServerSession
698696

699697
mcp = MCPServer(name="Progress Example")
700698

701699

702700
@mcp.tool()
703-
async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str:
701+
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
704702
"""Execute a task with progress updates."""
705703
await ctx.info(f"Starting: {task_name}")
706704

@@ -826,7 +824,6 @@ import uuid
826824
from pydantic import BaseModel, Field
827825

828826
from mcp.server.mcpserver import Context, MCPServer
829-
from mcp.server.session import ServerSession
830827
from mcp.shared.exceptions import UrlElicitationRequiredError
831828
from mcp.types import ElicitRequestURLParams
832829

@@ -844,7 +841,7 @@ class BookingPreferences(BaseModel):
844841

845842

846843
@mcp.tool()
847-
async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerSession, None]) -> str:
844+
async def book_table(date: str, time: str, party_size: int, ctx: Context) -> str:
848845
"""Book a table with date availability check.
849846
850847
This demonstrates form mode elicitation for collecting non-sensitive user input.
@@ -868,7 +865,7 @@ async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerS
868865

869866

870867
@mcp.tool()
871-
async def secure_payment(amount: float, ctx: Context[ServerSession, None]) -> str:
868+
async def secure_payment(amount: float, ctx: Context) -> str:
872869
"""Process a secure payment requiring URL confirmation.
873870
874871
This demonstrates URL mode elicitation using ctx.elicit_url() for
@@ -892,7 +889,7 @@ async def secure_payment(amount: float, ctx: Context[ServerSession, None]) -> st
892889

893890

894891
@mcp.tool()
895-
async def connect_service(service_name: str, ctx: Context[ServerSession, None]) -> str:
892+
async def connect_service(service_name: str, ctx: Context) -> str:
896893
"""Connect to a third-party service requiring OAuth authorization.
897894
898895
This demonstrates the "throw error" pattern using UrlElicitationRequiredError.
@@ -933,14 +930,13 @@ Tools can interact with LLMs through sampling (generating text):
933930
<!-- snippet-source examples/snippets/servers/sampling.py -->
934931
```python
935932
from mcp.server.mcpserver import Context, MCPServer
936-
from mcp.server.session import ServerSession
937933
from mcp.types import SamplingMessage, TextContent
938934

939935
mcp = MCPServer(name="Sampling Example")
940936

941937

942938
@mcp.tool()
943-
async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
939+
async def generate_poem(topic: str, ctx: Context) -> str:
944940
"""Generate a poem using LLM sampling."""
945941
prompt = f"Write a short poem about {topic}"
946942

@@ -970,13 +966,12 @@ Tools can send logs and notifications through the context:
970966
<!-- snippet-source examples/snippets/servers/notifications.py -->
971967
```python
972968
from mcp.server.mcpserver import Context, MCPServer
973-
from mcp.server.session import ServerSession
974969

975970
mcp = MCPServer(name="Notifications Example")
976971

977972

978973
@mcp.tool()
979-
async def process_data(data: str, ctx: Context[ServerSession, None]) -> str:
974+
async def process_data(data: str, ctx: Context) -> str:
980975
"""Process data with logging."""
981976
# Different log levels
982977
await ctx.debug(f"Debug: Processing '{data}'")

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from mcp.server import ServerRequestContext
1414
from mcp.server.mcpserver import Context, MCPServer
1515
from mcp.server.mcpserver.prompts.base import UserMessage
16-
from mcp.server.session import ServerSession
1716
from mcp.server.streamable_http import EventCallback, EventMessage, EventStore
1817
from mcp.types import (
1918
AudioContent,
@@ -142,7 +141,7 @@ def test_multiple_content_types() -> list[TextContent | ImageContent | EmbeddedR
142141

143142

144143
@mcp.tool()
145-
async def test_tool_with_logging(ctx: Context[ServerSession, None]) -> str:
144+
async def test_tool_with_logging(ctx: Context) -> str:
146145
"""Tests tool that emits log messages during execution"""
147146
await ctx.info("Tool execution started")
148147
await asyncio.sleep(0.05)
@@ -155,7 +154,7 @@ async def test_tool_with_logging(ctx: Context[ServerSession, None]) -> str:
155154

156155

157156
@mcp.tool()
158-
async def test_tool_with_progress(ctx: Context[ServerSession, None]) -> str:
157+
async def test_tool_with_progress(ctx: Context) -> str:
159158
"""Tests tool that reports progress notifications"""
160159
await ctx.report_progress(progress=0, total=100, message="Completed step 0 of 100")
161160
await asyncio.sleep(0.05)
@@ -173,7 +172,7 @@ async def test_tool_with_progress(ctx: Context[ServerSession, None]) -> str:
173172

174173

175174
@mcp.tool()
176-
async def test_sampling(prompt: str, ctx: Context[ServerSession, None]) -> str:
175+
async def test_sampling(prompt: str, ctx: Context) -> str:
177176
"""Tests server-initiated sampling (LLM completion request)"""
178177
try:
179178
# Request sampling from client
@@ -198,7 +197,7 @@ class UserResponse(BaseModel):
198197

199198

200199
@mcp.tool()
201-
async def test_elicitation(message: str, ctx: Context[ServerSession, None]) -> str:
200+
async def test_elicitation(message: str, ctx: Context) -> str:
202201
"""Tests server-initiated elicitation (user input request)"""
203202
try:
204203
# Request user input from client
@@ -230,7 +229,7 @@ class SEP1034DefaultsSchema(BaseModel):
230229

231230

232231
@mcp.tool()
233-
async def test_elicitation_sep1034_defaults(ctx: Context[ServerSession, None]) -> str:
232+
async def test_elicitation_sep1034_defaults(ctx: Context) -> str:
234233
"""Tests elicitation with default values for all primitive types (SEP-1034)"""
235234
try:
236235
# Request user input with defaults for all primitive types
@@ -289,7 +288,7 @@ class EnumSchemasTestSchema(BaseModel):
289288

290289

291290
@mcp.tool()
292-
async def test_elicitation_sep1330_enums(ctx: Context[ServerSession, None]) -> str:
291+
async def test_elicitation_sep1330_enums(ctx: Context) -> str:
293292
"""Tests elicitation with enum schema variations per SEP-1330"""
294293
try:
295294
result = await ctx.elicit(
@@ -313,7 +312,7 @@ def test_error_handling() -> str:
313312

314313

315314
@mcp.tool()
316-
async def test_reconnection(ctx: Context[ServerSession, None]) -> str:
315+
async def test_reconnection(ctx: Context) -> str:
317316
"""Tests SSE polling by closing stream mid-call (SEP-1699)"""
318317
await ctx.info("Before disconnect")
319318

examples/snippets/servers/elicitation.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from pydantic import BaseModel, Field
1111

1212
from mcp.server.mcpserver import Context, MCPServer
13-
from mcp.server.session import ServerSession
1413
from mcp.shared.exceptions import UrlElicitationRequiredError
1514
from mcp.types import ElicitRequestURLParams
1615

@@ -28,7 +27,7 @@ class BookingPreferences(BaseModel):
2827

2928

3029
@mcp.tool()
31-
async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerSession, None]) -> str:
30+
async def book_table(date: str, time: str, party_size: int, ctx: Context) -> str:
3231
"""Book a table with date availability check.
3332
3433
This demonstrates form mode elicitation for collecting non-sensitive user input.
@@ -52,7 +51,7 @@ async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerS
5251

5352

5453
@mcp.tool()
55-
async def secure_payment(amount: float, ctx: Context[ServerSession, None]) -> str:
54+
async def secure_payment(amount: float, ctx: Context) -> str:
5655
"""Process a secure payment requiring URL confirmation.
5756
5857
This demonstrates URL mode elicitation using ctx.elicit_url() for
@@ -76,7 +75,7 @@ async def secure_payment(amount: float, ctx: Context[ServerSession, None]) -> st
7675

7776

7877
@mcp.tool()
79-
async def connect_service(service_name: str, ctx: Context[ServerSession, None]) -> str:
78+
async def connect_service(service_name: str, ctx: Context) -> str:
8079
"""Connect to a third-party service requiring OAuth authorization.
8180
8281
This demonstrates the "throw error" pattern using UrlElicitationRequiredError.

examples/snippets/servers/notifications.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from mcp.server.mcpserver import Context, MCPServer
2-
from mcp.server.session import ServerSession
32

43
mcp = MCPServer(name="Notifications Example")
54

65

76
@mcp.tool()
8-
async def process_data(data: str, ctx: Context[ServerSession, None]) -> str:
7+
async def process_data(data: str, ctx: Context) -> str:
98
"""Process data with logging."""
109
# Different log levels
1110
await ctx.debug(f"Debug: Processing '{data}'")

examples/snippets/servers/sampling.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from mcp.server.mcpserver import Context, MCPServer
2-
from mcp.server.session import ServerSession
32
from mcp.types import SamplingMessage, TextContent
43

54
mcp = MCPServer(name="Sampling Example")
65

76

87
@mcp.tool()
9-
async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
8+
async def generate_poem(topic: str, ctx: Context) -> str:
109
"""Generate a poem using LLM sampling."""
1110
prompt = f"Write a short poem about {topic}"
1211

examples/snippets/servers/tool_progress.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from mcp.server.mcpserver import Context, MCPServer
2-
from mcp.server.session import ServerSession
32

43
mcp = MCPServer(name="Progress Example")
54

65

76
@mcp.tool()
8-
async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str:
7+
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
98
"""Execute a task with progress updates."""
109
await ctx.info(f"Starting: {task_name}")
1110

src/mcp/server/lowlevel/server.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,6 @@ async def _handle_message(
414414
)
415415
case Exception():
416416
logger.error(f"Received exception from stream: {message}")
417-
await session.send_log_message(
418-
level="error",
419-
data="Internal Server Error",
420-
logger="mcp.server.exception_handler",
421-
)
422417
if raise_exceptions:
423418
raise message
424419
case _:

src/mcp/server/streamable_http.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,19 @@ async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> No
391391
await self._handle_unsupported_request(request, send)
392392

393393
def _check_accept_headers(self, request: Request) -> tuple[bool, bool]:
394-
"""Check if the request accepts the required media types."""
394+
"""Check if the request accepts the required media types.
395+
396+
Supports wildcard media types per RFC 7231, section 5.3.2:
397+
- */* matches any media type
398+
- application/* matches any application/ subtype
399+
- text/* matches any text/ subtype
400+
"""
395401
accept_header = request.headers.get("accept", "")
396-
accept_types = [media_type.strip() for media_type in accept_header.split(",")]
402+
accept_types = [media_type.strip().split(";")[0].strip().lower() for media_type in accept_header.split(",")]
397403

398-
has_json = any(media_type.startswith(CONTENT_TYPE_JSON) for media_type in accept_types)
399-
has_sse = any(media_type.startswith(CONTENT_TYPE_SSE) for media_type in accept_types)
404+
has_wildcard = "*/*" in accept_types
405+
has_json = has_wildcard or any(t in (CONTENT_TYPE_JSON, "application/*") for t in accept_types)
406+
has_sse = has_wildcard or any(t in (CONTENT_TYPE_SSE, "text/*") for t in accept_types)
400407

401408
return has_json, has_sse
402409

src/mcp/shared/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ async def _receive_loop(self) -> None:
334334
async with self._read_stream, self._write_stream:
335335
try:
336336
async for message in self._read_stream:
337-
if isinstance(message, Exception): # pragma: no cover
337+
if isinstance(message, Exception):
338338
await self._handle_incoming(message)
339339
elif isinstance(message.message, JSONRPCRequest):
340340
try:

tests/client/test_list_roots_callback.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async def list_roots_callback(
2525
return callback_return
2626

2727
@server.tool("test_list_roots")
28-
async def test_list_roots(context: Context[None], message: str):
28+
async def test_list_roots(context: Context, message: str):
2929
roots = await context.session.list_roots()
3030
assert roots == callback_return
3131
return True

0 commit comments

Comments
 (0)