Skip to content

fix(mcp): project a streamed Agent.Progress fallback to notifications/progress (#448) - #498

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
fix/448-progress-fallback-notifications
Sep 4, 2026
Merged

fix(mcp): project a streamed Agent.Progress fallback to notifications/progress (#448)#498
ScriptedAlchemy merged 3 commits into
mainfrom
fix/448-progress-fallback-notifications

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Fixes #448.

What

The MCP projector (packages/rsc-runtime/src/project-mcp.ts, projectMcpEventStream) now reads Agent.Progress nodes out of every streamed shell and replace document and sends them as notifications/progress when the request carried _meta.progressToken. A <Suspense fallback={<Agent.Progress …/>}> is therefore enough to reach an MCP client; the route no longer has to repeat the same message through (await agent()).progress.report().

Both sources — progress events and streamed progress nodes — go through one monotonic rule (MCP requires progress to increase with every notification), so:

  • a fallback re-streamed on a later chunk is not re-sent;
  • a fallback and an explicit report of the same completed produce one notification, not two;
  • a later fallback that advances completed is sent;
  • no token → no notifications at all (unchanged);
  • a progress node in the final complete document remains content only and never becomes a content block.

Notification shape follows the SDK's ProgressNotificationParamsSchema: { progressToken, progress: completed, total?, message? } (message shortened to 200 chars as before).

Exact sequence a fallback now produces

For the route-harness catalog tool (<Suspense fallback={<Agent.Progress completed={0} message="loading mystery" total={2} />}>), a tools/call with _meta.progressToken: "tok-448" yields:

  1. notifications/progress { progressToken: "tok-448", progress: 0, total: 2, message: "loading mystery" } (from the shell event)
  2. CallToolResult with the resolved boundary content and structuredContent — the fallback is not in content.

The same call without a token yields only the CallToolResult.

Consumer code this lets you delete

movie-library's src/progress.ts (announce()) and the progress prop plumbing that mirrors each Suspense fallback message into progress.report() across 39 routes (e.g. src/pages/search.tsx:30-49). In this repo, examples/audiobook-curator audit_library dropped its duplicate progress.report() and its streaming test now proves the fallback alone is announced.

Other projections checked

  • Workbench renders progress nodes from documents directly (agent-document-stage.tsx) and consumes real notifications/progress from the server, so it picks this up with no change.
  • Hooks have no progress channel; events/projection.ts already ignores progress nodes (content-only), unchanged.
  • Rendered CLI (TTY) (cli-entry.ts runRenderedInvocation, added in 501622db1 after review): the in-place progress line is now also drawn from Agent.Progress nodes in shell/replace events, redrawn only when the fallback itself changes (explicit reports always redraw, as before). Piped Markdown, --json, and --ndjson are unchanged. Pinned by a new cli-dispatch-rendered.test.ts case on the projected harness catalog command (\r\x1b[2Kloading mystery (0/2) drawn exactly once; absent from piped output).

Tests

  • packages/rsc-runtime/tests/mcp-projector.test.ts — new Agent.Progress rendered as a streamed Suspense fallback (#448) block: fallback → one notification with message/progress/total; re-streamed fallback not repeated and advancing fallback sent; no token → nothing; fallback + explicit report → no duplicate; progress node in the complete document → content only. Existing "buffers shell and replace" test updated to expect the shell's fallback notification (markdown beside it still never leaks).
  • packages/agent-bundle/tests/projection/target-capabilities.test.ts — route-unit: tool:harness/catalog has no progress event yet projects one notification; progress: false fixture projects none.
  • packages/agent-bundle/tests/projection/mcp-in-memory.test.tsopenInMemoryMcpServer() + real SDK client: no token → no notification; _meta.progressToken → exactly one notifications/progress with the fallback's fields, and the resolved result.
  • examples/audiobook-curator/tests/route-unit/streaming.test.ts — asserts no progress event exists and the projector still announces the fallback.

Ran: pnpm lint, pnpm typecheck, pnpm test:unit (3096), pnpm test:route-unit (46), pnpm test:projection (148), audiobook-curator test:routes (31), pnpm docs:site:build (parity OK).

Docs

  • docs/framework-mode.md "What reaches the MCP wire": new Agent.Progress row.
  • website/docs/{en,zh}/guide/authoring/mcp.mdx: new "Streaming and progress" section.
  • website/docs/{en,zh}/examples/audiobook-curator.mdx, examples/audiobook-curator/README.md, packages/rsc-runtime/README.md: updated to the single-source form.

Changeset

.changeset/448-progress-fallback-notifications.mdpatch for @agent-bundle/runtime and agent-bundle.

Review status

Codex reviewed 5f2aa0001 and opened two threads; both are addressed in 501622db1 (no PR comments are posted from this side by instruction — answers live here):

  • audit_library.tsx:25 — "Restore CLI-visible progress for the projected command." Agreed on the gap, fixed at the framework level instead of in the example: the rendered CLI TTY now draws its progress line from the streamed fallback node (see "Other projections checked"), so the projected curator audit_library command shows Analyzing duplicate and multipart groups (0) while LibraryAnalysis suspends without the duplicate progress.report(). Restoring the report would have kept the double form the issue exists to remove.
  • mcp.mdx:91 — "Scope fallback-only progress guidance to MCP." The "on every host" wording was wrong at that commit. With the CLI change it is now true for MCP and the rendered CLI, and the section says exactly that: "the streaming progress surface on MCP, in the rendered CLI, and in the Workbench", with a new paragraph on the CLI (TTY only; piped/--json/--ndjson never print it) and a note that hooks have no progress channel. Same in zh and in docs/framework-mode.md.

Latest head reviewed by Codex: 5f2aa0001. Rebased onto main (60f75c1e7, after #506 merged; the only conflict was two independent test blocks appended at the same spot in cli-dispatch-rendered.test.ts, both kept). Unreviewed head at merge time (if no fresh review arrives): ace2fa193.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ace2fa1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@agent-bundle/runtime Patch
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T01:05:20.799398Z 5f2aa00 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@498
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@498
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@498

commit: ace2fa1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f2aa00016

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});
// The Suspense fallback is the progress surface: the MCP projector turns the
// streamed `Agent.Progress` node into `notifications/progress` for a client
// that sent a progress token, so no `progress.report()` repeats the message.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore CLI-visible progress for the projected command

Because agent-bundle.config.ts enables routes.mcpCommands, this MCP route is also exposed as the rendered curator audit_library CLI command. runRenderedInvocation in packages/agent-bundle/src/cli-entry.ts ignores shell/replace events and updates the interactive TTY line only for explicit progress events, so deleting this report makes the command appear idle while LibraryAnalysis suspends. Keep the explicit report for this dual-surface route; the new monotonic MCP filter will still deduplicate its matching fallback notification.

AGENTS.md reference: AGENTS.md:L59-L59

Useful? React with 👍 / 👎.

Comment thread website/docs/en/guide/authoring/mcp.mdx Outdated
Comment on lines +89 to +91
A route streams by rendering React `Suspense`: the shell goes out first with the fallback in
place, and each resolved boundary replaces it. `Agent.Progress` is the fallback to use — it is the
framework's streaming progress surface on every host:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope fallback-only progress guidance to MCP

The claim that Agent.Progress is the streaming progress surface “on every host” is false for rendered CLI commands: packages/agent-bundle/src/cli-entry.ts ignores progress nodes in shell/replace documents and displays TTY progress only for explicit progress events. A route author following this guidance and removing progress.report() will therefore lose interactive CLI progress, so the fallback-only recommendation should be qualified as MCP-specific and the CLI requirement documented.

AGENTS.md reference: AGENTS.md:L78-L81

Useful? React with 👍 / 👎.

@ScriptedAlchemy
ScriptedAlchemy force-pushed the fix/448-progress-fallback-notifications branch from 501622d to a0f9c8b Compare September 4, 2026 02:45
@ScriptedAlchemy
ScriptedAlchemy force-pushed the fix/448-progress-fallback-notifications branch from a0f9c8b to ace2fa1 Compare September 4, 2026 03:34
@ScriptedAlchemy
ScriptedAlchemy merged commit adb25b4 into main Sep 4, 2026
13 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fix/448-progress-fallback-notifications branch September 4, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant