@mcp-ts/sdk 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- import { useRef, useState, useEffect, useCallback, useSyncExternalStore, useMemo } from 'react';
1
+ import { memo, useRef, useState, useEffect, useCallback } from 'react';
2
2
  import { nanoid } from 'nanoid';
3
3
  import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
5
 
5
6
  var __defProp = Object.defineProperty;
6
7
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -926,302 +927,102 @@ function useAppHost(client, iframeRef, options) {
926
927
  }, [client, iframeRef]);
927
928
  return { host, error };
928
929
  }
929
- function useMcpAppIframe({
930
- resourceUri,
931
- sessionId,
932
- toolInput,
933
- toolResult,
934
- toolStatus,
935
- sseClient
930
+ var McpAppRenderer = memo(function McpAppRenderer2({
931
+ metadata,
932
+ input,
933
+ result,
934
+ status,
935
+ sseClient,
936
+ className
936
937
  }) {
937
938
  const iframeRef = useRef(null);
938
939
  const { host, error: hostError } = useAppHost(sseClient, iframeRef);
939
940
  const [isLaunched, setIsLaunched] = useState(false);
940
941
  const [error, setError] = useState(null);
941
- const launchAttemptedRef = useRef(false);
942
- const toolInputSentRef = useRef(false);
943
- const toolResultSentRef = useRef(false);
942
+ const sentInputRef = useRef(false);
943
+ const sentResultRef = useRef(false);
944
+ const lastInputRef = useRef(input);
945
+ const lastResultRef = useRef(result);
946
+ const lastStatusRef = useRef(status);
944
947
  useEffect(() => {
945
- if (hostError) {
946
- setError(hostError);
947
- }
948
- }, [hostError]);
949
- useEffect(() => {
950
- if (!host || !resourceUri || !sessionId || launchAttemptedRef.current) return;
951
- launchAttemptedRef.current = true;
952
- host.launch(resourceUri, sessionId).then(() => {
953
- setIsLaunched(true);
954
- }).catch((err) => {
955
- const error2 = err instanceof Error ? err : new Error(String(err));
956
- setError(error2);
957
- });
958
- }, [host, resourceUri, sessionId]);
948
+ if (!host || !metadata.resourceUri || !metadata.sessionId) return;
949
+ host.launch(metadata.resourceUri, metadata.sessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
950
+ }, [host, metadata.resourceUri, metadata.sessionId]);
959
951
  useEffect(() => {
960
- if (!host || !isLaunched || !toolInput || toolInputSentRef.current) return;
961
- toolInputSentRef.current = true;
962
- host.sendToolInput(toolInput);
963
- }, [host, isLaunched, toolInput]);
964
- useEffect(() => {
965
- if (!host || !isLaunched || toolResult === void 0 || toolResultSentRef.current) return;
966
- if (toolStatus !== "complete") return;
967
- toolResultSentRef.current = true;
968
- const formattedResult = typeof toolResult === "string" ? { content: [{ type: "text", text: toolResult }] } : toolResult;
969
- host.sendToolResult(formattedResult);
970
- }, [host, isLaunched, toolResult, toolStatus]);
971
- return {
972
- iframeRef,
973
- isLaunched,
974
- error
975
- };
976
- }
977
-
978
- // src/client/react/agui-subscriber.ts
979
- function createMcpAppSubscriber(config) {
980
- const eventName = config.eventName || "mcp-apps-ui";
981
- const subscriber = {
982
- // Listen for custom MCP app events from middleware
983
- onCustomEvent: ({ event }) => {
984
- if (event.name === eventName && config.onMcpApp) {
985
- const payload = event.value;
986
- config.onMcpApp(payload);
987
- }
988
- },
989
- // Listen for tool call lifecycle events
990
- onToolCallStartEvent: (params) => {
991
- if (config.onToolCall && params.event.toolCallName) {
992
- config.onToolCall({
993
- toolCallId: params.event.toolCallId || "",
994
- toolName: params.event.toolCallName,
995
- status: "start"
996
- });
997
- }
998
- },
999
- onToolCallArgsEvent: (params) => {
1000
- if (config.onToolCall) {
1001
- const args = params.partialToolCallArgs;
1002
- config.onToolCall({
1003
- toolCallId: params.event.toolCallId || "",
1004
- toolName: params.toolCallName || "",
1005
- // toolCallName is in params, not event
1006
- args,
1007
- status: "args"
1008
- });
1009
- }
1010
- },
1011
- onToolCallEndEvent: (params) => {
1012
- if (config.onToolCall && params.event.toolCallId) {
1013
- config.onToolCall({
1014
- toolCallId: params.event.toolCallId,
1015
- toolName: params.toolCallName || "",
1016
- // toolCallName is in params, not event
1017
- status: "end"
1018
- });
1019
- }
1020
- },
1021
- onToolCallResultEvent: (params) => {
1022
- if (config.onToolCall && params.event.toolCallId) {
1023
- config.onToolCall({
1024
- toolCallId: params.event.toolCallId,
1025
- toolName: "",
1026
- // Not available in result event
1027
- result: params.event.content,
1028
- // content contains the result
1029
- status: "result"
1030
- });
1031
- }
1032
- }
1033
- };
1034
- return subscriber;
1035
- }
1036
- function subscribeMcpAppEvents(agent, config) {
1037
- const subscriber = createMcpAppSubscriber(config);
1038
- const { unsubscribe } = agent.subscribe(subscriber);
1039
- return unsubscribe;
1040
- }
1041
- var McpAppEventManager = class {
1042
- constructor() {
1043
- __publicField(this, "events", /* @__PURE__ */ new Map());
1044
- __publicField(this, "toolCalls", /* @__PURE__ */ new Map());
1045
- __publicField(this, "listeners", /* @__PURE__ */ new Set());
1046
- __publicField(this, "unsubscribe");
1047
- __publicField(this, "cachedSnapshot", {});
1048
- }
1049
- /**
1050
- * Attach to an AG-UI agent
1051
- */
1052
- attach(agent) {
1053
- if (this.unsubscribe) {
1054
- this.unsubscribe();
952
+ if (!host || !isLaunched || !input) return;
953
+ if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
954
+ sentInputRef.current = true;
955
+ lastInputRef.current = input;
956
+ host.sendToolInput(input);
1055
957
  }
1056
- this.unsubscribe = subscribeMcpAppEvents(agent, {
1057
- onMcpApp: (event) => {
1058
- this.events.set(event.toolName, event);
1059
- this.cachedSnapshot = Object.fromEntries(this.events);
1060
- this.notify();
1061
- },
1062
- onToolCall: (event) => {
1063
- if (event.toolCallId) {
1064
- this.toolCalls.set(event.toolCallId, event);
1065
- this.notify();
1066
- }
1067
- }
1068
- });
1069
- }
1070
- /**
1071
- * Detach from the current agent
1072
- */
1073
- detach() {
1074
- if (this.unsubscribe) {
1075
- this.unsubscribe();
1076
- this.unsubscribe = void 0;
1077
- }
1078
- }
1079
- /**
1080
- * Get MCP app event for a specific tool
1081
- */
1082
- getEvent(toolName) {
1083
- return this.events.get(toolName);
1084
- }
1085
- /**
1086
- * Get all MCP app events (cached for useSyncExternalStore)
1087
- */
1088
- getAllEvents() {
1089
- return this.cachedSnapshot;
1090
- }
1091
- /**
1092
- * Get tool call event by ID
1093
- */
1094
- getToolCall(toolCallId) {
1095
- return this.toolCalls.get(toolCallId);
1096
- }
1097
- /**
1098
- * Subscribe to event changes
1099
- */
1100
- subscribe(listener) {
1101
- this.listeners.add(listener);
1102
- return () => this.listeners.delete(listener);
1103
- }
1104
- /**
1105
- * Clear all events
1106
- */
1107
- clear() {
1108
- this.events.clear();
1109
- this.toolCalls.clear();
1110
- this.cachedSnapshot = {};
1111
- this.notify();
1112
- }
1113
- notify() {
1114
- this.listeners.forEach((listener) => listener());
1115
- }
1116
- };
1117
-
1118
- // src/client/react/use-agui-subscriber.ts
1119
- function useAguiSubscriber(agent, config) {
958
+ }, [host, isLaunched, input]);
1120
959
  useEffect(() => {
1121
- if (!agent) return;
1122
- const subscriber = createMcpAppSubscriber(config);
1123
- const { unsubscribe } = agent.subscribe(subscriber);
1124
- return () => unsubscribe();
1125
- }, [agent, config]);
1126
- }
1127
- function useMcpApps(agent, mcpClient) {
1128
- const [manager] = useState(() => new McpAppEventManager());
960
+ if (!host || !isLaunched || result === void 0) return;
961
+ if (status !== "complete") return;
962
+ if (!sentResultRef.current || JSON.stringify(result) !== JSON.stringify(lastResultRef.current)) {
963
+ sentResultRef.current = true;
964
+ lastResultRef.current = result;
965
+ const formattedResult = typeof result === "string" ? { content: [{ type: "text", text: result }] } : result;
966
+ host.sendToolResult(formattedResult);
967
+ }
968
+ }, [host, isLaunched, result, status]);
1129
969
  useEffect(() => {
1130
- if (!agent) {
1131
- manager.detach();
1132
- return;
970
+ if (status === "executing" && lastStatusRef.current !== "executing") {
971
+ sentInputRef.current = false;
972
+ sentResultRef.current = false;
1133
973
  }
1134
- manager.attach(agent);
1135
- return () => manager.detach();
1136
- }, [agent, manager]);
1137
- const agentApps = useSyncExternalStore(
1138
- (callback) => manager.subscribe(callback),
1139
- () => manager.getAllEvents(),
1140
- () => ({})
1141
- // Server-side snapshot
1142
- );
1143
- const apps = useMemo(() => {
1144
- const combined = {};
1145
- if (mcpClient) {
974
+ lastStatusRef.current = status;
975
+ }, [status]);
976
+ const displayError = error || hostError;
977
+ if (displayError) {
978
+ return /* @__PURE__ */ jsxs("div", { className: `p-4 bg-red-900/20 border border-red-700 rounded text-red-200 ${className || ""}`, children: [
979
+ "Error: ",
980
+ displayError.message || String(displayError)
981
+ ] });
982
+ }
983
+ return /* @__PURE__ */ jsxs("div", { className: `w-full border border-gray-700 rounded overflow-hidden bg-white min-h-96 my-2 relative ${className || ""}`, children: [
984
+ /* @__PURE__ */ jsx(
985
+ "iframe",
986
+ {
987
+ ref: iframeRef,
988
+ sandbox: "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads",
989
+ className: "w-full h-full min-h-96",
990
+ style: { height: "auto" },
991
+ title: "MCP App"
992
+ }
993
+ ),
994
+ !isLaunched && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gray-900/50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" }) })
995
+ ] });
996
+ });
997
+ function useMcpApps(mcpClient) {
998
+ const getAppMetadata = useCallback(
999
+ (toolName) => {
1000
+ if (!mcpClient) return void 0;
1001
+ const extractedName = extractToolName(toolName);
1146
1002
  for (const conn of mcpClient.connections) {
1147
1003
  for (const tool of conn.tools) {
1148
- const meta = tool._meta;
1149
- if (!meta?.ui) continue;
1150
- const ui = meta.ui;
1151
- if (typeof ui !== "object" || !ui) continue;
1152
- if (ui.visibility && !ui.visibility.includes("app")) continue;
1153
- const resourceUri = typeof ui.resourceUri === "string" ? ui.resourceUri : typeof ui.uri === "string" ? ui.uri : void 0;
1154
- if (resourceUri) {
1155
- combined[tool.name] = {
1156
- toolCallId: "",
1004
+ const candidateName = extractToolName(tool.name);
1005
+ const resourceUri = tool.mcpApp?.resourceUri ?? tool._meta?.ui?.resourceUri ?? tool._meta?.["ui/resourceUri"];
1006
+ if (resourceUri && candidateName === extractedName) {
1007
+ return {
1008
+ toolName: candidateName,
1157
1009
  resourceUri,
1158
- sessionId: conn.sessionId,
1159
- toolName: tool.name
1010
+ sessionId: conn.sessionId
1160
1011
  };
1161
1012
  }
1162
1013
  }
1163
1014
  }
1164
- }
1165
- for (const [toolName, event] of Object.entries(agentApps)) {
1166
- if (combined[toolName]) {
1167
- combined[toolName] = {
1168
- ...combined[toolName],
1169
- toolCallId: event.toolCallId || combined[toolName].toolCallId,
1170
- result: event.result,
1171
- input: event.input,
1172
- status: event.status
1173
- };
1174
- } else {
1175
- combined[toolName] = event;
1176
- }
1177
- }
1178
- return new Proxy(combined, {
1179
- get(target, prop) {
1180
- if (typeof prop !== "string") return void 0;
1181
- if (prop in target) return target[prop];
1182
- const match = prop.match(/^tool_[^_]+_(.+)$/);
1183
- if (match && match[1] in target) {
1184
- return target[match[1]];
1185
- }
1186
- return void 0;
1187
- },
1188
- // Support Object.entries, Object.keys, etc. by returning base names
1189
- ownKeys(target) {
1190
- return Reflect.ownKeys(target);
1191
- },
1192
- getOwnPropertyDescriptor(target, prop) {
1193
- return Reflect.getOwnPropertyDescriptor(target, prop);
1194
- }
1195
- });
1196
- }, [agentApps, mcpClient]);
1197
- return {
1198
- apps,
1199
- // getApp handles both base and prefixed names transparently via the Proxy
1200
- getApp: (toolName) => apps[toolName],
1201
- clear: () => manager.clear()
1202
- };
1015
+ return void 0;
1016
+ },
1017
+ [mcpClient]
1018
+ );
1019
+ return { getAppMetadata, McpAppRenderer };
1203
1020
  }
1204
- function useToolCallEvents(agent) {
1205
- const [toolCalls, setToolCalls] = useState({});
1206
- useEffect(() => {
1207
- if (!agent) {
1208
- setToolCalls({});
1209
- return;
1210
- }
1211
- const subscriber = createMcpAppSubscriber({
1212
- onToolCall: (event) => {
1213
- setToolCalls((prev) => ({
1214
- ...prev,
1215
- [event.toolCallId]: event
1216
- }));
1217
- }
1218
- });
1219
- const { unsubscribe } = agent.subscribe(subscriber);
1220
- return () => unsubscribe();
1221
- }, [agent]);
1222
- return { toolCalls };
1021
+ function extractToolName(fullName) {
1022
+ const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
1023
+ return match?.[1] || fullName;
1223
1024
  }
1224
1025
 
1225
- export { AppHost, McpAppEventManager, SSEClient, createMcpAppSubscriber, subscribeMcpAppEvents, useAguiSubscriber, useAppHost, useMcp, useMcpAppIframe, useMcpApps, useToolCallEvents };
1026
+ export { AppHost, SSEClient, useAppHost, useMcp, useMcpApps };
1226
1027
  //# sourceMappingURL=react.mjs.map
1227
1028
  //# sourceMappingURL=react.mjs.map