@assistant-ui/react-mcp 0.0.10 → 0.0.12

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 (70) hide show
  1. package/dist/auth/buildHeaders.js.map +1 -1
  2. package/dist/auth/createOAuthProvider.js.map +1 -1
  3. package/dist/connector.js.map +1 -1
  4. package/dist/context/McpConnectorByIndexProvider.js +29 -6
  5. package/dist/context/McpConnectorByIndexProvider.js.map +1 -1
  6. package/dist/context/McpCustomServerByIndexProvider.js +29 -6
  7. package/dist/context/McpCustomServerByIndexProvider.js.map +1 -1
  8. package/dist/context/McpServerByIdProvider.js +29 -6
  9. package/dist/context/McpServerByIdProvider.js.map +1 -1
  10. package/dist/hooks/useMcpOAuthCallback.js +30 -2
  11. package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
  12. package/dist/primitives/addForm/McpAddFormAuthFields.js +59 -16
  13. package/dist/primitives/addForm/McpAddFormAuthFields.js.map +1 -1
  14. package/dist/primitives/addForm/McpAddFormAuthSelect.js +33 -9
  15. package/dist/primitives/addForm/McpAddFormAuthSelect.js.map +1 -1
  16. package/dist/primitives/addForm/McpAddFormCancel.js +24 -7
  17. package/dist/primitives/addForm/McpAddFormCancel.js.map +1 -1
  18. package/dist/primitives/addForm/McpAddFormError.js +16 -5
  19. package/dist/primitives/addForm/McpAddFormError.js.map +1 -1
  20. package/dist/primitives/addForm/McpAddFormNameField.js +25 -8
  21. package/dist/primitives/addForm/McpAddFormNameField.js.map +1 -1
  22. package/dist/primitives/addForm/McpAddFormRoot.js +10 -10
  23. package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
  24. package/dist/primitives/addForm/McpAddFormSubmit.js +20 -7
  25. package/dist/primitives/addForm/McpAddFormSubmit.js.map +1 -1
  26. package/dist/primitives/addForm/McpAddFormUrlField.js +26 -9
  27. package/dist/primitives/addForm/McpAddFormUrlField.js.map +1 -1
  28. package/dist/primitives/addForm/context.js.map +1 -1
  29. package/dist/primitives/addForm.js.map +1 -1
  30. package/dist/primitives/manager/McpManagerAddCustomTrigger.js +14 -5
  31. package/dist/primitives/manager/McpManagerAddCustomTrigger.js.map +1 -1
  32. package/dist/primitives/manager/McpManagerConnectors.js.map +1 -1
  33. package/dist/primitives/manager/McpManagerCustomServers.js.map +1 -1
  34. package/dist/primitives/manager/McpManagerRoot.js +19 -6
  35. package/dist/primitives/manager/McpManagerRoot.js.map +1 -1
  36. package/dist/primitives/manager.js.map +1 -1
  37. package/dist/primitives/server/McpServerConnectButton.js +31 -9
  38. package/dist/primitives/server/McpServerConnectButton.js.map +1 -1
  39. package/dist/primitives/server/McpServerDisconnectButton.js +28 -8
  40. package/dist/primitives/server/McpServerDisconnectButton.js.map +1 -1
  41. package/dist/primitives/server/McpServerError.js +20 -6
  42. package/dist/primitives/server/McpServerError.js.map +1 -1
  43. package/dist/primitives/server/McpServerIcon.js +41 -9
  44. package/dist/primitives/server/McpServerIcon.js.map +1 -1
  45. package/dist/primitives/server/McpServerName.js +20 -6
  46. package/dist/primitives/server/McpServerName.js.map +1 -1
  47. package/dist/primitives/server/McpServerOAuthLink.js +35 -9
  48. package/dist/primitives/server/McpServerOAuthLink.js.map +1 -1
  49. package/dist/primitives/server/McpServerRemoveButton.js +28 -8
  50. package/dist/primitives/server/McpServerRemoveButton.js.map +1 -1
  51. package/dist/primitives/server/McpServerRoot.js +37 -12
  52. package/dist/primitives/server/McpServerRoot.js.map +1 -1
  53. package/dist/primitives/server/McpServerStatus.js +22 -7
  54. package/dist/primitives/server/McpServerStatus.js.map +1 -1
  55. package/dist/primitives/server/McpServerToolName.js +18 -6
  56. package/dist/primitives/server/McpServerToolName.js.map +1 -1
  57. package/dist/primitives/server/McpServerTools.js +31 -6
  58. package/dist/primitives/server/McpServerTools.js.map +1 -1
  59. package/dist/primitives/server.js.map +1 -1
  60. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  61. package/dist/resources/McpManagerResource.js +286 -99
  62. package/dist/resources/McpManagerResource.js.map +1 -1
  63. package/dist/resources/McpServerResource.js +31 -31
  64. package/dist/resources/McpServerResource.js.map +1 -1
  65. package/dist/resources/storage/McpCustomStorage.js.map +1 -1
  66. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  67. package/dist/resources/storage/McpMemoryStorage.js.map +1 -1
  68. package/dist/utils/serverId.js.map +1 -1
  69. package/package.json +6 -6
  70. package/src/resources/McpManagerResource.ts +21 -13
@@ -1 +1 @@
1
- {"version":3,"file":"McpServerResource.js","names":[],"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 { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\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 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\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\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 finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await client.connect(transport as unknown as Transport);\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 list = await client.listTools();\n clientRef.current = client;\n transportRef.current = transport;\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 setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\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 // 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);\n } catch (err) {\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 transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\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 setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\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 transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"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 signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\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 }),\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 ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\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 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 };\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","StreamableHTTPClientTransportOptions","UnauthorizedError","Transport","createOAuthProvider","buildHeaders","assertValidServerId","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","clientRef","transportRef","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","finalizeConnect","transport","client","version","connect","list","listTools","current","map","t","info","inputSchema","description","undefined","closeTransport","close","doConnect","err","Error","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","get","finishAuth","tryAutoConnect","signal","cancelled","token","tokens","catch","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","readResource","uri","completeAuth","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 { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\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 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\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\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 finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await client.connect(transport as unknown as Transport);\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 list = await client.listTools();\n clientRef.current = client;\n transportRef.current = transport;\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 setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\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 // 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);\n } catch (err) {\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 transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\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 setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\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 transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"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 signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\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 }),\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 ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\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 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 };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpCustomStorage.js","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {\n return impl;\n};\n\nexport const McpCustomStorage = resource(useMcpCustomStorage);\n"],"mappings":";;AAGA,MAAM,uBAAuB,SAAiC;CAC5D,OAAO;AACT;AAEA,MAAa,mBAAmB,SAAS,mBAAmB"}
1
+ {"version":3,"file":"McpCustomStorage.js","names":["resource","MCPStorage","useMcpCustomStorage","impl","McpCustomStorage"],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {\n return impl;\n};\n\nexport const McpCustomStorage = resource(useMcpCustomStorage);\n"],"mappings":";;AAGA,MAAME,uBAAuBC,SAAiC;CAC5D,OAAOA;AACT;AAEA,MAAaC,mBAAmBJ,SAASE,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.js","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\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 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 read<MCPCustomServerRecord[]>(customServersKey, []),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n read<MCPPersistedAuthState | null>(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":";;AAYA,SAAS,eAAe,MAA8C;CACpE,IAAI,KAAK,SAAS,OAAO,KAAK;CAC9B,IAAI,OAAO,eAAe,eAAe,kBAAkB,YACzD,IAAI;EACF,OAAQ,WAAyC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAM,sBAAsB,OAA+B,CAAC,MAAkB;CAC5E,MAAM,SAAS,KAAK,aAAa;CACjC,MAAM,mBAAmB,GAAG,OAAO;CACnC,MAAM,WAAW,OAAe,GAAG,OAAO,QAAQ;CAClD,MAAM,UAAU,eAAe,IAAI;CAEnC,MAAM,QAAW,KAAa,aAAmB;EAC/C,IAAI,CAAC,SAAS,OAAO;EACrB,IAAI;GACF,MAAM,MAAM,QAAQ,QAAQ,GAAG;GAC/B,IAAI,OAAO,MAAM,OAAO;GACxB,OAAO,KAAK,MAAM,GAAG;EACvB,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,SAAS,KAAa,UAAyB;EACnD,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;EAC5C,QAAQ,CAER;CACF;CAEA,MAAM,UAAU,QAAsB;EACpC,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,WAAW,GAAG;EACxB,QAAQ,CAER;CACF;CAEA,OAAO;EACL,mBAAmB,YACjB,KAA8B,kBAAkB,CAAC,CAAC;EACpD,mBAAmB,OAAO,YAAY;GACpC,MAAM,kBAAkB,OAAO;EACjC;EACA,eAAe,OAAO,OACpB,KAAmC,QAAQ,EAAE,GAAG,IAAI;EACtD,eAAe,OAAO,IAAI,UAAU;GAClC,MAAM,QAAQ,EAAE,GAAG,KAAK;EAC1B;EACA,gBAAgB,OAAO,OAAO;GAC5B,OAAO,QAAQ,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAa,kBAAkB,SAAS,kBAAkB"}
1
+ {"version":3,"file":"McpLocalStorage.js","names":["resource","MCPCustomServerRecord","MCPPersistedAuthState","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","useMcpLocalStorage","prefix","customServersKey","authKey","id","read","key","fallback","T","raw","getItem","JSON","parse","write","value","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","state","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\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 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 read<MCPCustomServerRecord[]>(customServersKey, []),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n read<MCPPersistedAuthState | null>(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":";;AAYA,SAASQ,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,sBAAsBH,OAA+B,CAAC,MAAkB;CAC5E,MAAMI,SAASJ,KAAKJ,aAAa;CACjC,MAAMS,mBAAmB,GAAGD,OAAM;CAClC,MAAME,WAAWC,OAAe,GAAGH,OAAM,QAASG;CAClD,MAAMV,UAAUE,eAAeC,IAAI;CAEnC,MAAMQ,QAAWC,KAAaC,aAAmB;EAC/C,IAAI,CAACb,SAAS,OAAOa;EACrB,IAAI;GACF,MAAME,MAAMf,QAAQgB,QAAQJ,GAAG;GAC/B,IAAIG,OAAO,MAAM,OAAOF;GACxB,OAAOI,KAAKC,MAAMH,GAAG;EACvB,QAAQ;GACN,OAAOF;EACT;CACF;CAEA,MAAMM,SAASP,KAAaQ,UAAyB;EACnD,IAAI,CAACpB,SAAS;EACd,IAAI;GACFA,QAAQqB,QAAQT,KAAKK,KAAKK,UAAUF,KAAK,CAAC;EAC5C,QAAQ,CACN;CAEJ;CAEA,MAAMG,UAAUX,QAAsB;EACpC,IAAI,CAACZ,SAAS;EACd,IAAI;GACFA,QAAQwB,WAAWZ,GAAG;EACxB,QAAQ,CACN;CAEJ;CAEA,OAAO;EACLa,mBAAmB,YACjBd,KAA8BH,kBAAkB,CAAA,CAAE;EACpDkB,mBAAmB,OAAOC,YAAY;GACpCR,MAAMX,kBAAkBmB,OAAO;EACjC;EACAC,eAAe,OAAOlB,OACpBC,KAAmCF,QAAQC,EAAE,GAAG,IAAI;EACtDmB,eAAe,OAAOnB,IAAIoB,UAAU;GAClCX,MAAMV,QAAQC,EAAE,GAAGoB,KAAK;EAC1B;EACAC,gBAAgB,OAAOrB,OAAO;GAC5Ba,OAAOd,QAAQC,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAasB,kBAAkBtC,SAASY,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpMemoryStorage.js","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpMemoryStorage = (): MCPStorage => {\n let servers: MCPCustomServerRecord[] = [];\n const auth = new Map<string, MCPPersistedAuthState>();\n return {\n loadCustomServers: async () => [...servers],\n saveCustomServers: async (records) => {\n servers = [...records];\n },\n loadAuthState: async (id) => auth.get(id) ?? null,\n saveAuthState: async (id, state) => {\n auth.set(id, state);\n },\n clearAuthState: async (id) => {\n auth.delete(id);\n },\n };\n};\n\nexport const McpMemoryStorage = resource(useMcpMemoryStorage);\n"],"mappings":";;AAKA,MAAM,4BAAwC;CAC5C,IAAI,UAAmC,CAAC;CACxC,MAAM,uBAAO,IAAI,IAAmC;CACpD,OAAO;EACL,mBAAmB,YAAY,CAAC,GAAG,OAAO;EAC1C,mBAAmB,OAAO,YAAY;GACpC,UAAU,CAAC,GAAG,OAAO;EACvB;EACA,eAAe,OAAO,OAAO,KAAK,IAAI,EAAE,KAAK;EAC7C,eAAe,OAAO,IAAI,UAAU;GAClC,KAAK,IAAI,IAAI,KAAK;EACpB;EACA,gBAAgB,OAAO,OAAO;GAC5B,KAAK,OAAO,EAAE;EAChB;CACF;AACF;AAEA,MAAa,mBAAmB,SAAS,mBAAmB"}
1
+ {"version":3,"file":"McpMemoryStorage.js","names":["resource","MCPCustomServerRecord","MCPPersistedAuthState","MCPStorage","useMcpMemoryStorage","servers","auth","Map","loadCustomServers","saveCustomServers","records","loadAuthState","id","get","saveAuthState","state","set","clearAuthState","delete","McpMemoryStorage"],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpMemoryStorage = (): MCPStorage => {\n let servers: MCPCustomServerRecord[] = [];\n const auth = new Map<string, MCPPersistedAuthState>();\n return {\n loadCustomServers: async () => [...servers],\n saveCustomServers: async (records) => {\n servers = [...records];\n },\n loadAuthState: async (id) => auth.get(id) ?? null,\n saveAuthState: async (id, state) => {\n auth.set(id, state);\n },\n clearAuthState: async (id) => {\n auth.delete(id);\n },\n };\n};\n\nexport const McpMemoryStorage = resource(useMcpMemoryStorage);\n"],"mappings":";;AAKA,MAAMI,4BAAwC;CAC5C,IAAIC,UAAmC,CAAA;CACvC,MAAMC,uBAAO,IAAIC,IAAmC;CACpD,OAAO;EACLC,mBAAmB,YAAY,CAAC,GAAGH,OAAO;EAC1CI,mBAAmB,OAAOC,YAAY;GACpCL,UAAU,CAAC,GAAGK,OAAO;EACvB;EACAC,eAAe,OAAOC,OAAON,KAAKO,IAAID,EAAE,KAAK;EAC7CE,eAAe,OAAOF,IAAIG,UAAU;GAClCT,KAAKU,IAAIJ,IAAIG,KAAK;EACpB;EACAE,gBAAgB,OAAOL,OAAO;GAC5BN,KAAKY,OAAON,EAAE;EAChB;CACF;AACF;AAEA,MAAaO,mBAAmBnB,SAASI,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"serverId.js","names":[],"sources":["../../src/utils/serverId.ts"],"sourcesContent":["// Server ids are joined with tool names via `__` to form unique tool\n// names for the AI SDK. The separator must not appear inside ids,\n// otherwise two distinct `(serverId, toolName)` pairs could produce\n// the same composite name.\nexport function assertValidServerId(id: string): void {\n if (id.length === 0) {\n throw new Error(\"MCP server id must not be empty.\");\n }\n if (id.includes(\"__\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain \"__\" — that sequence is reserved for separating server id from tool name.`,\n );\n }\n if (id.includes(\"\\x1f\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain the ASCII Unit Separator (\\\\x1f) — it is reserved as the list delimiter in primitive id-list selectors.`,\n );\n }\n}\n"],"mappings":";AAIA,SAAgB,oBAAoB,IAAkB;CACpD,IAAI,GAAG,WAAW,GAChB,MAAM,IAAI,MAAM,kCAAkC;CAEpD,IAAI,GAAG,SAAS,IAAI,GAClB,MAAM,IAAI,MACR,kBAAkB,GAAG,6FACvB;CAEF,IAAI,GAAG,SAAS,GAAM,GACpB,MAAM,IAAI,MACR,kBAAkB,GAAG,2HACvB;AAEJ"}
1
+ {"version":3,"file":"serverId.js","names":["assertValidServerId","id","length","Error","includes"],"sources":["../../src/utils/serverId.ts"],"sourcesContent":["// Server ids are joined with tool names via `__` to form unique tool\n// names for the AI SDK. The separator must not appear inside ids,\n// otherwise two distinct `(serverId, toolName)` pairs could produce\n// the same composite name.\nexport function assertValidServerId(id: string): void {\n if (id.length === 0) {\n throw new Error(\"MCP server id must not be empty.\");\n }\n if (id.includes(\"__\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain \"__\" — that sequence is reserved for separating server id from tool name.`,\n );\n }\n if (id.includes(\"\\x1f\")) {\n throw new Error(\n `MCP server id \"${id}\" must not contain the ASCII Unit Separator (\\\\x1f) — it is reserved as the list delimiter in primitive id-list selectors.`,\n );\n }\n}\n"],"mappings":";AAIA,SAAgBA,oBAAoBC,IAAkB;CACpD,IAAIA,GAAGC,WAAW,GAChB,MAAM,IAAIC,MAAM,kCAAkC;CAEpD,IAAIF,GAAGG,SAAS,IAAI,GAClB,MAAM,IAAID,MACR,kBAAkBF,GAAE,6FACtB;CAEF,IAAIA,GAAGG,SAAS,GAAM,GACpB,MAAM,IAAID,MACR,kBAAkBF,GAAE,2HACtB;AAEJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-mcp",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "MCP server configuration and connection primitives for @assistant-ui",
5
5
  "keywords": [
6
6
  "mcp",
@@ -29,10 +29,10 @@
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.29.0",
31
31
  "@radix-ui/react-primitive": "^2.1.5",
32
- "@assistant-ui/core": "0.2.14",
33
- "@assistant-ui/store": "0.2.16",
34
- "@assistant-ui/tap": "0.7.1",
35
- "assistant-stream": "0.3.21"
32
+ "@assistant-ui/store": "0.2.18",
33
+ "assistant-stream": "0.3.23",
34
+ "@assistant-ui/tap": "0.9.1",
35
+ "@assistant-ui/core": "0.2.16"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@types/react": "*",
@@ -46,7 +46,7 @@
46
46
  "devDependencies": {
47
47
  "@types/react": "^19.2.17",
48
48
  "react": "^19.2.7",
49
- "@assistant-ui/x-buildutils": "0.0.12"
49
+ "@assistant-ui/x-buildutils": "0.0.14"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public",
@@ -54,23 +54,31 @@ const useMcpManagerResource = (
54
54
  const hydratedRef = useRef(false);
55
55
 
56
56
  const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {
57
- try {
58
- const records = await storage.loadCustomServers();
59
- if (signal.cancelled) return;
60
- // Merge rather than replace so any addCustomServer calls that
61
- // happened before hydration resolved aren't silently overwritten.
62
- // Persisted order wins; pre-hydration locals append.
63
- setCustomServers((prev) => {
64
- if (prev.length === 0) return records;
65
- const persistedIds = new Set(records.map((r) => r.id));
66
- return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];
67
- });
68
- } finally {
57
+ const markHydrated = () => {
69
58
  if (!signal.cancelled) {
70
59
  hydratedRef.current = true;
71
60
  setIsHydrated(true);
72
61
  }
62
+ };
63
+
64
+ let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;
65
+ try {
66
+ records = await storage.loadCustomServers();
67
+ } catch (error) {
68
+ markHydrated();
69
+ throw error;
73
70
  }
71
+
72
+ if (signal.cancelled) return;
73
+ // Merge rather than replace so any addCustomServer calls that
74
+ // happened before hydration resolved aren't silently overwritten.
75
+ // Persisted order wins; pre-hydration locals append.
76
+ setCustomServers((prev) => {
77
+ if (prev.length === 0) return records;
78
+ const persistedIds = new Set(records.map((r) => r.id));
79
+ return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];
80
+ });
81
+ markHydrated();
74
82
  });
75
83
 
76
84
  useEffect(() => {
@@ -133,7 +141,7 @@ const useMcpManagerResource = (
133
141
  return [...connectorElements, ...customElements];
134
142
  }, [connectors, customServers, storage, redirectUri, autoConnect]);
135
143
 
136
- const lookup = useClientLookup(() => serverElements, [serverElements]);
144
+ const lookup = useClientLookup(serverElements);
137
145
 
138
146
  const state = useMemo<MCPManagerState>(() => {
139
147
  const all = lookup.state;