diff --git a/README.md b/README.md index 5dbc4bd9d..e3f3f47f4 100644 --- a/README.md +++ b/README.md @@ -1032,7 +1032,7 @@ async def notify_data_update(resource_uri: str, ctx: Context) -> str: # Perform data update logic here # Notify clients that this specific resource changed - await ctx.session.send_resource_updated(AnyUrl(resource_uri)) + await ctx.session.send_resource_updated(resource_uri) # If this affects the overall resource list, notify about that too await ctx.session.send_resource_list_changed() @@ -1923,8 +1923,6 @@ For servers that need to handle large datasets, the low-level server provides pa Example of implementing pagination with MCP server decorators. """ -from pydantic import AnyUrl - import mcp.types as types from mcp.server.lowlevel import Server @@ -1949,7 +1947,7 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types # Get page of resources page_items = [ - types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}") + types.Resource(uri=f"resource://items/{item}", name=item, description=f"Description for {item}") for item in ITEMS[start:end] ] @@ -2035,8 +2033,6 @@ cd to the `examples/snippets/clients` directory and run: import asyncio import os -from pydantic import AnyUrl - from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client from mcp.shared.context import RequestContext @@ -2089,7 +2085,7 @@ async def run(): print(f"Available tools: {[t.name for t in tools.tools]}") # Read a resource (greeting resource from fastmcp_quickstart) - resource_content = await session.read_resource(AnyUrl("greeting://World")) + resource_content = await session.read_resource("greeting://World") content_block = resource_content.contents[0] if isinstance(content_block, types.TextContent): print(f"Resource content: {content_block.text}") @@ -2256,8 +2252,6 @@ cd to the `examples/snippets` directory and run: import asyncio from urllib.parse import parse_qs, urlparse -from pydantic import AnyUrl - from mcp import ClientSession from mcp.client.auth import OAuthClientProvider, TokenStorage from mcp.client.streamable_http import streamablehttp_client @@ -2304,7 +2298,7 @@ async def main(): server_url="http://localhost:8001", client_metadata=OAuthClientMetadata( client_name="Example MCP Client", - redirect_uris=[AnyUrl("http://localhost:3000/callback")], + redirect_uris=["http://localhost:3000/callback"], grant_types=["authorization_code", "refresh_token"], response_types=["code"], scope="user", diff --git a/examples/servers/simple-pagination/mcp_simple_pagination/server.py b/examples/servers/simple-pagination/mcp_simple_pagination/server.py index 360cbc3cf..241284104 100644 --- a/examples/servers/simple-pagination/mcp_simple_pagination/server.py +++ b/examples/servers/simple-pagination/mcp_simple_pagination/server.py @@ -11,7 +11,6 @@ import click import mcp.types as types from mcp.server.lowlevel import Server -from pydantic import AnyUrl from starlette.requests import Request # Sample data - in real scenarios, this might come from a database @@ -27,7 +26,7 @@ SAMPLE_RESOURCES = [ types.Resource( - uri=AnyUrl(f"file:///path/to/resource_{i}.txt"), + uri=f"file:///path/to/resource_{i}.txt", name=f"resource_{i}", description=f"This is sample resource number {i}", ) @@ -160,7 +159,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB # Implement read_resource handler @app.read_resource() - async def read_resource(uri: AnyUrl) -> str: + async def read_resource(uri: str) -> str: # Find the resource in our sample data resource = next((r for r in SAMPLE_RESOURCES if r.uri == uri), None) if not resource: diff --git a/examples/servers/simple-resource/README.md b/examples/servers/simple-resource/README.md index df674e91e..b99737ebf 100644 --- a/examples/servers/simple-resource/README.md +++ b/examples/servers/simple-resource/README.md @@ -22,7 +22,6 @@ Using the MCP client, you can retrieve resources like this using the STDIO trans ```python import asyncio -from mcp.types import AnyUrl from mcp.client.session import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client @@ -39,7 +38,7 @@ async def main(): print(resources) # Get a specific resource - resource = await session.read_resource(AnyUrl("file:///greeting.txt")) + resource = await session.read_resource("file:///greeting.txt") print(resource) diff --git a/examples/servers/simple-resource/mcp_simple_resource/server.py b/examples/servers/simple-resource/mcp_simple_resource/server.py index 151a23eab..813c4dd5c 100644 --- a/examples/servers/simple-resource/mcp_simple_resource/server.py +++ b/examples/servers/simple-resource/mcp_simple_resource/server.py @@ -3,7 +3,6 @@ import mcp.types as types from mcp.server.lowlevel import Server from mcp.server.lowlevel.helper_types import ReadResourceContents -from pydantic import AnyUrl, FileUrl from starlette.requests import Request SAMPLE_RESOURCES = { @@ -37,7 +36,7 @@ def main(port: int, transport: str) -> int: async def list_resources() -> list[types.Resource]: return [ types.Resource( - uri=FileUrl(f"file:///{name}.txt"), + uri=f"file:///{name}.txt", name=name, title=SAMPLE_RESOURCES[name]["title"], description=f"A sample text resource named {name}", @@ -47,7 +46,7 @@ async def list_resources() -> list[types.Resource]: ] @app.read_resource() - async def read_resource(uri: AnyUrl): + async def read_resource(uri: str): if uri.path is None: raise ValueError(f"Invalid resource path: {uri}") name = uri.path.replace(".txt", "").lstrip("/") diff --git a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py index 4b2604b9a..bfa9b2372 100644 --- a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py +++ b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py @@ -8,7 +8,6 @@ import mcp.types as types from mcp.server.lowlevel import Server from mcp.server.streamable_http_manager import StreamableHTTPSessionManager -from pydantic import AnyUrl from starlette.applications import Starlette from starlette.middleware.cors import CORSMiddleware from starlette.routing import Mount @@ -74,7 +73,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB # This will send a resource notificaiton though standalone SSE # established by GET request - await ctx.session.send_resource_updated(uri=AnyUrl("http:///test_resource")) + await ctx.session.send_resource_updated(uri="http:///test_resource") return [ types.TextContent( type="text", diff --git a/examples/snippets/clients/oauth_client.py b/examples/snippets/clients/oauth_client.py index 45026590a..3b66c6476 100644 --- a/examples/snippets/clients/oauth_client.py +++ b/examples/snippets/clients/oauth_client.py @@ -10,8 +10,6 @@ import asyncio from urllib.parse import parse_qs, urlparse -from pydantic import AnyUrl - from mcp import ClientSession from mcp.client.auth import OAuthClientProvider, TokenStorage from mcp.client.streamable_http import streamablehttp_client @@ -58,7 +56,7 @@ async def main(): server_url="http://localhost:8001", client_metadata=OAuthClientMetadata( client_name="Example MCP Client", - redirect_uris=[AnyUrl("http://localhost:3000/callback")], + redirect_uris=["http://localhost:3000/callback"], grant_types=["authorization_code", "refresh_token"], response_types=["code"], scope="user", diff --git a/examples/snippets/clients/stdio_client.py b/examples/snippets/clients/stdio_client.py index ac978035d..05ea0a08c 100644 --- a/examples/snippets/clients/stdio_client.py +++ b/examples/snippets/clients/stdio_client.py @@ -6,8 +6,6 @@ import asyncio import os -from pydantic import AnyUrl - from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client from mcp.shared.context import RequestContext @@ -60,7 +58,7 @@ async def run(): print(f"Available tools: {[t.name for t in tools.tools]}") # Read a resource (greeting resource from fastmcp_quickstart) - resource_content = await session.read_resource(AnyUrl("greeting://World")) + resource_content = await session.read_resource("greeting://World") content_block = resource_content.contents[0] if isinstance(content_block, types.TextContent): print(f"Resource content: {content_block.text}") diff --git a/examples/snippets/servers/pagination_example.py b/examples/snippets/servers/pagination_example.py index 70c3b3492..d62ee5931 100644 --- a/examples/snippets/servers/pagination_example.py +++ b/examples/snippets/servers/pagination_example.py @@ -2,8 +2,6 @@ Example of implementing pagination with MCP server decorators. """ -from pydantic import AnyUrl - import mcp.types as types from mcp.server.lowlevel import Server @@ -28,7 +26,7 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types # Get page of resources page_items = [ - types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}") + types.Resource(uri=f"resource://items/{item}", name=item, description=f"Description for {item}") for item in ITEMS[start:end] ] diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index 8f071021d..42911fdc7 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -5,7 +5,7 @@ import anyio.lowlevel from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from jsonschema import SchemaError, ValidationError, validate -from pydantic import AnyUrl, TypeAdapter +from pydantic import TypeAdapter from typing_extensions import deprecated import mcp.types as types @@ -299,7 +299,7 @@ async def list_resource_templates( types.ListResourceTemplatesResult, ) - async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult: + async def read_resource(self, uri: str) -> types.ReadResourceResult: """Send a resources/read request.""" return await self.send_request( types.ClientRequest( @@ -310,7 +310,7 @@ async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult: types.ReadResourceResult, ) - async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: + async def subscribe_resource(self, uri: str) -> types.EmptyResult: """Send a resources/subscribe request.""" return await self.send_request( types.ClientRequest( @@ -321,7 +321,7 @@ async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: types.EmptyResult, ) - async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: + async def unsubscribe_resource(self, uri: str) -> types.EmptyResult: """Send a resources/unsubscribe request.""" return await self.send_request( types.ClientRequest( diff --git a/src/mcp/server/auth/handlers/authorize.py b/src/mcp/server/auth/handlers/authorize.py index 850f8373d..3fe5a8f70 100644 --- a/src/mcp/server/auth/handlers/authorize.py +++ b/src/mcp/server/auth/handlers/authorize.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import Any, Literal -from pydantic import AnyUrl, BaseModel, Field, RootModel, ValidationError +from pydantic import BaseModel, Field, RootModel, ValidationError from starlette.datastructures import FormData, QueryParams from starlette.requests import Request from starlette.responses import RedirectResponse, Response @@ -24,7 +24,7 @@ class AuthorizationRequest(BaseModel): # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 client_id: str = Field(..., description="The client ID") - redirect_uri: AnyUrl | None = Field(None, description="URL to redirect to after authorization") + redirect_uri: str | None = Field(None, description="URL to redirect to after authorization") # see OAuthClientMetadata; we only support `code` response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow") @@ -44,7 +44,7 @@ class AuthorizationRequest(BaseModel): class AuthorizationErrorResponse(BaseModel): error: AuthorizationErrorCode error_description: str | None - error_uri: AnyUrl | None = None + error_uri: str | None = None # must be set if provided in the request state: str | None = None @@ -106,9 +106,7 @@ async def error_response( if params is not None and "redirect_uri" not in params: raw_redirect_uri = None else: - raw_redirect_uri = AnyUrlModel.model_validate( - best_effort_extract_string("redirect_uri", params) - ).root + raw_redirect_uri = best_effort_extract_string("redirect_uri", params) redirect_uri = client.validate_redirect_uri(raw_redirect_uri) except (ValidationError, InvalidRedirectUriError): # if the redirect URI is invalid, ignore it & just return the diff --git a/src/mcp/server/auth/provider.py b/src/mcp/server/auth/provider.py index a7b108602..53701b79a 100644 --- a/src/mcp/server/auth/provider.py +++ b/src/mcp/server/auth/provider.py @@ -2,7 +2,7 @@ from typing import Generic, Literal, Protocol, TypeVar from urllib.parse import parse_qs, urlencode, urlparse, urlunparse -from pydantic import AnyUrl, BaseModel +from pydantic import BaseModel from mcp.shared.auth import OAuthClientInformationFull, OAuthToken @@ -11,7 +11,7 @@ class AuthorizationParams(BaseModel): state: str | None scopes: list[str] | None code_challenge: str - redirect_uri: AnyUrl + redirect_uri: str redirect_uri_provided_explicitly: bool resource: str | None = None # RFC 8707 resource indicator @@ -22,7 +22,7 @@ class AuthorizationCode(BaseModel): expires_at: float client_id: str code_challenge: str - redirect_uri: AnyUrl + redirect_uri: str redirect_uri_provided_explicitly: bool resource: str | None = None # RFC 8707 resource indicator diff --git a/src/mcp/server/fastmcp/resources/base.py b/src/mcp/server/fastmcp/resources/base.py index a44c9db9e..80418b63c 100644 --- a/src/mcp/server/fastmcp/resources/base.py +++ b/src/mcp/server/fastmcp/resources/base.py @@ -4,7 +4,6 @@ from typing import Annotated from pydantic import ( - AnyUrl, BaseModel, ConfigDict, Field, @@ -21,7 +20,7 @@ class Resource(BaseModel, abc.ABC): model_config = ConfigDict(validate_default=True) - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource") + uri: Annotated[str, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource") name: str | None = Field(description="Name of the resource", default=None) title: str | None = Field(description="Human-readable title of the resource", default=None) description: str | None = Field(description="Description of the resource", default=None) diff --git a/src/mcp/server/session.py b/src/mcp/server/session.py index 7a99218fa..932399894 100644 --- a/src/mcp/server/session.py +++ b/src/mcp/server/session.py @@ -43,7 +43,6 @@ async def handle_list_prompts(ctx: RequestContext) -> list[types.Prompt]: import anyio import anyio.lowlevel from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream -from pydantic import AnyUrl import mcp.types as types from mcp.server.models import InitializationOptions @@ -202,7 +201,7 @@ async def send_log_message( related_request_id, ) - async def send_resource_updated(self, uri: AnyUrl) -> None: + async def send_resource_updated(self, uri: str) -> None: """Send a resource updated notification.""" await self.send_notification( types.ServerNotification( diff --git a/src/mcp/types.py b/src/mcp/types.py index 871322740..79e258e60 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1,8 +1,7 @@ from collections.abc import Callable from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar -from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel -from pydantic.networks import AnyUrl, UrlConstraints +from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel, UrlConstraints from typing_extensions import deprecated """ @@ -431,7 +430,7 @@ class Annotations(BaseModel): class Resource(BaseMetadata): """A known resource that the server is capable of reading.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: Annotated[str, UrlConstraints(host_required=False)] """The URI of this resource.""" description: str | None = None """A description of what this resource represents.""" @@ -502,7 +501,7 @@ class ListResourceTemplatesResult(PaginatedResult): class ReadResourceRequestParams(RequestParams): """Parameters for reading a resource.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: Annotated[str, UrlConstraints(host_required=False)] """ The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. @@ -520,7 +519,7 @@ class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/ class ResourceContents(BaseModel): """The contents of a specific resource or sub-resource.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: Annotated[str, UrlConstraints(host_required=False)] """The URI of this resource.""" mimeType: str | None = None """The MIME type of this resource, if known.""" @@ -570,7 +569,7 @@ class ResourceListChangedNotification( class SubscribeRequestParams(RequestParams): """Parameters for subscribing to a resource.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: Annotated[str, UrlConstraints(host_required=False)] """ The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. @@ -591,7 +590,7 @@ class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscr class UnsubscribeRequestParams(RequestParams): """Parameters for unsubscribing from a resource.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: Annotated[str, UrlConstraints(host_required=False)] """The URI of the resource to unsubscribe from.""" model_config = ConfigDict(extra="allow") @@ -609,7 +608,7 @@ class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/un class ResourceUpdatedNotificationParams(NotificationParams): """Parameters for resource update notifications.""" - uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + uri: str """ The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. @@ -1085,7 +1084,7 @@ class ResourceTemplateReference(BaseModel): """A reference to a resource or resource template definition.""" type: Literal["ref/resource"] - uri: str + uri: Annotated[str, UrlConstraints(host_required=False)] """The URI or URI template of the resource.""" model_config = ConfigDict(extra="allow")