Update URL validation to allow file and other nonstandard schemas

This commit is contained in:
Henry Wildermuth
2025-02-13 14:21:50 -08:00
parent faf3a9d7d6
commit b53e090299
2 changed files with 19 additions and 12 deletions

View File

@@ -2,7 +2,7 @@ import anyio
import click import click
import mcp.types as types import mcp.types as types
from mcp.server.lowlevel import Server from mcp.server.lowlevel import Server
from pydantic import AnyUrl from pydantic import FileUrl
SAMPLE_RESOURCES = { SAMPLE_RESOURCES = {
"greeting": "Hello! This is a sample text resource.", "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]: async def list_resources() -> list[types.Resource]:
return [ return [
types.Resource( types.Resource(
uri=AnyUrl(f"file:///{name}.txt"), uri=FileUrl(f"file:///{name}.txt"),
name=name, name=name,
description=f"A sample text resource named {name}", description=f"A sample text resource named {name}",
mimeType="text/plain", mimeType="text/plain",
@@ -35,8 +35,7 @@ def main(port: int, transport: str) -> int:
] ]
@app.read_resource() @app.read_resource()
async def read_resource(uri: AnyUrl) -> str | bytes: async def read_resource(uri: FileUrl) -> str | bytes:
assert uri.path is not None
name = uri.path.replace(".txt", "").lstrip("/") name = uri.path.replace(".txt", "").lstrip("/")
if name not in SAMPLE_RESOURCES: if name not in SAMPLE_RESOURCES:

View File

@@ -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 import BaseModel, ConfigDict, Field, FileUrl, RootModel
from pydantic.networks import AnyUrl from pydantic.networks import AnyUrl, UrlConstraints
""" """
Model Context Protocol bindings for Python Model Context Protocol bindings for Python
@@ -353,7 +361,7 @@ class Annotations(BaseModel):
class Resource(BaseModel): class Resource(BaseModel):
"""A known resource that the server is capable of reading.""" """A known resource that the server is capable of reading."""
uri: AnyUrl uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
"""The URI of this resource.""" """The URI of this resource."""
name: str name: str
"""A human-readable name for this resource.""" """A human-readable name for this resource."""
@@ -415,7 +423,7 @@ class ListResourceTemplatesResult(PaginatedResult):
class ReadResourceRequestParams(RequestParams): class ReadResourceRequestParams(RequestParams):
"""Parameters for reading a resource.""" """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 The URI of the resource to read. The URI can use any protocol; it is up to the
server how to interpret it. server how to interpret it.
@@ -433,7 +441,7 @@ class ReadResourceRequest(Request):
class ResourceContents(BaseModel): class ResourceContents(BaseModel):
"""The contents of a specific resource or sub-resource.""" """The contents of a specific resource or sub-resource."""
uri: AnyUrl uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
"""The URI of this resource.""" """The URI of this resource."""
mimeType: str | None = None mimeType: str | None = None
"""The MIME type of this resource, if known.""" """The MIME type of this resource, if known."""
@@ -476,7 +484,7 @@ class ResourceListChangedNotification(Notification):
class SubscribeRequestParams(RequestParams): class SubscribeRequestParams(RequestParams):
"""Parameters for subscribing to a resource.""" """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 URI of the resource to subscribe to. The URI can use any protocol; it is up to
the server how to interpret it. the server how to interpret it.
@@ -497,7 +505,7 @@ class SubscribeRequest(Request):
class UnsubscribeRequestParams(RequestParams): class UnsubscribeRequestParams(RequestParams):
"""Parameters for unsubscribing from a resource.""" """Parameters for unsubscribing from a resource."""
uri: AnyUrl uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
"""The URI of the resource to unsubscribe from.""" """The URI of the resource to unsubscribe from."""
model_config = ConfigDict(extra="allow") model_config = ConfigDict(extra="allow")
@@ -515,7 +523,7 @@ class UnsubscribeRequest(Request):
class ResourceUpdatedNotificationParams(NotificationParams): class ResourceUpdatedNotificationParams(NotificationParams):
"""Parameters for resource update notifications.""" """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 The URI of the resource that has been updated. This might be a sub-resource of the
one that the client actually subscribed to. one that the client actually subscribed to.