Skip to content

fix: parse {;var} before a literal starting with '=' - #3860

Closed
media-ctrl wants to merge 1 commit into
modelcontextprotocol:mainfrom
media-ctrl:fix/uri-template-ifemp-literal-prefix
Closed

media-ctrl wants to merge 1 commit into
modelcontextprotocol:mainfrom
media-ctrl:fix/uri-template-ifemp-literal-prefix

Conversation

@media-ctrl

Copy link
Copy Markdown

What this fixes

Fixes #3852.

_scan_prefix in src/mcp/shared/uri_template.py took the empty-value reading whenever the URI at the name's position began with the following literal, and it did that before checking for the = separator. When the literal itself starts with =, that test fires on the separator, so the value is read as empty and the literal text is pushed into the greedy variable.

Measured on 77001c9, for template {;a}=end{+rest} with a="end", rest="/x":

stage value
expand() ;a=end=end/x
match() before {'a': '', 'rest': '=end/x'}
match() after {'a': 'end', 'rest': '/x'}

So match(expand(vars)) != vars for that case. The narrow failing set matches what was measured in the issue thread: a="" and a="x" already round-trip, and so does /api{;mode}=fast{/id}.

The fix

In the ifemp branch, when uri[pos] == "=", try the ;name=value reading first, bounded to the next literal, then fall back to the empty reading.

The bounded span is the part that matters. Taking the stop-char span for the value captures end=end from ;a=end=end/x, because = does not end a ; value, and the following =end literal then fails and falls back to the reading that is wrong today. Finding the next literal instead gives end, which round-trips.

Ambiguity, stated rather than hidden

;a=end=end/x is genuinely ambiguous for {;a}=end{+rest}: both {a: "end", rest: "/x"} and {a: "", rest: "=end/x"} expand to it. The value reading wins here, for consistency with the suffix scan, which already prefers ;name=value for {;a}=end{/rest}. A test names that trade explicitly so a later change cannot flip it silently.

Verification

  • uv run pytest tests/shared/test_uri_template.py -> 317 passed
  • uv run pytest tests/shared/ tests/issues/ tests/docs_src/test_uri_templates.py -> 1047 passed, 1 skipped
  • The three added tests fail on unpatched 77001c9 and pass with this patch, so they are regression tests and not just descriptions
  • uv run ruff check and uv run ruff format --check clean on both changed files

Full test suite was not run, only the suites that touch uri_template plus the shared and issues trees.

_scan_prefix took the empty-value reading whenever the URI at the
name's position began with the following literal, so a {;var}
expression followed by a literal beginning with '=' parsed the
value as empty and pushed the literal text into the greedy var.

For template {;a}=end{+rest} with a='end', expand() emits
';a=end=end/x' and match() returned {'a': '', 'rest': '=end/x'},
so match(expand(vars)) != vars.

Try the ;name=value reading first, bounded to the next literal,
and fall back to the empty reading. The bounded span matters: the
stop-char span would take 'end=end' because '=' does not end a ';'
value, and would then fail the following literal.

Fixes modelcontextprotocol#3852
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3852.

If a maintainer assigns you to #3852, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take.

You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way.

CONTRIBUTING.md has the full reasoning, but in short:

  • We're a small team with very little capacity to review community PRs right now.
  • Many recent PRs are AI-generated with little human review, and reviewing one carefully still costs a maintainer as much time as it ever did. A well-described issue is usually more useful to us than the code.

Maintainers: reopen, remove missing-issue-link, or add bypass-issue-check to override.

@github-actions github-actions Bot closed this Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UriTemplate.match() misparses {;var} when following literal begins with '='

1 participant