@mcp-ts/sdk 1.6.1 → 1.6.2
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/client/react.js +96 -44
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +96 -44
- package/dist/client/react.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client/react/oauth-popup.tsx +111 -51
package/package.json
CHANGED
|
@@ -42,6 +42,19 @@ export interface McpOAuthCallbackContentProps {
|
|
|
42
42
|
|
|
43
43
|
const AUTH_CODE_MESSAGE = 'MCP_AUTH_CODE';
|
|
44
44
|
const AUTH_RESULT_MESSAGE = 'MCP_AUTH_RESULT';
|
|
45
|
+
const AUTH_CHANNEL_NAME = 'mcp-auth-channel';
|
|
46
|
+
|
|
47
|
+
function createAuthBroadcastChannel(): BroadcastChannel | null {
|
|
48
|
+
if (typeof BroadcastChannel === 'undefined') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return new BroadcastChannel(AUTH_CHANNEL_NAME);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
45
58
|
|
|
46
59
|
function postPopupResult(
|
|
47
60
|
popupWindow: WindowProxy | null,
|
|
@@ -51,13 +64,23 @@ function postPopupResult(
|
|
|
51
64
|
error?: string;
|
|
52
65
|
}
|
|
53
66
|
): void {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
const payload = {
|
|
68
|
+
type: AUTH_RESULT_MESSAGE,
|
|
69
|
+
...result,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
popupWindow?.postMessage(payload, window.location.origin);
|
|
74
|
+
} catch {
|
|
75
|
+
// COOP can leave a WindowProxy reference that is no longer usable.
|
|
76
|
+
// The BroadcastChannel path below is the reliable fallback.
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const channel = createAuthBroadcastChannel();
|
|
80
|
+
if (channel) {
|
|
81
|
+
channel.postMessage(payload);
|
|
82
|
+
channel.close();
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
/**
|
|
@@ -130,14 +153,16 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
|
|
|
130
153
|
finishAuth: (sessionId: string, code: string) => Promise<unknown>
|
|
131
154
|
): void {
|
|
132
155
|
const pendingPopupsRef = useRef<Map<string, WindowProxy>>(new Map());
|
|
156
|
+
const processingCodesRef = useRef<Set<string>>(new Set());
|
|
133
157
|
|
|
134
158
|
useEffect(() => {
|
|
135
159
|
const handleMessage = async (event: MessageEvent) => {
|
|
136
|
-
if (event.origin !== window.location.origin) {
|
|
160
|
+
if (event.origin && event.origin !== window.location.origin) {
|
|
137
161
|
return;
|
|
138
162
|
}
|
|
139
163
|
|
|
140
|
-
|
|
164
|
+
const code = typeof event.data?.code === 'string' ? event.data.code : '';
|
|
165
|
+
if (event.data?.type !== AUTH_CODE_MESSAGE || !code) {
|
|
141
166
|
return;
|
|
142
167
|
}
|
|
143
168
|
|
|
@@ -146,56 +171,84 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
|
|
|
146
171
|
: null;
|
|
147
172
|
const targetSessionId = typeof event.data.sessionId === 'string' ? event.data.sessionId : '';
|
|
148
173
|
|
|
174
|
+
if (popupWindow && targetSessionId) {
|
|
175
|
+
pendingPopupsRef.current.set(targetSessionId, popupWindow);
|
|
176
|
+
}
|
|
177
|
+
|
|
149
178
|
if (!targetSessionId) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
if (popupWindow) {
|
|
180
|
+
postPopupResult(popupWindow, {
|
|
181
|
+
success: false,
|
|
182
|
+
error: 'Missing OAuth session identifier',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
154
185
|
return;
|
|
155
186
|
}
|
|
156
187
|
|
|
157
188
|
const targetSession = connections.find((connection) => connection.sessionId === targetSessionId);
|
|
158
189
|
if (!targetSession) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
if (popupWindow) {
|
|
191
|
+
postPopupResult(popupWindow, {
|
|
192
|
+
sessionId: targetSessionId,
|
|
193
|
+
success: false,
|
|
194
|
+
error: 'OAuth session not found in the current client state',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
164
197
|
return;
|
|
165
198
|
}
|
|
166
199
|
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
const codeKey = `${targetSession.sessionId}:${code}`;
|
|
201
|
+
if (processingCodesRef.current.has(codeKey)) {
|
|
202
|
+
return;
|
|
169
203
|
}
|
|
204
|
+
processingCodesRef.current.add(codeKey);
|
|
170
205
|
|
|
171
206
|
try {
|
|
172
|
-
await finishAuth(targetSession.sessionId,
|
|
207
|
+
await finishAuth(targetSession.sessionId, code);
|
|
173
208
|
} catch (error) {
|
|
209
|
+
processingCodesRef.current.delete(codeKey);
|
|
174
210
|
pendingPopupsRef.current.delete(targetSession.sessionId);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
211
|
+
if (popupWindow) {
|
|
212
|
+
postPopupResult(popupWindow, {
|
|
213
|
+
sessionId: targetSession.sessionId,
|
|
214
|
+
success: false,
|
|
215
|
+
error: error instanceof Error ? error.message : 'Failed to finish auth',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const channel = createAuthBroadcastChannel();
|
|
222
|
+
const handleChannelMessage = (event: MessageEvent) => {
|
|
223
|
+
if (event.data?.type === AUTH_CODE_MESSAGE) {
|
|
224
|
+
void handleMessage(event);
|
|
180
225
|
}
|
|
181
226
|
};
|
|
182
227
|
|
|
183
228
|
window.addEventListener('message', handleMessage);
|
|
184
|
-
|
|
229
|
+
channel?.addEventListener('message', handleChannelMessage);
|
|
230
|
+
|
|
231
|
+
return () => {
|
|
232
|
+
window.removeEventListener('message', handleMessage);
|
|
233
|
+
channel?.removeEventListener('message', handleChannelMessage);
|
|
234
|
+
channel?.close();
|
|
235
|
+
};
|
|
185
236
|
}, [connections, finishAuth]);
|
|
186
237
|
|
|
187
238
|
useEffect(() => {
|
|
188
239
|
for (const connection of connections) {
|
|
189
|
-
const popupWindow = pendingPopupsRef.current.get(connection.sessionId);
|
|
190
|
-
if (!popupWindow) {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
240
|
+
const popupWindow = pendingPopupsRef.current.get(connection.sessionId) || null;
|
|
193
241
|
|
|
194
|
-
if (connection.state === 'AUTHENTICATED') {
|
|
242
|
+
if (connection.state === 'AUTHENTICATED' || connection.state === 'READY' || connection.state === 'CONNECTED') {
|
|
195
243
|
postPopupResult(popupWindow, {
|
|
196
244
|
sessionId: connection.sessionId,
|
|
197
245
|
success: true,
|
|
198
246
|
});
|
|
247
|
+
for (const codeKey of processingCodesRef.current) {
|
|
248
|
+
if (codeKey.startsWith(`${connection.sessionId}:`)) {
|
|
249
|
+
processingCodesRef.current.delete(codeKey);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
199
252
|
pendingPopupsRef.current.delete(connection.sessionId);
|
|
200
253
|
continue;
|
|
201
254
|
}
|
|
@@ -206,6 +259,11 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
|
|
|
206
259
|
success: false,
|
|
207
260
|
error: connection.error || 'Failed to complete authorization',
|
|
208
261
|
});
|
|
262
|
+
for (const codeKey of processingCodesRef.current) {
|
|
263
|
+
if (codeKey.startsWith(`${connection.sessionId}:`)) {
|
|
264
|
+
processingCodesRef.current.delete(codeKey);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
209
267
|
pendingPopupsRef.current.delete(connection.sessionId);
|
|
210
268
|
}
|
|
211
269
|
}
|
|
@@ -238,16 +296,13 @@ export function McpOAuthCallbackContent({
|
|
|
238
296
|
const [phase, setPhase] = useState<'loading' | 'success' | 'error'>(debugPhase || 'loading');
|
|
239
297
|
const [errorMessage, setErrorMessage] = useState('');
|
|
240
298
|
|
|
241
|
-
const openerMissing = typeof window !== 'undefined' ? !window.opener : false;
|
|
242
299
|
const missingCode = !code;
|
|
243
300
|
const missingSessionId = !sessionId;
|
|
244
|
-
const blockingError =
|
|
245
|
-
? 'Error: No
|
|
246
|
-
:
|
|
247
|
-
? 'Error: No
|
|
248
|
-
:
|
|
249
|
-
? 'Error: No OAuth state received.'
|
|
250
|
-
: null;
|
|
301
|
+
const blockingError = missingCode
|
|
302
|
+
? 'Error: No authorization code received.'
|
|
303
|
+
: missingSessionId
|
|
304
|
+
? 'Error: No OAuth state received.'
|
|
305
|
+
: null;
|
|
251
306
|
|
|
252
307
|
useEffect(() => {
|
|
253
308
|
if (debugPhase) {
|
|
@@ -263,9 +318,9 @@ export function McpOAuthCallbackContent({
|
|
|
263
318
|
}
|
|
264
319
|
|
|
265
320
|
let closed = false;
|
|
266
|
-
|
|
321
|
+
const channel = createAuthBroadcastChannel();
|
|
267
322
|
const handleResult = (event: MessageEvent) => {
|
|
268
|
-
if (event.origin !== window.location.origin) {
|
|
323
|
+
if (event.origin && event.origin !== window.location.origin) {
|
|
269
324
|
return;
|
|
270
325
|
}
|
|
271
326
|
|
|
@@ -280,6 +335,7 @@ export function McpOAuthCallbackContent({
|
|
|
280
335
|
if (event.data.success) {
|
|
281
336
|
setPhase('success');
|
|
282
337
|
window.removeEventListener('message', handleResult);
|
|
338
|
+
channel?.close();
|
|
283
339
|
closed = true;
|
|
284
340
|
window.setTimeout(() => window.close(), 1200);
|
|
285
341
|
return;
|
|
@@ -294,23 +350,27 @@ export function McpOAuthCallbackContent({
|
|
|
294
350
|
};
|
|
295
351
|
|
|
296
352
|
window.addEventListener('message', handleResult);
|
|
353
|
+
channel?.addEventListener('message', handleResult);
|
|
354
|
+
|
|
355
|
+
const payload = { type: AUTH_CODE_MESSAGE, code, sessionId };
|
|
297
356
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error('Failed to communicate with opener:', error);
|
|
305
|
-
window.setTimeout(() => {
|
|
357
|
+
if (window.opener) {
|
|
358
|
+
try {
|
|
359
|
+
window.opener.postMessage(payload, window.location.origin);
|
|
360
|
+
} catch {
|
|
306
361
|
setPhase('error');
|
|
307
362
|
setErrorMessage('Error: Could not communicate with main window.');
|
|
308
|
-
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (channel) {
|
|
367
|
+
channel.postMessage(payload);
|
|
309
368
|
}
|
|
310
369
|
|
|
311
370
|
return () => {
|
|
312
371
|
if (!closed) {
|
|
313
372
|
window.removeEventListener('message', handleResult);
|
|
373
|
+
channel?.close();
|
|
314
374
|
}
|
|
315
375
|
};
|
|
316
376
|
}, [blockingError, code, sessionId, debugPhase]);
|