Skip to content

Conversation

@flvndvd
Copy link
Contributor

@flvndvd flvndvd commented Apr 30, 2025

Description

The Model Context Protocol was designed to facilitate tool interactions in a standardized way. However, our current implementation only displays results once a tool has completed execution, which can lead to a degraded user experience, especially for long-running tools that may take several minutes to complete.

Previously, with Dust Apps, we could stream reasoning tokens as they were generated, providing immediate feedback to users. The introduction of tools changed this paradigm, creating a gap in user feedback during tool execution.

Implementation

This PR introduces foundational support for MCP notifications, enabling server-to-client intermediate updates during tool execution. We leverage the notifications/progress method from the MCP specification, which provides a structured way to communicate progress updates.

The notification system requires:

  • Progress indicator (normalized between 0 and 100)
  • Optional label for status description
  • Optional output that follows our existing MCPToolResultContentType structure

We temporary hack the progressToken used to ensure only active requests can get progress notification, since it's not yet available in the ToolCallback. The PR was recently merged and the SDK has not been published yet. This logic will be removed once it's available (PR modelcontextprotocol/typescript-sdk#328).

For example, an image generation tool can now provide:

{
  method: "notifications/progress",
  params: {
    progress: 50,
    label: "Generating image...",
    output: {
      type: "image",
      mimeType: "image/png"
    }
  }
}

Dependencies

We've updated to the latest MCP SDK which includes proper notification support and fixes the ESM issues reported in modelcontextprotocol/typescript-sdk#217.

Future Work

While this PR establishes the basic infrastructure, the current implementation prioritizes functionality over type safety. A follow-up PR will introduce proper TypeScript types and wrappers around the notification system.

The decision to use a subset of our output types rather than the complete MCPToolResultContentType allows tools to send partial results during execution, which is essential for progressive updates. This approach maintains flexibility while ensuring consistent structure in notifications.

Some refactoring is required around the AgentMessage component to handle notification as part of the streaming message.

Tests

Risk

Deploy Plan

@flvndvd flvndvd changed the title MCP notification foundations MCP Notifications Support for Live Tool Updates Apr 30, 2025
@flvndvd flvndvd marked this pull request as ready for review April 30, 2025 16:39

// Read from notificationStream and yield events until the tool is done.
let toolDone = false;
while (!toolDone) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you rely on the timeout above to exit the loop if the tool never ends ?

is the toolPromise resolved when the timeout is reached or does it raise an error or something ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the tools never ends, it works like before. If timeout is reached, then it raises an McpError that will be caught by the surrounding try/catch block.

Here is the Promise.race() is used to either yield the notification or the result based on the first one that resolves.

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand but if you have an error, how does the while(toolDone) behave ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are two scenarios here:

  1. Looking at the return type of callTool it returns an object with isError. This one will properly exit the loop from the toolPromise.then(() => MCP_TOOL_DONE_EVENT_NAME),,
  2. callTool can also throw an error like it does when the timeout is reached. Here is what will happen:
  • toolPromise will reject with a timeout error
  • The Promise.race() will reject with that same error
  • The error will propagate out of the while loop
  • The outer try/catch will catch the error


// If the tool completed, break from the loop and stop reading notifications.
if (notificationOrDone === MCP_TOOL_DONE_EVENT_NAME) {
toolDone = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

i find it a bit convoluted to have the tool promise to turn into a string to turn into a boolean. Can't you make it a bit more simple ?

Copy link
Contributor

@Fraggle Fraggle left a comment

Choose a reason for hiding this comment

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

My main concerns is the handling of the potential infinite loop.

@flvndvd flvndvd added the sdk-ack Used to acknowledge that you are not breaking the public API. label May 5, 2025
@flvndvd flvndvd merged commit 8bf74b8 into main May 5, 2025
10 checks passed
@flvndvd flvndvd deleted the flav/mcp-notification-foundations branch May 5, 2025 11:55
franckq25655 pushed a commit to jamon8888/dust that referenced this pull request May 13, 2025
* MCP Notifications Foundations

* Add notifications in image_generation and reasoning tools

* ✨

* ✂️

* Address comments from review

* 👕

* ✨

* Fix types + tentative fix linter

* Fix TS peer dependencies

* :exploded_brain:

* 🙈

* ✨
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk-ack Used to acknowledge that you are not breaking the public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants