Skip to content

Commit 5052de7

Browse files
committed
fix: reject trailing newline in resource template matching
The same $-with-re.match pattern: a URI with a single trailing newline after a literal template segment matched as if clean. Switch to re.fullmatch. Parameter segments matching raw newlines into values is a separate pre-existing looseness, unchanged here.
1 parent c6b2fc0 commit 5052de7

2 files changed

Lines changed: 16 additions & 1 deletion

File tree

src/mcp/server/fastmcp/resources/templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def matches(self, uri: str) -> dict[str, Any] | None:
8686
"""Check if URI matches template and extract parameters."""
8787
# Convert template to regex pattern
8888
pattern = self.uri_template.replace("{", "(?P<").replace("}", ">[^/]+)")
89-
match = re.match(f"^{pattern}$", uri)
89+
match = re.fullmatch(pattern, uri)
9090
if match:
9191
return match.groupdict()
9292
return None

tests/server/fastmcp/resources/test_resource_template.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ def my_func(key: str, value: int) -> dict[str, Any]: # pragma: no cover
4848
assert template.matches("test://foo") is None
4949
assert template.matches("other://foo/123") is None
5050

51+
def test_template_matches_rejects_trailing_newline_after_literal(self):
52+
"""A trailing newline after a literal segment slipped past `$` with re.match."""
53+
54+
def my_func(key: str) -> str: # pragma: no cover
55+
return key
56+
57+
template = ResourceTemplate.from_function(
58+
fn=my_func,
59+
uri_template="test://{key}/data",
60+
name="test",
61+
)
62+
63+
assert template.matches("test://foo/data") == {"key": "foo"}
64+
assert template.matches("test://foo/data\n") is None
65+
5166
@pytest.mark.anyio
5267
async def test_create_resource(self):
5368
"""Test creating a resource from a template."""

0 commit comments

Comments
 (0)