Repository navigation
Generate CLI, MCPorter, and script projections from the compiled route graph #102
Description
Summary
Make CLI commands and executable scripts first-class outputs of the Agent Bundle
compiler. Projects may author a custom CLI tree, generate CLI commands from MCP
routes through MCPorter, ship ordinary scripts, and ship React-rendered scripts.
All modes use the existing Rslib build path and the same compiled route graph as
MCP and Workbench.
Generated CLI commands and rendered scripts enter through the same typed Agent
request context as MCP and event routes. Plain scripts remain ordinary Node
programs unless their extension and route contract explicitly opt into Agent
Document rendering.
Existing capabilities to preserve
src/cli.tsis already a conventional package bin entry.src/index.tsis already a conventional library entry.- configured scripts already compile into host artifacts.
- generated executable shells already support shebangs and self-contained
Rslib output. - framework builds already validate and track source provenance.
This issue removes remaining manual dispatch and adds compiler-generated
projections; it does not create a second packaging system.
Goals
- Support a custom filesystem-routed CLI root.
- Optionally expose compiled MCP tools as CLI commands through MCPorter.
- Merge custom and generated commands into one coherent product CLI.
- Support plain JS/TS scripts without forcing them into the React renderer.
- Support JSX/TSX scripts whose output uses the Agent Document renderer.
- Provide deterministic streaming and machine-readable output modes.
- Detect command collisions and unsupported projections during compilation.
Proposed configuration
export default defineConfig({
cli: {
mcpPorter: true,
},
});The exact spelling can change during API review. The semantics are:
src/cli/*is discovered automatically when present;cli.mcpPorterprojects eligible local MCP tools into the same CLI tree;src/scripts/*is discovered automatically when present;- custom roots are optional overrides, not required setup;
- explicit command/script declarations override conventional paths;
- compiler diagnostics report uncovered, shadowed, or conflicting files.
Custom CLI routes
src/cli/
doctor.tsx
cache/
clear.ts
config/
show.tsx
Each route exports its parser/schema and default handler or component:
export const config = {
description: 'Inspect the generated Agent Bundle runtime.',
aliases: ['health'],
} satisfies CliRouteConfig;
export const input = z.object({
verbose: z.boolean().default(false),
});
export default async function Doctor({ input }: CliRouteProps<typeof input>) {
const health = await inspectHealth(input);
return <Agent.Json value={health} />;
}The compiler generates:
- command names and nesting from paths;
- help and typed argument parsing;
- lazy command imports where appropriate;
- final exit-code and error mapping;
- one Rslib-built executable;
- the route types used by tests and Workbench.
The command path supplies identity. An optional named config export supplies
descriptions, aliases, visibility, and safety metadata. There is no per-command
sidecar config file or duplicate command declaration in
agent-bundle.config.ts; the root config only supplies project-wide CLI roots,
defaults, policies, and explicit overrides.
MCPorter projection
MCPorter can generate a standalone CLI
from an MCP server. Agent Bundle should integrate that capability at compile
time from its already-validated MCP route manifest rather than rediscovering a
running server during every build.
Recommended behavior when mcpPorter: true:
- Select local generated MCP servers and tools eligible for CLI exposure.
- Generate MCP-backed command definitions from the compiled tool schemas and
listing metadata. - Merge those definitions with custom CLI routes.
- Fail the build when custom and generated commands claim the same command
path unless config explicitly resolves the conflict. - Preserve one executable, one help tree, and one version/identity surface.
- Record the MCP route that generated each command in artifact provenance.
Configuration may include bounded inclusion/exclusion:
cli: {
mcpPorter: {
include: ['curator:*'],
exclude: ['curator:apply_*'],
},
},Mutation-capable MCP tools must retain their safety semantics in CLI help and
must not become less guarded merely because they are callable from a shell.
The generated CLI preserves the route's authorization and confirmation policy;
when the original surface requires interactive consent and no equivalent CLI
confirmation/authorization channel exists, invocation fails closed.
Script conventions
src/scripts/
rebuild-index.ts
summarize-library.tsx
export-report.js
render-release-notes.jsx
Behavior by extension:
.js,.mjs,.ts,.mts: ordinary executable entry. The framework
supplies compilation, packaging, cancellation, and provenance but does not
reinterpret its stdout..jsx,.tsx: rendered script entry. The default export is executed through
the Agent Document renderer and supports Markdown, JSON, progress, resources,
Suspense, and final output modes.
Rendered script example:
export const config = {
description: 'Summarize the configured audiobook library.',
} satisfies RenderedScriptConfig;
export default async function SummarizeLibrary({ argv, signal }: ScriptProps) {
const summary = await summarize(argv, { signal });
return (
<Agent.Result value={summary}>
<Agent.Markdown>
Found **{summary.books}** books across **{summary.roots}** roots.
</Agent.Markdown>
</Agent.Result>
);
}Ordinary scripts remain useful for maintenance helpers that do not need
agent-facing rendering. The file extension is an explicit, visible contract;
the framework must not silently wrap a plain script in React behavior.
Output contract
Every rendered CLI command or TSX/JSX script supports:
textormarkdown: human/agent-readable final output;json: one canonical final JSON document;ndjson: sequence-numbered render events;- interactive TTY progress;
- quiet/final-only operation.
Plain scripts retain normal Node stdout/stderr semantics.
NDJSON render events are an Agent Bundle CLI/script output dialect, not MCP
JSON-RPC or partial tool-result streaming. They must never be written as
non-MCP bytes to an MCP server's stdout.
Rules:
- rendered machine output uses stdout;
- logs and diagnostics use stderr;
- partial fallbacks are never left in final piped Markdown;
SIGINT/SIGTERMreach the frameworkAbortSignal;- exit codes are deterministic and declared per route or derived from the final
Agent Result status; - secrets and credentials are never synthesized into command arguments or
artifact manifests.
Generated build model
The meta-framework compiler emits:
- a custom CLI route manifest;
- an MCPorter-generated command manifest;
- a collision-resolved combined command graph;
- ordinary script Rslib entries;
- rendered-script entries bound to the Agent renderer;
- generated help, types, and test fixtures;
- host artifact script declarations.
The generated command graph is inspected by agent-bundle inspect and the
Workbench. The same graph drives completion/help generation and Rstest.
Migration
- Keep
src/cli.tsand explicitscriptsentries working. - Add zero-config filesystem CLI/script discovery with explicit override and
disable controls. - Generate a CLI for one reference MCP server using MCPorter.
- Merge generated and custom commands with collision diagnostics.
- Migrate Audiobook Curator's manual CLI operation registry.
- Update scaffolder templates.
- Deprecate manual framework dispatch while retaining explicit raw entries as
an escape hatch.
Acceptance criteria
- One project ships custom commands, MCP-generated commands, plain scripts, and
rendered scripts through one Agent Bundle build. - Help, types, schemas, Workbench catalog, and packed executable agree on the
command graph. - A generated MCP command invokes the same route contract as the MCP tool.
- When an MCP-generated command uses the wire rather than direct local route
invocation, it preserves negotiated progress/task support and the distinct
ordinary-request versus task cancellation/result flows. - A custom command can use the Agent renderer without becoming an MCP tool.
- A plain script remains a normal executable with no renderer assumptions.
- Command collisions fail at build time with actionable diagnostics.
- Mutation-capable projections preserve authorization/confirmation behavior or
fail closed; help text alone is not treated as protection. - TTY, Markdown, JSON, and NDJSON output have direct Rstest and packed-artifact
coverage.
Design references
- Current Agent Bundle entry conventions
- Current generated entry-shell lifecycle
- Current RSC CLI bypasses JSX and prints validated JSON
- MCPorter project
- MCP 2025-11-25 tools contract
- Rslib plugin integration
Stack position
- Depends on: #93 routes, #95 context/providers, and #96 RSC renderer for rendered commands and scripts.
- Plain-script packaging can land earlier but is not completion of this issue.
- #103 generated Rstest must cover every output mode before completion.
Full meta-framework stack
- Foundations: #93 routes - #94 identity - #95 context - #96 renderer
- Runtime: #97 events - #98 state - #99 notices
- Host: #100 components - #101 lifecycle
- Surfaces and proof: #102 CLI/scripts - #103 Rstest - #104 proximity example - #105 Workbench
Activity
feat(config): ship conventional src/scripts/ modules through the scri…
feat(config): ship conventional src/scripts/ modules through the scri…
feat(cli): compile src/cli/** routes into a routed CLI executable (#102…
feat(cli): render .tsx commands and scripts through the dispatcher (#102
Merge origin/main into wave4/102-routed-cli (adopt prepareRender; keep …
feat(cli): rendered commands and output modes (#102 stage 3) (#186)
ScriptedAlchemy commentedon Sep 2, 2026
Stage-4 proof dependency note: with #253 landed (#266, merged as 0131bc6), the public
agent-bundle/testharness now proves rendered commands at thecli-dispatchlevel —invokeClidrives the same render-session contract as the generated executable (piped Markdown, explicit TTY,--json,--ndjsonvia the newcliNdjson, cancellation, render/result-schema/input-schema error mapping), in-process with zero packed-pool cost. Consumer-harness evidence for rendered CLI routes is available to build stage 4 on; the packed suite (cli-routes-build.test.ts) remains the process-level receipt.feat(cli): project MCP tools into the routed CLI via routes.mcpComman…
test(example): adopt routes.mcpCommands in audiobook-curator (#102) (#…
test: ship a plain script in the packed one-build fixture (#102) (#278)
ScriptedAlchemy commentedon Sep 2, 2026
Stage 4 landed — issue complete. Four PRs, all gated locally (scoped tests + typecheck + rslint) before merge:
cb071499cli-dispatchproof level via the #266 harness — plain-command JSON byte parity with direct route invocation, receipt-authoritative exit codes (0 and 1), usage/validation failures, help agreement, and the renderedlibrary-auditacross piped Markdown, explicit TTY progress-in-place,--json, and--ndjson(CLI dialect, nojsonrpc).f06a91c6routes.mcpCommands: true | { include?, exclude? }: in-house projection of generated MCP-server tools into the collision-checked routed command graph and generated executable (standalone projection included). Uniform--input '<JSON object>'; annotation-derived--yesthat fails closed (exit 2, no route execution) for every tool not explicitlyreadOnlyHint: true; merged AB4813 collisions; new AB4822 for dead include/exclude patterns; provenance (mcp: { server, tool, confirm }) through help, inspect, dev manifest, and the Workbench decoder. Changeset:agent-bundleminor.c5885463curatortools beside its 15 custom commands (30-command graph, zero diagnostics). Hermetic dispatch proofs: read-onlyinspect_sourceswithout--yes;convert_audiobookfail-closed then opened into its own domain validation;--inputcontract failures; merged-help provenance lines.addf4254cli-routes-buildfixture ships custom plain/rendered commands, projected MCP commands, a rendered.tsxscript, and a plain.tsscript in one build — the plain script executes with no-flightworker sibling.MCPorter / gate G7 reasoning. The issue's "MCPorter projection" and migration steps 3–4 are satisfied in-house, with no MCPorter dependency, per the #107 G7 verdict. MCPorter is a live-server client: it connects to a running MCP server and generates commands from runtime introspection, which would bolt a second command model (own parsing, help, output) onto the compiled graph and add a hard dependency for an offline build step. Instead the compiler projects tools directly from the already-compiled route graph, and projected commands invoke the route locally under the same contract as the MCP server (
invocation: { kind: 'tool' }, request context{ kind: 'tool', operationId, surface, artifactEpoch }) — proven by a parity test asserting CLI--jsonoutput equals the in-memory MCPstructuredContentfor identical input. MCPorter remains fully usable against the generated MCP server for anyone wanting a live-wire client;docs/entry-conventions.mdrecords this interop position.Acceptance criteria → evidence.
cli-routes-buildfixture (feat(cli): project MCP tools into the routed CLI via routes.mcpCommands (#102 stage 4) #275 + test: ship a plain script in the packed one-build fixture (#102 stage 4) #278); the audiobook example additionally ships 15 custom + 15 projected commands (test(example): adopt routes.mcpCommands in audiobook-curator (#102 stage 4) #277).library-audit(test(example): audiobook CLI dispatch acceptance across all output modes (#102 stage 4) #274).--yesis enforced by the shell before any route execution (side-effect-counter proof in feat(cli): project MCP tools into the routed CLI via routes.mcpCommands (#102 stage 4) #275; real-example proof incl. gate-opens-into-domain-validation in test(example): adopt routes.mcpCommands in audiobook-curator (#102 stage 4) #277); missing/malformed annotations default to mutation-capable.cli-routes-build(stage 3 + feat(cli): project MCP tools into the routed CLI via routes.mcpCommands (#102 stage 4) #275/test: ship a plain script in the packed one-build fixture (#102 stage 4) #278).Notes. The
create-agent-bundlecli-tooltemplate intentionally stays on the conventionalsrc/cli.tstier — that tier remains first-class product surface (migration step 1) and already rides the #102 build model (framework-generated bin, no hand-written shim); moving templates to routed CLI is a non-blocking follow-up. Stage-4 gates ran locally: unit 2454/0, projection 57/0, route-unit 18/0, packed CLI 1/0, audiobook example check 33/0 + 20/0, typecheck clean, rslint 0/0 across all four branches.Stages 1–4 are all landed (#143, #178, #186, and the four PRs above). Closing.
ScriptedAlchemy commentedon Sep 3, 2026
Closed-issue re-audit (G3) — VERIFIED. On
origin/main:src/scripts/**andsrc/cli/**conventions (routes/graph.ts:48-57,docs/entry-conventions.mdtable),routes.mcpCommandswith--yesfail-closed andAB4813/AB4822(docs/entry-conventions.md:309-347,docs/diagnostics.md), the in-house G7 projection position on MCPorter recorded indocs/entry-conventions.md:347;examples/audiobook-curatorrunsroutes: { mcpCommands: true }with 15 custom + 15 projected commands. Run green here:cli-routes-build(packed one-build fixture: plain/rendered commands, projected MCP commands, rendered + plain scripts), audiobookcheck(40 + 31 route-unit incl.cli-dispatch/cli-rendered),pnpm test:projection63/63. The "when an MCP-generated command uses the wire" bullet stays vacuous (projection never uses the wire); if a wire-backed projection ever lands, #369 carries the task/progress semantics it would inherit. Thecli-tooltemplate remaining onsrc/cli.tsis the non-blocking follow-up already named in the closing comment.fix(test): address Codex review on script-dispatch and workbench-surface
feat(test): script-dispatch and workbench-surface proof levels; route…
fix(test): address Codex review on script-dispatch and workbench-surface
feat(test): script-dispatch and workbench-surface proof levels; route…
fix(test): address Codex review on script-dispatch and workbench-surface
feat(test): script-dispatch and workbench-surface proof levels; route…
fix(test): address Codex review on script-dispatch and workbench-surface
feat(test): script-dispatch and workbench-surface proof levels; route…
fix(test): address Codex review on script-dispatch and workbench-surface
feat(test): script-dispatch and workbench-surface proof levels; route…
feat(test): script-dispatch and workbench-surface proof levels; route…
feat(test): script-dispatch and workbench-surface proof levels; route…
ScriptedAlchemy commentedon Sep 3, 2026
Migration 6 is done: the
cli-toolscaffold template now ships a routed CLI (src/cli/greet.ts) and a conventionalsrc/scripts/hello.tsinstead of the hand-writtensrc/cli.ts, with its README proof table, scaffolder unit tests, andscaffold-packed.e2eupdated, proved by a generated projection pool at thecli-dispatchandscript-dispatchlevels. Landed in #398 (d10b60f56), together with therunScriptproof level forsrc/scripts/*; evidence on #370.