Skip to content

Conversation

beaterblank
Copy link

Feature Added:
Enhanced ResourceTemplate URI matching and type handling logic.

Motivation and Context

This change introduces a typed-aware URI parsing mechanism similar to FastAPI’s path parameter system.
It resolves issue #220 by adding a more flexible and self-contained approach for handling resource URIs with type annotations.

How Has This Been Tested?

  • Verified functionality through new and existing unit tests for:

    • Typed placeholders ({name:str}, {id:int}, {value:float}, {uuid:UUID}, {path:path})
    • Automatic type conversion and validation
  • Confirmed correct parameter extraction and type enforcement in all matching scenarios.

Breaking Changes

  • None expected, but users relying on untyped string-only behaviour may observe typed conversions.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the [MCP Documentation](https://modelcontextprotocol.io)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The implementation assumes URI templates follow a strict pattern and defaults to str when no type is provided.
Optional parameters and wildcard matching beyond {path: path} are not supported / cannot be supported.

Agasthya Kasturi and others added 11 commits April 4, 2025 17:25
Improve ResourceTemplate URI matching and type handling

Fixes issue modelcontextprotocol#220

- Implemented typed-aware URI matching logic within ResourceTemplate.matches()
  supporting {name:str}, {id:int}, {value:float},{uuid:UUID}, and {path:path} placeholders.
  similar to what FastAPI supports
- Added automatic type conversion and validation for URI parameters.
- Updated unit tests to cover typed placeholders, numeric and float parameters,
  and path-based URIs.

Limitations and notes:
- Current implementation assumes URI templates follow a strict pattern or default
  to str and do not support optional parameters or wildcards beyond {path:path}.
@felixweinberger felixweinberger self-assigned this Oct 7, 2025
@felixweinberger felixweinberger added needs more eyes Needs alignment among maintainers whether this is something we want to add needs maintainer action Potentially serious issue - needs proactive fix and maintainer attention labels Oct 7, 2025
…g it on every match, removed bare exception added value error
@felixweinberger felixweinberger removed their assignment Oct 10, 2025
@beaterblank
Copy link
Author

beaterblank commented Oct 13, 2025

Hello, @felixweinberger, would you be able to help find someone who could review the code? Thank you!

@felixweinberger felixweinberger self-assigned this Oct 14, 2025
@maxisbey
Copy link
Contributor

related: #436

@felixweinberger felixweinberger self-assigned this Oct 16, 2025
Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @beaterblank thank you for this contribution - FYI there's another PR #427 that's also enhancing URI handling, though for a slightly different usecase, might be some conflicts.

This PR takes the convertor approach suggested in #220, but @Kludex raised a good point on #427 whether we should aim for more of a FastAPI-level design, which makes sense in terms of abstraction level to me.

Rather than embedding convertor syntax in the URI template ({path:path}, {id:int}), we could specify parameter behavior in the function signature using Annotated:

from typing import Annotated

@mcp.resource("files://{path}")
def get_file(path: Annotated[str, Path(allow_slashes=True)]):
    ...

This would also provide a unified way to handle both path and query parameters:

@mcp.resource("search://{category}")
def search(
    category: str,  # Simple path param, no annotation needed
    format: Annotated[str, Query(default="json")],  # Query param
    limit: Annotated[int, Query(default=10)]
):
    ...

@beaterblank
Copy link
Author

Hi @beaterblank thank you for this contribution - FYI there's another PR #427 that's also enhancing URI handling, though for a slightly different usecase, might be some conflicts.

This PR takes the convertor approach suggested in #220, but @Kludex raised a good point on #427 whether we should aim for more of a FastAPI-level design, which makes sense in terms of abstraction level to me.

Rather than embedding convertor syntax in the URI template ({path:path}, {id:int}), we could specify parameter behavior in the function signature using Annotated:

from typing import Annotated

@mcp.resource("files://{path}")
def get_file(path: Annotated[str, Path(allow_slashes=True)]):
    ...

This would also provide a unified way to handle both path and query parameters:

@mcp.resource("search://{category}")
def search(
    category: str,  # Simple path param, no annotation needed
    format: Annotated[str, Query(default="json")],  # Query param
    limit: Annotated[int, Query(default=10)]
):
    ...

Yes, I've also just noticed this, and this is partly done in #427 ,

try:
result[name] = conv.convert(raw_value)
except Exception as e:
raise ValueError(f"Failed to convert '{raw_value}' for '{name}': {e}")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should This be a ValueError? Since it only happens when the converter has a bad implementation of converting.

Should we go for RuntimeError?

@beaterblank
Copy link
Author

Hi @beaterblank thank you for this contribution - FYI there's another PR #427 that's also enhancing URI handling, though for a slightly different usecase, might be some conflicts.
This PR takes the convertor approach suggested in #220, but @Kludex raised a good point on #427 whether we should aim for more of a FastAPI-level design, which makes sense in terms of abstraction level to me.
Rather than embedding convertor syntax in the URI template ({path:path}, {id:int}), we could specify parameter behavior in the function signature using Annotated:

from typing import Annotated

@mcp.resource("files://{path}")
def get_file(path: Annotated[str, Path(allow_slashes=True)]):
    ...

This would also provide a unified way to handle both path and query parameters:

@mcp.resource("search://{category}")
def search(
    category: str,  # Simple path param, no annotation needed
    format: Annotated[str, Query(default="json")],  # Query param
    limit: Annotated[int, Query(default=10)]
):
    ...

Yes, I've also just noticed this, and this is partly done in #427 ,

Merging #427 into this.

@beaterblank
Copy link
Author

beaterblank commented Oct 17, 2025

Merged #427

Currently working on the Annotated parameter logic implementation, and addressing comments from #427.
Reference: fastapi/param_functions.py

While reviewing the merge, I noticed this line in the MCP SDK:

class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]):

Should we leave this as-is and handle Path and Query separately, or try to integrate context injection here as well?
Integrating it could introduce regressions or break existing behaviour.

@beaterblank
Copy link
Author

weather://{city}/current{?units,format})
Removing the query parameter hints from the UI. Instead, using function annotations to define parameters, skipping known parameters such as Context, and treating any remaining parameters automatically as query parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs maintainer action Potentially serious issue - needs proactive fix and maintainer attention needs more eyes Needs alignment among maintainers whether this is something we want to add

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants