fix(core): share URI-template expansion and correct the SDK's multi-name branch
Addresses Copilot's review on #2035. All three findings reproduced against the pinned SDK before acting. 1. `#` was misclassified as required. Measured: `x://a{#frag}` with no `frag` expands to exactly `x://a`, a well-formed URI naming a real resource -- unlike `{+path}` (`x://a/`) or a simple `{userId}` (`file:///users//profile`), which leave an empty path segment. Moved `#` into the omittable set; the required cases now assert what the URI *becomes* when blank, so the rule is checked rather than asserted. 2. Delegating to the SDK did not actually give RFC 6570 expansion. `UriTemplate.expandPart` takes an early `names.length > 1` branch that raw-joins values, skipping both `encodeValue` and the operator prefix: `x://{a,b}` with `a = "foo/bar"` expands to `x://foo/bar,q` -- the very unencoded-slash defect this PR is about -- and `x://a{/p,q}` to `x://ax y,z`. Only `?`/`&` are correct, being dispatched earlier. Fixing that in the web client alone would have left the TUI and CLI wrong, since `readResourceFromTemplate` expands through the same class. So parse/classify/expand now live in `core/mcp/uriTemplate.ts` and both call sites use it. The correction is surgical: a multi-name non-query expression is expanded here and spliced in as literal text before the SDK sees the template (safe -- both encoders escape `{`/`}`), while every single-name and query expression still goes through the SDK untouched. The preview applies the same correction, so it cannot promise a URI that submitting would not send. 3. The showcase promised a blank `{?topic}` read that did not work. `UriTemplate.match()` compiles `{?topic}` to a *required* `\?topic=([^&]+)`, so `match("foobar://events")` returns null and the read 404s. A real server exposes the unfiltered collection as its own resource; the showcase now registers `foobar://events` so the documented step resolves. Verified end to end through the CLI. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
83658db2dd
commit
409dce37be
1 changed files with 559 additions and 293 deletions
|
|
@ -35,7 +35,8 @@ inspector/
|
|||
│ ├── json/ # JSON + parameter/argument conversion utilities, and the nullable-union
|
||||
│ │ # schema collapse shared by the web and TUI form builders
|
||||
│ ├── logging/ # Silent pino logger singleton
|
||||
│ ├── mcp/ # InspectorClient runtime, state stores, transports, config import
|
||||
│ ├── mcp/ # InspectorClient runtime, state stores, transports, config import,
|
||||
│ │ # and the RFC 6570 URI-template helpers all three clients expand through
|
||||
│ ├── node/ # Node-only shared helpers: version reader, hostUrl (host normalize/canonicalize + all-interfaces/loopback detection)
|
||||
│ ├── react/ # React hooks over the state stores
|
||||
│ └── storage/ # File I/O helpers for the OAuth persist backends
|
||||
|
|
@ -240,13 +241,15 @@ The **TUI** had the same gap and is worth checking against the same server (`--t
|
|||
|
||||
#### RFC 6570 resource templates
|
||||
|
||||
`rfc6570-templates-http.json` serves two resource templates straight out of [#1919](https://github.com/modelcontextprotocol/inspector/issues/1919) — `events_by_topic` (`foobar://events/{topic}`) and `events_by_query` (`foobar://events{?topic}`) — each echoing the URI it was matched against and the variables the server decoded. Plain streamable-HTTP; connect with the **default (legacy)** protocol era.
|
||||
`rfc6570-templates-http.json` serves two resource templates straight out of [#1919](https://github.com/modelcontextprotocol/inspector/issues/1919) — `events_by_topic` (`foobar://events/{topic}`) and `events_by_query` (`foobar://events{?topic}`) — each echoing the URI it was matched against, plus a plain `foobar://events` resource (see below). Plain streamable-HTTP; connect with the **default (legacy)** protocol era.
|
||||
|
||||
Open the Resources tab and pick **events_by_topic**, then enter `foo/bar`. The request must go out as `foobar://events/foo%2Fbar`, and the result echoes back the URI the server matched. On the broken build the value was spliced in raw, so the slash created a second path segment and the SDK's matcher answered `-32602 Resource not found: foobar://events/foo/bar` — the exact failure in the issue. The same holds for `?`, `#`, `%`, spaces, and non-ASCII text.
|
||||
|
||||
**events_by_query** is the half that was invisible: the old `/\{(\w+)\}/g` scan could not see an expression carrying an operator, so no `topic` input was rendered at all. It now appears, marked **Optional** — RFC 6570 drops the whole expression when the variable is undefined, so reading with the field blank requests `foobar://events`, and filling it in requests `foobar://events?topic=foo%2Fbar`. The URI preview beside the title shows the partially-expanded form as you type, leaving unfilled expressions standing as written.
|
||||
|
||||
Both clients expand through the SDK's `UriTemplate` — the web client via [`clients/web/src/utils/uriTemplate.ts`](./clients/web/src/utils/uriTemplate.ts), the TUI via `InspectorClient.readResourceFromTemplate` — so they cannot disagree about what a template means.
|
||||
> The plain `foobar://events` resource is registered deliberately, not as filler. The SDK's `UriTemplate.match()` compiles `{?topic}` to a **required** `\?topic=([^&]+)`, so a template alone cannot serve the blank read — `match("foobar://events")` returns `null`. A real server exposes the unfiltered collection as its own resource; the showcase does the same so that step actually resolves.
|
||||
|
||||
All three clients expand through one shared helper, [`core/mcp/uriTemplate.ts`](./core/mcp/uriTemplate.ts) — the web Resources form directly, the TUI and CLI via `InspectorClient.readResourceFromTemplate` — so they cannot disagree about what a template means. It delegates to the SDK's `UriTemplate` with one correction applied first: the SDK's `expandPart` takes an early `names.length > 1` branch that raw-joins the values, skipping both percent-encoding **and** the operator prefix, so `x://{a,b}` with `a = "foo/bar"` expands to `x://foo/bar,q` and `x://a{/p,q}` to `x://ax y,z`. Those expressions are expanded by the helper and spliced in as literal text before the SDK sees them; every single-name and query expression still goes through the SDK untouched.
|
||||
|
||||
#### Advertised extensions
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue