Skip to content

Commit e713784

Browse files
Merge branch 'main' into main
2 parents 904294e + c47c767 commit e713784

31 files changed

+689
-160
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,32 @@
3131
- [Prompts](#prompts)
3232
- [Images](#images)
3333
- [Context](#context)
34+
- [Getting Context in Functions](#getting-context-in-functions)
35+
- [Context Properties and Methods](#context-properties-and-methods)
3436
- [Completions](#completions)
3537
- [Elicitation](#elicitation)
3638
- [Sampling](#sampling)
3739
- [Logging and Notifications](#logging-and-notifications)
3840
- [Authentication](#authentication)
3941
- [FastMCP Properties](#fastmcp-properties)
40-
- [Session Properties](#session-properties-and-methods)
42+
- [Session Properties and Methods](#session-properties-and-methods)
4143
- [Request Context Properties](#request-context-properties)
4244
- [Running Your Server](#running-your-server)
4345
- [Development Mode](#development-mode)
4446
- [Claude Desktop Integration](#claude-desktop-integration)
4547
- [Direct Execution](#direct-execution)
4648
- [Streamable HTTP Transport](#streamable-http-transport)
49+
- [CORS Configuration for Browser-Based Clients](#cors-configuration-for-browser-based-clients)
4750
- [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server)
51+
- [StreamableHTTP servers](#streamablehttp-servers)
52+
- [Basic mounting](#basic-mounting)
53+
- [Host-based routing](#host-based-routing)
54+
- [Multiple servers with path configuration](#multiple-servers-with-path-configuration)
55+
- [Path configuration at initialization](#path-configuration-at-initialization)
56+
- [SSE servers](#sse-servers)
4857
- [Advanced Usage](#advanced-usage)
4958
- [Low-Level Server](#low-level-server)
59+
- [Structured Output Support](#structured-output-support)
5060
- [Writing MCP Clients](#writing-mcp-clients)
5161
- [Client Display Utilities](#client-display-utilities)
5262
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
@@ -400,7 +410,7 @@ def get_weather(city: str) -> WeatherData:
400410
"""Get weather for a city - returns structured data."""
401411
# Simulated weather data
402412
return WeatherData(
403-
temperature=72.5,
413+
temperature=22.5,
404414
humidity=45.0,
405415
condition="sunny",
406416
wind_speed=5.2,
@@ -2137,6 +2147,7 @@ MCP servers declare capabilities during initialization:
21372147

21382148
## Documentation
21392149

2150+
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
21402151
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
21412152
- [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
21422153
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)

examples/servers/simple-resource/mcp_simple_resource/server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import click
33
import mcp.types as types
44
from mcp.server.lowlevel import Server
5+
from mcp.server.lowlevel.helper_types import ReadResourceContents
56
from pydantic import AnyUrl, FileUrl
67
from starlette.requests import Request
78

@@ -46,15 +47,15 @@ async def list_resources() -> list[types.Resource]:
4647
]
4748

4849
@app.read_resource()
49-
async def read_resource(uri: AnyUrl) -> str | bytes:
50+
async def read_resource(uri: AnyUrl):
5051
if uri.path is None:
5152
raise ValueError(f"Invalid resource path: {uri}")
5253
name = uri.path.replace(".txt", "").lstrip("/")
5354

5455
if name not in SAMPLE_RESOURCES:
5556
raise ValueError(f"Unknown resource: {uri}")
5657

57-
return SAMPLE_RESOURCES[name]["content"]
58+
return [ReadResourceContents(content=SAMPLE_RESOURCES[name]["content"], mime_type="text/plain")]
5859

5960
if transport == "sse":
6061
from mcp.server.sse import SseServerTransport

examples/snippets/servers/structured_output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def get_weather(city: str) -> WeatherData:
2424
"""Get weather for a city - returns structured data."""
2525
# Simulated weather data
2626
return WeatherData(
27-
temperature=72.5,
27+
temperature=22.5,
2828
humidity=45.0,
2929
condition="sunny",
3030
wind_speed=5.2,

pyproject.toml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,36 @@ executionEnvironments = [
102102
{ root = "examples/servers", reportUnusedFunction = false },
103103
]
104104

105-
[tool.ruff.lint]
106-
select = ["C4", "E", "F", "I", "PERF", "UP"]
107-
ignore = ["PERF203"]
108-
109105
[tool.ruff]
110106
line-length = 120
111107
target-version = "py310"
112108
extend-exclude = ["README.md"]
113109

110+
[tool.ruff.lint]
111+
select = [
112+
"C4", # flake8-comprehensions
113+
"C90", # mccabe
114+
"E", # pycodestyle
115+
"F", # pyflakes
116+
"I", # isort
117+
"PERF", # Perflint
118+
"PL", # Pylint
119+
"UP", # pyupgrade
120+
]
121+
ignore = ["PERF203", "PLC0415", "PLR0402"]
122+
mccabe.max-complexity = 24 # Default is 10
123+
114124
[tool.ruff.lint.per-file-ignores]
115125
"__init__.py" = ["F401"]
116126
"tests/server/fastmcp/test_func_metadata.py" = ["E501"]
127+
"tests/shared/test_progress_notifications.py" = ["PLW0603"]
128+
129+
[tool.ruff.lint.pylint]
130+
allow-magic-value-types = ["bytes", "float", "int", "str"]
131+
max-args = 23 # Default is 5
132+
max-branches = 23 # Default is 12
133+
max-returns = 13 # Default is 6
134+
max-statements = 102 # Default is 50
117135

118136
[tool.uv.workspace]
119137
members = ["examples/servers/*", "examples/snippets"]

src/mcp/client/auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,6 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
546546
logger.exception("OAuth flow error")
547547
raise
548548

549-
# Retry with new tokens
550-
self._add_auth_header(request)
551-
yield request
549+
# Retry with new tokens
550+
self._add_auth_header(request)
551+
yield request

src/mcp/client/session.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ async def initialize(self) -> types.InitializeResult:
151151
result = await self.send_request(
152152
types.ClientRequest(
153153
types.InitializeRequest(
154-
method="initialize",
155154
params=types.InitializeRequestParams(
156155
protocolVersion=types.LATEST_PROTOCOL_VERSION,
157156
capabilities=types.ClientCapabilities(
@@ -170,20 +169,14 @@ async def initialize(self) -> types.InitializeResult:
170169
if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS:
171170
raise RuntimeError(f"Unsupported protocol version from the server: {result.protocolVersion}")
172171

173-
await self.send_notification(
174-
types.ClientNotification(types.InitializedNotification(method="notifications/initialized"))
175-
)
172+
await self.send_notification(types.ClientNotification(types.InitializedNotification()))
176173

177174
return result
178175

179176
async def send_ping(self) -> types.EmptyResult:
180177
"""Send a ping request."""
181178
return await self.send_request(
182-
types.ClientRequest(
183-
types.PingRequest(
184-
method="ping",
185-
)
186-
),
179+
types.ClientRequest(types.PingRequest()),
187180
types.EmptyResult,
188181
)
189182

@@ -198,7 +191,6 @@ async def send_progress_notification(
198191
await self.send_notification(
199192
types.ClientNotification(
200193
types.ProgressNotification(
201-
method="notifications/progress",
202194
params=types.ProgressNotificationParams(
203195
progressToken=progress_token,
204196
progress=progress,
@@ -214,7 +206,6 @@ async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResul
214206
return await self.send_request(
215207
types.ClientRequest(
216208
types.SetLevelRequest(
217-
method="logging/setLevel",
218209
params=types.SetLevelRequestParams(level=level),
219210
)
220211
),
@@ -226,7 +217,6 @@ async def list_resources(self, cursor: str | None = None) -> types.ListResources
226217
return await self.send_request(
227218
types.ClientRequest(
228219
types.ListResourcesRequest(
229-
method="resources/list",
230220
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
231221
)
232222
),
@@ -238,7 +228,6 @@ async def list_resource_templates(self, cursor: str | None = None) -> types.List
238228
return await self.send_request(
239229
types.ClientRequest(
240230
types.ListResourceTemplatesRequest(
241-
method="resources/templates/list",
242231
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
243232
)
244233
),
@@ -250,7 +239,6 @@ async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
250239
return await self.send_request(
251240
types.ClientRequest(
252241
types.ReadResourceRequest(
253-
method="resources/read",
254242
params=types.ReadResourceRequestParams(uri=uri),
255243
)
256244
),
@@ -262,7 +250,6 @@ async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
262250
return await self.send_request(
263251
types.ClientRequest(
264252
types.SubscribeRequest(
265-
method="resources/subscribe",
266253
params=types.SubscribeRequestParams(uri=uri),
267254
)
268255
),
@@ -274,7 +261,6 @@ async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
274261
return await self.send_request(
275262
types.ClientRequest(
276263
types.UnsubscribeRequest(
277-
method="resources/unsubscribe",
278264
params=types.UnsubscribeRequestParams(uri=uri),
279265
)
280266
),
@@ -293,7 +279,6 @@ async def call_tool(
293279
result = await self.send_request(
294280
types.ClientRequest(
295281
types.CallToolRequest(
296-
method="tools/call",
297282
params=types.CallToolRequestParams(
298283
name=name,
299284
arguments=arguments,
@@ -337,7 +322,6 @@ async def list_prompts(self, cursor: str | None = None) -> types.ListPromptsResu
337322
return await self.send_request(
338323
types.ClientRequest(
339324
types.ListPromptsRequest(
340-
method="prompts/list",
341325
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
342326
)
343327
),
@@ -349,7 +333,6 @@ async def get_prompt(self, name: str, arguments: dict[str, str] | None = None) -
349333
return await self.send_request(
350334
types.ClientRequest(
351335
types.GetPromptRequest(
352-
method="prompts/get",
353336
params=types.GetPromptRequestParams(name=name, arguments=arguments),
354337
)
355338
),
@@ -370,7 +353,6 @@ async def complete(
370353
return await self.send_request(
371354
types.ClientRequest(
372355
types.CompleteRequest(
373-
method="completion/complete",
374356
params=types.CompleteRequestParams(
375357
ref=ref,
376358
argument=types.CompletionArgument(**argument),
@@ -386,7 +368,6 @@ async def list_tools(self, cursor: str | None = None) -> types.ListToolsResult:
386368
result = await self.send_request(
387369
types.ClientRequest(
388370
types.ListToolsRequest(
389-
method="tools/list",
390371
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
391372
)
392373
),
@@ -402,13 +383,7 @@ async def list_tools(self, cursor: str | None = None) -> types.ListToolsResult:
402383

403384
async def send_roots_list_changed(self) -> None:
404385
"""Send a roots/list_changed notification."""
405-
await self.send_notification(
406-
types.ClientNotification(
407-
types.RootsListChangedNotification(
408-
method="notifications/roots/list_changed",
409-
)
410-
)
411-
)
386+
await self.send_notification(types.ClientNotification(types.RootsListChangedNotification()))
412387

413388
async def _received_request(self, responder: RequestResponder[types.ServerRequest, types.ClientResult]) -> None:
414389
ctx = RequestContext[ClientSession, Any](

src/mcp/client/stdio/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"HOMEPATH",
3333
"LOCALAPPDATA",
3434
"PATH",
35+
"PATHEXT",
3536
"PROCESSOR_ARCHITECTURE",
3637
"SYSTEMDRIVE",
3738
"SYSTEMROOT",

src/mcp/client/streamable_http.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,19 @@ async def _handle_post_request(self, ctx: RequestContext) -> None:
279279
if is_initialization:
280280
self._maybe_extract_session_id_from_response(response)
281281

282-
content_type = response.headers.get(CONTENT_TYPE, "").lower()
283-
284-
if content_type.startswith(JSON):
285-
await self._handle_json_response(response, ctx.read_stream_writer, is_initialization)
286-
elif content_type.startswith(SSE):
287-
await self._handle_sse_response(response, ctx, is_initialization)
288-
else:
289-
await self._handle_unexpected_content_type(
290-
content_type,
291-
ctx.read_stream_writer,
292-
)
282+
# Per https://modelcontextprotocol.io/specification/2025-06-18/basic#notifications:
283+
# The server MUST NOT send a response to notifications.
284+
if isinstance(message.root, JSONRPCRequest):
285+
content_type = response.headers.get(CONTENT_TYPE, "").lower()
286+
if content_type.startswith(JSON):
287+
await self._handle_json_response(response, ctx.read_stream_writer, is_initialization)
288+
elif content_type.startswith(SSE):
289+
await self._handle_sse_response(response, ctx, is_initialization)
290+
else:
291+
await self._handle_unexpected_content_type(
292+
content_type,
293+
ctx.read_stream_writer,
294+
)
293295

294296
async def _handle_json_response(
295297
self,

src/mcp/server/auth/provider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ async def revoke_token(
281281

282282
def construct_redirect_uri(redirect_uri_base: str, **params: str | None) -> str:
283283
parsed_uri = urlparse(redirect_uri_base)
284-
query_params = [(k, v) for k, vs in parse_qs(parsed_uri.query) for v in vs]
284+
query_params = [(k, v) for k, vs in parse_qs(parsed_uri.query).items() for v in vs]
285285
for k, v in params.items():
286286
if v is not None:
287287
query_params.append((k, v))

src/mcp/server/fastmcp/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from importlib.metadata import version
44

55
from .server import Context, FastMCP
6-
from .utilities.types import Image
6+
from .utilities.types import Audio, Image
77

88
__version__ = version("mcp")
9-
__all__ = ["FastMCP", "Context", "Image"]
9+
__all__ = ["FastMCP", "Context", "Image", "Audio"]

0 commit comments

Comments
 (0)