Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/uri-template-multi-variable-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@modelcontextprotocol/core": patch
---

fix(core): match multi-variable URI templates like `{a,b}` (#2166)
35 changes: 35 additions & 0 deletions packages/core/src/shared/uriTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,41 @@ export class UriTemplate {
return patterns;
}

// Multi-variable expressions like `{a,b}` or `{/a,b}` expand each value
// and join them with commas (see `expandPart`). Mirror that on the way
// back by emitting one capture per name with literal commas between
// them. Without this, only the first name was assigned and the rest of
// the path silently never matched (#2166).
if (part.names.length > 1) {
let firstPrefix: string;
switch (part.operator) {
case '/': {
firstPrefix = '/';
break;
}
case '.': {
firstPrefix = String.raw`\.`;
break;
}
case '#': {
firstPrefix = '#';
break;
}
default: {
firstPrefix = '';
}
}
for (let i = 0; i < part.names.length; i++) {
const name = part.names[i]!;
const prefix = i === 0 ? firstPrefix : ',';
patterns.push({
pattern: prefix + '([^/,]+)',
name
});
}
return patterns;
}

let pattern: string;
const name = part.name;

Expand Down
19 changes: 19 additions & 0 deletions packages/core/test/shared/uriTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,25 @@ describe('UriTemplate', () => {
expect(match).toEqual({ username: 'fred', postId: '123' });
});

it('should match a comma-separated multi-variable expression (#2166)', () => {
const template = new UriTemplate('/users/{userId,format}');
const match = template.match('/users/42,json');
expect(match).toEqual({ userId: '42', format: 'json' });
});

it('should round-trip expand and match for a multi-variable expression (#2166)', () => {
const template = new UriTemplate('data://users/{userId,format}');
const expanded = template.expand({ userId: '42', format: 'json' });
expect(expanded).toBe('data://users/42,json');
expect(template.match(expanded)).toEqual({ userId: '42', format: 'json' });
});

it('should match a multi-variable expression with the path operator (#2166)', () => {
const template = new UriTemplate('{/userId,format}');
const match = template.match('/42,json');
expect(match).toEqual({ userId: '42', format: 'json' });
});

it('should return null for non-matching URIs', () => {
const template = new UriTemplate('/users/{username}');
const match = template.match('/posts/123');
Expand Down
Loading