Skip to content

Commit 74c6779

Browse files
committed
Merge remote-tracking branch 'origin/main' into enforce-120-length
2 parents 118a643 + f7265f7 commit 74c6779

File tree

21 files changed

+354
-92
lines changed

21 files changed

+354
-92
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ async def exchange_authorization_code(
207207

208208
return OAuthToken(
209209
access_token=mcp_token,
210-
token_type="bearer",
210+
token_type="Bearer",
211211
expires_in=3600,
212212
scope=" ".join(authorization_code.scopes),
213213
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import click
33
import mcp.types as types
44
from mcp.server.lowlevel import Server
5-
from pydantic import AnyUrl
5+
from pydantic import AnyUrl, FileUrl
66

77
SAMPLE_RESOURCES = {
88
"greeting": "Hello! This is a sample text resource.",
@@ -26,7 +26,7 @@ def main(port: int, transport: str) -> int:
2626
async def list_resources() -> list[types.Resource]:
2727
return [
2828
types.Resource(
29-
uri=AnyUrl(f"file:///{name}.txt"),
29+
uri=FileUrl(f"file:///{name}.txt"),
3030
name=name,
3131
description=f"A sample text resource named {name}",
3232
mimeType="text/plain",

examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ def main(
4343
@app.call_tool()
4444
async def call_tool(
4545
name: str, arguments: dict
46-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
46+
) -> list[
47+
types.TextContent
48+
| types.ImageContent
49+
| types.AudioContent
50+
| types.EmbeddedResource
51+
]:
4752
ctx = app.request_context
4853
interval = arguments.get("interval", 1.0)
4954
count = arguments.get("count", 5)

examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ def main(
4747
@app.call_tool()
4848
async def call_tool(
4949
name: str, arguments: dict
50-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
50+
) -> list[
51+
types.TextContent
52+
| types.ImageContent
53+
| types.AudioContent
54+
| types.EmbeddedResource
55+
]:
5156
ctx = app.request_context
5257
interval = arguments.get("interval", 1.0)
5358
count = arguments.get("count", 5)

examples/servers/simple-tool/mcp_simple_tool/server.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
async def fetch_website(
99
url: str,
10-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
10+
) -> list[
11+
types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource
12+
]:
1113
headers = {
1214
"User-Agent": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)"
1315
}
@@ -31,7 +33,12 @@ def main(port: int, transport: str) -> int:
3133
@app.call_tool()
3234
async def fetch_tool(
3335
name: str, arguments: dict
34-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
36+
) -> list[
37+
types.TextContent
38+
| types.ImageContent
39+
| types.AudioContent
40+
| types.EmbeddedResource
41+
]:
3542
if name != "fetch":
3643
raise ValueError(f"Unknown tool: {name}")
3744
if "url" not in arguments:

src/mcp/client/streamable_http.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ async def handle_get_stream(
184184
"GET",
185185
self.url,
186186
headers=headers,
187-
timeout=httpx.Timeout(self.timeout.seconds, read=self.sse_read_timeout.seconds),
187+
timeout=httpx.Timeout(
188+
self.timeout.total_seconds(),
189+
read=self.sse_read_timeout.total_seconds(),
190+
),
188191
) as event_source:
189192
event_source.response.raise_for_status()
190193
logger.debug("GET SSE connection established")
@@ -213,7 +216,10 @@ async def _handle_resumption_request(self, ctx: RequestContext) -> None:
213216
"GET",
214217
self.url,
215218
headers=headers,
216-
timeout=httpx.Timeout(self.timeout.seconds, read=ctx.sse_read_timeout.seconds),
219+
timeout=httpx.Timeout(
220+
self.timeout.total_seconds(),
221+
read=ctx.sse_read_timeout.total_seconds(),
222+
),
217223
) as event_source:
218224
event_source.response.raise_for_status()
219225
logger.debug("Resumption GET SSE connection established")
@@ -443,7 +449,10 @@ async def streamablehttp_client(
443449

444450
async with httpx_client_factory(
445451
headers=transport.request_headers,
446-
timeout=httpx.Timeout(transport.timeout.seconds, read=transport.sse_read_timeout.seconds),
452+
timeout=httpx.Timeout(
453+
transport.timeout.total_seconds(),
454+
read=transport.sse_read_timeout.total_seconds(),
455+
),
447456
auth=transport.auth,
448457
) as client:
449458
# Define callbacks that need access to tg

src/mcp/server/auth/handlers/authorize.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dataclasses import dataclass
33
from typing import Any, Literal
44

5-
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, RootModel, ValidationError
5+
from pydantic import AnyUrl, BaseModel, Field, RootModel, ValidationError
66
from starlette.datastructures import FormData, QueryParams
77
from starlette.requests import Request
88
from starlette.responses import RedirectResponse, Response
@@ -24,7 +24,7 @@
2424
class AuthorizationRequest(BaseModel):
2525
# See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
2626
client_id: str = Field(..., description="The client ID")
27-
redirect_uri: AnyHttpUrl | None = Field(None, description="URL to redirect to after authorization")
27+
redirect_uri: AnyUrl | None = Field(None, description="URL to redirect to after authorization")
2828

2929
# see OAuthClientMetadata; we only support `code`
3030
response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow")
@@ -54,8 +54,8 @@ def best_effort_extract_string(key: str, params: None | FormData | QueryParams)
5454
return None
5555

5656

57-
class AnyHttpUrlModel(RootModel[AnyHttpUrl]):
58-
root: AnyHttpUrl
57+
class AnyUrlModel(RootModel[AnyUrl]):
58+
root: AnyUrl
5959

6060

6161
@dataclass
@@ -102,7 +102,7 @@ async def error_response(
102102
if params is not None and "redirect_uri" not in params:
103103
raw_redirect_uri = None
104104
else:
105-
raw_redirect_uri = AnyHttpUrlModel.model_validate(
105+
raw_redirect_uri = AnyUrlModel.model_validate(
106106
best_effort_extract_string("redirect_uri", params)
107107
).root
108108
redirect_uri = client.validate_redirect_uri(raw_redirect_uri)

src/mcp/server/auth/handlers/token.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from dataclasses import dataclass
55
from typing import Annotated, Any, Literal
66

7-
from pydantic import AnyHttpUrl, BaseModel, Field, RootModel, ValidationError
7+
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, RootModel, ValidationError
88
from starlette.requests import Request
99

1010
from mcp.server.auth.errors import stringify_pydantic_error
@@ -18,7 +18,7 @@ class AuthorizationCodeRequest(BaseModel):
1818
# See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
1919
grant_type: Literal["authorization_code"]
2020
code: str = Field(..., description="The authorization code")
21-
redirect_uri: AnyHttpUrl | None = Field(None, description="Must be the same as redirect URI provided in /authorize")
21+
redirect_uri: AnyUrl | None = Field(None, description="Must be the same as redirect URI provided in /authorize")
2222
client_id: str
2323
# we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
2424
client_secret: str | None = None

src/mcp/server/auth/provider.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Generic, Literal, Protocol, TypeVar
33
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
44

5-
from pydantic import AnyHttpUrl, BaseModel
5+
from pydantic import AnyUrl, BaseModel
66

77
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
88

@@ -11,7 +11,7 @@ class AuthorizationParams(BaseModel):
1111
state: str | None
1212
scopes: list[str] | None
1313
code_challenge: str
14-
redirect_uri: AnyHttpUrl
14+
redirect_uri: AnyUrl
1515
redirect_uri_provided_explicitly: bool
1616

1717

@@ -21,7 +21,7 @@ class AuthorizationCode(BaseModel):
2121
expires_at: float
2222
client_id: str
2323
code_challenge: str
24-
redirect_uri: AnyHttpUrl
24+
redirect_uri: AnyUrl
2525
redirect_uri_provided_explicitly: bool
2626

2727

src/mcp/server/fastmcp/prompts/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import pydantic_core
88
from pydantic import BaseModel, Field, TypeAdapter, validate_call
99

10-
from mcp.types import EmbeddedResource, ImageContent, TextContent
10+
from mcp.types import AudioContent, EmbeddedResource, ImageContent, TextContent
1111

12-
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
12+
CONTENT_TYPES = TextContent | ImageContent | AudioContent | EmbeddedResource
1313

1414

1515
class Message(BaseModel):

src/mcp/server/fastmcp/server.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
5353
from mcp.types import (
5454
AnyFunction,
55+
AudioContent,
5556
EmbeddedResource,
5657
GetPromptResult,
5758
ImageContent,
@@ -257,7 +258,7 @@ def get_context(self) -> Context[ServerSession, object, Request]:
257258

258259
async def call_tool(
259260
self, name: str, arguments: dict[str, Any]
260-
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
261+
) -> Sequence[TextContent | ImageContent | AudioContent | EmbeddedResource]:
261262
"""Call a tool by name with arguments."""
262263
context = self.get_context()
263264
result = await self._tool_manager.call_tool(name, arguments, context=context)
@@ -841,12 +842,12 @@ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -
841842

842843
def _convert_to_content(
843844
result: Any,
844-
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
845+
) -> Sequence[TextContent | ImageContent | AudioContent | EmbeddedResource]:
845846
"""Convert a result to a sequence of content objects."""
846847
if result is None:
847848
return []
848849

849-
if isinstance(result, TextContent | ImageContent | EmbeddedResource):
850+
if isinstance(result, TextContent | ImageContent | AudioContent | EmbeddedResource):
850851
return [result]
851852

852853
if isinstance(result, Image):

src/mcp/server/lowlevel/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ def call_tool(self):
384384
def decorator(
385385
func: Callable[
386386
...,
387-
Awaitable[Iterable[types.TextContent | types.ImageContent | types.EmbeddedResource]],
387+
Awaitable[
388+
Iterable[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource]
389+
],
388390
],
389391
):
390392
logger.debug("Registering handler for CallToolRequest")
@@ -510,9 +512,7 @@ async def _handle_message(
510512
await self._handle_notification(notify)
511513

512514
for warning in w:
513-
logger.info(
514-
"Warning: %s: %s", warning.category.__name__, warning.message
515-
)
515+
logger.info("Warning: %s: %s", warning.category.__name__, warning.message)
516516

517517
async def _handle_request(
518518
self,

src/mcp/shared/auth.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Literal
22

3-
from pydantic import AnyHttpUrl, BaseModel, Field
3+
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator
44

55

66
class OAuthToken(BaseModel):
@@ -9,11 +9,20 @@ class OAuthToken(BaseModel):
99
"""
1010

1111
access_token: str
12-
token_type: Literal["bearer"] = "bearer"
12+
token_type: Literal["Bearer"] = "Bearer"
1313
expires_in: int | None = None
1414
scope: str | None = None
1515
refresh_token: str | None = None
1616

17+
@field_validator("token_type", mode="before")
18+
@classmethod
19+
def normalize_token_type(cls, v: str | None) -> str | None:
20+
if isinstance(v, str):
21+
# Bearer is title-cased in the spec, so we normalize it
22+
# https://datatracker.ietf.org/doc/html/rfc6750#section-4
23+
return v.title()
24+
return v
25+
1726

1827
class InvalidScopeError(Exception):
1928
def __init__(self, message: str):
@@ -32,7 +41,7 @@ class OAuthClientMetadata(BaseModel):
3241
for the full specification.
3342
"""
3443

35-
redirect_uris: list[AnyHttpUrl] = Field(..., min_length=1)
44+
redirect_uris: list[AnyUrl] = Field(..., min_length=1)
3645
# token_endpoint_auth_method: this implementation only supports none &
3746
# client_secret_post;
3847
# ie: we do not support client_secret_basic
@@ -69,7 +78,7 @@ def validate_scope(self, requested_scope: str | None) -> list[str] | None:
6978
raise InvalidScopeError(f"Client was not registered with scope {scope}")
7079
return requested_scopes
7180

72-
def validate_redirect_uri(self, redirect_uri: AnyHttpUrl | None) -> AnyHttpUrl:
81+
def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
7382
if redirect_uri is not None:
7483
# Validate redirect_uri against client's registered redirect URIs
7584
if redirect_uri not in self.redirect_uris:
@@ -104,19 +113,19 @@ class OAuthMetadata(BaseModel):
104113
token_endpoint: AnyHttpUrl
105114
registration_endpoint: AnyHttpUrl | None = None
106115
scopes_supported: list[str] | None = None
107-
response_types_supported: list[Literal["code"]] = ["code"]
116+
response_types_supported: list[str] = ["code"]
108117
response_modes_supported: list[Literal["query", "fragment"]] | None = None
109-
grant_types_supported: list[Literal["authorization_code", "refresh_token"]] | None = None
110-
token_endpoint_auth_methods_supported: list[Literal["none", "client_secret_post"]] | None = None
118+
grant_types_supported: list[str] | None = None
119+
token_endpoint_auth_methods_supported: list[str] | None = None
111120
token_endpoint_auth_signing_alg_values_supported: None = None
112121
service_documentation: AnyHttpUrl | None = None
113122
ui_locales_supported: list[str] | None = None
114123
op_policy_uri: AnyHttpUrl | None = None
115124
op_tos_uri: AnyHttpUrl | None = None
116125
revocation_endpoint: AnyHttpUrl | None = None
117-
revocation_endpoint_auth_methods_supported: list[Literal["client_secret_post"]] | None = None
126+
revocation_endpoint_auth_methods_supported: list[str] | None = None
118127
revocation_endpoint_auth_signing_alg_values_supported: None = None
119128
introspection_endpoint: AnyHttpUrl | None = None
120-
introspection_endpoint_auth_methods_supported: list[Literal["client_secret_post"]] | None = None
129+
introspection_endpoint_auth_methods_supported: list[str] | None = None
121130
introspection_endpoint_auth_signing_alg_values_supported: None = None
122-
code_challenge_methods_supported: list[Literal["S256"]] | None = None
131+
code_challenge_methods_supported: list[str] | None = None

0 commit comments

Comments
 (0)