@assistant-ui/react-mcp 0.1.15 → 0.1.17
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/createOAuthProvider.d.ts +19 -2
- package/dist/auth/createOAuthProvider.d.ts.map +1 -1
- package/dist/auth/createOAuthProvider.js +197 -35
- package/dist/auth/createOAuthProvider.js.map +1 -1
- package/dist/auth/types.d.ts +6 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.d.ts.map +1 -1
- package/dist/hooks/useMcpOAuthCallback.js +5 -6
- package/dist/hooks/useMcpOAuthCallback.js.map +1 -1
- package/dist/resources/McpManagerResource.d.ts.map +1 -1
- package/dist/resources/McpManagerResource.js +2 -1
- package/dist/resources/McpManagerResource.js.map +1 -1
- package/dist/resources/McpServerResource.d.ts +2 -1
- package/dist/resources/McpServerResource.d.ts.map +1 -1
- package/dist/resources/McpServerResource.js +79 -19
- package/dist/resources/McpServerResource.js.map +1 -1
- package/dist/resources/storage/McpLocalStorage.d.ts +7 -0
- package/dist/resources/storage/McpLocalStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpLocalStorage.js +146 -37
- package/dist/resources/storage/McpLocalStorage.js.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.d.ts.map +1 -1
- package/dist/resources/storage/McpMemoryStorage.js +9 -6
- package/dist/resources/storage/McpMemoryStorage.js.map +1 -1
- package/dist/resources/storage/types.d.ts +12 -0
- package/dist/resources/storage/types.d.ts.map +1 -1
- package/dist/utils/serverUrl.d.ts +8 -0
- package/dist/utils/serverUrl.d.ts.map +1 -0
- package/dist/utils/serverUrl.js +15 -0
- package/dist/utils/serverUrl.js.map +1 -0
- package/package.json +7 -7
- package/src/auth/createOAuthProvider.test.ts +919 -5
- package/src/auth/createOAuthProvider.ts +328 -42
- package/src/auth/types.ts +6 -1
- package/src/hooks/useMcpOAuthCallback.test.ts +74 -1
- package/src/hooks/useMcpOAuthCallback.tsx +11 -8
- package/src/resources/McpManagerResource.ts +5 -1
- package/src/resources/McpServerResource.test.ts +612 -16
- package/src/resources/McpServerResource.ts +95 -23
- package/src/resources/storage/McpLocalStorage.test.ts +97 -1
- package/src/resources/storage/McpLocalStorage.ts +90 -47
- package/src/resources/storage/McpMemoryStorage.test.ts +99 -0
- package/src/resources/storage/McpMemoryStorage.ts +23 -17
- package/src/resources/storage/types.ts +12 -0
- package/src/utils/serverUrl.test.ts +66 -0
- package/src/utils/serverUrl.ts +23 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { MCPAuthConfig } from "../mcp-scope.js";
|
|
2
2
|
import { MCPStorage } from "../resources/storage/types.js";
|
|
3
|
-
import { OAuthClientProvider } from "@modelcontextprotocol/client";
|
|
3
|
+
import { OAuthClientInformationFull, OAuthClientProvider, OAuthTokens } from "@modelcontextprotocol/client";
|
|
4
4
|
//#region src/auth/createOAuthProvider.d.ts
|
|
5
5
|
declare function decodeServerIdFromState(state: string): string | null;
|
|
6
6
|
type CreateOAuthProviderOptions = {
|
|
7
7
|
serverId: string;
|
|
8
|
+
serverUrl: string;
|
|
8
9
|
/** Must be `auth.type === "oauth"`. */
|
|
9
10
|
config: Extract<MCPAuthConfig, {
|
|
10
11
|
type: "oauth";
|
|
@@ -14,6 +15,22 @@ type CreateOAuthProviderOptions = {
|
|
|
14
15
|
/** Called by the SDK to start the authorization redirect. */
|
|
15
16
|
onAuthorizationUrl: (url: URL) => void;
|
|
16
17
|
};
|
|
18
|
+
type OAuthConfig = Extract<MCPAuthConfig, {
|
|
19
|
+
type: "oauth";
|
|
20
|
+
}>;
|
|
21
|
+
type OAuthCredentialState = {
|
|
22
|
+
tokens?: OAuthTokens | undefined;
|
|
23
|
+
tokensClientId?: string | undefined;
|
|
24
|
+
clientInformation?: OAuthClientInformationFull | undefined;
|
|
25
|
+
clientInformationSource?: "registered" | undefined;
|
|
26
|
+
};
|
|
27
|
+
declare const hasUsableOAuthTokens: (state: OAuthCredentialState | null | undefined, config: OAuthConfig) => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Clears persisted OAuth state after the in-flight load and every queued write
|
|
30
|
+
* for that server have settled, so a discarded provider cannot recreate the
|
|
31
|
+
* record it was mid-save on.
|
|
32
|
+
*/
|
|
33
|
+
declare const clearOAuthProviderAuthState: (storage: MCPStorage, serverId: string) => Promise<void>;
|
|
17
34
|
/**
|
|
18
35
|
* Builds an OAuthClientProvider for the MCP SDK, backed by MCPStorage.
|
|
19
36
|
* Token refresh and DCR are handled by the SDK; this provider only mediates
|
|
@@ -21,5 +38,5 @@ type CreateOAuthProviderOptions = {
|
|
|
21
38
|
*/
|
|
22
39
|
declare function createOAuthProvider(opts: CreateOAuthProviderOptions): OAuthClientProvider;
|
|
23
40
|
//#endregion
|
|
24
|
-
export { CreateOAuthProviderOptions, createOAuthProvider, decodeServerIdFromState };
|
|
41
|
+
export { CreateOAuthProviderOptions, clearOAuthProviderAuthState, createOAuthProvider, decodeServerIdFromState, hasUsableOAuthTokens };
|
|
25
42
|
//# sourceMappingURL=createOAuthProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createOAuthProvider.d.ts","names":[],"sources":["../../src/auth/createOAuthProvider.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"createOAuthProvider.d.ts","names":[],"sources":["../../src/auth/createOAuthProvider.ts"],"mappings":";;;;iBAsCgB,wBAAwB;KAgB5B;EACV;EACA;;EAEA,QAAQ,QAAQ;IAAiB;;EACjC,SAAS;EACT;;EAEA,qBAAqB,KAAK;;KAcvB,cAAc,QAAQ;EAAiB;;KAEvC;EACH,SAAS;EACT;EACA,oBAAoB;EACpB;;cAUW,uBACX,OAAO,yCACP,QAAQ;;;;;;cAkHG,8BACX,SAAS,YACT,qBACC;;;;;;iBA2Ba,oBACd,MAAM,6BACL"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAuthStateForServerUrl, normalizeMcpServerUrl } from "../utils/serverUrl.js";
|
|
1
2
|
//#region src/auth/createOAuthProvider.ts
|
|
2
3
|
const STATE_PREFIX = "aui-mcp:";
|
|
3
4
|
function bytesToBase64Url(bytes) {
|
|
@@ -26,42 +27,165 @@ function decodeServerIdFromState(state) {
|
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
const registeredClientId = (state) => state?.clientInformationSource === "registered" ? state.clientInformation?.client_id : void 0;
|
|
31
|
+
const hasUsableOAuthTokens = (state, config) => {
|
|
32
|
+
const clientId = config.clientId ?? registeredClientId(state);
|
|
33
|
+
return clientId !== void 0 && state?.tokens !== void 0 && state.tokensClientId === clientId;
|
|
34
|
+
};
|
|
35
|
+
const hasUsableRegisteredClientInformation = (state, config) => {
|
|
36
|
+
const clientId = registeredClientId(state);
|
|
37
|
+
return clientId !== void 0 && (config.clientId === void 0 || config.clientId === clientId);
|
|
38
|
+
};
|
|
39
|
+
const anchorByStorage = /* @__PURE__ */ new WeakMap();
|
|
40
|
+
const anchorByScope = /* @__PURE__ */ new Map();
|
|
41
|
+
const anchorRegistry = new FinalizationRegistry((scopeId) => {
|
|
42
|
+
if (!anchorByScope.get(scopeId)?.deref()) anchorByScope.delete(scopeId);
|
|
43
|
+
});
|
|
44
|
+
const getStorageIdentity = (storage) => {
|
|
45
|
+
const existing = anchorByStorage.get(storage);
|
|
46
|
+
if (existing) return existing;
|
|
47
|
+
const { scopeId } = storage;
|
|
48
|
+
if (scopeId === void 0) return storage;
|
|
49
|
+
let anchor = anchorByScope.get(scopeId)?.deref();
|
|
50
|
+
if (!anchor) {
|
|
51
|
+
anchor = {};
|
|
52
|
+
anchorByScope.set(scopeId, new WeakRef(anchor));
|
|
53
|
+
anchorRegistry.register(anchor, scopeId);
|
|
54
|
+
}
|
|
55
|
+
anchorByStorage.set(storage, anchor);
|
|
56
|
+
return anchor;
|
|
57
|
+
};
|
|
58
|
+
const persistenceByIdentity = /* @__PURE__ */ new WeakMap();
|
|
59
|
+
const getPersistence = (storage, serverId, serverUrl) => {
|
|
60
|
+
const identity = getStorageIdentity(storage);
|
|
61
|
+
let byServerId = persistenceByIdentity.get(identity);
|
|
62
|
+
if (!byServerId) {
|
|
63
|
+
byServerId = /* @__PURE__ */ new Map();
|
|
64
|
+
persistenceByIdentity.set(identity, byServerId);
|
|
65
|
+
}
|
|
66
|
+
let persistence = byServerId.get(serverId);
|
|
67
|
+
if (!persistence) {
|
|
68
|
+
persistence = {
|
|
69
|
+
endpoint: null,
|
|
70
|
+
queue: Promise.resolve(),
|
|
71
|
+
invalidated: false
|
|
72
|
+
};
|
|
73
|
+
byServerId.set(serverId, persistence);
|
|
74
|
+
}
|
|
75
|
+
let endpoint = persistence.endpoint;
|
|
76
|
+
if (endpoint?.serverUrl !== serverUrl) {
|
|
77
|
+
if (endpoint) endpoint.invalidated = true;
|
|
78
|
+
endpoint = {
|
|
79
|
+
serverUrl,
|
|
80
|
+
cached: null,
|
|
81
|
+
cachePromise: null,
|
|
82
|
+
invalidated: false
|
|
83
|
+
};
|
|
84
|
+
persistence.endpoint = endpoint;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
persistence,
|
|
88
|
+
endpoint
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Clears persisted OAuth state after the in-flight load and every queued write
|
|
93
|
+
* for that server have settled, so a discarded provider cannot recreate the
|
|
94
|
+
* record it was mid-save on.
|
|
95
|
+
*/
|
|
96
|
+
const clearOAuthProviderAuthState = async (storage, serverId) => {
|
|
97
|
+
const identity = getStorageIdentity(storage);
|
|
98
|
+
const byServerId = persistenceByIdentity.get(identity);
|
|
99
|
+
const persistence = byServerId?.get(serverId);
|
|
100
|
+
if (!byServerId || !persistence) {
|
|
101
|
+
await storage.clearAuthState(serverId);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
persistence.invalidated = true;
|
|
105
|
+
byServerId.delete(serverId);
|
|
106
|
+
if (byServerId.size === 0) persistenceByIdentity.delete(identity);
|
|
107
|
+
if (persistence.endpoint) persistence.endpoint.invalidated = true;
|
|
108
|
+
const cachePromise = persistence.endpoint?.cachePromise;
|
|
109
|
+
if (cachePromise) await Promise.allSettled([cachePromise]);
|
|
110
|
+
await persistence.queue;
|
|
111
|
+
await storage.clearAuthState(serverId);
|
|
112
|
+
};
|
|
29
113
|
/**
|
|
30
114
|
* Builds an OAuthClientProvider for the MCP SDK, backed by MCPStorage.
|
|
31
115
|
* Token refresh and DCR are handled by the SDK; this provider only mediates
|
|
32
116
|
* load/save and the redirect step.
|
|
33
117
|
*/
|
|
34
118
|
function createOAuthProvider(opts) {
|
|
35
|
-
const { serverId, config, storage, redirectUri, onAuthorizationUrl } = opts;
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (config.clientSecret) ci.client_secret = config.clientSecret;
|
|
48
|
-
initial.clientInformation = ci;
|
|
49
|
-
} else if (persisted?.clientInformation) initial.clientInformation = persisted.clientInformation;
|
|
50
|
-
if (persisted?.codeVerifier) initial.codeVerifier = persisted.codeVerifier;
|
|
51
|
-
if (persisted?.discoveryState) initial.discoveryState = persisted.discoveryState;
|
|
52
|
-
cached = initial;
|
|
53
|
-
return cached;
|
|
119
|
+
const { serverId, serverUrl, config, storage, redirectUri, onAuthorizationUrl } = opts;
|
|
120
|
+
const normalizedServerUrl = normalizeMcpServerUrl(serverUrl);
|
|
121
|
+
const { persistence, endpoint } = getPersistence(storage, serverId, normalizedServerUrl);
|
|
122
|
+
let pendingState;
|
|
123
|
+
const configuredClientInformation = () => {
|
|
124
|
+
if (!config.clientId) return void 0;
|
|
125
|
+
const ci = {
|
|
126
|
+
client_id: config.clientId,
|
|
127
|
+
redirect_uris: [redirectUri]
|
|
128
|
+
};
|
|
129
|
+
if (config.clientSecret) ci.client_secret = config.clientSecret;
|
|
130
|
+
return ci;
|
|
54
131
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
132
|
+
let clientInformationOverlay = configuredClientInformation();
|
|
133
|
+
const activeClientId = (cache) => clientInformationOverlay?.client_id ?? registeredClientId(cache);
|
|
134
|
+
const loadCache = () => {
|
|
135
|
+
if (endpoint.invalidated) return Promise.resolve({});
|
|
136
|
+
if (endpoint.cached) return Promise.resolve(endpoint.cached);
|
|
137
|
+
if (endpoint.cachePromise) return endpoint.cachePromise;
|
|
138
|
+
endpoint.cachePromise = persistence.queue.then(() => storage.loadAuthState(serverId)).then(async (persisted) => {
|
|
139
|
+
const initial = {};
|
|
140
|
+
let needsMigration = false;
|
|
141
|
+
if (endpoint.invalidated) return initial;
|
|
142
|
+
if (persisted && isAuthStateForServerUrl(persisted, normalizedServerUrl)) {
|
|
143
|
+
if (hasUsableRegisteredClientInformation(persisted, config) && persisted.clientInformation) {
|
|
144
|
+
initial.clientInformation = persisted.clientInformation;
|
|
145
|
+
initial.clientInformationSource = "registered";
|
|
146
|
+
} else if (persisted?.clientInformation || persisted?.clientInformationSource !== void 0) needsMigration = true;
|
|
147
|
+
if (hasUsableOAuthTokens(persisted, config)) {
|
|
148
|
+
initial.tokens = persisted.tokens;
|
|
149
|
+
initial.tokensClientId = persisted.tokensClientId;
|
|
150
|
+
} else if (persisted?.tokens || persisted?.tokensClientId !== void 0) needsMigration = true;
|
|
151
|
+
if (persisted?.token) initial.token = persisted.token;
|
|
152
|
+
if (persisted?.codeVerifier) initial.codeVerifier = persisted.codeVerifier;
|
|
153
|
+
if (persisted?.state) initial.state = persisted.state;
|
|
154
|
+
if (persisted?.discoveryState) initial.discoveryState = persisted.discoveryState;
|
|
155
|
+
}
|
|
156
|
+
endpoint.cached = initial;
|
|
157
|
+
if (needsMigration) await persist().catch(() => {});
|
|
158
|
+
return initial;
|
|
159
|
+
}, (error) => {
|
|
160
|
+
endpoint.cachePromise = null;
|
|
161
|
+
throw error;
|
|
162
|
+
});
|
|
163
|
+
return endpoint.cachePromise;
|
|
64
164
|
};
|
|
165
|
+
function persist() {
|
|
166
|
+
const task = persistence.queue.then(async () => {
|
|
167
|
+
if (persistence.invalidated || endpoint.invalidated) return;
|
|
168
|
+
const c = endpoint.cached;
|
|
169
|
+
if (!c) return;
|
|
170
|
+
const next = {};
|
|
171
|
+
if (hasUsableOAuthTokens(c, config) && c.tokens && c.tokensClientId) {
|
|
172
|
+
next.tokens = c.tokens;
|
|
173
|
+
next.tokensClientId = c.tokensClientId;
|
|
174
|
+
}
|
|
175
|
+
if (c.clientInformation && c.clientInformationSource === "registered") {
|
|
176
|
+
next.clientInformation = c.clientInformation;
|
|
177
|
+
next.clientInformationSource = "registered";
|
|
178
|
+
}
|
|
179
|
+
if (c.token) next.token = c.token;
|
|
180
|
+
if (c.codeVerifier) next.codeVerifier = c.codeVerifier;
|
|
181
|
+
if (c.state) next.state = c.state;
|
|
182
|
+
if (c.discoveryState) next.discoveryState = c.discoveryState;
|
|
183
|
+
next.serverUrl = normalizedServerUrl;
|
|
184
|
+
await storage.saveAuthState(serverId, next);
|
|
185
|
+
});
|
|
186
|
+
persistence.queue = task.catch(() => {});
|
|
187
|
+
return task;
|
|
188
|
+
}
|
|
65
189
|
const clientMetadata = {
|
|
66
190
|
client_name: "assistant-ui",
|
|
67
191
|
redirect_uris: [redirectUri],
|
|
@@ -79,22 +203,42 @@ function createOAuthProvider(opts) {
|
|
|
79
203
|
},
|
|
80
204
|
state() {
|
|
81
205
|
const nonce = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}.${Math.random()}`;
|
|
82
|
-
|
|
206
|
+
pendingState = `${encodeServerIdInState(serverId)}.${nonce}`;
|
|
207
|
+
return pendingState;
|
|
83
208
|
},
|
|
84
209
|
async clientInformation() {
|
|
85
|
-
|
|
210
|
+
const c = await loadCache();
|
|
211
|
+
if (clientInformationOverlay) return clientInformationOverlay;
|
|
212
|
+
if (c.clientInformationSource !== "registered") return void 0;
|
|
213
|
+
return c.clientInformation;
|
|
86
214
|
},
|
|
87
215
|
async saveClientInformation(info) {
|
|
216
|
+
if (clientInformationOverlay) {
|
|
217
|
+
clientInformationOverlay = info;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
88
220
|
const c = await loadCache();
|
|
89
221
|
c.clientInformation = info;
|
|
222
|
+
c.clientInformationSource = "registered";
|
|
223
|
+
if (c.tokensClientId !== c.clientInformation.client_id) {
|
|
224
|
+
delete c.tokens;
|
|
225
|
+
delete c.tokensClientId;
|
|
226
|
+
}
|
|
90
227
|
await persist();
|
|
91
228
|
},
|
|
92
229
|
async tokens() {
|
|
93
|
-
|
|
230
|
+
const c = await loadCache();
|
|
231
|
+
const clientId = activeClientId(c);
|
|
232
|
+
if (clientId === void 0 || c.tokensClientId !== clientId) return void 0;
|
|
233
|
+
return c.tokens;
|
|
94
234
|
},
|
|
95
235
|
async saveTokens(tokens) {
|
|
96
236
|
const c = await loadCache();
|
|
97
237
|
c.tokens = tokens;
|
|
238
|
+
const clientId = activeClientId(c);
|
|
239
|
+
if (clientId) c.tokensClientId = clientId;
|
|
240
|
+
else delete c.tokensClientId;
|
|
241
|
+
delete c.state;
|
|
98
242
|
await persist();
|
|
99
243
|
},
|
|
100
244
|
async redirectToAuthorization(url) {
|
|
@@ -103,6 +247,10 @@ function createOAuthProvider(opts) {
|
|
|
103
247
|
async saveCodeVerifier(codeVerifier) {
|
|
104
248
|
const c = await loadCache();
|
|
105
249
|
c.codeVerifier = codeVerifier;
|
|
250
|
+
if (pendingState) {
|
|
251
|
+
c.state = pendingState;
|
|
252
|
+
pendingState = void 0;
|
|
253
|
+
}
|
|
106
254
|
await persist();
|
|
107
255
|
},
|
|
108
256
|
async codeVerifier() {
|
|
@@ -120,15 +268,29 @@ function createOAuthProvider(opts) {
|
|
|
120
268
|
},
|
|
121
269
|
async invalidateCredentials(scope) {
|
|
122
270
|
const c = await loadCache();
|
|
123
|
-
if (scope === "all" || scope === "tokens")
|
|
124
|
-
|
|
125
|
-
|
|
271
|
+
if (scope === "all" || scope === "tokens") {
|
|
272
|
+
delete c.tokens;
|
|
273
|
+
delete c.tokensClientId;
|
|
274
|
+
}
|
|
275
|
+
if (scope === "all" || scope === "client") {
|
|
276
|
+
delete c.clientInformation;
|
|
277
|
+
delete c.clientInformationSource;
|
|
278
|
+
if (!config.clientId) {
|
|
279
|
+
delete c.tokens;
|
|
280
|
+
delete c.tokensClientId;
|
|
281
|
+
}
|
|
282
|
+
clientInformationOverlay = configuredClientInformation();
|
|
283
|
+
}
|
|
284
|
+
if (scope === "all" || scope === "verifier") {
|
|
285
|
+
delete c.codeVerifier;
|
|
286
|
+
delete c.state;
|
|
287
|
+
}
|
|
126
288
|
if (scope === "all" || scope === "discovery") delete c.discoveryState;
|
|
127
289
|
await persist();
|
|
128
290
|
}
|
|
129
291
|
};
|
|
130
292
|
}
|
|
131
293
|
//#endregion
|
|
132
|
-
export { createOAuthProvider, decodeServerIdFromState };
|
|
294
|
+
export { clearOAuthProviderAuthState, createOAuthProvider, decodeServerIdFromState, hasUsableOAuthTokens };
|
|
133
295
|
|
|
134
296
|
//# sourceMappingURL=createOAuthProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createOAuthProvider.js","names":["OAuthClientProvider","OAuthClientInformationFull","OAuthClientMetadata","OAuthDiscoveryState","OAuthTokens","MCPStorage","MCPAuthConfig","STATE_PREFIX","bytesToBase64Url","bytes","Uint8Array","s","b","String","fromCharCode","btoa","replace","base64UrlToBytes","b64url","padded","repeat","length","bin","atob","out","i","charCodeAt","encodeServerIdInState","serverId","TextEncoder","encode","decodeServerIdFromState","state","startsWith","dot","indexOf","encoded","slice","TextDecoder","fatal","decode","CreateOAuthProviderOptions","config","Extract","type","storage","redirectUri","onAuthorizationUrl","url","URL","createOAuthProvider","opts","Cache","tokens","clientInformation","codeVerifier","discoveryState","cached","loadCache","Promise","persisted","loadAuthState","initial","clientId","ci","client_id","redirect_uris","clientSecret","client_secret","persist","c","next","Parameters","saveAuthState","clientMetadata","client_name","grant_types","response_types","token_endpoint_auth_method","scope","scopes","join","redirectUrl","nonce","crypto","randomUUID","Date","now","Math","random","saveClientInformation","info","saveTokens","redirectToAuthorization","saveCodeVerifier","Error","saveDiscoveryState","invalidateCredentials"],"sources":["../../src/auth/createOAuthProvider.ts"],"sourcesContent":["import type {\n OAuthClientProvider,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthDiscoveryState,\n OAuthTokens,\n} from \"@modelcontextprotocol/client\";\nimport type { MCPStorage } from \"../resources/storage/types\";\nimport type { MCPAuthConfig } from \"../mcp-scope\";\n\nconst STATE_PREFIX = \"aui-mcp:\";\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction base64UrlToBytes(b64url: string): Uint8Array {\n const padded =\n b64url.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"=\".repeat((4 - (b64url.length % 4)) % 4);\n const bin = atob(padded);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nfunction encodeServerIdInState(serverId: string): string {\n const bytes = new TextEncoder().encode(serverId);\n return `${STATE_PREFIX}${bytesToBase64Url(bytes)}`;\n}\n\nexport function decodeServerIdFromState(state: string): string | null {\n if (!state.startsWith(STATE_PREFIX)) return null;\n const dot = state.indexOf(\".\", STATE_PREFIX.length);\n const encoded =\n dot === -1\n ? state.slice(STATE_PREFIX.length)\n : state.slice(STATE_PREFIX.length, dot);\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n base64UrlToBytes(encoded),\n );\n } catch {\n return null;\n }\n}\n\nexport type CreateOAuthProviderOptions = {\n serverId: string;\n /** Must be `auth.type === \"oauth\"`. */\n config: Extract<MCPAuthConfig, { type: \"oauth\" }>;\n storage: MCPStorage;\n redirectUri: string;\n /** Called by the SDK to start the authorization redirect. */\n onAuthorizationUrl: (url: URL) => void;\n};\n\n/**\n * Builds an OAuthClientProvider for the MCP SDK, backed by MCPStorage.\n * Token refresh and DCR are handled by the SDK; this provider only mediates\n * load/save and the redirect step.\n */\nexport function createOAuthProvider(\n opts: CreateOAuthProviderOptions,\n): OAuthClientProvider {\n const { serverId, config, storage, redirectUri, onAuthorizationUrl } = opts;\n\n type Cache = {\n tokens?: OAuthTokens | undefined;\n clientInformation?: OAuthClientInformationFull | undefined;\n codeVerifier?: string | undefined;\n discoveryState?: OAuthDiscoveryState | undefined;\n };\n let cached: Cache | null = null;\n\n const loadCache = async (): Promise<Cache> => {\n if (cached) return cached;\n const persisted = await storage.loadAuthState(serverId);\n const initial: Cache = {};\n if (persisted?.tokens) initial.tokens = persisted.tokens;\n if (config.clientId) {\n const ci: OAuthClientInformationFull = {\n client_id: config.clientId,\n redirect_uris: [redirectUri],\n };\n if (config.clientSecret) ci.client_secret = config.clientSecret;\n initial.clientInformation = ci;\n } else if (persisted?.clientInformation) {\n initial.clientInformation = persisted.clientInformation;\n }\n if (persisted?.codeVerifier) initial.codeVerifier = persisted.codeVerifier;\n if (persisted?.discoveryState)\n initial.discoveryState = persisted.discoveryState;\n cached = initial;\n return cached;\n };\n\n const persist = async () => {\n const c = cached;\n if (!c) return;\n const next: Parameters<typeof storage.saveAuthState>[1] = {};\n if (c.tokens) next.tokens = c.tokens;\n if (c.clientInformation) next.clientInformation = c.clientInformation;\n if (c.codeVerifier) next.codeVerifier = c.codeVerifier;\n if (c.discoveryState) next.discoveryState = c.discoveryState;\n await storage.saveAuthState(serverId, next);\n };\n\n const clientMetadata: OAuthClientMetadata = {\n client_name: \"assistant-ui\",\n redirect_uris: [redirectUri],\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n scope: config.scopes?.join(\" \"),\n };\n\n return {\n get redirectUrl() {\n return redirectUri;\n },\n get clientMetadata() {\n return clientMetadata;\n },\n state() {\n // Embed the server id so the callback handler can route it back to the\n // right MCPServerResource without app-level wiring.\n const nonce =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}.${Math.random()}`;\n return `${encodeServerIdInState(serverId)}.${nonce}`;\n },\n async clientInformation() {\n const c = await loadCache();\n return c.clientInformation;\n },\n async saveClientInformation(info) {\n const c = await loadCache();\n c.clientInformation = info as OAuthClientInformationFull;\n await persist();\n },\n async tokens() {\n const c = await loadCache();\n return c.tokens;\n },\n async saveTokens(tokens) {\n const c = await loadCache();\n c.tokens = tokens;\n await persist();\n },\n async redirectToAuthorization(url) {\n onAuthorizationUrl(url);\n },\n async saveCodeVerifier(codeVerifier) {\n const c = await loadCache();\n c.codeVerifier = codeVerifier;\n await persist();\n },\n async codeVerifier() {\n const c = await loadCache();\n if (!c.codeVerifier) {\n throw new Error(\"No code verifier saved for this OAuth flow\");\n }\n return c.codeVerifier;\n },\n async saveDiscoveryState(discoveryState) {\n const c = await loadCache();\n c.discoveryState = discoveryState;\n await persist();\n },\n async discoveryState() {\n const c = await loadCache();\n return c.discoveryState;\n },\n async invalidateCredentials(scope) {\n const c = await loadCache();\n if (scope === \"all\" || scope === \"tokens\") delete c.tokens;\n if (scope === \"all\" || scope === \"client\") delete c.clientInformation;\n if (scope === \"all\" || scope === \"verifier\") delete c.codeVerifier;\n if (scope === \"all\" || scope === \"discovery\") delete c.discoveryState;\n await persist();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createOAuthProvider.js","names":["OAuthClientProvider","OAuthClientInformationFull","OAuthClientMetadata","OAuthDiscoveryState","OAuthTokens","MCPStorage","MCPAuthConfig","MCPPersistedAuthState","isAuthStateForServerUrl","normalizeMcpServerUrl","STATE_PREFIX","bytesToBase64Url","bytes","Uint8Array","s","b","String","fromCharCode","btoa","replace","base64UrlToBytes","b64url","padded","repeat","length","bin","atob","out","i","charCodeAt","encodeServerIdInState","serverId","TextEncoder","encode","decodeServerIdFromState","state","startsWith","dot","indexOf","encoded","slice","TextDecoder","fatal","decode","CreateOAuthProviderOptions","serverUrl","config","Extract","type","storage","redirectUri","onAuthorizationUrl","url","URL","OAuthProviderCache","token","tokens","tokensClientId","clientInformation","clientInformationSource","codeVerifier","discoveryState","OAuthConfig","OAuthCredentialState","registeredClientId","client_id","undefined","hasUsableOAuthTokens","clientId","hasUsableRegisteredClientInformation","OAuthProviderEndpointCache","cached","cachePromise","Promise","invalidated","OAuthProviderPersistence","endpoint","queue","anchorByStorage","WeakMap","anchorByScope","Map","WeakRef","anchorRegistry","FinalizationRegistry","scopeId","get","deref","delete","getStorageIdentity","existing","anchor","set","register","persistenceByIdentity","getPersistence","persistence","identity","byServerId","resolve","clearOAuthProviderAuthState","clearAuthState","size","allSettled","createOAuthProvider","opts","normalizedServerUrl","pendingState","configuredClientInformation","ci","redirect_uris","clientSecret","client_secret","clientInformationOverlay","activeClientId","cache","loadCache","then","loadAuthState","persisted","initial","needsMigration","persist","catch","error","task","c","next","Parameters","saveAuthState","clientMetadata","client_name","grant_types","response_types","token_endpoint_auth_method","scope","scopes","join","redirectUrl","nonce","crypto","randomUUID","Date","now","Math","random","saveClientInformation","info","saveTokens","redirectToAuthorization","saveCodeVerifier","Error","saveDiscoveryState","invalidateCredentials"],"sources":["../../src/auth/createOAuthProvider.ts"],"sourcesContent":["import type {\n OAuthClientProvider,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthDiscoveryState,\n OAuthTokens,\n} from \"@modelcontextprotocol/client\";\nimport type { MCPStorage } from \"../resources/storage/types\";\nimport type { MCPAuthConfig } from \"../mcp-scope\";\nimport type { MCPPersistedAuthState } from \"./types\";\nimport {\n isAuthStateForServerUrl,\n normalizeMcpServerUrl,\n} from \"../utils/serverUrl\";\n\nconst STATE_PREFIX = \"aui-mcp:\";\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction base64UrlToBytes(b64url: string): Uint8Array {\n const padded =\n b64url.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"=\".repeat((4 - (b64url.length % 4)) % 4);\n const bin = atob(padded);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nfunction encodeServerIdInState(serverId: string): string {\n const bytes = new TextEncoder().encode(serverId);\n return `${STATE_PREFIX}${bytesToBase64Url(bytes)}`;\n}\n\nexport function decodeServerIdFromState(state: string): string | null {\n if (!state.startsWith(STATE_PREFIX)) return null;\n const dot = state.indexOf(\".\", STATE_PREFIX.length);\n const encoded =\n dot === -1\n ? state.slice(STATE_PREFIX.length)\n : state.slice(STATE_PREFIX.length, dot);\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n base64UrlToBytes(encoded),\n );\n } catch {\n return null;\n }\n}\n\nexport type CreateOAuthProviderOptions = {\n serverId: string;\n serverUrl: string;\n /** Must be `auth.type === \"oauth\"`. */\n config: Extract<MCPAuthConfig, { type: \"oauth\" }>;\n storage: MCPStorage;\n redirectUri: string;\n /** Called by the SDK to start the authorization redirect. */\n onAuthorizationUrl: (url: URL) => void;\n};\n\ntype OAuthProviderCache = {\n token?: string | undefined;\n tokens?: OAuthTokens | undefined;\n tokensClientId?: string | undefined;\n clientInformation?: OAuthClientInformationFull | undefined;\n clientInformationSource?: MCPPersistedAuthState[\"clientInformationSource\"];\n codeVerifier?: string | undefined;\n state?: string | undefined;\n discoveryState?: OAuthDiscoveryState | undefined;\n};\n\ntype OAuthConfig = Extract<MCPAuthConfig, { type: \"oauth\" }>;\n\ntype OAuthCredentialState = {\n tokens?: OAuthTokens | undefined;\n tokensClientId?: string | undefined;\n clientInformation?: OAuthClientInformationFull | undefined;\n clientInformationSource?: \"registered\" | undefined;\n};\n\nconst registeredClientId = (\n state: OAuthCredentialState | null | undefined,\n): string | undefined =>\n state?.clientInformationSource === \"registered\"\n ? state.clientInformation?.client_id\n : undefined;\n\nexport const hasUsableOAuthTokens = (\n state: OAuthCredentialState | null | undefined,\n config: OAuthConfig,\n): boolean => {\n const clientId = config.clientId ?? registeredClientId(state);\n return (\n clientId !== undefined &&\n state?.tokens !== undefined &&\n state.tokensClientId === clientId\n );\n};\n\nconst hasUsableRegisteredClientInformation = (\n state: OAuthCredentialState | null | undefined,\n config: OAuthConfig,\n): boolean => {\n const clientId = registeredClientId(state);\n return (\n clientId !== undefined &&\n (config.clientId === undefined || config.clientId === clientId)\n );\n};\n\ntype OAuthProviderEndpointCache = {\n serverUrl: string;\n cached: OAuthProviderCache | null;\n cachePromise: Promise<OAuthProviderCache> | null;\n invalidated: boolean;\n};\n\ntype OAuthProviderPersistence = {\n endpoint: OAuthProviderEndpointCache | null;\n queue: Promise<void>;\n invalidated: boolean;\n};\n\n// scopeId, not object identity, is what addresses the same persisted data, so\n// storages sharing one share an anchor and an unscoped storage is its own\n// identity. Every storage declaring a scope holds that scope's anchor, so the\n// coordination state below is collected once the last of them is gone.\nconst anchorByStorage = new WeakMap<MCPStorage, object>();\nconst anchorByScope = new Map<string, WeakRef<object>>();\nconst anchorRegistry = new FinalizationRegistry<string>((scopeId) => {\n if (!anchorByScope.get(scopeId)?.deref()) anchorByScope.delete(scopeId);\n});\n\nconst getStorageIdentity = (storage: MCPStorage): object => {\n const existing = anchorByStorage.get(storage);\n if (existing) return existing;\n\n const { scopeId } = storage;\n if (scopeId === undefined) return storage;\n\n let anchor = anchorByScope.get(scopeId)?.deref();\n if (!anchor) {\n anchor = {};\n anchorByScope.set(scopeId, new WeakRef(anchor));\n anchorRegistry.register(anchor, scopeId);\n }\n anchorByStorage.set(storage, anchor);\n return anchor;\n};\n\n// McpServerResource builds a fresh provider for every transport, so the cache,\n// the in-flight load, and the write queue have to outlive any one provider.\n// saveAuthState replaces the whole record, so two providers writing their own\n// snapshots concurrently would drop whichever field the loser had added.\nconst persistenceByIdentity = new WeakMap<\n object,\n Map<string, OAuthProviderPersistence>\n>();\n\nconst getPersistence = (\n storage: MCPStorage,\n serverId: string,\n serverUrl: string,\n): {\n persistence: OAuthProviderPersistence;\n endpoint: OAuthProviderEndpointCache;\n} => {\n const identity = getStorageIdentity(storage);\n let byServerId = persistenceByIdentity.get(identity);\n if (!byServerId) {\n byServerId = new Map();\n persistenceByIdentity.set(identity, byServerId);\n }\n\n let persistence = byServerId.get(serverId);\n if (!persistence) {\n persistence = {\n endpoint: null,\n queue: Promise.resolve(),\n invalidated: false,\n };\n byServerId.set(serverId, persistence);\n }\n\n let endpoint = persistence.endpoint;\n if (endpoint?.serverUrl !== serverUrl) {\n if (endpoint) endpoint.invalidated = true;\n endpoint = {\n serverUrl,\n cached: null,\n cachePromise: null,\n invalidated: false,\n };\n persistence.endpoint = endpoint;\n }\n return { persistence, endpoint };\n};\n\n/**\n * Clears persisted OAuth state after the in-flight load and every queued write\n * for that server have settled, so a discarded provider cannot recreate the\n * record it was mid-save on.\n */\nexport const clearOAuthProviderAuthState = async (\n storage: MCPStorage,\n serverId: string,\n): Promise<void> => {\n const identity = getStorageIdentity(storage);\n const byServerId = persistenceByIdentity.get(identity);\n const persistence = byServerId?.get(serverId);\n if (!byServerId || !persistence) {\n await storage.clearAuthState(serverId);\n return;\n }\n\n // Detaching the entry before awaiting keeps a provider built during the clear\n // on a fresh generation instead of inheriting the fenced one.\n persistence.invalidated = true;\n byServerId.delete(serverId);\n if (byServerId.size === 0) persistenceByIdentity.delete(identity);\n\n if (persistence.endpoint) persistence.endpoint.invalidated = true;\n const cachePromise = persistence.endpoint?.cachePromise;\n if (cachePromise) await Promise.allSettled([cachePromise]);\n await persistence.queue;\n await storage.clearAuthState(serverId);\n};\n\n/**\n * Builds an OAuthClientProvider for the MCP SDK, backed by MCPStorage.\n * Token refresh and DCR are handled by the SDK; this provider only mediates\n * load/save and the redirect step.\n */\nexport function createOAuthProvider(\n opts: CreateOAuthProviderOptions,\n): OAuthClientProvider {\n const {\n serverId,\n serverUrl,\n config,\n storage,\n redirectUri,\n onAuthorizationUrl,\n } = opts;\n const normalizedServerUrl = normalizeMcpServerUrl(serverUrl);\n const { persistence, endpoint } = getPersistence(\n storage,\n serverId,\n normalizedServerUrl,\n );\n let pendingState: string | undefined;\n\n // The cache is shared with every other provider for this storage, server id,\n // and server URL, so a statically configured client stays a read-time overlay\n // owned by this provider. Writing it into the cache would leak this provider's\n // registration to a replacement built for a different, or absent, clientId.\n // The SDK's write-backs, its issuer stamp included, replace the overlay.\n const configuredClientInformation = ():\n | OAuthClientInformationFull\n | undefined => {\n if (!config.clientId) return undefined;\n const ci: OAuthClientInformationFull = {\n client_id: config.clientId,\n redirect_uris: [redirectUri],\n };\n if (config.clientSecret) ci.client_secret = config.clientSecret;\n return ci;\n };\n let clientInformationOverlay = configuredClientInformation();\n\n const activeClientId = (cache: OAuthProviderCache): string | undefined =>\n clientInformationOverlay?.client_id ?? registeredClientId(cache);\n\n const loadCache = (): Promise<OAuthProviderCache> => {\n if (endpoint.invalidated) return Promise.resolve({});\n if (endpoint.cached) return Promise.resolve(endpoint.cached);\n if (endpoint.cachePromise) return endpoint.cachePromise;\n\n endpoint.cachePromise = persistence.queue\n .then(() => storage.loadAuthState(serverId))\n .then(\n async (persisted) => {\n const initial: OAuthProviderCache = {};\n let needsMigration = false;\n if (endpoint.invalidated) return initial;\n if (\n persisted &&\n isAuthStateForServerUrl(persisted, normalizedServerUrl)\n ) {\n if (\n hasUsableRegisteredClientInformation(persisted, config) &&\n persisted.clientInformation\n ) {\n initial.clientInformation = persisted.clientInformation;\n initial.clientInformationSource = \"registered\";\n } else if (\n persisted?.clientInformation ||\n persisted?.clientInformationSource !== undefined\n ) {\n needsMigration = true;\n }\n if (hasUsableOAuthTokens(persisted, config)) {\n initial.tokens = persisted.tokens;\n initial.tokensClientId = persisted.tokensClientId;\n } else if (\n persisted?.tokens ||\n persisted?.tokensClientId !== undefined\n ) {\n needsMigration = true;\n }\n if (persisted?.token) initial.token = persisted.token;\n if (persisted?.codeVerifier)\n initial.codeVerifier = persisted.codeVerifier;\n if (persisted?.state) initial.state = persisted.state;\n if (persisted?.discoveryState)\n initial.discoveryState = persisted.discoveryState;\n }\n endpoint.cached = initial;\n if (needsMigration) await persist().catch(() => {});\n return initial;\n },\n (error) => {\n endpoint.cachePromise = null;\n throw error;\n },\n );\n return endpoint.cachePromise;\n };\n\n function persist() {\n const task = persistence.queue.then(async () => {\n if (persistence.invalidated || endpoint.invalidated) return;\n const c = endpoint.cached;\n if (!c) return;\n const next: Parameters<typeof storage.saveAuthState>[1] = {};\n if (hasUsableOAuthTokens(c, config) && c.tokens && c.tokensClientId) {\n next.tokens = c.tokens;\n next.tokensClientId = c.tokensClientId;\n }\n if (c.clientInformation && c.clientInformationSource === \"registered\") {\n next.clientInformation = c.clientInformation;\n next.clientInformationSource = \"registered\";\n }\n if (c.token) next.token = c.token;\n if (c.codeVerifier) next.codeVerifier = c.codeVerifier;\n if (c.state) next.state = c.state;\n if (c.discoveryState) next.discoveryState = c.discoveryState;\n next.serverUrl = normalizedServerUrl;\n await storage.saveAuthState(serverId, next);\n });\n persistence.queue = task.catch(() => {});\n return task;\n }\n\n const clientMetadata: OAuthClientMetadata = {\n client_name: \"assistant-ui\",\n redirect_uris: [redirectUri],\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n scope: config.scopes?.join(\" \"),\n };\n\n return {\n get redirectUrl() {\n return redirectUri;\n },\n get clientMetadata() {\n return clientMetadata;\n },\n state() {\n // Embed the server id so the callback handler can route it back to the\n // right MCPServerResource without app-level wiring.\n const nonce =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}.${Math.random()}`;\n pendingState = `${encodeServerIdInState(serverId)}.${nonce}`;\n return pendingState;\n },\n async clientInformation() {\n const c = await loadCache();\n if (clientInformationOverlay) return clientInformationOverlay;\n if (c.clientInformationSource !== \"registered\") return undefined;\n return c.clientInformation;\n },\n async saveClientInformation(info) {\n if (clientInformationOverlay) {\n clientInformationOverlay = info as OAuthClientInformationFull;\n return;\n }\n const c = await loadCache();\n c.clientInformation = info as OAuthClientInformationFull;\n c.clientInformationSource = \"registered\";\n if (c.tokensClientId !== c.clientInformation.client_id) {\n delete c.tokens;\n delete c.tokensClientId;\n }\n await persist();\n },\n async tokens() {\n const c = await loadCache();\n const clientId = activeClientId(c);\n if (clientId === undefined || c.tokensClientId !== clientId)\n return undefined;\n return c.tokens;\n },\n async saveTokens(tokens) {\n const c = await loadCache();\n c.tokens = tokens;\n const clientId = activeClientId(c);\n if (clientId) c.tokensClientId = clientId;\n else delete c.tokensClientId;\n delete c.state;\n await persist();\n },\n async redirectToAuthorization(url) {\n onAuthorizationUrl(url);\n },\n async saveCodeVerifier(codeVerifier) {\n const c = await loadCache();\n c.codeVerifier = codeVerifier;\n if (pendingState) {\n c.state = pendingState;\n pendingState = undefined;\n }\n await persist();\n },\n async codeVerifier() {\n const c = await loadCache();\n if (!c.codeVerifier) {\n throw new Error(\"No code verifier saved for this OAuth flow\");\n }\n return c.codeVerifier;\n },\n async saveDiscoveryState(discoveryState) {\n const c = await loadCache();\n c.discoveryState = discoveryState;\n await persist();\n },\n async discoveryState() {\n const c = await loadCache();\n return c.discoveryState;\n },\n async invalidateCredentials(scope) {\n const c = await loadCache();\n if (scope === \"all\" || scope === \"tokens\") {\n delete c.tokens;\n delete c.tokensClientId;\n }\n if (scope === \"all\" || scope === \"client\") {\n delete c.clientInformation;\n delete c.clientInformationSource;\n if (!config.clientId) {\n delete c.tokens;\n delete c.tokensClientId;\n }\n clientInformationOverlay = configuredClientInformation();\n }\n if (scope === \"all\" || scope === \"verifier\") {\n delete c.codeVerifier;\n delete c.state;\n }\n if (scope === \"all\" || scope === \"discovery\") delete c.discoveryState;\n await persist();\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { OAuthClientInformationFull, OAuthDiscoveryState, OAuthTokens } from "@modelcontextprotocol/client";
|
|
2
2
|
//#region src/auth/types.d.ts
|
|
3
3
|
type MCPPersistedAuthState = {
|
|
4
|
+
/** MCP server URL this authentication state belongs to. Required with credentials. */
|
|
5
|
+
serverUrl?: string;
|
|
4
6
|
tokens?: OAuthTokens;
|
|
7
|
+
tokensClientId?: string;
|
|
5
8
|
clientInformation?: OAuthClientInformationFull;
|
|
9
|
+
clientInformationSource?: "registered";
|
|
6
10
|
codeVerifier?: string;
|
|
11
|
+
state?: string;
|
|
7
12
|
discoveryState?: OAuthDiscoveryState;
|
|
8
|
-
/**
|
|
13
|
+
/** Host-persisted bearer token. Must be paired with serverUrl. */
|
|
9
14
|
token?: string;
|
|
10
15
|
};
|
|
11
16
|
//#endregion
|
package/dist/auth/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/auth/types.ts"],"mappings":";;KAMY;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/auth/types.ts"],"mappings":";;KAMY;;EAEV;EACA,SAAS;EACT;EACA,oBAAoB;EACpB;EACA;EACA;EACA,iBAAiB;;EAEjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpOAuthCallback.d.ts","names":[],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"useMcpOAuthCallback.d.ts","names":[],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"mappings":";;cAYa,8BACX,cACA,4BACC;KAWS;;EAEV;EACA,cAAc;EACd,WAAW,KAAK;;KAGN;EACV;EACA;EACA,OAAO;;iBAGO,oBACd,OAAM,6BACL;cAkDU,kBAAkB,GAC7B;EACE,YAAY,QAAQ,8BAA8B"}
|
|
@@ -2,7 +2,7 @@ import { decodeServerIdFromState } from "../auth/createOAuthProvider.js";
|
|
|
2
2
|
import { invokeMcpCallback } from "../utils/invokeMcpCallback.js";
|
|
3
3
|
import { useAui } from "@assistant-ui/store";
|
|
4
4
|
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
5
|
-
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
5
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
6
6
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/hooks/useMcpOAuthCallback.tsx
|
|
8
8
|
const createMcpOAuthCallbackError = (err, serverId) => {
|
|
@@ -19,7 +19,9 @@ function useMcpOAuthCallback(opts = {}) {
|
|
|
19
19
|
});
|
|
20
20
|
const startedRef = useRef(null);
|
|
21
21
|
const optsRef = useRef(opts);
|
|
22
|
-
|
|
22
|
+
useInsertionEffect(() => {
|
|
23
|
+
optsRef.current = opts;
|
|
24
|
+
});
|
|
23
25
|
useEffect(() => {
|
|
24
26
|
const url = opts.url ?? (typeof window !== "undefined" ? window.location.href : null);
|
|
25
27
|
if (!url) return;
|
|
@@ -28,11 +30,8 @@ function useMcpOAuthCallback(opts = {}) {
|
|
|
28
30
|
(async () => {
|
|
29
31
|
let serverId = null;
|
|
30
32
|
try {
|
|
31
|
-
const
|
|
32
|
-
const state = parsed.searchParams.get("state");
|
|
33
|
+
const state = new URL(url).searchParams.get("state");
|
|
33
34
|
if (state) serverId = decodeServerIdFromState(state);
|
|
34
|
-
const error_0 = parsed.searchParams.get("error");
|
|
35
|
-
if (error_0) throw new Error(parsed.searchParams.get("error_description") ?? error_0);
|
|
36
35
|
if (!state) throw new Error("missing \"state\" parameter");
|
|
37
36
|
if (!serverId) throw new Error("state was not created by assistant-ui MCP");
|
|
38
37
|
setResult({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useRef","useState","useAui","decodeServerIdFromState","invokeMcpCallback","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","error","useMcpOAuthCallback","opts","aui","result","setResult","startedRef","optsRef","current","window","location","href","parsed","URL","state","searchParams","get","mcp","server","id","completeAuth","McpOAuthCallback","children","t0","$","_c","t1","t2"],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"useMcpOAuthCallback.js","names":["FC","ReactNode","useEffect","useInsertionEffect","useRef","useState","useAui","decodeServerIdFromState","invokeMcpCallback","createMcpOAuthCallbackError","err","serverId","Error","message","String","cause","UseMcpOAuthCallbackOptions","url","onComplete","onError","UseMcpOAuthCallbackResult","status","error","useMcpOAuthCallback","opts","aui","result","setResult","startedRef","optsRef","current","window","location","href","parsed","URL","state","searchParams","get","mcp","server","id","completeAuth","McpOAuthCallback","children","t0","$","_c","t1","t2"],"sources":["../../src/hooks/useMcpOAuthCallback.tsx"],"sourcesContent":["import {\n type FC,\n type ReactNode,\n useEffect,\n useInsertionEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { decodeServerIdFromState } from \"../auth/createOAuthProvider\";\nimport { invokeMcpCallback } from \"../utils/invokeMcpCallback\";\n\nexport const createMcpOAuthCallbackError = (\n err: unknown,\n serverId: string | null,\n): Error => {\n const message = err instanceof Error ? err.message : String(err);\n if (serverId) {\n return new Error(\n `MCP OAuth callback for server \"${serverId}\" failed: ${message}`,\n { cause: err },\n );\n }\n return new Error(`MCP OAuth callback failed: ${message}`, { cause: err });\n};\n\nexport type UseMcpOAuthCallbackOptions = {\n /** Defaults to `window.location.href`. */\n url?: string;\n onComplete?: (serverId: string) => void;\n onError?: (err: Error) => void;\n};\n\nexport type UseMcpOAuthCallbackResult = {\n status: \"idle\" | \"running\" | \"done\" | \"error\";\n serverId: string | null;\n error: Error | null;\n};\n\nexport function useMcpOAuthCallback(\n opts: UseMcpOAuthCallbackOptions = {},\n): UseMcpOAuthCallbackResult {\n const aui = useAui();\n const [result, setResult] = useState<UseMcpOAuthCallbackResult>({\n status: \"idle\",\n serverId: null,\n error: null,\n });\n // Guard against React 18 Strict Mode's mount-unmount-remount: the effect\n // body must not run completeAuth twice for the same URL, otherwise the\n // single-use OAuth code is double-redeemed and the second attempt 4xxs.\n const startedRef = useRef<string | null>(null);\n const optsRef = useRef(opts);\n useInsertionEffect(() => {\n optsRef.current = opts;\n });\n\n useEffect(() => {\n const url =\n opts.url ?? (typeof window !== \"undefined\" ? window.location.href : null);\n if (!url) return;\n if (startedRef.current === url) return;\n startedRef.current = url;\n\n void (async () => {\n let serverId: string | null = null;\n try {\n const parsed = new URL(url);\n const state = parsed.searchParams.get(\"state\");\n if (state) serverId = decodeServerIdFromState(state);\n if (!state) throw new Error('missing \"state\" parameter');\n if (!serverId) {\n throw new Error(\"state was not created by assistant-ui MCP\");\n }\n setResult({ status: \"running\", serverId, error: null });\n await aui.mcp.server({ id: serverId }).completeAuth(url);\n setResult({ status: \"done\", serverId, error: null });\n } catch (err) {\n const error = createMcpOAuthCallbackError(err, serverId);\n setResult({ status: \"error\", serverId, error });\n invokeMcpCallback(\"onError\", optsRef.current.onError, error);\n return;\n }\n\n invokeMcpCallback(\"onComplete\", optsRef.current.onComplete, serverId);\n })();\n }, [aui, opts.url]);\n\n return result;\n}\n\nexport const McpOAuthCallback: FC<\n UseMcpOAuthCallbackOptions & {\n children?: (result: UseMcpOAuthCallbackResult) => ReactNode;\n }\n> = ({ children, ...opts }) => {\n const result = useMcpOAuthCallback(opts);\n if (children) return <>{children(result)}</>;\n return null;\n};\n"],"mappings":";;;;;;;AAYA,MAAaS,+BACXC,KACAC,aACU;CACV,MAAME,UAAUH,eAAeE,QAAQF,IAAIG,UAAUC,OAAOJ,GAAG;CAC/D,IAAIC,UACF,OAAO,IAAIC,MACT,kCAAkCD,SAAQ,YAAaE,WACvD,EAAEE,OAAOL,IAAI,CACf;CAEF,OAAO,IAAIE,MAAM,8BAA8BC,WAAW,EAAEE,OAAOL,IAAI,CAAC;AAC1E;AAeA,SAAgBa,oBACdC,OAAmC,CAAC,GACT;CAC3B,MAAMC,MAAMnB,OAAO;CACnB,MAAM,CAACoB,QAAQC,aAAatB,SAAoC;EAC9DgB,QAAQ;EACRV,UAAU;EACVW,OAAO;CACT,CAAC;CAID,MAAMM,aAAaxB,OAAsB,IAAI;CAC7C,MAAMyB,UAAUzB,OAAOoB,IAAI;CAC3BrB,yBAAyB;EACvB0B,QAAQC,UAAUN;CACpB,CAAC;CAEDtB,gBAAgB;EACd,MAAMe,MACJO,KAAKP,QAAQ,OAAOc,WAAW,cAAcA,OAAOC,SAASC,OAAO;EACtE,IAAI,CAAChB,KAAK;EACV,IAAIW,WAAWE,YAAYb,KAAK;EAChCW,WAAWE,UAAUb;EAErB,CAAM,YAAY;GAChB,IAAIN,WAA0B;GAC9B,IAAI;IAEF,MAAMyB,QAAQF,IADKC,IAAIlB,GACTiB,CAAM,CAACG,aAAaC,IAAI,OAAO;IAC7C,IAAIF,OAAOzB,WAAWJ,wBAAwB6B,KAAK;IACnD,IAAI,CAACA,OAAO,MAAM,IAAIxB,MAAM,6BAA2B;IACvD,IAAI,CAACD,UACH,MAAM,IAAIC,MAAM,2CAA2C;IAE7De,UAAU;KAAEN,QAAQ;KAAWV;KAAUW,OAAO;IAAK,CAAC;IACtD,MAAMG,IAAIc,IAAIC,OAAO,EAAEC,IAAI9B,SAAS,CAAC,CAAC,CAAC+B,aAAazB,GAAG;IACvDU,UAAU;KAAEN,QAAQ;KAAQV;KAAUW,OAAO;IAAK,CAAC;GACrD,SAASZ,KAAK;IACZ,MAAMY,QAAQb,4BAA4BC,KAAKC,QAAQ;IACvDgB,UAAU;KAAEN,QAAQ;KAASV;KAAUW;IAAM,CAAC;IAC9Cd,kBAAkB,WAAWqB,QAAQC,QAAQX,SAASG,KAAK;IAC3D;GACF;GAEAd,kBAAkB,cAAcqB,QAAQC,QAAQZ,YAAYP,QAAQ;EACtE,EAAA,CAAG;CACL,GAAG,CAACc,KAAKD,KAAKP,GAAG,CAAC;CAElB,OAAOS;AACT;AAEA,MAAaiB,oBAITE,OAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAH;CAAA,IAAApB;CAAA,IAAAsB,EAAA,OAAAD,IAAA;EAAC,CAAA,CAAAD,UAAA,GAAApB,QAAAqB;EAAqBC,EAAA,KAAAD;EAAAC,EAAA,KAAAF;EAAAE,EAAA,KAAAtB;CAAA,OAAA;EAAAoB,WAAAE,EAAA;EAAAtB,OAAAsB,EAAA;CAAA;CACxB,MAAApB,SAAeH,oBAAoBC,IAAI;CACvC,IAAIoB,UAAQ;EAAA,IAAAI;EAAA,IAAAF,EAAA,OAAAF,YAAAE,EAAA,OAAApB,QAAA;GAAYsB,KAAAJ,SAASlB,MAAM;GAACoB,EAAA,KAAAF;GAAAE,EAAA,KAAApB;GAAAoB,EAAA,KAAAE;EAAA,OAAAA,KAAAF,EAAA;EAAA,IAAAG;EAAA,IAAAH,EAAA,OAAAE,IAAA;GAAnBC,KAAA,oBAAA,UAAA,EAAA,UAAGD,GAAgB,CAAA;GAAIF,EAAA,KAAAE;GAAAF,EAAA,KAAAG;EAAA,OAAAA,KAAAH,EAAA;EAAA,OAAvBG;CAAuB;CAAC,OACtC;AAAI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"McpManagerResource.d.ts","names":[],"sources":["../../src/resources/McpManagerResource.ts"],"mappings":";;;;KAwBY;EACV,aAAa;EACb,UAAU;;EAEV;;EAEA;;EAEA;;cA4RW,gDAAkB,SAAA,sBAAA,OAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertUniqueServerIds } from "../utils/serverId.js";
|
|
2
2
|
import { createMcpId } from "../utils/createMcpId.js";
|
|
3
|
+
import { clearOAuthProviderAuthState } from "../auth/createOAuthProvider.js";
|
|
3
4
|
import { McpServerResource } from "./McpServerResource.js";
|
|
4
5
|
import { McpLocalStorage } from "./storage/McpLocalStorage.js";
|
|
5
6
|
import { attachTransformScopes, useAssistantClientRef, useClientLookup } from "@assistant-ui/store";
|
|
@@ -340,7 +341,7 @@ const useMcpManagerResource = (props) => {
|
|
|
340
341
|
try {
|
|
341
342
|
await lookup.get({ key: id }).remove();
|
|
342
343
|
} catch {
|
|
343
|
-
await storage
|
|
344
|
+
await clearOAuthProviderAuthState(storage, id);
|
|
344
345
|
setCustomServers((prev_2) => prev_2.filter((s_3) => s_3.id !== id));
|
|
345
346
|
}
|
|
346
347
|
};
|