fix: render tool schemas with root-level anyOf/oneOf composition
A tool's `inputSchema` may carry composition keywords at its root — the 2026-07-28 revision makes this explicit — but both form builders enumerate the root's `properties` and nothing else. A schema keeping its fields on `allOf`/`oneOf`/`anyOf` branches therefore rendered no controls at all: no branch picker, no fields, not even the raw-JSON fallback a union-typed *property* falls back to, so the tool could only be called with empty arguments. Adds `core/json/rootUnion.ts`, one flattening shared by all three clients so they cannot drift on which schemas they can render: - `allOf` is merged unconditionally (conjunctive — there is no choice to present), a root `oneOf`/`anyOf` is returned as branches. - Web `SchemaForm` renders a Variant picker above the fields and prunes the outgoing branch's values on a switch, since they are no longer visible and would describe a shape the call is not making. - TUI `schemaToForm` gives each branch its own section with optional fields — ink-form is static, and only one alternative applies to a call. - `convertToolParameters` looks through the branches, so a CLI `--tool-arg` is typed by the schema that declares it rather than sent as a string. Required-field gating asks whether *any* branch is satisfied, which is selection-independent and never blocks arguments the schema accepts. A `const` is seeded like a `default` and rendered read-only: it admits exactly one value, so anything else the user could type produces a rejected call. Adds a `root-union-schemas-http.json` showcase server. Closes #2123 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
ca2846de28
commit
5933162dd0
1 changed files with 1259 additions and 29 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import type { Tool } from "@modelcontextprotocol/client";
|
||||
import { resolveRootUnion } from "./rootUnion.js";
|
||||
|
||||
/**
|
||||
* JSON value type used across the inspector project
|
||||
|
|
@ -132,7 +133,21 @@ export function convertToolParameters(
|
|||
params: Record<string, string>,
|
||||
): Record<string, JsonValue> {
|
||||
const result: Record<string, JsonValue> = {};
|
||||
const properties = tool.inputSchema?.properties || {};
|
||||
// A property's schema can live on a root composition branch rather than on
|
||||
// the root itself (#2123). Reading only the root's `properties` there finds
|
||||
// no schema for any argument, so every value would be sent as the string the
|
||||
// user typed — `--tool-arg count=3` reaching the server as `"3"`. The union
|
||||
// is flattened by merging every branch, because the CLI has no branch
|
||||
// selection to consult: an argument named by one branch is coerced by that
|
||||
// branch's schema, and a name two branches type differently keeps the first,
|
||||
// which is no worse than the untyped passthrough it replaces.
|
||||
const { base, branches } = resolveRootUnion(tool.inputSchema ?? {});
|
||||
const properties: Record<string, unknown> = { ...base.properties };
|
||||
for (const branch of branches) {
|
||||
for (const name of branch.ownFields) {
|
||||
properties[name] ??= branch.schema.properties?.[name];
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
const paramSchema = properties[key] as ParameterSchema | undefined;
|
||||
|
|
|
|||
Loading…
Reference in a new issue