Skip to content

Config validation rejects any mcp.servers declaration for a route-generated server (AB4304/AB4322), though normalize supports env/args/targets/apps on it #380

Closed

Description

@ScriptedAlchemy

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 (or args/targets) → AB4304 "must define exactly one of entry, command, or url, or provide the conventional stdio entry src/mcp/.ts"
  • a declaration with apps → AB4304 plus AB4322 "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

  1. validateMcpServer/validateMcpApps accept a declaration for a server that the route graph compiles in generated mode when it carries only the keys normalization honors (env, args, targets, apps), and reject entry/command/url there via the existing AB4800; or
  2. the restriction is deliberate, in which case AB4304's message/recovery should say so for generated servers, and docs/framework-mode.md should 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

  1. ScriptedAlchemy commented on Sep 3, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Fixed in #405 (merged as 56b77db).

    • A mcp.servers.<id> block for a server the route graph compiles in generated mode now augments it: env, args (appended after the hashed entry), targets, apps, and transport: 'stdio' apply; AB4304/AB4322 no longer fire. validateMcp receives the discovered route graph and applies the same judgment normalization already did.
    • Redeclaring entry/command/url under an explicit routes.servers.<id>: 'generated' is the new precise AB4340 (message names the field, recovery names the mode switch); without an explicit mode the collision stays AB4800. The local-entry field rules (AB4305, AB4308–AB4312, AB432x) still apply, and config Apps are checked against the server's route-declared Apps (AB4325 name / AB4330 resourceUri).
    • Evidence: route-graph.test.ts (augmentation through validate + inspect; AB4340; field rules; App collisions) and generated-route-server.test.ts (built artifact's mcp.json carries the env/args, the config App is served over real stdio). Docs: precedence table in docs/entry-conventions.md, AB4340 in docs/diagnostics.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmeta-frameworkAgent Bundle compiler-coupled meta-framework

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions