@assistant-ui/react-mcp 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/buildHeaders.d.ts +0 -1
- package/dist/auth/buildHeaders.d.ts.map +1 -1
- package/dist/auth/createOAuthProvider.d.ts +4 -3
- package/dist/auth/createOAuthProvider.d.ts.map +1 -1
- package/dist/auth/createOAuthProvider.js +2 -1
- package/dist/auth/createOAuthProvider.js.map +1 -1
- package/dist/auth/types.d.ts +2 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/connector.d.ts +0 -1
- package/dist/connector.d.ts.map +1 -1
- package/dist/context/McpConnectorByIndexProvider.d.ts +0 -1
- package/dist/context/McpConnectorByIndexProvider.d.ts.map +1 -1
- package/dist/context/McpCustomServerByIndexProvider.d.ts +0 -1
- package/dist/context/McpCustomServerByIndexProvider.d.ts.map +1 -1
- package/dist/context/McpServerByIdProvider.d.ts +0 -1
- package/dist/context/McpServerByIdProvider.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.d.ts +4 -3
- package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.js +14 -8
- package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
- package/dist/mcp-scope.d.ts +16 -6
- package/dist/mcp-scope.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthFields.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormAuthFields.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormAuthSelect.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormCancel.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormCancel.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormError.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormError.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormError.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormNameField.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormNameField.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormRoot.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormRoot.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormRoot.js.map +1 -1
- package/dist/primitives/addForm/McpAddFormSubmit.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormSubmit.d.ts.map +1 -1
- package/dist/primitives/addForm/McpAddFormUrlField.d.ts +0 -1
- package/dist/primitives/addForm/McpAddFormUrlField.d.ts.map +1 -1
- package/dist/primitives/addForm/context.d.ts +0 -1
- package/dist/primitives/addForm/context.d.ts.map +1 -1
- package/dist/primitives/addForm.d.ts +0 -2
- package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts +0 -1
- package/dist/primitives/manager/McpManagerAddCustomTrigger.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerConnectors.d.ts +0 -1
- package/dist/primitives/manager/McpManagerConnectors.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerConnectors.js.map +1 -1
- package/dist/primitives/manager/McpManagerCustomServers.d.ts +0 -1
- package/dist/primitives/manager/McpManagerCustomServers.d.ts.map +1 -1
- package/dist/primitives/manager/McpManagerCustomServers.js.map +1 -1
- package/dist/primitives/manager/McpManagerRoot.d.ts +0 -1
- package/dist/primitives/manager/McpManagerRoot.d.ts.map +1 -1
- package/dist/primitives/manager.d.ts +0 -2
- package/dist/primitives/server/McpServerConnectButton.d.ts +0 -1
- package/dist/primitives/server/McpServerConnectButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerDisconnectButton.d.ts +0 -1
- package/dist/primitives/server/McpServerDisconnectButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerError.d.ts +0 -1
- package/dist/primitives/server/McpServerError.d.ts.map +1 -1
- package/dist/primitives/server/McpServerError.js.map +1 -1
- package/dist/primitives/server/McpServerIcon.d.ts +4 -3
- package/dist/primitives/server/McpServerIcon.d.ts.map +1 -1
- package/dist/primitives/server/McpServerName.d.ts +0 -1
- package/dist/primitives/server/McpServerName.d.ts.map +1 -1
- package/dist/primitives/server/McpServerName.js.map +1 -1
- package/dist/primitives/server/McpServerOAuthLink.d.ts +4 -3
- package/dist/primitives/server/McpServerOAuthLink.d.ts.map +1 -1
- package/dist/primitives/server/McpServerRemoveButton.d.ts +0 -1
- package/dist/primitives/server/McpServerRemoveButton.d.ts.map +1 -1
- package/dist/primitives/server/McpServerRoot.d.ts +0 -1
- package/dist/primitives/server/McpServerRoot.d.ts.map +1 -1
- package/dist/primitives/server/McpServerStatus.d.ts +0 -1
- package/dist/primitives/server/McpServerStatus.d.ts.map +1 -1
- package/dist/primitives/server/McpServerStatus.js.map +1 -1
- package/dist/primitives/server/McpServerToolName.d.ts +0 -1
- package/dist/primitives/server/McpServerToolName.d.ts.map +1 -1
- package/dist/primitives/server/McpServerToolName.js.map +1 -1
- package/dist/primitives/server/McpServerTools.d.ts +0 -1
- package/dist/primitives/server/McpServerTools.d.ts.map +1 -1
- package/dist/primitives/server/McpServerTools.js.map +1 -1
- package/dist/primitives/server.d.ts +0 -2
- package/dist/resources/McpManagerResource.d.ts +6 -4
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.d.ts +0 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +99 -34
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/resources/storage/McpCustomStorage.d.ts +0 -1
- package/dist/resources/storage/McpCustomStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.d.ts +8 -3
- package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.js +66 -3
- package/dist/resources/storage/McpLocalStorage.js.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts +0 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
- package/dist/resources/storage/types.d.ts +0 -1
- package/dist/resources/storage/types.d.ts.map +1 -1
- package/dist/utils/serverId.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/hooks/useMcpOAuthCallback.test.ts +24 -0
- package/src/hooks/useMcpOAuthCallback.tsx +20 -5
- package/src/mcp-scope.ts +2 -0
- package/src/resources/McpServerResource.test.ts +327 -11
- package/src/resources/McpServerResource.ts +124 -30
- package/src/resources/storage/McpLocalStorage.test.ts +240 -0
- package/src/resources/storage/McpLocalStorage.ts +134 -3
|
@@ -15,6 +15,39 @@ const useMcpServerResource = (props) => {
|
|
|
15
15
|
const [authorizationUrl, setAuthorizationUrl] = useState(null);
|
|
16
16
|
const clientRef = useRef(null);
|
|
17
17
|
const transportRef = useRef(null);
|
|
18
|
+
const pendingTransportRef = useRef(null);
|
|
19
|
+
const transportCloseQueueRef = useRef(Promise.resolve());
|
|
20
|
+
const connectionGenerationRef = useRef(0);
|
|
21
|
+
const pendingDisposalRef = useRef(null);
|
|
22
|
+
const mountedRef = useRef(true);
|
|
23
|
+
const closeTransportSafely = async (transport) => {
|
|
24
|
+
try {
|
|
25
|
+
await transport.close();
|
|
26
|
+
} catch {}
|
|
27
|
+
};
|
|
28
|
+
const closeQueuedTransports = (transports) => {
|
|
29
|
+
const task = transportCloseQueueRef.current.then(async () => {
|
|
30
|
+
await Promise.all(transports.map(closeTransportSafely));
|
|
31
|
+
});
|
|
32
|
+
transportCloseQueueRef.current = task;
|
|
33
|
+
return task;
|
|
34
|
+
};
|
|
35
|
+
const closePendingTransport = async () => {
|
|
36
|
+
const transport_0 = pendingTransportRef.current;
|
|
37
|
+
pendingTransportRef.current = null;
|
|
38
|
+
await closeQueuedTransports(transport_0 ? [transport_0] : []);
|
|
39
|
+
};
|
|
40
|
+
const closeTransports = async () => {
|
|
41
|
+
const pendingTransport = pendingTransportRef.current;
|
|
42
|
+
const activeTransport = transportRef.current;
|
|
43
|
+
pendingTransportRef.current = null;
|
|
44
|
+
transportRef.current = null;
|
|
45
|
+
clientRef.current = null;
|
|
46
|
+
const transports_0 = new Set([pendingTransport, activeTransport].filter((transport_1) => transport_1 !== null));
|
|
47
|
+
await closeQueuedTransports([...transports_0]);
|
|
48
|
+
};
|
|
49
|
+
const isCurrentConnection = (generation) => mountedRef.current && generation === connectionGenerationRef.current;
|
|
50
|
+
const createInterruptedAuthError = (cause) => new Error(`MCP server "${props.id}" authorization was interrupted before completion.`, cause === void 0 ? void 0 : { cause });
|
|
18
51
|
const withConnectionTimeout = useEffectEvent(async (promise, phase, startedAt) => {
|
|
19
52
|
const timeoutMs = props.connectionTimeout;
|
|
20
53
|
if (timeoutMs === void 0) return await promise;
|
|
@@ -50,16 +83,19 @@ const useMcpServerResource = (props) => {
|
|
|
50
83
|
}
|
|
51
84
|
return new StreamableHTTPClientTransport(new URL(props.url));
|
|
52
85
|
});
|
|
53
|
-
const finalizeConnect = useEffectEvent(async (
|
|
86
|
+
const finalizeConnect = useEffectEvent(async (transport_2, generation_0) => {
|
|
54
87
|
const client = new Client({
|
|
55
88
|
name: "assistant-ui-mcp",
|
|
56
89
|
version: "0.0.0"
|
|
57
90
|
});
|
|
58
91
|
const startedAt_0 = Date.now();
|
|
59
|
-
await withConnectionTimeout(client.connect(
|
|
92
|
+
await withConnectionTimeout(client.connect(transport_2), "connecting", startedAt_0);
|
|
93
|
+
if (!isCurrentConnection(generation_0)) return false;
|
|
60
94
|
const list = await withConnectionTimeout(client.listTools(), "listing tools", startedAt_0);
|
|
95
|
+
if (!isCurrentConnection(generation_0)) return false;
|
|
96
|
+
pendingTransportRef.current = null;
|
|
61
97
|
clientRef.current = client;
|
|
62
|
-
transportRef.current =
|
|
98
|
+
transportRef.current = transport_2;
|
|
63
99
|
setTools(list.tools.map((t) => {
|
|
64
100
|
const info = {
|
|
65
101
|
name: t.name,
|
|
@@ -69,62 +105,79 @@ const useMcpServerResource = (props) => {
|
|
|
69
105
|
return info;
|
|
70
106
|
}));
|
|
71
107
|
setConnectionState("connected");
|
|
108
|
+
return true;
|
|
72
109
|
});
|
|
73
|
-
const closeTransport = async () => {
|
|
74
|
-
const t_0 = transportRef.current;
|
|
75
|
-
transportRef.current = null;
|
|
76
|
-
clientRef.current = null;
|
|
77
|
-
if (t_0) try {
|
|
78
|
-
await t_0.close();
|
|
79
|
-
} catch {}
|
|
80
|
-
};
|
|
81
110
|
const doConnect = useEffectEvent(async () => {
|
|
82
|
-
|
|
111
|
+
const generation_1 = ++connectionGenerationRef.current;
|
|
112
|
+
await closeTransports();
|
|
113
|
+
if (!isCurrentConnection(generation_1)) return;
|
|
83
114
|
setConnectionState("connecting");
|
|
84
115
|
setLastError(null);
|
|
85
116
|
setAuthorizationUrl(null);
|
|
86
117
|
setTools([]);
|
|
87
|
-
let
|
|
118
|
+
let transport_3 = null;
|
|
88
119
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
transport_3 = await buildTransport();
|
|
121
|
+
if (!isCurrentConnection(generation_1)) {
|
|
122
|
+
await closeQueuedTransports([transport_3]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
pendingTransportRef.current = transport_3;
|
|
126
|
+
await finalizeConnect(transport_3, generation_1);
|
|
91
127
|
} catch (err) {
|
|
128
|
+
if (!isCurrentConnection(generation_1)) return;
|
|
92
129
|
if (err instanceof UnauthorizedError) {
|
|
93
|
-
|
|
130
|
+
pendingTransportRef.current = null;
|
|
131
|
+
transportRef.current = transport_3;
|
|
94
132
|
setConnectionState("authRequired");
|
|
95
133
|
} else {
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
134
|
+
if (transport_3) {
|
|
135
|
+
pendingTransportRef.current = null;
|
|
136
|
+
await closeQueuedTransports([transport_3]);
|
|
137
|
+
}
|
|
99
138
|
setLastError({ message: err instanceof Error ? err.message : String(err) });
|
|
100
139
|
setConnectionState("error");
|
|
101
140
|
}
|
|
102
141
|
}
|
|
103
142
|
});
|
|
104
143
|
const doDisconnect = useEffectEvent(async () => {
|
|
144
|
+
connectionGenerationRef.current += 1;
|
|
105
145
|
setTools([]);
|
|
106
146
|
setAuthorizationUrl(null);
|
|
107
147
|
setConnectionState("disconnected");
|
|
108
|
-
await
|
|
148
|
+
await closeTransports();
|
|
109
149
|
});
|
|
110
150
|
const doCompleteAuth = useEffectEvent(async (callbackUrl) => {
|
|
151
|
+
const generation_2 = ++connectionGenerationRef.current;
|
|
152
|
+
await closePendingTransport();
|
|
153
|
+
if (!isCurrentConnection(generation_2)) throw createInterruptedAuthError();
|
|
111
154
|
setConnectionState("authPending");
|
|
112
155
|
setLastError(null);
|
|
113
156
|
try {
|
|
114
157
|
const code = new URL(callbackUrl).searchParams.get("code");
|
|
115
158
|
if (!code) throw new Error("missing authorization code in callback URL");
|
|
116
|
-
let
|
|
117
|
-
if (!
|
|
118
|
-
|
|
119
|
-
|
|
159
|
+
let transport_4 = transportRef.current;
|
|
160
|
+
if (!transport_4) {
|
|
161
|
+
transport_4 = await buildTransport();
|
|
162
|
+
if (!isCurrentConnection(generation_2)) {
|
|
163
|
+
await closeQueuedTransports([transport_4]);
|
|
164
|
+
throw createInterruptedAuthError();
|
|
165
|
+
}
|
|
120
166
|
}
|
|
121
|
-
|
|
167
|
+
transportRef.current = null;
|
|
168
|
+
clientRef.current = null;
|
|
169
|
+
pendingTransportRef.current = transport_4;
|
|
170
|
+
await transport_4.finishAuth(code);
|
|
171
|
+
if (!isCurrentConnection(generation_2)) throw createInterruptedAuthError();
|
|
122
172
|
setAuthorizationUrl(null);
|
|
123
|
-
await finalizeConnect(
|
|
173
|
+
if (!await finalizeConnect(transport_4, generation_2)) throw createInterruptedAuthError();
|
|
124
174
|
} catch (err_0) {
|
|
125
|
-
|
|
126
|
-
|
|
175
|
+
const error = err_0 instanceof Error ? err_0 : new Error(String(err_0));
|
|
176
|
+
if (!isCurrentConnection(generation_2)) throw createInterruptedAuthError(error);
|
|
177
|
+
await closeTransports();
|
|
178
|
+
setLastError({ message: error.message });
|
|
127
179
|
setConnectionState("error");
|
|
180
|
+
throw error;
|
|
128
181
|
}
|
|
129
182
|
});
|
|
130
183
|
const tryAutoConnect = useEffectEvent(async (signal) => {
|
|
@@ -145,14 +198,21 @@ const useMcpServerResource = (props) => {
|
|
|
145
198
|
doConnect();
|
|
146
199
|
});
|
|
147
200
|
useEffect(() => {
|
|
201
|
+
const previousDisposal = pendingDisposalRef.current;
|
|
202
|
+
if (previousDisposal) previousDisposal.cancelled = true;
|
|
203
|
+
const pendingDisposal = { cancelled: false };
|
|
204
|
+
pendingDisposalRef.current = pendingDisposal;
|
|
205
|
+
mountedRef.current = true;
|
|
148
206
|
const signal_0 = { cancelled: false };
|
|
149
207
|
tryAutoConnect(signal_0);
|
|
150
208
|
return () => {
|
|
209
|
+
mountedRef.current = false;
|
|
151
210
|
signal_0.cancelled = true;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
211
|
+
queueMicrotask(() => {
|
|
212
|
+
if (pendingDisposal.cancelled) return;
|
|
213
|
+
connectionGenerationRef.current += 1;
|
|
214
|
+
closeTransports();
|
|
215
|
+
});
|
|
156
216
|
};
|
|
157
217
|
}, []);
|
|
158
218
|
const state = useMemo(() => ({
|
|
@@ -193,10 +253,15 @@ const useMcpServerResource = (props) => {
|
|
|
193
253
|
arguments: args
|
|
194
254
|
});
|
|
195
255
|
},
|
|
196
|
-
|
|
256
|
+
listResources: async (params) => {
|
|
197
257
|
const client_1 = clientRef.current;
|
|
198
258
|
if (!client_1) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
199
|
-
return await client_1.
|
|
259
|
+
return await client_1.listResources(params);
|
|
260
|
+
},
|
|
261
|
+
readResource: async (uri) => {
|
|
262
|
+
const client_2 = clientRef.current;
|
|
263
|
+
if (!client_2) throw new Error(`MCP server "${props.id}" is not connected`);
|
|
264
|
+
return await client_2.readResource({ uri });
|
|
200
265
|
},
|
|
201
266
|
completeAuth: doCompleteAuth
|
|
202
267
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","StreamableHTTPClientTransportOptions","UnauthorizedError","Transport","createOAuthProvider","buildHeaders","assertValidServerId","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","clientRef","transportRef","withConnectionTimeout","promise","T","phase","startedAt","timeoutMs","undefined","remainingMs","Date","now","timeoutError","Error","timeout","ReturnType","setTimeout","race","_","reject","clearTimeout","buildTransport","type","authProvider","serverId","config","onAuthorizationUrl","toString","URL","persisted","loadAuthState","headers","transportOpts","requestInit","finalizeConnect","transport","client","version","connect","list","listTools","current","map","t","info","inputSchema","description","closeTransport","close","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","get","finishAuth","tryAutoConnect","signal","cancelled","token","tokens","catch","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","readResource","uri","completeAuth","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n\n const withConnectionTimeout = useEffectEvent(\n async <T>(\n promise: Promise<T>,\n phase: \"connecting\" | \"listing tools\",\n startedAt: number,\n ): Promise<T> => {\n const timeoutMs = props.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - startedAt);\n const timeoutError = () =>\n new Error(\n `MCP server \"${props.id}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n },\n );\n\n const buildTransport = useEffectEvent(\n async (): Promise<StreamableHTTPClientTransport> => {\n if (props.auth.type === \"oauth\") {\n const authProvider = createOAuthProvider({\n serverId: props.id,\n config: props.auth,\n storage: props.storage,\n redirectUri: props.redirectUri,\n onAuthorizationUrl: (url) => setAuthorizationUrl(url.toString()),\n });\n return new StreamableHTTPClientTransport(new URL(props.url), {\n authProvider,\n });\n }\n if (props.auth.type === \"bearer\") {\n const persisted = await props.storage.loadAuthState(props.id);\n const headers = buildHeaders(props.auth, persisted);\n const transportOpts: StreamableHTTPClientTransportOptions = {};\n if (headers) transportOpts.requestInit = { headers };\n return new StreamableHTTPClientTransport(\n new URL(props.url),\n transportOpts,\n );\n }\n return new StreamableHTTPClientTransport(new URL(props.url));\n },\n );\n\n const finalizeConnect = useEffectEvent(\n async (transport: StreamableHTTPClientTransport) => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n const startedAt = Date.now();\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await withConnectionTimeout(\n client.connect(transport as unknown as Transport),\n \"connecting\",\n startedAt,\n );\n // Defer ref assignment until listTools() also succeeds — otherwise a\n // post-connect failure leaves stale refs that `callTool()` would\n // happily walk into, producing confusing SDK errors instead of\n // \"not connected\".\n const list = await withConnectionTimeout(\n client.listTools(),\n \"listing tools\",\n startedAt,\n );\n clientRef.current = client;\n transportRef.current = transport;\n setTools(\n list.tools.map((t) => {\n const info: MCPToolInfo = {\n name: t.name,\n inputSchema: t.inputSchema,\n };\n if (t.description !== undefined) info.description = t.description;\n return info;\n }),\n );\n setConnectionState(\"connected\");\n },\n );\n\n const closeTransport = async () => {\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) {\n try {\n await t.close();\n } catch {\n // ignore close errors\n }\n }\n };\n\n const doConnect = useEffectEvent(async () => {\n // Close any prior transport/client so a re-connect doesn't leak.\n await closeTransport();\n setConnectionState(\"connecting\");\n setLastError(null);\n setAuthorizationUrl(null);\n // Clear tools so a reconnect (error → connect, or authRequired\n // → connect) doesn't expose the previous attempt's tool list.\n setTools([]);\n let transport: StreamableHTTPClientTransport | null = null;\n try {\n transport = await buildTransport();\n // Don't assign to transportRef until connect succeeds — otherwise a\n // failed `listTools()` leaves an orphaned transport that future\n // doConnect / doDisconnect calls treat as live.\n await finalizeConnect(transport);\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n // OAuth: keep the transport alive so completeAuth can call\n // finishAuth on it. Closing it before storing would leave a\n // closed transport on transportRef.\n transportRef.current = transport;\n setConnectionState(\"authRequired\");\n } else {\n if (transport) {\n try {\n await transport.close();\n } catch {\n // ignore close errors\n }\n }\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n }\n });\n\n const doDisconnect = useEffectEvent(async () => {\n setTools([]);\n setAuthorizationUrl(null);\n setConnectionState(\"disconnected\");\n await closeTransport();\n });\n\n const doCompleteAuth = useEffectEvent(async (callbackUrl: string) => {\n setConnectionState(\"authPending\");\n setLastError(null);\n try {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"missing authorization code in callback URL\");\n let transport = transportRef.current;\n if (!transport) {\n transport = await buildTransport();\n transportRef.current = transport;\n }\n await transport.finishAuth(code);\n setAuthorizationUrl(null);\n await finalizeConnect(transport);\n } catch (err) {\n await closeTransport();\n setLastError({\n message: err instanceof Error ? err.message : String(err),\n });\n setConnectionState(\"error\");\n }\n });\n\n const tryAutoConnect = useEffectEvent(\n async (signal: { cancelled: boolean }) => {\n if (!props.autoConnect) return;\n if (props.auth.type === \"none\") {\n void doConnect();\n return;\n }\n // Static connector token short-circuits the storage read.\n if (props.auth.type === \"bearer\" && props.auth.token) {\n void doConnect();\n return;\n }\n const persisted = await props.storage.loadAuthState(props.id);\n if (signal.cancelled) return;\n if (props.auth.type === \"oauth\") {\n if (!persisted?.tokens) return;\n } else if (!persisted?.token) {\n return;\n }\n void doConnect();\n },\n );\n\n // Auto-connect on mount when usable auth exists.\n useEffect(() => {\n const signal = { cancelled: false };\n void tryAutoConnect(signal);\n return () => {\n signal.cancelled = true;\n const t = transportRef.current;\n transportRef.current = null;\n clientRef.current = null;\n if (t) t.close().catch(() => {});\n };\n }, []);\n\n const state = useMemo<MCPServerState>(\n () => ({\n id: props.id,\n kind: props.kind,\n name: props.name,\n url: props.url,\n icon: props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpServerResource.js","names":["useState","useRef","useEffect","useMemo","useEffectEvent","resource","ClientOutput","Client","StreamableHTTPClientTransport","StreamableHTTPClientTransportOptions","UnauthorizedError","Transport","createOAuthProvider","buildHeaders","assertValidServerId","MCPStorage","MCPAuthConfig","MCPConnectionState","MCPServerKind","MCPServerState","MCPToolInfo","McpServerResourceProps","id","kind","name","url","icon","auth","storage","redirectUri","autoConnect","connectionTimeout","onRemove","Promise","useMcpServerResource","props","connectionState","setConnectionState","tools","setTools","lastError","setLastError","message","authorizationUrl","setAuthorizationUrl","clientRef","transportRef","pendingTransportRef","transportCloseQueueRef","resolve","connectionGenerationRef","pendingDisposalRef","cancelled","mountedRef","closeTransportSafely","transport","close","closeQueuedTransports","transports","task","current","then","all","map","closePendingTransport","closeTransports","pendingTransport","activeTransport","Set","filter","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","finalizeConnect","client","version","connect","list","listTools","t","info","inputSchema","description","doConnect","err","String","doDisconnect","doCompleteAuth","callbackUrl","code","searchParams","get","finishAuth","connected","error","tryAutoConnect","signal","token","tokens","previousDisposal","pendingDisposal","queueMicrotask","state","getState","disconnect","remove","clearAuthState","callTool","args","arguments","Record","listResources","params","readResource","uri","completeAuth","McpServerResource"],"sources":["../../src/resources/McpServerResource.ts"],"sourcesContent":["import { useState, useRef, useEffect, useMemo, useEffectEvent } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientOutput } from \"@assistant-ui/store\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions,\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { createOAuthProvider } from \"../auth/createOAuthProvider\";\nimport { buildHeaders } from \"../auth/buildHeaders\";\nimport { assertValidServerId } from \"../utils/serverId\";\nimport type { MCPStorage } from \"./storage/types\";\nimport type {\n MCPAuthConfig,\n MCPConnectionState,\n MCPServerKind,\n MCPServerState,\n MCPToolInfo,\n} from \"../mcp-scope\";\n\nexport type McpServerResourceProps = {\n id: string;\n kind: MCPServerKind;\n name: string;\n url: string;\n icon?: string | undefined;\n auth: MCPAuthConfig;\n storage: MCPStorage;\n redirectUri: string;\n autoConnect: boolean;\n connectionTimeout?: number | undefined;\n onRemove: () => Promise<void>;\n};\n\nconst useMcpServerResource = (\n props: McpServerResourceProps,\n): ClientOutput<\"mcpServer\"> => {\n assertValidServerId(props.id);\n const [connectionState, setConnectionState] =\n useState<MCPConnectionState>(\"disconnected\");\n const [tools, setTools] = useState<MCPToolInfo[]>([]);\n const [lastError, setLastError] = useState<{ message: string } | null>(null);\n const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);\n\n const clientRef = useRef<Client | null>(null);\n const transportRef = useRef<StreamableHTTPClientTransport | null>(null);\n const pendingTransportRef = useRef<StreamableHTTPClientTransport | null>(\n null,\n );\n const transportCloseQueueRef = useRef(Promise.resolve());\n const connectionGenerationRef = useRef(0);\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 closeTransports = async () => {\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 finalizeConnect = useEffectEvent(\n async (\n transport: StreamableHTTPClientTransport,\n generation: number,\n ): Promise<boolean> => {\n const client = new Client({\n name: \"assistant-ui-mcp\",\n version: \"0.0.0\",\n });\n const startedAt = Date.now();\n // SDK's StreamableHTTPClientTransport.sessionId is `string | undefined`\n // but Transport.sessionId is declared `string?` — under\n // exactOptionalPropertyTypes the SDK's own classes don't satisfy its\n // Transport interface. Cast to bridge the gap.\n await withConnectionTimeout(\n client.connect(transport as unknown as Transport),\n \"connecting\",\n startedAt,\n );\n 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 list = await withConnectionTimeout(\n client.listTools(),\n \"listing tools\",\n startedAt,\n );\n if (!isCurrentConnection(generation)) return false;\n\n pendingTransportRef.current = null;\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 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 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 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 }),\n [\n props.id,\n props.kind,\n props.name,\n props.url,\n props.icon,\n connectionState,\n lastError,\n tools,\n authorizationUrl,\n ],\n );\n\n return {\n getState: () => state,\n connect: doConnect,\n disconnect: doDisconnect,\n remove: async () => {\n await doDisconnect();\n await props.storage.clearAuthState(props.id);\n await props.onRemove();\n },\n callTool: async (name, args) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.callTool({\n name,\n arguments: args as Record<string, unknown> | undefined,\n });\n },\n listResources: async (params) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.listResources(params);\n },\n readResource: async (uri) => {\n const client = clientRef.current;\n if (!client) {\n throw new Error(`MCP server \"${props.id}\" is not connected`);\n }\n return await client.readResource({ uri });\n },\n completeAuth: doCompleteAuth,\n };\n};\n\nexport const McpServerResource = resource(useMcpServerResource);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpCustomStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpCustomStorage.ts"],"mappings":";;cAOa,8CAAgB,SAAA,aAAA,MAAA"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { MCPCustomServerRecord } from "../../mcp-scope.js";
|
|
2
|
+
import { MCPPersistedAuthState } from "../../auth/types.js";
|
|
1
3
|
import { MCPStorage } from "./types.js";
|
|
2
|
-
|
|
3
4
|
//#region src/resources/storage/McpLocalStorage.d.ts
|
|
4
5
|
type McpLocalStorageOptions = {
|
|
5
|
-
/** Namespace prefix for keys. Default "aui-mcp". */
|
|
6
|
+
/** Namespace prefix for keys. Default "aui-mcp". */
|
|
7
|
+
keyPrefix?: string;
|
|
8
|
+
/** Override the underlying Storage. Defaults to globalThis.localStorage. */
|
|
6
9
|
storage?: Storage;
|
|
7
10
|
};
|
|
11
|
+
declare const normalizeCustomServerRecords: (value: unknown) => MCPCustomServerRecord[];
|
|
12
|
+
declare const normalizePersistedAuthState: (value: unknown) => MCPPersistedAuthState | null;
|
|
8
13
|
declare const McpLocalStorage: import("@assistant-ui/tap").Resource<MCPStorage, [opts?: McpLocalStorageOptions | undefined]>;
|
|
9
14
|
//#endregion
|
|
10
|
-
export { McpLocalStorage, McpLocalStorageOptions };
|
|
15
|
+
export { McpLocalStorage, McpLocalStorageOptions, normalizeCustomServerRecords, normalizePersistedAuthState };
|
|
11
16
|
//# sourceMappingURL=McpLocalStorage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpLocalStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"mappings":";;;;KAUY;;EAEV;;EAEA,UAAU;;cAiGC,+BACX,mBACC;cAsBU,8BACX,mBACC;cAsEU,6CAAe,SAAA,aAAA,OAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { assertValidServerId } from "../../utils/serverId.js";
|
|
1
2
|
import { resource } from "@assistant-ui/tap";
|
|
3
|
+
import { OAuthClientInformationFullSchema, OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
4
|
//#region src/resources/storage/McpLocalStorage.ts
|
|
3
5
|
function resolveStorage(opts) {
|
|
4
6
|
if (opts.storage) return opts.storage;
|
|
@@ -9,6 +11,67 @@ function resolveStorage(opts) {
|
|
|
9
11
|
}
|
|
10
12
|
return null;
|
|
11
13
|
}
|
|
14
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
const isOptionalString = (value) => value === void 0 || typeof value === "string";
|
|
16
|
+
const isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
17
|
+
const isOptionalNonEmptyString = (value) => value === void 0 || isNonEmptyString(value);
|
|
18
|
+
const isOptionalStringArray = (value) => value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
19
|
+
const isOptionalConnectionTimeout = (value) => value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
20
|
+
const isValidServerId = (id) => {
|
|
21
|
+
try {
|
|
22
|
+
assertValidServerId(id);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const isMCPAuthConfig = (auth) => {
|
|
29
|
+
if (!isRecord(auth)) return false;
|
|
30
|
+
switch (auth.type) {
|
|
31
|
+
case "none": return true;
|
|
32
|
+
case "bearer": return isOptionalNonEmptyString(auth.token);
|
|
33
|
+
case "oauth": return isOptionalStringArray(auth.scopes) && isOptionalString(auth.authorizationEndpoint) && isOptionalString(auth.tokenEndpoint) && isOptionalString(auth.registrationEndpoint) && isOptionalString(auth.clientId) && isOptionalString(auth.clientSecret);
|
|
34
|
+
default: return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const isCustomServerRecord = (value) => {
|
|
38
|
+
if (!isRecord(value)) return false;
|
|
39
|
+
if (typeof value.id !== "string" || !isValidServerId(value.id)) return false;
|
|
40
|
+
return isNonEmptyString(value.name) && isNonEmptyString(value.url) && Number.isFinite(value.createdAt) && isMCPAuthConfig(value.auth) && isOptionalConnectionTimeout(value.connectionTimeout);
|
|
41
|
+
};
|
|
42
|
+
const normalizeCustomServerRecord = (value) => {
|
|
43
|
+
if (isCustomServerRecord(value)) return value;
|
|
44
|
+
if (!isRecord(value)) return null;
|
|
45
|
+
const record = { ...value };
|
|
46
|
+
delete record.connectionTimeout;
|
|
47
|
+
return isCustomServerRecord(record) ? record : null;
|
|
48
|
+
};
|
|
49
|
+
const normalizeCustomServerRecords = (value) => {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
return value.flatMap((item) => {
|
|
52
|
+
const record = normalizeCustomServerRecord(item);
|
|
53
|
+
return record === null ? [] : [record];
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const normalizeOAuthTokens = (value) => {
|
|
57
|
+
const result = OAuthTokensSchema.safeParse(value);
|
|
58
|
+
return result.success ? result.data : void 0;
|
|
59
|
+
};
|
|
60
|
+
const normalizeClientInformation = (value) => {
|
|
61
|
+
const result = OAuthClientInformationFullSchema.safeParse(value);
|
|
62
|
+
return result.success ? result.data : void 0;
|
|
63
|
+
};
|
|
64
|
+
const normalizePersistedAuthState = (value) => {
|
|
65
|
+
if (!isRecord(value)) return null;
|
|
66
|
+
const state = {};
|
|
67
|
+
if (isNonEmptyString(value.token)) state.token = value.token;
|
|
68
|
+
if (isNonEmptyString(value.codeVerifier)) state.codeVerifier = value.codeVerifier;
|
|
69
|
+
const tokens = normalizeOAuthTokens(value.tokens);
|
|
70
|
+
if (tokens) state.tokens = tokens;
|
|
71
|
+
const clientInformation = normalizeClientInformation(value.clientInformation);
|
|
72
|
+
if (clientInformation) state.clientInformation = clientInformation;
|
|
73
|
+
return Object.keys(state).length > 0 ? state : null;
|
|
74
|
+
};
|
|
12
75
|
const useMcpLocalStorage = (opts = {}) => {
|
|
13
76
|
const prefix = opts.keyPrefix ?? "aui-mcp";
|
|
14
77
|
const customServersKey = `${prefix}:custom-servers`;
|
|
@@ -37,11 +100,11 @@ const useMcpLocalStorage = (opts = {}) => {
|
|
|
37
100
|
} catch {}
|
|
38
101
|
};
|
|
39
102
|
return {
|
|
40
|
-
loadCustomServers: async () => read(customServersKey, []),
|
|
103
|
+
loadCustomServers: async () => normalizeCustomServerRecords(read(customServersKey, [])),
|
|
41
104
|
saveCustomServers: async (records) => {
|
|
42
105
|
write(customServersKey, records);
|
|
43
106
|
},
|
|
44
|
-
loadAuthState: async (id) => read(authKey(id), null),
|
|
107
|
+
loadAuthState: async (id) => normalizePersistedAuthState(read(authKey(id), null)),
|
|
45
108
|
saveAuthState: async (id, state) => {
|
|
46
109
|
write(authKey(id), state);
|
|
47
110
|
},
|
|
@@ -52,6 +115,6 @@ const useMcpLocalStorage = (opts = {}) => {
|
|
|
52
115
|
};
|
|
53
116
|
const McpLocalStorage = resource(useMcpLocalStorage);
|
|
54
117
|
//#endregion
|
|
55
|
-
export { McpLocalStorage };
|
|
118
|
+
export { McpLocalStorage, normalizeCustomServerRecords, normalizePersistedAuthState };
|
|
56
119
|
|
|
57
120
|
//# sourceMappingURL=McpLocalStorage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpLocalStorage.js","names":["resource","MCPCustomServerRecord","MCPPersistedAuthState","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","useMcpLocalStorage","prefix","customServersKey","authKey","id","read","key","fallback","T","raw","getItem","JSON","parse","write","value","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","state","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport type { MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n read<MCPCustomServerRecord[]>(customServersKey, []),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n read<MCPPersistedAuthState | null>(authKey(id), null),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":";;AAYA,SAASQ,eAAeC,MAA8C;CACpE,IAAIA,KAAKH,SAAS,OAAOG,KAAKH;CAC9B,IAAI,OAAOI,eAAe,eAAe,kBAAkBA,YACzD,IAAI;EACF,OAAQA,WAAyCC;CACnD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;AAEA,MAAMC,sBAAsBH,OAA+B,CAAC,MAAkB;CAC5E,MAAMI,SAASJ,KAAKJ,aAAa;CACjC,MAAMS,mBAAmB,GAAGD,OAAM;CAClC,MAAME,WAAWC,OAAe,GAAGH,OAAM,QAASG;CAClD,MAAMV,UAAUE,eAAeC,IAAI;CAEnC,MAAMQ,QAAWC,KAAaC,aAAmB;EAC/C,IAAI,CAACb,SAAS,OAAOa;EACrB,IAAI;GACF,MAAME,MAAMf,QAAQgB,QAAQJ,GAAG;GAC/B,IAAIG,OAAO,MAAM,OAAOF;GACxB,OAAOI,KAAKC,MAAMH,GAAG;EACvB,QAAQ;GACN,OAAOF;EACT;CACF;CAEA,MAAMM,SAASP,KAAaQ,UAAyB;EACnD,IAAI,CAACpB,SAAS;EACd,IAAI;GACFA,QAAQqB,QAAQT,KAAKK,KAAKK,UAAUF,KAAK,CAAC;EAC5C,QAAQ,CACN;CAEJ;CAEA,MAAMG,UAAUX,QAAsB;EACpC,IAAI,CAACZ,SAAS;EACd,IAAI;GACFA,QAAQwB,WAAWZ,GAAG;EACxB,QAAQ,CACN;CAEJ;CAEA,OAAO;EACLa,mBAAmB,YACjBd,KAA8BH,kBAAkB,CAAA,CAAE;EACpDkB,mBAAmB,OAAOC,YAAY;GACpCR,MAAMX,kBAAkBmB,OAAO;EACjC;EACAC,eAAe,OAAOlB,OACpBC,KAAmCF,QAAQC,EAAE,GAAG,IAAI;EACtDmB,eAAe,OAAOnB,IAAIoB,UAAU;GAClCX,MAAMV,QAAQC,EAAE,GAAGoB,KAAK;EAC1B;EACAC,gBAAgB,OAAOrB,OAAO;GAC5Ba,OAAOd,QAAQC,EAAE,CAAC;EACpB;CACF;AACF;AAEA,MAAasB,kBAAkBtC,SAASY,kBAAkB"}
|
|
1
|
+
{"version":3,"file":"McpLocalStorage.js","names":["resource","OAuthClientInformationFullSchema","OAuthTokensSchema","MCPAuthConfig","MCPCustomServerRecord","MCPPersistedAuthState","assertValidServerId","MCPStorage","McpLocalStorageOptions","keyPrefix","storage","Storage","resolveStorage","opts","globalThis","localStorage","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isNonEmptyString","trim","length","isOptionalNonEmptyString","isOptionalStringArray","every","item","isOptionalConnectionTimeout","Number","isFinite","isValidServerId","id","isMCPAuthConfig","auth","type","token","scopes","authorizationEndpoint","tokenEndpoint","registrationEndpoint","clientId","clientSecret","isCustomServerRecord","name","url","createdAt","connectionTimeout","normalizeCustomServerRecord","record","normalizeCustomServerRecords","flatMap","normalizeOAuthTokens","result","safeParse","success","data","normalizeClientInformation","normalizePersistedAuthState","state","codeVerifier","tokens","clientInformation","Object","keys","useMcpLocalStorage","prefix","customServersKey","authKey","read","key","fallback","T","raw","getItem","JSON","parse","write","setItem","stringify","remove","removeItem","loadCustomServers","saveCustomServers","records","loadAuthState","saveAuthState","clearAuthState","McpLocalStorage"],"sources":["../../../src/resources/storage/McpLocalStorage.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport {\n OAuthClientInformationFullSchema,\n OAuthTokensSchema,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { MCPAuthConfig, MCPCustomServerRecord } from \"../../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"../../auth/types\";\nimport { assertValidServerId } from \"../../utils/serverId\";\nimport type { MCPStorage } from \"./types\";\n\nexport type McpLocalStorageOptions = {\n /** Namespace prefix for keys. Default \"aui-mcp\". */\n keyPrefix?: string;\n /** Override the underlying Storage. Defaults to globalThis.localStorage. */\n storage?: Storage;\n};\n\nfunction resolveStorage(opts: McpLocalStorageOptions): Storage | null {\n if (opts.storage) return opts.storage;\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n try {\n return (globalThis as { localStorage: Storage }).localStorage;\n } catch {\n return null;\n }\n }\n return null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isOptionalString = (value: unknown): value is string | undefined =>\n value === undefined || typeof value === \"string\";\n\nconst isNonEmptyString = (value: unknown): value is string =>\n typeof value === \"string\" && value.trim().length > 0;\n\nconst isOptionalNonEmptyString = (\n value: unknown,\n): value is string | undefined =>\n value === undefined || isNonEmptyString(value);\n\nconst isOptionalStringArray = (value: unknown): value is string[] | undefined =>\n value === undefined ||\n (Array.isArray(value) && value.every((item) => typeof item === \"string\"));\n\nconst isOptionalConnectionTimeout = (\n value: unknown,\n): value is number | undefined =>\n value === undefined ||\n (typeof value === \"number\" && Number.isFinite(value) && value >= 0);\n\nconst isValidServerId = (id: string): boolean => {\n try {\n assertValidServerId(id);\n return true;\n } catch {\n return false;\n }\n};\n\nconst isMCPAuthConfig = (auth: unknown): auth is MCPAuthConfig => {\n if (!isRecord(auth)) return false;\n\n switch (auth.type) {\n case \"none\":\n return true;\n case \"bearer\":\n return isOptionalNonEmptyString(auth.token);\n case \"oauth\":\n return (\n isOptionalStringArray(auth.scopes) &&\n isOptionalString(auth.authorizationEndpoint) &&\n isOptionalString(auth.tokenEndpoint) &&\n isOptionalString(auth.registrationEndpoint) &&\n isOptionalString(auth.clientId) &&\n isOptionalString(auth.clientSecret)\n );\n default:\n return false;\n }\n};\n\nconst isCustomServerRecord = (\n value: unknown,\n): value is MCPCustomServerRecord => {\n if (!isRecord(value)) return false;\n if (typeof value.id !== \"string\" || !isValidServerId(value.id)) {\n return false;\n }\n return (\n isNonEmptyString(value.name) &&\n isNonEmptyString(value.url) &&\n Number.isFinite(value.createdAt) &&\n isMCPAuthConfig(value.auth) &&\n isOptionalConnectionTimeout(value.connectionTimeout)\n );\n};\n\nconst normalizeCustomServerRecord = (\n value: unknown,\n): MCPCustomServerRecord | null => {\n if (isCustomServerRecord(value)) return value;\n if (!isRecord(value)) return null;\n\n const record = { ...value };\n delete record.connectionTimeout;\n return isCustomServerRecord(record) ? record : null;\n};\n\nexport const normalizeCustomServerRecords = (\n value: unknown,\n): MCPCustomServerRecord[] => {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) => {\n const record = normalizeCustomServerRecord(item);\n return record === null ? [] : [record];\n });\n};\n\nconst normalizeOAuthTokens = (\n value: unknown,\n): MCPPersistedAuthState[\"tokens\"] | undefined => {\n const result = OAuthTokensSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nconst normalizeClientInformation = (\n value: unknown,\n): MCPPersistedAuthState[\"clientInformation\"] | undefined => {\n const result = OAuthClientInformationFullSchema.safeParse(value);\n return result.success ? result.data : undefined;\n};\n\nexport const normalizePersistedAuthState = (\n value: unknown,\n): MCPPersistedAuthState | null => {\n if (!isRecord(value)) return null;\n\n const state: MCPPersistedAuthState = {};\n if (isNonEmptyString(value.token)) state.token = value.token;\n if (isNonEmptyString(value.codeVerifier)) {\n state.codeVerifier = value.codeVerifier;\n }\n\n const tokens = normalizeOAuthTokens(value.tokens);\n if (tokens) state.tokens = tokens;\n\n const clientInformation = normalizeClientInformation(value.clientInformation);\n if (clientInformation) state.clientInformation = clientInformation;\n\n return Object.keys(state).length > 0 ? state : null;\n};\n\nconst useMcpLocalStorage = (opts: McpLocalStorageOptions = {}): MCPStorage => {\n const prefix = opts.keyPrefix ?? \"aui-mcp\";\n const customServersKey = `${prefix}:custom-servers`;\n const authKey = (id: string) => `${prefix}:auth:${id}`;\n const storage = resolveStorage(opts);\n\n const read = <T>(key: string, fallback: T): T => {\n if (!storage) return fallback;\n try {\n const raw = storage.getItem(key);\n if (raw == null) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n };\n\n const write = (key: string, value: unknown): void => {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // quota or serialization failure — silently drop\n }\n };\n\n const remove = (key: string): void => {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n };\n\n return {\n loadCustomServers: async () =>\n normalizeCustomServerRecords(read<unknown>(customServersKey, [])),\n saveCustomServers: async (records) => {\n write(customServersKey, records);\n },\n loadAuthState: async (id) =>\n normalizePersistedAuthState(read<unknown>(authKey(id), null)),\n saveAuthState: async (id, state) => {\n write(authKey(id), state);\n },\n clearAuthState: async (id) => {\n remove(authKey(id));\n },\n };\n};\n\nexport const McpLocalStorage = resource(useMcpLocalStorage);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpMemoryStorage.d.ts","names":[],"sources":["../../../src/resources/storage/McpMemoryStorage.ts"],"mappings":";;cAuBa,8CAAgB,SAAA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MCPCustomServerRecord } from "../../mcp-scope.js";
|
|
2
2
|
import { MCPPersistedAuthState } from "../../auth/types.js";
|
|
3
3
|
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
|
-
|
|
5
4
|
//#region src/resources/storage/types.d.ts
|
|
6
5
|
type MCPStorage = {
|
|
7
6
|
loadCustomServers: () => Promise<MCPCustomServerRecord[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/resources/storage/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/resources/storage/types.ts"],"mappings":";;;;KAIY;EACV,yBAAyB,QAAQ;EACjC,oBAAoB,SAAS,4BAA4B;EACzD,gBAAgB,qBAAqB,QAAQ;EAC7C,gBACE,kBACA,OAAO,0BACJ;EACL,iBAAiB,qBAAqB;;KAG5B,oBAAoB,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverId.d.ts","names":[],"sources":["../../src/utils/serverId.ts"],"mappings":";iBAIgB,
|
|
1
|
+
{"version":3,"file":"serverId.d.ts","names":[],"sources":["../../src/utils/serverId.ts"],"mappings":";iBAIgB,oBAAoB;iBAgBpB,sBAAsB,KAAK"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "MCP server configuration and connection primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
-
"@radix-ui/react-primitive": "^2.1.
|
|
32
|
-
"@assistant-ui/core": "0.2.
|
|
33
|
-
"@assistant-ui/store": "0.2.
|
|
34
|
-
"@assistant-ui/tap": "0.9.
|
|
35
|
-
"assistant-stream": "0.3.
|
|
31
|
+
"@radix-ui/react-primitive": "^2.1.10",
|
|
32
|
+
"@assistant-ui/core": "0.2.22",
|
|
33
|
+
"@assistant-ui/store": "0.2.21",
|
|
34
|
+
"@assistant-ui/tap": "0.9.5",
|
|
35
|
+
"assistant-stream": "0.3.27"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@types/react": "*",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/react": "^19.2.17",
|
|
48
|
-
"react": "^19.2.
|
|
49
|
-
"vitest": "^4.1.
|
|
50
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
48
|
+
"react": "^19.2.8",
|
|
49
|
+
"vitest": "^4.1.10",
|
|
50
|
+
"@assistant-ui/x-buildutils": "0.0.19"
|
|
51
51
|
},
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMcpOAuthCallbackError } from "./useMcpOAuthCallback";
|
|
3
|
+
|
|
4
|
+
describe("createMcpOAuthCallbackError", () => {
|
|
5
|
+
it("adds MCP OAuth callback context without a server id", () => {
|
|
6
|
+
const cause = new Error('missing "state" parameter');
|
|
7
|
+
const error = createMcpOAuthCallbackError(cause, null);
|
|
8
|
+
|
|
9
|
+
expect(error.message).toBe(
|
|
10
|
+
'MCP OAuth callback failed: missing "state" parameter',
|
|
11
|
+
);
|
|
12
|
+
expect(error.cause).toBe(cause);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("adds MCP OAuth callback context with a server id", () => {
|
|
16
|
+
const cause = new Error("invalid_grant");
|
|
17
|
+
const error = createMcpOAuthCallbackError(cause, "github");
|
|
18
|
+
|
|
19
|
+
expect(error.message).toBe(
|
|
20
|
+
'MCP OAuth callback for server "github" failed: invalid_grant',
|
|
21
|
+
);
|
|
22
|
+
expect(error.cause).toBe(cause);
|
|
23
|
+
});
|
|
24
|
+
});
|