💻 feat: deeper MCP UI integration in the chat UI using plugins

---------

Co-authored-by: Samuel Path <samuel.path@shopify.com>
Co-authored-by: Pierre-Luc Godin <pierreluc.godin@shopify.com>
This commit is contained in:
Pierre-Luc Godin 2025-10-16 14:51:01 -04:00 committed by Dustin Healy
parent b8a149e563
commit 08103ffb22
22 changed files with 1441 additions and 54 deletions

View file

@ -180,7 +180,6 @@ describe('formatToolContent', () => {
{
type: 'text',
text:
'Resource Text: {"items": []}\n' +
'Resource URI: ui://carousel\n' +
'Resource MIME Type: application/json',
},
@ -276,7 +275,6 @@ describe('formatToolContent', () => {
type: 'text',
text:
'Some text\n\n' +
'Resource Text: {"label": "Click me"}\n' +
'Resource URI: ui://button\n' +
'Resource MIME Type: application/json\n\n' +
'Resource URI: file://data.csv',
@ -319,10 +317,7 @@ describe('formatToolContent', () => {
},
{
type: 'text',
text:
'Resource Text: {"type": "line"}\n' +
'Resource URI: ui://graph\n' +
'Resource MIME Type: application/json',
text: 'Resource URI: ui://graph\n' + 'Resource MIME Type: application/json',
},
]);
expect(artifacts).toEqual({
@ -399,7 +394,6 @@ describe('formatToolContent', () => {
type: 'text',
text:
'Middle section\n\n' +
'Resource Text: {"type": "bar"}\n' +
'Resource URI: ui://chart\n' +
'Resource MIME Type: application/json\n\n' +
'Resource URI: https://api.example.com/data',

View file

@ -132,12 +132,14 @@ export function formatToolContent(
},
resource: (item) => {
if (item.resource.uri.startsWith('ui://')) {
const isUiResource = item.resource.uri.startsWith('ui://');
if (isUiResource) {
uiResources.push(item.resource as UIResource);
}
const resourceText = [];
if ('text' in item.resource && item.resource.text != null && item.resource.text) {
if (!isUiResource && 'text' in item.resource && item.resource.text) {
resourceText.push(`Resource Text: ${item.resource.text}`);
}
if (item.resource.uri.length) {
@ -165,10 +167,14 @@ export function formatToolContent(
}
let artifacts: t.Artifacts = undefined;
if (imageUrls.length || uiResources.length) {
if (imageUrls.length) {
artifacts = { content: imageUrls };
}
if (uiResources.length) {
artifacts = {
...(imageUrls.length && { content: imageUrls }),
...(uiResources.length && { [Tools.ui_resources]: { data: uiResources } }),
...artifacts,
[Tools.ui_resources]: { data: uiResources },
};
}

View file

@ -90,11 +90,7 @@ export type Provider =
export type FormattedContent =
| {
type: 'text';
metadata?: {
type: string;
data: UIResource[];
};
text?: string;
text: string;
}
| {
type: 'image';

View file

@ -12,7 +12,7 @@ export function normalizeServerName(serverName: string): string {
}
/** Replace non-matching characters with underscores.
This preserves the general structure while ensuring compatibility.
This preserves the general structure while ensuring compatibility.
Trims leading/trailing underscores
*/
const normalized = serverName.replace(/[^a-zA-Z0-9_.-]/g, '_').replace(/^_+|_+$/g, '');