@assistant-ui/react-mcp 0.1.14 → 0.1.16

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.
Files changed (52) hide show
  1. package/dist/auth/createOAuthProvider.d.ts +7 -1
  2. package/dist/auth/createOAuthProvider.d.ts.map +1 -1
  3. package/dist/auth/createOAuthProvider.js +115 -31
  4. package/dist/auth/createOAuthProvider.js.map +1 -1
  5. package/dist/auth/types.d.ts +1 -0
  6. package/dist/auth/types.d.ts.map +1 -1
  7. package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
  8. package/dist/hooks/useMcpOAuthCallback.js +5 -6
  9. package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
  10. package/dist/primitives/server/McpServerIcon.js.map +1 -1
  11. package/dist/primitives/server/McpServerOAuthLink.js.map +1 -1
  12. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  13. package/dist/resources/McpManagerResource.js +4 -2
  14. package/dist/resources/McpManagerResource.js.map +1 -1
  15. package/dist/resources/McpServerResource.d.ts +2 -1
  16. package/dist/resources/McpServerResource.d.ts.map +1 -1
  17. package/dist/resources/McpServerResource.js +107 -21
  18. package/dist/resources/McpServerResource.js.map +1 -1
  19. package/dist/resources/storage/McpLocalStorage.d.ts +7 -0
  20. package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
  21. package/dist/resources/storage/McpLocalStorage.js +132 -37
  22. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  23. package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
  24. package/dist/resources/storage/McpMemoryStorage.js +9 -6
  25. package/dist/resources/storage/McpMemoryStorage.js.map +1 -1
  26. package/dist/resources/storage/types.d.ts +12 -0
  27. package/dist/resources/storage/types.d.ts.map +1 -1
  28. package/dist/utils/createMcpId.d.ts +9 -0
  29. package/dist/utils/createMcpId.d.ts.map +1 -0
  30. package/dist/utils/createMcpId.js +11 -0
  31. package/dist/utils/createMcpId.js.map +1 -0
  32. package/dist/utils/invokeMcpCallback.d.ts.map +1 -1
  33. package/dist/utils/invokeMcpCallback.js +2 -12
  34. package/dist/utils/invokeMcpCallback.js.map +1 -1
  35. package/package.json +8 -8
  36. package/src/auth/createOAuthProvider.test.ts +407 -2
  37. package/src/auth/createOAuthProvider.ts +171 -40
  38. package/src/auth/types.ts +1 -0
  39. package/src/hooks/useMcpOAuthCallback.test.ts +74 -1
  40. package/src/hooks/useMcpOAuthCallback.tsx +11 -8
  41. package/src/resources/McpManagerResource.test.ts +128 -0
  42. package/src/resources/McpManagerResource.ts +4 -5
  43. package/src/resources/McpServerResource.test.ts +420 -18
  44. package/src/resources/McpServerResource.ts +148 -27
  45. package/src/resources/storage/McpLocalStorage.test.ts +71 -1
  46. package/src/resources/storage/McpLocalStorage.ts +69 -47
  47. package/src/resources/storage/McpMemoryStorage.test.ts +99 -0
  48. package/src/resources/storage/McpMemoryStorage.ts +23 -17
  49. package/src/resources/storage/types.ts +12 -0
  50. package/src/utils/createMcpId.test.ts +25 -0
  51. package/src/utils/createMcpId.ts +10 -0
  52. package/src/utils/invokeMcpCallback.ts +3 -21
@@ -1,12 +1,40 @@
1
1
  import { assertValidServerId } from "../utils/serverId.js";
2
- import { createOAuthProvider } from "../auth/createOAuthProvider.js";
2
+ import { createMcpId } from "../utils/createMcpId.js";
3
+ import { clearOAuthProviderAuthState, createOAuthProvider } from "../auth/createOAuthProvider.js";
3
4
  import { buildHeaders } from "../auth/buildHeaders.js";
4
5
  import { validateElicitationContent } from "./validateElicitationContent.js";
5
6
  import { useEffect, useEffectEvent, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
6
- import { resource } from "@assistant-ui/tap";
7
+ import { resource, useResource, withKey } from "@assistant-ui/tap";
7
8
  import { Client, StreamableHTTPClientTransport, UnauthorizedError } from "@modelcontextprotocol/client";
8
9
  //#region src/resources/McpServerResource.ts
9
- const useMcpServerResource = (props) => {
10
+ const getConnectionDependencies = (props) => {
11
+ const auth = props.auth;
12
+ const authDependencies = auth.type === "bearer" ? [
13
+ auth.type,
14
+ auth.token,
15
+ props.storage.scopeId
16
+ ] : auth.type === "oauth" ? [
17
+ auth.type,
18
+ auth.scopes?.length,
19
+ ...auth.scopes ?? [],
20
+ auth.authorizationEndpoint,
21
+ auth.tokenEndpoint,
22
+ auth.registrationEndpoint,
23
+ auth.clientId,
24
+ auth.clientSecret,
25
+ props.storage.scopeId
26
+ ] : [auth.type];
27
+ return [
28
+ props.id,
29
+ props.url,
30
+ ...authDependencies,
31
+ props.redirectUri,
32
+ props.cache?.defaultTtlMs,
33
+ props.elicitation !== false
34
+ ];
35
+ };
36
+ const areConnectionDependenciesEqual = (left, right) => left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
37
+ const useMcpServerResourceInstance = (props) => {
10
38
  assertValidServerId(props.id);
11
39
  const [connectionState, setConnectionState] = useState("disconnected");
12
40
  const [tools, setTools] = useState([]);
@@ -16,8 +44,8 @@ const useMcpServerResource = (props) => {
16
44
  const clientRef = useRef(null);
17
45
  const transportRef = useRef(null);
18
46
  const pendingTransportRef = useRef(null);
19
- const transportCloseQueueRef = useRef(Promise.resolve());
20
47
  const connectionGenerationRef = useRef(0);
48
+ const pendingAuthValidationRef = useRef(null);
21
49
  const elicitationResolversRef = useRef(/* @__PURE__ */ new Map());
22
50
  const pendingDisposalRef = useRef(null);
23
51
  const mountedRef = useRef(true);
@@ -27,10 +55,10 @@ const useMcpServerResource = (props) => {
27
55
  } catch {}
28
56
  };
29
57
  const closeQueuedTransports = (transports) => {
30
- const task = transportCloseQueueRef.current.then(async () => {
58
+ const task = props.transportCloseQueueRef.current.then(async () => {
31
59
  await Promise.all(transports.map(closeTransportSafely));
32
60
  });
33
- transportCloseQueueRef.current = task;
61
+ props.transportCloseQueueRef.current = task;
34
62
  return task;
35
63
  };
36
64
  const closePendingTransport = async () => {
@@ -58,15 +86,17 @@ const useMcpServerResource = (props) => {
58
86
  const cancelPendingElicitations = () => {
59
87
  for (const [id_1] of elicitationResolversRef.current) resolvePendingElicitation(id_1, { action: "cancel" });
60
88
  };
61
- const closeTransports = async () => {
89
+ const detachTransports = () => {
62
90
  cancelPendingElicitations();
63
91
  const pendingTransport = pendingTransportRef.current;
64
92
  const activeTransport = transportRef.current;
65
93
  pendingTransportRef.current = null;
66
94
  transportRef.current = null;
67
95
  clientRef.current = null;
68
- const transports_0 = new Set([pendingTransport, activeTransport].filter((transport_1) => transport_1 !== null));
69
- await closeQueuedTransports([...transports_0]);
96
+ return [...new Set([pendingTransport, activeTransport].filter((transport_1) => transport_1 !== null))];
97
+ };
98
+ const closeTransports = async () => {
99
+ await closeQueuedTransports(detachTransports());
70
100
  };
71
101
  const isCurrentConnection = (generation) => mountedRef.current && generation === connectionGenerationRef.current;
72
102
  const createInterruptedAuthError = (cause) => new Error(`MCP server "${props.id}" authorization was interrupted before completion.`, cause === void 0 ? void 0 : { cause });
@@ -149,7 +179,7 @@ const useMcpServerResource = (props) => {
149
179
  if (!isCurrentConnection(generation_1)) return Promise.resolve({ action: "cancel" });
150
180
  if (!("requestedSchema" in request.params)) return Promise.resolve({ action: "cancel" });
151
181
  const { message, requestedSchema } = request.params;
152
- const id_2 = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
182
+ const id_2 = createMcpId();
153
183
  const promise_0 = new Promise((resolve) => {
154
184
  const onAbort = () => {
155
185
  resolvePendingElicitation(id_2, { action: "cancel" });
@@ -225,6 +255,36 @@ const useMcpServerResource = (props) => {
225
255
  await closeTransports();
226
256
  });
227
257
  const doCompleteAuth = useEffectEvent(async (callbackUrl) => {
258
+ const validationGeneration = connectionGenerationRef.current;
259
+ const url_0 = new URL(callbackUrl);
260
+ const state = url_0.searchParams.get("state");
261
+ if (!state) throw new Error("missing \"state\" parameter");
262
+ let pendingAuthValidation = pendingAuthValidationRef.current;
263
+ if (!pendingAuthValidation) {
264
+ let resolve_0;
265
+ pendingAuthValidation = {
266
+ count: 0,
267
+ promise: new Promise((resolvePromise) => {
268
+ resolve_0 = resolvePromise;
269
+ }),
270
+ resolve: resolve_0
271
+ };
272
+ pendingAuthValidationRef.current = pendingAuthValidation;
273
+ }
274
+ pendingAuthValidation.count += 1;
275
+ try {
276
+ const persisted = await props.storage.loadAuthState(props.id);
277
+ if (!isCurrentConnection(validationGeneration)) throw createInterruptedAuthError();
278
+ if (!persisted?.state) throw new Error("no pending OAuth authorization request for this server");
279
+ if (persisted.state !== state) throw new Error("OAuth state does not match the authorization request");
280
+ if (!url_0.searchParams.get("code") && !url_0.searchParams.get("error")) throw new Error("missing authorization code in callback URL");
281
+ } finally {
282
+ pendingAuthValidation.count -= 1;
283
+ if (pendingAuthValidation.count === 0) {
284
+ pendingAuthValidationRef.current = null;
285
+ pendingAuthValidation.resolve();
286
+ }
287
+ }
228
288
  const generation_3 = ++connectionGenerationRef.current;
229
289
  cancelPendingElicitations();
230
290
  await closePendingTransport();
@@ -232,8 +292,6 @@ const useMcpServerResource = (props) => {
232
292
  setConnectionState("authPending");
233
293
  setLastError(null);
234
294
  try {
235
- const code = new URL(callbackUrl).searchParams.get("code");
236
- if (!code) throw new Error("missing authorization code in callback URL");
237
295
  let transport_4 = transportRef.current;
238
296
  if (!transport_4) {
239
297
  transport_4 = await buildTransport();
@@ -245,7 +303,7 @@ const useMcpServerResource = (props) => {
245
303
  transportRef.current = null;
246
304
  clientRef.current = null;
247
305
  pendingTransportRef.current = transport_4;
248
- await transport_4.finishAuth(code);
306
+ await transport_4.finishAuth(url_0.searchParams);
249
307
  if (!isCurrentConnection(generation_3)) throw createInterruptedAuthError();
250
308
  setAuthorizationUrl(null);
251
309
  if (!await finalizeConnect(transport_4, generation_3)) throw createInterruptedAuthError();
@@ -283,9 +341,15 @@ const useMcpServerResource = (props) => {
283
341
  if (props.auth.type === "oauth") {
284
342
  if (!persisted_0?.tokens) return;
285
343
  } else if (!persisted_0?.token) return;
344
+ const pendingAuthValidation_0 = pendingAuthValidationRef.current;
345
+ if (pendingAuthValidation_0) {
346
+ await pendingAuthValidation_0.promise;
347
+ if (signal.cancelled || !isCurrentConnection(generation_4)) return;
348
+ }
286
349
  doConnect();
287
350
  });
288
351
  useEffect(() => {
352
+ const transportCloseQueueRef = props.transportCloseQueueRef;
289
353
  const previousDisposal = pendingDisposalRef.current;
290
354
  if (previousDisposal) previousDisposal.cancelled = true;
291
355
  const pendingDisposal = { cancelled: false };
@@ -296,14 +360,16 @@ const useMcpServerResource = (props) => {
296
360
  return () => {
297
361
  mountedRef.current = false;
298
362
  signal_0.cancelled = true;
299
- queueMicrotask(() => {
363
+ const task_0 = transportCloseQueueRef.current.then(async () => {
364
+ await new Promise((resolve_1) => queueMicrotask(resolve_1));
300
365
  if (pendingDisposal.cancelled) return;
301
366
  connectionGenerationRef.current += 1;
302
- closeTransports();
367
+ await Promise.all(detachTransports().map(closeTransportSafely));
303
368
  });
369
+ transportCloseQueueRef.current = task_0;
304
370
  };
305
- }, []);
306
- const state = useMemo(() => ({
371
+ }, [props.transportCloseQueueRef]);
372
+ const state_0 = useMemo(() => ({
307
373
  id: props.id,
308
374
  kind: props.kind,
309
375
  name: props.name,
@@ -327,13 +393,13 @@ const useMcpServerResource = (props) => {
327
393
  pendingElicitations
328
394
  ]);
329
395
  return {
330
- getState: () => state,
396
+ getState: () => state_0,
331
397
  connect: doConnect,
332
398
  disconnect: doDisconnect,
333
399
  remove: async () => {
334
400
  await doDisconnect();
335
401
  try {
336
- await props.storage.clearAuthState(props.id);
402
+ await clearOAuthProviderAuthState(props.storage, props.id);
337
403
  await props.onRemove();
338
404
  } catch (err_1) {
339
405
  setLastError({ message: err_1 instanceof Error ? err_1.message : String(err_1) });
@@ -394,8 +460,28 @@ const useMcpServerResource = (props) => {
394
460
  }
395
461
  };
396
462
  };
397
- const McpServerResource = resource(useMcpServerResource);
463
+ const McpServerResourceInstance = resource(useMcpServerResourceInstance);
464
+ const McpServerResource = resource(function useMcpServerResource(props) {
465
+ const transportCloseQueueRef = useRef(Promise.resolve());
466
+ const dependencies = getConnectionDependencies(props);
467
+ const [connection, setConnection] = useState({
468
+ dependencies,
469
+ generation: 0
470
+ });
471
+ let currentConnection = connection;
472
+ if (!areConnectionDependenciesEqual(connection.dependencies, dependencies)) {
473
+ currentConnection = {
474
+ dependencies,
475
+ generation: connection.generation + 1
476
+ };
477
+ setConnection(currentConnection);
478
+ }
479
+ return useResource(withKey(currentConnection.generation, McpServerResourceInstance({
480
+ ...props,
481
+ transportCloseQueueRef
482
+ })));
483
+ });
398
484
  //#endregion
399
- export { McpServerResource };
485
+ export { McpServerResource, getConnectionDependencies };
400
486
 
401
487
  //# sourceMappingURL=McpServerResource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","transportCloseQueueRef","resolve","connectionGenerationRef","elicitationResolversRef","Map","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","current","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","closeTransports","pendingTransport","activeTransport","Set","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","crypto","randomUUID","Math","random","slice","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","finishAuth","connected","tryAutoConnect","token","Awaited","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","length","join","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const transportCloseQueueRef = useRef(Promise.resolve());\n const connectionGenerationRef = useRef(0);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const closeTransports = async () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n const transports = new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n );\n await closeQueuedTransports([...transports]);\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(code);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const generation = connectionGenerationRef.current;\n let persisted: Awaited<ReturnType<MCPStorage[\"loadAuthState\"]>>;\n try {\n persisted = await props.storage.loadAuthState(props.id);\n } catch (error) {\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n const message = error instanceof Error ? error.message : String(error);\n setLastError({\n message: `MCP server \"${props.id}\" failed to load saved authentication: ${message}`,\n });\n setConnectionState(\"error\");\n return;\n }\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n // Defer disposal so StrictMode can replay setup before closing the transport.\n queueMicrotask(() => {\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n void closeTransports();\n });\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n try {\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n } catch (err) {\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
1
+ {"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","useResource","withKey","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","clearOAuthProviderAuthState","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","createMcpId","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","McpServerResourceInstanceProps","transportCloseQueueRef","current","getConnectionDependencies","props","authDependencies","type","token","scopeId","scopes","length","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","areConnectionDependenciesEqual","left","right","every","value","index","Object","is","useMcpServerResourceInstance","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","connectionGenerationRef","pendingAuthValidationRef","count","promise","resolve","elicitationResolversRef","Map","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","detachTransports","pendingTransport","activeTransport","Set","closeTransports","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","validationGeneration","state","searchParams","pendingAuthValidation","resolvePromise","finishAuth","connected","tryAutoConnect","Awaited","tokens","previousDisposal","pendingDisposal","queueMicrotask","getState","disconnect","remove","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","join","McpServerResourceInstance","McpServerResource","useMcpServerResource","dependencies","connection","setConnection","currentConnection"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource, useResource, withKey } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport {\n clearOAuthProviderAuthState,\n createOAuthProvider,\n} from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\nimport { createMcpId } from \"../utils/createMcpId\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\ntype McpServerResourceInstanceProps = McpServerResourceProps & {\n transportCloseQueueRef: { current: Promise<void> };\n};\n\nexport const getConnectionDependencies = (\n props: McpServerResourceProps,\n): readonly unknown[] => {\n const auth = props.auth;\n const authDependencies =\n auth.type === \"bearer\"\n ? [auth.type, auth.token, props.storage.scopeId]\n : auth.type === \"oauth\"\n ? [\n auth.type,\n auth.scopes?.length,\n ...(auth.scopes ?? []),\n auth.authorizationEndpoint,\n auth.tokenEndpoint,\n auth.registrationEndpoint,\n auth.clientId,\n auth.clientSecret,\n props.storage.scopeId,\n ]\n : [auth.type];\n\n return [\n props.id,\n props.url,\n ...authDependencies,\n props.redirectUri,\n props.cache?.defaultTtlMs,\n props.elicitation !== false,\n ];\n};\n\nconst areConnectionDependenciesEqual = (\n left: readonly unknown[],\n right: readonly unknown[],\n) =>\n left.length === right.length &&\n left.every((value, index) => Object.is(value, right[index]));\n\nconst useMcpServerResourceInstance = (\n props: McpServerResourceInstanceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const connectionGenerationRef = useRef(0);\n const pendingAuthValidationRef = useRef<{\n count: number;\n promise: Promise<void>;\n resolve: () => void;\n } | null>(null);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = props.transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n props.transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const detachTransports = () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n return [\n ...new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n ),\n ];\n };\n\n const closeTransports = async () => {\n await closeQueuedTransports(detachTransports());\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id = createMcpId();\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const validationGeneration = connectionGenerationRef.current;\n const url = new URL(callbackUrl);\n const state = url.searchParams.get(\"state\");\n if (!state) throw new Error('missing \"state\" parameter');\n let pendingAuthValidation = pendingAuthValidationRef.current;\n if (!pendingAuthValidation) {\n let resolve!: () => void;\n const promise = new Promise<void>((resolvePromise) => {\n resolve = resolvePromise;\n });\n pendingAuthValidation = { count: 0, promise, resolve };\n pendingAuthValidationRef.current = pendingAuthValidation;\n }\n pendingAuthValidation.count += 1;\n try {\n const persisted = await props.storage.loadAuthState(props.id);\n if (!isCurrentConnection(validationGeneration)) {\n throw createInterruptedAuthError();\n }\n if (!persisted?.state) {\n throw new Error(\n \"no pending OAuth authorization request for this server\",\n );\n }\n if (persisted.state !== state) {\n throw new Error(\"OAuth state does not match the authorization request\");\n }\n if (!url.searchParams.get(\"code\") && !url.searchParams.get(\"error\")) {\n throw new Error(\"missing authorization code in callback URL\");\n }\n } finally {\n pendingAuthValidation.count -= 1;\n if (pendingAuthValidation.count === 0) {\n pendingAuthValidationRef.current = null;\n pendingAuthValidation.resolve();\n }\n }\n\n // Claim the generation before a waiting auto-connect can resume.\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(url.searchParams);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const generation = connectionGenerationRef.current;\n let persisted: Awaited<ReturnType<MCPStorage[\"loadAuthState\"]>>;\n try {\n persisted = await props.storage.loadAuthState(props.id);\n } catch (error) {\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n const message = error instanceof Error ? error.message : String(error);\n setLastError({\n message: `MCP server \"${props.id}\" failed to load saved authentication: ${message}`,\n });\n setConnectionState(\"error\");\n return;\n }\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n const pendingAuthValidation = pendingAuthValidationRef.current;\n if (pendingAuthValidation) {\n await pendingAuthValidation.promise;\n if (signal.cancelled || !isCurrentConnection(generation)) return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const transportCloseQueueRef = props.transportCloseQueueRef;\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n const task = transportCloseQueueRef.current.then(async () => {\n await new Promise<void>((resolve) => queueMicrotask(resolve));\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n await Promise.all(detachTransports().map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n };\n }, [props.transportCloseQueueRef]);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n try {\n await clearOAuthProviderAuthState(props.storage, props.id);\n await props.onRemove();\n } catch (err) {\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nconst McpServerResourceInstance = resource(useMcpServerResourceInstance);\n\nexport const McpServerResource = resource(function useMcpServerResource(\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> {\n const transportCloseQueueRef = useRef(Promise.resolve());\n const dependencies = getConnectionDependencies(props);\n const [connection, setConnection] = useState({ dependencies, generation: 0 });\n let currentConnection = connection;\n if (!areConnectionDependenciesEqual(connection.dependencies, dependencies)) {\n currentConnection = {\n dependencies,\n generation: connection.generation + 1,\n };\n setConnection(currentConnection);\n }\n\n // Storage keys remounts through its optional scopeId rather than object\n // identity, because a defaulted storage element is rebuilt on ordinary\n // renders.\n return useResource(\n withKey(\n currentConnection.generation,\n McpServerResourceInstance({ ...props, transportCloseQueueRef }),\n ),\n );\n});\n"],"mappings":"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"}
@@ -7,6 +7,13 @@ type McpLocalStorageOptions = {
7
7
  keyPrefix?: string;
8
8
  /** Override the underlying Storage. Defaults to globalThis.localStorage. */
9
9
  storage?: Storage;
10
+ /**
11
+ * Stable identity for the backing data, used to key server reconnects.
12
+ * Required to get reconnect-on-swap behavior when `storage` is overridden;
13
+ * without it a custom backing store declares no scope, since a prefix
14
+ * alone cannot distinguish two different stores.
15
+ */
16
+ scopeId?: string;
10
17
  };
11
18
  declare const normalizeCustomServerRecords: (value: unknown) => MCPCustomServerRecord[];
12
19
  declare const normalizePersistedAuthState: (value: unknown) => MCPPersistedAuthState | null;
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;;KAYY;;EAEV;;EAEA,UAAU;;cAiGC,+BACX,mBACC;cAsEU,8BACX,mBACC;cAyEU,6CAAe,SAAA,aAAA,OAAA"}
1
+ {"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;;KAaY;;EAEV;;EAEA,UAAU;;;;;;;EAOV;;cAiGW,+BACX,mBACC;cAsEU,8BACX,mBACC;cAuFU,6CAAe,SAAA,aAAA,OAAA"}
@@ -1,4 +1,6 @@
1
1
  import { assertValidServerId } from "../../utils/serverId.js";
2
+ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
+ import "@assistant-ui/tap/react-shim";
2
4
  import { resource } from "@assistant-ui/tap";
3
5
  import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from "@modelcontextprotocol/core";
4
6
  //#region src/resources/storage/McpLocalStorage.ts
@@ -85,6 +87,7 @@ const normalizePersistedAuthState = (value) => {
85
87
  const state = {};
86
88
  if (isNonEmptyString(value.token)) state.token = value.token;
87
89
  if (isNonEmptyString(value.codeVerifier)) state.codeVerifier = value.codeVerifier;
90
+ if (isNonEmptyString(value.state)) state.state = value.state;
88
91
  const tokens = normalizeOAuthTokens(value.tokens);
89
92
  if (tokens) state.tokens = tokens;
90
93
  const clientInformation = normalizeClientInformation(value.clientInformation);
@@ -93,46 +96,138 @@ const normalizePersistedAuthState = (value) => {
93
96
  if (discoveryState) state.discoveryState = discoveryState;
94
97
  return Object.keys(state).length > 0 ? state : null;
95
98
  };
96
- const useMcpLocalStorage = (opts = {}) => {
99
+ const useMcpLocalStorage = (t0) => {
100
+ const $ = c(36);
101
+ let t1;
102
+ if ($[0] !== t0) {
103
+ t1 = t0 === void 0 ? {} : t0;
104
+ $[0] = t0;
105
+ $[1] = t1;
106
+ } else t1 = $[1];
107
+ const opts = t1;
97
108
  const prefix = opts.keyPrefix ?? "aui-mcp";
109
+ let t2;
110
+ if ($[2] !== opts) {
111
+ t2 = resolveStorage(opts);
112
+ $[2] = opts;
113
+ $[3] = t2;
114
+ } else t2 = $[3];
115
+ const storage = t2;
116
+ const scopeId = opts.scopeId ?? (opts.storage === void 0 ? `local-storage:${prefix}` : void 0);
98
117
  const customServersKey = `${prefix}:custom-servers`;
99
- const authKey = (id) => `${prefix}:auth:${id}`;
100
- const storage = resolveStorage(opts);
101
- const read = (key, fallback) => {
102
- if (!storage) return fallback;
103
- try {
104
- const raw = storage.getItem(key);
105
- if (raw == null) return fallback;
106
- return JSON.parse(raw);
107
- } catch {
108
- return fallback;
109
- }
110
- };
111
- const write = (key, value) => {
112
- if (!storage) return;
113
- try {
114
- storage.setItem(key, JSON.stringify(value));
115
- } catch {}
116
- };
117
- const remove = (key) => {
118
- if (!storage) return;
119
- try {
120
- storage.removeItem(key);
121
- } catch {}
122
- };
123
- return {
124
- loadCustomServers: async () => normalizeCustomServerRecords(read(customServersKey, [])),
125
- saveCustomServers: async (records) => {
118
+ let t3;
119
+ if ($[4] !== prefix) {
120
+ t3 = (id) => `${prefix}:auth:${id}`;
121
+ $[4] = prefix;
122
+ $[5] = t3;
123
+ } else t3 = $[5];
124
+ const authKey = t3;
125
+ let t4;
126
+ if ($[6] !== storage) {
127
+ t4 = (key, fallback) => {
128
+ if (!storage) return fallback;
129
+ try {
130
+ const raw = storage.getItem(key);
131
+ if (raw == null) return fallback;
132
+ return JSON.parse(raw);
133
+ } catch {
134
+ return fallback;
135
+ }
136
+ };
137
+ $[6] = storage;
138
+ $[7] = t4;
139
+ } else t4 = $[7];
140
+ const read = t4;
141
+ let t5;
142
+ if ($[8] !== storage) {
143
+ t5 = (key_0, value) => {
144
+ if (!storage) return;
145
+ try {
146
+ storage.setItem(key_0, JSON.stringify(value));
147
+ } catch {}
148
+ };
149
+ $[8] = storage;
150
+ $[9] = t5;
151
+ } else t5 = $[9];
152
+ const write = t5;
153
+ let t6;
154
+ if ($[10] !== storage) {
155
+ t6 = (key_1) => {
156
+ if (!storage) return;
157
+ try {
158
+ storage.removeItem(key_1);
159
+ } catch {}
160
+ };
161
+ $[10] = storage;
162
+ $[11] = t6;
163
+ } else t6 = $[11];
164
+ const remove = t6;
165
+ let t7;
166
+ if ($[12] !== scopeId) {
167
+ t7 = scopeId !== void 0 ? { scopeId } : {};
168
+ $[12] = scopeId;
169
+ $[13] = t7;
170
+ } else t7 = $[13];
171
+ let t8;
172
+ if ($[14] !== customServersKey || $[15] !== read) {
173
+ t8 = async () => normalizeCustomServerRecords(read(customServersKey, []));
174
+ $[14] = customServersKey;
175
+ $[15] = read;
176
+ $[16] = t8;
177
+ } else t8 = $[16];
178
+ let t9;
179
+ if ($[17] !== customServersKey || $[18] !== write) {
180
+ t9 = async (records) => {
126
181
  write(customServersKey, records);
127
- },
128
- loadAuthState: async (id) => normalizePersistedAuthState(read(authKey(id), null)),
129
- saveAuthState: async (id, state) => {
130
- write(authKey(id), state);
131
- },
132
- clearAuthState: async (id) => {
133
- remove(authKey(id));
134
- }
135
- };
182
+ };
183
+ $[17] = customServersKey;
184
+ $[18] = write;
185
+ $[19] = t9;
186
+ } else t9 = $[19];
187
+ let t10;
188
+ if ($[20] !== authKey || $[21] !== read) {
189
+ t10 = async (id_0) => normalizePersistedAuthState(read(authKey(id_0), null));
190
+ $[20] = authKey;
191
+ $[21] = read;
192
+ $[22] = t10;
193
+ } else t10 = $[22];
194
+ let t11;
195
+ if ($[23] !== authKey || $[24] !== write) {
196
+ t11 = async (id_1, state) => {
197
+ write(authKey(id_1), state);
198
+ };
199
+ $[23] = authKey;
200
+ $[24] = write;
201
+ $[25] = t11;
202
+ } else t11 = $[25];
203
+ let t12;
204
+ if ($[26] !== authKey || $[27] !== remove) {
205
+ t12 = async (id_2) => {
206
+ remove(authKey(id_2));
207
+ };
208
+ $[26] = authKey;
209
+ $[27] = remove;
210
+ $[28] = t12;
211
+ } else t12 = $[28];
212
+ let t13;
213
+ if ($[29] !== t10 || $[30] !== t11 || $[31] !== t12 || $[32] !== t7 || $[33] !== t8 || $[34] !== t9) {
214
+ t13 = {
215
+ ...t7,
216
+ loadCustomServers: t8,
217
+ saveCustomServers: t9,
218
+ loadAuthState: t10,
219
+ saveAuthState: t11,
220
+ clearAuthState: t12
221
+ };
222
+ $[29] = t10;
223
+ $[30] = t11;
224
+ $[31] = t12;
225
+ $[32] = t7;
226
+ $[33] = t8;
227
+ $[34] = t9;
228
+ $[35] = t13;
229
+ } else t13 = $[35];
230
+ return t13;
136
231
  };
137
232
  const McpLocalStorage = resource(useMcpLocalStorage);
138
233
  //#endregion