Summary
Agent Bundle already owns configuration normalization, host artifact generation,
Rslib builds, Rsbuild-based MCP App compilation, generated lifecycle shells,
virtual modules, Workbench development, and Rstest verification. The missing
framework feature is the application compiler between project source and those
existing build/runtime layers.
Today, framework mode discovers package-level entries such as src/cli.ts and
src/mcp/<server>.ts, but individual MCP tools, resources, prompts, CLI
commands, and rendered operations are assembled manually at runtime. Projects
must import every operation, concatenate registries, repeat server identifiers,
and write thin MCP/CLI entry modules.
Add a convention-driven route compiler. The framework supplies standard source
roots and infers servers and route kinds from paths. Root configuration is only
for project-wide policy and explicit overrides. A route is one module:
its filesystem path supplies stable identity, its optional named config export
adds protocol metadata, and its remaining exports provide schemas, execution,
and React rendering. The compiler validates the complete route graph, generates
types and virtual registries, and feeds the existing Rslib/Rsbuild build paths.
This deliberately supersedes the prior 2026-08-26 design decision that listed
a filesystem router as a non-goal.
Existing capabilities to preserve
This issue does not propose rebuilding capabilities that already exist:
agent-bundle and create-agent-bundle are built with Rslib.
- Workbench and browser-facing MCP Apps are built with Rsbuild.
- The repository already uses Rstest for unit, integration, browser, packed,
and native-smoke coverage.
- Explicit config already overrides conventional package entries.
- Generated lifecycle shells already own stdio startup, stdout protection,
cancellation, shutdown, and heartbeat behavior.
- Generated modules already ride the Rslib build through protected virtual
module entries.
- MCP Apps already compile into self-contained HTML and a generated registry.
- Project snapshots and the development watcher already detect source additions,
edits, and deletions.
The compiler should extend these mechanisms rather than create a parallel build
system or expose Rspack as a product API.
Problem
The current Audiobook Curator illustrates the gap:
agent-bundle.config.ts declares a server named curator.
src/application.ts manually imports five operation families and joins their
arrays.
- each operation repeats its MCP server assignment and listing metadata;
src/mcp/curator.ts repeats the server name and calls
createRscMcpServer;
src/cli.ts manually calls runRscCli;
- MCP Apps still require explicit resource registration in a custom server.
This is convention-driven packaging around a runtime registry, not a complete
meta-framework authoring model.
Goals
- Make the project tree, route-module exports, and
agent-bundle.config.ts the
complete application declaration.
- Remove hand-authored operation arrays, application registries, server
selector strings, and framework lifecycle entry modules.
- Generate one validated route graph used by build, dev, type generation,
Workbench, CLI, MCP, scripts, tests, and provenance.
- Keep public metadata in a statically discoverable route-module export rather
than hiding it inside runtime registration calls.
- Fail at build time for missing implementations, duplicate routes, unsafe
names, ambiguous custom-server/route-server combinations, or unsupported
target capabilities.
- Preserve custom MCP server entries as an explicit escape hatch.
Non-goals
- A bundler-neutral compiler or adapter ecosystem.
- A direct Rspack authoring surface.
- Replacing Rslib, Rsbuild, Rstest, host adapters, or the current artifact
model.
- Requiring a sidecar config file for every route.
- Dynamic web-style URL parameters in the first filesystem-routing release.
Proposed source structure
src/
mcp/
curator/
tools/
inspect.tsx
inventory.tsx
convert.tsx
resources/
catalog.ts
book.ts
prompts/
curate.tsx
apps/
dashboard.tsx
cli/
doctor.tsx
scripts/
rebuild-index.ts
summarize-library.tsx
events/
file/
saved.tsx
providers/
git-worktree.ts
These are built-in framework conventions, not paths that every project must
repeat in config:
src/mcp/<server>/tools/*
src/mcp/<server>/resources/*
src/mcp/<server>/prompts/*
src/mcp/<server>/apps/*
src/events/*
src/providers/*
src/cli/*
src/scripts/*
For example, src/mcp/curator/tools/inspect.tsx is enough to declare the
inspect tool on the generated curator MCP server. Adding that file is the
declaration. No mcp.servers.curator.root, hooks.root, cli.root, or
scripts.root setting is required.
The conventional root config stays small:
import { defineConfig } from 'agent-bundle/config';
export default defineConfig({
plugin: {
description: 'Evidence-backed audiobook curation.',
name: 'audiobook-curator',
},
targets: ['claude', 'codex'],
});
Root/path configuration exists only as an escape hatch for a nonstandard source
tree, a remote or prebuilt MCP server, a custom transport, bounded discovery,
target restrictions, disabling a convention, or another explicit override.
Route-module contract
Each route is a single module. Its path supplies kind, owning surface, and
identity. An optional named config export supplies additional protocol
metadata and overrides. There is no tool.config.ts, resource.config.ts,
prompt.config.ts, app.config.ts, or hook.config.ts sidecar convention.
This follows the route-module style used by React Router: recognized exports
live beside the route implementation, while the root config remains
project-wide. The compiler extracts recognized exports through a controlled
build transform. V1 accepts only statically analyzable literal/config-helper
forms; it never executes a route handler to discover metadata. Unsupported
dynamic config produces a build diagnostic rather than an incomplete manifest.
// src/mcp/curator/tools/inspect.tsx
export const config = {
description: 'Inspect audiobook sources without changing them.',
annotations: { readOnlyHint: true },
title: 'Inspect audiobook sources',
} satisfies ToolConfig;
Root config owns project-wide concerns:
- plugin identity and convention overrides;
- server transports and host targets;
- shared defaults, policies, and bounded overrides;
- build outputs and framework plugins.
The route module's optional config export owns route-specific static details,
such as tool descriptions and annotations, resource URIs, prompt descriptions,
App resource identity, hook filters, and timeouts. When config is absent, the
compiler uses only honest path-derived defaults; it does not fabricate a
description or capability claim.
Other recognized route exports own executable behavior:
- input and result schemas;
- loading or execution;
- React rendering;
- explicit capabilities injected by the framework;
- route-local error mapping where needed.
// src/mcp/curator/tools/inspect.tsx
import { Agent } from '@agent-bundle/runtime';
import { z } from 'zod';
export const config = {
description: 'Inspect audiobook sources without changing them.',
annotations: { readOnlyHint: true },
title: 'Inspect audiobook sources',
} satisfies ToolConfig;
export const inputSchema = z.object({
maxFiles: z.number().int().min(1).max(256).optional(),
root: z.string(),
});
export default async function Inspect({ input, signal }: ToolRouteProps<typeof inputSchema>) {
const receipt = await inspectSources(input, { signal });
return (
<Agent.Result value={receipt}>
<Agent.Markdown>
Inspected **{receipt.files.length}** audiobook files.
</Agent.Markdown>
</Agent.Result>
);
}
No defineOperation, operation array, application registry, manual
registerTool, createRscMcpServer, or server selector is required.
Declaration and discovery rules
- A recognized file beneath a standard framework root is an application
declaration; its path declares route kind, server, and identity. Root config
is unnecessary for the standard layout.
- A route may optionally export
config. The compiler validates it according
to the route kind and includes it in the semantic manifest.
- Required executable exports are route-kind-specific and validated at build
time. The compiler never imports a route merely to discover whether it is a
tool, resource, prompt, App, event, CLI command, script, or provider.
include/exclude globs, private filename conventions, and ignored paths
may bound discovery. Files outside those rules do not ship.
- An explicit per-route override in root config may redirect or disable a
conventional route and produces a shadowing diagnostic when applicable.
- A server is in exactly one mode:
- generated routes;
- custom local entry;
- command/prebuilt entry;
- remote URL.
- Generated routes and a custom server entry may not silently coexist.
- Apps-only servers may be generated automatically; authors should not write
an otherwise-empty server merely to register compiled resources.
- Route discovery uses the repository's existing sorted globbing, ignore,
provenance, and safe-path machinery.
- Existing
src/mcp/<server>.ts conflicts with route-mode
src/mcp/<server>/; existing src/cli.ts conflicts with routed src/cli/.
Migration requires an explicit mode/override, and the compiler never
silently chooses one.
Compiler output
The compiler produces an immutable route graph containing:
interface CompiledAgentRoute {
kind: 'tool' | 'resource' | 'prompt' | 'app' | 'event-route' | 'cli' | 'script';
id: string;
source: string;
serverId?: string;
inputTypeModule?: string;
config: Readonly<Record<string, unknown>>;
provenance: RouteProvenance;
}
The same compiler IR owns one capability-state contract used by route
validation, projectors, events, and host components:
type CapabilityState =
| { state: 'supported'; evidence: CapabilityEvidence }
| { state: 'degraded'; reason: string; evidence?: CapabilityEvidence }
| { state: 'unavailable'; reason: string }
| { state: 'prohibited'; reason: string };
This evolves the existing adapter capability lookup; it is not a second
capability registry. A required route fails before packaging unless all
selected targets support it or the route is explicitly target-restricted.
From this graph it generates:
- route-specific TypeScript types;
- one virtual server entry per generated MCP server;
- CLI and script registries;
- semantic event and context-provider manifests;
- Workbench capability/catalog data;
- build and artifact provenance inputs;
- exact host manifests through the existing adapters;
- compiler-aware Rstest fixtures.
The generated MCP server entry:
- imports the discovered route implementations;
- constructs the real MCP server using config/plugin identity;
- registers tools, resources, prompts, and compiled Apps;
- binds the Agent renderer and transport projector;
- exports a server factory consumed by the existing lifecycle shell.
Development behavior
- Adding, removing, or renaming a discovered route invalidates the route graph.
- Editing a route recompiles only its owning entry and affected generated
registries.
- Workbench receives the generated capability catalog rather than inferring
capabilities from unrelated runtime endpoints.
- Invalid source retains the last good active build, matching current project
and runtime-generation behavior.
- Generated types are deterministic and derived from the same graph used to
compile artifacts.
Migration
- Keep explicit handwritten entries available as a documented escape hatch;
compatibility duration and deprecation policy are decided separately.
- Add route mode to one server at a time.
- Move each operation into one route module with recognized exports and an
optional named config export.
- Generate server and CLI entries after the final manual registration leaves.
- Convert Audiobook Curator into the reference framework-mode application.
- Update
create-agent-bundle templates to generate single-file route modules.
- Deprecate manual operation registries after route mode proves parity.
Acceptance criteria
- A project can declare tools, resources, prompts, and Apps by adding route
modules beneath conventional roots and ship them without a hand-authored MCP
server factory.
- A project can delete source after building and the packed artifact still
lists, calls, reads, and retrieves prompts through a real MCP client.
- Generated route types, Workbench catalogs, host manifests, and runtime
registration all derive from the same immutable route graph.
- Route
config metadata is semantically equal after the selected protocol
SDK's documented wire serialization and omission rules.
- Route changes invalidate dev output and provenance deterministically.
- Custom server mode continues to support applications outside generated-route
conventions.
- Audiobook Curator no longer contains
src/application.ts, operation arrays,
repeated mcp.server strings, or a one-line src/mcp/curator.ts adapter.
- No generated-route project needs per-route sidecar config files or duplicate
per-tool declarations in agent-bundle.config.ts.
- A conventional project does not configure the standard MCP, event, provider,
CLI, or script roots; adding a recognized source file is sufficient.
Design references
Stack position
Full meta-framework stack
Shared generated runtime topology
Generated routes should not create an unrelated execution stack per surface.
The route graph also produces a shared server-runtime plan:
host MCP client ── tools/call ─┐
├─ generated Agent runtime
thin hook client ─ render RPC ┘ ├─ request/context providers
├─ optional process state
└─ RSC/Flight render dispatcher
The generated MCP entry may host both the public MCP server and the internal
Flight render dispatcher. A tool invocation validates MCP input, supplies it as
typed route props, performs one Flight render, decodes the final Agent Document,
and projects it into a standards-valid CallToolResult. A generated hook
wrapper may reuse the same runtime through a compiler-owned local transport,
or through a native MCP-tool hook when the host exposes an equivalent contract.
The compiler owns runtime discovery, handshake, artifact/protocol identity,
cancellation, deadlines, and shutdown. Applications do not construct query
strings, local sockets, or MCP clients manually. If a host cannot connect a
hook to the shared runtime, inspect reports the degraded execution mode and the
compiler either emits an explicit standalone fallback or rejects a route that
requires shared process state.
MCP and Flight remain different layers. MCP is the public host protocol and may
also supply the host-managed process lifecycle. Flight is the internal React
render protocol. The final edge remains a legal MCP result or native hook
response; raw Flight bytes are never presented to a host that did not negotiate
them.
Summary
Agent Bundle already owns configuration normalization, host artifact generation,
Rslib builds, Rsbuild-based MCP App compilation, generated lifecycle shells,
virtual modules, Workbench development, and Rstest verification. The missing
framework feature is the application compiler between project source and those
existing build/runtime layers.
Today, framework mode discovers package-level entries such as
src/cli.tsandsrc/mcp/<server>.ts, but individual MCP tools, resources, prompts, CLIcommands, and rendered operations are assembled manually at runtime. Projects
must import every operation, concatenate registries, repeat server identifiers,
and write thin MCP/CLI entry modules.
Add a convention-driven route compiler. The framework supplies standard source
roots and infers servers and route kinds from paths. Root configuration is only
for project-wide policy and explicit overrides. A route is one module:
its filesystem path supplies stable identity, its optional named
configexportadds protocol metadata, and its remaining exports provide schemas, execution,
and React rendering. The compiler validates the complete route graph, generates
types and virtual registries, and feeds the existing Rslib/Rsbuild build paths.
This deliberately supersedes the prior
2026-08-26design decision that listeda filesystem router as a non-goal.
Existing capabilities to preserve
This issue does not propose rebuilding capabilities that already exist:
agent-bundleandcreate-agent-bundleare built with Rslib.and native-smoke coverage.
cancellation, shutdown, and heartbeat behavior.
module entries.
edits, and deletions.
The compiler should extend these mechanisms rather than create a parallel build
system or expose Rspack as a product API.
Problem
The current Audiobook Curator illustrates the gap:
agent-bundle.config.tsdeclares a server namedcurator.src/application.tsmanually imports five operation families and joins theirarrays.
src/mcp/curator.tsrepeats the server name and callscreateRscMcpServer;src/cli.tsmanually callsrunRscCli;This is convention-driven packaging around a runtime registry, not a complete
meta-framework authoring model.
Goals
agent-bundle.config.tsthecomplete application declaration.
selector strings, and framework lifecycle entry modules.
Workbench, CLI, MCP, scripts, tests, and provenance.
than hiding it inside runtime registration calls.
names, ambiguous custom-server/route-server combinations, or unsupported
target capabilities.
Non-goals
model.
Proposed source structure
These are built-in framework conventions, not paths that every project must
repeat in config:
For example,
src/mcp/curator/tools/inspect.tsxis enough to declare theinspecttool on the generatedcuratorMCP server. Adding that file is thedeclaration. No
mcp.servers.curator.root,hooks.root,cli.root, orscripts.rootsetting is required.The conventional root config stays small:
Root/path configuration exists only as an escape hatch for a nonstandard source
tree, a remote or prebuilt MCP server, a custom transport, bounded discovery,
target restrictions, disabling a convention, or another explicit override.
Route-module contract
Each route is a single module. Its path supplies kind, owning surface, and
identity. An optional named
configexport supplies additional protocolmetadata and overrides. There is no
tool.config.ts,resource.config.ts,prompt.config.ts,app.config.ts, orhook.config.tssidecar convention.This follows the route-module style used by React Router: recognized exports
live beside the route implementation, while the root config remains
project-wide. The compiler extracts recognized exports through a controlled
build transform. V1 accepts only statically analyzable literal/config-helper
forms; it never executes a route handler to discover metadata. Unsupported
dynamic config produces a build diagnostic rather than an incomplete manifest.
Root config owns project-wide concerns:
The route module's optional
configexport owns route-specific static details,such as tool descriptions and annotations, resource URIs, prompt descriptions,
App resource identity, hook filters, and timeouts. When
configis absent, thecompiler uses only honest path-derived defaults; it does not fabricate a
description or capability claim.
Other recognized route exports own executable behavior:
No
defineOperation, operation array, application registry, manualregisterTool,createRscMcpServer, or server selector is required.Declaration and discovery rules
declaration; its path declares route kind, server, and identity. Root config
is unnecessary for the standard layout.
config. The compiler validates it accordingto the route kind and includes it in the semantic manifest.
time. The compiler never imports a route merely to discover whether it is a
tool, resource, prompt, App, event, CLI command, script, or provider.
include/excludeglobs, private filename conventions, and ignored pathsmay bound discovery. Files outside those rules do not ship.
conventional route and produces a shadowing diagnostic when applicable.
an otherwise-empty server merely to register compiled resources.
provenance, and safe-path machinery.
src/mcp/<server>.tsconflicts with route-modesrc/mcp/<server>/; existingsrc/cli.tsconflicts with routedsrc/cli/.Migration requires an explicit mode/override, and the compiler never
silently chooses one.
Compiler output
The compiler produces an immutable route graph containing:
The same compiler IR owns one capability-state contract used by route
validation, projectors, events, and host components:
This evolves the existing adapter capability lookup; it is not a second
capability registry. A required route fails before packaging unless all
selected targets support it or the route is explicitly target-restricted.
From this graph it generates:
The generated MCP server entry:
Development behavior
registries.
capabilities from unrelated runtime endpoints.
and runtime-generation behavior.
compile artifacts.
Migration
compatibility duration and deprecation policy are decided separately.
optional named
configexport.create-agent-bundletemplates to generate single-file route modules.Acceptance criteria
modules beneath conventional roots and ship them without a hand-authored MCP
server factory.
lists, calls, reads, and retrieves prompts through a real MCP client.
registration all derive from the same immutable route graph.
configmetadata is semantically equal after the selected protocolSDK's documented wire serialization and omission rules.
conventions.
src/application.ts, operation arrays,repeated
mcp.serverstrings, or a one-linesrc/mcp/curator.tsadapter.per-tool declarations in
agent-bundle.config.ts.CLI, or script roots; adding a recognized source file is sufficient.
Design references
Stack position
Full meta-framework stack
Shared generated runtime topology
Generated routes should not create an unrelated execution stack per surface.
The route graph also produces a shared server-runtime plan:
The generated MCP entry may host both the public MCP server and the internal
Flight render dispatcher. A tool invocation validates MCP input, supplies it as
typed route props, performs one Flight render, decodes the final Agent Document,
and projects it into a standards-valid
CallToolResult. A generated hookwrapper may reuse the same runtime through a compiler-owned local transport,
or through a native MCP-tool hook when the host exposes an equivalent contract.
The compiler owns runtime discovery, handshake, artifact/protocol identity,
cancellation, deadlines, and shutdown. Applications do not construct query
strings, local sockets, or MCP clients manually. If a host cannot connect a
hook to the shared runtime, inspect reports the degraded execution mode and the
compiler either emits an explicit standalone fallback or rejects a route that
requires shared process state.
MCP and Flight remain different layers. MCP is the public host protocol and may
also supply the host-managed process lifecycle. Flight is the internal React
render protocol. The final edge remains a legal MCP result or native hook
response; raw Flight bytes are never presented to a host that did not negotiate
them.