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/examples/mcp-app.md.
Skip to main content
  • English
  • MCP App

    One service-readiness workflow, expressed once and emitted as every surface a host can reach: a real local MCP server, a typed tool, an interactive MCP App resource, a Skill, a session-start hook, a fixture-check script, and a deterministic eval. It is the example to read when you want to see how the surfaces fit together instead of studying one of them alone.

    • Run from the repository root: pnpm example:mcp-app
    • Package: @agent-bundle-example/mcp-app
    • Public dependencies: agent-bundle (workspace:*), @agent-bundle/runtime, react, zod; @rstest/browser, @rstest/core, and playwright for the browser-app test
    • Targets: portable, codex, claude: every target ships the status server and its App
    • Credentials: none: the eval and the fixture check read only checked-in data
    • Source: examples/mcp-app

    What it proves

    • Routes are the registration. The config names no server, tool, resource, or App. The status server exists because src/mcp/status/** holds three route modules: tools/show-status.tsx (one service's health), resources/readiness-policy.tsx (the release rule at policy://mcp-app-example/readiness), and apps/status.ts + status.html (the App the tool opens through _meta.ui.resourceUri, spelled once with appResourceUri('status')). show-status uses defineTool, so that App binding, its schemas, and its handler stay in one definition. There is no handwritten McpServer, no registerTool, and no @modelcontextprotocol/server dependency. See MCP servers and MCP Apps.

    • The generated stdio lifecycle is not yours to write. The build emits the server entry and its lifecycle shell: the console-to-stderr guard, signal handling, stdin-EOF exit, bounded shutdown, and the heartbeat.

    • An MCP App is a compiled resource, not a served page. The App compiles once, to mcp-apps/status.html in the artifact root with a stable resourceUri, and is inlined into the generated status server entry, which every selected host launches from that same root.

    • Degraded evidence is first-class. src/service-status.ts holds the schemas and the compiler and payments-api records, and payments-api deliberately returns degraded latency. The interesting output is a failing check that is reported honestly, not a green screenshot.

    • A browser surface is its own proof level. tests/browser-app/status-panel.browser.test.ts mounts the production-compiled App HTML over the product bridge through agent-bundle/test/browser, the browser-app level in Testing.

    • The App is a client of the bridge, not a transport of its own. src/mcp/status/apps/status.ts creates its client with createAppClient from agent-bundle/app, receives the opening show-status call's input and structured result through onToolInput / onToolResult, and surfaces failed or unstructured opening results through onToolError registered on tool:status/show-status. Those route listeners fire when the host's initialize result names show-status as the opening tool; onToolCancelled separately surfaces a host-cancelled opening call. Refresh status calls the same show-status tool again for the service on screen, and Read policy reads the readiness-policy resource through request('resources/read', …). The route id, input, and result are typed by the generated .agent-bundle/routes.d.ts, so the App declares no contract map of its own. No JSON-RPC ids, pending maps, postMessage('*'), or structuredContent unwrapping remain in the view. See The typed App client.

    • The web surface ships inside the artifact. The example configures web: { apps: [{ app: 'status/status', tool: 'show-status', allow: ['call-tool'] }] }, so the composite root gains a web section in agent-bundle.manifest.json and bin/mcp-app-example.mjs even without src/cli/**. From the built artifact:

      node artifact/bin/mcp-app-example.mjs web --no-open

      That prints the ready line and serves the status App on loopback. agent-bundle dev serves the same page at /web/status/status. See Exposing an App in the browser.

    What is authored

    PathWhat it is
    src/service-status.tsThe service schemas and the two health records.
    src/mcp/status/tools/show-status.tsxThe defineTool route; its inline _meta.ui.resourceUri opens the App.
    src/mcp/status/resources/readiness-policy.tsxThe policy://mcp-app-example/readiness resource route.
    src/mcp/status/apps/status.ts / status.htmlThe MCP App entry and template compiled to mcp-apps/status.html.
    src/skills/service-readiness/The evidence, checks, and report needed for a service-readiness decision.
    src/hooks/session-start.tsAdds the readiness workflow to compatible host sessions.
    src/scripts/check-service-fixture.tsValidates the checked-in compiler fixture before a release walkthrough.
    evals/status.eval.tsThe deterministic mcp-app-status suite and its status-is-healthy case.

    Working in the Workbench

    1. Under Application → Skills, select service-readiness. Its document renders by default; use the inspector for source/generated output, resources, and eval coverage.
    2. Under Application → Events / Hooks, select the configured sessionStart hook and choose the Claude fixture. Run it to inspect canonical input, returned context, and the Claude projection.
    3. Under Application → Scripts, select check-service-fixture and run it. The emitted checker resolves the packaged status fixture beside its emitted module, so it succeeds independently of the shell's working directory.
    4. Select the status App under Application → MCP → status → Apps. The workspace opens a session against the published build with show-status bound to the App; choose a service and Call tool and preview to render the panel beside the tool result. Advanced → Artifact shows the composite plugin root, where mcp-apps/status.html appears whichever target is selected.
    5. Before two eval runs exist, Advanced → Evals → Compare deliberately shows At least two recorded runs are needed before a comparison can be aligned., the precise empty state, not an error.
    6. Open Advanced → Protocol, choose portable and the status server, open the session, list tools, select show-status, choose payments-api, and invoke it. Invocation history shows the degraded summary with labelled Availability and P95 latency checks, the latter failing. Open the App preview: the rendered panel shows the same record through the MCP Apps bridge, with a text-labelled amber degraded indicator; Refresh status and Read policy call the real tool and resource. Inspect the protocol trace, use Restart MCP session, then close, reset, and reopen the session to exercise the lifecycle. Then open Trace to see the MCP request, response, notifications, and session activity joined by their session and JSON-RPC request ids.
    7. In Advanced → Evals → Runs, select mcp-app-status, run status-is-healthy, and inspect the completed passing trial attributed to service-readiness.

    If you edit a source file, rebuild and wait for a Failed or Idle state before judging the result; a Building state is still in progress.

    Noninteractive checks

    After the repository-level pnpm build:

    cd examples/mcp-app
    pnpm validate
    pnpm build
    pnpm typecheck
    pnpm exec agent-bundle eval --case status-is-healthy --trials 1

    pnpm check runs validation, build, and typecheck without the Workbench.

    Running the server on stdio

    pnpm exec agent-bundle mcp run --server status --target portable

    mcp run resolves the generated entry from the portable target's MCP manifest, building a temporary artifact first; pass --artifact artifact to reuse the pnpm build output instead. Closing stdin exits 0 and Ctrl-C exits 130, and per-server state persists under .agent-bundle/mcp-run/portable/status.

    The command loads the project-root .env set, including the selected --mode variants, by default. Launch environment precedence is manifest env, then .env files, then exported operator variables. Repeatable --env-file <path> replaces the conventional files, --no-env skips them, and --plugin-root <path> is only for a copied-artifact rehearsal. The full contract is in Runtime environment.