Skip to content

UriTemplate treats RFC 6570 prefix modifiers as variable-name text and drops scalar values #3000

Description

@jayfeng7

Summary

The SDK's UriTemplate silently treats an RFC 6570 prefix modifier as part of the variable name. Supplying the actual variable therefore removes its value from the expanded resource URI.

For example, resource:///{name:3} with name = "abcdef" expands to resource:///, and variableNames reports ["name:3"] rather than ["name"].

Reproduction

Environment: Windows, Node.js 24.20.0, @modelcontextprotocol/client@2.3.1.

Install in an empty directory:

npm install @modelcontextprotocol/client@2.3.1
node repro.mjs

repro.mjs:

import { UriTemplate } from '@modelcontextprotocol/client';

for (const [template, variables] of [
  ['{name:3}', { name: 'abcdef' }],
  ['resource:///{name:3}', { name: 'abcdef' }],
  ['{name:2}', { name: '你好世界' }],
  ['{name}', { name: 'abcdef' }],
]) {
  const uri = new UriTemplate(template);
  console.log(JSON.stringify({
    template, variableNames: uri.variableNames,
    actual: uri.expand(variables),
  }));
}

Observed:

{"template":"{name:3}","variableNames":["name:3"],"actual":""}
{"template":"resource:///{name:3}","variableNames":["name:3"],"actual":"resource:///"}
{"template":"{name:2}","variableNames":["name:2"],"actual":""}
{"template":"{name}","variableNames":["name"],"actual":"abcdef"}

I independently ran the same cases against the unmodified packages/core-internal/src/shared/uriTemplate.ts from main b022522089a0c8b632595c6e7b536453945ed5a9 with the same results. The reproduction exercises the real implementation, requires no service credentials, and makes no network requests after installation.

Expected behavior / scope question

RFC 6570 §2.4.1 defines :N as a modifier on a variable, rather than part of its name. If this syntax is supported, the corresponding scalar expansions should be abc, resource:///abc, and %E4%BD%A0%E5%A5%BD. The ordinary {name} control already works.

If prefix modifiers are intentionally outside the SDK's supported subset, explicit validation or documented rejection would be preferable to silently producing a different URI. I would appreciate maintainer guidance on the intended support level.

Cause and proposed scope

getNames() removes * but retains :N; expansion then looks up variables["name:3"].

I'd like to implement a focused fix and submit a PR if this direction is welcome. For support, I propose parsing a scalar's name and prefix length separately, applying the prefix to Unicode characters before percent-encoding, and adding regressions in packages/core-internal/test/shared/uriTemplate.test.ts. Coverage would include ASCII, non-ASCII and supplementary characters, a prefix longer than the value, missing variables, and the existing no-modifier control. Validating malformed modifiers and defining matching behavior should be agreed before implementation: a truncated URI cannot reconstruct the original full value.

This is separate from the existing explode-modifier work (#2892), multi-variable expansion work (#2994), and percent-decoding/matching work (#2810/#1079). I checked the full open-PR title list and issue searches for prefix modifiers before drafting this report.

Could a maintainer confirm the intended scope and assign this issue to jayfeng7 if appropriate? I will follow that direction and provide focused regression evidence before submitting a PR.

Prepared with Codex assistance for analysis and reproduction. No implementation PR or full SDK test-suite result is claimed here.

Activity

  1. added
    v1Issues / PRs related to v1.x
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Oct 11, 2026
  2. 0xamlab commented on Oct 11, 2026

    @0xamlab

    Confirmed on main: a targeted vitest run for {name:3} and {name:2} fails both tests (expected ['name:3'] to deeply equal ['name'], and expansion returns '' instead of the prefixed value). The root cause is packages/core-internal/src/shared/uriTemplate.ts:100, where getNames() strips * but leaves :N attached to the name, so parse() stores "name:3" at line 68 and expandPart() looks up variables["name:3"] at line 144. The fix needs to split the prefix length from the variable name there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions