@mcp-ts/sdk 1.3.9 → 1.3.10

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.
package/README.md CHANGED
@@ -270,7 +270,6 @@ export function ToolRenderer() {
270
270
  name: "*",
271
271
  render: ({ name, args, result, status }) => (
272
272
  <McpAppRenderer
273
- mcpClient={mcpClient}
274
273
  name={name}
275
274
  input={args}
276
275
  result={result}
@@ -172,10 +172,11 @@ declare function useMcp(options: UseMcpOptions): McpClient$1;
172
172
  /**
173
173
  * Hook to host an MCP App in a React component
174
174
  *
175
- * Optimized for instant loading:
176
- * - Creates AppHost synchronously
177
- * - Starts bridge connection immediately
178
- * - Returns host before connection completes (ready to call launch)
175
+ * Initialization is async but optimized for instant availability:
176
+ * - Constructor runs synchronously (sandbox + bridge handler setup)
177
+ * - Host is set in state immediately so launch() can be called right away
178
+ * - start() is a lightweight no-op reserved for future async pre-init work
179
+ * - The real async work (iframe load, bridge connect) happens inside launch()
179
180
  *
180
181
  * @param client - Connected SSEClient instance
181
182
  * @param iframeRef - Reference to the iframe element
@@ -222,8 +223,8 @@ interface McpAppMetadata {
222
223
  resourceUri: string;
223
224
  sessionId: string;
224
225
  }
226
+ /** Props for {@link useMcpApps}'s `McpAppRenderer` (client is supplied via the hook). */
225
227
  interface McpAppRendererProps {
226
- mcpClient: McpClient | null;
227
228
  name: string;
228
229
  input?: Record<string, unknown>;
229
230
  result?: unknown;
@@ -232,32 +233,13 @@ interface McpAppRendererProps {
232
233
  className?: string;
233
234
  }
234
235
  /**
235
- * Simple hook to get MCP app metadata
236
+ * Helpers scoped to one `mcpClient`. Pass the client here once; `McpAppRenderer` only needs per-tool props (`name`, `input`, `result`, `status`).
236
237
  *
237
- * @param mcpClient - The MCP client from useMcp() or context
238
- * @returns Object with getAppMetadata function and McpAppRenderer component
239
- *
240
- * @example
241
- * ```tsx
242
- * function ToolRenderer(props) {
243
- * const { getAppMetadata, McpAppRenderer } = useMcpApps(mcpClient);
244
- * const metadata = getAppMetadata(props.name);
245
- *
246
- * if (!metadata) return null;
247
- * return (
248
- * <McpAppRenderer
249
- * metadata={metadata}
250
- * input={props.args}
251
- * result={props.result}
252
- * status={props.status}
253
- * />
254
- * );
255
- * }
256
- * ```
238
+ * @param mcpClient - From `useMcp()` or context (for example `useMcpContext()`).
257
239
  */
258
240
  declare function useMcpApps(mcpClient: McpClient | null): {
259
241
  getAppMetadata: (toolName: string) => McpAppMetadata | undefined;
260
242
  McpAppRenderer: React$1.NamedExoticComponent<McpAppRendererProps>;
261
243
  };
262
244
 
263
- export { AppHost, type McpClient$1 as McpClient, type McpConnection, McpConnectionEvent, McpConnectionState, SSEClient, ToolInfo, type UseMcpOptions, useAppHost, useMcp, useMcpApps };
245
+ export { AppHost, type McpAppMetadata, type McpAppRendererProps, type McpClient$1 as McpClient, type McpConnection, McpConnectionEvent, McpConnectionState, SSEClient, ToolInfo, type UseMcpOptions, useAppHost, useMcp, useMcpApps };
@@ -172,10 +172,11 @@ declare function useMcp(options: UseMcpOptions): McpClient$1;
172
172
  /**
173
173
  * Hook to host an MCP App in a React component
174
174
  *
175
- * Optimized for instant loading:
176
- * - Creates AppHost synchronously
177
- * - Starts bridge connection immediately
178
- * - Returns host before connection completes (ready to call launch)
175
+ * Initialization is async but optimized for instant availability:
176
+ * - Constructor runs synchronously (sandbox + bridge handler setup)
177
+ * - Host is set in state immediately so launch() can be called right away
178
+ * - start() is a lightweight no-op reserved for future async pre-init work
179
+ * - The real async work (iframe load, bridge connect) happens inside launch()
179
180
  *
180
181
  * @param client - Connected SSEClient instance
181
182
  * @param iframeRef - Reference to the iframe element
@@ -222,8 +223,8 @@ interface McpAppMetadata {
222
223
  resourceUri: string;
223
224
  sessionId: string;
224
225
  }
226
+ /** Props for {@link useMcpApps}'s `McpAppRenderer` (client is supplied via the hook). */
225
227
  interface McpAppRendererProps {
226
- mcpClient: McpClient | null;
227
228
  name: string;
228
229
  input?: Record<string, unknown>;
229
230
  result?: unknown;
@@ -232,32 +233,13 @@ interface McpAppRendererProps {
232
233
  className?: string;
233
234
  }
234
235
  /**
235
- * Simple hook to get MCP app metadata
236
+ * Helpers scoped to one `mcpClient`. Pass the client here once; `McpAppRenderer` only needs per-tool props (`name`, `input`, `result`, `status`).
236
237
  *
237
- * @param mcpClient - The MCP client from useMcp() or context
238
- * @returns Object with getAppMetadata function and McpAppRenderer component
239
- *
240
- * @example
241
- * ```tsx
242
- * function ToolRenderer(props) {
243
- * const { getAppMetadata, McpAppRenderer } = useMcpApps(mcpClient);
244
- * const metadata = getAppMetadata(props.name);
245
- *
246
- * if (!metadata) return null;
247
- * return (
248
- * <McpAppRenderer
249
- * metadata={metadata}
250
- * input={props.args}
251
- * result={props.result}
252
- * status={props.status}
253
- * />
254
- * );
255
- * }
256
- * ```
238
+ * @param mcpClient - From `useMcp()` or context (for example `useMcpContext()`).
257
239
  */
258
240
  declare function useMcpApps(mcpClient: McpClient | null): {
259
241
  getAppMetadata: (toolName: string) => McpAppMetadata | undefined;
260
242
  McpAppRenderer: React$1.NamedExoticComponent<McpAppRendererProps>;
261
243
  };
262
244
 
263
- export { AppHost, type McpClient$1 as McpClient, type McpConnection, McpConnectionEvent, McpConnectionState, SSEClient, ToolInfo, type UseMcpOptions, useAppHost, useMcp, useMcpApps };
245
+ export { AppHost, type McpAppMetadata, type McpAppRendererProps, type McpClient$1 as McpClient, type McpConnection, McpConnectionEvent, McpConnectionState, SSEClient, ToolInfo, type UseMcpOptions, useAppHost, useMcp, useMcpApps };
@@ -277,6 +277,7 @@ function useMcp(options) {
277
277
  "disconnected"
278
278
  );
279
279
  const [isInitializing, setIsInitializing] = react.useState(false);
280
+ const [sseClient, setSseClient] = react.useState(null);
280
281
  react.useEffect(() => {
281
282
  isMountedRef.current = true;
282
283
  const clientOptions = {
@@ -299,6 +300,7 @@ function useMcp(options) {
299
300
  };
300
301
  const client = new SSEClient(clientOptions);
301
302
  clientRef.current = client;
303
+ setSseClient(client);
302
304
  if (autoConnect) {
303
305
  client.connect();
304
306
  if (autoInitialize) {
@@ -532,29 +534,54 @@ function useMcp(options) {
532
534
  },
533
535
  [getConnection]
534
536
  );
535
- return {
536
- connections,
537
- status,
538
- isInitializing,
539
- connect,
540
- disconnect,
541
- getConnection,
542
- getConnectionByServerId,
543
- isServerConnected,
544
- getTools,
545
- refresh,
546
- connectSSE,
547
- disconnectSSE,
548
- finishAuth,
549
- resumeAuth,
550
- callTool,
551
- listTools,
552
- listPrompts,
553
- getPrompt,
554
- listResources,
555
- readResource,
556
- sseClient: clientRef.current
557
- };
537
+ return react.useMemo(
538
+ () => ({
539
+ connections,
540
+ status,
541
+ isInitializing,
542
+ connect,
543
+ disconnect,
544
+ getConnection,
545
+ getConnectionByServerId,
546
+ isServerConnected,
547
+ getTools,
548
+ refresh,
549
+ connectSSE,
550
+ disconnectSSE,
551
+ finishAuth,
552
+ resumeAuth,
553
+ callTool,
554
+ listTools,
555
+ listPrompts,
556
+ getPrompt,
557
+ listResources,
558
+ readResource,
559
+ sseClient
560
+ }),
561
+ [
562
+ connections,
563
+ status,
564
+ isInitializing,
565
+ connect,
566
+ disconnect,
567
+ getConnection,
568
+ getConnectionByServerId,
569
+ isServerConnected,
570
+ getTools,
571
+ refresh,
572
+ connectSSE,
573
+ disconnectSSE,
574
+ finishAuth,
575
+ resumeAuth,
576
+ callTool,
577
+ listTools,
578
+ listPrompts,
579
+ getPrompt,
580
+ listResources,
581
+ readResource,
582
+ sseClient
583
+ ]
584
+ );
558
585
  }
559
586
  var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
560
587
  var SANDBOX_PERMISSIONS = [
@@ -886,37 +913,19 @@ function useAppHost(client, iframeRef, options) {
886
913
  }, [client, iframeRef]);
887
914
  return { host, error };
888
915
  }
889
- var McpAppRenderer = react.memo(function McpAppRenderer2({
890
- mcpClient,
916
+ var McpAppView = react.memo(function McpAppView2({
917
+ clientRef,
891
918
  name,
892
919
  input,
893
920
  result,
894
921
  status,
895
922
  className
896
923
  }) {
897
- const getAppMetadata = react.useCallback(() => {
898
- if (!mcpClient) return void 0;
899
- const extractedName = extractToolName(name);
900
- for (const conn of mcpClient.connections) {
901
- for (const tool of conn.tools) {
902
- const candidateName = extractToolName(tool.name);
903
- const resourceUri = tool.mcpApp?.resourceUri ?? tool._meta?.ui?.resourceUri ?? tool._meta?.["ui/resourceUri"];
904
- if (resourceUri && candidateName === extractedName) {
905
- return {
906
- toolName: candidateName,
907
- resourceUri,
908
- sessionId: conn.sessionId
909
- };
910
- }
911
- }
912
- }
913
- return void 0;
914
- }, [mcpClient, name]);
915
- const metadata = getAppMetadata();
924
+ const mcpClient = clientRef.current;
925
+ const metadata = getMcpAppMetadata(mcpClient, name);
916
926
  const sseClient = mcpClient?.sseClient ?? null;
917
- if (!metadata || !sseClient) {
918
- return null;
919
- }
927
+ const resourceUri = metadata?.resourceUri;
928
+ const appSessionId = metadata?.sessionId;
920
929
  const iframeRef = react.useRef(null);
921
930
  const { host, error: hostError } = useAppHost(sseClient, iframeRef);
922
931
  const [isLaunched, setIsLaunched] = react.useState(false);
@@ -927,19 +936,23 @@ var McpAppRenderer = react.memo(function McpAppRenderer2({
927
936
  const lastResultRef = react.useRef(result);
928
937
  const lastStatusRef = react.useRef(status);
929
938
  react.useEffect(() => {
930
- if (!host || !metadata.resourceUri || !metadata.sessionId) return;
931
- host.launch(metadata.resourceUri, metadata.sessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
932
- }, [host, metadata.resourceUri, metadata.sessionId]);
939
+ setIsLaunched(false);
940
+ setError(null);
941
+ }, [resourceUri, appSessionId]);
942
+ react.useEffect(() => {
943
+ if (!host || !resourceUri || !appSessionId) return;
944
+ host.launch(resourceUri, appSessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
945
+ }, [host, resourceUri, appSessionId]);
933
946
  react.useEffect(() => {
934
- if (!host || !isLaunched || !input) return;
947
+ if (!host || !isLaunched || !resourceUri || !appSessionId || !input) return;
935
948
  if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
936
949
  sentInputRef.current = true;
937
950
  lastInputRef.current = input;
938
951
  host.sendToolInput(input);
939
952
  }
940
- }, [host, isLaunched, input]);
953
+ }, [host, isLaunched, input, resourceUri, appSessionId, name]);
941
954
  react.useEffect(() => {
942
- if (!host || !isLaunched || result === void 0) return;
955
+ if (!host || !isLaunched || !resourceUri || !appSessionId || result === void 0) return;
943
956
  if (status !== "complete") return;
944
957
  if (!sentResultRef.current || JSON.stringify(result) !== JSON.stringify(lastResultRef.current)) {
945
958
  sentResultRef.current = true;
@@ -947,7 +960,7 @@ var McpAppRenderer = react.memo(function McpAppRenderer2({
947
960
  const formattedResult = typeof result === "string" ? { content: [{ type: "text", text: result }] } : result;
948
961
  host.sendToolResult(formattedResult);
949
962
  }
950
- }, [host, isLaunched, result, status]);
963
+ }, [host, isLaunched, result, status, resourceUri, appSessionId, name]);
951
964
  react.useEffect(() => {
952
965
  if (status === "executing" && lastStatusRef.current !== "executing") {
953
966
  sentInputRef.current = false;
@@ -955,6 +968,9 @@ var McpAppRenderer = react.memo(function McpAppRenderer2({
955
968
  }
956
969
  lastStatusRef.current = status;
957
970
  }, [status]);
971
+ if (!metadata || !sseClient) {
972
+ return null;
973
+ }
958
974
  const displayError = error || hostError;
959
975
  if (displayError) {
960
976
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `p-4 bg-red-900/20 border border-red-700 rounded text-red-200 ${className || ""}`, children: [
@@ -977,33 +993,43 @@ var McpAppRenderer = react.memo(function McpAppRenderer2({
977
993
  ] });
978
994
  });
979
995
  function useMcpApps(mcpClient) {
996
+ const clientRef = react.useRef(mcpClient);
997
+ clientRef.current = mcpClient;
980
998
  const getAppMetadata = react.useCallback(
981
- (toolName) => {
982
- if (!mcpClient) return void 0;
983
- const extractedName = extractToolName(toolName);
984
- for (const conn of mcpClient.connections) {
985
- for (const tool of conn.tools) {
986
- const candidateName = extractToolName(tool.name);
987
- const resourceUri = tool.mcpApp?.resourceUri ?? tool._meta?.ui?.resourceUri ?? tool._meta?.["ui/resourceUri"];
988
- if (resourceUri && candidateName === extractedName) {
989
- return {
990
- toolName: candidateName,
991
- resourceUri,
992
- sessionId: conn.sessionId
993
- };
994
- }
995
- }
996
- }
997
- return void 0;
998
- },
999
- [mcpClient]
999
+ (toolName) => getMcpAppMetadata(clientRef.current, toolName),
1000
+ []
1000
1001
  );
1002
+ const McpAppRenderer = react.useMemo(() => {
1003
+ const Renderer = react.memo(function McpAppRenderer2(props) {
1004
+ return /* @__PURE__ */ jsxRuntime.jsx(McpAppView, { clientRef, ...props });
1005
+ });
1006
+ Renderer.displayName = "McpAppRenderer";
1007
+ return Renderer;
1008
+ }, []);
1001
1009
  return { getAppMetadata, McpAppRenderer };
1002
1010
  }
1003
1011
  function extractToolName(fullName) {
1004
1012
  const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
1005
1013
  return match?.[1] || fullName;
1006
1014
  }
1015
+ function getMcpAppMetadata(mcpClient, toolName) {
1016
+ if (!mcpClient) return void 0;
1017
+ const extractedName = extractToolName(toolName);
1018
+ for (const conn of mcpClient.connections) {
1019
+ for (const tool of conn.tools) {
1020
+ const candidateName = extractToolName(tool.name);
1021
+ const resourceUri = tool.mcpApp?.resourceUri ?? tool._meta?.ui?.resourceUri ?? tool._meta?.["ui/resourceUri"];
1022
+ if (resourceUri && candidateName === extractedName) {
1023
+ return {
1024
+ toolName: candidateName,
1025
+ resourceUri,
1026
+ sessionId: conn.sessionId
1027
+ };
1028
+ }
1029
+ }
1030
+ }
1031
+ return void 0;
1032
+ }
1007
1033
 
1008
1034
  exports.AppHost = AppHost;
1009
1035
  exports.SSEClient = SSEClient;