@assistant-ui/react-mcp 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/auth/createOAuthProvider.js.map +1 -1
  2. package/dist/context/McpConnectorByIndexProvider.js.map +1 -1
  3. package/dist/context/McpCustomServerByIndexProvider.js.map +1 -1
  4. package/dist/context/McpServerByIdProvider.js.map +1 -1
  5. package/dist/hooks/useMcpOAuthCallback.js +1 -1
  6. package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
  7. package/dist/primitives/addForm/McpAddFormAuthSelect.js +1 -1
  8. package/dist/primitives/addForm/McpAddFormCancel.js +1 -1
  9. package/dist/primitives/addForm/McpAddFormError.js +1 -1
  10. package/dist/primitives/addForm/McpAddFormNameField.js +1 -1
  11. package/dist/primitives/addForm/McpAddFormRoot.js +1 -1
  12. package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
  13. package/dist/primitives/addForm/McpAddFormSubmit.js +1 -1
  14. package/dist/primitives/addForm/McpAddFormUrlField.js +1 -1
  15. package/dist/primitives/addForm/context.js +1 -1
  16. package/dist/primitives/manager/McpManagerAddCustomTrigger.js +1 -1
  17. package/dist/primitives/manager/McpManagerConnectors.js +1 -1
  18. package/dist/primitives/manager/McpManagerConnectors.js.map +1 -1
  19. package/dist/primitives/manager/McpManagerCustomServers.js +1 -1
  20. package/dist/primitives/manager/McpManagerCustomServers.js.map +1 -1
  21. package/dist/primitives/manager/McpManagerRoot.js +1 -1
  22. package/dist/primitives/server/McpServerConnectButton.js +1 -1
  23. package/dist/primitives/server/McpServerConnectButton.js.map +1 -1
  24. package/dist/primitives/server/McpServerDisconnectButton.js +1 -1
  25. package/dist/primitives/server/McpServerDisconnectButton.js.map +1 -1
  26. package/dist/primitives/server/McpServerError.js +1 -1
  27. package/dist/primitives/server/McpServerIcon.js +1 -1
  28. package/dist/primitives/server/McpServerName.js +1 -1
  29. package/dist/primitives/server/McpServerOAuthLink.js +1 -1
  30. package/dist/primitives/server/McpServerRemoveButton.js +1 -1
  31. package/dist/primitives/server/McpServerRemoveButton.js.map +1 -1
  32. package/dist/primitives/server/McpServerRoot.js +1 -1
  33. package/dist/primitives/server/McpServerStatus.js +1 -1
  34. package/dist/primitives/server/McpServerToolName.js +1 -1
  35. package/dist/primitives/server/McpServerTools.js +1 -1
  36. package/dist/resources/McpManagerResource.d.ts.map +1 -1
  37. package/dist/resources/McpManagerResource.js +20 -18
  38. package/dist/resources/McpManagerResource.js.map +1 -1
  39. package/dist/resources/McpServerResource.d.ts.map +1 -1
  40. package/dist/resources/McpServerResource.js +17 -16
  41. package/dist/resources/McpServerResource.js.map +1 -1
  42. package/dist/resources/storage/McpCustomStorage.js +3 -1
  43. package/dist/resources/storage/McpCustomStorage.js.map +1 -1
  44. package/dist/resources/storage/McpLocalStorage.js +1 -1
  45. package/dist/resources/storage/McpLocalStorage.js.map +1 -1
  46. package/dist/resources/storage/McpMemoryStorage.js +1 -1
  47. package/dist/resources/storage/McpMemoryStorage.js.map +1 -1
  48. package/package.json +9 -9
  49. package/src/resources/McpManagerResource.ts +196 -203
  50. package/src/resources/McpServerResource.ts +228 -239
  51. package/src/resources/storage/McpCustomStorage.ts +5 -3
  52. package/src/resources/storage/McpLocalStorage.ts +49 -49
  53. package/src/resources/storage/McpMemoryStorage.ts +19 -17
  54. package/src/resources/storage/types.ts +1 -1
@@ -1,11 +1,5 @@
1
- import {
2
- resource,
3
- tapState,
4
- tapRef,
5
- tapEffect,
6
- tapMemo,
7
- tapEffectEvent,
8
- } from "@assistant-ui/tap";
1
+ import { useState, useRef, useEffect, useMemo, useEffectEvent } from "react";
2
+ import { resource } from "@assistant-ui/tap";
9
3
  import type { ClientOutput } from "@assistant-ui/store";
10
4
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
5
  import {
@@ -39,254 +33,249 @@ export type McpServerResourceProps = {
39
33
  onRemove: () => Promise<void>;
40
34
  };
41
35
 
42
- export const McpServerResource = resource(
43
- (props: McpServerResourceProps): ClientOutput<"mcpServer"> => {
44
- assertValidServerId(props.id);
45
- const [connectionState, setConnectionState] =
46
- tapState<MCPConnectionState>("disconnected");
47
- const [tools, setTools] = tapState<MCPToolInfo[]>([]);
48
- const [lastError, setLastError] = tapState<{ message: string } | null>(
49
- null,
50
- );
51
- const [authorizationUrl, setAuthorizationUrl] = tapState<string | null>(
52
- null,
53
- );
54
-
55
- const clientRef = tapRef<Client | null>(null);
56
- const transportRef = tapRef<StreamableHTTPClientTransport | null>(null);
36
+ export const McpServerResource = resource(function McpServerResource(
37
+ props: McpServerResourceProps,
38
+ ): ClientOutput<"mcpServer"> {
39
+ assertValidServerId(props.id);
40
+ const [connectionState, setConnectionState] =
41
+ useState<MCPConnectionState>("disconnected");
42
+ const [tools, setTools] = useState<MCPToolInfo[]>([]);
43
+ const [lastError, setLastError] = useState<{ message: string } | null>(null);
44
+ const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);
57
45
 
58
- const buildTransport = tapEffectEvent(
59
- async (): Promise<StreamableHTTPClientTransport> => {
60
- if (props.auth.type === "oauth") {
61
- const authProvider = createOAuthProvider({
62
- serverId: props.id,
63
- config: props.auth,
64
- storage: props.storage,
65
- redirectUri: props.redirectUri,
66
- onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),
67
- });
68
- return new StreamableHTTPClientTransport(new URL(props.url), {
69
- authProvider,
70
- });
71
- }
72
- if (props.auth.type === "bearer") {
73
- const persisted = await props.storage.loadAuthState(props.id);
74
- const headers = buildHeaders(props.auth, persisted);
75
- const transportOpts: StreamableHTTPClientTransportOptions = {};
76
- if (headers) transportOpts.requestInit = { headers };
77
- return new StreamableHTTPClientTransport(
78
- new URL(props.url),
79
- transportOpts,
80
- );
81
- }
82
- return new StreamableHTTPClientTransport(new URL(props.url));
83
- },
84
- );
46
+ const clientRef = useRef<Client | null>(null);
47
+ const transportRef = useRef<StreamableHTTPClientTransport | null>(null);
85
48
 
86
- const finalizeConnect = tapEffectEvent(
87
- async (transport: StreamableHTTPClientTransport) => {
88
- const client = new Client({
89
- name: "assistant-ui-mcp",
90
- version: "0.0.0",
49
+ const buildTransport = useEffectEvent(
50
+ async (): Promise<StreamableHTTPClientTransport> => {
51
+ if (props.auth.type === "oauth") {
52
+ const authProvider = createOAuthProvider({
53
+ serverId: props.id,
54
+ config: props.auth,
55
+ storage: props.storage,
56
+ redirectUri: props.redirectUri,
57
+ onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),
91
58
  });
92
- // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`
93
- // but Transport.sessionId is declared `string?` — under
94
- // exactOptionalPropertyTypes the SDK's own classes don't satisfy its
95
- // Transport interface. Cast to bridge the gap.
96
- await client.connect(transport as unknown as Transport);
97
- // Defer ref assignment until listTools() also succeeds — otherwise a
98
- // post-connect failure leaves stale refs that `callTool()` would
99
- // happily walk into, producing confusing SDK errors instead of
100
- // "not connected".
101
- const list = await client.listTools();
102
- clientRef.current = client;
103
- transportRef.current = transport;
104
- setTools(
105
- list.tools.map((t) => {
106
- const info: MCPToolInfo = {
107
- name: t.name,
108
- inputSchema: t.inputSchema,
109
- };
110
- if (t.description !== undefined) info.description = t.description;
111
- return info;
112
- }),
59
+ return new StreamableHTTPClientTransport(new URL(props.url), {
60
+ authProvider,
61
+ });
62
+ }
63
+ if (props.auth.type === "bearer") {
64
+ const persisted = await props.storage.loadAuthState(props.id);
65
+ const headers = buildHeaders(props.auth, persisted);
66
+ const transportOpts: StreamableHTTPClientTransportOptions = {};
67
+ if (headers) transportOpts.requestInit = { headers };
68
+ return new StreamableHTTPClientTransport(
69
+ new URL(props.url),
70
+ transportOpts,
113
71
  );
114
- setConnectionState("connected");
115
- },
116
- );
117
-
118
- const closeTransport = async () => {
119
- const t = transportRef.current;
120
- transportRef.current = null;
121
- clientRef.current = null;
122
- if (t) {
123
- try {
124
- await t.close();
125
- } catch {
126
- // ignore close errors
127
- }
128
72
  }
129
- };
73
+ return new StreamableHTTPClientTransport(new URL(props.url));
74
+ },
75
+ );
130
76
 
131
- const doConnect = tapEffectEvent(async () => {
132
- // Close any prior transport/client so a re-connect doesn't leak.
133
- await closeTransport();
134
- setConnectionState("connecting");
135
- setLastError(null);
136
- setAuthorizationUrl(null);
137
- // Clear tools so a reconnect (error → connect, or authRequired
138
- // → connect) doesn't expose the previous attempt's tool list.
139
- setTools([]);
140
- let transport: StreamableHTTPClientTransport | null = null;
77
+ const finalizeConnect = useEffectEvent(
78
+ async (transport: StreamableHTTPClientTransport) => {
79
+ const client = new Client({
80
+ name: "assistant-ui-mcp",
81
+ version: "0.0.0",
82
+ });
83
+ // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`
84
+ // but Transport.sessionId is declared `string?` — under
85
+ // exactOptionalPropertyTypes the SDK's own classes don't satisfy its
86
+ // Transport interface. Cast to bridge the gap.
87
+ await client.connect(transport as unknown as Transport);
88
+ // Defer ref assignment until listTools() also succeeds — otherwise a
89
+ // post-connect failure leaves stale refs that `callTool()` would
90
+ // happily walk into, producing confusing SDK errors instead of
91
+ // "not connected".
92
+ const list = await client.listTools();
93
+ clientRef.current = client;
94
+ transportRef.current = transport;
95
+ setTools(
96
+ list.tools.map((t) => {
97
+ const info: MCPToolInfo = {
98
+ name: t.name,
99
+ inputSchema: t.inputSchema,
100
+ };
101
+ if (t.description !== undefined) info.description = t.description;
102
+ return info;
103
+ }),
104
+ );
105
+ setConnectionState("connected");
106
+ },
107
+ );
108
+
109
+ const closeTransport = async () => {
110
+ const t = transportRef.current;
111
+ transportRef.current = null;
112
+ clientRef.current = null;
113
+ if (t) {
141
114
  try {
142
- transport = await buildTransport();
143
- // Don't assign to transportRef until connect succeeds — otherwise a
144
- // failed `listTools()` leaves an orphaned transport that future
145
- // doConnect / doDisconnect calls treat as live.
146
- await finalizeConnect(transport);
147
- } catch (err) {
148
- if (err instanceof UnauthorizedError) {
149
- // OAuth: keep the transport alive so completeAuth can call
150
- // finishAuth on it. Closing it before storing would leave a
151
- // closed transport on transportRef.
152
- transportRef.current = transport;
153
- setConnectionState("authRequired");
154
- } else {
155
- if (transport) {
156
- try {
157
- await transport.close();
158
- } catch {
159
- // ignore close errors
160
- }
161
- }
162
- setLastError({
163
- message: err instanceof Error ? err.message : String(err),
164
- });
165
- setConnectionState("error");
166
- }
115
+ await t.close();
116
+ } catch {
117
+ // ignore close errors
167
118
  }
168
- });
119
+ }
120
+ };
169
121
 
170
- const doDisconnect = tapEffectEvent(async () => {
171
- setTools([]);
172
- setAuthorizationUrl(null);
173
- setConnectionState("disconnected");
174
- await closeTransport();
175
- });
176
-
177
- const doCompleteAuth = tapEffectEvent(async (callbackUrl: string) => {
178
- setConnectionState("authPending");
179
- setLastError(null);
180
- try {
181
- const url = new URL(callbackUrl);
182
- const code = url.searchParams.get("code");
183
- if (!code)
184
- throw new Error("missing authorization code in callback URL");
185
- let transport = transportRef.current;
186
- if (!transport) {
187
- transport = await buildTransport();
188
- transportRef.current = transport;
122
+ const doConnect = useEffectEvent(async () => {
123
+ // Close any prior transport/client so a re-connect doesn't leak.
124
+ await closeTransport();
125
+ setConnectionState("connecting");
126
+ setLastError(null);
127
+ setAuthorizationUrl(null);
128
+ // Clear tools so a reconnect (error → connect, or authRequired
129
+ // → connect) doesn't expose the previous attempt's tool list.
130
+ setTools([]);
131
+ let transport: StreamableHTTPClientTransport | null = null;
132
+ try {
133
+ transport = await buildTransport();
134
+ // Don't assign to transportRef until connect succeeds — otherwise a
135
+ // failed `listTools()` leaves an orphaned transport that future
136
+ // doConnect / doDisconnect calls treat as live.
137
+ await finalizeConnect(transport);
138
+ } catch (err) {
139
+ if (err instanceof UnauthorizedError) {
140
+ // OAuth: keep the transport alive so completeAuth can call
141
+ // finishAuth on it. Closing it before storing would leave a
142
+ // closed transport on transportRef.
143
+ transportRef.current = transport;
144
+ setConnectionState("authRequired");
145
+ } else {
146
+ if (transport) {
147
+ try {
148
+ await transport.close();
149
+ } catch {
150
+ // ignore close errors
151
+ }
189
152
  }
190
- await transport.finishAuth(code);
191
- setAuthorizationUrl(null);
192
- await finalizeConnect(transport);
193
- } catch (err) {
194
- await closeTransport();
195
153
  setLastError({
196
154
  message: err instanceof Error ? err.message : String(err),
197
155
  });
198
156
  setConnectionState("error");
199
157
  }
200
- });
158
+ }
159
+ });
201
160
 
202
- const tryAutoConnect = tapEffectEvent(
203
- async (signal: { cancelled: boolean }) => {
204
- if (!props.autoConnect) return;
205
- if (props.auth.type === "none") {
206
- void doConnect();
207
- return;
208
- }
209
- // Static connector token short-circuits the storage read.
210
- if (props.auth.type === "bearer" && props.auth.token) {
211
- void doConnect();
212
- return;
213
- }
214
- const persisted = await props.storage.loadAuthState(props.id);
215
- if (signal.cancelled) return;
216
- if (props.auth.type === "oauth") {
217
- if (!persisted?.tokens) return;
218
- } else if (!persisted?.token) {
219
- return;
220
- }
221
- void doConnect();
222
- },
223
- );
161
+ const doDisconnect = useEffectEvent(async () => {
162
+ setTools([]);
163
+ setAuthorizationUrl(null);
164
+ setConnectionState("disconnected");
165
+ await closeTransport();
166
+ });
224
167
 
225
- // Auto-connect on mount when usable auth exists.
226
- tapEffect(() => {
227
- const signal = { cancelled: false };
228
- void tryAutoConnect(signal);
229
- return () => {
230
- signal.cancelled = true;
231
- const t = transportRef.current;
232
- transportRef.current = null;
233
- clientRef.current = null;
234
- if (t) t.close().catch(() => {});
235
- };
236
- }, []);
168
+ const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {
169
+ setConnectionState("authPending");
170
+ setLastError(null);
171
+ try {
172
+ const url = new URL(callbackUrl);
173
+ const code = url.searchParams.get("code");
174
+ if (!code) throw new Error("missing authorization code in callback URL");
175
+ let transport = transportRef.current;
176
+ if (!transport) {
177
+ transport = await buildTransport();
178
+ transportRef.current = transport;
179
+ }
180
+ await transport.finishAuth(code);
181
+ setAuthorizationUrl(null);
182
+ await finalizeConnect(transport);
183
+ } catch (err) {
184
+ await closeTransport();
185
+ setLastError({
186
+ message: err instanceof Error ? err.message : String(err),
187
+ });
188
+ setConnectionState("error");
189
+ }
190
+ });
237
191
 
238
- const state = tapMemo<MCPServerState>(
239
- () => ({
240
- id: props.id,
241
- kind: props.kind,
242
- name: props.name,
243
- url: props.url,
244
- icon: props.icon,
245
- connectionState,
246
- lastError,
247
- tools,
248
- authorizationUrl,
249
- }),
250
- [
251
- props.id,
252
- props.kind,
253
- props.name,
254
- props.url,
255
- props.icon,
256
- connectionState,
257
- lastError,
258
- tools,
259
- authorizationUrl,
260
- ],
261
- );
192
+ const tryAutoConnect = useEffectEvent(
193
+ async (signal: { cancelled: boolean }) => {
194
+ if (!props.autoConnect) return;
195
+ if (props.auth.type === "none") {
196
+ void doConnect();
197
+ return;
198
+ }
199
+ // Static connector token short-circuits the storage read.
200
+ if (props.auth.type === "bearer" && props.auth.token) {
201
+ void doConnect();
202
+ return;
203
+ }
204
+ const persisted = await props.storage.loadAuthState(props.id);
205
+ if (signal.cancelled) return;
206
+ if (props.auth.type === "oauth") {
207
+ if (!persisted?.tokens) return;
208
+ } else if (!persisted?.token) {
209
+ return;
210
+ }
211
+ void doConnect();
212
+ },
213
+ );
262
214
 
263
- return {
264
- getState: () => state,
265
- connect: doConnect,
266
- disconnect: doDisconnect,
267
- remove: async () => {
268
- await doDisconnect();
269
- await props.storage.clearAuthState(props.id);
270
- await props.onRemove();
271
- },
272
- callTool: async (name, args) => {
273
- const client = clientRef.current;
274
- if (!client) {
275
- throw new Error(`MCP server "${props.id}" is not connected`);
276
- }
277
- return await client.callTool({
278
- name,
279
- arguments: args as Record<string, unknown> | undefined,
280
- });
281
- },
282
- readResource: async (uri) => {
283
- const client = clientRef.current;
284
- if (!client) {
285
- throw new Error(`MCP server "${props.id}" is not connected`);
286
- }
287
- return await client.readResource({ uri });
288
- },
289
- completeAuth: doCompleteAuth,
215
+ // Auto-connect on mount when usable auth exists.
216
+ useEffect(() => {
217
+ const signal = { cancelled: false };
218
+ void tryAutoConnect(signal);
219
+ return () => {
220
+ signal.cancelled = true;
221
+ const t = transportRef.current;
222
+ transportRef.current = null;
223
+ clientRef.current = null;
224
+ if (t) t.close().catch(() => {});
290
225
  };
291
- },
292
- );
226
+ }, []);
227
+
228
+ const state = useMemo<MCPServerState>(
229
+ () => ({
230
+ id: props.id,
231
+ kind: props.kind,
232
+ name: props.name,
233
+ url: props.url,
234
+ icon: props.icon,
235
+ connectionState,
236
+ lastError,
237
+ tools,
238
+ authorizationUrl,
239
+ }),
240
+ [
241
+ props.id,
242
+ props.kind,
243
+ props.name,
244
+ props.url,
245
+ props.icon,
246
+ connectionState,
247
+ lastError,
248
+ tools,
249
+ authorizationUrl,
250
+ ],
251
+ );
252
+
253
+ return {
254
+ getState: () => state,
255
+ connect: doConnect,
256
+ disconnect: doDisconnect,
257
+ remove: async () => {
258
+ await doDisconnect();
259
+ await props.storage.clearAuthState(props.id);
260
+ await props.onRemove();
261
+ },
262
+ callTool: async (name, args) => {
263
+ const client = clientRef.current;
264
+ if (!client) {
265
+ throw new Error(`MCP server "${props.id}" is not connected`);
266
+ }
267
+ return await client.callTool({
268
+ name,
269
+ arguments: args as Record<string, unknown> | undefined,
270
+ });
271
+ },
272
+ readResource: async (uri) => {
273
+ const client = clientRef.current;
274
+ if (!client) {
275
+ throw new Error(`MCP server "${props.id}" is not connected`);
276
+ }
277
+ return await client.readResource({ uri });
278
+ },
279
+ completeAuth: doCompleteAuth,
280
+ };
281
+ });
@@ -1,6 +1,8 @@
1
1
  import { resource } from "@assistant-ui/tap";
2
2
  import type { MCPStorage } from "./types";
3
3
 
4
- export const McpCustomStorage = resource(
5
- (impl: MCPStorage): MCPStorage => impl,
6
- );
4
+ export const McpCustomStorage = resource(function McpCustomStorage(
5
+ impl: MCPStorage,
6
+ ): MCPStorage {
7
+ return impl;
8
+ });
@@ -22,56 +22,56 @@ function resolveStorage(opts: McpLocalStorageOptions): Storage | null {
22
22
  return null;
23
23
  }
24
24
 
25
- export const McpLocalStorage = resource(
26
- (opts: McpLocalStorageOptions = {}): MCPStorage => {
27
- const prefix = opts.keyPrefix ?? "aui-mcp";
28
- const customServersKey = `${prefix}:custom-servers`;
29
- const authKey = (id: string) => `${prefix}:auth:${id}`;
30
- const storage = resolveStorage(opts);
25
+ export const McpLocalStorage = resource(function McpLocalStorage(
26
+ opts: McpLocalStorageOptions = {},
27
+ ): MCPStorage {
28
+ const prefix = opts.keyPrefix ?? "aui-mcp";
29
+ const customServersKey = `${prefix}:custom-servers`;
30
+ const authKey = (id: string) => `${prefix}:auth:${id}`;
31
+ const storage = resolveStorage(opts);
31
32
 
32
- const read = <T>(key: string, fallback: T): T => {
33
- if (!storage) return fallback;
34
- try {
35
- const raw = storage.getItem(key);
36
- if (raw == null) return fallback;
37
- return JSON.parse(raw) as T;
38
- } catch {
39
- return fallback;
40
- }
41
- };
33
+ const read = <T>(key: string, fallback: T): T => {
34
+ if (!storage) return fallback;
35
+ try {
36
+ const raw = storage.getItem(key);
37
+ if (raw == null) return fallback;
38
+ return JSON.parse(raw) as T;
39
+ } catch {
40
+ return fallback;
41
+ }
42
+ };
42
43
 
43
- const write = (key: string, value: unknown): void => {
44
- if (!storage) return;
45
- try {
46
- storage.setItem(key, JSON.stringify(value));
47
- } catch {
48
- // quota or serialization failure — silently drop
49
- }
50
- };
44
+ const write = (key: string, value: unknown): void => {
45
+ if (!storage) return;
46
+ try {
47
+ storage.setItem(key, JSON.stringify(value));
48
+ } catch {
49
+ // quota or serialization failure — silently drop
50
+ }
51
+ };
51
52
 
52
- const remove = (key: string): void => {
53
- if (!storage) return;
54
- try {
55
- storage.removeItem(key);
56
- } catch {
57
- // ignore
58
- }
59
- };
53
+ const remove = (key: string): void => {
54
+ if (!storage) return;
55
+ try {
56
+ storage.removeItem(key);
57
+ } catch {
58
+ // ignore
59
+ }
60
+ };
60
61
 
61
- return {
62
- loadCustomServers: async () =>
63
- read<MCPCustomServerRecord[]>(customServersKey, []),
64
- saveCustomServers: async (records) => {
65
- write(customServersKey, records);
66
- },
67
- loadAuthState: async (id) =>
68
- read<MCPPersistedAuthState | null>(authKey(id), null),
69
- saveAuthState: async (id, state) => {
70
- write(authKey(id), state);
71
- },
72
- clearAuthState: async (id) => {
73
- remove(authKey(id));
74
- },
75
- };
76
- },
77
- );
62
+ return {
63
+ loadCustomServers: async () =>
64
+ read<MCPCustomServerRecord[]>(customServersKey, []),
65
+ saveCustomServers: async (records) => {
66
+ write(customServersKey, records);
67
+ },
68
+ loadAuthState: async (id) =>
69
+ read<MCPPersistedAuthState | null>(authKey(id), null),
70
+ saveAuthState: async (id, state) => {
71
+ write(authKey(id), state);
72
+ },
73
+ clearAuthState: async (id) => {
74
+ remove(authKey(id));
75
+ },
76
+ };
77
+ });
@@ -3,20 +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((): 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
- });
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
+ );
@@ -1,4 +1,4 @@
1
- import type { ResourceElement } from "@assistant-ui/tap";
1
+ import { type ResourceElement } from "@assistant-ui/tap";
2
2
  import type { MCPCustomServerRecord } from "../../mcp-scope";
3
3
  import type { MCPPersistedAuthState } from "../../auth/types";
4
4