-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
💻 feat: deeper MCP UI integration in the chat UI #9669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
9933de0
to
2fc4441
Compare
|
||
// Store UI resources when output is available | ||
useEffect(() => { | ||
if (output && attachments && !isSubmitting) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of adding this useEffect
, can you leverage the existing useAttachments
hook?
client/src/hooks/Messages/useAttachments.ts
packages/api/src/mcp/utils.ts
Outdated
} | ||
} | ||
|
||
export const uiResourcesInstructions = `The tool response contains UI resources (i.e. Resource URI starting with "ui://"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's unnecessary to couple this with artifacts. We can easily show the UI resources adjacent to the tool call, or even in place of the tool call, without needing the LLM to "display" it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's also possible to create a separate plugin for MCP UI resources if LLM input for rendering is what's desired, similar to the one for web search:
client/src/components/Web/plugin.ts
this would also make it "easier" for the LLM so the "artifacts" boilerplate is not necessary. In my experience, it's best to reduce the amount the LLM has to generate and even key the specific "attachment" to render, so it doesn't need to generate a long string to render the expected output.
For example, in web search, we can reference specific links from the search like this:
\ueturn0search3
2fc4441
to
9d8cb66
Compare
9225fbd
to
2d7b88e
Compare
2d7b88e
to
e21eab2
Compare
import type { Node } from 'unist'; | ||
import type { UIResourceNode } from './types'; | ||
|
||
export const UI_RESOURCE_MARKER = '\ue205'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not recommend using this as a marker, and instead use \ui
or something similar, especially since the marker does not vary from \ue205
.
As long as the plugin correctly falls back to \ui
or the chosen marker, if it has no corresponding resource, to avoid edge cases where the AI writes the marker for purposes not related to UI resources.
Using "unicode-like" markers was a convention introduced by ChatGPT for their web search, which was a large inspiration for LC's UI/LLM interaction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! The marker is now \ui0
(or any other index) to show the resources. It works just as well!
e21eab2
to
fcf97ac
Compare
Co-authored-by: Samuel Path [email protected] Co-authored-by: Pierre-Luc Godin [email protected]
fcf97ac
to
42ee75f
Compare
}; | ||
continue; | ||
} else if (tool && mcpToolPattern.test(tool)) { | ||
toolContextMap[tool] = `# MCP Tool \`${tool}\`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take issue with this being added to all MCP tools. These instructions can be part of the response that we format in formatToolContent
i.e. packages/api/src/mcp/parsers.ts
to ensure it's only applied once per "MCP UI" output.
Even better, would be to make use of serverInstructions
for this to be on the MCP server to provide:
https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/mcp_servers#serverinstructions
Some MCP Servers have 50+ tools and are loaded individually at this level, and we would have repeated instructions for them all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we don't need to add this to all MCP tools and I think formatToolContent
is the right place for this.
These instructions aren't about how to use the tools on the server, but rather what to do with the tool response, i.e. how to render them in the client using markers. This is specific to LibreChat's implementation of MCP-UI, and other MCP clients could choose to use a different marker or syntax to render UI resources. This makes no difference for the MCP server, so I don't think the serverInstructions
are the right place. What do you think?
const [showRightArrow, setShowRightArrow] = useState(true); | ||
const [isContainerHovered, setIsContainerHovered] = useState(false); | ||
const scrollContainerRef = React.useRef<HTMLDivElement>(null); | ||
const { submitMessage } = useSubmitMessage(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not utilize this hook within the message tree as it can cause unnecessary re-renders across all messages and cause significant client latency.
See ~/components/Chat/Messages/Content/Parts/EditTextPart
and client/src/components/Chat/Messages/Content/EditMessage.tsx
for how this is prevented and submissions are still allowed.
import type { Pluggable } from 'unified'; | ||
import { Citation, CompositeCitation, HighlightedText } from '~/components/Web/Citation'; | ||
import { | ||
mcpUIResourcePlugin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imports order
const localize = useLocalize(); | ||
const { submitMessage } = useSubmitMessage(); | ||
const { messageId } = useMessageContext(); | ||
const { conversation } = useChatContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useChatContext
and useSubmitMessage
should not be used here as they can cause unnecessary re-renders across all messages and cause significant client latency.
See how other adjacent components access the same things (EditTextPart
and EditMessage
for submissions)
|
||
export function MCPUIResourceCarousel(props: MCPUIResourceCarouselProps) { | ||
const { messageId } = useMessageContext(); | ||
const { conversation } = useChatContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No use of useChatContext
allowed here (will cause lag within the message tree)
I was able to resolve significant client rendering delays in this PR by addressing similar concerns based on my latest review: |
Excited for this to get in! |
Looking forward to this, saw the great presentation at the MCP London conference, which pushed me to add an MCP-UI to one of our current servers. Found it a little limited when it comes to UX right now. Once this is in place, it will enable make so many things possible! |
Note
Most lines of code changed in this PR are tests. Advice for reviewer: first toggle out all test files to focus on the main changes in the logic, since there are not that many of them. And only then can you review the tests.
Summary
The past weeks, we shipped the following 3 PRs to integrate MCP UI with LibreChat:
These PRs made it such that whenever a tool response would send back a UI Resource, it would be displayed in the tool response detail section, as such:
This was only a first step.
The goal of the current PR is to allow the LLM to integrate UI Resource elements directly as part of the chat response. Here is what we would have now:
Or this:
Or when the chat is able to aggregate UI Resources from multiple tool responses:
You can now interact with UI Resources. When you click on an action for which the client should be aware, a message is posted to the client and can be picked up by the LLM.
For instance here I clicked on "Add to cart" in one of the product cards:
We see that LibreChat gets the message from the UI Resource, interprets it. For now, it's as if the UI Resource is impersonating the user. I will explain below how we intend to improve it in a follow-up PR.
The LLM then calls another tool call that flows from the user action (click on add to cart on a given product, product added to the cart, cart displayed):
Key changes made
1. Plugin-Based Architecture for MCP UI Resources
2. Data Structure Improvements
3. New Components Created
4. Enhanced Integration Points
5. Better User Experience
6. Comprehensive Testing
The overall improvement transforms MCP UI from a separate attachment display to a seamless, inline experience where AI models can precisely control where and how UI resources appear within their responses.
Samples of improvement follow-ups to come
Change Type
Please delete any irrelevant options.
Testing
Here are the MCP servers I use for my testing:
For the storefront servers, you can enter a prompt like: "looking for men sneakers", and then try to play with the UI elements displayed.
For the
mcp-aharvard
server, you can ask what the weather is like in some city.I've shown above what kind of result you should be seeing.
Test Configuration:
Checklist
Please delete any irrelevant options.