Skip to content

Static route config grammar forbids identifier references, so an MCP App's resourceUri must be duplicated as a literal in every tool's _meta.ui.resourceUri; config.template resolves from the project root #388

Closed

Description

@ScriptedAlchemy

What happened

Two small drift traps hit while moving cargo-hauler to conventional routes:

  1. AB4806 — identifier in static config. src/mcp/hauler/tools/hauler_status.tsx had

    export const config = { _meta: { ui: { resourceUri: APP_RESOURCE_URI } }, ... }

    with APP_RESOURCE_URI imported from src/constants.ts. The static extractor rejects the identifier, so the same string (ui://cargo-hauler/dashboard.html) must now be spelled as a literal in the app route's config.resourceUri, in src/constants.ts (runtime use), and in each tool that wants the widget. Nothing checks they agree.

    Ask: let a tool reference the app route it targets (e.g. _meta: { ui: { app: 'hauler/dashboard' } } resolved by the compiler to that app's resourceUri), or allow const string identifiers from the same module in the static grammar.

  2. config.template is project-root relative. In src/mcp/hauler/apps/dashboard.tsx, template: './src/mcp/hauler/apps/dashboard.html' — the path resolves from the project root, not the route file, so moving the app route requires editing the literal. Route-relative resolution (or accepting both with route-relative preferred) would match how every other import in the file resolves.

Minor, same session: agent-bundle inspect --routes aborts on the first diagnostic set instead of emitting the partial route graph, which makes it impossible to verify one route while another is mid-edit.

Pin: agent-bundle@105c65d8f.

Activity

  1. ScriptedAlchemy commented on Sep 3, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Fixed in #418 (merged as 681fd8d).

    What changed

    1. Static route config can reference an MCP App instead of repeating its ui:// literal — both requested forms:
      • appResourceUri('<app>'), exported from the new light agent-bundle/routes subpath (types plus this helper; 545-byte chunk with no imports, so a route module can import it at run time without pulling the compiler into its generated bundle). The route-graph compiler resolves it after the tree is discovered to the target App route's static config.resourceUri. Accepted references: '<app>', '<server>/<app>', 'app:<server>/<app>', or a relative module path (with or without its .ts/.tsx extension). The App must belong to the referencing route's own generated server — a generated server registers only its own Apps. Unknown / other-server / non-MCP-route references are AB4826.
      • const string-literal identifiers — a top-level const X = '<literal>' in the route module, or export const X = '<literal>' from a module reached through a relative import inside the project (import { APP_RESOURCE_URI } from '../constants'; .ts/.tsx/.js-style/index resolution, one hop, parsed never executed). This is exactly the src/constants.ts scenario from the report, and the value stays available at run time for result _meta.
      • Everything else stays AB4806, whose message now says why the identifier was rejected and whose recovery names both supported forms.
      • ToolConfig/ResourceConfig/PromptConfig/AppRouteConfig._meta type ui.resourceUri (RouteMeta, RouteUiMeta; the ui block stays open for the rest of the MCP Apps vocabulary).
      • New AB4828 (validation): a generated route advertising _meta.ui.resourceUri of an App whose targets are narrower than the server's is rejected, whether referenced or literal.
    2. App route config.template resolves relative to the route module, like its imports. The legacy project-root-relative form is still accepted with no diagnostic while it is the only interpretation naming an existing file; when both interpretations name different existing files, or neither exists, AB4827 names both candidate paths and the fix. The IR keeps the authored path (digest stays machine-independent); the normalized model carries the resolved absolute file.
    3. Docs: docs/diagnostics.md (grammar, template rule, AB4806/AB4826/AB4827/AB4828), docs/entry-conventions.md, docs/framework-mode.md (App route + tool example). Changeset for agent-bundle (patch, per the pre-1.0 rule).

    Not done here: the minor note about agent-bundle inspect --routes aborting on the first diagnostic set instead of emitting the partial route graph — that changes the invalid-inspection result shape / CLI output and is left for a follow-up issue.

    Tests

    • tests/route-config-extract.test.ts: same-module and imported const resolution (incl. .js-style and index specifiers); rejections for package specifier, missing/outside-project module, missing export, non-literal export, type-only and default imports (all AB4806 naming both forms); appResourceUri() placeholder recording and resolution by bare name, qualified id, app: id, relative path (dotted names, .jsx spelling, mistyped suffix rejected); unknown/other-server/non-MCP references → AB4826; helper not imported / wrong specifier / arity / non-string / dynamic argument → AB4806; type-level satisfies ToolConfig with an open ui block.
    • tests/route-graph.test.ts: cross-route resolution (tool/resource/prompt) with digest sensitivity; AB4826 beside AB4806; same-server rule with custom/conflict/generated-override modes; template route-relative / legacy root-relative / ambiguous / missing / absolute, digest stability; normalized mcpApps[].template; AB4828 for referenced and literal URIs, restricted server, config-declared App.
    • tests/generated-route-server.test.ts (build level): App route with imported-const resourceUri and route-relative template, tool using appResourceUri('dashboard'), resource importing the same const; the built stdio server lists _meta.ui.resourceUri resolved for both and the compiled App HTML comes from the route-relative template.
    • Gates: typecheck, lint, test:unit, test:route-unit, test:projection, build + test:integration:run (948 passed), examples/* checks; CI green and Codex review clean on the merged head.
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

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions