From b53e0902991b43d1e9f32774849b5c87b4d4c178 Mon Sep 17 00:00:00 2001 From: Henry Wildermuth <hmwildermuth@gmail.com> Date: Thu, 13 Feb 2025 14:21:50 -0800 Subject: [PATCH] Update URL validation to allow file and other nonstandard schemas --- .../mcp_simple_resource/server.py | 7 +++--- src/mcp/types.py | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/examples/servers/simple-resource/mcp_simple_resource/server.py b/examples/servers/simple-resource/mcp_simple_resource/server.py index 11ba5692..0ec1d926 100644 --- a/examples/servers/simple-resource/mcp_simple_resource/server.py +++ b/examples/servers/simple-resource/mcp_simple_resource/server.py @@ -2,7 +2,7 @@ import click import mcp.types as types from mcp.server.lowlevel import Server -from pydantic import AnyUrl +from pydantic import FileUrl SAMPLE_RESOURCES = { "greeting": "Hello! This is a sample text resource.", @@ -26,7 +26,7 @@ def main(port: int, transport: str) -> int: async def list_resources() -> list[types.Resource]: return [ types.Resource( - uri=AnyUrl(f"file:///{name}.txt"), + uri=FileUrl(f"file:///{name}.txt"), name=name, description=f"A sample text resource named {name}", mimeType="text/plain", @@ -35,8 +35,7 @@ async def list_resources() -> list[types.Resource]: ] @app.read_resource() - async def read_resource(uri: AnyUrl) -> str | bytes: - assert uri.path is not None + async def read_resource(uri: FileUrl) -> str | bytes: name = uri.path.replace(".txt", "").lstrip("/") if name not in SAMPLE_RESOURCES: diff --git a/src/mcp/types.py b/src/mcp/types.py index d1157aa6..7d867bd3 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1,7 +1,15 @@ -from typing import Annotated, Any, Callable, Generic, Literal, TypeAlias, TypeVar +from typing import ( + Annotated, + Any, + Callable, + Generic, + Literal, + TypeAlias, + TypeVar, +) from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel -from pydantic.networks import AnyUrl +from pydantic.networks import AnyUrl, UrlConstraints """ Model Context Protocol bindings for Python @@ -353,7 +361,7 @@ class Annotations(BaseModel): class Resource(BaseModel): """A known resource that the server is capable of reading.""" - uri: AnyUrl + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] """The URI of this resource.""" name: str """A human-readable name for this resource.""" @@ -415,7 +423,7 @@ class ListResourceTemplatesResult(PaginatedResult): class ReadResourceRequestParams(RequestParams): """Parameters for reading a resource.""" - uri: AnyUrl + uri: Annotated[AnyUrl, 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. @@ -433,7 +441,7 @@ class ReadResourceRequest(Request): class ResourceContents(BaseModel): """The contents of a specific resource or sub-resource.""" - uri: AnyUrl + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] """The URI of this resource.""" mimeType: str | None = None """The MIME type of this resource, if known.""" @@ -476,7 +484,7 @@ class ResourceListChangedNotification(Notification): class SubscribeRequestParams(RequestParams): """Parameters for subscribing to a resource.""" - uri: AnyUrl + uri: Annotated[AnyUrl, 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. @@ -497,7 +505,7 @@ class SubscribeRequest(Request): class UnsubscribeRequestParams(RequestParams): """Parameters for unsubscribing from a resource.""" - uri: AnyUrl + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] """The URI of the resource to unsubscribe from.""" model_config = ConfigDict(extra="allow") @@ -515,7 +523,7 @@ class UnsubscribeRequest(Request): class ResourceUpdatedNotificationParams(NotificationParams): """Parameters for resource update notifications.""" - uri: AnyUrl + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] """ The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.