Skip to content

[P2] Let a named CLI projection accept canonical JSON when its tool schema cannot map to flags; do not make authors weaken the tool contract #746

Description

@ScriptedAlchemy

Problem / source evidence

Audited main 14c9822bc6c01d8ff2788454d0e1dc7861bc3816, source only. CLI projection reuse is delivered (#596/#616/#734); this is the remaining expressiveness boundary, not a request to restore duplicate CLI routes.

A valid MCP tool can use a nested object, union, transform or shared schema outside the bounded source grammar. Adding a colocated .cli.ts merely to give that operation an idiomatic command then requires the tool's canonical schema to satisfy the flag grammar. mapInput does not solve that problem: the projection must compile its argv contract before the mapper can run.

  • routes/cli-argv.ts limits input extraction to scalar/enum/array chains and emits AB4814 with recovery telling the author to restrict the canonical initializer.
  • routes/input-schema.ts follows supported top-level relative aliases; it deliberately is not a general Zod/TypeScript interpreter.
  • routes/cli-projection.ts has a closed projection config containing command/aliases/confirm/description/exitCode/flags/positionals, with no canonical-JSON mode.
  • The existing bulk MCP-command path already supports JSON --input and executes the canonical tool. Reuse that execution/input path rather than implementing another dispatcher.

Desired author experience

A rich tool remains one tool. Its CLI projection should be able to choose a documented canonical-JSON input mode while declaring a friendly command name, or produce an actionable recovery offering that mode when automatic flags are unavailable. The exact option name is a design choice, not an API that exists today.

Example contract to test: z.object({ selection: z.discriminatedUnion(...), filters: z.object(...) }) remains unchanged and is callable as a named command with --input '<JSON object>'. It must not require a second route, a second input schema, a copied domain handler or a custom argv parser.

Minimal implementation

  • Factor the existing canonical JSON input machinery into the normal per-tool projection path.
  • Keep ordinary inferable tools' existing flag/positional behavior unchanged; do not silently change grammar for existing commands.
  • Reject ambiguous combinations of raw JSON mode and per-field flag bindings instead of guessing merge precedence.
  • Retain the canonical tool ID, schema validation, defaults/transforms, provider/state/context mounting, result/error semantics, confirmation policy, cancellation and exit-code behavior.
  • Name unsupported automatic projection precisely. A valid rich tool is not an invalid application merely because it cannot have an automatic scalar form.
  • Do not grow input-schema.ts into a general evaluator, execute arbitrary route modules during metadata discovery, or weaken runtime validation to avoid AB4814.

Acceptance

Test a nested/union schema, a supported relative shared schema, and a runtime transformation through the route harness and an installed source-free generated bin. Compare the final canonical input/result with MCP execution and assert one domain invocation. Malformed JSON, schema failures and denied confirmation fail before side effects; application-owned yes remains distinct from framework confirmation. Help, inspection, type declarations and the authoritative manifest identify the chosen input mode accurately, with required contract-version changes when the closed manifest shape changes.

Document the short decision: automatic flags when supported, canonical JSON when not, an independent CLI route only for a genuinely independent workflow. This is a focused follow-up to #592, not a reopening of its delivered graph/manifest work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions