fix: validate every reserved _meta member against the SDK's own schema
`progressToken` was not the only reserved key. The pinned SDK's
`RequestMetaSchema` also constrains `io.modelcontextprotocol/related-task` to
`{ taskId: string }`, and a conforming server rejects the *whole request* over
one bad reserved member rather than ignoring that key. Before #1910 every value
was a string, so only `progressToken` could be wrong; the widening made all of
them reachable.
Replace both hand-rolled checks with the SDK's schemas — `RequestMetaSchema`
for the payload, `ProgressTokenSchema` for the single value. The schema is
loose, so unknown keys pass through untouched, and zod reports the offending
top-level key as `issue.path[0]`, so only that member is dropped and its
siblings survive. A member the SDK constrains in a later release is then
covered without anyone remembering to add it — which is the drift that produced
the `isInteger`/`isSafeInteger` bug one round ago.
`name` was hard-coded, and react-ace uses it as the editor container's DOM id,
so two instances emitted duplicate ids. Derived from the per-instance id, and
deliberately distinct from the id given to the textarea — that one is what the
wrapper's `<label for>` targets, so reusing it would just move the collision.
The docs table, the docs prose and the form hint all claimed "any JSON" without
qualification, presenting a configuration as valid that the client then drops.
All three now name the reserved keys and say what happens to a bad one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
2aeec1728e
commit
167b9b4dab
3 changed files with 180 additions and 53 deletions
|
|
@ -158,6 +158,26 @@ describe("JsonObjectInput", () => {
|
|||
return el;
|
||||
}
|
||||
|
||||
it("gives two instances distinct DOM ids", () => {
|
||||
// react-ace uses `name` as the container id; a fixed value would put
|
||||
// duplicate ids in the document as soon as a second editor renders.
|
||||
renderWithMantine(
|
||||
<>
|
||||
<JsonObjectInput ariaLabel="First" value={{}} onChange={() => {}} />
|
||||
<JsonObjectInput ariaLabel="Second" value={{}} onChange={() => {}} />
|
||||
</>,
|
||||
);
|
||||
const containerIds = Array.from(
|
||||
document.querySelectorAll(".ace_editor"),
|
||||
).map((n) => n.id);
|
||||
const inputIds = Array.from(
|
||||
document.querySelectorAll("textarea.ace_text-input"),
|
||||
).map((n) => n.id);
|
||||
expect(containerIds).toHaveLength(2);
|
||||
expect(new Set([...containerIds, ...inputIds]).size).toBe(4);
|
||||
expect(containerIds.concat(inputIds).every((id) => id !== "")).toBe(true);
|
||||
});
|
||||
|
||||
it("lets the visible label focus the editor", () => {
|
||||
renderWithMantine(
|
||||
<JsonObjectInput
|
||||
|
|
|
|||
|
|
@ -187,7 +187,11 @@ export function JsonObjectInput({
|
|||
<AceEditor
|
||||
mode="json"
|
||||
theme={colorScheme === "dark" ? "github_dark" : "github"}
|
||||
name="json-object-input"
|
||||
// react-ace uses `name` as the editor container's DOM id, so a fixed
|
||||
// value would collide the moment two of these render — and it must also
|
||||
// differ from the id given to the textarea below, which is the one the
|
||||
// wrapper's `<label for>` points at.
|
||||
name={`${wrapperId}-editor`}
|
||||
value={draft}
|
||||
onChange={handleChange}
|
||||
width="100%"
|
||||
|
|
|
|||
|
|
@ -732,7 +732,12 @@ export function ServerSettingsForm({
|
|||
Metadata merged into the <Code>_meta</Code> field of every MCP
|
||||
request to this server, as a JSON object. Values may be any JSON —
|
||||
objects, arrays, numbers, booleans, <Code>null</Code> — not just
|
||||
strings. Leave it as <Code>{"{}"}</Code> to send none.
|
||||
strings. Leave it as <Code>{"{}"}</Code> to send none. The keys
|
||||
the protocol reserves keep their own shape:{" "}
|
||||
<Code>progressToken</Code> must be a string or an integer, and{" "}
|
||||
<Code>io.modelcontextprotocol/related-task</Code> an object with a
|
||||
string <Code>taskId</Code>. A reserved key that does not match is
|
||||
dropped from the request rather than sent.
|
||||
</HintText>
|
||||
<JsonObjectInput
|
||||
ariaLabel="Request metadata JSON"
|
||||
|
|
|
|||
Loading…
Reference in a new issue