Skip to content

Commit f1b4265

Browse files
authored
Merge pull request modelcontextprotocol#1075 from infoxicator/fix/apps-tool-result
fix: add missing tool result to AppsRenderer
2 parents 6573bf9 + f470fcb commit f1b4265

File tree

7 files changed

+1085
-117
lines changed

7 files changed

+1085
-117
lines changed

client/src/App.tsx

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
hasValidMetaPrefix,
3131
isReservedMetaKey,
3232
} from "@/utils/metaUtils";
33+
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
3334
import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "./lib/auth-types";
3435
import { OAuthStateMachine } from "./lib/oauth-state-machine";
3536
import { cacheToolOutputSchemas } from "./utils/schemaUtils";
@@ -101,6 +102,27 @@ import MetadataTab from "./components/MetadataTab";
101102

102103
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
103104

105+
type PrefilledAppsToolCall = {
106+
id: number;
107+
toolName: string;
108+
params: Record<string, unknown>;
109+
result: CompatibilityCallToolResult;
110+
};
111+
112+
const hasAppResourceUri = (tool: Tool): boolean => {
113+
return Boolean(getToolUiResourceUri(tool));
114+
};
115+
116+
const cloneToolParams = (
117+
source: Record<string, unknown>,
118+
): Record<string, unknown> => {
119+
try {
120+
return structuredClone(source);
121+
} catch {
122+
return { ...source };
123+
}
124+
};
125+
104126
const filterReservedMetadata = (
105127
metadata: Record<string, string>,
106128
): Record<string, string> => {
@@ -137,6 +159,8 @@ const App = () => {
137159
const [tasks, setTasks] = useState<Task[]>([]);
138160
const [toolResult, setToolResult] =
139161
useState<CompatibilityCallToolResult | null>(null);
162+
const [prefilledAppsToolCall, setPrefilledAppsToolCall] =
163+
useState<PrefilledAppsToolCall | null>(null);
140164
const [errors, setErrors] = useState<Record<string, string | null>>({
141165
resources: null,
142166
prompts: null,
@@ -291,6 +315,7 @@ const App = () => {
291315
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
292316
const [nextTaskCursor, setNextTaskCursor] = useState<string | undefined>();
293317
const progressTokenRef = useRef(0);
318+
const prefilledAppsToolCallIdRef = useRef(0);
294319

295320
const [activeTab, setActiveTab] = useState<string>(() => {
296321
const hash = window.location.hash.slice(1);
@@ -977,7 +1002,7 @@ const App = () => {
9771002
params: Record<string, unknown>,
9781003
toolMetadata?: Record<string, unknown>,
9791004
runAsTask?: boolean,
980-
) => {
1005+
): Promise<CompatibilityCallToolResult> => {
9811006
lastToolCallOriginTabRef.current = currentTabRef.current;
9821007

9831008
try {
@@ -1045,7 +1070,7 @@ const App = () => {
10451070
"_meta" in (response as Record<string, unknown>)
10461071
? ((response as { _meta?: Record<string, unknown> })._meta ?? {})
10471072
: undefined;
1048-
setToolResult({
1073+
let latestToolResult: CompatibilityCallToolResult = {
10491074
content: [
10501075
{
10511076
type: "text",
@@ -1056,7 +1081,8 @@ const App = () => {
10561081
...(initialResponseMeta || {}),
10571082
"io.modelcontextprotocol/related-task": { taskId },
10581083
},
1059-
} as CompatibilityCallToolResult);
1084+
};
1085+
setToolResult(latestToolResult);
10601086

10611087
// Polling loop
10621088
let taskCompleted = false;
@@ -1093,20 +1119,22 @@ const App = () => {
10931119
CompatibilityCallToolResultSchema,
10941120
);
10951121
console.log(`Result received for task ${taskId}:`, result);
1096-
setToolResult(result as CompatibilityCallToolResult);
1122+
latestToolResult = result as CompatibilityCallToolResult;
1123+
setToolResult(latestToolResult);
10971124

10981125
// Refresh tasks list to show completed state
10991126
void listTasks();
11001127
} else {
1101-
setToolResult({
1128+
latestToolResult = {
11021129
content: [
11031130
{
11041131
type: "text",
11051132
text: `Task ${taskStatus.status}: ${taskStatus.statusMessage || "No additional information"}`,
11061133
},
11071134
],
11081135
isError: true,
1109-
});
1136+
};
1137+
setToolResult(latestToolResult);
11101138
// Refresh tasks list to show failed/cancelled state
11111139
void listTasks();
11121140
}
@@ -1120,7 +1148,7 @@ const App = () => {
11201148
? ((response as { _meta?: Record<string, unknown> })._meta ??
11211149
{})
11221150
: undefined;
1123-
setToolResult({
1151+
latestToolResult = {
11241152
content: [
11251153
{
11261154
type: "text",
@@ -1131,30 +1159,37 @@ const App = () => {
11311159
...(pollingResponseMeta || {}),
11321160
"io.modelcontextprotocol/related-task": { taskId },
11331161
},
1134-
} as CompatibilityCallToolResult);
1162+
};
1163+
setToolResult(latestToolResult);
11351164
// Refresh tasks list to show progress
11361165
void listTasks();
11371166
}
11381167
} catch (pollingError) {
11391168
console.error("Error polling task status:", pollingError);
1140-
setToolResult({
1169+
latestToolResult = {
11411170
content: [
11421171
{
11431172
type: "text",
11441173
text: `Error polling task status: ${pollingError instanceof Error ? pollingError.message : String(pollingError)}`,
11451174
},
11461175
],
11471176
isError: true,
1148-
});
1177+
};
1178+
setToolResult(latestToolResult);
11491179
taskCompleted = true;
11501180
}
11511181
}
11521182
setIsPollingTask(false);
1183+
// Clear any validation errors since tool execution completed
1184+
setErrors((prev) => ({ ...prev, tools: null }));
1185+
return latestToolResult;
11531186
} else {
1154-
setToolResult(response as CompatibilityCallToolResult);
1187+
const directResult = response as CompatibilityCallToolResult;
1188+
setToolResult(directResult);
1189+
// Clear any validation errors since tool execution completed
1190+
setErrors((prev) => ({ ...prev, tools: null }));
1191+
return directResult;
11551192
}
1156-
// Clear any validation errors since tool execution completed
1157-
setErrors((prev) => ({ ...prev, tools: null }));
11581193
} catch (e) {
11591194
const toolResult: CompatibilityCallToolResult = {
11601195
content: [
@@ -1168,6 +1203,7 @@ const App = () => {
11681203
setToolResult(toolResult);
11691204
// Clear validation errors - tool execution errors are shown in ToolResults
11701205
setErrors((prev) => ({ ...prev, tools: null }));
1206+
return toolResult;
11711207
}
11721208
};
11731209

@@ -1508,7 +1544,26 @@ const App = () => {
15081544
) => {
15091545
clearError("tools");
15101546
setToolResult(null);
1511-
await callTool(name, params, metadata, runAsTask);
1547+
const result = await callTool(
1548+
name,
1549+
params,
1550+
metadata,
1551+
runAsTask,
1552+
);
1553+
const calledTool = tools.find(
1554+
(tool) => tool.name === name,
1555+
);
1556+
if (calledTool && hasAppResourceUri(calledTool)) {
1557+
setPrefilledAppsToolCall({
1558+
id: ++prefilledAppsToolCallIdRef.current,
1559+
toolName: name,
1560+
params: cloneToolParams(params),
1561+
result,
1562+
});
1563+
} else {
1564+
setPrefilledAppsToolCall(null);
1565+
}
1566+
return result;
15121567
}}
15131568
selectedTool={selectedTool}
15141569
setSelectedTool={(tool) => {
@@ -1552,6 +1607,22 @@ const App = () => {
15521607
clearError("tools");
15531608
listTools();
15541609
}}
1610+
callTool={async (
1611+
name: string,
1612+
params: Record<string, unknown>,
1613+
metadata?: Record<string, unknown>,
1614+
runAsTask?: boolean,
1615+
) => {
1616+
clearError("tools");
1617+
setToolResult(null);
1618+
return callTool(name, params, metadata, runAsTask);
1619+
}}
1620+
prefilledToolCall={prefilledAppsToolCall}
1621+
onPrefilledToolCallConsumed={(callId) => {
1622+
setPrefilledAppsToolCall((prev) =>
1623+
prev?.id === callId ? null : prev,
1624+
);
1625+
}}
15551626
error={errors.tools}
15561627
mcpClient={mcpClient}
15571628
onNotification={(notification) => {

0 commit comments

Comments
 (0)