fix: URL-decode parameters extracted from resource templates#1864
fix: URL-decode parameters extracted from resource templates#1864felixweinberger merged 4 commits intomainfrom
Conversation
4a9ea40 to
8b53094
Compare
|
Clarifying the motivation for future reviewers: When a client requests a resource template like {"method": "resources/read", "params": {"uri": "products://caf%C3%A9"}}The bug: FastMCP extracts The fix: Call This matches Starlette's behavior—handlers receive decoded path parameters. You can verify this yourself: from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
def search(request):
query = request.path_params["query"]
print(f"Handler received: {repr(query)}")
return PlainTextResponse(query)
app = Starlette(routes=[Route("/search/{query}", search)])
client = TestClient(app)
client.get("/search/café")
# Prints: Handler received: 'café'In Starlette, decoding happens at the ASGI layer before the framework sees the request. Since FastMCP doesn't have that layer (URIs arrive as JSON strings), it must decode explicitly. This is a FastMCP-layer fix. Requiring every server implementor to manually decode would defeat FastMCP's purpose as an ergonomic convenience layer. Why we decode only extracted parameters, not the whole URI: # Template: files://{filename}/download
# User wants to download: "my/file.txt" (filename contains a slash)
# Client sends: files://my%2Ffile.txt/download
# Current approach (decode after matching):
# 1. Match against raw URI: files://my%2Ffile.txt/download
# 2. Pattern [^/]+ captures "my%2Ffile.txt" (encoded slash is not a separator)
# 3. "/download" matches ✓
# 4. Decode param: "my%2Ffile.txt" → "my/file.txt"
# 5. Handler receives filename="my/file.txt" ✓
# If we decoded the whole URI first:
# 1. Decode URI: files://my/file.txt/download
# 2. Pattern [^/]+ captures only "my" (slash is now a separator)
# 3. Remaining "/file.txt/download" doesn't match "/download"
# 4. NO MATCH ✗This is the same issue discussed in Starlette #1827 — decoding before routing breaks URIs with encoded path separators. |
Summary
URL-decode parameters extracted from resource template URIs so that percent-encoded characters (like
%20for space,%C3%A9foré) are properly decoded before being passed to resource handlers.Motivation and Context
When a client requests a resource using a URI like
search://hello%20world, the{query}parameter was being passed to the handler as the literal string"hello%20world"instead of"hello world". This broke handlers that expected decoded strings.Fixes #973
How Has This Been Tested?
Added
tests/issues/test_973_url_decoding.pywith 4 test cases:%20→)%C3%A9→é)+remains as+(correct URI behavior vs form encoding)Breaking Changes
None. This is a bug fix that makes resource templates work as users would expect.
Types of changes
Checklist
Additional context
Uses
urllib.parse.unquotewhich handles UTF-8 encoded characters correctly. The+sign is intentionally NOT converted to space because that behavior is specific toapplication/x-www-form-urlencoded(HTML forms), not URI encoding.