@assistant-ui/react-mcp 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/buildHeaders.d.ts +0 -1
- package/dist/auth/buildHeaders.d.ts.map +1 -1
- package/dist/auth/createOAuthProvider.d.ts +4 -3
- package/dist/auth/createOAuthProvider.d.ts.map +1 -1
- package/dist/auth/createOAuthProvider.js +2 -1
- package/dist/auth/createOAuthProvider.js.map +1 -1
- package/dist/auth/types.d.ts +2 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/connector.d.ts +0 -1
- package/dist/connector.d.ts.map +1 -1
- package/dist/context/McpConnectorByIndexProvider.d.ts +0 -1
- package/dist/context/McpConnectorByIndexProvider.d.ts.map +1 -1
- package/dist/context/McpCustomServerByIndexProvider.d.ts +0 -1
- package/dist/context/McpCustomServerByIndexProvider.d.ts.map +1 -1
- package/dist/context/McpServerByIdProvider.d.ts +0 -1
- package/dist/context/McpServerByIdProvider.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.d.ts +4 -3
- package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.js +14 -8
- package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
- package/dist/mcp-scope.d.ts +19 -6
- package/dist/mcp-scope.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthFields.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormAuthFields.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormCancel.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormCancel.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormError.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormError.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormNameField.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormNameField.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormRoot.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormRoot.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormSubmit.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormSubmit.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormUrlField.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormUrlField.d.ts.map +1 -1
- package/dist/primitives/addForm/context.d.ts +0 -1
- package/dist/primitives/addForm/context.d.ts.map +1 -1
- package/dist/primitives/addForm.d.ts +0 -2
- package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts +0 -1
- package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerConnectors.d.ts +0 -1
- package/dist/primitives/manager/McpManagerConnectors.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerCustomServers.d.ts +0 -1
- package/dist/primitives/manager/McpManagerCustomServers.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerRoot.d.ts +0 -1
- package/dist/primitives/manager/McpManagerRoot.d.ts.map +1 -1
- package/dist/primitives/manager.d.ts +0 -2
- package/dist/primitives/server/McpServerConnectButton.d.ts +0 -1
- package/dist/primitives/server/McpServerConnectButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerDisconnectButton.d.ts +0 -1
- package/dist/primitives/server/McpServerDisconnectButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerError.d.ts +0 -1
- package/dist/primitives/server/McpServerError.d.ts.map +1 -1
- package/dist/primitives/server/McpServerIcon.d.ts +4 -3
- package/dist/primitives/server/McpServerIcon.d.ts.map +1 -1
- package/dist/primitives/server/McpServerName.d.ts +0 -1
- package/dist/primitives/server/McpServerName.d.ts.map +1 -1
- package/dist/primitives/server/McpServerOAuthLink.d.ts +4 -3
- package/dist/primitives/server/McpServerOAuthLink.d.ts.map +1 -1
- package/dist/primitives/server/McpServerRemoveButton.d.ts +0 -1
- package/dist/primitives/server/McpServerRemoveButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerRoot.d.ts +0 -1
- package/dist/primitives/server/McpServerRoot.d.ts.map +1 -1
- package/dist/primitives/server/McpServerStatus.d.ts +0 -1
- package/dist/primitives/server/McpServerStatus.d.ts.map +1 -1
- package/dist/primitives/server/McpServerToolName.d.ts +0 -1
- package/dist/primitives/server/McpServerToolName.d.ts.map +1 -1
- package/dist/primitives/server/McpServerTools.d.ts +0 -1
- package/dist/primitives/server/McpServerTools.d.ts.map +1 -1
- package/dist/primitives/server.d.ts +0 -2
- package/dist/resources/McpManagerResource.d.ts +6 -3
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +122 -107
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts +1 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +28 -5
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/resources/storage/McpCustomStorage.d.ts +0 -1
- package/dist/resources/storage/McpCustomStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.d.ts +8 -3
- package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.js +55 -3
- package/dist/resources/storage/McpLocalStorage.js.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts +0 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
- package/dist/resources/storage/types.d.ts +0 -1
- package/dist/resources/storage/types.d.ts.map +1 -1
- package/dist/utils/serverId.d.ts +2 -1
- package/dist/utils/serverId.d.ts.map +1 -1
- package/dist/utils/serverId.js +8 -1
- package/dist/utils/serverId.js.map +1 -1
- package/package.json +11 -8
- package/src/hooks/useMcpOAuthCallback.test.ts +24 -0
- package/src/hooks/useMcpOAuthCallback.tsx +20 -5
- package/src/mcp-scope.ts +5 -0
- package/src/resources/McpManagerResource.test.ts +40 -0
- package/src/resources/McpManagerResource.ts +21 -2
- package/src/resources/McpServerResource.test.ts +364 -0
- package/src/resources/McpServerResource.ts +51 -3
- package/src/resources/storage/McpLocalStorage.test.ts +184 -0
- package/src/resources/storage/McpLocalStorage.ts +113 -3
- package/src/utils/serverId.ts +12 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpManagerResource.js","names":["useState","useEffect","useMemo","useEffectEvent","useRef","useResource","resource","withKey","useClientLookup","useAssistantClientRef","attachTransformScopes","ClientOutput","ModelContext","Tool","McpServerResource","McpLocalStorage","MCPStorageElement","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","defaultRedirectUri","window","location","origin","NO_CONNECTORS","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","t3","signal","markHydrated","cancelled","current","records","loadCustomServers","t4","error","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t5","t6","records_0","saveCustomServers","persistCustomServers","t7","t8","t9","t10","c","kind","name","url","icon","auth","onRemove","_temp2","connectorElements","t11","s","prev_0","x","customElements","serverElements","lookup","all","state","_temp3","_temp4","t12","servers","out","server","connectionState","tool","tools","fullName","type","description","undefined","parameters","inputSchema","execute","args","get","key","callTool","toolkit","clientRef","t13","t14","client","modelContext","register","getModelContext","t15","index","list","entry","Error","serverByKind","t16","t17","query","t18","t19","t20","index_0","t21","index_1","record","crypto","randomUUID","Date","now","Math","random","toString","slice","createdAt","prev_1","some","c_0","remove","clearAuthState","prev_2","s_2","t22","getState","connector","customServer","addCustomServer","removeServer","McpManagerResource","scopes","parent","source","s_0","s_1"],"sources":["../../src/resources/McpManagerResource.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useEffectEvent, useRef } from \"react\";\nimport { useResource, resource, withKey } from \"@assistant-ui/tap\";\nimport {\n useClientLookup,\n useAssistantClientRef,\n attachTransformScopes,\n type ClientOutput,\n} from \"@assistant-ui/store\";\nimport { ModelContext } from \"@assistant-ui/core/store\";\nimport type { Tool } from \"assistant-stream\";\nimport { McpServerResource } from \"./McpServerResource\";\nimport { McpLocalStorage } from \"./storage/McpLocalStorage\";\nimport type { MCPStorageElement } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnector,\n MCPCustomServerRecord,\n MCPManagerState,\n} from \"../mcp-scope\";\n\nexport type McpManagerResourceProps = {\n connectors?: MCPConnector[] | undefined;\n storage?: MCPStorageElement | undefined;\n /** OAuth redirect target. Defaults to `${origin}/mcp/callback`. */\n oauthRedirectUri?: string | undefined;\n /** Connect on mount when usable auth exists. Default true. */\n autoConnect?: boolean | undefined;\n};\n\nfunction defaultRedirectUri(): string {\n if (typeof window === \"undefined\") return \"\";\n return `${window.location.origin}/mcp/callback`;\n}\n\n// Stable empty fallback so an absent `connectors` prop doesn't produce a fresh\n// array each render (which would invalidate the serverElements memo below).\nconst NO_CONNECTORS: MCPConnector[] = [];\n\nconst useMcpManagerResource = (\n props: McpManagerResourceProps,\n): ClientOutput<\"mcp\"> => {\n const connectors = props.connectors ?? NO_CONNECTORS;\n const autoConnect = props.autoConnect ?? true;\n const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();\n\n const storageElement = props.storage ?? McpLocalStorage();\n const storage = useResource(storageElement);\n\n const [customServers, setCustomServers] = useState<MCPCustomServerRecord[]>(\n [],\n );\n const [isHydrated, setIsHydrated] = useState(false);\n\n const hydratedRef = useRef(false);\n\n const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {\n const markHydrated = () => {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n };\n\n let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;\n try {\n records = await storage.loadCustomServers();\n } catch (error) {\n markHydrated();\n throw error;\n }\n\n if (signal.cancelled) return;\n // Merge rather than replace so any addCustomServer calls that\n // happened before hydration resolved aren't silently overwritten.\n // Persisted order wins; pre-hydration locals append.\n setCustomServers((prev) => {\n if (prev.length === 0) return records;\n const persistedIds = new Set(records.map((r) => r.id));\n return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];\n });\n markHydrated();\n });\n\n useEffect(() => {\n const signal = { cancelled: false };\n void hydrate(signal);\n return () => {\n signal.cancelled = true;\n };\n }, []);\n\n const persistCustomServers = useEffectEvent(\n async (records: MCPCustomServerRecord[]) => {\n if (!hydratedRef.current) return;\n await storage.saveCustomServers(records);\n },\n );\n\n useEffect(() => {\n void persistCustomServers(customServers);\n }, [customServers]);\n\n const serverElements = useMemo(() => {\n const connectorElements = connectors.map((c) =>\n withKey(\n c.id,\n McpServerResource({\n id: c.id,\n kind: \"connector\",\n name: c.name,\n url: c.url,\n icon: c.icon,\n auth: c.auth,\n storage,\n redirectUri,\n autoConnect,\n onRemove: async () => {\n // connectors cannot be removed\n },\n }),\n ),\n );\n const customElements = customServers.map((s) =>\n withKey(\n s.id,\n McpServerResource({\n id: s.id,\n kind: \"custom\",\n name: s.name,\n url: s.url,\n auth: s.auth,\n storage,\n redirectUri,\n autoConnect,\n onRemove: async () => {\n setCustomServers((prev) => prev.filter((x) => x.id !== s.id));\n },\n }),\n ),\n );\n return [...connectorElements, ...customElements];\n }, [connectors, customServers, storage, redirectUri, autoConnect]);\n\n const lookup = useClientLookup(serverElements);\n\n const state = useMemo<MCPManagerState>(() => {\n const all = lookup.state;\n return {\n servers: all,\n connectors: all.filter((s) => s.kind === \"connector\"),\n customServers: all.filter((s) => s.kind === \"custom\"),\n isHydrated,\n };\n }, [lookup.state, isHydrated]);\n\n // ─── Auto-register MCP tools as frontend tools in modelContext ─────\n // Build the toolkit from connected servers; re-register when the visible\n // tool surface changes. Tool names are prefixed with the server id to\n // avoid collisions across connected servers — connector ids must not\n // contain `__` (enforced by `defineConnector`); `addCustomServer`\n // generates UUIDs that satisfy the constraint by construction.\n const toolkit = useMemo<Record<string, Tool<any, any>>>(() => {\n const out: Record<string, Tool<any, any>> = {};\n for (const server of state.servers) {\n if (server.connectionState !== \"connected\") continue;\n for (const tool of server.tools) {\n const fullName = `${server.id}__${tool.name}`;\n out[fullName] = {\n type: \"frontend\",\n ...(tool.description !== undefined\n ? { description: tool.description }\n : {}),\n parameters: tool.inputSchema as never,\n execute: (args) =>\n lookup.get({ key: server.id }).callTool(tool.name, args as unknown),\n };\n }\n }\n return out;\n }, [state, lookup]);\n\n const clientRef = useAssistantClientRef();\n\n useEffect(() => {\n const client = clientRef.current;\n if (!client) return;\n return client.modelContext().register({\n getModelContext: () => ({ tools: toolkit }),\n });\n }, [toolkit, clientRef]);\n\n const serverByKind = (kind: \"connector\" | \"custom\", index: number) => {\n const list = kind === \"connector\" ? state.connectors : state.customServers;\n const entry = list[index];\n if (!entry) {\n throw new Error(\n `McpManagerResource: no ${kind} at index ${index} (length ${list.length})`,\n );\n }\n return lookup.get({ key: entry.id });\n };\n\n return {\n getState: () => state,\n server: (query) => {\n if (\"id\" in query) return lookup.get({ key: query.id });\n return serverByKind(query.kind, query.index);\n },\n connector: ({ index }) => serverByKind(\"connector\", index),\n customServer: ({ index }) => serverByKind(\"custom\", index),\n addCustomServer: async ({ name, url, auth }) => {\n const record: MCPCustomServerRecord = {\n id:\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name,\n url,\n auth: auth as MCPAuthConfig,\n createdAt: Date.now(),\n };\n setCustomServers((prev) => [...prev, record]);\n return record.id;\n },\n removeServer: async (id) => {\n // removeServer is custom-server only — connectors are app-defined\n // and not user-removable. Refuse rather than silently no-op.\n if (state.connectors.some((c) => c.id === id)) {\n throw new Error(\n `Cannot remove connector \"${id}\" — connectors are app-defined and not removable. Use a custom server id instead.`,\n );\n }\n // Delegate to McpServerResource.remove() which disconnects,\n // clears auth state, and unregisters from customServers in one\n // place. Fallback to manual cleanup if the lookup is empty\n // (server already gone).\n try {\n await lookup.get({ key: id }).remove();\n } catch {\n await storage.clearAuthState(id);\n setCustomServers((prev) => prev.filter((s) => s.id !== id));\n }\n },\n };\n};\n\nexport const McpManagerResource = resource(useMcpManagerResource);\n\n// Ensure modelContext exists as a sibling when the manager mounts. If an\n// ancestor (e.g. a chat runtime) already provides modelContext, this is a\n// no-op; otherwise it's auto-mounted alongside `mcp`.\nattachTransformScopes(useMcpManagerResource, (scopes, parent) => {\n if (!scopes.modelContext && parent.modelContext.source === null) {\n scopes.modelContext = ModelContext();\n }\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpManagerResource.js","names":["useState","useEffect","useMemo","useEffectEvent","useRef","useResource","resource","withKey","useClientLookup","useAssistantClientRef","attachTransformScopes","ClientOutput","ModelContext","Tool","McpServerResource","McpLocalStorage","MCPStorageElement","assertUniqueServerIds","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","connectionTimeout","defaultRedirectUri","window","location","origin","NO_CONNECTORS","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","t3","signal","markHydrated","cancelled","current","records","loadCustomServers","t4","error","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t5","t6","records_0","saveCustomServers","persistCustomServers","t7","t8","_temp2","_temp3","t9","t10","c_0","c","kind","name","url","icon","auth","onRemove","_temp4","connectorElements","t11","s_0","s","prev_0","x","customElements","serverElements","lookup","all","state","_temp5","_temp6","t12","servers","out","server","connectionState","tool","tools","fullName","type","description","undefined","parameters","inputSchema","execute","args","get","key","callTool","toolkit","clientRef","t13","t14","client","modelContext","register","getModelContext","t15","index","list","entry","Error","serverByKind","t16","t17","query","t18","t19","t20","index_0","t21","index_1","connectionTimeout_0","record","crypto","randomUUID","Date","now","Math","random","toString","slice","createdAt","prev_1","some","c_1","remove","clearAuthState","prev_2","s_3","t22","getState","connector","customServer","addCustomServer","removeServer","McpManagerResource","scopes","parent","source","s_1","s_2"],"sources":["../../src/resources/McpManagerResource.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useEffectEvent, useRef } from \"react\";\nimport { useResource, resource, withKey } from \"@assistant-ui/tap\";\nimport {\n useClientLookup,\n useAssistantClientRef,\n attachTransformScopes,\n type ClientOutput,\n} from \"@assistant-ui/store\";\nimport { ModelContext } from \"@assistant-ui/core/store\";\nimport type { Tool } from \"assistant-stream\";\nimport { McpServerResource } from \"./McpServerResource\";\nimport { McpLocalStorage } from \"./storage/McpLocalStorage\";\nimport type { MCPStorageElement } from \"./storage/types\";\nimport { assertUniqueServerIds } from \"../utils/serverId\";\nimport type {\n MCPAuthConfig,\n MCPConnector,\n MCPCustomServerRecord,\n MCPManagerState,\n} from \"../mcp-scope\";\n\nexport type McpManagerResourceProps = {\n connectors?: MCPConnector[] | undefined;\n storage?: MCPStorageElement | undefined;\n /** OAuth redirect target. Defaults to `${origin}/mcp/callback`. */\n oauthRedirectUri?: string | undefined;\n /** Connect on mount when usable auth exists. Default true. */\n autoConnect?: boolean | undefined;\n /** Optional timeout in milliseconds for connect/listTools calls. Disabled by default. */\n connectionTimeout?: number | undefined;\n};\n\nfunction defaultRedirectUri(): string {\n if (typeof window === \"undefined\") return \"\";\n return `${window.location.origin}/mcp/callback`;\n}\n\n// Stable empty fallback so an absent `connectors` prop doesn't produce a fresh\n// array each render (which would invalidate the serverElements memo below).\nconst NO_CONNECTORS: MCPConnector[] = [];\n\nconst useMcpManagerResource = (\n props: McpManagerResourceProps,\n): ClientOutput<\"mcp\"> => {\n const connectors = props.connectors ?? NO_CONNECTORS;\n const autoConnect = props.autoConnect ?? true;\n const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();\n const connectionTimeout = props.connectionTimeout;\n\n const storageElement = props.storage ?? McpLocalStorage();\n const storage = useResource(storageElement);\n\n const [customServers, setCustomServers] = useState<MCPCustomServerRecord[]>(\n [],\n );\n const [isHydrated, setIsHydrated] = useState(false);\n\n const hydratedRef = useRef(false);\n\n const hydrate = useEffectEvent(async (signal: { cancelled: boolean }) => {\n const markHydrated = () => {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n };\n\n let records: Awaited<ReturnType<typeof storage.loadCustomServers>>;\n try {\n records = await storage.loadCustomServers();\n } catch (error) {\n markHydrated();\n throw error;\n }\n\n if (signal.cancelled) return;\n // Merge rather than replace so any addCustomServer calls that\n // happened before hydration resolved aren't silently overwritten.\n // Persisted order wins; pre-hydration locals append.\n setCustomServers((prev) => {\n if (prev.length === 0) return records;\n const persistedIds = new Set(records.map((r) => r.id));\n return [...records, ...prev.filter((r) => !persistedIds.has(r.id))];\n });\n markHydrated();\n });\n\n useEffect(() => {\n const signal = { cancelled: false };\n void hydrate(signal);\n return () => {\n signal.cancelled = true;\n };\n }, []);\n\n const persistCustomServers = useEffectEvent(\n async (records: MCPCustomServerRecord[]) => {\n if (!hydratedRef.current) return;\n await storage.saveCustomServers(records);\n },\n );\n\n useEffect(() => {\n void persistCustomServers(customServers);\n }, [customServers]);\n\n const serverElements = useMemo(() => {\n assertUniqueServerIds([\n ...connectors.map((c) => c.id),\n ...customServers.map((s) => s.id),\n ]);\n\n const connectorElements = connectors.map((c) =>\n withKey(\n c.id,\n McpServerResource({\n id: c.id,\n kind: \"connector\",\n name: c.name,\n url: c.url,\n icon: c.icon,\n auth: c.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: c.connectionTimeout ?? connectionTimeout,\n onRemove: async () => {\n // connectors cannot be removed\n },\n }),\n ),\n );\n const customElements = customServers.map((s) =>\n withKey(\n s.id,\n McpServerResource({\n id: s.id,\n kind: \"custom\",\n name: s.name,\n url: s.url,\n auth: s.auth,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout: s.connectionTimeout ?? connectionTimeout,\n onRemove: async () => {\n setCustomServers((prev) => prev.filter((x) => x.id !== s.id));\n },\n }),\n ),\n );\n return [...connectorElements, ...customElements];\n }, [\n connectors,\n customServers,\n storage,\n redirectUri,\n autoConnect,\n connectionTimeout,\n ]);\n\n const lookup = useClientLookup(serverElements);\n\n const state = useMemo<MCPManagerState>(() => {\n const all = lookup.state;\n return {\n servers: all,\n connectors: all.filter((s) => s.kind === \"connector\"),\n customServers: all.filter((s) => s.kind === \"custom\"),\n isHydrated,\n };\n }, [lookup.state, isHydrated]);\n\n // ─── Auto-register MCP tools as frontend tools in modelContext ─────\n // Build the toolkit from connected servers; re-register when the visible\n // tool surface changes. Tool names are prefixed with the server id to\n // avoid collisions across connected servers — connector ids must not\n // contain `__` (enforced by `defineConnector`); `addCustomServer`\n // generates UUIDs that satisfy the constraint by construction.\n const toolkit = useMemo<Record<string, Tool<any, any>>>(() => {\n const out: Record<string, Tool<any, any>> = {};\n for (const server of state.servers) {\n if (server.connectionState !== \"connected\") continue;\n for (const tool of server.tools) {\n const fullName = `${server.id}__${tool.name}`;\n out[fullName] = {\n type: \"frontend\",\n ...(tool.description !== undefined\n ? { description: tool.description }\n : {}),\n parameters: tool.inputSchema as never,\n execute: (args) =>\n lookup.get({ key: server.id }).callTool(tool.name, args as unknown),\n };\n }\n }\n return out;\n }, [state, lookup]);\n\n const clientRef = useAssistantClientRef();\n\n useEffect(() => {\n const client = clientRef.current;\n if (!client) return;\n return client.modelContext().register({\n getModelContext: () => ({ tools: toolkit }),\n });\n }, [toolkit, clientRef]);\n\n const serverByKind = (kind: \"connector\" | \"custom\", index: number) => {\n const list = kind === \"connector\" ? state.connectors : state.customServers;\n const entry = list[index];\n if (!entry) {\n throw new Error(\n `McpManagerResource: no ${kind} at index ${index} (length ${list.length})`,\n );\n }\n return lookup.get({ key: entry.id });\n };\n\n return {\n getState: () => state,\n server: (query) => {\n if (\"id\" in query) return lookup.get({ key: query.id });\n return serverByKind(query.kind, query.index);\n },\n connector: ({ index }) => serverByKind(\"connector\", index),\n customServer: ({ index }) => serverByKind(\"custom\", index),\n addCustomServer: async ({ name, url, auth, connectionTimeout }) => {\n const record: MCPCustomServerRecord = {\n id:\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name,\n url,\n auth: auth as MCPAuthConfig,\n connectionTimeout,\n createdAt: Date.now(),\n };\n setCustomServers((prev) => [...prev, record]);\n return record.id;\n },\n removeServer: async (id) => {\n // removeServer is custom-server only — connectors are app-defined\n // and not user-removable. Refuse rather than silently no-op.\n if (state.connectors.some((c) => c.id === id)) {\n throw new Error(\n `Cannot remove connector \"${id}\" — connectors are app-defined and not removable. Use a custom server id instead.`,\n );\n }\n // Delegate to McpServerResource.remove() which disconnects,\n // clears auth state, and unregisters from customServers in one\n // place. Fallback to manual cleanup if the lookup is empty\n // (server already gone).\n try {\n await lookup.get({ key: id }).remove();\n } catch {\n await storage.clearAuthState(id);\n setCustomServers((prev) => prev.filter((s) => s.id !== id));\n }\n },\n };\n};\n\nexport const McpManagerResource = resource(useMcpManagerResource);\n\n// Ensure modelContext exists as a sibling when the manager mounts. If an\n// ancestor (e.g. a chat runtime) already provides modelContext, this is a\n// no-op; otherwise it's auto-mounted alongside `mcp`.\nattachTransformScopes(useMcpManagerResource, (scopes, parent) => {\n if (!scopes.modelContext && parent.modelContext.source === null) {\n scopes.modelContext = ModelContext();\n }\n});\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MCPAuthConfig, MCPServerKind } from "../mcp-scope.js";
|
|
2
2
|
import { MCPStorage } from "./storage/types.js";
|
|
3
3
|
import { ClientOutput } from "@assistant-ui/store";
|
|
4
|
-
|
|
5
4
|
//#region src/resources/McpServerResource.d.ts
|
|
6
5
|
type McpServerResourceProps = {
|
|
7
6
|
id: string;
|
|
@@ -13,6 +12,7 @@ type McpServerResourceProps = {
|
|
|
13
12
|
storage: MCPStorage;
|
|
14
13
|
redirectUri: string;
|
|
15
14
|
autoConnect: boolean;
|
|
15
|
+
connectionTimeout?: number | undefined;
|
|
16
16
|
onRemove: () => Promise<void>;
|
|
17
17
|
};
|
|
18
18
|
declare const McpServerResource: import("@assistant-ui/tap").Resource<ClientOutput<"mcpServer">, [props: McpServerResourceProps]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;KAsBY;EACV;EACA,MAAM;EACN;EACA;EACA;EACA,MAAM;EACN,SAAS;EACT;EACA;EACA;EACA,gBAAgB;;cAySL,+CAAiB,SAAA,4BAAA,OAAA"}
|
|
@@ -15,6 +15,21 @@ const useMcpServerResource = (props) => {
|
|
|
15
15
|
const [authorizationUrl, setAuthorizationUrl] = useState(null);
|
|
16
16
|
const clientRef = useRef(null);
|
|
17
17
|
const transportRef = useRef(null);
|
|
18
|
+
const withConnectionTimeout = useEffectEvent(async (promise, phase, startedAt) => {
|
|
19
|
+
const timeoutMs = props.connectionTimeout;
|
|
20
|
+
if (timeoutMs === void 0) return await promise;
|
|
21
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
22
|
+
const timeoutError = () => /* @__PURE__ */ new Error(`MCP server "${props.id}" timed out while ${phase} after ${timeoutMs}ms.`);
|
|
23
|
+
if (remainingMs <= 0) throw timeoutError();
|
|
24
|
+
let timeout;
|
|
25
|
+
try {
|
|
26
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
27
|
+
timeout = setTimeout(() => reject(timeoutError()), remainingMs);
|
|
28
|
+
})]);
|
|
29
|
+
} finally {
|
|
30
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
18
33
|
const buildTransport = useEffectEvent(async () => {
|
|
19
34
|
if (props.auth.type === "oauth") {
|
|
20
35
|
const authProvider = createOAuthProvider({
|
|
@@ -40,8 +55,9 @@ const useMcpServerResource = (props) => {
|
|
|
40
55
|
name: "assistant-ui-mcp",
|
|
41
56
|
version: "0.0.0"
|
|
42
57
|
});
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
const startedAt_0 = Date.now();
|
|
59
|
+
await withConnectionTimeout(client.connect(transport), "connecting", startedAt_0);
|
|
60
|
+
const list = await withConnectionTimeout(client.listTools(), "listing tools", startedAt_0);
|
|
45
61
|
clientRef.current = client;
|
|
46
62
|
transportRef.current = transport;
|
|
47
63
|
setTools(list.tools.map((t) => {
|
|
@@ -107,8 +123,10 @@ const useMcpServerResource = (props) => {
|
|
|
107
123
|
await finalizeConnect(transport_1);
|
|
108
124
|
} catch (err_0) {
|
|
109
125
|
await closeTransport();
|
|
110
|
-
|
|
126
|
+
const error = err_0 instanceof Error ? err_0 : new Error(String(err_0));
|
|
127
|
+
setLastError({ message: error.message });
|
|
111
128
|
setConnectionState("error");
|
|
129
|
+
throw error;
|
|
112
130
|
}
|
|
113
131
|
});
|
|
114
132
|
const tryAutoConnect = useEffectEvent(async (signal) => {
|
|
@@ -177,10 +195,15 @@ const useMcpServerResource = (props) => {
|
|
|
177
195
|
arguments: args
|
|
178
196
|
});
|
|
179
197
|
},
|
|
180
|
-
|
|
198
|
+
listResources: async (params) => {
|
|
181
199
|
const client_1 = clientRef.current;
|
|
182
200
|
if (!client_1) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
183
|
-
return await client_1.
|
|
201
|
+
return await client_1.listResources(params);
|
|
202
|
+
},
|
|
203
|
+
readResource: async (uri) => {
|
|
204
|
+
const client_2 = clientRef.current;
|
|
205
|
+
if (!client_2) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
206
|
+
return await client_2.readResource({ uri });
|
|
184
207
|
},
|
|
185
208
|
completeAuth: doCompleteAuth
|
|
186
209
|
};
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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","connectionTimeout","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","clientRef","transportRef","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","undefined","remainingMs","Date","now","timeoutError","Error","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","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","closeTransport","close","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","get","finishAuth","error","tryAutoConnect","signal","cancelled","token","tokens","catch","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","params","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 connectionTimeout?: number | undefined;\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 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 finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n const startedAt = Date.now();\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 withConnectionTimeout(\n client.connect(transport as unknown as Transport),\n \"connecting\",\n startedAt,\n );\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 withConnectionTimeout(\n client.listTools(),\n \"listing tools\",\n startedAt,\n );\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 const error = err instanceof Error ? err : new Error(String(err));\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 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 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 };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;cAOa,8CAAgB,SAAA,aAAA,MAAA"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { MCPCustomServerRecord } from "../../mcp-scope.js";
|
|
2
|
+
import { MCPPersistedAuthState } from "../../auth/types.js";
|
|
1
3
|
import { MCPStorage } from "./types.js";
|
|
2
|
-
|
|
3
4
|
//#region src/resources/storage/McpLocalStorage.d.ts
|
|
4
5
|
type McpLocalStorageOptions = {
|
|
5
|
-
/** Namespace prefix for keys. Default "aui-mcp". */
|
|
6
|
+
/** Namespace prefix for keys. Default "aui-mcp". */
|
|
7
|
+
keyPrefix?: string;
|
|
8
|
+
/** Override the underlying Storage. Defaults to globalThis.localStorage. */
|
|
6
9
|
storage?: Storage;
|
|
7
10
|
};
|
|
11
|
+
declare const normalizeCustomServerRecords: (value: unknown) => MCPCustomServerRecord[];
|
|
12
|
+
declare const normalizePersistedAuthState: (value: unknown) => MCPPersistedAuthState | null;
|
|
8
13
|
declare const McpLocalStorage: import("@assistant-ui/tap").Resource<MCPStorage, [opts?: McpLocalStorageOptions | undefined]>;
|
|
9
14
|
//#endregion
|
|
10
|
-
export { McpLocalStorage, McpLocalStorageOptions };
|
|
15
|
+
export { McpLocalStorage, McpLocalStorageOptions, normalizeCustomServerRecords, normalizePersistedAuthState };
|
|
11
16
|
//# sourceMappingURL=McpLocalStorage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;;KAUY;;EAEV;;EAEA,UAAU;;cA+EC,+BACX,mBACC;cAmBU,8BACX,mBACC;cAsEU,6CAAe,SAAA,aAAA,OAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { assertValidServerId } from "../../utils/serverId.js";
|
|
1
2
|
import { resource } from "@assistant-ui/tap";
|
|
3
|
+
import { OAuthClientInformationFullSchema, OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
4
|
//#region src/resources/storage/McpLocalStorage.ts
|
|
3
5
|
function resolveStorage(opts) {
|
|
4
6
|
if (opts.storage) return opts.storage;
|
|
@@ -9,6 +11,56 @@ function resolveStorage(opts) {
|
|
|
9
11
|
}
|
|
10
12
|
return null;
|
|
11
13
|
}
|
|
14
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
const isOptionalString = (value) => value === void 0 || typeof value === "string";
|
|
16
|
+
const isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
17
|
+
const isOptionalNonEmptyString = (value) => value === void 0 || isNonEmptyString(value);
|
|
18
|
+
const isOptionalStringArray = (value) => value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
19
|
+
const isValidServerId = (id) => {
|
|
20
|
+
try {
|
|
21
|
+
assertValidServerId(id);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const isMCPAuthConfig = (auth) => {
|
|
28
|
+
if (!isRecord(auth)) return false;
|
|
29
|
+
switch (auth.type) {
|
|
30
|
+
case "none": return true;
|
|
31
|
+
case "bearer": return isOptionalNonEmptyString(auth.token);
|
|
32
|
+
case "oauth": return isOptionalStringArray(auth.scopes) && isOptionalString(auth.authorizationEndpoint) && isOptionalString(auth.tokenEndpoint) && isOptionalString(auth.registrationEndpoint) && isOptionalString(auth.clientId) && isOptionalString(auth.clientSecret);
|
|
33
|
+
default: return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const isCustomServerRecord = (value) => {
|
|
37
|
+
if (!isRecord(value)) return false;
|
|
38
|
+
if (typeof value.id !== "string" || !isValidServerId(value.id)) return false;
|
|
39
|
+
return isNonEmptyString(value.name) && isNonEmptyString(value.url) && Number.isFinite(value.createdAt) && isMCPAuthConfig(value.auth);
|
|
40
|
+
};
|
|
41
|
+
const normalizeCustomServerRecords = (value) => {
|
|
42
|
+
if (!Array.isArray(value)) return [];
|
|
43
|
+
return value.filter(isCustomServerRecord);
|
|
44
|
+
};
|
|
45
|
+
const normalizeOAuthTokens = (value) => {
|
|
46
|
+
const result = OAuthTokensSchema.safeParse(value);
|
|
47
|
+
return result.success ? result.data : void 0;
|
|
48
|
+
};
|
|
49
|
+
const normalizeClientInformation = (value) => {
|
|
50
|
+
const result = OAuthClientInformationFullSchema.safeParse(value);
|
|
51
|
+
return result.success ? result.data : void 0;
|
|
52
|
+
};
|
|
53
|
+
const normalizePersistedAuthState = (value) => {
|
|
54
|
+
if (!isRecord(value)) return null;
|
|
55
|
+
const state = {};
|
|
56
|
+
if (isNonEmptyString(value.token)) state.token = value.token;
|
|
57
|
+
if (isNonEmptyString(value.codeVerifier)) state.codeVerifier = value.codeVerifier;
|
|
58
|
+
const tokens = normalizeOAuthTokens(value.tokens);
|
|
59
|
+
if (tokens) state.tokens = tokens;
|
|
60
|
+
const clientInformation = normalizeClientInformation(value.clientInformation);
|
|
61
|
+
if (clientInformation) state.clientInformation = clientInformation;
|
|
62
|
+
return Object.keys(state).length > 0 ? state : null;
|
|
63
|
+
};
|
|
12
64
|
const useMcpLocalStorage = (opts = {}) => {
|
|
13
65
|
const prefix = opts.keyPrefix ?? "aui-mcp";
|
|
14
66
|
const customServersKey = `${prefix}:custom-servers`;
|
|
@@ -37,11 +89,11 @@ const useMcpLocalStorage = (opts = {}) => {
|
|
|
37
89
|
} catch {}
|
|
38
90
|
};
|
|
39
91
|
return {
|
|
40
|
-
loadCustomServers: async () => read(customServersKey, []),
|
|
92
|
+
loadCustomServers: async () => normalizeCustomServerRecords(read(customServersKey, [])),
|
|
41
93
|
saveCustomServers: async (records) => {
|
|
42
94
|
write(customServersKey, records);
|
|
43
95
|
},
|
|
44
|
-
loadAuthState: async (id) => read(authKey(id), null),
|
|
96
|
+
loadAuthState: async (id) => normalizePersistedAuthState(read(authKey(id), null)),
|
|
45
97
|
saveAuthState: async (id, state) => {
|
|
46
98
|
write(authKey(id), state);
|
|
47
99
|
},
|
|
@@ -52,6 +104,6 @@ const useMcpLocalStorage = (opts = {}) => {
|
|
|
52
104
|
};
|
|
53
105
|
const McpLocalStorage = resource(useMcpLocalStorage);
|
|
54
106
|
//#endregion
|
|
55
|
-
export { McpLocalStorage };
|
|
107
|
+
export { McpLocalStorage, normalizeCustomServerRecords, normalizePersistedAuthState };
|
|
56
108
|
|
|
57
109
|
//# sourceMappingURL=McpLocalStorage.js.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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","isValidServerId","id","isMCPAuthConfig","auth","type","token","scopes","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","isCustomServerRecord","name","url","Number","isFinite","createdAt","normalizeCustomServerRecords","filter","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/sdk/shared/auth.js\";\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 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 );\n};\n\nexport const normalizeCustomServerRecords = (\n value: unknown,\n): MCPCustomServerRecord[] => {\n if (!Array.isArray(value)) return [];\n return value.filter(isCustomServerRecord);\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;cAuBa,8CAAgB,SAAA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MCPCustomServerRecord } from "../../mcp-scope.js";
|
|
2
2
|
import { MCPPersistedAuthState } from "../../auth/types.js";
|
|
3
3
|
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
|
-
|
|
5
4
|
//#region src/resources/storage/types.d.ts
|
|
6
5
|
type MCPStorage = {
|
|
7
6
|
loadCustomServers: () => Promise<MCPCustomServerRecord[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/resources/storage/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/resources/storage/types.ts"],"mappings":";;;;KAIY;EACV,yBAAyB,QAAQ;EACjC,oBAAoB,SAAS,4BAA4B;EACzD,gBAAgB,qBAAqB,QAAQ;EAC7C,gBACE,kBACA,OAAO,0BACJ;EACL,iBAAiB,qBAAqB;;KAG5B,oBAAoB,gBAAgB"}
|
package/dist/utils/serverId.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//#region src/utils/serverId.d.ts
|
|
2
2
|
declare function assertValidServerId(id: string): void;
|
|
3
|
+
declare function assertUniqueServerIds(ids: Iterable<string>): void;
|
|
3
4
|
//#endregion
|
|
4
|
-
export { assertValidServerId };
|
|
5
|
+
export { assertUniqueServerIds, assertValidServerId };
|
|
5
6
|
//# sourceMappingURL=serverId.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverId.d.ts","names":[],"sources":["../../src/utils/serverId.ts"],"mappings":";iBAIgB,
|
|
1
|
+
{"version":3,"file":"serverId.d.ts","names":[],"sources":["../../src/utils/serverId.ts"],"mappings":";iBAIgB,oBAAoB;iBAgBpB,sBAAsB,KAAK"}
|
package/dist/utils/serverId.js
CHANGED
|
@@ -4,7 +4,14 @@ function assertValidServerId(id) {
|
|
|
4
4
|
if (id.includes("__")) throw new Error(`MCP server id "${id}" must not contain "__" — that sequence is reserved for separating server id from tool name.`);
|
|
5
5
|
if (id.includes("")) throw new Error(`MCP server id "${id}" must not contain the ASCII Unit Separator (\\x1f) — it is reserved as the list delimiter in primitive id-list selectors.`);
|
|
6
6
|
}
|
|
7
|
+
function assertUniqueServerIds(ids) {
|
|
8
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
if (seen.has(id)) throw new Error(`McpManagerResource received duplicate MCP server id "${id}". Server ids must be unique because they are used for lookups, OAuth routing, and tool name prefixes.`);
|
|
11
|
+
seen.add(id);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
7
14
|
//#endregion
|
|
8
|
-
export { assertValidServerId };
|
|
15
|
+
export { assertUniqueServerIds, assertValidServerId };
|
|
9
16
|
|
|
10
17
|
//# sourceMappingURL=serverId.js.map
|
|
@@ -1 +1 @@
|
|
|
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"}
|
|
1
|
+
{"version":3,"file":"serverId.js","names":["assertValidServerId","id","length","Error","includes","assertUniqueServerIds","ids","Iterable","seen","Set","has","add"],"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\nexport function assertUniqueServerIds(ids: Iterable<string>): void {\n const seen = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n throw new Error(\n `McpManagerResource received duplicate MCP server id \"${id}\". Server ids must be unique because they are used for lookups, OAuth routing, and tool name prefixes.`,\n );\n }\n seen.add(id);\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;AAEA,SAAgBI,sBAAsBC,KAA6B;CACjE,MAAME,uBAAO,IAAIC,IAAY;CAC7B,KAAK,MAAMR,MAAMK,KAAK;EACpB,IAAIE,KAAKE,IAAIT,EAAE,GACb,MAAM,IAAIE,MACR,wDAAwDF,GAAE,uGAC5D;EAEFO,KAAKG,IAAIV,EAAE;CACb;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "MCP server configuration and connection primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
-
"@radix-ui/react-primitive": "^2.1.
|
|
32
|
-
"@assistant-ui/core": "0.2.
|
|
33
|
-
"@assistant-ui/store": "0.2.
|
|
34
|
-
"@assistant-ui/tap": "0.9.
|
|
35
|
-
"assistant-stream": "0.3.
|
|
31
|
+
"@radix-ui/react-primitive": "^2.1.7",
|
|
32
|
+
"@assistant-ui/core": "0.2.21",
|
|
33
|
+
"@assistant-ui/store": "0.2.20",
|
|
34
|
+
"@assistant-ui/tap": "0.9.4",
|
|
35
|
+
"assistant-stream": "0.3.26"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@types/react": "*",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/react": "^19.2.17",
|
|
48
48
|
"react": "^19.2.7",
|
|
49
|
-
"
|
|
49
|
+
"vitest": "^4.1.10",
|
|
50
|
+
"@assistant-ui/x-buildutils": "0.0.18"
|
|
50
51
|
},
|
|
51
52
|
"publishConfig": {
|
|
52
53
|
"access": "public",
|
|
@@ -62,6 +63,8 @@
|
|
|
62
63
|
"url": "https://github.com/assistant-ui/assistant-ui/issues"
|
|
63
64
|
},
|
|
64
65
|
"scripts": {
|
|
65
|
-
"build": "aui-build"
|
|
66
|
+
"build": "aui-build",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:watch": "vitest"
|
|
66
69
|
}
|
|
67
70
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMcpOAuthCallbackError } from "./useMcpOAuthCallback";
|
|
3
|
+
|
|
4
|
+
describe("createMcpOAuthCallbackError", () => {
|
|
5
|
+
it("adds MCP OAuth callback context without a server id", () => {
|
|
6
|
+
const cause = new Error('missing "state" parameter');
|
|
7
|
+
const error = createMcpOAuthCallbackError(cause, null);
|
|
8
|
+
|
|
9
|
+
expect(error.message).toBe(
|
|
10
|
+
'MCP OAuth callback failed: missing "state" parameter',
|
|
11
|
+
);
|
|
12
|
+
expect(error.cause).toBe(cause);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("adds MCP OAuth callback context with a server id", () => {
|
|
16
|
+
const cause = new Error("invalid_grant");
|
|
17
|
+
const error = createMcpOAuthCallbackError(cause, "github");
|
|
18
|
+
|
|
19
|
+
expect(error.message).toBe(
|
|
20
|
+
'MCP OAuth callback for server "github" failed: invalid_grant',
|
|
21
|
+
);
|
|
22
|
+
expect(error.cause).toBe(cause);
|
|
23
|
+
});
|
|
24
|
+
});
|