Repository navigation
fix: parse {;var} before a literal starting with '=' - #3860
media-ctrl wants to merge 1 commit into
Conversation
_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
|
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:
Maintainers: reopen, remove |
What this fixes
Fixes #3852.
_scan_prefixinsrc/mcp/shared/uri_template.pytook 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}witha="end", rest="/x":expand();a=end=end/xmatch()before{'a': '', 'rest': '=end/x'}match()after{'a': 'end', 'rest': '/x'}So
match(expand(vars)) != varsfor that case. The narrow failing set matches what was measured in the issue thread:a=""anda="x"already round-trip, and so does/api{;mode}=fast{/id}.The fix
In the
ifempbranch, whenuri[pos] == "=", try the;name=valuereading 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=endfrom;a=end=end/x, because=does not end a;value, and the following=endliteral then fails and falls back to the reading that is wrong today. Finding the next literal instead givesend, which round-trips.Ambiguity, stated rather than hidden
;a=end=end/xis 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=valuefor{;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 passeduv run pytest tests/shared/ tests/issues/ tests/docs_src/test_uri_templates.py-> 1047 passed, 1 skipped77001c9and pass with this patch, so they are regression tests and not just descriptionsuv run ruff checkanduv run ruff format --checkclean on both changed filesFull test suite was not run, only the suites that touch
uri_templateplus the shared and issues trees.