From 4de2469d29f9cbcb9165054ebcf4b04efba8cea8 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:31:10 -0400 Subject: [PATCH] Improve matching logic for quoted chars --- src/fastmcp/resources/template.py | 5 ++++- tests/resources/test_resource_template.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py index 08f39585c..bba1c4467 100644 --- a/src/fastmcp/resources/template.py +++ b/src/fastmcp/resources/template.py @@ -6,6 +6,7 @@ import inspect import re from collections.abc import Callable from typing import Annotated, Any +from urllib.parse import unquote from mcp.types import ResourceTemplate as MCPResourceTemplate from pydantic import ( @@ -38,7 +39,9 @@ def build_regex(template: str) -> re.Pattern: def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None: regex = build_regex(uri_template) match = regex.match(uri) - return match.groupdict() if match else None + if match: + return {k: unquote(v) for k, v in match.groupdict().items()} + return None class MyModel(BaseModel): diff --git a/tests/resources/test_resource_template.py b/tests/resources/test_resource_template.py index 4f6638ed8..68b408aab 100644 --- a/tests/resources/test_resource_template.py +++ b/tests/resources/test_resource_template.py @@ -1,4 +1,5 @@ import json +from urllib.parse import quote import pytest from pydantic import BaseModel @@ -312,6 +313,18 @@ class TestMatchUriTemplate: ("test://foo/123", {"x": "foo", "y": "123"}), ("test://bar/456", {"x": "bar", "y": "456"}), ("test://foo/bar", {"x": "foo", "y": "bar"}), + ("test://foo/bar/baz", None), + ("test://foo/email@domain.com", {"x": "foo", "y": "email@domain.com"}), + ("test://two words/foo", {"x": "two words", "y": "foo"}), + ("test://two.words/foo+bar", {"x": "two.words", "y": "foo+bar"}), + ( + f"test://escaped{quote('/', safe='')}word/bar", + {"x": "escaped/word", "y": "bar"}, + ), + ( + f"test://escaped{quote('{', safe='')}x{quote('}', safe='')}word/bar", + {"x": "escaped{x}word", "y": "bar"}, + ), ("prefix+test://foo/123", None), ("test://foo", None), ("other://foo/123", None),