Skip to content

Conversation

@felixweinberger
Copy link
Contributor

Summary

URL-decode parameters extracted from resource template URIs so that percent-encoded characters (like %20 for space, %C3%A9 for é) 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.py with 4 test cases:

  • Space decoding (%20 )
  • Accented characters (%C3%A9é)
  • Complex French phrase from the original issue
  • Verification that + 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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Uses urllib.parse.unquote which handles UTF-8 encoded characters correctly. The + sign is intentionally NOT converted to space because that behavior is specific to application/x-www-form-urlencoded (HTML forms), not URI encoding.

@felixweinberger felixweinberger force-pushed the fweinberger/fix-url-param-decoding branch from 4a9ea40 to 8b53094 Compare January 16, 2026 08:21
@felixweinberger felixweinberger marked this pull request as ready for review January 16, 2026 08:55
@felixweinberger
Copy link
Contributor Author

Clarifying the motivation for future reviewers:

When a client requests a resource template like products://{query} with an accented character, they percent-encode it per RFC 3986:

{"method": "resources/read", "params": {"uri": "products://caf%C3%A9"}}

The bug: FastMCP extracts {query} but doesn't decode it, so the handler receives "caf%C3%A9" instead of "café".

The fix: Call urllib.parse.unquote() on extracted parameters in ResourceTemplate.matches().

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.

@felixweinberger felixweinberger merged commit 0622bab into main Jan 16, 2026
27 checks passed
@felixweinberger felixweinberger deleted the fweinberger/fix-url-param-decoding branch January 16, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URL Parameters Not Decoded in Dynamic Resources

3 participants