modelcontextprotocol / modelcontextprotocol/python-sdk
A resource template with a non-ASCII or space literal is advertised but can never be read
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.3k
- Forks
- 4k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 31
Description
Initial Checks
- I confirm that I'm using the newest release of my line (main @ 6affe5c)
- I confirm that I searched for my issue in the issue tracker before opening this issue
Release line
2.x (current stable)
Description
UriTemplate copies literal runs into the expansion verbatim. RFC 6570 §3.1 requires a literal that the URI grammar does not allow (ucschar such as café, or a space) to be pct-encoded as UTF-8 when the template is expanded, so expand() currently returns a string that is not a valid RFC 3986 URI:
UriTemplate.parse("file:///docs/café/{name}").expand({"name": "a.txt"})
# 'file:///docs/café/a.txt' (uritemplate-test "Literal Encoding" expects caf%C3%A9)
match() is built from the same verbatim literals, so it accepts only that invalid form and rejects the encoded one:
t = UriTemplate.parse("file:///docs/café/{name}")
t.match("file:///docs/caf%C3%A9/a.txt") # None
t.match("file:///docs/café/a.txt") # {'name': 'a.txt'}
That second line is the part that bites in practice. A resource URI crosses the wire as a pydantic AnyUrl, which pct-encodes non-ASCII and spaces (AnyUrl('file:///docs/café/a.txt') → file:///docs/caf%C3%A9/a.txt). So the encoded form is the only one a server ever sees, and the template can never match it: the resource is listed in resources/templates/list and is permanently unreadable. The same applies to a literal space (file:///my docs/{name} → my%20docs).
I hit this while building resource templates whose paths carry Spanish accents, which is ordinary for non-English servers.
Expected: expand() pct-encodes literals per §3.1, and match() accepts the encoded URI a conforming client sends, so an expanded URI round-trips.
Note the direction of the behavior change this implies: after the fix, the raw (unencoded) form stops matching. I believe that is correct, since it is not a valid RFC 3986 URI and it is not what reaches a server over the wire, but it is a visible change for anyone calling UriTemplate.match() directly with a raw IRI, so it seems worth your call rather than mine. Accepting both forms is possible (a fallback scan over the raw atoms) at the cost of a second pass and an ambiguity to document.
I have a patch (the literal encoding applied in both directions, sharing the existing _encode helper) plus unit tests, a routing test, and the official uritemplate-test "Literal Encoding" vector. The full suite passes (5978 passed, 10 skipped, 1 xfailed; baseline 5968), and the new assertions fail on main. Happy to open a PR if you'd like it and can assign this to me.
Example Code
import anyio
from mcp.client.client import Client
from mcp.server.mcpserver import MCPServer
mcp = MCPServer()
@mcp.resource("file:///docs/café/{name}")
def doc(name: str) -> str:
return f"contents of {name}"
async def main() -> None:
async with Client(mcp) as client:
templates = await client.list_resource_templates()
print(templates.resource_templates[0].uri_template)
# file:///docs/café/{name}
# what a conforming client puts on the wire (AnyUrl pct-encodes it)
await client.read_resource("file:///docs/caf%C3%A9/a.txt")
# McpError: Unknown resource: file:///docs/caf%C3%A9/a.txt
anyio.run(main)
Python & MCP Python SDK
Python 3.11.3, mcp-python-sdk main @ 6affe5c0d3588fd1705713b3703dc68015cfe3eb
Disclosure: this was found and written with Claude Code. It ran the official uritemplate-test vectors against mcp.shared.uri_template, reduced the failure to this case, and drafted this report and the patch. I reviewed it before posting and can walk through the change.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with mcp.shared.uri_template and the existing _encode helper, then inspect the unit and routing tests described in the report. Verify the official uritemplate-test Literal Encoding vector and the resource-template round trip; done means expansion and matching agree on percent-encoded literals and the full suite passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100