Skip to content

UriTemplate.match() cannot read back prefix{.ext} when ext holds a dot, though nothing else can claim it #3693

Open

Description

@chrikrah

Initial Checks

Release line

2.x (current stable)

Description

A resource registered as backup://nightly{.ext} cannot be read when the extension holds a dot. UriTemplate.expand({"ext": "tar.gz"}) gives backup://nightly.tar.gz, because . is unreserved. match() on that same string returns None, and resources/read answers "Unknown resource" for a URI the template itself produced.

The match() docstring lists this exact case as an inherent RFC 6570 §1.4 limitation. I think that overstates it. The ambiguity needs a second capture that could claim the dot, as in {name}{.ext}. When {.ext} is the first capture after a literal, the anchored branch of _scan_suffix knows where the value starts. Nothing else can take that dot. A client that reads the template from resources/templates/list also cannot know it must send %2E.

The fix bounds a non-explode {.var} in that branch by /, ? and # alone. The docstring sentence would then cover only the case with a capture on the left, which stays unmatched. With the change, tests/shared/test_uri_template.py plus tests/server/mcpserver go from 949 passed on c9356a2 to 950. The new case is ("file{.ext}", {"ext": "tar.gz"}) in test_roundtrip_expand_then_match, and it fails without the change.

Example Code

import anyio
from mcp import Client
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("demo")

@mcp.resource("backup://nightly{.ext}")
def nightly(ext: str) -> str:
    return f"archive in {ext}"

async def main() -> None:
    template = mcp._resource_manager.list_templates()[0].parsed_template
    uri = template.expand({"ext": "tar.gz"})
    print("expand:", uri)
    async with Client(mcp) as client:
        for u in ("backup://nightly.zip", uri):
            try:
                r = await client.read_resource(u)
                print(u, "->", r.contents[0].text)
            except Exception as e:
                print(u, "->", type(e).__name__, e)

anyio.run(main)
$ .venv/bin/python -I repro_dot.py
expand: backup://nightly.tar.gz
backup://nightly.zip -> archive in zip
backup://nightly.tar.gz -> MCPError Unknown resource: backup://nightly.tar.gz
# not run: the full test suite, only the two test paths named above

Python & MCP Python SDK

Python 3.14.7
mcp at main c9356a2; uri_template.py is unchanged since v2.3.0

@maxisbey, this is the template module from #2356. As the reporter I would like to fix it, and the change is ready. Would you narrow the limitation to the anchored case?

Activity

  1. added
    bugSomething isn't working
    v2Affects the v2 line (2.x on main)
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions