@assistant-ui/react-mcp 0.1.11 → 0.1.13

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 (38) hide show
  1. package/dist/auth/createOAuthProvider.d.ts.map +1 -1
  2. package/dist/auth/createOAuthProvider.js +11 -0
  3. package/dist/auth/createOAuthProvider.js.map +1 -1
  4. package/dist/auth/types.d.ts +2 -1
  5. package/dist/auth/types.d.ts.map +1 -1
  6. package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
  7. package/dist/hooks/useMcpOAuthCallback.js +3 -16
  8. package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
  9. package/dist/primitives/addForm/McpAddFormRoot.d.ts.map +1 -1
  10. package/dist/primitives/addForm/McpAddFormRoot.js +2 -1
  11. package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
  12. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  13. package/dist/resources/McpManagerResource.js +47 -46
  14. package/dist/resources/McpManagerResource.js.map +1 -1
  15. package/dist/resources/McpServerResource.d.ts.map +1 -1
  16. package/dist/resources/McpServerResource.js +13 -3
  17. package/dist/resources/McpServerResource.js.map +1 -1
  18. package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
  19. package/dist/resources/storage/McpLocalStorage.js +22 -1
  20. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  21. package/dist/utils/invokeMcpCallback.d.ts +5 -0
  22. package/dist/utils/invokeMcpCallback.d.ts.map +1 -0
  23. package/dist/utils/invokeMcpCallback.js +19 -0
  24. package/dist/utils/invokeMcpCallback.js.map +1 -0
  25. package/package.json +6 -6
  26. package/src/auth/createOAuthProvider.test.ts +86 -0
  27. package/src/auth/createOAuthProvider.ts +15 -0
  28. package/src/auth/types.ts +2 -0
  29. package/src/hooks/useMcpOAuthCallback.tsx +3 -36
  30. package/src/primitives/addForm/McpAddFormRoot.test.tsx +80 -0
  31. package/src/primitives/addForm/McpAddFormRoot.tsx +2 -1
  32. package/src/resources/McpManagerResource.test.ts +21 -0
  33. package/src/resources/McpManagerResource.ts +12 -7
  34. package/src/resources/McpServerResource.test.ts +141 -1
  35. package/src/resources/McpServerResource.ts +14 -2
  36. package/src/resources/storage/McpLocalStorage.test.ts +180 -0
  37. package/src/resources/storage/McpLocalStorage.ts +53 -0
  38. package/src/utils/invokeMcpCallback.ts +27 -0
@@ -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","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 persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) 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","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":";;;;;;;;AA2CA,MAAM0C,wBACJC,UAC8B;CAC9B3B,oBAAoB2B,MAAMhB,EAAE;CAC5B,MAAM,CAACiB,iBAAiBC,sBACtB7C,SAA6B,cAAc;CAC7C,MAAM,CAAC8C,OAAOC,YAAY/C,SAAwB,CAAA,CAAE;CACpD,MAAM,CAACgD,WAAWC,gBAAgBjD,SAAqC,IAAI;CAC3E,MAAM,CAACmD,kBAAkBC,uBAAuBpD,SAAwB,IAAI;CAC5E,MAAM,CAACqD,qBAAqBC,0BAA0BtD,SAEpD,CAAA,CAAE;CAEJ,MAAMuD,YAAYtD,OAAsB,IAAI;CAC5C,MAAMuD,eAAevD,OAA6C,IAAI;CACtE,MAAMwD,sBAAsBxD,OAC1B,IACF;CACA,MAAMyD,yBAAyBzD,OAAOwC,QAAQkB,QAAQ,CAAC;CACvD,MAAMC,0BAA0B3D,OAAO,CAAC;CACxC,MAAM4D,0BAA0B5D,uBAC9B,IAAI6D,IAQF,CACJ;CACA,MAAMM,qBAAqBnE,OAAsC,IAAI;CACrE,MAAMqE,aAAarE,OAAO,IAAI;CAE9B,MAAMsE,uBAAuB,OAC3BC,cACkB;EAClB,IAAI;GACF,MAAMA,UAAUC,MAAM;EACxB,QAAQ,CACN;CAEJ;CAEA,MAAMC,yBACJC,eACkB;EAClB,MAAMC,OAAOlB,uBAAuBmB,QAAQC,KAAK,YAAY;GAC3D,MAAMrC,QAAQsC,IAAIJ,WAAWK,IAAIT,oBAAoB,CAAC;EACxD,CAAC;EACDb,uBAAuBmB,UAAUD;EACjC,OAAOA;CACT;CAEA,MAAMK,wBAAwB,YAAY;EACxC,MAAMT,cAAYf,oBAAoBoB;EACtCpB,oBAAoBoB,UAAU;EAC9B,MAAMH,sBAAsBF,cAAY,CAACA,WAAS,IAAI,CAAA,CAAE;CAC1D;CAEA,MAAMU,6BAA6BvD,IAAYoC,WAAyB;EACtE,MAAMoB,QAAQtB,wBAAwBgB,QAAQO,IAAIzD,EAAE;EACpD,IAAI,CAACwD,OAAO,OAAO;EACnBtB,wBAAwBgB,QAAQQ,OAAO1D,EAAE;EACzCwD,MAAMnB,OAAOsB,oBAAoB,SAASH,MAAMjB,OAAO;EACvDZ,wBAAwBuB,YACtBA,QAAQU,QAAQhD,gBAAgBA,YAAYZ,OAAOA,EAAE,CACvD;EACAwD,MAAMxB,QAAQI,MAAM;EACpB,OAAO;CACT;CAEA,MAAMyB,8BACJ7D,MACA8D,UACG;EACH,IAAI,CAAC5B,wBAAwBgB,QAAQc,IAAIhE,IAAE,GAAG,OAAO;EACrD2B,wBAAwBuB,cACtBA,UAAQG,KAAKzC,kBACXA,cAAYZ,OAAOA,OAAK;GAAE,GAAGY;GAAakD;EAAM,IAAIlD,aACtD,CACF;EACA,OAAO;CACT;CAEA,MAAMqD,kCAAkC;EACtC,KAAK,MAAM,CAACjE,SAAOkC,wBAAwBgB,SACzCK,0BAA0BvD,MAAI,EAAEkE,QAAQ,SAAS,CAAC;CAEtD;CAEA,MAAMC,kBAAkB,YAAY;EAClCF,0BAA0B;EAC1B,MAAMG,mBAAmBtC,oBAAoBoB;EAC7C,MAAMmB,kBAAkBxC,aAAaqB;EACrCpB,oBAAoBoB,UAAU;EAC9BrB,aAAaqB,UAAU;EACvBtB,UAAUsB,UAAU;EAEpB,MAAMF,eAAa,IAAIsB,IACrB,CAACF,kBAAkBC,eAAe,CAAC,CAACT,QACjCf,gBACCA,gBAAc,IAClB,CACF;EACA,MAAME,sBAAsB,CAAC,GAAGC,YAAU,CAAC;CAC7C;CAEA,MAAMuB,uBAAuBC,eAC3B7B,WAAWO,WAAWsB,eAAevC,wBAAwBiB;CAE/D,MAAMuB,8BAA8BC,UAClC,IAAIC,MACF,eAAe3D,MAAMhB,GAAE,qDACvB0E,UAAUE,KAAAA,IAAYA,KAAAA,IAAY,EAAEF,MAAM,CAC5C;CAEF,MAAMG,wBAAwBpG,eAC5B,OACEqG,SACAE,OACAC,cACe;EACf,MAAMC,YAAYlE,MAAMP;EACxB,IAAIyE,cAAcN,KAAAA,GAAW,OAAO,MAAME;EAC1C,MAAMK,cAAcD,aAAaE,KAAKC,IAAI,IAAIJ;EAC9C,MAAMK,qCACJ,IAAIX,MACF,eAAe3D,MAAMhB,GAAE,oBAAqBgF,MAAK,SAAUE,UAAS,IACtE;EACF,IAAIC,eAAe,GAAG,MAAMG,aAAa;EAEzC,IAAIC;EACJ,IAAI;GACF,OAAO,MAAMzE,QAAQ4E,KAAK,CACxBZ,SACA,IAAIhE,SAAgB6E,GAAGC,WAAW;IAChCL,UAAUE,iBAAiBG,OAAON,aAAa,CAAC,GAAGH,WAAW;GAChE,CAAC,CAAC,CACH;EACH,UAAU;GACR,IAAII,YAAYX,KAAAA,GAAWiB,aAAaN,OAAO;EACjD;CACF,CACF;CAEA,MAAMO,iBAAiBrH,eACrB,YAAoD;EAClD,IAAIuC,MAAMX,KAAK0F,SAAS,SAAS;GAC/B,MAAMC,eAAe7G,oBAAoB;IACvC8G,UAAUjF,MAAMhB;IAChBkG,QAAQlF,MAAMX;IACdC,SAASU,MAAMV;IACfC,aAAaS,MAAMT;IACnB4F,qBAAqBhG,QAAQsB,oBAAoBtB,IAAIiG,SAAS,CAAC;GACjE,CAAC;GACD,OAAO,IAAIvH,8BAA8B,IAAIwH,IAAIrF,MAAMb,GAAG,GAAG,EAC3D6F,aACF,CAAC;EACH;EACA,IAAIhF,MAAMX,KAAK0F,SAAS,UAAU;GAChC,MAAMO,YAAY,MAAMtF,MAAMV,QAAQiG,cAAcvF,MAAMhB,EAAE;GAC5D,MAAMwG,UAAUpH,aAAa4B,MAAMX,MAAMiG,SAAS;GAClD,MAAMG,gBAAsD,CAAC;GAC7D,IAAID,SAASC,cAAcC,cAAc,EAAEF,QAAQ;GACnD,OAAO,IAAI3H,8BACT,IAAIwH,IAAIrF,MAAMb,GAAG,GACjBsG,aACF;EACF;EACA,OAAO,IAAI5H,8BAA8B,IAAIwH,IAAIrF,MAAMb,GAAG,CAAC;CAC7D,CACF;CAEA,MAAMwG,iBAAiBlI,gBACpBmI,SAMK;EACJxF,SACEwF,KAAKzF,MAAMkC,KAAK2D,MAAM;GACpB,MAAMC,OAAoB;IACxB/G,MAAM8G,EAAE9G;IACR6G,aAAaC,EAAED;GACjB;GACA,IAAIC,EAAEF,gBAAgBlC,KAAAA,GAAWqC,KAAKH,cAAcE,EAAEF;GACtD,OAAOG;EACT,CAAC,CACH;CACF,CACF;CAEA,MAAMC,YAAYzI,eAChB,OACE0I,QACA3C,cACA4C,YACqB;EACrB,MAAMC,cAAcF,OAAOG,UAAU;EACrC,MAAMV,SACJQ,SAASnC,cAAcL,KAAAA,IACnB,MAAMyC,cACN,MAAMxC,sBACJwC,aACA,iBACAD,QAAQnC,SACV;EACN,IAAI,CAACV,oBAAoBC,YAAU,GAAG,OAAO;EAC7CmC,eAAeC,MAAI;EACnB,OAAO;CACT,CACF;CAEA,MAAMW,kBAAkB9I,eACtB,OACEoE,aACA2B,iBACqB;EACrB,MAAMgD,gBAA+B;GACnC,GAAIxG,MAAMJ,gBAAgB,QACtB,CAAC,IACD,EAAE6G,cAAc,EAAE7G,aAAa,CAAC,EAAE,EAAE;GACxC8G,aAAa,EACXvG,OAAO;IACLwG,aAAa;IACbC,YAAY;IACZC,YAAY/D,SAAOgE,UAAU;KAC3B,IAAI,CAACvD,oBAAoBC,YAAU,GAAG;KACtC,IAAIV,YAAU,MAAM;MAClBxC,aAAa,EAAEC,SAASuC,QAAMvC,QAAQ,CAAC;MACvC;KACF;KACA,IAAIuG,UAAU,MAAM;KACpBxG,aAAa,IAAI;KACjBqF,eAAe,EAAExF,OAAO2G,MAAM,CAAC;IACjC;GACF,EACF;EACF;EACA,IAAI9G,MAAMN,OAAOC,iBAAiBiE,KAAAA,GAChC4C,cAAcO,oBAAoB/G,MAAMN,MAAMC;EAEhD,MAAMwG,WAAS,IAAIvI,OACjB;GACEsB,MAAM;GACN8H,SAAS;EACX,GACAR,aACF;EACA,IAAIxG,MAAMJ,gBAAgB,OACxBuG,SAAOc,kBACL,uBACCC,SAAwBC,YAAmC;GAC1D,IAAI,CAAC5D,oBAAoBC,YAAU,GACjC,OAAO1D,QAAQkB,QAAQ,EAAEkC,QAAQ,SAAS,CAAC;GAE7C,IAAI,EAAE,qBAAqBgE,QAAQE,SACjC,OAAOtH,QAAQkB,QAAQ,EAAEkC,QAAQ,SAAS,CAAC;GAE7C,MAAM,EAAE3C,SAASiB,oBAAoB0F,QAAQE;GAE7C,MAAMpI,OACJ,OAAOqI,WAAW,eAAe,gBAAgBA,SAC7CA,OAAOC,WAAW,IAClB,OAAOlD,KAAKC,IAAI,EAAC,GAAIkD,KAAKC,OAAO,CAAC,CAACpC,SAAS,EAAE,CAAC,CAACqC,MAAM,CAAC;GAC7D,MAAM3D,YAAU,IAAIhE,SAAuBkB,YAAY;IACrD,MAAMO,gBAAgB;KACpBgB,0BAA0BvD,MAAI,EAAEkE,QAAQ,SAAS,CAAC;IACpD;IACAhC,wBAAwBgB,QAAQwF,IAAI1I,MAAI;KACtCgC;KACAK,QAAQ8F,QAAQ9F;KAChBE;KACAC;IACF,CAAC;GACH,CAAC;GACDb,wBAAwBuB,cAAY,CAClC,GAAGA,WACH;IACElD,IAAAA;IACAuB;IACAiB;GACF,CAAC,CACF;GACD,MAAMgB,UAAQtB,wBAAwBgB,QAAQO,IAAIzD,IAAE;GACpD,IAAIwD,SAAO;IACT,IAAI2E,QAAQ9F,OAAOsG,SACjBnF,QAAMjB,QAAQ;SAEd4F,QAAQ9F,OAAOuG,iBAAiB,SAASpF,QAAMjB,SAAS,EACtDsG,MAAM,KACR,CAAC;GAEL;GACA,OAAO/D;EACT,CACF;EAEF,MAAMG,cAAYG,KAAKC,IAAI;EAC3B,MAAMR,sBACJsC,SAAO2B,QAAQjG,WAAS,GACxB,cACAoC,WACF;EACA,IAAI,CAACV,oBAAoBC,YAAU,GAAG,OAAO;EAM7C,IAAI,CAACuE,MADgB7B,UAAUC,UAAQ3C,cAAY,EAAES,WAAAA,YAAU,CAAC,GACnD,OAAO;EAEpBnD,oBAAoBoB,UAAU;EAC9BtB,UAAUsB,UAAUiE;EACpBtF,aAAaqB,UAAUL;EACvB3B,mBAAmB,WAAW;EAC9B,OAAO;CACT,CACF;CAEA,MAAM8H,YAAYvK,eAAe,YAAY;EAC3C,MAAM+F,eAAa,EAAEvC,wBAAwBiB;EAE7C,MAAMiB,gBAAgB;EACtB,IAAI,CAACI,oBAAoBC,YAAU,GAAG;EAEtCtD,mBAAmB,YAAY;EAC/BI,aAAa,IAAI;EACjBG,oBAAoB,IAAI;EAGxBL,SAAS,CAAA,CAAE;EACX,IAAIyB,cAAkD;EACtD,IAAI;GACFA,cAAY,MAAMiD,eAAe;GACjC,IAAI,CAACvB,oBAAoBC,YAAU,GAAG;IACpC,MAAMzB,sBAAsB,CAACF,WAAS,CAAC;IACvC;GACF;GACAf,oBAAoBoB,UAAUL;GAI9B,MAAM0E,gBAAgB1E,aAAW2B,YAAU;EAC7C,SAASyE,KAAK;GACZ,IAAI,CAAC1E,oBAAoBC,YAAU,GAAG;GAEtC,IAAIyE,eAAenK,mBAAmB;IAIpCgD,oBAAoBoB,UAAU;IAC9BrB,aAAaqB,UAAUL;IACvB3B,mBAAmB,cAAc;GACnC,OAAO;IACL+C,0BAA0B;IAC1B,IAAIpB,aAAW;KACbf,oBAAoBoB,UAAU;KAC9B,MAAMH,sBAAsB,CAACF,WAAS,CAAC;IACzC;IACAvB,aAAa,EACXC,SAAS0H,eAAetE,QAAQsE,IAAI1H,UAAU2H,OAAOD,GAAG,EAC1D,CAAC;IACD/H,mBAAmB,OAAO;GAC5B;EACF;CACF,CAAC;CAED,MAAMiI,eAAe1K,eAAe,YAAY;EAC9CwD,wBAAwBiB,WAAW;EACnC9B,SAAS,CAAA,CAAE;EACXK,oBAAoB,IAAI;EACxBP,mBAAmB,cAAc;EACjC,MAAMiD,gBAAgB;CACxB,CAAC;CAED,MAAMiF,iBAAiB3K,eAAe,OAAO4K,gBAAwB;EACnE,MAAM7E,eAAa,EAAEvC,wBAAwBiB;EAC7Ce,0BAA0B;EAC1B,MAAMX,sBAAsB;EAC5B,IAAI,CAACiB,oBAAoBC,YAAU,GAAG,MAAMC,2BAA2B;EAEvEvD,mBAAmB,aAAa;EAChCI,aAAa,IAAI;EACjB,IAAI;GAEF,MAAMgI,OAAOnJ,IADGkG,IAAIgD,WACPlJ,CAAG,CAACoJ,aAAa9F,IAAI,MAAM;GACxC,IAAI,CAAC6F,MAAM,MAAM,IAAI3E,MAAM,4CAA4C;GACvE,IAAI9B,cAAYhB,aAAaqB;GAC7B,IAAI,CAACL,aAAW;IACdA,cAAY,MAAMiD,eAAe;IACjC,IAAI,CAACvB,oBAAoBC,YAAU,GAAG;KACpC,MAAMzB,sBAAsB,CAACF,WAAS,CAAC;KACvC,MAAM4B,2BAA2B;IACnC;GACF;GACA5C,aAAaqB,UAAU;GACvBtB,UAAUsB,UAAU;GACpBpB,oBAAoBoB,UAAUL;GAC9B,MAAMA,YAAU2G,WAAWF,IAAI;GAC/B,IAAI,CAAC/E,oBAAoBC,YAAU,GAAG,MAAMC,2BAA2B;GACvEhD,oBAAoB,IAAI;GAExB,IAAI,CAACgI,MADmBlC,gBAAgB1E,aAAW2B,YAAU,GAC7C,MAAMC,2BAA2B;EACnD,SAASwE,OAAK;GACZ,MAAMnF,UAAQmF,iBAAetE,QAAQsE,QAAM,IAAItE,MAAMuE,OAAOD,KAAG,CAAC;GAChE,IAAI,CAAC1E,oBAAoBC,YAAU,GACjC,MAAMC,2BAA2BX,OAAK;GAExC,MAAMK,gBAAgB;GACtB7C,aAAa,EACXC,SAASuC,QAAMvC,QACjB,CAAC;GACDL,mBAAmB,OAAO;GAC1B,MAAM4C;EACR;CACF,CAAC;CAED,MAAM4F,iBAAiBjL,eACrB,OAAO4D,WAAmC;EACxC,IAAI,CAACrB,MAAMR,aAAa;EACxB,IAAIQ,MAAMX,KAAK0F,SAAS,QAAQ;GAC9B,UAAe;GACf;EACF;EAEA,IAAI/E,MAAMX,KAAK0F,SAAS,YAAY/E,MAAMX,KAAKsJ,OAAO;GACpD,UAAe;GACf;EACF;EACA,MAAMnF,eAAavC,wBAAwBiB;EAC3C,IAAIoD;EACJ,IAAI;GACFA,cAAY,MAAMtF,MAAMV,QAAQiG,cAAcvF,MAAMhB,EAAE;EACxD,SAAS8D,SAAO;GACd,IAAIzB,OAAOK,aAAa,CAAC6B,oBAAoBC,YAAU,GAAG;GAC1D,MAAMjD,YAAUuC,mBAAiBa,QAAQb,QAAMvC,UAAU2H,OAAOpF,OAAK;GACrExC,aAAa,EACXC,SAAS,eAAeP,MAAMhB,GAAE,yCAA0CuB,YAC5E,CAAC;GACDL,mBAAmB,OAAO;GAC1B;EACF;EACA,IAAImB,OAAOK,aAAa,CAAC6B,oBAAoBC,YAAU,GAAG;EAC1D,IAAIxD,MAAMX,KAAK0F,SAAS,SAClB;OAAA,CAACO,aAAWuD,QAAQ;EAAA,OACnB,IAAI,CAACvD,aAAWqD,OACrB;EAEF,UAAe;CACjB,CACF;CAGApL,gBAAgB;EACd,MAAMuL,mBAAmBrH,mBAAmBS;EAC5C,IAAI4G,kBAAkBA,iBAAiBpH,YAAY;EACnD,MAAMqH,kBAAkB,EAAErH,WAAW,MAAM;EAC3CD,mBAAmBS,UAAU6G;EAC7BpH,WAAWO,UAAU;EACrB,MAAMb,WAAS,EAAEK,WAAW,MAAM;EAClC,eAAoBL,QAAM;EAC1B,aAAa;GACXM,WAAWO,UAAU;GACrBb,SAAOK,YAAY;GAEnBsH,qBAAqB;IACnB,IAAID,gBAAgBrH,WAAW;IAC/BT,wBAAwBiB,WAAW;IACnC,gBAAqB;GACvB,CAAC;EACH;CACF,GAAG,CAAA,CAAE;CAEL,MAAM+G,QAAQzL,eACL;EACLwB,IAAIgB,MAAMhB;EACVC,MAAMe,MAAMf;EACZC,MAAMc,MAAMd;EACZC,KAAKa,MAAMb;EACXC,MAAMY,MAAMZ;EACZa;EACAI;EACAF;EACAK;EACAE;CACF,IACA;EACEV,MAAMhB;EACNgB,MAAMf;EACNe,MAAMd;EACNc,MAAMb;EACNa,MAAMZ;EACNa;EACAI;EACAF;EACAK;EACAE;CAAmB,CAEvB;CAEA,OAAO;EACLwI,gBAAgBD;EAChBnB,SAASE;EACTmB,YAAYhB;EACZiB,QAAQ,YAAY;GAClB,MAAMjB,aAAa;GACnB,IAAI;IACF,MAAMnI,MAAMV,QAAQ+J,eAAerJ,MAAMhB,EAAE;IAC3C,MAAMgB,MAAMH,SAAS;GACvB,SAASoI,OAAK;IACZ3H,aAAa,EACXC,SAAS0H,iBAAetE,QAAQsE,MAAI1H,UAAU2H,OAAOD,KAAG,EAC1D,CAAC;IACD,MAAMA;GACR;EACF;EACAqB,UAAU,OAAOpK,MAAMqK,SAAS;GAC9B,MAAMpD,WAASvF,UAAUsB;GACzB,IAAI,CAACiE,UACH,MAAM,IAAIxC,MAAM,eAAe3D,MAAMhB,GAAE,mBAAoB;GAE7D,OAAO,MAAMmH,SAAOmD,SAAS;IAC3BpK;IACAsK,WAAWD;GACb,CAAC;EACH;EACAG,eAAe,OAAOtC,WAAW;GAC/B,MAAMjB,WAASvF,UAAUsB;GACzB,IAAI,CAACiE,UACH,MAAM,IAAIxC,MAAM,eAAe3D,MAAMhB,GAAE,mBAAoB;GAE7D,OAAO,MAAMmH,SAAOuD,cAActC,MAAM;EAC1C;EACAuC,cAAc,OAAOC,QAAQ;GAC3B,MAAMzD,WAASvF,UAAUsB;GACzB,IAAI,CAACiE,UACH,MAAM,IAAIxC,MAAM,eAAe3D,MAAMhB,GAAE,mBAAoB;GAE7D,OAAO,MAAMmH,SAAOwD,aAAa,EAAEC,IAAI,CAAC;EAC1C;EACAC,cAAczB;EACd0B,oBACE9K,MACA+K,aACiE;GACjE,IAAIA,SAAS7G,WAAW,UAAU;IAChC,MAAMV,UAAQtB,wBAAwBgB,QAAQO,IAAIzD,IAAE;IACpD,IAAI,CAACwD,SAAO;IAEZ,IACE,OAAOuH,SAASE,YAAY,YAC5BF,SAASE,YAAY,QACrBpE,MAAMqE,QAAQH,SAASE,OAAO,GAC9B;KACA,MAAME,SAAS,CACb;MACEH,UAAU;MACVzJ,SAAS;KACX,CAAC;KAEHsC,2BAA2B7D,MAAI;MAC7BuB,SAAS;MACT6J,YAAY,CAAC,SAAS;KACxB,CAAC;KACD,OAAOD;IACT;IAEA,MAAMA,WAAS7L,2BACbkE,QAAMhB,iBACNuI,SAASE,OACX;IACA,IAAIE,SAAOE,SAAS,GAAG;KACrB,MAAMD,aAAa,CACjB,GAAG,IAAI9G,IAAI6G,SAAO9H,KAAKS,YAAUA,QAAMkH,QAAQ,CAAC,CAAC;KAEnDnH,2BAA2B7D,MAAI;MAC7BuB,SAAS,gCAAgC6J,WAAWE,KAAK,IAAI,EAAC;MAC9DF;KACF,CAAC;KACD,OAAOD;IACT;IAEA,MAAM/I,WAAuB;KAC3B8B,QAAQ;KACR+G,SAASF,SAASE;IACpB;IACA1H,0BAA0BvD,MAAIoC,QAAM;IACpC;GACF;GAEAmB,0BAA0BvD,MAAI,EAAEkE,QAAQ6G,SAAS7G,OAAO,CAAC;EAC3D;CACF;AACF;AAEA,MAAaqH,oBAAoB7M,SAASqC,oBAAoB"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;;KAUY;;EAEV;;EAEA,UAAU;;cAiGC,+BACX,mBACC;cAsBU,8BACX,mBACC;cAsEU,6CAAe,SAAA,aAAA,OAAA"}
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,6 +1,6 @@
1
1
  import { assertValidServerId } from "../../utils/serverId.js";
2
2
  import { resource } from "@assistant-ui/tap";
3
- import { OAuthClientInformationFullSchema, OAuthTokensSchema } from "@modelcontextprotocol/core";
3
+ import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from "@modelcontextprotocol/core";
4
4
  //#region src/resources/storage/McpLocalStorage.ts
5
5
  function resolveStorage(opts) {
6
6
  if (opts.storage) return opts.storage;
@@ -61,6 +61,25 @@ const normalizeClientInformation = (value) => {
61
61
  const result = OAuthClientInformationFullSchema.safeParse(value);
62
62
  return result.success ? result.data : void 0;
63
63
  };
64
+ const isSecureNetworkUrl = (value) => {
65
+ if (!isNonEmptyString(value)) return false;
66
+ try {
67
+ const url = new URL(value);
68
+ return url.protocol === "https:" || url.protocol === "http:" && (url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname.startsWith("127.") || url.hostname === "[::1]");
69
+ } catch {
70
+ return false;
71
+ }
72
+ };
73
+ const normalizeDiscoveryState = (value) => {
74
+ if (!isRecord(value) || !isSecureNetworkUrl(value.authorizationServerUrl)) return;
75
+ const state = { authorizationServerUrl: value.authorizationServerUrl };
76
+ if (isSecureNetworkUrl(value.resourceMetadataUrl)) state.resourceMetadataUrl = value.resourceMetadataUrl;
77
+ const metadata = OAuthMetadataSchema.safeParse(value.authorizationServerMetadata);
78
+ if (metadata.success) state.authorizationServerMetadata = metadata.data;
79
+ const resourceMetadata = OAuthProtectedResourceMetadataSchema.safeParse(value.resourceMetadata);
80
+ if (resourceMetadata.success) state.resourceMetadata = resourceMetadata.data;
81
+ return state;
82
+ };
64
83
  const normalizePersistedAuthState = (value) => {
65
84
  if (!isRecord(value)) return null;
66
85
  const state = {};
@@ -70,6 +89,8 @@ const normalizePersistedAuthState = (value) => {
70
89
  if (tokens) state.tokens = tokens;
71
90
  const clientInformation = normalizeClientInformation(value.clientInformation);
72
91
  if (clientInformation) state.clientInformation = clientInformation;
92
+ const discoveryState = normalizeDiscoveryState(value.discoveryState);
93
+ if (discoveryState) state.discoveryState = discoveryState;
73
94
  return Object.keys(state).length > 0 ? state : null;
74
95
  };
75
96
  const useMcpLocalStorage = (opts = {}) => {
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.js","names":["resource","OAuthClientInformationFullSchema","OAuthTokensSchema","MCPAuthConfig","MCPCustomServerRecord","MCPPersistedAuthState","assertValidServerId","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isNonEmptyString","trim","length","isOptionalNonEmptyString","isOptionalStringArray","every","item","isOptionalConnectionTimeout","Number","isFinite","isValidServerId","id","isMCPAuthConfig","auth","type","token","scopes","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","isCustomServerRecord","name","url","createdAt","connectionTimeout","normalizeCustomServerRecord","record","normalizeCustomServerRecords","flatMap","normalizeOAuthTokens","result","safeParse","success","data","normalizeClientInformation","normalizePersistedAuthState","state","codeVerifier","tokens","clientInformation","Object","keys","useMcpLocalStorage","prefix","customServersKey","authKey","read","key","fallback","T","raw","getItem","JSON","parse","write","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport {\n OAuthClientInformationFullSchema,\n OAuthTokensSchema,\n} from \"@modelcontextprotocol/core\";\nimport type { MCPAuthConfig, MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport { assertValidServerId } from \"../../utils/serverId\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isOptionalString = (value: unknown): value is string | undefined =>\n value === undefined || typeof value === \"string\";\n\nconst isNonEmptyString = (value: unknown): value is string =>\n typeof value === \"string\" && value.trim().length > 0;\n\nconst isOptionalNonEmptyString = (\n value: unknown,\n): value is string | undefined =>\n value === undefined || isNonEmptyString(value);\n\nconst isOptionalStringArray = (value: unknown): value is string[] | undefined =>\n value === undefined ||\n (Array.isArray(value) && value.every((item) => typeof item === \"string\"));\n\nconst isOptionalConnectionTimeout = (\n value: unknown,\n): value is number | undefined =>\n value === undefined ||\n (typeof value === \"number\" && Number.isFinite(value) && value >= 0);\n\nconst isValidServerId = (id: string): boolean => {\n try {\n assertValidServerId(id);\n return true;\n } catch {\n return false;\n }\n};\n\nconst isMCPAuthConfig = (auth: unknown): auth is MCPAuthConfig => {\n if (!isRecord(auth)) return false;\n\n switch (auth.type) {\n case \"none\":\n return true;\n case \"bearer\":\n return isOptionalNonEmptyString(auth.token);\n case \"oauth\":\n return (\n isOptionalStringArray(auth.scopes) &&\n isOptionalString(auth.authorizationEndpoint) &&\n isOptionalString(auth.tokenEndpoint) &&\n isOptionalString(auth.registrationEndpoint) &&\n isOptionalString(auth.clientId) &&\n isOptionalString(auth.clientSecret)\n );\n default:\n return false;\n }\n};\n\nconst isCustomServerRecord = (\n value: unknown,\n): value is MCPCustomServerRecord => {\n if (!isRecord(value)) return false;\n if (typeof value.id !== \"string\" || !isValidServerId(value.id)) {\n return false;\n }\n return (\n isNonEmptyString(value.name) &&\n isNonEmptyString(value.url) &&\n Number.isFinite(value.createdAt) &&\n isMCPAuthConfig(value.auth) &&\n isOptionalConnectionTimeout(value.connectionTimeout)\n );\n};\n\nconst normalizeCustomServerRecord = (\n value: unknown,\n): MCPCustomServerRecord | null => {\n if (isCustomServerRecord(value)) return value;\n if (!isRecord(value)) return null;\n\n const record = { ...value };\n delete record.connectionTimeout;\n return isCustomServerRecord(record) ? record : null;\n};\n\nexport const normalizeCustomServerRecords = (\n value: unknown,\n): MCPCustomServerRecord[] => {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) => {\n const record = normalizeCustomServerRecord(item);\n return record === null ? [] : [record];\n });\n};\n\nconst normalizeOAuthTokens = (\n value: unknown,\n): MCPPersistedAuthState[\"tokens\"] | undefined => {\n const result = OAuthTokensSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nconst normalizeClientInformation = (\n value: unknown,\n): MCPPersistedAuthState[\"clientInformation\"] | undefined => {\n const result = OAuthClientInformationFullSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nexport const normalizePersistedAuthState = (\n value: unknown,\n): MCPPersistedAuthState | null => {\n if (!isRecord(value)) return null;\n\n const state: MCPPersistedAuthState = {};\n if (isNonEmptyString(value.token)) state.token = value.token;\n if (isNonEmptyString(value.codeVerifier)) {\n state.codeVerifier = value.codeVerifier;\n }\n\n const tokens = normalizeOAuthTokens(value.tokens);\n if (tokens) state.tokens = tokens;\n\n const clientInformation = normalizeClientInformation(value.clientInformation);\n if (clientInformation) state.clientInformation = clientInformation;\n\n return Object.keys(state).length > 0 ? state : null;\n};\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n normalizeCustomServerRecords(read<unknown>(customServersKey, [])),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n normalizePersistedAuthState(read<unknown>(authKey(id), null)),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":"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"}
1
+ {"version":3,"file":"McpLocalStorage.js","names":["resource","OAuthMetadataSchema","OAuthClientInformationFullSchema","OAuthProtectedResourceMetadataSchema","OAuthTokensSchema","MCPAuthConfig","MCPCustomServerRecord","MCPPersistedAuthState","assertValidServerId","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isNonEmptyString","trim","length","isOptionalNonEmptyString","isOptionalStringArray","every","item","isOptionalConnectionTimeout","Number","isFinite","isValidServerId","id","isMCPAuthConfig","auth","type","token","scopes","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","isCustomServerRecord","name","url","createdAt","connectionTimeout","normalizeCustomServerRecord","record","normalizeCustomServerRecords","flatMap","normalizeOAuthTokens","result","safeParse","success","data","normalizeClientInformation","isSecureNetworkUrl","URL","protocol","hostname","endsWith","startsWith","normalizeDiscoveryState","authorizationServerUrl","state","NonNullable","resourceMetadataUrl","metadata","authorizationServerMetadata","resourceMetadata","normalizePersistedAuthState","codeVerifier","tokens","clientInformation","discoveryState","Object","keys","useMcpLocalStorage","prefix","customServersKey","authKey","read","key","fallback","T","raw","getItem","JSON","parse","write","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport {\n OAuthMetadataSchema,\n OAuthClientInformationFullSchema,\n OAuthProtectedResourceMetadataSchema,\n OAuthTokensSchema,\n} from \"@modelcontextprotocol/core\";\nimport type { MCPAuthConfig, MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport { assertValidServerId } from \"../../utils/serverId\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isOptionalString = (value: unknown): value is string | undefined =>\n value === undefined || typeof value === \"string\";\n\nconst isNonEmptyString = (value: unknown): value is string =>\n typeof value === \"string\" && value.trim().length > 0;\n\nconst isOptionalNonEmptyString = (\n value: unknown,\n): value is string | undefined =>\n value === undefined || isNonEmptyString(value);\n\nconst isOptionalStringArray = (value: unknown): value is string[] | undefined =>\n value === undefined ||\n (Array.isArray(value) && value.every((item) => typeof item === \"string\"));\n\nconst isOptionalConnectionTimeout = (\n value: unknown,\n): value is number | undefined =>\n value === undefined ||\n (typeof value === \"number\" && Number.isFinite(value) && value >= 0);\n\nconst isValidServerId = (id: string): boolean => {\n try {\n assertValidServerId(id);\n return true;\n } catch {\n return false;\n }\n};\n\nconst isMCPAuthConfig = (auth: unknown): auth is MCPAuthConfig => {\n if (!isRecord(auth)) return false;\n\n switch (auth.type) {\n case \"none\":\n return true;\n case \"bearer\":\n return isOptionalNonEmptyString(auth.token);\n case \"oauth\":\n return (\n isOptionalStringArray(auth.scopes) &&\n isOptionalString(auth.authorizationEndpoint) &&\n isOptionalString(auth.tokenEndpoint) &&\n isOptionalString(auth.registrationEndpoint) &&\n isOptionalString(auth.clientId) &&\n isOptionalString(auth.clientSecret)\n );\n default:\n return false;\n }\n};\n\nconst isCustomServerRecord = (\n value: unknown,\n): value is MCPCustomServerRecord => {\n if (!isRecord(value)) return false;\n if (typeof value.id !== \"string\" || !isValidServerId(value.id)) {\n return false;\n }\n return (\n isNonEmptyString(value.name) &&\n isNonEmptyString(value.url) &&\n Number.isFinite(value.createdAt) &&\n isMCPAuthConfig(value.auth) &&\n isOptionalConnectionTimeout(value.connectionTimeout)\n );\n};\n\nconst normalizeCustomServerRecord = (\n value: unknown,\n): MCPCustomServerRecord | null => {\n if (isCustomServerRecord(value)) return value;\n if (!isRecord(value)) return null;\n\n const record = { ...value };\n delete record.connectionTimeout;\n return isCustomServerRecord(record) ? record : null;\n};\n\nexport const normalizeCustomServerRecords = (\n value: unknown,\n): MCPCustomServerRecord[] => {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) => {\n const record = normalizeCustomServerRecord(item);\n return record === null ? [] : [record];\n });\n};\n\nconst normalizeOAuthTokens = (\n value: unknown,\n): MCPPersistedAuthState[\"tokens\"] | undefined => {\n const result = OAuthTokensSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nconst normalizeClientInformation = (\n value: unknown,\n): MCPPersistedAuthState[\"clientInformation\"] | undefined => {\n const result = OAuthClientInformationFullSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nconst isSecureNetworkUrl = (value: unknown): value is string => {\n if (!isNonEmptyString(value)) return false;\n try {\n const url = new URL(value);\n return (\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" &&\n (url.hostname === \"localhost\" ||\n url.hostname.endsWith(\".localhost\") ||\n url.hostname.startsWith(\"127.\") ||\n url.hostname === \"[::1]\"))\n );\n } catch {\n return false;\n }\n};\n\nconst normalizeDiscoveryState = (\n value: unknown,\n): MCPPersistedAuthState[\"discoveryState\"] | undefined => {\n if (!isRecord(value) || !isSecureNetworkUrl(value.authorizationServerUrl)) {\n return undefined;\n }\n\n // A malformed optional field is dropped alone: keeping the validated\n // authorization server URL preserves the redirect-time binding, and the SDK\n // re-discovers whatever metadata is missing.\n const state: NonNullable<MCPPersistedAuthState[\"discoveryState\"]> = {\n authorizationServerUrl: value.authorizationServerUrl,\n };\n\n if (isSecureNetworkUrl(value.resourceMetadataUrl)) {\n state.resourceMetadataUrl = value.resourceMetadataUrl;\n }\n\n const metadata = OAuthMetadataSchema.safeParse(\n value.authorizationServerMetadata,\n );\n if (metadata.success) state.authorizationServerMetadata = metadata.data;\n\n const resourceMetadata = OAuthProtectedResourceMetadataSchema.safeParse(\n value.resourceMetadata,\n );\n if (resourceMetadata.success) state.resourceMetadata = resourceMetadata.data;\n\n return state;\n};\n\nexport const normalizePersistedAuthState = (\n value: unknown,\n): MCPPersistedAuthState | null => {\n if (!isRecord(value)) return null;\n\n const state: MCPPersistedAuthState = {};\n if (isNonEmptyString(value.token)) state.token = value.token;\n if (isNonEmptyString(value.codeVerifier)) {\n state.codeVerifier = value.codeVerifier;\n }\n\n const tokens = normalizeOAuthTokens(value.tokens);\n if (tokens) state.tokens = tokens;\n\n const clientInformation = normalizeClientInformation(value.clientInformation);\n if (clientInformation) state.clientInformation = clientInformation;\n\n const discoveryState = normalizeDiscoveryState(value.discoveryState);\n if (discoveryState) state.discoveryState = discoveryState;\n\n return Object.keys(state).length > 0 ? state : null;\n};\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n normalizeCustomServerRecords(read<unknown>(customServersKey, [])),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n normalizePersistedAuthState(read<unknown>(authKey(id), null)),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":";;;;AAmBA,SAASc,eAAeC,MAA8C;CACpE,IAAIA,KAAKH,SAAS,OAAOG,KAAKH;CAC9B,IAAI,OAAOI,eAAe,eAAe,kBAAkBA,YACzD,IAAI;EACF,OAAQA,WAAyCC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAMC,YAAYC,UAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACE,MAAMC,QAAQH,KAAK;AAErE,MAAMI,oBAAoBJ,UACxBA,UAAUK,KAAAA,KAAa,OAAOL,UAAU;AAE1C,MAAMM,oBAAoBN,UACxB,OAAOA,UAAU,YAAYA,MAAMO,KAAK,CAAC,CAACC,SAAS;AAErD,MAAMC,4BACJT,UAEAA,UAAUK,KAAAA,KAAaC,iBAAiBN,KAAK;AAE/C,MAAMU,yBAAyBV,UAC7BA,UAAUK,KAAAA,KACTH,MAAMC,QAAQH,KAAK,KAAKA,MAAMW,OAAOC,SAAS,OAAOA,SAAS,QAAQ;AAEzE,MAAMC,+BACJb,UAEAA,UAAUK,KAAAA,KACT,OAAOL,UAAU,YAAYc,OAAOC,SAASf,KAAK,KAAKA,SAAS;AAEnE,MAAMgB,mBAAmBC,OAAwB;CAC/C,IAAI;EACF5B,oBAAoB4B,EAAE;EACtB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAMC,mBAAmBC,SAAyC;CAChE,IAAI,CAACpB,SAASoB,IAAI,GAAG,OAAO;CAE5B,QAAQA,KAAKC,MAAb;EACE,KAAK,QACH,OAAO;EACT,KAAK,UACH,OAAOX,yBAAyBU,KAAKE,KAAK;EAC5C,KAAK,SACH,OACEX,sBAAsBS,KAAKG,MAAM,KACjClB,iBAAiBe,KAAKI,qBAAqB,KAC3CnB,iBAAiBe,KAAKK,aAAa,KACnCpB,iBAAiBe,KAAKM,oBAAoB,KAC1CrB,iBAAiBe,KAAKO,QAAQ,KAC9BtB,iBAAiBe,KAAKQ,YAAY;EAEtC,SACE,OAAO;CACX;AACF;AAEA,MAAMC,wBACJ5B,UACmC;CACnC,IAAI,CAACD,SAASC,KAAK,GAAG,OAAO;CAC7B,IAAI,OAAOA,MAAMiB,OAAO,YAAY,CAACD,gBAAgBhB,MAAMiB,EAAE,GAC3D,OAAO;CAET,OACEX,iBAAiBN,MAAM6B,IAAI,KAC3BvB,iBAAiBN,MAAM8B,GAAG,KAC1BhB,OAAOC,SAASf,MAAM+B,SAAS,KAC/Bb,gBAAgBlB,MAAMmB,IAAI,KAC1BN,4BAA4Bb,MAAMgC,iBAAiB;AAEvD;AAEA,MAAMC,+BACJjC,UACiC;CACjC,IAAI4B,qBAAqB5B,KAAK,GAAG,OAAOA;CACxC,IAAI,CAACD,SAASC,KAAK,GAAG,OAAO;CAE7B,MAAMkC,SAAS,EAAE,GAAGlC,MAAM;CAC1B,OAAOkC,OAAOF;CACd,OAAOJ,qBAAqBM,MAAM,IAAIA,SAAS;AACjD;AAEA,MAAaC,gCACXnC,UAC4B;CAC5B,IAAI,CAACE,MAAMC,QAAQH,KAAK,GAAG,OAAO,CAAA;CAClC,OAAOA,MAAMoC,SAASxB,SAAS;EAC7B,MAAMsB,SAASD,4BAA4BrB,IAAI;EAC/C,OAAOsB,WAAW,OAAO,CAAA,IAAK,CAACA,MAAM;CACvC,CAAC;AACH;AAEA,MAAMG,wBACJrC,UACgD;CAChD,MAAMsC,SAASrD,kBAAkBsD,UAAUvC,KAAK;CAChD,OAAOsC,OAAOE,UAAUF,OAAOG,OAAOpC,KAAAA;AACxC;AAEA,MAAMqC,8BACJ1C,UAC2D;CAC3D,MAAMsC,SAASvD,iCAAiCwD,UAAUvC,KAAK;CAC/D,OAAOsC,OAAOE,UAAUF,OAAOG,OAAOpC,KAAAA;AACxC;AAEA,MAAMsC,sBAAsB3C,UAAoC;CAC9D,IAAI,CAACM,iBAAiBN,KAAK,GAAG,OAAO;CACrC,IAAI;EACF,MAAM8B,MAAM,IAAIc,IAAI5C,KAAK;EACzB,OACE8B,IAAIe,aAAa,YAChBf,IAAIe,aAAa,YACff,IAAIgB,aAAa,eAChBhB,IAAIgB,SAASC,SAAS,YAAY,KAClCjB,IAAIgB,SAASE,WAAW,MAAM,KAC9BlB,IAAIgB,aAAa;CAEzB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAMG,2BACJjD,UACwD;CACxD,IAAI,CAACD,SAASC,KAAK,KAAK,CAAC2C,mBAAmB3C,MAAMkD,sBAAsB,GACtE;CAMF,MAAMC,QAA8D,EAClED,wBAAwBlD,MAAMkD,uBAChC;CAEA,IAAIP,mBAAmB3C,MAAMqD,mBAAmB,GAC9CF,MAAME,sBAAsBrD,MAAMqD;CAGpC,MAAMC,WAAWxE,oBAAoByD,UACnCvC,MAAMuD,2BACR;CACA,IAAID,SAASd,SAASW,MAAMI,8BAA8BD,SAASb;CAEnE,MAAMe,mBAAmBxE,qCAAqCuD,UAC5DvC,MAAMwD,gBACR;CACA,IAAIA,iBAAiBhB,SAASW,MAAMK,mBAAmBA,iBAAiBf;CAExE,OAAOU;AACT;AAEA,MAAaM,+BACXzD,UACiC;CACjC,IAAI,CAACD,SAASC,KAAK,GAAG,OAAO;CAE7B,MAAMmD,QAA+B,CAAC;CACtC,IAAI7C,iBAAiBN,MAAMqB,KAAK,GAAG8B,MAAM9B,QAAQrB,MAAMqB;CACvD,IAAIf,iBAAiBN,MAAM0D,YAAY,GACrCP,MAAMO,eAAe1D,MAAM0D;CAG7B,MAAMC,SAAStB,qBAAqBrC,MAAM2D,MAAM;CAChD,IAAIA,QAAQR,MAAMQ,SAASA;CAE3B,MAAMC,oBAAoBlB,2BAA2B1C,MAAM4D,iBAAiB;CAC5E,IAAIA,mBAAmBT,MAAMS,oBAAoBA;CAEjD,MAAMC,iBAAiBZ,wBAAwBjD,MAAM6D,cAAc;CACnE,IAAIA,gBAAgBV,MAAMU,iBAAiBA;CAE3C,OAAOC,OAAOC,KAAKZ,KAAK,CAAC,CAAC3C,SAAS,IAAI2C,QAAQ;AACjD;AAEA,MAAMa,sBAAsBpE,OAA+B,CAAC,MAAkB;CAC5E,MAAMqE,SAASrE,KAAKJ,aAAa;CACjC,MAAM0E,mBAAmB,GAAGD,OAAM;CAClC,MAAME,WAAWlD,OAAe,GAAGgD,OAAM,QAAShD;CAClD,MAAMxB,UAAUE,eAAeC,IAAI;CAEnC,MAAMwE,QAAWC,KAAaC,aAAmB;EAC/C,IAAI,CAAC7E,SAAS,OAAO6E;EACrB,IAAI;GACF,MAAME,MAAM/E,QAAQgF,QAAQJ,GAAG;GAC/B,IAAIG,OAAO,MAAM,OAAOF;GACxB,OAAOI,KAAKC,MAAMH,GAAG;EACvB,QAAQ;GACN,OAAOF;EACT;CACF;CAEA,MAAMM,SAASP,KAAarE,UAAyB;EACnD,IAAI,CAACP,SAAS;EACd,IAAI;GACFA,QAAQoF,QAAQR,KAAKK,KAAKI,UAAU9E,KAAK,CAAC;EAC5C,QAAQ,CACN;CAEJ;CAEA,MAAM+E,UAAUV,QAAsB;EACpC,IAAI,CAAC5E,SAAS;EACd,IAAI;GACFA,QAAQuF,WAAWX,GAAG;EACxB,QAAQ,CACN;CAEJ;CAEA,OAAO;EACLY,mBAAmB,YACjB9C,6BAA6BiC,KAAcF,kBAAkB,CAAA,CAAE,CAAC;EAClEgB,mBAAmB,OAAOC,YAAY;GACpCP,MAAMV,kBAAkBiB,OAAO;EACjC;EACAC,eAAe,OAAOnE,OACpBwC,4BAA4BW,KAAcD,QAAQlD,EAAE,GAAG,IAAI,CAAC;EAC9DoE,eAAe,OAAOpE,IAAIkC,UAAU;GAClCyB,MAAMT,QAAQlD,EAAE,GAAGkC,KAAK;EAC1B;EACAmC,gBAAgB,OAAOrE,OAAO;GAC5B8D,OAAOZ,QAAQlD,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAasE,kBAAkB1G,SAASmF,kBAAkB"}
@@ -0,0 +1,5 @@
1
+ //#region src/utils/invokeMcpCallback.d.ts
2
+ declare const invokeMcpCallback: <TArgs extends unknown[]>(name: string, callback: ((...args: TArgs) => void) | undefined, ...args: TArgs) => void;
3
+ //#endregion
4
+ export { invokeMcpCallback };
5
+ //# sourceMappingURL=invokeMcpCallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invokeMcpCallback.d.ts","names":[],"sources":["../../src/utils/invokeMcpCallback.ts"],"mappings":";cAIa,oBAAqB,yBAChC,cACA,eAAe,MAAM,gCAClB,MAAM"}
@@ -0,0 +1,19 @@
1
+ //#region src/utils/invokeMcpCallback.ts
2
+ const reportCallbackError = (name, error) => {
3
+ console.error(`[react-mcp] ${name} callback threw an error`, error);
4
+ };
5
+ const invokeMcpCallback = (name, callback, ...args) => {
6
+ if (!callback) return;
7
+ try {
8
+ const result = callback(...args);
9
+ if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") Promise.resolve(result).catch((error) => {
10
+ reportCallbackError(name, error);
11
+ });
12
+ } catch (error) {
13
+ reportCallbackError(name, error);
14
+ }
15
+ };
16
+ //#endregion
17
+ export { invokeMcpCallback };
18
+
19
+ //# sourceMappingURL=invokeMcpCallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invokeMcpCallback.js","names":["reportCallbackError","name","error","console","invokeMcpCallback","callback","args","TArgs","result","then","Promise","resolve","catch"],"sources":["../../src/utils/invokeMcpCallback.ts"],"sourcesContent":["const reportCallbackError = (name: string, error: unknown) => {\n console.error(`[react-mcp] ${name} callback threw an error`, error);\n};\n\nexport const invokeMcpCallback = <TArgs extends unknown[]>(\n name: string,\n callback: ((...args: TArgs) => void) | undefined,\n ...args: TArgs\n) => {\n if (!callback) return;\n\n try {\n const result = callback(...args) as unknown;\n if (\n result !== null &&\n (typeof result === \"object\" || typeof result === \"function\") &&\n \"then\" in result &&\n typeof result.then === \"function\"\n ) {\n void Promise.resolve(result).catch((error) => {\n reportCallbackError(name, error);\n });\n }\n } catch (error) {\n reportCallbackError(name, error);\n }\n};\n"],"mappings":";AAAA,MAAMA,uBAAuBC,MAAcC,UAAmB;CAC5DC,QAAQD,MAAM,eAAeD,KAAI,2BAA4BC,KAAK;AACpE;AAEA,MAAaE,qBACXH,MACAI,UACA,GAAGC,SACA;CACH,IAAI,CAACD,UAAU;CAEf,IAAI;EACF,MAAMG,SAASH,SAAS,GAAGC,IAAI;EAC/B,IACEE,WAAW,SACV,OAAOA,WAAW,YAAY,OAAOA,WAAW,eACjD,UAAUA,UACV,OAAOA,OAAOC,SAAS,YAEvB,QAAaE,QAAQH,MAAM,CAAC,CAACI,OAAOV,UAAU;GAC5CF,oBAAoBC,MAAMC,KAAK;EACjC,CAAC;CAEL,SAASA,OAAO;EACdF,oBAAoBC,MAAMC,KAAK;CACjC;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-mcp",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "MCP server configuration and connection primitives for @assistant-ui",
5
5
  "keywords": [
6
6
  "mcp",
@@ -30,10 +30,10 @@
30
30
  "@modelcontextprotocol/client": "^2.0.0",
31
31
  "@modelcontextprotocol/core": "^2.0.0",
32
32
  "@radix-ui/react-primitive": "^2.1.10",
33
- "@assistant-ui/core": "0.3.12",
34
- "@assistant-ui/store": "0.3.8",
35
- "@assistant-ui/tap": "0.9.11",
36
- "assistant-stream": "0.3.37"
33
+ "@assistant-ui/core": "0.3.14",
34
+ "@assistant-ui/store": "0.3.10",
35
+ "@assistant-ui/tap": "0.9.13",
36
+ "assistant-stream": "0.3.38"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@types/react": "*",
@@ -51,7 +51,7 @@
51
51
  "jsdom": "^30.0.1",
52
52
  "react": "^19.2.8",
53
53
  "vitest": "^4.1.10",
54
- "@assistant-ui/x-buildutils": "0.0.22"
54
+ "@assistant-ui/x-buildutils": "0.0.23"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public",
@@ -0,0 +1,86 @@
1
+ import type { OAuthDiscoveryState } from "@modelcontextprotocol/client";
2
+ import { describe, expect, it } from "vitest";
3
+ import type { MCPStorage } from "../resources/storage/types";
4
+ import type { MCPPersistedAuthState } from "./types";
5
+ import { createOAuthProvider } from "./createOAuthProvider";
6
+
7
+ const discoveryState: OAuthDiscoveryState = {
8
+ authorizationServerUrl: "https://auth.example.com",
9
+ resourceMetadataUrl:
10
+ "https://mcp.example.com/.well-known/oauth-protected-resource",
11
+ authorizationServerMetadata: {
12
+ issuer: "https://auth.example.com",
13
+ authorization_endpoint: "https://auth.example.com/authorize",
14
+ token_endpoint: "https://auth.example.com/token",
15
+ response_types_supported: ["code"],
16
+ },
17
+ resourceMetadata: {
18
+ resource: "https://mcp.example.com",
19
+ authorization_servers: ["https://auth.example.com"],
20
+ },
21
+ };
22
+
23
+ const createStorage = (initial: MCPPersistedAuthState | null = null) => {
24
+ let state = initial;
25
+ const storage: MCPStorage = {
26
+ loadCustomServers: async () => [],
27
+ saveCustomServers: async () => {},
28
+ loadAuthState: async () => state,
29
+ saveAuthState: async (_serverId, next) => {
30
+ state = next;
31
+ },
32
+ clearAuthState: async () => {
33
+ state = null;
34
+ },
35
+ };
36
+ return { storage, getState: () => state };
37
+ };
38
+
39
+ const createProvider = (storage: MCPStorage) =>
40
+ createOAuthProvider({
41
+ serverId: "docs",
42
+ config: { type: "oauth" },
43
+ storage,
44
+ redirectUri: "http://localhost/callback",
45
+ onAuthorizationUrl: () => {},
46
+ });
47
+
48
+ describe("createOAuthProvider discovery state", () => {
49
+ it("persists discovery state alongside the PKCE verifier", async () => {
50
+ const { storage, getState } = createStorage({
51
+ codeVerifier: "pkce-verifier",
52
+ });
53
+ const provider = createProvider(storage);
54
+
55
+ await provider.saveDiscoveryState?.(discoveryState);
56
+
57
+ expect(getState()).toEqual({
58
+ codeVerifier: "pkce-verifier",
59
+ discoveryState,
60
+ });
61
+ });
62
+
63
+ it("restores discovery state on the OAuth callback leg", async () => {
64
+ const { storage } = createStorage({ discoveryState });
65
+ const provider = createProvider(storage);
66
+
67
+ await expect(provider.discoveryState?.()).resolves.toEqual(discoveryState);
68
+ });
69
+
70
+ it.each(["discovery", "all"] as const)(
71
+ "clears discovery state through the %s invalidation scope",
72
+ async (scope) => {
73
+ const { storage, getState } = createStorage({
74
+ codeVerifier: "pkce-verifier",
75
+ discoveryState,
76
+ });
77
+ const provider = createProvider(storage);
78
+
79
+ await provider.invalidateCredentials?.(scope);
80
+
81
+ expect(getState()).toEqual(
82
+ scope === "all" ? {} : { codeVerifier: "pkce-verifier" },
83
+ );
84
+ },
85
+ );
86
+ });
@@ -2,6 +2,7 @@ import type {
2
2
  OAuthClientProvider,
3
3
  OAuthClientInformationFull,
4
4
  OAuthClientMetadata,
5
+ OAuthDiscoveryState,
5
6
  OAuthTokens,
6
7
  } from "@modelcontextprotocol/client";
7
8
  import type { MCPStorage } from "../resources/storage/types";
@@ -70,6 +71,7 @@ export function createOAuthProvider(
70
71
  tokens?: OAuthTokens | undefined;
71
72
  clientInformation?: OAuthClientInformationFull | undefined;
72
73
  codeVerifier?: string | undefined;
74
+ discoveryState?: OAuthDiscoveryState | undefined;
73
75
  };
74
76
  let cached: Cache | null = null;
75
77
 
@@ -89,6 +91,8 @@ export function createOAuthProvider(
89
91
  initial.clientInformation = persisted.clientInformation;
90
92
  }
91
93
  if (persisted?.codeVerifier) initial.codeVerifier = persisted.codeVerifier;
94
+ if (persisted?.discoveryState)
95
+ initial.discoveryState = persisted.discoveryState;
92
96
  cached = initial;
93
97
  return cached;
94
98
  };
@@ -100,6 +104,7 @@ export function createOAuthProvider(
100
104
  if (c.tokens) next.tokens = c.tokens;
101
105
  if (c.clientInformation) next.clientInformation = c.clientInformation;
102
106
  if (c.codeVerifier) next.codeVerifier = c.codeVerifier;
107
+ if (c.discoveryState) next.discoveryState = c.discoveryState;
103
108
  await storage.saveAuthState(serverId, next);
104
109
  };
105
110
 
@@ -161,11 +166,21 @@ export function createOAuthProvider(
161
166
  }
162
167
  return c.codeVerifier;
163
168
  },
169
+ async saveDiscoveryState(discoveryState) {
170
+ const c = await loadCache();
171
+ c.discoveryState = discoveryState;
172
+ await persist();
173
+ },
174
+ async discoveryState() {
175
+ const c = await loadCache();
176
+ return c.discoveryState;
177
+ },
164
178
  async invalidateCredentials(scope) {
165
179
  const c = await loadCache();
166
180
  if (scope === "all" || scope === "tokens") delete c.tokens;
167
181
  if (scope === "all" || scope === "client") delete c.clientInformation;
168
182
  if (scope === "all" || scope === "verifier") delete c.codeVerifier;
183
+ if (scope === "all" || scope === "discovery") delete c.discoveryState;
169
184
  await persist();
170
185
  },
171
186
  };
package/src/auth/types.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import type {
2
2
  OAuthTokens,
3
3
  OAuthClientInformationFull,
4
+ OAuthDiscoveryState,
4
5
  } from "@modelcontextprotocol/client";
5
6
 
6
7
  export type MCPPersistedAuthState = {
7
8
  tokens?: OAuthTokens;
8
9
  clientInformation?: OAuthClientInformationFull;
9
10
  codeVerifier?: string;
11
+ discoveryState?: OAuthDiscoveryState;
10
12
  /** Bearer token (entered at add-form time). */
11
13
  token?: string;
12
14
  };
@@ -1,36 +1,7 @@
1
1
  import { type FC, type ReactNode, useEffect, useRef, useState } from "react";
2
2
  import { useAui } from "@assistant-ui/store";
3
3
  import { decodeServerIdFromState } from "../auth/createOAuthProvider";
4
-
5
- type McpOAuthCallbackName = "onComplete" | "onError";
6
-
7
- const reportCallbackError = (name: McpOAuthCallbackName, error: unknown) => {
8
- console.error(`[react-mcp] ${name} callback threw an error`, error);
9
- };
10
-
11
- const invokeMcpOAuthCallback = <TArgs extends unknown[]>(
12
- name: McpOAuthCallbackName,
13
- callback: ((...args: TArgs) => void) | undefined,
14
- ...args: TArgs
15
- ) => {
16
- if (!callback) return;
17
-
18
- try {
19
- const result = callback(...args) as unknown;
20
- if (
21
- result !== null &&
22
- (typeof result === "object" || typeof result === "function") &&
23
- "then" in result &&
24
- typeof result.then === "function"
25
- ) {
26
- void Promise.resolve(result).catch((error) => {
27
- reportCallbackError(name, error);
28
- });
29
- }
30
- } catch (error) {
31
- reportCallbackError(name, error);
32
- }
33
- };
4
+ import { invokeMcpCallback } from "../utils/invokeMcpCallback";
34
5
 
35
6
  export const createMcpOAuthCallbackError = (
36
7
  err: unknown,
@@ -104,15 +75,11 @@ export function useMcpOAuthCallback(
104
75
  } catch (err) {
105
76
  const error = createMcpOAuthCallbackError(err, serverId);
106
77
  setResult({ status: "error", serverId, error });
107
- invokeMcpOAuthCallback("onError", optsRef.current.onError, error);
78
+ invokeMcpCallback("onError", optsRef.current.onError, error);
108
79
  return;
109
80
  }
110
81
 
111
- invokeMcpOAuthCallback(
112
- "onComplete",
113
- optsRef.current.onComplete,
114
- serverId,
115
- );
82
+ invokeMcpCallback("onComplete", optsRef.current.onComplete, serverId);
116
83
  })();
117
84
  }, [aui, opts.url]);
118
85