Skip to content

Commit 31eaf72

Browse files
committed
Pin literal regex-metacharacter matching in ResourceTemplate.matches
The original fix escaped literal template text before building the matching regex (#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.
1 parent 9bdc03d commit 31eaf72

1 file changed

Lines changed: 41 additions & 0 deletions

File tree

tests/server/mcpserver/resources/test_resource_template.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,47 @@ def test_matches_escapes_template_literals():
157157
assert t.matches("data://v1X0/42") is None
158158

159159

160+
def test_matches_literal_dot_does_not_route_to_wrong_template():
161+
"""A "." in a template's literal text matches only a literal dot (#2961).
162+
163+
The old regex-based matcher let "api://v1.0/{v}" match "api://v1X0/abc",
164+
so an earlier registered template could capture URIs meant for a later one.
165+
"""
166+
t = _make("api://v1.0/{version}")
167+
assert t.matches("api://v1.0/abc") == {"version": "abc"}
168+
assert t.matches("api://v1X0/abc") is None
169+
170+
171+
def test_matches_literal_regex_metacharacters_carry_no_regex_meaning():
172+
"""Quantifiers and grouping characters in literal text match only themselves (#2961).
173+
174+
Under the old regex-based matcher, "+" / "*" / "?" acted as quantifiers and
175+
"(" ")" created groups, producing false negatives on the template's own URIs.
176+
"""
177+
plus = _make("res://a+b/{x}")
178+
assert plus.matches("res://a+b/v") == {"x": "v"}
179+
assert plus.matches("res://aaab/v") is None # "+" is not one-or-more
180+
181+
star = _make("res://a*b/{x}")
182+
assert star.matches("res://a*b/v") == {"x": "v"}
183+
assert star.matches("res://b/v") is None # "*" is not zero-or-more
184+
185+
optional = _make("res://a?b/{x}")
186+
assert optional.matches("res://a?b/v") == {"x": "v"}
187+
assert optional.matches("res://ab/v") is None # "?" is not optionality
188+
189+
parens = _make("res://a(b)c/{x}")
190+
assert parens.matches("res://a(b)c/v") == {"x": "v"}
191+
assert parens.matches("res://abc/v") is None # "(" ")" is not a group
192+
193+
194+
def test_matches_template_of_every_regex_metacharacter_matches_itself():
195+
"""A literal run of all the regex metacharacters matches only itself (#2961)."""
196+
t = _make("data:.+*?^$|()[]{name}")
197+
assert t.matches("data:.+*?^$|()[]hello") == {"name": "hello"}
198+
assert t.matches("data:Xhello") is None
199+
200+
160201
class TestResourceTemplate:
161202
"""Test ResourceTemplate functionality."""
162203

0 commit comments

Comments
 (0)