Repository navigation
Conversation
`UriTemplate.match` built one mandatory group per query name:
prefix + name + '=([^&]+)'
so a template like `/search{?q,page}` only matched a URI carrying *every*
pair. `expand` is the opposite: it drops a pair whose variable is undefined,
and it emits `name=` for an empty string. `match(expand({ q: 'test' }))`
therefore returned `null` for a URI the class had just produced.
Each query pair is now optional — `(?:\?name=([^&]*))?` — and a group that did
not participate is skipped instead of being read as `undefined`. The full-query
case is unchanged, and `/search` alone now matches the documented expansion of
an empty variable set.
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What breaks
UriTemplatedisagrees with itself for query expressions.expandtreats a query variable as optional — it drops the pair when the variable isundefined:matchdoes not.partToRegExpbuilds one mandatory group per name:so the matcher demands every pair in the template:
A client that registers a resource template with optional query variables (
{?cursor,limit}is the common shape: the server expands it with whatever it needs, and the client then asks whether a resource URI belongs to the template) can never match a partially expanded URI. The second failure mode is the same bug seen from the other side:expand({ q: '' })emits/search?q=, and([^&]+)refuses that too, because it requires at least one character.Fix
Each query pair becomes optional and the value may be empty:
pattern: `(?:${prefix}${this.escapeRegExp(name)}=([^&]*))?`A group that did not participate is skipped rather than turned into
undefined:match(t.expand(vars))can then no longer benullfor reasons the class itself created, and an omitted variable is simply absent from the result ({ q: 'test' }, not{ q: 'test', page: undefined }).Order, literal text and the mandatory path segments are untouched: the groups stay in template order and every other operator keeps its existing pattern.
Tests
Three cases added to
packages/core-internal/test/shared/uriTemplate.test.ts(matching complex patterns):match(expand({ q: 'test' }))→{ q: 'test' }match('/search?q=test')→{ q: 'test' }match('/search?q=test&page=1')→{ q: 'test', page: '1' }(unchanged)Results (vitest, Node v22.22.2, Windows):
2 failed | 39 passed— cases 1 and 2 fail withexpected null to deeply equal { q: 'test' }, the control and all 36 pre-existing cases pass;41 passed(36 pre-existing + 2 new + control).Counter-proof: the new tests were re-run with
uriTemplate.tsrestored byte-for-byte frommainand only the test file kept — the same two cases fail, the control still passes.Evidence boundary
uriTemplate.tsin a standalone vitest workspace (this machine has no pnpm install of the repo), with the repo's own test file; the assertions use only the module's public API.prettier --checkwith the repo config (.prettierrc.json: 4 spaces, single quotes, printWidth 140) passes on both files.tsc --noEmit --strictpasses onuriTemplate.ts;match[i + 1]narrowing tostring | undefinedis what the new guard consumes.{?a}against a bare path now returns{}); that is the documented expansion of an empty variable set, but it is a visible semantic change for such callers and CI will run the package's own suites.