Summary
Route mode lowers an Agent Document to a CallToolResult through documentToCallToolResult / projectMcpRenderStream (packages/rsc-runtime/src/project-mcp.ts) and registerGeneratedRoutes (packages/agent-bundle/src/mcp-server-runtime.ts). Those produce content, structuredContent (from Agent.Result value), and isError (from a represented Agent.Error), but nothing on the route contract reaches the result's _meta. Agent.Result does accept a metadata?: JsonValue prop, and decodeAgentDocument carries it into the document root (AgentResultNode.metadata), yet the MCP projection ignores it — so there is a documented-looking prop with no wire effect, and no other way for a generated tool route to set per-result _meta.
The defineOperation escape hatch could do this (Mcp.Result _meta={...}), so consumers migrating from createRscMcpServer to conventional routes lose the capability. The MCP Apps convention (ext-apps registerAppTool) stamps _meta.ui.resourceUri on every tool result as well as on the listing; the listing half works in route mode through config._meta, the result half cannot be expressed.
Observed on framework main af1c185.
Repro
// src/mcp/demo/tools/status.tsx
export const config = { _meta: { ui: { resourceUri: 'ui://demo/panel.html' } }, description: 'Status.' } satisfies ToolConfig;
export const inputSchema = z.object({}).strict();
export const resultSchema = z.object({ status: z.literal('ready') }).strict();
export default async function Status() {
return (
<Agent.Result metadata={{ ui: { resourceUri: 'ui://demo/panel.html' } }} value={{ status: 'ready' }}>
<Agent.Text>ready</Agent.Text>
</Agent.Result>
);
}
tools/list shows _meta.ui.resourceUri on the tool (good); tools/call returns { content, structuredContent } with no _meta. The runtime primitives show where it is lost:
import { decodeAgentDocument, documentToCallToolResult } from '@agent-bundle/runtime';
import { createElement } from 'react';
const tree = createElement('agent-result', { metadata: { ui: { resourceUri: 'ui://demo/panel.html' } }, value: { status: 'ready' } }, createElement('agent-text', null, 'ready'));
const document = decodeAgentDocument(tree);
console.log(JSON.stringify({ rootMetadata: document.root.metadata, callToolResult: documentToCallToolResult(document) }));
// {"rootMetadata":{"ui":{"resourceUri":"ui://demo/panel.html"}},"callToolResult":{"content":[{"text":"ready","type":"text"}],"structuredContent":{"status":"ready"}}}
document.root.metadata is populated; documentToCallToolResult never reads it.
Expected
Either project AgentResultNode.metadata to CallToolResult._meta (the natural reading of the prop name), or document in docs/framework-mode.md that generated routes have no result _meta and what Agent.Result metadata is for.
Context
Hit while re-porting ScriptedAlchemy/movie-library (five generated servers + an MCP App) onto af1c185; the plugin now relies on listing _meta only and records the missing result _meta as a wire deviation from its previous defineOperation-based build.
Summary
Route mode lowers an Agent Document to a
CallToolResultthroughdocumentToCallToolResult/projectMcpRenderStream(packages/rsc-runtime/src/project-mcp.ts) andregisterGeneratedRoutes(packages/agent-bundle/src/mcp-server-runtime.ts). Those producecontent,structuredContent(fromAgent.Result value), andisError(from a representedAgent.Error), but nothing on the route contract reaches the result's_meta.Agent.Resultdoes accept ametadata?: JsonValueprop, anddecodeAgentDocumentcarries it into the document root (AgentResultNode.metadata), yet the MCP projection ignores it — so there is a documented-looking prop with no wire effect, and no other way for a generated tool route to set per-result_meta.The
defineOperationescape hatch could do this (Mcp.Result _meta={...}), so consumers migrating fromcreateRscMcpServerto conventional routes lose the capability. The MCP Apps convention (ext-appsregisterAppTool) stamps_meta.ui.resourceUrion every tool result as well as on the listing; the listing half works in route mode throughconfig._meta, the result half cannot be expressed.Observed on framework main
af1c185.Repro
tools/listshows_meta.ui.resourceUrion the tool (good);tools/callreturns{ content, structuredContent }with no_meta. The runtime primitives show where it is lost:document.root.metadatais populated;documentToCallToolResultnever reads it.Expected
Either project
AgentResultNode.metadatatoCallToolResult._meta(the natural reading of the prop name), or document indocs/framework-mode.mdthat generated routes have no result_metaand whatAgent.Result metadatais for.Context
Hit while re-porting
ScriptedAlchemy/movie-library(five generated servers + an MCP App) ontoaf1c185; the plugin now relies on listing_metaonly and records the missing result_metaas a wire deviation from its previousdefineOperation-based build.