Update matching logic for resources

This commit is contained in:
Jeremiah Lowin 2025-04-15 10:04:41 -04:00
commit 9fdcb8ac93
3 changed files with 194 additions and 12 deletions

View file

@ -8,7 +8,7 @@ from pydantic import AnyUrl
from fastmcp.exceptions import ResourceError
from fastmcp.resources import FunctionResource, Resource
from fastmcp.resources.template import ResourceTemplate
from fastmcp.resources.template import ResourceTemplate, match_uri_template
from fastmcp.settings import DuplicateBehavior
from fastmcp.utilities.logging import get_logger
@ -207,9 +207,10 @@ class ResourceManager:
if resource := self._resources.get(uri_str):
return resource
# Then check templates
for template in self._templates.values():
if params := template.matches(uri_str):
# Then check templates - use the utility function to match against storage keys
for storage_key, template in self._templates.items():
# Try to match against the storage key (which might be a custom key)
if params := match_uri_template(uri_str, storage_key):
try:
return await template.create_resource(uri_str, params)
except Exception as e:

View file

@ -7,12 +7,37 @@ import re
from collections.abc import Callable
from typing import Annotated, Any
from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
from pydantic import (
AnyUrl,
BaseModel,
BeforeValidator,
Field,
TypeAdapter,
validate_call,
)
from fastmcp.resources.types import FunctionResource, Resource
from fastmcp.utilities.types import _convert_set_defaults
def match_uri_template(uri: str, uri_template: str) -> dict[str, Any] | None:
"""Match a URI against a template and extract parameters.
Args:
uri: The URI to match against the template
uri_template: The URI template to match against
Returns:
A dictionary of extracted parameters if there's a match, or None if no match
"""
# Convert template to regex pattern
pattern = uri_template.replace("{", "(?P<").replace("}", ">[^/]+)")
match = re.match(f"^{pattern}$", uri)
if match:
return match.groupdict()
return None
class MyModel(BaseModel):
key: str
value: int
@ -94,12 +119,7 @@ class ResourceTemplate(BaseModel):
def matches(self, uri: str) -> dict[str, Any] | None:
"""Check if URI matches template and extract parameters."""
# Convert template to regex pattern
pattern = self.uri_template.replace("{", "(?P<").replace("}", ">[^/]+)")
match = re.match(f"^{pattern}$", uri)
if match:
return match.groupdict()
return None
return match_uri_template(uri, self.uri_template)
async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
"""Create a resource from the template with the given parameters."""
@ -110,7 +130,7 @@ class ResourceTemplate(BaseModel):
result = await result
return FunctionResource(
uri=uri, # type: ignore
uri=AnyUrl(uri), # Explicitly convert to AnyUrl
name=self.name,
description=self.description,
mime_type=self.mime_type,