-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[v1.x] fix: Update UriTemplate implementation to handle optional/omitted, out-of-order, and encoded query parameters #1083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mgyarmathy
wants to merge
7
commits into
modelcontextprotocol:v1.x
Choose a base branch
from
mgyarmathy:1079-uritemplate-query-params
base: v1.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+164
−11
Open
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3caa7e4
update UriTemplate implementation to handle optional or out-of-order …
mgyarmathy 0924abb
omit absent query params from match result
mgyarmathy e2a569c
add safe decodeURIComponent helper; add changeset entry
mgyarmathy 749fc4b
handle malformed percent-encoding in UriTemplate.match()
mgyarmathy 98e31bb
fix(core): add boundary assertion to hasLiteralQuery regex and addres…
mgyarmathy 1fbe148
refactor(core): reject literal-? in templates at construction; fix fr…
mgyarmathy 0e89da3
fix: make ?/# pre-processing conditional on template operators; don't…
mgyarmathy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@modelcontextprotocol/core': patch | ||
| --- | ||
|
|
||
| Fix `UriTemplate.match()` to correctly handle optional, out-of-order, and URL-encoded query parameters. Previously, query parameters had to appear in the exact order specified in the template and omitted parameters would cause match failures. Omitted query parameters are now | ||
| absent from the result (rather than set to `''`), so callers can use `vars.param ?? defaultValue`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 The new
match()unconditionally splits the URI at the first?(line 260) to separate path from query, which breaks templates using the{&var}(form-continuation) operator. For example,/search?static=true{&q}fails to match/search?static=true&q=testbecausepathPartbecomes/searchwhile the path regex expects^/search\?static\=true$. This is a regression from previously working behavior where the regex was applied to the full URI.Extended reasoning...
What the bug is
The
match()method introduced a URI-splitting strategy at line 260 that unconditionally splits the input URI at the first?character. The path portion (before?) is matched against a regex built from non-query template parts, while query parameters (after?) are parsed separately with aMap. This breaks any template that contains a literal?character in its string parts, which is exactly the use case for the RFC 6570{&var}(form-continuation) operator.How it manifests
The
{&var}operator exists specifically for templates where a literal?already appears in the template string, and additional query parameters should be appended with&. For template/search?static=true{&q}, the parser produces two parts: the string/search?static=trueand the expression{&q}. The string part (including its literal?) gets escaped into the path regex as^/search\?static\=true$. However, the URI/search?static=true&q=testis split at the first?, yieldingpathPart="/search"andqueryPart="static=true&q=test". The regex^/search\?static\=true$is then matched against just"/search", which obviously fails, and the method returnsnull.Step-by-step proof
/search?static=true{&q}parse()produces:["/search?static=true", {operator: "&", names: ["q"]}]match("/search?static=true&q=test")is calledqueryIndex = uri.indexOf("?")→ index 7 (the literal?after "search")pathPart = "/search",queryPart = "static=true&q=test""/search?static=true"→escapeRegExp→"/search\\?static\\=true"added to path regex{&q}has operator&→ pushed toqueryParts, not included in regex^/search\?static\=true$pathPart.match(regex)→"/search".match(/^\/search\?static\=true$/)→nullnull— match failsThe even simpler case
X{&var}matchingX&var=testalso fails: the URI has no?sopathPart="X&var=test"but the regex is^X$.Impact
This completely breaks
{&var}template matching, which is part of the RFC 6570 specification. The{&var}operator is the canonical way to express "append additional query parameters to a URI that already has some." While{?var}(which introduces the?) is more common in MCP, the{&var}operator is a valid and supported pattern that previously worked correctly.How to fix
The split logic needs to account for literal
?characters in template string parts. One approach: count the number of literal?characters in string parts, and split the URI at the Nth+1?(where N is the count of literal?chars in string parts). Alternatively, detect whether the template contains any{&var}expressions and, if so, include the literal query string in the path regex rather than splitting at the first?.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hit the same issue on #1785 and fixed it in 01bcc47 by checking whether the template itself has a literal ? before deciding where to split.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I copied over your commit in 749fc4b and added an extra spec you introduced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to use
.replace(/\*/g, '')instead ofreplaceAll('*', '')since v1.x targets es2020