@assistant-ui/react-mcp 0.0.8 → 0.0.10

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.
@@ -9,7 +9,7 @@ type McpManagerResourceProps = {
9
9
  oauthRedirectUri?: string | undefined; /** Connect on mount when usable auth exists. Default true. */
10
10
  autoConnect?: boolean | undefined;
11
11
  };
12
- declare const McpManagerResource: (props: McpManagerResourceProps) => import("@assistant-ui/tap").ResourceElement<ClientOutput<"mcp">, McpManagerResourceProps>;
12
+ declare const McpManagerResource: import("@assistant-ui/tap").Resource<ClientOutput<"mcp">, [props: McpManagerResourceProps]>;
13
13
  //#endregion
14
14
  export { McpManagerResource, McpManagerResourceProps };
15
15
  //# sourceMappingURL=McpManagerResource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;;KAoBY,uBAAA;EACV,UAAA,GAAa,YAAA;EACb,OAAA,GAAU,iBAAiB,cAFM;EAIjC,gBAAA,uBAF2B;EAI3B,WAAA;AAAA;AAAA,cAYW,kBAAA,GAAkB,KAAA,EAAA,uBAAA,iCAAA,eAAA,CAAA,YAAA,SAAA,uBAAA"}
1
+ {"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;;KAoBY,uBAAA;EACV,UAAA,GAAa,YAAA;EACb,OAAA,GAAU,iBAAiB,cAFM;EAIjC,gBAAA,uBAF2B;EAI3B,WAAA;AAAA;AAAA,cAoNW,kBAAA,8BAAkB,QAAA,CAAA,YAAA,UAAA,KAAA,EAAA,uBAAA"}
@@ -10,7 +10,7 @@ function defaultRedirectUri() {
10
10
  return `${window.location.origin}/mcp/callback`;
11
11
  }
12
12
  const NO_CONNECTORS = [];
13
- const McpManagerResource = resource(function McpManagerResource(props) {
13
+ const useMcpManagerResource = (props) => {
14
14
  const connectors = props.connectors ?? NO_CONNECTORS;
15
15
  const autoConnect = props.autoConnect ?? true;
16
16
  const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();
@@ -149,8 +149,9 @@ const McpManagerResource = resource(function McpManagerResource(props) {
149
149
  }
150
150
  }
151
151
  };
152
- });
153
- attachTransformScopes(McpManagerResource, (scopes, parent) => {
152
+ };
153
+ const McpManagerResource = resource(useMcpManagerResource);
154
+ attachTransformScopes(useMcpManagerResource, (scopes, parent) => {
154
155
  if (!scopes.modelContext && parent.modelContext.source === null) scopes.modelContext = ModelContext();
155
156
  });
156
157
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"McpManagerResource.js","names":[],"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\nexport const McpManagerResource = resource(function McpManagerResource(\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 try {\n const records = await storage.loadCustomServers();\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 } finally {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n }\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, [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\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(McpManagerResource, (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":[],"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 try {\n const records = await storage.loadCustomServers();\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 } finally {\n if (!signal.cancelled) {\n hydratedRef.current = true;\n setIsHydrated(true);\n }\n }\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, [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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"}
@@ -15,7 +15,7 @@ type McpServerResourceProps = {
15
15
  autoConnect: boolean;
16
16
  onRemove: () => Promise<void>;
17
17
  };
18
- declare const McpServerResource: (props: McpServerResourceProps) => import("@assistant-ui/tap").ResourceElement<ClientOutput<"mcpServer">, McpServerResourceProps>;
18
+ declare const McpServerResource: import("@assistant-ui/tap").Resource<ClientOutput<"mcpServer">, [props: McpServerResourceProps]>;
19
19
  //#endregion
20
20
  export { McpServerResource, McpServerResourceProps };
21
21
  //# sourceMappingURL=McpServerResource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;;KAsBY,sBAAA;EACV,EAAA;EACA,IAAA,EAAM,aAAA;EACN,IAAA;EACA,GAAA;EACA,IAAA;EACA,IAAA,EAAM,aAAA;EACN,OAAA,EAAS,UAAA;EACT,WAAA;EACA,WAAA;EACA,QAAA,QAAgB,OAAA;AAAA;AAAA,cAGL,iBAAA,GAAiB,KAAA,EAAA,sBAAA,iCAAA,eAAA,CAAA,YAAA,eAAA,sBAAA"}
1
+ {"version":3,"file":"McpServerResource.d.ts","names":[],"sources":["../../src/resources/McpServerResource.ts"],"mappings":";;;;;KAsBY,sBAAA;EACV,EAAA;EACA,IAAA,EAAM,aAAA;EACN,IAAA;EACA,GAAA;EACA,IAAA;EACA,IAAA,EAAM,aAAA;EACN,OAAA,EAAS,UAAA;EACT,WAAA;EACA,WAAA;EACA,QAAA,QAAgB,OAAA;AAAA;AAAA,cA0PL,iBAAA,8BAAiB,QAAA,CAAA,YAAA,gBAAA,KAAA,EAAA,sBAAA"}
@@ -7,7 +7,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
7
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8
8
  import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
9
9
  //#region src/resources/McpServerResource.ts
10
- const McpServerResource = resource(function McpServerResource(props) {
10
+ const useMcpServerResource = (props) => {
11
11
  assertValidServerId(props.id);
12
12
  const [connectionState, setConnectionState] = useState("disconnected");
13
13
  const [tools, setTools] = useState([]);
@@ -184,7 +184,8 @@ const McpServerResource = resource(function McpServerResource(props) {
184
184
  },
185
185
  completeAuth: doCompleteAuth
186
186
  };
187
- });
187
+ };
188
+ const McpServerResource = resource(useMcpServerResource);
188
189
  //#endregion
189
190
  export { McpServerResource };
190
191
 
@@ -1 +1 @@
1
- {"version":3,"file":"McpServerResource.js","names":[],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n onRemove: () => Promise<void>;\n};\n\nexport const McpServerResource = resource(function McpServerResource(\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"],"mappings":"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"}
1
+ {"version":3,"file":"McpServerResource.js","names":[],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await client.connect(transport as unknown as Transport);\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const list = await client.listTools();\n clientRef.current = client;\n transportRef.current = transport;\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport);\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { MCPStorage } from "./types.js";
2
2
 
3
3
  //#region src/resources/storage/McpCustomStorage.d.ts
4
- declare const McpCustomStorage: (props: MCPStorage) => import("@assistant-ui/tap").ResourceElement<MCPStorage, MCPStorage>;
4
+ declare const McpCustomStorage: import("@assistant-ui/tap").Resource<MCPStorage, [impl: MCPStorage]>;
5
5
  //#endregion
6
6
  export { McpCustomStorage };
7
7
  //# sourceMappingURL=McpCustomStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;;cAGa,gBAAA,GAAgB,KAAA,EAAA,UAAA,iCAAA,eAAA,CAAA,UAAA,EAAA,UAAA"}
1
+ {"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;;cAOa,gBAAA,8BAAgB,QAAA,CAAA,UAAA,GAAA,IAAA,EAAA,UAAA"}
@@ -1,8 +1,9 @@
1
1
  import { resource } from "@assistant-ui/tap";
2
2
  //#region src/resources/storage/McpCustomStorage.ts
3
- const McpCustomStorage = resource(function McpCustomStorage(impl) {
3
+ const useMcpCustomStorage = (impl) => {
4
4
  return impl;
5
- });
5
+ };
6
+ const McpCustomStorage = resource(useMcpCustomStorage);
6
7
  //#endregion
7
8
  export { McpCustomStorage };
8
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"McpCustomStorage.js","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\nexport const McpCustomStorage = resource(function McpCustomStorage(\n impl: MCPStorage,\n): MCPStorage {\n return impl;\n});\n"],"mappings":";;AAGA,MAAa,mBAAmB,SAAS,SAAS,iBAChD,MACY;CACZ,OAAO;AACT,CAAC"}
1
+ {"version":3,"file":"McpCustomStorage.js","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {\n return impl;\n};\n\nexport const McpCustomStorage = resource(useMcpCustomStorage);\n"],"mappings":";;AAGA,MAAM,uBAAuB,SAAiC;CAC5D,OAAO;AACT;AAEA,MAAa,mBAAmB,SAAS,mBAAmB"}
@@ -5,7 +5,7 @@ type McpLocalStorageOptions = {
5
5
  /** Namespace prefix for keys. Default "aui-mcp". */keyPrefix?: string; /** Override the underlying Storage. Defaults to globalThis.localStorage. */
6
6
  storage?: Storage;
7
7
  };
8
- declare const McpLocalStorage: () => import("@assistant-ui/tap").ResourceElement<MCPStorage, undefined>;
8
+ declare const McpLocalStorage: import("@assistant-ui/tap").Resource<MCPStorage, [opts?: McpLocalStorageOptions | undefined]>;
9
9
  //#endregion
10
10
  export { McpLocalStorage, McpLocalStorageOptions };
11
11
  //# sourceMappingURL=McpLocalStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;KAKY,sBAAA;sDAEV,SAAA,WAFgC;EAIhC,OAAA,GAAU,OAAO;AAAA;AAAA,cAeN,eAAA,oCAAe,eAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;KAKY,sBAAA;sDAEV,SAAA,WAFgC;EAIhC,OAAA,GAAU,OAAO;AAAA;AAAA,cAmEN,eAAA,8BAAe,QAAA,CAAA,UAAA,GAAA,IAAA,GAAA,sBAAA"}
@@ -9,7 +9,7 @@ function resolveStorage(opts) {
9
9
  }
10
10
  return null;
11
11
  }
12
- const McpLocalStorage = resource(function McpLocalStorage(opts = {}) {
12
+ const useMcpLocalStorage = (opts = {}) => {
13
13
  const prefix = opts.keyPrefix ?? "aui-mcp";
14
14
  const customServersKey = `${prefix}:custom-servers`;
15
15
  const authKey = (id) => `${prefix}:auth:${id}`;
@@ -49,7 +49,8 @@ const McpLocalStorage = resource(function McpLocalStorage(opts = {}) {
49
49
  remove(authKey(id));
50
50
  }
51
51
  };
52
- });
52
+ };
53
+ const McpLocalStorage = resource(useMcpLocalStorage);
53
54
  //#endregion
54
55
  export { McpLocalStorage };
55
56
 
@@ -1 +1 @@
1
- {"version":3,"file":"McpLocalStorage.js","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nexport const McpLocalStorage = resource(function McpLocalStorage(\n opts: McpLocalStorageOptions = {},\n): 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"],"mappings":";;AAYA,SAAS,eAAe,MAA8C;CACpE,IAAI,KAAK,SAAS,OAAO,KAAK;CAC9B,IAAI,OAAO,eAAe,eAAe,kBAAkB,YACzD,IAAI;EACF,OAAQ,WAAyC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAa,kBAAkB,SAAS,SAAS,gBAC/C,OAA+B,CAAC,GACpB;CACZ,MAAM,SAAS,KAAK,aAAa;CACjC,MAAM,mBAAmB,GAAG,OAAO;CACnC,MAAM,WAAW,OAAe,GAAG,OAAO,QAAQ;CAClD,MAAM,UAAU,eAAe,IAAI;CAEnC,MAAM,QAAW,KAAa,aAAmB;EAC/C,IAAI,CAAC,SAAS,OAAO;EACrB,IAAI;GACF,MAAM,MAAM,QAAQ,QAAQ,GAAG;GAC/B,IAAI,OAAO,MAAM,OAAO;GACxB,OAAO,KAAK,MAAM,GAAG;EACvB,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,SAAS,KAAa,UAAyB;EACnD,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;EAC5C,QAAQ,CAER;CACF;CAEA,MAAM,UAAU,QAAsB;EACpC,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,WAAW,GAAG;EACxB,QAAQ,CAER;CACF;CAEA,OAAO;EACL,mBAAmB,YACjB,KAA8B,kBAAkB,CAAC,CAAC;EACpD,mBAAmB,OAAO,YAAY;GACpC,MAAM,kBAAkB,OAAO;EACjC;EACA,eAAe,OAAO,OACpB,KAAmC,QAAQ,EAAE,GAAG,IAAI;EACtD,eAAe,OAAO,IAAI,UAAU;GAClC,MAAM,QAAQ,EAAE,GAAG,KAAK;EAC1B;EACA,gBAAgB,OAAO,OAAO;GAC5B,OAAO,QAAQ,EAAE,CAAC;EACpB;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"McpLocalStorage.js","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n read<MCPCustomServerRecord[]>(customServersKey, []),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n read<MCPPersistedAuthState | null>(authKey(id), null),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":";;AAYA,SAAS,eAAe,MAA8C;CACpE,IAAI,KAAK,SAAS,OAAO,KAAK;CAC9B,IAAI,OAAO,eAAe,eAAe,kBAAkB,YACzD,IAAI;EACF,OAAQ,WAAyC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAM,sBAAsB,OAA+B,CAAC,MAAkB;CAC5E,MAAM,SAAS,KAAK,aAAa;CACjC,MAAM,mBAAmB,GAAG,OAAO;CACnC,MAAM,WAAW,OAAe,GAAG,OAAO,QAAQ;CAClD,MAAM,UAAU,eAAe,IAAI;CAEnC,MAAM,QAAW,KAAa,aAAmB;EAC/C,IAAI,CAAC,SAAS,OAAO;EACrB,IAAI;GACF,MAAM,MAAM,QAAQ,QAAQ,GAAG;GAC/B,IAAI,OAAO,MAAM,OAAO;GACxB,OAAO,KAAK,MAAM,GAAG;EACvB,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,SAAS,KAAa,UAAyB;EACnD,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;EAC5C,QAAQ,CAER;CACF;CAEA,MAAM,UAAU,QAAsB;EACpC,IAAI,CAAC,SAAS;EACd,IAAI;GACF,QAAQ,WAAW,GAAG;EACxB,QAAQ,CAER;CACF;CAEA,OAAO;EACL,mBAAmB,YACjB,KAA8B,kBAAkB,CAAC,CAAC;EACpD,mBAAmB,OAAO,YAAY;GACpC,MAAM,kBAAkB,OAAO;EACjC;EACA,eAAe,OAAO,OACpB,KAAmC,QAAQ,EAAE,GAAG,IAAI;EACtD,eAAe,OAAO,IAAI,UAAU;GAClC,MAAM,QAAQ,EAAE,GAAG,KAAK;EAC1B;EACA,gBAAgB,OAAO,OAAO;GAC5B,OAAO,QAAQ,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAa,kBAAkB,SAAS,kBAAkB"}
@@ -1,7 +1,7 @@
1
1
  import { MCPStorage } from "./types.js";
2
2
 
3
3
  //#region src/resources/storage/McpMemoryStorage.d.ts
4
- declare const McpMemoryStorage: () => import("@assistant-ui/tap").ResourceElement<MCPStorage, undefined>;
4
+ declare const McpMemoryStorage: import("@assistant-ui/tap").Resource<MCPStorage, []>;
5
5
  //#endregion
6
6
  export { McpMemoryStorage };
7
7
  //# sourceMappingURL=McpMemoryStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;;cAKa,gBAAA,oCAAgB,eAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;;cAuBa,gBAAA,8BAAgB,QAAA,CAAA,UAAA"}
@@ -1,6 +1,6 @@
1
1
  import { resource } from "@assistant-ui/tap";
2
2
  //#region src/resources/storage/McpMemoryStorage.ts
3
- const McpMemoryStorage = resource(function McpMemoryStorage() {
3
+ const useMcpMemoryStorage = () => {
4
4
  let servers = [];
5
5
  const auth = /* @__PURE__ */ new Map();
6
6
  return {
@@ -16,7 +16,8 @@ const McpMemoryStorage = resource(function McpMemoryStorage() {
16
16
  auth.delete(id);
17
17
  }
18
18
  };
19
- });
19
+ };
20
+ const McpMemoryStorage = resource(useMcpMemoryStorage);
20
21
  //#endregion
21
22
  export { McpMemoryStorage };
22
23
 
@@ -1 +1 @@
1
- {"version":3,"file":"McpMemoryStorage.js","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport const McpMemoryStorage = resource(\n function McpMemoryStorage(): MCPStorage {\n let servers: MCPCustomServerRecord[] = [];\n const auth = new Map<string, MCPPersistedAuthState>();\n return {\n loadCustomServers: async () => [...servers],\n saveCustomServers: async (records) => {\n servers = [...records];\n },\n loadAuthState: async (id) => auth.get(id) ?? null,\n saveAuthState: async (id, state) => {\n auth.set(id, state);\n },\n clearAuthState: async (id) => {\n auth.delete(id);\n },\n };\n },\n);\n"],"mappings":";;AAKA,MAAa,mBAAmB,SAC9B,SAAS,mBAA+B;CACtC,IAAI,UAAmC,CAAC;CACxC,MAAM,uBAAO,IAAI,IAAmC;CACpD,OAAO;EACL,mBAAmB,YAAY,CAAC,GAAG,OAAO;EAC1C,mBAAmB,OAAO,YAAY;GACpC,UAAU,CAAC,GAAG,OAAO;EACvB;EACA,eAAe,OAAO,OAAO,KAAK,IAAI,EAAE,KAAK;EAC7C,eAAe,OAAO,IAAI,UAAU;GAClC,KAAK,IAAI,IAAI,KAAK;EACpB;EACA,gBAAgB,OAAO,OAAO;GAC5B,KAAK,OAAO,EAAE;EAChB;CACF;AACF,CACF"}
1
+ {"version":3,"file":"McpMemoryStorage.js","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nconst useMcpMemoryStorage = (): MCPStorage => {\n let servers: MCPCustomServerRecord[] = [];\n const auth = new Map<string, MCPPersistedAuthState>();\n return {\n loadCustomServers: async () => [...servers],\n saveCustomServers: async (records) => {\n servers = [...records];\n },\n loadAuthState: async (id) => auth.get(id) ?? null,\n saveAuthState: async (id, state) => {\n auth.set(id, state);\n },\n clearAuthState: async (id) => {\n auth.delete(id);\n },\n };\n};\n\nexport const McpMemoryStorage = resource(useMcpMemoryStorage);\n"],"mappings":";;AAKA,MAAM,4BAAwC;CAC5C,IAAI,UAAmC,CAAC;CACxC,MAAM,uBAAO,IAAI,IAAmC;CACpD,OAAO;EACL,mBAAmB,YAAY,CAAC,GAAG,OAAO;EAC1C,mBAAmB,OAAO,YAAY;GACpC,UAAU,CAAC,GAAG,OAAO;EACvB;EACA,eAAe,OAAO,OAAO,KAAK,IAAI,EAAE,KAAK;EAC7C,eAAe,OAAO,IAAI,UAAU;GAClC,KAAK,IAAI,IAAI,KAAK;EACpB;EACA,gBAAgB,OAAO,OAAO;GAC5B,KAAK,OAAO,EAAE;EAChB;CACF;AACF;AAEA,MAAa,mBAAmB,SAAS,mBAAmB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-mcp",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "MCP server configuration and connection primitives for @assistant-ui",
5
5
  "keywords": [
6
6
  "mcp",
@@ -29,9 +29,9 @@
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.29.0",
31
31
  "@radix-ui/react-primitive": "^2.1.5",
32
- "@assistant-ui/core": "0.2.12",
33
- "@assistant-ui/store": "0.2.14",
34
- "@assistant-ui/tap": "0.6.1",
32
+ "@assistant-ui/core": "0.2.14",
33
+ "@assistant-ui/store": "0.2.16",
34
+ "@assistant-ui/tap": "0.7.1",
35
35
  "assistant-stream": "0.3.21"
36
36
  },
37
37
  "peerDependencies": {
@@ -36,9 +36,9 @@ function defaultRedirectUri(): string {
36
36
  // array each render (which would invalidate the serverElements memo below).
37
37
  const NO_CONNECTORS: MCPConnector[] = [];
38
38
 
39
- export const McpManagerResource = resource(function McpManagerResource(
39
+ const useMcpManagerResource = (
40
40
  props: McpManagerResourceProps,
41
- ): ClientOutput<"mcp"> {
41
+ ): ClientOutput<"mcp"> => {
42
42
  const connectors = props.connectors ?? NO_CONNECTORS;
43
43
  const autoConnect = props.autoConnect ?? true;
44
44
  const redirectUri = props.oauthRedirectUri ?? defaultRedirectUri();
@@ -234,12 +234,14 @@ export const McpManagerResource = resource(function McpManagerResource(
234
234
  }
235
235
  },
236
236
  };
237
- });
237
+ };
238
+
239
+ export const McpManagerResource = resource(useMcpManagerResource);
238
240
 
239
241
  // Ensure modelContext exists as a sibling when the manager mounts. If an
240
242
  // ancestor (e.g. a chat runtime) already provides modelContext, this is a
241
243
  // no-op; otherwise it's auto-mounted alongside `mcp`.
242
- attachTransformScopes(McpManagerResource, (scopes, parent) => {
244
+ attachTransformScopes(useMcpManagerResource, (scopes, parent) => {
243
245
  if (!scopes.modelContext && parent.modelContext.source === null) {
244
246
  scopes.modelContext = ModelContext();
245
247
  }
@@ -33,9 +33,9 @@ export type McpServerResourceProps = {
33
33
  onRemove: () => Promise<void>;
34
34
  };
35
35
 
36
- export const McpServerResource = resource(function McpServerResource(
36
+ const useMcpServerResource = (
37
37
  props: McpServerResourceProps,
38
- ): ClientOutput<"mcpServer"> {
38
+ ): ClientOutput<"mcpServer"> => {
39
39
  assertValidServerId(props.id);
40
40
  const [connectionState, setConnectionState] =
41
41
  useState<MCPConnectionState>("disconnected");
@@ -278,4 +278,6 @@ export const McpServerResource = resource(function McpServerResource(
278
278
  },
279
279
  completeAuth: doCompleteAuth,
280
280
  };
281
- });
281
+ };
282
+
283
+ export const McpServerResource = resource(useMcpServerResource);
@@ -1,8 +1,8 @@
1
1
  import { resource } from "@assistant-ui/tap";
2
2
  import type { MCPStorage } from "./types";
3
3
 
4
- export const McpCustomStorage = resource(function McpCustomStorage(
5
- impl: MCPStorage,
6
- ): MCPStorage {
4
+ const useMcpCustomStorage = (impl: MCPStorage): MCPStorage => {
7
5
  return impl;
8
- });
6
+ };
7
+
8
+ export const McpCustomStorage = resource(useMcpCustomStorage);
@@ -22,9 +22,7 @@ function resolveStorage(opts: McpLocalStorageOptions): Storage | null {
22
22
  return null;
23
23
  }
24
24
 
25
- export const McpLocalStorage = resource(function McpLocalStorage(
26
- opts: McpLocalStorageOptions = {},
27
- ): MCPStorage {
25
+ const useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {
28
26
  const prefix = opts.keyPrefix ?? "aui-mcp";
29
27
  const customServersKey = `${prefix}:custom-servers`;
30
28
  const authKey = (id: string) => `${prefix}:auth:${id}`;
@@ -74,4 +72,6 @@ export const McpLocalStorage = resource(function McpLocalStorage(
74
72
  remove(authKey(id));
75
73
  },
76
74
  };
77
- });
75
+ };
76
+
77
+ export const McpLocalStorage = resource(useMcpLocalStorage);
@@ -3,22 +3,22 @@ import type { MCPCustomServerRecord } from "../../mcp-scope";
3
3
  import type { MCPPersistedAuthState } from "../../auth/types";
4
4
  import type { MCPStorage } from "./types";
5
5
 
6
- export const McpMemoryStorage = resource(
7
- function McpMemoryStorage(): MCPStorage {
8
- let servers: MCPCustomServerRecord[] = [];
9
- const auth = new Map<string, MCPPersistedAuthState>();
10
- return {
11
- loadCustomServers: async () => [...servers],
12
- saveCustomServers: async (records) => {
13
- servers = [...records];
14
- },
15
- loadAuthState: async (id) => auth.get(id) ?? null,
16
- saveAuthState: async (id, state) => {
17
- auth.set(id, state);
18
- },
19
- clearAuthState: async (id) => {
20
- auth.delete(id);
21
- },
22
- };
23
- },
24
- );
6
+ const useMcpMemoryStorage = (): MCPStorage => {
7
+ let servers: MCPCustomServerRecord[] = [];
8
+ const auth = new Map<string, MCPPersistedAuthState>();
9
+ return {
10
+ loadCustomServers: async () => [...servers],
11
+ saveCustomServers: async (records) => {
12
+ servers = [...records];
13
+ },
14
+ loadAuthState: async (id) => auth.get(id) ?? null,
15
+ saveAuthState: async (id, state) => {
16
+ auth.set(id, state);
17
+ },
18
+ clearAuthState: async (id) => {
19
+ auth.delete(id);
20
+ },
21
+ };
22
+ };
23
+
24
+ export const McpMemoryStorage = resource(useMcpMemoryStorage);