Skip to content

Route projections: let one operation drive MCP + idiomatic CLI without duplicate route modules #596

Description

@ScriptedAlchemy

Problem

ScriptedAlchemy/cargo-hauler implements the same logical operations twice: once under src/mcp/hauler/tools/* and again under src/cli/*.

Examples:

  • hauler_request.tsx and cli/request.tsx both call submitTicketRequest(...) and render RequestDocument;
  • hauler_status.tsx and cli/status.tsx both call loadStatusResult(...) and render StatusDocument;
  • similar pairs exist for await/result/kill/last/log.

This is not simply application duplication. The current automatic MCP-to-CLI projection is too rigid for a mature CLI:

  • CLI wants short command names (hauler request, not a generic server/tool path);
  • CLI wants positional argv (hauler request -- cargo check ...);
  • flag names may differ from protocol field names (lane -> laneKey, repeated ticket/status flags -> arrays);
  • CLI help/description/exit behavior can differ;
  • the underlying execution/result contract is still the same operation.

The result is one domain operation with two route modules that must remain semantically synchronized.

This conflicts with #592's "one application graph, many projections" model.

Direction

Introduce a first-class operation/route projection model where one canonical executable route can declare surface-specific projection metadata/adapters without duplicating execution logic.

Illustrative only:

export const config = defineTool({
  description: 'Submit a background cargo request',
  cli: {
    command: 'request',
    positionals: ['argv'],
    mapInput(cli) {
      return {
        ...cli,
        cwd: cli.cwd ?? process.cwd(),
        after: parseTicketList(cli.after ?? []),
      };
    },
  },
});

or a colocated projection module:

src/mcp/hauler/tools/request.tsx
src/mcp/hauler/tools/request.cli.ts

The exact authoring API is open. Architectural requirements:

  1. One canonical operation owns execution, result schema, domain errors, and rendered document.
  2. MCP projection owns protocol-facing name/metadata/annotations.
  3. CLI projection may customize command path, positional mapping, flag aliases, help text, defaults, and exit-code policy.
  4. Surface input adapters are typed from the canonical route input contract and compile into Application/Projection IR.
  5. Tests can invoke the canonical operation once and separately test each projection's argument mapping.

Important boundary

Do not force every MCP tool to become a CLI command. Projection remains opt-in.

Do not make CLI-specific transport concerns leak into MCP input schemas merely so automatic projection can work.

This should build on #593: the canonical route contract must be normalized once from shared schemas, then projections derive their own user-facing grammar.

Acceptance

  • A canonical tool/operation can expose an idiomatic routed CLI command without a second execution route module.
  • CLI can rename fields/flags and define positionals while mapping back to the canonical input type.
  • CLI can choose a short command path independent of MCP server/tool wire names.
  • Result schema/rendered document are shared.
  • Mutation/read-only annotations and host semantics remain MCP projection concerns.
  • Projection mapping is represented in Application/Projection IR and visible in Workbench/inspect.
  • cargo-hauler can collapse request/status/await/result/kill/last/log CLI+MCP pairs to one operation implementation each without degrading its CLI UX.

Consumer evidence

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

    enhancementNew feature or requestmeta-frameworkAgent Bundle compiler-coupled meta-framework

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions