Skip to content

MCP projector emits notifications/progress only for progress events, never for an Agent.Progress node streamed in a Suspense shell #448

Description

@ScriptedAlchemy

Summary

A route that streams a Suspense fallback of <Agent.Progress completed={0} message="…" /> produces a shell document carrying a progress node (visible in renderRouteEvents, the Workbench, and --ndjson), but the MCP projector never turns that node into a notifications/progress notification: projectMcpEventStream only notifies on progress events (case 'progress' in the event loop), and a progress node inside a shell/replace document is not one.

So an MCP client that passed a progressToken receives nothing while the page is suspended, even though the framework's own documentation frames Agent.Progress as the streaming progress surface and the shell demonstrably streamed it. The only way to reach MCP progress today is (await agent()).progress.report(...), which means every streamed page has to state the same message twice (once in the fallback, once in the report) or route it through a shared constant.

Repro

Preview agent-bundle@43abb2db0, a generated tool route:

async function Page({ input, signal }) {
  await new Promise((resolve) => setTimeout(resolve, 50));
  return <Agent.Result value={{ ok: true }}><Agent.Text>done</Agent.Text></Agent.Result>;
}

export default async function Route({ input, signal }) {
  return (
    <Suspense fallback={<Agent.Progress completed={0} message="Reaching the seedbox" />}>
      <Page input={input} signal={signal} />
    </Suspense>
  );
}
const rendered = await renderRouteEvents('tool:seedbox/check', { input: {} });
// rendered.events: shell (root contains {"kind":"progress","message":"Reaching the seedbox"}), replace, complete
const projected = await projectTargetCapabilities(
  rendered,
  createTargetCapabilityFixture({ audio: false, image: false, progress: true, resource: false, richContentFallback: 'text' }),
);
projected.progress // []  ← expected one notification with message "Reaching the seedbox", progress 0

Adding await (await agent()).progress.report({ completed: 0, message: 'Reaching the seedbox' }) at the top of Page makes projected.progress non-empty; the fallback node alone never does.

Expected

One of:

  1. The projector emits a notifications/progress for each new progress node that appears in a streamed shell/replace document (deduplicated by the same monotonic completed rule it applies to events), so a Suspense fallback is enough; or
  2. docs/framework-mode.md / the Agent.Progress reference states explicitly that a progress node is document content only and that MCP progress requires progress.report, and the example (audiobook-curator audit_library, which does both with the same string) calls out why.

movie-library ships the double form (src/progress.ts announce(message) + the fallback reading the same computed message) with a comment pointing here.

Activity

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions