@assistant-ui/react-mcp 0.1.3 → 0.1.4
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/context/McpConnectorByIndexProvider.js +16 -19
- package/dist/context/McpConnectorByIndexProvider.js.map +1 -1
- package/dist/context/McpCustomServerByIndexProvider.js +16 -19
- package/dist/context/McpCustomServerByIndexProvider.js.map +1 -1
- package/dist/context/McpServerByIdProvider.js +16 -19
- package/dist/context/McpServerByIdProvider.js.map +1 -1
- package/dist/primitives/server/McpServerRemoveButton.js +2 -1
- package/dist/primitives/server/McpServerRemoveButton.js.map +1 -1
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +155 -139
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +7 -2
- package/dist/resources/McpServerResource.js.map +1 -1
- package/package.json +6 -6
- package/src/context/McpConnectorByIndexProvider.tsx +8 -4
- package/src/context/McpCustomServerByIndexProvider.tsx +8 -4
- package/src/context/McpServerByIdProvider.tsx +8 -4
- package/src/primitives/server/McpServerRemoveButton.test.tsx +71 -0
- package/src/primitives/server/McpServerRemoveButton.tsx +1 -1
- package/src/resources/McpManagerResource.test.ts +108 -1
- package/src/resources/McpManagerResource.ts +23 -13
- package/src/resources/McpServerResource.test.ts +37 -3
- package/src/resources/McpServerResource.ts +9 -2
|
@@ -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","assertUniqueServerIds","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","connectionTimeout","defaultRedirectUri","window","location","origin","NO_CONNECTORS","reportCustomStorageFailure","operation","error","console","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","t3","signal","markHydrated","cancelled","current","records","loadCustomServers","t4","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t5","t6","records_0","saveCustomServers","t7","persistCustomServers","t8","_temp2","_temp3","t9","t10","c_0","c","kind","name","url","icon","auth","cache","undefined","elicitation","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","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 reportCustomStorageFailure = (\n operation: \"load\" | \"save\",\n error: unknown,\n) => {\n console.error(\n `[assistant-ui/react-mcp] failed to ${operation} custom servers:`,\n error,\n );\n};\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 if (!signal.cancelled) {\n reportCustomStorageFailure(\"load\", error);\n }\n markHydrated();\n return;\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 try {\n await storage.saveCustomServers(records);\n } catch (error) {\n reportCustomStorageFailure(\"save\", error);\n }\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 ...(c.cache !== undefined ? { cache: c.cache } : {}),\n ...(c.elicitation !== undefined\n ? { elicitation: c.elicitation }\n : {}),\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 ...(s.cache !== undefined ? { cache: s.cache } : {}),\n ...(s.elicitation !== undefined\n ? { elicitation: s.elicitation }\n : {}),\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 ({\n name,\n url,\n auth,\n connectionTimeout,\n cache,\n elicitation,\n }) => {\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 ...(cache !== undefined ? { cache } : {}),\n ...(elicitation !== undefined ? { elicitation } : {}),\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","MCPStorage","MCPStorageElement","assertUniqueServerIds","MCPAuthConfig","MCPConnector","MCPCustomServerRecord","MCPManagerState","McpManagerResourceProps","connectors","storage","oauthRedirectUri","autoConnect","connectionTimeout","defaultRedirectUri","window","location","origin","NO_CONNECTORS","reportCustomStorageFailure","operation","error","console","persistCustomServers","records","saveCustomServers","useMcpManagerResource","props","$","_c","t0","redirectUri","t1","storageElement","t2","Symbol","for","customServers","setCustomServers","isHydrated","setIsHydrated","hydratedRef","storageRef","t3","Promise","resolve","persistenceQueueRef","t4","t5","current","t6","signal","markHydrated","cancelled","loadCustomServers","t7","prev","length","persistedIds","Set","map","_temp","filter","r_0","has","r","id","hydrate","signal_0","t8","t10","t9","targetStorage","then","_temp2","_temp3","t11","t12","c_0","c","kind","name","url","icon","auth","cache","undefined","elicitation","onRemove","_temp4","connectorElements","t13","s_0","s","prev_0","x","customElements","serverElements","lookup","all","state","_temp5","_temp6","t14","servers","out","server","connectionState","tool","tools","fullName","type","description","parameters","inputSchema","execute","args","get","key","callTool","toolkit","clientRef","t15","t16","client","modelContext","register","getModelContext","t17","index","list","entry","Error","serverByKind","t18","t19","query","t20","t21","t22","index_0","t23","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","t24","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 { MCPStorage, 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 reportCustomStorageFailure = (\n operation: \"load\" | \"save\",\n error: unknown,\n) => {\n console.error(\n `[assistant-ui/react-mcp] failed to ${operation} custom servers:`,\n error,\n );\n};\n\nconst persistCustomServers = async (\n storage: MCPStorage,\n records: MCPCustomServerRecord[],\n) => {\n try {\n await storage.saveCustomServers(records);\n } catch (error) {\n reportCustomStorageFailure(\"save\", error);\n }\n};\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 const storageRef = useRef(storage);\n const persistenceQueueRef = useRef(Promise.resolve());\n\n useEffect(() => {\n storageRef.current = storage;\n }, [storage]);\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 if (!signal.cancelled) {\n reportCustomStorageFailure(\"load\", error);\n }\n markHydrated();\n return;\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 useEffect(() => {\n if (!hydratedRef.current) return;\n const targetStorage = storageRef.current;\n persistenceQueueRef.current = persistenceQueueRef.current.then(() =>\n persistCustomServers(targetStorage, customServers),\n );\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 ...(c.cache !== undefined ? { cache: c.cache } : {}),\n ...(c.elicitation !== undefined\n ? { elicitation: c.elicitation }\n : {}),\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 ...(s.cache !== undefined ? { cache: s.cache } : {}),\n ...(s.elicitation !== undefined\n ? { elicitation: s.elicitation }\n : {}),\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 ({\n name,\n url,\n auth,\n connectionTimeout,\n cache,\n elicitation,\n }) => {\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 ...(cache !== undefined ? { cache } : {}),\n ...(elicitation !== undefined ? { elicitation } : {}),\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;KA2BY;EACV;EACA,MAAM;EACN;EACA;EACA;EACA,MAAM;EACN,SAAS;EACT;EACA;EACA;EACA;aAAmB;;WACV;EACT,gBAAgB;;
|
|
1
|
+
{"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;KA2BY;EACV;EACA,MAAM;EACN;EACA;EACA;EACA,MAAM;EACN,SAAS;EACT;EACA;EACA;EACA;aAAmB;;WACV;EACT,gBAAgB;;cA8kBL,+CAAiB,SAAA,4BAAA,OAAA"}
|
|
@@ -320,8 +320,13 @@ const useMcpServerResource = (props) => {
|
|
|
320
320
|
disconnect: doDisconnect,
|
|
321
321
|
remove: async () => {
|
|
322
322
|
await doDisconnect();
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
try {
|
|
324
|
+
await props.storage.clearAuthState(props.id);
|
|
325
|
+
await props.onRemove();
|
|
326
|
+
} catch (err_1) {
|
|
327
|
+
setLastError({ message: err_1 instanceof Error ? err_1.message : String(err_1) });
|
|
328
|
+
throw err_1;
|
|
329
|
+
}
|
|
325
330
|
},
|
|
326
331
|
callTool: async (name, args) => {
|
|
327
332
|
const client_1 = clientRef.current;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","transportCloseQueueRef","resolve","connectionGenerationRef","elicitationResolversRef","Map","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","current","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","closeTransports","pendingTransport","activeTransport","Set","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","crypto","randomUUID","Math","random","slice","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","finishAuth","connected","tryAutoConnect","token","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","length","join","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const transportCloseQueueRef = useRef(Promise.resolve());\n const connectionGenerationRef = useRef(0);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const closeTransports = async () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n const transports = new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n );\n await closeQueuedTransports([...transports]);\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(code);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n // Defer disposal so StrictMode can replay setup before closing the transport.\n queueMicrotask(() => {\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n void closeTransports();\n });\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n 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 answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","UnauthorizedError","ClientOptions","ElicitRequest","ElicitResult","StreamableHTTPClientTransportOptions","createOAuthProvider","buildHeaders","assertValidServerId","validateElicitationContent","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPElicitation","MCPElicitationResponse","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","cache","defaultTtlMs","elicitation","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","pendingElicitations","setPendingElicitations","clientRef","transportRef","pendingTransportRef","transportCloseQueueRef","resolve","connectionGenerationRef","elicitationResolversRef","Map","result","signal","AbortSignal","onAbort","requestedSchema","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","current","then","all","map","closePendingTransport","resolvePendingElicitation","entry","get","delete","removeEventListener","filter","setPendingElicitationError","error","NonNullable","has","cancelPendingElicitations","action","closeTransports","pendingTransport","activeTransport","Set","isCurrentConnection","generation","createInterruptedAuthError","cause","Error","undefined","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","remainingMs","Date","now","timeoutError","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","applyToolsList","list","Array","description","inputSchema","t","info","syncTools","client","options","listPromise","listTools","finalizeConnect","clientOptions","capabilities","listChanged","autoRefresh","debounceMs","onChanged","items","defaultCacheTtlMs","version","setRequestHandler","request","context","params","crypto","randomUUID","Math","random","slice","set","aborted","addEventListener","once","connect","synced","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","finishAuth","connected","tryAutoConnect","token","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","readResource","uri","completeAuth","answerElicitation","response","property","content","isArray","errors","properties","length","join","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport {\n Client,\n StreamableHTTPClientTransport,\n UnauthorizedError,\n type ClientOptions,\n type ElicitRequest,\n type ElicitResult,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/client\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport { validateElicitationContent } from \"./validateElicitationContent\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPElicitation,\n MCPElicitationResponse,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n cache?: { readonly defaultTtlMs?: number } | undefined;\n readonly elicitation?: boolean;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n const [pendingElicitations, setPendingElicitations] = useState<\n MCPElicitation[]\n >([]);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const transportCloseQueueRef = useRef(Promise.resolve());\n const connectionGenerationRef = useRef(0);\n const elicitationResolversRef = useRef(\n new Map<\n string,\n {\n resolve: (result: ElicitResult) => void;\n signal: AbortSignal;\n onAbort: () => void;\n requestedSchema: unknown;\n }\n >(),\n );\n const pendingDisposalRef = useRef<{ cancelled: boolean } | null>(null);\n const mountedRef = useRef(true);\n\n const closeTransportSafely = async (\n transport: StreamableHTTPClientTransport,\n ): Promise<void> => {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n };\n\n const closeQueuedTransports = (\n transports: StreamableHTTPClientTransport[],\n ): Promise<void> => {\n const task = transportCloseQueueRef.current.then(async () => {\n await Promise.all(transports.map(closeTransportSafely));\n });\n transportCloseQueueRef.current = task;\n return task;\n };\n\n const closePendingTransport = async () => {\n const transport = pendingTransportRef.current;\n pendingTransportRef.current = null;\n await closeQueuedTransports(transport ? [transport] : []);\n };\n\n const resolvePendingElicitation = (id: string, result: ElicitResult) => {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return false;\n elicitationResolversRef.current.delete(id);\n entry.signal.removeEventListener(\"abort\", entry.onAbort);\n setPendingElicitations((current) =>\n current.filter((elicitation) => elicitation.id !== id),\n );\n entry.resolve(result);\n return true;\n };\n\n const setPendingElicitationError = (\n id: string,\n error: NonNullable<MCPElicitation[\"error\"]>,\n ) => {\n if (!elicitationResolversRef.current.has(id)) return false;\n setPendingElicitations((current) =>\n current.map((elicitation) =>\n elicitation.id === id ? { ...elicitation, error } : elicitation,\n ),\n );\n return true;\n };\n\n const cancelPendingElicitations = () => {\n for (const [id] of elicitationResolversRef.current) {\n resolvePendingElicitation(id, { action: \"cancel\" });\n }\n };\n\n const closeTransports = async () => {\n cancelPendingElicitations();\n const pendingTransport = pendingTransportRef.current;\n const activeTransport = transportRef.current;\n pendingTransportRef.current = null;\n transportRef.current = null;\n clientRef.current = null;\n\n const transports = new Set(\n [pendingTransport, activeTransport].filter(\n (transport): transport is StreamableHTTPClientTransport =>\n transport !== null,\n ),\n );\n await closeQueuedTransports([...transports]);\n };\n\n const isCurrentConnection = (generation: number) =>\n mountedRef.current && generation === connectionGenerationRef.current;\n\n const createInterruptedAuthError = (cause?: unknown) =>\n new Error(\n `MCP server \"${props.id}\" authorization was interrupted before completion.`,\n cause === undefined ? undefined : { cause },\n );\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const applyToolsList = useEffectEvent(\n (list: {\n tools: Array<{\n name: string;\n description?: string | undefined;\n inputSchema: unknown;\n }>;\n }) => {\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n },\n );\n\n const syncTools = useEffectEvent(\n async (\n client: Client,\n generation: number,\n options?: { startedAt?: number | undefined },\n ): Promise<boolean> => {\n const listPromise = client.listTools();\n const list =\n options?.startedAt === undefined\n ? await listPromise\n : await withConnectionTimeout(\n listPromise,\n \"listing tools\",\n options.startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n applyToolsList(list);\n return true;\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const clientOptions: ClientOptions = {\n ...(props.elicitation === false\n ? {}\n : { capabilities: { elicitation: {} } }),\n listChanged: {\n tools: {\n autoRefresh: true,\n debounceMs: 300,\n onChanged: (error, items) => {\n if (!isCurrentConnection(generation)) return;\n if (error !== null) {\n setLastError({ message: error.message });\n return;\n }\n if (items === null) return;\n setLastError(null);\n applyToolsList({ tools: items });\n },\n },\n },\n };\n if (props.cache?.defaultTtlMs !== undefined) {\n clientOptions.defaultCacheTtlMs = props.cache.defaultTtlMs;\n }\n const client = new Client(\n {\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n },\n clientOptions,\n );\n if (props.elicitation !== false) {\n client.setRequestHandler(\n \"elicitation/create\",\n (request: ElicitRequest, context): Promise<ElicitResult> => {\n if (!isCurrentConnection(generation)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n if (!(\"requestedSchema\" in request.params)) {\n return Promise.resolve({ action: \"cancel\" });\n }\n const { message, requestedSchema } = request.params;\n\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n const promise = new Promise<ElicitResult>((resolve) => {\n const onAbort = () => {\n resolvePendingElicitation(id, { action: \"cancel\" });\n };\n elicitationResolversRef.current.set(id, {\n resolve,\n signal: context.signal,\n onAbort,\n requestedSchema,\n });\n });\n setPendingElicitations((current) => [\n ...current,\n {\n id,\n message,\n requestedSchema,\n },\n ]);\n const entry = elicitationResolversRef.current.get(id);\n if (entry) {\n if (context.signal.aborted) {\n entry.onAbort();\n } else {\n context.signal.addEventListener(\"abort\", entry.onAbort, {\n once: true,\n });\n }\n }\n return promise;\n },\n );\n }\n const startedAt = Date.now();\n await withConnectionTimeout(\n client.connect(transport),\n \"connecting\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const synced = await syncTools(client, generation, { startedAt });\n if (!synced) return false;\n\n pendingTransportRef.current = null;\n clientRef.current = client;\n transportRef.current = transport;\n setConnectionState(\"connected\");\n return true;\n },\n );\n\n const doConnect = useEffectEvent(async () => {\n const generation = ++connectionGenerationRef.current;\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransports();\n if (!isCurrentConnection(generation)) return;\n\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n return;\n }\n pendingTransportRef.current = transport;\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport, generation);\n } catch (err) {\n if (!isCurrentConnection(generation)) return;\n\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n pendingTransportRef.current = null;\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n cancelPendingElicitations();\n if (transport) {\n pendingTransportRef.current = null;\n await closeQueuedTransports([transport]);\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n connectionGenerationRef.current += 1;\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransports();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n const generation = ++connectionGenerationRef.current;\n cancelPendingElicitations();\n await closePendingTransport();\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n if (!isCurrentConnection(generation)) {\n await closeQueuedTransports([transport]);\n throw createInterruptedAuthError();\n }\n }\n transportRef.current = null;\n clientRef.current = null;\n pendingTransportRef.current = transport;\n await transport.finishAuth(code);\n if (!isCurrentConnection(generation)) throw createInterruptedAuthError();\n setAuthorizationUrl(null);\n const connected = await finalizeConnect(transport, generation);\n if (!connected) throw createInterruptedAuthError();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (!isCurrentConnection(generation))\n throw createInterruptedAuthError(error);\n\n await closeTransports();\n setLastError({\n message: error.message,\n });\n setConnectionState(\"error\");\n throw error;\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const previousDisposal = pendingDisposalRef.current;\n if (previousDisposal) previousDisposal.cancelled = true;\n const pendingDisposal = { cancelled: false };\n pendingDisposalRef.current = pendingDisposal;\n mountedRef.current = true;\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n mountedRef.current = false;\n signal.cancelled = true;\n // Defer disposal so StrictMode can replay setup before closing the transport.\n queueMicrotask(() => {\n if (pendingDisposal.cancelled) return;\n connectionGenerationRef.current += 1;\n void closeTransports();\n });\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n pendingElicitations,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n try {\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n } catch (err) {\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n answerElicitation: (\n id: string,\n response: MCPElicitationResponse,\n ): readonly { property: string; message: string }[] | undefined => {\n if (response.action === \"accept\") {\n const entry = elicitationResolversRef.current.get(id);\n if (!entry) return;\n\n if (\n typeof response.content !== \"object\" ||\n response.content === null ||\n Array.isArray(response.content)\n ) {\n const errors = [\n {\n property: \"content\",\n message: \"Response content must be an object.\",\n },\n ];\n setPendingElicitationError(id, {\n message: \"Invalid elicitation content: content.\",\n properties: [\"content\"],\n });\n return errors;\n }\n\n const errors = validateElicitationContent(\n entry.requestedSchema,\n response.content,\n );\n if (errors.length > 0) {\n const properties = [\n ...new Set(errors.map((error) => error.property)),\n ];\n setPendingElicitationError(id, {\n message: `Invalid elicitation content: ${properties.join(\", \")}.`,\n properties,\n });\n return errors;\n }\n\n const result: ElicitResult = {\n action: \"accept\",\n content: response.content as ElicitResult[\"content\"],\n };\n resolvePendingElicitation(id, result);\n return;\n }\n\n resolvePendingElicitation(id, { action: response.action });\n },\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "MCP server configuration and connection primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
31
31
|
"@modelcontextprotocol/core": "^2.0.0",
|
|
32
32
|
"@radix-ui/react-primitive": "^2.1.10",
|
|
33
|
-
"@assistant-ui/core": "0.3.
|
|
34
|
-
"@assistant-ui/store": "0.3.
|
|
35
|
-
"@assistant-ui/tap": "0.9.
|
|
36
|
-
"assistant-stream": "0.3.
|
|
33
|
+
"@assistant-ui/core": "0.3.6",
|
|
34
|
+
"@assistant-ui/store": "0.3.4",
|
|
35
|
+
"@assistant-ui/tap": "0.9.10",
|
|
36
|
+
"assistant-stream": "0.3.34"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@types/react": "*",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@types/react": "^19.2.17",
|
|
49
49
|
"react": "^19.2.8",
|
|
50
50
|
"vitest": "^4.1.10",
|
|
51
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
51
|
+
"@assistant-ui/x-buildutils": "0.0.21"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public",
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { FC, PropsWithChildren } from "react";
|
|
2
|
-
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
2
|
+
import { useAui, AuiConfig, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export const McpConnectorByIndexProvider: FC<
|
|
5
5
|
PropsWithChildren<{ index: number }>
|
|
6
6
|
> = ({ index, children }) => {
|
|
7
|
-
const aui = useAui(
|
|
7
|
+
const aui = useAui();
|
|
8
|
+
const config = AuiConfig({
|
|
8
9
|
mcpServer: Derived({
|
|
9
10
|
source: "mcp",
|
|
10
11
|
query: { kind: "connector", index },
|
|
11
12
|
get: (parent) => parent.mcp.connector({ index }),
|
|
12
13
|
}),
|
|
13
14
|
});
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
return (
|
|
16
|
+
<AuiProvider extends={aui} config={config}>
|
|
17
|
+
{children}
|
|
18
|
+
</AuiProvider>
|
|
19
|
+
);
|
|
16
20
|
};
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { FC, PropsWithChildren } from "react";
|
|
2
|
-
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
2
|
+
import { useAui, AuiConfig, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export const McpCustomServerByIndexProvider: FC<
|
|
5
5
|
PropsWithChildren<{ index: number }>
|
|
6
6
|
> = ({ index, children }) => {
|
|
7
|
-
const aui = useAui(
|
|
7
|
+
const aui = useAui();
|
|
8
|
+
const config = AuiConfig({
|
|
8
9
|
mcpServer: Derived({
|
|
9
10
|
source: "mcp",
|
|
10
11
|
query: { kind: "custom", index },
|
|
11
12
|
get: (parent) => parent.mcp.customServer({ index }),
|
|
12
13
|
}),
|
|
13
14
|
});
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
return (
|
|
16
|
+
<AuiProvider extends={aui} config={config}>
|
|
17
|
+
{children}
|
|
18
|
+
</AuiProvider>
|
|
19
|
+
);
|
|
16
20
|
};
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type { FC, PropsWithChildren } from "react";
|
|
2
|
-
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
2
|
+
import { useAui, AuiConfig, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export const McpServerByIdProvider: FC<PropsWithChildren<{ id: string }>> = ({
|
|
5
5
|
id,
|
|
6
6
|
children,
|
|
7
7
|
}) => {
|
|
8
|
-
const aui = useAui(
|
|
8
|
+
const aui = useAui();
|
|
9
|
+
const config = AuiConfig({
|
|
9
10
|
mcpServer: Derived({
|
|
10
11
|
source: "mcp",
|
|
11
12
|
query: { id },
|
|
12
13
|
get: (parent) => parent.mcp.server({ id }),
|
|
13
14
|
}),
|
|
14
15
|
});
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
return (
|
|
17
|
+
<AuiProvider extends={aui} config={config}>
|
|
18
|
+
{children}
|
|
19
|
+
</AuiProvider>
|
|
20
|
+
);
|
|
17
21
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AssistantState } from "@assistant-ui/store";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
remove: vi.fn<() => Promise<void>>(),
|
|
6
|
+
kind: "custom" as "connector" | "custom",
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
10
|
+
...(await importOriginal()),
|
|
11
|
+
useAui: () => ({ mcpServer: { remove: mocks.remove } }),
|
|
12
|
+
useAuiState: function useAuiState<T>(selector: (state: AssistantState) => T) {
|
|
13
|
+
return selector({
|
|
14
|
+
mcpServer: { kind: mocks.kind },
|
|
15
|
+
} as AssistantState);
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const { McpServerPrimitiveRemoveButton } =
|
|
20
|
+
await import("./McpServerRemoveButton");
|
|
21
|
+
|
|
22
|
+
const captureUnhandledRejections = async (callback: () => void) => {
|
|
23
|
+
const reasons: unknown[] = [];
|
|
24
|
+
const listener = (reason: unknown) => reasons.push(reason);
|
|
25
|
+
process.on("unhandledRejection", listener);
|
|
26
|
+
try {
|
|
27
|
+
callback();
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
29
|
+
return reasons;
|
|
30
|
+
} finally {
|
|
31
|
+
process.off("unhandledRejection", listener);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const renderButton = () =>
|
|
36
|
+
(
|
|
37
|
+
McpServerPrimitiveRemoveButton as unknown as {
|
|
38
|
+
render: (
|
|
39
|
+
props: Record<string, unknown>,
|
|
40
|
+
ref: null,
|
|
41
|
+
) => {
|
|
42
|
+
props: {
|
|
43
|
+
onClick: (event: { defaultPrevented: boolean }) => void;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
).render({}, null);
|
|
48
|
+
|
|
49
|
+
describe("McpServerPrimitiveRemoveButton", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
mocks.remove.mockReset();
|
|
52
|
+
mocks.kind = "custom";
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("does not render for connectors", () => {
|
|
56
|
+
mocks.kind = "connector";
|
|
57
|
+
|
|
58
|
+
expect(renderButton()).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("handles rejected custom server remove actions", async () => {
|
|
62
|
+
mocks.remove.mockRejectedValueOnce(new Error("storage unavailable"));
|
|
63
|
+
|
|
64
|
+
const unhandledRejections = await captureUnhandledRejections(() => {
|
|
65
|
+
renderButton().props.onClick({ defaultPrevented: false });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(mocks.remove).toHaveBeenCalledOnce();
|
|
69
|
+
expect(unhandledRejections).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createTapRoot, useResource } from "@assistant-ui/tap";
|
|
1
|
+
import { createTapRoot, resource, useResource } from "@assistant-ui/tap";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import { defineConnector } from "../connector";
|
|
4
5
|
import type { MCPConnector } from "../mcp-scope";
|
|
5
6
|
import { assertUniqueServerIds } from "../utils/serverId";
|
|
@@ -208,3 +209,109 @@ describe("McpManagerResource storage failures", () => {
|
|
|
208
209
|
}
|
|
209
210
|
});
|
|
210
211
|
});
|
|
212
|
+
|
|
213
|
+
describe("McpManagerResource storage ordering", () => {
|
|
214
|
+
it("persists custom server updates in invocation order", async () => {
|
|
215
|
+
let resolveFirstSave: (() => void) | undefined;
|
|
216
|
+
const firstSave = new Promise<void>((resolve) => {
|
|
217
|
+
resolveFirstSave = resolve;
|
|
218
|
+
});
|
|
219
|
+
let blockNextSave = false;
|
|
220
|
+
const persistedSnapshots: string[][] = [];
|
|
221
|
+
const saveCustomServers = vi.fn(async (records: { name: string }[]) => {
|
|
222
|
+
persistedSnapshots.push(records.map((record) => record.name));
|
|
223
|
+
if (blockNextSave) {
|
|
224
|
+
blockNextSave = false;
|
|
225
|
+
await firstSave;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const root = mount(
|
|
229
|
+
[],
|
|
230
|
+
McpCustomStorage({
|
|
231
|
+
loadCustomServers: vi.fn(async () => []),
|
|
232
|
+
saveCustomServers,
|
|
233
|
+
loadAuthState: vi.fn(async () => null),
|
|
234
|
+
saveAuthState: vi.fn(async () => {}),
|
|
235
|
+
clearAuthState: vi.fn(async () => {}),
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
await vi.waitFor(() =>
|
|
241
|
+
expect(root.getValue().getState().isHydrated).toBe(true),
|
|
242
|
+
);
|
|
243
|
+
await vi.waitFor(() => expect(saveCustomServers).toHaveBeenCalled());
|
|
244
|
+
saveCustomServers.mockClear();
|
|
245
|
+
persistedSnapshots.length = 0;
|
|
246
|
+
blockNextSave = true;
|
|
247
|
+
|
|
248
|
+
await root.getValue().addCustomServer({
|
|
249
|
+
name: "Docs",
|
|
250
|
+
url: "https://example.com/docs/mcp",
|
|
251
|
+
auth: { type: "none" },
|
|
252
|
+
});
|
|
253
|
+
await vi.waitFor(() =>
|
|
254
|
+
expect(saveCustomServers).toHaveBeenCalledTimes(1),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
await root.getValue().addCustomServer({
|
|
258
|
+
name: "Linear",
|
|
259
|
+
url: "https://example.com/linear/mcp",
|
|
260
|
+
auth: { type: "none" },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
264
|
+
expect(saveCustomServers).toHaveBeenCalledTimes(1);
|
|
265
|
+
|
|
266
|
+
resolveFirstSave?.();
|
|
267
|
+
await vi.waitFor(() =>
|
|
268
|
+
expect(saveCustomServers).toHaveBeenCalledTimes(2),
|
|
269
|
+
);
|
|
270
|
+
expect(persistedSnapshots).toEqual([["Docs"], ["Docs", "Linear"]]);
|
|
271
|
+
} finally {
|
|
272
|
+
resolveFirstSave?.();
|
|
273
|
+
root.unmount();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("does not persist unchanged servers when inline storage rerenders", async () => {
|
|
278
|
+
const saveCustomServers = vi.fn(async () => {});
|
|
279
|
+
let rerender = () => {};
|
|
280
|
+
const DynamicManager = resource(function useDynamicManager() {
|
|
281
|
+
const [, setVersion] = useState(0);
|
|
282
|
+
rerender = () => setVersion((version) => version + 1);
|
|
283
|
+
|
|
284
|
+
return useResource(
|
|
285
|
+
McpManagerResource({
|
|
286
|
+
connectors: [],
|
|
287
|
+
storage: McpCustomStorage({
|
|
288
|
+
loadCustomServers: vi.fn(async () => []),
|
|
289
|
+
saveCustomServers,
|
|
290
|
+
loadAuthState: vi.fn(async () => null),
|
|
291
|
+
saveAuthState: vi.fn(async () => {}),
|
|
292
|
+
clearAuthState: vi.fn(async () => {}),
|
|
293
|
+
}),
|
|
294
|
+
autoConnect: false,
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
const root = createTapRoot(function Root() {
|
|
299
|
+
return useResource(DynamicManager());
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await vi.waitFor(() =>
|
|
304
|
+
expect(root.getValue().getState().isHydrated).toBe(true),
|
|
305
|
+
);
|
|
306
|
+
await vi.waitFor(() => expect(saveCustomServers).toHaveBeenCalled());
|
|
307
|
+
saveCustomServers.mockClear();
|
|
308
|
+
|
|
309
|
+
rerender();
|
|
310
|
+
|
|
311
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
312
|
+
expect(saveCustomServers).not.toHaveBeenCalled();
|
|
313
|
+
} finally {
|
|
314
|
+
root.unmount();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
});
|