fix(resources): escape literal regex metacharacters in ResourceTemplate.matches#2749
fix(resources): escape literal regex metacharacters in ResourceTemplate.matches#2749vidigoat wants to merge 1 commit into
Conversation
ResourceTemplate.matches() built its regex with a naive string
substitution:
pattern = self.uri_template.replace('{', '(?P<').replace('}', '>[^/]+)')
The literal portions of the template were never re.escape-d, so regex
metacharacters in the template text were interpreted as operators. A
template like 'api://v1.0/{version}' treated '.' as 'any character' and
wrongly matched 'api://v1X0/abc' (false positive routing a URI to the
wrong template), while a template with '+', '(', '[' etc. in a literal
segment failed to match its own valid URIs (false negative).
Tokenize the template into literal/placeholder parts, re.escape the
literals, and turn '{param}' into named capture groups. Adds a
regression test.
Signed-off-by: Vidit Patankar <vidit.patankar16@gmail.com>
StantonMatt
left a comment
There was a problem hiding this comment.
I checked this against the resource-manager routing surface, since the bad . behavior can let an earlier template catch a URI intended for a later one. With api://v1.0/{version} registered before api://v1X0/{version}, ResourceManager.get_resource(api://v1X0/abc) now returns the v1X0 template result (x:abc), while api://v1.0/abc still returns the dotted template result (dot:abc).
Local checks:
uv run --frozen pytest tests/server/mcpserver/resources/test_resource_template.py tests/issues/test_973_url_decoding.py -quv run --frozen ruff check src/mcp/server/mcpserver/resources/templates.py tests/server/mcpserver/resources/test_resource_template.pygit diff --check origin/main...HEAD
|
Gentle nudge on this one whenever a maintainer has a moment. CI is green, and @StantonMatt independently reproduced the template-routing issue and verified the fix against the resource-manager surface. It's a small, self-contained change (escaping literal regex metacharacters in |
Robin1987China
left a comment
There was a problem hiding this comment.
Fix logic is correct: re.findall splits the template into literal/param pairs, re.escape() on literals avoids metacharacter interpretation. The (\w+) for parameter names properly constrains to valid Python identifiers (matching MCPServer's function parameter constraint).
The test covers . and + — good minimal coverage. Suggestion: consider adding a few more boundary cases for completeness, since the fix changes regex behavior for ALL special characters:
*(asterisk) — could match zero-or-more incorrectly?(question mark) — could act as optional quantifier()(parentheses) — could create false capture groups- A full-special-char regression test (e.g.,
data:.+*?^$|()[]{name}) to validate all metacharacters are escaped
These would prevent regressions if the regex construction is refactored later. Happy to share test cases if helpful.
AI assistance disclosure: Reviewed with AI assistance (opencode). I have verified the fix logic against the original bug report (#2961).
Problem
ResourceTemplate.matches()(src/mcp/server/mcpserver/resources/templates.py) builds its matching regex by naive string substitution:The literal portions of the template are never
re.escape-d, so any regex metacharacter in the template text is interpreted as a regex operator. This causes both:api://v1.0/{version}treats.as "any character" and wrongly matchesapi://v1X0/abc, routing a URI to a template it shouldn't match (returning{'version': 'abc'}instead ofNone).+,(,[, etc. in a literal segment (e.g.res://a+b/{x}) fails to match its own valid URI.Fix
Tokenize the template into literal/placeholder parts,
re.escapethe literal text, and turn each{param}into a named capture group:Verification
ruff checkandruff format --checkpass on both changed files. Adds a regression test asserting metacharacters in literal segments are matched literally.