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
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).
The original fix escaped literal template text before building the matching regex (modelcontextprotocol#2961: "api://v1.0/{v}" wrongly matched "api://v1X0/abc", and "+" in a literal segment broke matching of the template's own URIs). main's RFC 6570 UriTemplate rewrite has since replaced the regex construction and subsumes the code change, so this is now regression tests only: the dot mis-routing case, quantifier/grouping metacharacters (+ * ? ( )), and a template containing every regex metacharacter, as requested in review.
44a4fef to
31eaf72
Compare
|
@StantonMatt Rebased onto latest main, and there's a wrinkle worth flagging for the routing scenario you verified: main's RFC 6570 rewrite (mcp.shared.uri_template) replaced the regex construction in ResourceTemplate.matches entirely, and the new matcher compares literal text literally — so api://v1.0/{version} no longer captures api://v1X0/abc even without my code change. I've reduced this PR to the regression tests (as top-level functions, per current test conventions) so that behavior stays pinned if matching is ever refactored again. |
|
@Robin1987China Added the boundary cases you listed: |
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.