fix(web): expand resource templates per RFC 6570
The Resources screen discovered and substituted template variables with a
bare `/\{(\w+)\}/g` regex, which is wrong two ways: it cannot see an
expression carrying an operator, so `foobar://events{?topic}` rendered no
input at all; and it splices the raw value in, so a `/`, `?`, `#`, `%`,
space or non-ASCII character in a simple `{topic}` landed unencoded and
changed the URI's structure -- `foo/bar` produced an extra path segment
that a conforming matcher rejects with `-32602 Resource not found`.
Delegate expansion to the SDK's `UriTemplate`, the same implementation
`InspectorClient.readResourceFromTemplate` (and so the TUI) already
expands through, so the two clients cannot disagree about what a template
means. The new `utils/uriTemplate` supplies only what that class does
not: which variables to render an input for, which of them a read cannot
proceed without, and a partially-expanded preview.
Required-ness follows the operator. Under `?`, `&`, `.` or `/` the whole
expression is omitted when the variable is undefined, so those fields are
marked Optional and reading with them blank is a legitimate request for
the unfiltered resource; under `""`, `+` or `#` the variable sits mid-URI,
so it stays required. Blank fields are dropped before expanding so an
untouched optional field reads as undefined rather than as the empty
string, which would expand to a valueless `?topic=`.
Adds the `rfc6570_templates` preset and a `rfc6570-templates-http.json`
showcase server serving the two templates from the issue.
Closes #1919
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
8dd0fdf28b
commit
83658db2dd
2 changed files with 578 additions and 34 deletions
|
|
@ -62,6 +62,7 @@ import {
|
|||
createFileResourceTemplate,
|
||||
createUserResourceTemplate,
|
||||
createNumberedResourceTemplates,
|
||||
createRfc6570ResourceTemplates,
|
||||
createSimplePrompt,
|
||||
createArgsPrompt,
|
||||
createNumberedPrompts,
|
||||
|
|
@ -260,6 +261,8 @@ function resolveResourceTemplatePreset(
|
|||
return createUserResourceTemplate();
|
||||
case "numbered_resource_templates":
|
||||
return createNumberedResourceTemplates(Number(get("count")) || 3);
|
||||
case "rfc6570_templates":
|
||||
return createRfc6570ResourceTemplates();
|
||||
default:
|
||||
throw new Error(`Unknown resource template preset: ${name}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1474,6 +1474,53 @@ export function createFileResourceTemplate(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource templates that exercise RFC 6570 expansion (#1919).
|
||||
*
|
||||
* `events_by_topic` is the simple `{topic}` expression from the issue: a `/`,
|
||||
* `?`, `#` or space in the value MUST be percent-encoded, or the URI gains a
|
||||
* path segment and a conforming matcher rejects it. `events_by_query` is the
|
||||
* `{?topic}` form, which the web client could not even render an input for.
|
||||
*
|
||||
* Both handlers echo the URI they were matched against plus the variables the
|
||||
* server decoded, so the round-trip is visible in the read result.
|
||||
*/
|
||||
export function createRfc6570ResourceTemplates(): ResourceTemplateDefinition[] {
|
||||
const echo =
|
||||
(label: string) => async (uri: URL, params: Record<string, unknown>) => ({
|
||||
contents: [
|
||||
{
|
||||
uri: uri.toString(),
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{ template: label, matchedUri: uri.toString(), variables: params },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
name: "events_by_topic",
|
||||
uriTemplate: "foobar://events/{topic}",
|
||||
description:
|
||||
"Simple expression - a reserved character in `topic` must be percent-encoded",
|
||||
inputSchema: { topic: z.string().describe("Topic name") },
|
||||
handler: echo("foobar://events/{topic}"),
|
||||
},
|
||||
{
|
||||
name: "events_by_query",
|
||||
uriTemplate: "foobar://events{?topic}",
|
||||
description:
|
||||
"Query expression - optional, and omitted entirely when `topic` is blank",
|
||||
inputSchema: { topic: z.string().describe("Topic name") },
|
||||
handler: echo("foobar://events{?topic}"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "user" resource template that returns user data by ID
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue