Configuration
The conceptual model, what config owns versus what the src/ conventions own, is in
Configuration model. This page is the field contract.
Top-level fields
Host-scoped extension keys (claude, codex, cursor, portable) are contributed by target
adapters through declaration merging on AgentBundleConfigExtensions. Extension values must be
strict finite JSON (AB4500), and host-specific values stay in their adapter rather than in
compiler core. The field-by-field list of every adapter-owned key is in
Host-scoped declarations.
Generated type definitions
The config is a TypeScript contract, not a runtime schema: defineConfig accepts an
AgentBundleConfig (or a factory returning one), and validateSource enforces the rules on this
page with structured diagnostics. The exact shapes below are generated from the package source
by TypeDoc on every documentation build, so they cannot drift from the published types.
Two keys in the table above are not members of AgentBundleConfig: evals and routes are read
through its [key: string]: unknown index signature, so tsc does not check their shape. The
route compiler still parses routes during discovery, so agent-bundle validate reports a
malformed override through the route graph's diagnostics; validateSource never reads evals,
the evals rules below fire when agent-bundle eval or the Workbench loads the config,
as EVAL_CONFIG_INVALID, EVAL_INCLUDE_INVALID, or EVAL_RUNS_DIR_INVALID errors rather than
AB diagnostics. A tool that already has a colocated <tool>.cli.ts projection module is
excluded from routes.mcpCommands so the bulk <bin> <server> <tool> --input command is not
also compiled for that operation. See
Project one tool as an idiomatic command.
plugin
Shared descriptive metadata
author, homepage, keywords, license, and repository are declared once, not once per
host block. Each field resolves in one order:
- the host block for that artifact,
portable.license,cursor.license,codex.license, orclaude.marketplace.plugin.license; plugin.metadata.license;- the
licensefield of the project'spackage.json.
null is a deliberate absence rather than a value: plugin.metadata.homepage: null keeps a
package.json homepage out of every artifact, and cursor: { homepage: null } keeps a shared
homepage out of the Cursor manifest alone while the other hosts still carry it. The one
exception is codex: { author: null }: the Codex manifest always names an author, so it falls
back to the generated plugin name rather than omitting the field.
Each host emits only what its pinned schema admits, and the value still passes that host's own
validation. Cursor's author object is closed to name and email, so a shared author.url is
dropped for Cursor and kept for the others; Cursor, Codex, and Claude Code all require
author.name, so a package.json author that is only an email address shares no author with
them, while the portable manifest still carries it.
Two package.json shapes are read: npm's person string ("Ada <ada@example.com> (https://ada.example.com)")
and its object form, and for repository, an absolute http(s) URL or the
git+https://github.com/owner/repo.git URL npm writes for one, whose git+ prefix and trailing
.git are removed. Shorthands like owner/repo, SSH URLs, and git+http:// URLs are not
converted to a provider's web URL: the field is withheld with the AB4015 warning, and
plugin.metadata.repository declares it explicitly. A URL or email address the pinned host
schemas would reject is withheld the same way rather than emitted for a host to refuse.
An empty package.json field is an absent one: "keywords": [] and "license": "" share
nothing and report nothing. Under plugin.metadata the same value is the config author's, so
plugin.metadata.license: '' reports AB4014; null is how a field is opted out.
Claude Code's .claude-plugin/plugin.json carries no descriptive fields, its pinned schema
admits none, and its author.name stays the generated plugin name. The shared values reach
Claude Code through the marketplace entry.
package.json is authoritative for release identity. Declare the release version there. A
release build with no valid package.json version is refused with AB4013 rather than shipping
the 0.0.0-dev.<short-revision> development fallback. The retired plugin.version key is
rejected with AB4001 so an un-typechecked config cannot silently ignore it.
targets
The host projections build lays into the composite plugin root: any combination of amp,
claude, codex, cursor, and portable. Order is irrelevant, the selection is sorted by name, so
['codex', 'claude'] and ['claude', 'codex'] build the same bytes. Omit the key and only the
portable projection is emitted. --target on the command line replaces the configured list for
one run. Any other name, plugin included, is an unknown target (AB4100). What each projection
places in the root is in Targets and artifacts.
hooks
Keys are the seven canonical events: sessionStart, beforeTool, afterTool, stop,
agentStart, agentStop, workspaceOpen. A value is one entry, or an array of entries, each
either a module path or { handler, tools?, targets?, timeout?, args? }. How each canonical
event and tool selector lowers to a host-native event and matcher is the generated
Event and hook matrix.
The hook result contract is documented in full in Hooks. One
field is worth repeating because it is easy to get wrong: reason is a non-empty string that is
valid only when denying a beforeTool, stop, or agentStop hook, and denying one of those
without a reason fails.
output and runtime
output.repositoryMarketplace: true makes an explicit build also generate the selected
hosts' marketplace documents at the project root, with local plugin sources pointing to the
artifact output directory. Cursor additionally requires marketplace: true. Commit these
files and the artifact directory for installation directly from GitHub. The compiler replaces
the selected marketplace files; do not hand-edit them. It refuses symlinked destinations,
paths that overlap the artifact, custom adapters, and authored marketplace source or
metadata.pluginRoot overrides. These generated files are excluded from source snapshots.
Development and evaluation staging do not publish repository marketplaces.
Temporary builds used by MCP commands, hook inspection/simulation, and serve-app also
leave these files unchanged, including when the operation fails.
output.distPath is the artifact output directory relative to the project root, the composite
plugin root every selected host reads, with no per-host subdirectory beneath it. The CLI
(build, prepack, dev) defaults it to artifact, because the CLI also runs the package build
and that owns dist/; the programmatic build() defaults to dist unless packageOutputs: true.
The per-invocation --output flag wins, but is subject to the same project-root
containment check; an output path that is absolute or external, has an invalid shape, or lands
in a reserved compiler namespace is unsupported (AB4707–AB4709).
output.sourceMap is an explicit opt-in for source maps on generated executables. It defaults to
false. When true, the compiler profile sets the bundler's output.sourceMap to an inline JS
map so the map stays inside the executable, sibling .map files are not part of the planned
artifact file set. A non-boolean value is AB4707. MCP App views keep their own default (off)
and still opt in through tools.rsbuild.output.sourceMap.
Generated executables keep readable code. The compiler profile runs the SWC minimizer only to
remove comments, with compression, mangling, and whitespace minification off, so bundled
dependency documentation does not ship. Legal comments, such as /*! … */ license headers, stay
inline. Set tools.rsbuild.output.minify to false to keep every comment.
runtime.node is a minimum Node.js version in major.minor[.patch] form. It can only raise the
default floor for generated executables, never lower it, and the selected floor is recorded as
runtime.node in the artifact manifest. The floor itself is described in
Configuration model.
notices
notices.retention sets the retention policy of the notice ledger a stateful project co-mounts
beside src/state.ts. Terminal notices, expired, unavailable, withdrawn, acknowledged,
and attempted with an exhausted retry budget, are pruned from the ledger once they have been
settled for terminalTtl, or earliest-settled first once more than maxTerminal remain, and the
store's journal is compacted onto its head once it exceeds maxJournalBytes. Pruning runs only
on admitted events and explicit retain() calls; no timer is implied.
Anything else, an unknown key, a non-positive or fractional value, a duration the grammar does
not spell, or a policy declared by a project without a state module, is AB4833. inspect --state and the Workbench State panel show the resolved policy and whether it was declared or
defaulted; live counts and the last compaction belong to each installed store
(AgentNoticeLedger.inspect()).
payload
Keys are artifact-root destination directories, one safe path segment outside the compiler-owned namespaces, and values are already-built source directories. Payload trees are copied byte-for-byte and are opaque to the compiler, which cannot rewrite their internal sibling references, so stable names are the correctness contract. Integrity stays content-addressed through the artifact manifest.
evals
Accepts exactly three keys; anything else is rejected.
Provider credential material in the evals block is rejected outright
(EVAL_CREDENTIAL_REJECTED), Agent Bundle reuses the host CLI's existing signed-in session. See
Security.
dev
Development-only settings that never become part of a built artifact.
web
Exposure and policy for the artifact-resident browser host. web.apps selects among MCP Apps
already declared under mcp.servers.<id>.apps; it is not a second route graph and does not
discover Apps from src/web/**. Omit the key and the build writes no web section, no
framework-owned web command, and no extra bin.
Invalid web is AB4341 (error): an app no mcp.servers.<id>.apps declares; an unknown
allow capability; open outside browser | never; a duplicate <server>/<app>; a tool
the route graph does not declare for that generated server; a generated-mode src/cli/**
command (or top-level alias) spelled web while web is configured; or a web surface with no
executable to carry it. A selected target whose adapter publishes no supported web capability
row is an AB4341 warning: its artifact does not host the surface. Message shapes and recoveries
are in the diagnostics reference.
The web command lives only in the framework-generated bin/<plugin>.mjs. web never
displaces authored code: a hand-written src/cli.ts, a bin entry that claims the plugin name,
or bin: false keeps the executable it describes, and AB4341 reports that the web surface has
nowhere to live, move the commands under src/cli/** (generated mode) or drop web.
When web is valid, the composite artifact's agent-bundle.manifest.json gains a web section
and bin/<plugin>.mjs exists even without src/cli/**, see
Targets and artifacts and
Exposing an App in the browser.
tools
The single bundler escape hatch. Both fragments merge last-but-bounded into every bundler config
the framework synthesizes, and the artifact invariant assertions still run after the merge, so a
hatch value that breaks an artifact contract is a hard diagnostic rather than a silent override
(AB472x). AB4720–AB4723 check the shape of tools, tools.rsbuild, and tools.rspack;
AB4724 rejects a tools.rsbuild.plugins entry that re-adds a framework-owned plugin
(@rsbuild/plugin-react, rsbuild:react), because plugins arrays concatenate and Rsbuild
never dedupes plugins by name, the framework registers it for you. AB4725 rejects
tools.rsbuild.output.autoExternal that is not false, and a string or object externals
entry that names a package (neither a Node built-in, pnpapi, nor a relative path), in
tools.rsbuild.output or an object-form tools.rspack. AB4726 rejects deprecated or removed
Rsbuild v2 paths in tools.rsbuild: source.alias, source.aliasStrategy,
performance.bundleAnalyze, performance.removeMomentLocale, performance.profile,
performance.chunkSplit, output.sourceMap.extract.js, provider, tools.webpack,
tools.webpackChain, dev.setupMiddlewares, and legacy proxy context / on* event keys.
Each diagnostic names the current replacement. This check is path-specific to tools.rsbuild;
authored html.templateParameters variables and valid Rspack configuration under
tools.rspack are not subject to it.
The hatch executes on one bundler engine, the @rspack/core behind agent-bundle's pinned
@rsbuild/core, shared by the Rslib-compiled outputs (artifact scripts, MCP entries, hooks, the
package build) and the MCP App views, but that engine is agent-bundle's dependency, not your
project's. Never construct plugins or perform instanceof checks against an imported
@rspack/core, use the utils argument passed to tools.rspack mutator functions
((config, { rspack }) => ...), which always hands you the executing engine's own rspack
object.
MCP App builds keep Rsbuild's default resolve.aliasStrategy (prefer-tsconfig), so the
project's tsconfig.json paths resolve inside a compiled view as they do anywhere else. The
reserved agent-bundle/meta specifier is the exception: the compiler rewrites that exact import
to the generated identity module before resolution starts, ahead of both paths and any
resolve.alias, so no paths mapping can shadow it. The resolved-config assertion refuses a
hatch value that removes that rewrite, as it refuses one that breaks the self-contained output.
The synthesized view profile targets Chrome >= 144, the oldest Chromium generation in the
currently shipped desktop MCP App hosts when this floor was set: Cursor 3.18 reports
Chromium 144,
Claude Desktop's official Linux package reports
Chromium 148, and the Codex Windows
package reports Chromium 151. It deduplicates
exactly react, react-dom, and scheduler so an App resolves one React runtime. Author HTML
templates receive only Rsbuild's safe mountId, entryName, and assetPrefix defaults;
compilation and plugin internals remain withheld. Framework script injection and the default
root mount, language, and App title remain intact.
Plugins
The framework registers exactly one Rsbuild plugin in the configs it synthesizes:
@rsbuild/plugin-react (plugin name rsbuild:react), on every artifact entry, routes are
authored as TSX and the plugin selects the automatic JSX runtime, and on every MCP App view,
whatever its entry's extension (fast refresh off), so a .ts entry importing .tsx components
compiles to the same automatic runtime. That set is frameworkOwnedRsbuildPlugins in
packages/agent-bundle/src/build/framework-plugins.ts; a unit test derives it from the synthesized
configs, so it cannot drift, and validateTools reads the same map to report AB4724 when a
tools.rsbuild.plugins entry re-adds one of its names.
Every other official Rsbuild plugin registers under its
own name and may be added through tools.rsbuild.plugins. The fragment reaches every synthesized
config; the table says which surface each plugin is relevant to:
Not applicable: the Vue, Vue JSX, Preact, Solid, and Svelte plugins (routes and views are React;
those plugins reconfigure the JSX transform the React plugin owns), @rsbuild/plugin-umd (the
framework emits ESM), and the package-publishing checks rsbuild-plugin-publint and
rsbuild-plugin-arethetypeswrong (they lint the package.json of the build root after the build,
not the artifact).