From 780425a6e23b57505841fa818f370f38105d56b4 Mon Sep 17 00:00:00 2001 From: lmdnipo <18809296239@163.com> Date: Fri, 18 Sep 2026 13:40:36 +0800 Subject: [PATCH] fix(uri-template): encode literal URI characters --- src/mcp/shared/uri_template.py | 3 ++- tests/shared/test_uri_template.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mcp/shared/uri_template.py b/src/mcp/shared/uri_template.py index 20d6fa9c2e..847b415f9e 100644 --- a/src/mcp/shared/uri_template.py +++ b/src/mcp/shared/uri_template.py @@ -451,7 +451,7 @@ def expand(self, variables: Mapping[str, str | Sequence[str]]) -> str: out: list[str] = [] for part in self._parts: if isinstance(part, str): - out.append(part) + out.append(_encode(part, allow_reserved=True)) else: out.append(_expand_expression(part, variables)) return "".join(out) @@ -892,6 +892,7 @@ def _flatten(parts: list[_Part]) -> list[_Atom]: def push_lit(text: str) -> None: if not text: return + text = _encode(text, allow_reserved=True) if atoms and isinstance(atoms[-1], _Lit): atoms[-1] = _Lit(atoms[-1].text + text) else: diff --git a/tests/shared/test_uri_template.py b/tests/shared/test_uri_template.py index cddbb82b0b..b2036f8652 100644 --- a/tests/shared/test_uri_template.py +++ b/tests/shared/test_uri_template.py @@ -479,6 +479,11 @@ def test_expand_encodes_special_chars_in_simple(): assert t.expand({"v": "a&b=c"}) == "a%26b%3Dc" +def test_expand_encodes_non_ascii_literal(): + t = UriTemplate.parse("file:///docs/café/{name}") + assert t.expand({"name": "a.txt"}) == "file:///docs/caf%C3%A9/a.txt" + + def test_expand_preserves_special_chars_in_reserved(): t = UriTemplate.parse("{+v}") assert t.expand({"v": "a&b=c"}) == "a&b=c" @@ -764,6 +769,11 @@ def test_match_decodes_percent_encoding(): assert t.match("file://docs/hello%20world.txt") == {"name": "hello world.txt"} +def test_match_encodes_non_ascii_literal(): + t = UriTemplate.parse("file:///docs/café/{name}") + assert t.match("file:///docs/caf%C3%A9/a.txt") == {"name": "a.txt"} + + def test_match_escapes_template_literals(): # Regression: previous impl didn't escape . in literals, making it # a regex wildcard. "fileXtxt" should NOT match "file.txt/{id}".