Skip to content

Generate CLI, MCPorter, and script projections from the compiled route graph #102

Description

@ScriptedAlchemy

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.ts is already a conventional package bin entry.
  • src/index.ts is 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

  1. Support a custom filesystem-routed CLI root.
  2. Optionally expose compiled MCP tools as CLI commands through MCPorter.
  3. Merge custom and generated commands into one coherent product CLI.
  4. Support plain JS/TS scripts without forcing them into the React renderer.
  5. Support JSX/TSX scripts whose output uses the Agent Document renderer.
  6. Provide deterministic streaming and machine-readable output modes.
  7. 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.mcpPorter projects 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:

  1. Select local generated MCP servers and tools eligible for CLI exposure.
  2. Generate MCP-backed command definitions from the compiled tool schemas and
    listing metadata.
  3. Merge those definitions with custom CLI routes.
  4. Fail the build when custom and generated commands claim the same command
    path unless config explicitly resolves the conflict.
  5. Preserve one executable, one help tree, and one version/identity surface.
  6. 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:

  • text or markdown: 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/SIGTERM reach the framework AbortSignal;
  • 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

  1. Keep src/cli.ts and explicit scripts entries working.
  2. Add zero-config filesystem CLI/script discovery with explicit override and
    disable controls.
  3. Generate a CLI for one reference MCP server using MCPorter.
  4. Merge generated and custom commands with collision diagnostics.
  5. Migrate Audiobook Curator's manual CLI operation registry.
  6. Update scaffolder templates.
  7. 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

Stack position

Full meta-framework stack

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-frameworkruntimeRuntime context, state, rendering, or execution

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions