fix: prevent infinite loop of tool-input notifications in MCP Apps (#6374)
This commit is contained in:
@@ -86,6 +86,21 @@ export function useSandboxBridge(options: SandboxBridgeOptions): SandboxBridgeRe
|
|||||||
|
|
||||||
case 'ui/notifications/initialized':
|
case 'ui/notifications/initialized':
|
||||||
isGuestInitializedRef.current = true;
|
isGuestInitializedRef.current = true;
|
||||||
|
// Send any pending tool data that arrived before initialization
|
||||||
|
if (toolInput) {
|
||||||
|
sendToSandbox({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'ui/notifications/tool-input',
|
||||||
|
params: { arguments: toolInput.arguments },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (toolResult) {
|
||||||
|
sendToSandbox({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'ui/notifications/tool-result',
|
||||||
|
params: toolResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ui/notifications/size-changed': {
|
case 'ui/notifications/size-changed': {
|
||||||
@@ -163,7 +178,16 @@ export function useSandboxBridge(options: SandboxBridgeOptions): SandboxBridgeRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[resourceHtml, resourceCsp, resolvedTheme, sendToSandbox, onMcpRequest, onSizeChanged]
|
[
|
||||||
|
resourceHtml,
|
||||||
|
resourceCsp,
|
||||||
|
resolvedTheme,
|
||||||
|
sendToSandbox,
|
||||||
|
onMcpRequest,
|
||||||
|
onSizeChanged,
|
||||||
|
toolInput,
|
||||||
|
toolResult,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ToolIconWithStatus, ToolCallStatus } from './ToolCallStatusIndicator';
|
import { ToolIconWithStatus, ToolCallStatus } from './ToolCallStatusIndicator';
|
||||||
import { getToolCallIcon } from '../utils/toolIconMapping';
|
import { getToolCallIcon } from '../utils/toolIconMapping';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { ToolCallArguments, ToolCallArgumentValue } from './ToolCallArguments';
|
import { ToolCallArguments, ToolCallArgumentValue } from './ToolCallArguments';
|
||||||
import MarkdownContent from './MarkdownContent';
|
import MarkdownContent from './MarkdownContent';
|
||||||
@@ -71,12 +71,19 @@ function isEmbeddedResource(content: Content): content is EmbeddedResource {
|
|||||||
return 'resource' in content && typeof (content as Record<string, unknown>).resource === 'object';
|
return 'resource' in content && typeof (content as Record<string, unknown>).resource === 'object';
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderMCPApp(
|
interface McpAppWrapperProps {
|
||||||
toolRequest: ToolRequestMessageContent,
|
toolRequest: ToolRequestMessageContent;
|
||||||
toolResponse: ToolResponseMessageContent | undefined,
|
toolResponse?: ToolResponseMessageContent;
|
||||||
sessionId: string,
|
sessionId: string;
|
||||||
append?: (value: string) => void
|
append?: (value: string) => void;
|
||||||
): React.ReactNode {
|
}
|
||||||
|
|
||||||
|
function McpAppWrapper({
|
||||||
|
toolRequest,
|
||||||
|
toolResponse,
|
||||||
|
sessionId,
|
||||||
|
append,
|
||||||
|
}: McpAppWrapperProps): React.ReactNode {
|
||||||
const requestWithMeta = toolRequest as ToolRequestWithMeta;
|
const requestWithMeta = toolRequest as ToolRequestWithMeta;
|
||||||
let resourceUri = requestWithMeta._meta?.['ui/resourceUri'];
|
let resourceUri = requestWithMeta._meta?.['ui/resourceUri'];
|
||||||
|
|
||||||
@@ -87,24 +94,37 @@ function maybeRenderMCPApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resourceUri) return null;
|
const extensionName =
|
||||||
if (requestWithMeta.toolCall.status !== 'success') return null;
|
requestWithMeta.toolCall.status === 'success'
|
||||||
|
? requestWithMeta.toolCall.value.name.split('__')[0]
|
||||||
|
: '';
|
||||||
|
|
||||||
const extensionName = requestWithMeta.toolCall.value.name.split('__')[0];
|
const toolArguments =
|
||||||
|
requestWithMeta.toolCall.status === 'success'
|
||||||
|
? requestWithMeta.toolCall.value.arguments
|
||||||
|
: undefined;
|
||||||
|
|
||||||
let toolResult: CallToolResponse | undefined;
|
// Memoize toolInput to prevent unnecessary re-renders
|
||||||
if (toolResponse) {
|
const toolInput = useMemo(() => ({ arguments: toolArguments || {} }), [toolArguments]);
|
||||||
|
|
||||||
|
// Memoize toolResult to prevent unnecessary re-renders
|
||||||
|
const toolResult = useMemo(() => {
|
||||||
|
if (!toolResponse) return undefined;
|
||||||
const resultWithMeta = toolResponse.toolResult as ToolResultWithMeta;
|
const resultWithMeta = toolResponse.toolResult as ToolResultWithMeta;
|
||||||
if (resultWithMeta?.status === 'success' && resultWithMeta.value) {
|
if (resultWithMeta?.status === 'success' && resultWithMeta.value) {
|
||||||
toolResult = resultWithMeta.value;
|
return resultWithMeta.value;
|
||||||
}
|
}
|
||||||
}
|
return undefined;
|
||||||
|
}, [toolResponse]);
|
||||||
|
|
||||||
|
if (!resourceUri) return null;
|
||||||
|
if (requestWithMeta.toolCall.status !== 'success') return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<McpAppRenderer
|
<McpAppRenderer
|
||||||
resourceUri={resourceUri}
|
resourceUri={resourceUri}
|
||||||
toolInput={{ arguments: requestWithMeta.toolCall.value.arguments || {} }}
|
toolInput={toolInput}
|
||||||
toolResult={toolResult}
|
toolResult={toolResult}
|
||||||
extensionName={extensionName}
|
extensionName={extensionName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
@@ -181,7 +201,14 @@ export default function ToolCallWithResponse({
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{sessionId && maybeRenderMCPApp(toolRequest, toolResponse, sessionId, append)}
|
{sessionId && (
|
||||||
|
<McpAppWrapper
|
||||||
|
toolRequest={toolRequest}
|
||||||
|
toolResponse={toolResponse}
|
||||||
|
sessionId={sessionId}
|
||||||
|
append={append}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user