fix: address Copilot review round 3 — conjunctive keywords, unflattenable allOf, branch inference

- rootUnion: a branch restating a constraint the root states differently is
  declined (keywords at one level are conjunctive and no intersection is
  computed here), while annotations may disagree freely. An `allOf` with a
  member it cannot fold in — JSON Schema's boolean form, or a $ref whose
  constraints are unknown rather than absent — is left intact instead of
  merged-and-stripped, which would turn an unsatisfiable schema into a
  fillable form.
- rootUnion: `declaresAnyFields` counts the raw composition members, so
  `hasInputFields` reports fields for a union the resolver declines and an
  App tool is never auto-invoked with empty arguments.
- SchemaForm: the picker opens on the branch the supplied values identify
  (an App deep link overlays its args, so they can name another branch), and
  a branch switch no longer carries a value the outgoing branch declared —
  the branches may type the same name differently.
- TUI: a base property some branch redeclares is offered in EVERY branch's
  section under that branch's inherited or specialized declaration, so a
  branch that does not redeclare it can still supply a required root
  argument.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 02:01:44 -04:00
commit 1a271e4931

View file

@ -1,5 +1,5 @@
import type { Tool } from "@modelcontextprotocol/client";
import { resolveRootUnion } from "@inspector/core/json/rootUnion.js";
import { declaresAnyFields } from "@inspector/core/json/rootUnion.js";
/**
* Returns the display label for an MCP entity that follows the BaseMetadata
@ -17,19 +17,16 @@ export function resolveDisplayLabel(name: string, title?: string): string {
* one place so the definition of "has fields" stays consistent if it ever
* grows to consider `additionalProperties` etc.
*
* Root composition is resolved first, since a schema declaring its fields on a
* root `allOf`/`oneOf`/`anyOf` has none of its own (#2123) — an App tool with
* such a schema would otherwise launch with empty arguments rather than asking
* for them.
* Root composition counts, since a schema declaring its fields on a root
* `allOf`/`oneOf`/`anyOf` has none of its own (#2123) — an App tool with such a
* schema would otherwise launch with empty arguments rather than asking for
* them. Counted from the composition members directly rather than from a
* resolved union, so a schema whose composition the form declines to flatten
* still reports the fields it has: it renders fewer controls, not none, and
* auto-invoking it would be wrong either way.
*/
export function hasInputFields(tool: Tool): boolean {
const { base, branches } = resolveRootUnion(tool.inputSchema);
return (
Object.keys(base.properties ?? {}).length > 0 ||
branches.some(
(branch) => Object.keys(branch.schema.properties ?? {}).length > 0,
)
);
return declaresAnyFields(tool.inputSchema);
}
/**