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:
- One canonical operation owns execution, result schema, domain errors, and rendered document.
- MCP projection owns protocol-facing name/metadata/annotations.
- CLI projection may customize command path, positional mapping, flag aliases, help text, defaults, and exit-code policy.
- Surface input adapters are typed from the canonical route input contract and compile into Application/Projection IR.
- 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
Consumer evidence
Problem
ScriptedAlchemy/cargo-haulerimplements the same logical operations twice: once undersrc/mcp/hauler/tools/*and again undersrc/cli/*.Examples:
hauler_request.tsxandcli/request.tsxboth callsubmitTicketRequest(...)and renderRequestDocument;hauler_status.tsxandcli/status.tsxboth callloadStatusResult(...)and renderStatusDocument;This is not simply application duplication. The current automatic MCP-to-CLI projection is too rigid for a mature CLI:
hauler request, not a generic server/tool path);hauler request -- cargo check ...);lane->laneKey, repeated ticket/status flags -> arrays);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:
or a colocated projection module:
The exact authoring API is open. Architectural requirements:
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
Consumer evidence
ScriptedAlchemy/cargo-hauler/src/cli/request.tsxScriptedAlchemy/cargo-hauler/src/mcp/hauler/tools/hauler_request.tsxScriptedAlchemy/cargo-hauler/src/cli/status.tsxScriptedAlchemy/cargo-hauler/src/mcp/hauler/tools/hauler_status.tsx