@mcp-ts/sdk 1.6.0 → 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.
Files changed (45) hide show
  1. package/dist/adapters/agui-adapter.d.mts +2 -2
  2. package/dist/adapters/agui-adapter.d.ts +2 -2
  3. package/dist/adapters/agui-middleware.d.mts +2 -2
  4. package/dist/adapters/agui-middleware.d.ts +2 -2
  5. package/dist/adapters/ai-adapter.d.mts +2 -2
  6. package/dist/adapters/ai-adapter.d.ts +2 -2
  7. package/dist/adapters/langchain-adapter.d.mts +2 -2
  8. package/dist/adapters/langchain-adapter.d.ts +2 -2
  9. package/dist/client/index.d.mts +2 -2
  10. package/dist/client/index.d.ts +2 -2
  11. package/dist/client/react.d.mts +4 -4
  12. package/dist/client/react.d.ts +4 -4
  13. package/dist/client/react.js +96 -44
  14. package/dist/client/react.js.map +1 -1
  15. package/dist/client/react.mjs +96 -44
  16. package/dist/client/react.mjs.map +1 -1
  17. package/dist/client/vue.d.mts +4 -4
  18. package/dist/client/vue.d.ts +4 -4
  19. package/dist/{index-GfC_eNEv.d.ts → index-DhA-OEAe.d.ts} +1 -1
  20. package/dist/{index-DcYfpY3H.d.mts → index-bFL4ZF2N.d.mts} +1 -1
  21. package/dist/index.d.mts +3 -3
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +12 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +12 -2
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/server/index.d.mts +2 -2
  28. package/dist/server/index.d.ts +2 -2
  29. package/dist/server/index.js +12 -2
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/server/index.mjs +12 -2
  32. package/dist/server/index.mjs.map +1 -1
  33. package/dist/shared/index.d.mts +4 -4
  34. package/dist/shared/index.d.ts +4 -4
  35. package/dist/shared/index.js.map +1 -1
  36. package/dist/shared/index.mjs.map +1 -1
  37. package/dist/{tool-router-_O2tIwf7.d.mts → tool-router-BVaV1udm.d.mts} +1 -1
  38. package/dist/{tool-router-Bn9R0KWr.d.ts → tool-router-Dh2804tM.d.ts} +1 -1
  39. package/dist/{types-CfCoIsWI.d.mts → types-rIuN1CQi.d.mts} +1 -0
  40. package/dist/{types-CfCoIsWI.d.ts → types-rIuN1CQi.d.ts} +1 -0
  41. package/package.json +1 -1
  42. package/src/client/react/oauth-popup.tsx +111 -51
  43. package/src/server/handlers/sse-handler.ts +12 -0
  44. package/src/server/mcp/oauth-client.ts +6 -2
  45. package/src/shared/types.ts +1 -0
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { u as ToolClientProvider, T as ToolClient } from './types-CfCoIsWI.mjs';
2
+ import { u as ToolClientProvider, T as ToolClient } from './types-rIuN1CQi.mjs';
3
3
 
4
4
  /**
5
5
  * SchemaCompressor — Utilities for reducing tool schema token overhead.
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { u as ToolClientProvider, T as ToolClient } from './types-CfCoIsWI.js';
2
+ import { u as ToolClientProvider, T as ToolClient } from './types-rIuN1CQi.js';
3
3
 
4
4
  /**
5
5
  * SchemaCompressor — Utilities for reducing tool schema token overhead.
@@ -102,6 +102,7 @@ interface ConnectParams {
102
102
  serverUrl: string;
103
103
  callbackUrl: string;
104
104
  transportType?: TransportType;
105
+ headers?: Record<string, string>;
105
106
  }
106
107
  interface DisconnectParams {
107
108
  sessionId: string;
@@ -102,6 +102,7 @@ interface ConnectParams {
102
102
  serverUrl: string;
103
103
  callbackUrl: string;
104
104
  transportType?: TransportType;
105
+ headers?: Record<string, string>;
105
106
  }
106
107
  interface DisconnectParams {
107
108
  sessionId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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
- popupWindow?.postMessage(
55
- {
56
- type: AUTH_RESULT_MESSAGE,
57
- ...result,
58
- },
59
- window.location.origin
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
- if (event.data?.type !== AUTH_CODE_MESSAGE || !event.data.code) {
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
- postPopupResult(popupWindow, {
151
- success: false,
152
- error: 'Missing OAuth session identifier',
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
- postPopupResult(popupWindow, {
160
- sessionId: targetSessionId,
161
- success: false,
162
- error: 'OAuth session not found in the current client state',
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
- if (popupWindow) {
168
- pendingPopupsRef.current.set(targetSession.sessionId, popupWindow);
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, event.data.code);
207
+ await finishAuth(targetSession.sessionId, code);
173
208
  } catch (error) {
209
+ processingCodesRef.current.delete(codeKey);
174
210
  pendingPopupsRef.current.delete(targetSession.sessionId);
175
- postPopupResult(popupWindow, {
176
- sessionId: targetSession.sessionId,
177
- success: false,
178
- error: error instanceof Error ? error.message : 'Failed to finish auth',
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
- return () => window.removeEventListener('message', handleMessage);
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 = openerMissing
245
- ? 'Error: No opener window found. This window should be opened from the app.'
246
- : missingCode
247
- ? 'Error: No authorization code received.'
248
- : missingSessionId
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
- try {
299
- window.opener.postMessage(
300
- { type: AUTH_CODE_MESSAGE, code, sessionId },
301
- window.location.origin
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
- }, 0);
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]);
@@ -73,6 +73,16 @@ export interface SSEHandlerOptions {
73
73
 
74
74
  const DEFAULT_HEARTBEAT_INTERVAL = 30000;
75
75
 
76
+ function normalizeHeaders(headers?: Record<string, string>): Record<string, string> | undefined {
77
+ if (!headers || typeof headers !== 'object') return undefined;
78
+
79
+ const entries = Object.entries(headers)
80
+ .map(([key, value]) => [key.trim(), String(value).trim()] as const)
81
+ .filter(([key, value]) => key.length > 0 && value.length > 0);
82
+
83
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
84
+ }
85
+
76
86
  // ============================================
77
87
  // SSEConnectionManager Class
78
88
  // ============================================
@@ -238,6 +248,7 @@ export class SSEConnectionManager {
238
248
  */
239
249
  private async connect(params: ConnectParams): Promise<ConnectResult> {
240
250
  const { serverName, serverUrl, callbackUrl, transportType } = params;
251
+ const headers = normalizeHeaders(params.headers);
241
252
 
242
253
  // Normalize serverId to max 12 chars to keep tool names under 64 chars (DeepSeek/OpenAI limits)
243
254
  // Tool name format: tool_<serverId>_<toolName> - with 12 char serverId leaves 46 chars for tool name
@@ -280,6 +291,7 @@ export class SSEConnectionManager {
280
291
  serverUrl,
281
292
  callbackUrl,
282
293
  transportType,
294
+ headers,
283
295
  ...clientMetadata, // Spread client metadata (clientName, clientUri, logoUri, policyUri)
284
296
  });
285
297
 
@@ -238,9 +238,11 @@ export class MCPClient {
238
238
  }
239
239
 
240
240
  const baseUrl = new URL(this.serverUrl);
241
+ const hasAuthorizationHeader = Object.keys(this.headers || {})
242
+ .some((key) => key.toLowerCase() === 'authorization');
241
243
  const transportOptions = {
242
- authProvider: this.oauthProvider!,
243
- ...(this.headers && { headers: this.headers }),
244
+ ...(!hasAuthorizationHeader && { authProvider: this.oauthProvider! }),
245
+ ...(this.headers && { requestInit: { headers: this.headers } }),
244
246
  /**
245
247
  * Custom fetch implementation to handle connection timeouts.
246
248
  * Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
@@ -359,6 +361,7 @@ export class MCPClient {
359
361
  serverUrl: this.serverUrl,
360
362
  callbackUrl: this.callbackUrl,
361
363
  transportType: this.transportType || 'streamable_http',
364
+ headers: this.headers,
362
365
  createdAt: this.createdAt,
363
366
  active: false,
364
367
  }, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
@@ -388,6 +391,7 @@ export class MCPClient {
388
391
  serverUrl: this.serverUrl,
389
392
  callbackUrl: this.callbackUrl,
390
393
  transportType: (this.transportType || 'streamable_http') as TransportType,
394
+ headers: this.headers,
391
395
  createdAt: this.createdAt || Date.now(),
392
396
  active,
393
397
  };
@@ -204,6 +204,7 @@ export interface ConnectParams {
204
204
  serverUrl: string;
205
205
  callbackUrl: string;
206
206
  transportType?: TransportType;
207
+ headers?: Record<string, string>;
207
208
  }
208
209
 
209
210
  export interface DisconnectParams {