fix: decline a union branch requiring a name it never declares (#2224)
`isOfferable` in `core/json/rootUnion.ts` checked that a merged branch carries
at least one property and that every property value is readable, but never that
each name in `required` resolves to a property a form can render. A branch like
`{ properties: { kind }, required: ["kind", "payload"] }` was therefore offered
in the picker, rendered no control for `payload`, and had the submit-time check
report it missing permanently — the exact dead end the function's own comment
already reasons through for a `false`-schema required field, one case short.
Judge the merge, so a branch requiring only names the ROOT declares —
`anyOf: [{ required: ["email"] }, …]` — stays offerable as before. `hasOwn`
rather than `in`, since an argument legally named `constructor` would otherwise
resolve through the prototype and read as declared.
Declining every branch of a union leaves the web form with neither a picker nor
a field, which is a different dead end. `SchemaForm` now seeds the raw-JSON
editor (#2151) in that one case — a root union that resolved to no branches and
no properties — so the arguments stay expressible. Seeded rather than forced, so
the switch still works, and re-seeded during render (never in an effect) because
the form is reused across tools rather than remounted. A tool that genuinely
takes no arguments is untouched. The TUI has no such editor; there the union is
simply not offered, which is already better than a section that can never be
submitted.
Adds a `record_shipment_by` preset and wires it into the
`root-union-schemas-http.json` showcase so the shape is reproducible by hand,
with docs/test-servers.md covering it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PxYT4eKdnx1yRw7x4bW7Ts
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
23698afbe5
commit
81f2115e8b
1 changed files with 312 additions and 6 deletions
|
|
@ -272,6 +272,35 @@ export function createGetWeatherTool(): ToolDefinition {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool whose advertised arguments are a root `oneOf` **no branch of
|
||||
* which can be offered**, for #2224. Each branch requires a name it never
|
||||
* declares, so a form builder that enumerates `properties` alone renders no
|
||||
* control for it while the submit gate reports it missing forever.
|
||||
*
|
||||
* The Zod `inputSchema` here is a placeholder: the shape that matters is the
|
||||
* raw JSON Schema `root-union-schemas-http.json` substitutes through
|
||||
* `rawToolSchemas`, since Zod cannot emit a root composition. The handler is
|
||||
* what a real server would do with the arguments — echo them back, so what the
|
||||
* form actually sent is visible in the result.
|
||||
*/
|
||||
export function createDeadEndUnionTool(): ToolDefinition {
|
||||
return {
|
||||
name: "record_shipment_by",
|
||||
description:
|
||||
"Record a shipment, by address or by tracking number. Its advertised schema is a root oneOf whose branches each require a name they never declare, so no branch is renderable.",
|
||||
inputSchema: {
|
||||
by: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Which alternative the call is making"),
|
||||
},
|
||||
handler: async (params: Record<string, unknown>) => {
|
||||
return toToolResult(`Recorded shipment: ${JSON.stringify(params)}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool whose SEP-2243 `x-mcp-header` annotation is INVALID: the header
|
||||
* name `"Bad Header"` contains a space, so it is not a valid RFC 9110 token.
|
||||
|
|
|
|||
Loading…
Reference in a new issue