For AI agents: the complete documentation index is available at https://scriptedalchemy.github.io/agent-bundle/llms.txt, the full documentation bundle is available at https://scriptedalchemy.github.io/agent-bundle/llms-full.txt, and this page is available as Markdown at https://scriptedalchemy.github.io/agent-bundle/guide/start/project-structure.md.
Skip to main content
  • English
  • Project structure

    An Agent Bundle project is an ordinary Node package with agent-bundle.config.ts and a conventional source tree. Start with the files your plugin needs. A static Skill does not need an MCP server; a tool does not need a browser App; an App does not need its own server registry.

    Conventions discover the application. Configuration supplies identity, policy, and explicit exceptions. Conflicting declarations are diagnosed rather than universally resolved by "config always wins."

    The layout

    This is an illustrative composite plugin, not a minimum template. The opening paths are ordinary authored source; host manifests and executable wrappers belong in generated output.

    my-plugin/
    ├── agent-bundle.config.ts
    ├── package.json
    ├── tsconfig.json
    ├── assets/                         # optional static assets
    ├── src/
    │   ├── mcp/ops/
    │   │   ├── tools/
    │   │   │   ├── inspect.tsx          # canonical operation + schemas + Agent document
    │   │   │   └── inspect.cli.ts       # optional CLI projection of that operation
    │   │   ├── resources/policy.tsx     # an MCP resource
    │   │   ├── prompts/review.tsx       # an MCP prompt
    │   │   ├── apps/dashboard.tsx       # browser entry, not an Agent Server Component
    │   │   └── layout.tsx               # optional shared agent-facing presentation
    │   ├── events/tool/
    │   │   ├── before.tsx               # semantic tool/before event route
    │   ├── hooks/session-start.ts       # optional plain hook referenced from config
    │   ├── skills/review/SKILL.md
    │   ├── commands/review.md           # optional native prompt/command document
    │   ├── rules/project.mdc           # optional host-supported rule
    │   ├── cli/doctor.tsx               # a genuinely independent CLI workflow
    │   ├── scripts/check-service.ts     # plain artifact script
    │   ├── providers/project.ts         # optional request dependency
    │   ├── state.ts                     # optional framework state definition
    │   ├── layout.tsx                   # optional root document layout
    │   ├── components/                  # ordinary reusable presentation
    │   ├── domain/                      # ordinary business logic, clients, policies
    │   └── install-bin.ts               # optional package-bound installer delegation
    └── tests/
        ├── route-unit/
        └── projection/

    There is no authored application.ts registry, generated MCP entry, or native hook JSON in this layout. The compiler derives them where needed. domain/ and components/ are organizational choices, not new framework conventions. Use an explicit payload declaration for prebuilt non-TypeScript assets or native executables; do not assume an arbitrary source directory ships.

    What each root means

    PathMeaningImportant boundary
    src/mcp/<server>/tools/*.{ts,tsx}Tools on the generated server.A rendered tool exports schemas, optional static config, and its default component. No manual registration.
    src/mcp/<server>/tools/<tool>.cli.tsNamed CLI projection of the sibling tool.Reuses the tool; it is not another MCP route. Optional mapInput changes CLI input mapping, not the operation registry.
    src/mcp/<server>/resources/* and prompts/*MCP resources and prompts.These are not tools; their protocol metadata and result contracts differ.
    src/mcp/<server>/apps/*.{ts,tsx}Browser App entries.Declare config.resourceUri; the framework compiles and registers self-contained HTML.
    src/events/**Supported semantic event routes, such as tool/before.The path names a supported event family, not an arbitrary event bus topic. Native envelopes are adapter-owned.
    src/hooks/*.tsPlain handlers referenced by hooks configuration.The directory alone does not register them. Use the public HookHandler contract; no JSX or custom stdin wrapper is required.
    src/skills/<name>/SKILL.mdStatic Skill plus its resource files.A Skill can ship without the rendered runtime. See Skills for rendered and host-specific forms.
    src/commands/*.mdNative command/prompt documents.These are not src/cli/ commands. Availability and frontmatter are host-specific.
    src/rules/*.mdcRule documents where the host supports them.A selected target is not a promise that it supports rules.
    src/cli/**/*.{ts,tsx}Independent routed CLI commands.Nesting is the command path. Prefer a tool's .cli.ts projection when it is the same operation.
    src/scripts/<name>.ts / .tsxPlain / rendered artifact scripts.Scripts are flat. Put shared helpers outside this conventional root or under a private path. Rendered scripts receive argv and signal.
    src/providers/<name>.{ts,tsx}Request-context provider.Read the generated provider value through agent(); do not add another global service registry.
    src/state.tsOptional defineState declaration.Framework state is optional and does not take ownership of your domain database. state: false disables discovery.
    src/layout.{ts,tsx}Root agent-document layout.Wraps rendered routes, not native event responses or browser Apps.
    src/mcp/<server>/layout.{ts,tsx}Server document layout inside the root layout.Applies to that generated server's routes.
    assets/Conventional static assets.Copied under the artifact's assets/; explicit configuration can narrow the selection.

    Private path segments beginning with _ or . and declaration files are excluded from route discovery. Event files use .ts for lightweight results and .tsx for JSX; call ctx.render() from a .ts handler to load its separate .view.tsx sibling. See hooks for execution and provider access.

    Route metadata uses a bounded static grammar. Tools put it in defineTool's inline object; resources, prompts, apps, and compatibility routes use config exports. Keep runtime work in the handler or domain module, and keep schemas authoritative rather than reconstructing protocol types independently in the CLI and browser.

    Adding an interactive App

    A tool's Agent.* JSX describes the agent-facing document. An MCP App entry runs in the browser and owns its DOM or React UI. Do not import a server route into the browser: that can pull Node APIs, domain services, and server-only dependencies into the wrong runtime.

    Connect the two through the existing App resource binding. For a browser entry at src/mcp/ops/apps/dashboard.tsx, declare its resource URI in that entry:

    import type { AppRouteConfig } from 'agent-bundle';
    
    export const config = {
      resourceUri: 'ui://my-plugin/dashboard.html',
    } satisfies AppRouteConfig;

    This is metadata for the browser entry, not a complete UI. The default HTML provides #root; use an explicit template when your bootstrap needs a different shell. The tool can reference the App without repeating the URI string:

    // Metadata in src/mcp/ops/tools/inspect.tsx
    import { appResourceUri, defineTool } from 'agent-bundle/routes';
    
    export default defineTool({
      description: 'Inspect project status.',
      annotations: { readOnlyHint: true },
      _meta: { ui: { resourceUri: appResourceUri('dashboard') } },
      inputSchema,
      resultSchema,
    }, handler);

    Use createAppClient from agent-bundle/app in the browser. Its opening-input/result/error listeners and tool calls are the integration boundary; do not recreate postMessage transport, request-ID maps, or an MCP client. Register opening listeners before connecting. Render the opening result supplied by the host instead of automatically calling a mutation again on mount. Dispose the client and subscriptions when the owning UI is retired.

    Generated route declarations supply App call types. They must be current and included in the browser's TypeScript project; they do not replace validation of untrusted data. Pure presentation components and validated data can be shared where their imports are browser-safe. A Node-rendered document component is not automatically a DOM component just because both use JSX.

    To expose the same App through the optional production browser surface, configure web.apps with its existing server/app identity and opening tool. There is no second src/web/ registry. See MCP servers and Apps, the working App example, and web configuration for complete examples and policy. Browser presentation and the host projection that launches its server are separate choices.

    Config versus conventions

    The conventional starting point remains:

    import { 
    const defineConfig: (config: AgentBundleConfig | ConfigFactory) => AgentBundleConfig | ConfigFactory
    defineConfig
    } from 'agent-bundle/config';
    export default
    function defineConfig(config: AgentBundleConfig | ConfigFactory): AgentBundleConfig | ConfigFactory
    defineConfig
    ({
    AgentBundleConfig.plugin: AgentBundlePluginConfig
    plugin
    : {
    AgentBundlePluginConfig.name: string
    name
    : 'my-plugin',
    AgentBundlePluginConfig.description?: string | undefined
    description
    : 'Project tools for an agent.' },
    AgentBundleConfig.targets?: string[] | undefined
    targets
    : ['portable', 'claude', 'codex'],
    });

    Add explicit configuration only for something the convention cannot express: a target-scoped plain hook, prebuilt payload, remote server, nonstandard path, package-only bin, or policy. Required capabilities still need a supported host or an explicit restriction.

    A handwritten src/mcp/<server>.ts entry is an interoperability path for a declared server. Combining it with generated routes for the same server needs an explicit mode decision; do not rely on filesystem order. Likewise, src/cli.ts and src/cli/ are alternative CLI modes. src/index.ts is an optional library entry, not application registration. bin: false and lib: false opt out of those package conventions.

    For declarations that intentionally differ from conventions, inspect the resulting graph rather than guessing which file won:

    npx --no-install agent-bundle inspect --routes
    npx --no-install agent-bundle validate

    The compiler must already be installed in this project. Details of source selection and migration diagnostics are in configuration and package entries.

    Where output lands

    Keep these responsibilities separate:

    LocationOwner and purpose
    src/, config, package.jsonAuthored application and package identity.
    .agent-bundle/Generated route declarations and development machinery. Do not treat it as an authored registry or a durable application-data contract.
    artifact/ by defaultThe validated composite plugin tree.
    dist/ when package output is enabledThe generated npm root: artifact bytes plus package metadata and package-only entries.
    Resolved writable state rootFramework state and deliberately selected application-owned data; separate from installed code.

    The composite plugin root

    A schematic output is:

    artifact/
    ├── agent-bundle.manifest.json
    ├── agent-bundle.compile-evidence.json
    ├── INSTALL.md
    ├── .claude-plugin/          # selected host manifests
    ├── .codex-plugin/
    ├── .cursor-plugin/
    ├── .amp/plugins/<name>/     # Amp's native directory plugin, when selected
    ├── plugin.json             # portable manifest, when selected
    ├── mcp/                    # compiled server entries and their runtime files
    ├── hooks/                  # generated native wrappers and hook documents
    ├── bin/<plugin-name>.mjs    # routed CLI / web executable, when declared
    └── skills/, scripts/, assets/, mcp-apps/, ...

    Only relevant declared surfaces are emitted. Exact native documents and additional runtime files are recorded by the manifest; this tree is not an inventory to reconstruct in application code. There is no artifact/<host>/ partition. Amp uses its nested native directory, while the framework's manifest-aware installation entry accepts the composite root and selects the correct host surface.

    output.distPath or CLI --output moves the composite root without changing its internal layout. CLI precedence is --output, then configuration, then artifact/. The programmatic build() API has separate package-output/default semantics; use its reference rather than copying a CLI default into a custom build harness.

    The manifest records file digests and canonical executable identities. Reordering targets does not create another application. Conflicting bytes or unintended cross-host discovery are validation problems, not a reason to hand-edit the emitted manifests. See targets and artifacts.

    The npm package build

    dist/ is the complete generated npm package root, not merely a folder of JavaScript:

    dist/
    ├── package.json                  # generated package-relative paths and bin map
    ├── agent-bundle.manifest.json
    ├── INSTALL.md
    ├── ...                          # the composite artifact copied unchanged
    ├── bin/<plugin-name>.mjs         # the same generated routed CLI, if present
    ├── bin/<authored-bin>.js         # optional explicit package-only executable
    └── <library>.js, <library>.d.ts  # optional library output

    A routed CLI is not rebuilt into a competing .js implementation for npm. The generated package.json points at the artifact executable. Explicit package-only bins and library entries are different outputs and keep their own compilation.

    The composite and npm outputs must not overlap; putting the artifact at dist/ when package output also occupies it is invalid. Build outputs are not source inputs. Pack the generated npm root, not the source repository or a guessed nested dist/artifact/ directory. Follow shipping a bundle for validation and installation.

    Writable state is not installed code

    Inside a request, use the framework-provided plugin/context binding. Standalone code can use the documented public resolvePluginRoot runtime resolver, which distinguishes root from stateRoot; AGENT_BUNDLE_STATE_ROOT is independent of the code-root override.

    Do not place runtime caches, refreshed credentials, or databases under the installed code tree just because their loader lives there. Application-owned data keeps its own schema and explicit migration/override policy, and must not collide with framework storage. The framework does not implicitly adopt or purge arbitrary plugin data. See configuration and state and installation lifecycle.

    Next steps

    Quick start exercises a real conventional route. Skills, hooks, MCP, and scripts/assets specify each surface. Read compiler architecture for implementation details, not as another setup step.