Repository navigation
Description
Summary
normalizeMcpServers (packages/agent-bundle/src/config/normalize.ts) explicitly merges a config-side declaration onto a route-generated server — const declaration = configured[name] ?? {} feeds env, args, and targets, and normalizeMcpApps reads rawServer.apps for any server with a source (generated servers have one) — but validateMcpServer / validateMcpApps (packages/agent-bundle/src/config/validate.ts) never consult the route graph. Any mcp.servers.<generated-server> declaration is therefore rejected before normalization runs:
- a declaration with only
env(orargs/targets) →AB4304"must define exactly one of entry, command, or url, or provide the conventional stdio entry src/mcp/.ts" - a declaration with
apps→AB4304plusAB4322"can declare Apps only with a local entry"
So there is no supported way to attach extra env/args/targets to a generated server, and the config-side apps record documented in docs/framework-mode.md / the mcp-app example is unusable for generated servers (the src/mcp/<server>/apps/*.ts route is the only working path). Neither docs/framework-mode.md, docs/entry-conventions.md, nor docs/diagnostics.md mention this restriction, and the AB4304 recovery text points at the handwritten-entry convention — which would then trigger AB4800 (both an entry and route modules).
Observed on framework main af1c185 (agent-bundle@0.0.0-preview-af1c185).
Minimal repro
repro/
package.json { "name": "repro", "private": true, "type": "module", "version": "0.0.1" }
agent-bundle.config.ts
src/mcp/demo/tools/status.tsx
agent-bundle.config.ts:
import { defineConfig } from 'agent-bundle/config';
export default defineConfig({
mcp: {
servers: {
// route-generated server (src/mcp/demo/tools/*.tsx) that only wants extra env
demo: { env: { DEMO_MODE: 'strict' } },
},
},
plugin: { description: 'AB4304 repro', name: 'repro', version: '0.0.1' },
targets: ['portable'],
});src/mcp/demo/tools/status.tsx:
import { Agent } from '@agent-bundle/runtime';
import type { ToolConfig, ToolRouteProps } from 'agent-bundle';
import React from 'react';
import { z } from 'zod';
export const config = { annotations: { readOnlyHint: true }, description: 'Read status.' } satisfies ToolConfig;
export const inputSchema = z.object({}).strict();
export const resultSchema = z.object({ status: z.literal('ready') }).strict();
export default async function Status(_props: ToolRouteProps<typeof inputSchema>) {
return <Agent.Result value={{ status: 'ready' }}><Agent.Text>ready</Agent.Text></Agent.Result>;
}$ agent-bundle validate --json
[{"code":"AB4304","message":"MCP server \"demo\" must define exactly one of entry, command, or url, or provide the conventional stdio entry src/mcp/demo.ts.", ...}]
Delete the mcp block → Validation succeeded (and the server builds). Replace env with apps: { panel: { entry: './views/panel.ts', resourceUri: 'ui://repro/panel.html' } } → AB4304 + AB4322.
Expected
Either
validateMcpServer/validateMcpAppsaccept a declaration for a server that the route graph compiles ingeneratedmode when it carries only the keys normalization honors (env,args,targets,apps), and rejectentry/command/urlthere via the existingAB4800; or- the restriction is deliberate, in which case
AB4304's message/recovery should say so for generated servers, anddocs/framework-mode.mdshould document that generated servers take no config declaration (env/args/targets/apps only through routes).
Context
Hit while re-porting ScriptedAlchemy/movie-library onto af1c185: the widget MCP App was declared under mcp.servers['movie-library-library'].apps and had to move to the src/mcp/movie-library-library/apps/widget.ts route convention (which works fine).
Activity
Fix route-mode defects found while re-porting movie-library (#380, #381…
fix(routes,mcp,package): movie-library port findings — config augment…
ScriptedAlchemy commentedon Sep 3, 2026
Fixed in #405 (merged as 56b77db).
mcp.servers.<id>block for a server the route graph compiles ingeneratedmode now augments it:env,args(appended after the hashed entry),targets,apps, andtransport: 'stdio'apply;AB4304/AB4322no longer fire.validateMcpreceives the discovered route graph and applies the same judgment normalization already did.entry/command/urlunder an explicitroutes.servers.<id>: 'generated'is the new preciseAB4340(message names the field, recovery names the mode switch); without an explicit mode the collision staysAB4800. The local-entry field rules (AB4305,AB4308–AB4312,AB432x) still apply, and config Apps are checked against the server's route-declared Apps (AB4325name /AB4330resourceUri).route-graph.test.ts(augmentation throughvalidate+inspect;AB4340; field rules; App collisions) andgenerated-route-server.test.ts(built artifact'smcp.jsoncarries the env/args, the config App is served over real stdio). Docs: precedence table indocs/entry-conventions.md,AB4340indocs/diagnostics.md.