@j0hanz/fetch-url-mcp 1.12.9 → 1.12.11

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 (51) hide show
  1. package/README.md +2 -0
  2. package/dist/http/auth.d.ts +4 -9
  3. package/dist/http/auth.d.ts.map +1 -1
  4. package/dist/http/auth.js +18 -26
  5. package/dist/http/native.d.ts +4 -1
  6. package/dist/http/native.d.ts.map +1 -1
  7. package/dist/http/native.js +127 -97
  8. package/dist/http/rate-limit.d.ts +12 -9
  9. package/dist/http/rate-limit.d.ts.map +1 -1
  10. package/dist/http/rate-limit.js +12 -9
  11. package/dist/lib/config.d.ts +1 -0
  12. package/dist/lib/config.d.ts.map +1 -1
  13. package/dist/lib/config.js +4 -2
  14. package/dist/lib/core.d.ts +11 -1
  15. package/dist/lib/core.d.ts.map +1 -1
  16. package/dist/lib/core.js +166 -86
  17. package/dist/lib/error/classes.d.ts.map +1 -1
  18. package/dist/lib/error/classes.js +4 -2
  19. package/dist/lib/error/classify.d.ts.map +1 -1
  20. package/dist/lib/error/classify.js +20 -16
  21. package/dist/lib/mcp-interop.d.ts +12 -0
  22. package/dist/lib/mcp-interop.d.ts.map +1 -1
  23. package/dist/lib/mcp-interop.js +20 -2
  24. package/dist/lib/net/pipeline.d.ts.map +1 -1
  25. package/dist/lib/net/pipeline.js +8 -8
  26. package/dist/lib/net/url.d.ts +0 -1
  27. package/dist/lib/net/url.d.ts.map +1 -1
  28. package/dist/lib/net/url.js +7 -5
  29. package/dist/lib/utils.d.ts +0 -2
  30. package/dist/lib/utils.d.ts.map +1 -1
  31. package/dist/lib/utils.js +3 -8
  32. package/dist/resources/index.d.ts.map +1 -1
  33. package/dist/resources/index.js +46 -21
  34. package/dist/schemas.d.ts +9 -1
  35. package/dist/schemas.d.ts.map +1 -1
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +1 -0
  38. package/dist/tasks/manager.d.ts +9 -106
  39. package/dist/tasks/manager.d.ts.map +1 -1
  40. package/dist/tasks/manager.js +189 -553
  41. package/dist/tasks/store.d.ts +104 -0
  42. package/dist/tasks/store.d.ts.map +1 -0
  43. package/dist/tasks/store.js +480 -0
  44. package/dist/tools/index.js +3 -3
  45. package/dist/transform/index.d.ts +1 -85
  46. package/dist/transform/index.d.ts.map +1 -1
  47. package/dist/transform/index.js +310 -941
  48. package/dist/transform/worker-pool.d.ts +19 -0
  49. package/dist/transform/worker-pool.d.ts.map +1 -0
  50. package/dist/transform/worker-pool.js +716 -0
  51. package/package.json +3 -1
package/README.md CHANGED
@@ -540,6 +540,8 @@ You get text content back by default. If output validation passes, the response
540
540
 
541
541
  To opt into progress updates, include `_meta.progressToken` in the tool call. The token may be a string or number. The server may then emit monotonic `notifications/progress` updates, and task mode reuses the same token until the task reaches a terminal state.
542
542
 
543
+ To run the tool in task mode, include `_meta["modelcontextprotocol.io/task"] = { "taskId": "<client-id>", "keepAlive": <ms> }`. `tasks/result` returns output only after the task reaches `completed`. Task-linked progress notifications, task summaries, and final results include `_meta["modelcontextprotocol.io/related-task"] = { "taskId": "<client-id>" }`.
544
+
543
545
  ```json
544
546
  {
545
547
  "method": "tools/call",
@@ -1,14 +1,9 @@
1
1
  import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
2
2
  import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
3
3
  import type { IncomingMessage, ServerResponse } from 'node:http';
4
- interface RequestContextLike {
5
- readonly req: IncomingMessage;
6
- readonly res: ServerResponse;
7
- readonly url: URL;
8
- readonly method: string | undefined;
9
- }
4
+ import type { RequestContext } from './native.js';
10
5
  declare class CorsPolicy {
11
- handle(ctx: RequestContextLike): boolean;
6
+ handle(ctx: RequestContext): boolean;
12
7
  }
13
8
  export declare const corsPolicy: CorsPolicy;
14
9
  declare class InsufficientScopeError extends InvalidTokenError {
@@ -17,7 +12,7 @@ declare class InsufficientScopeError extends InvalidTokenError {
17
12
  }
18
13
  export declare function isInsufficientScopeError(error: unknown): error is InsufficientScopeError;
19
14
  declare class HostOriginPolicy {
20
- validate(ctx: RequestContextLike): boolean;
15
+ validate(ctx: RequestContext): boolean;
21
16
  private resolveHostHeader;
22
17
  private resolveRequestOrigin;
23
18
  private resolveOrigin;
@@ -57,7 +52,7 @@ declare class AuthService {
57
52
  }
58
53
  export declare function applyUnauthorizedAuthHeaders(req: IncomingMessage, res: ServerResponse): void;
59
54
  export declare function applyInsufficientScopeAuthHeaders(req: IncomingMessage, res: ServerResponse, requiredScopes: readonly string[], message?: string): void;
60
- export declare function buildProtectedResourceMetadataDocument(req: IncomingMessage): {
55
+ export declare function buildProtectedResourceMetadataDocument(): {
61
56
  resource: string;
62
57
  resource_metadata: string;
63
58
  authorization_servers: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"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"}
package/dist/http/auth.js CHANGED
@@ -2,8 +2,9 @@ import { InvalidTokenError, ServerError, } from '@modelcontextprotocol/sdk/serve
2
2
  import { randomBytes } from 'node:crypto';
3
3
  import { config } from '../lib/config.js';
4
4
  import { logDebug, Loggers, logWarn } from '../lib/core.js';
5
+ import { sendJsonRpcError } from '../lib/mcp-interop.js';
5
6
  import { normalizeHost } from '../lib/net/index.js';
6
- import { composeAbortSignal, hmacSha256Hex, isObject, parseUrlOrNull, timingSafeEqualUtf8, } from '../lib/utils.js';
7
+ import { composeAbortSignal, hmacSha256Hex, isObject, timingSafeEqualUtf8, } from '../lib/utils.js';
7
8
  function setNoStoreHeaders(res) {
8
9
  res.setHeader('X-Content-Type-Options', 'nosniff');
9
10
  res.setHeader('Cache-Control', 'no-store');
@@ -19,10 +20,8 @@ function sendEmpty(res, status) {
19
20
  res.setHeader('Content-Length', '0');
20
21
  res.end();
21
22
  }
22
- function sendError(res, _code, message, status = 400,
23
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- kept for call-site compatibility
24
- _id) {
25
- sendJson(res, status, { error: message });
23
+ function sendError(res, code, message, status = 400, id) {
24
+ sendJsonRpcError(res, status, code, message, id ?? null);
26
25
  }
27
26
  function getHeaderValue(req, name) {
28
27
  const value = req.headers[name];
@@ -136,7 +135,7 @@ class HostOriginPolicy {
136
135
  return null;
137
136
  const isEncrypted = Reflect.get(req.socket, 'encrypted') === true;
138
137
  const scheme = isEncrypted ? 'https' : 'http';
139
- const parsed = parseUrlOrNull(`${scheme}://${hostHeader}`);
138
+ const parsed = URL.parse(`${scheme}://${hostHeader}`);
140
139
  if (!parsed)
141
140
  return null;
142
141
  const normalizedHost = normalizeHost(parsed.host);
@@ -151,7 +150,7 @@ class HostOriginPolicy {
151
150
  resolveOrigin(origin) {
152
151
  if (origin === 'null')
153
152
  return null;
154
- const parsed = parseUrlOrNull(origin);
153
+ const parsed = URL.parse(origin);
155
154
  if (!parsed)
156
155
  return null;
157
156
  const scheme = parsed.protocol === 'https:' ? 'https' : 'http';
@@ -200,6 +199,9 @@ export function assertHttpModeConfiguration() {
200
199
  if (config.auth.mode === 'oauth' && !config.auth.issuerUrl) {
201
200
  throw Error('OAuth mode requires OAUTH_ISSUER_URL to serve RFC9728 metadata');
202
201
  }
202
+ if (config.auth.mode === 'oauth' && !config.auth.introspectionUrl) {
203
+ throw Error('OAuth mode requires OAUTH_INTROSPECTION_URL');
204
+ }
203
205
  if (config.auth.mode === 'static' && config.auth.staticTokens.length === 0) {
204
206
  throw Error('Static auth requires ACCESS_TOKENS or API_KEY to be configured');
205
207
  }
@@ -222,11 +224,6 @@ export function ensureMcpProtocolVersion(req, res, options) {
222
224
  const version = resolveMcpProtocolVersion(req);
223
225
  const path = URL.parse(req.url ?? '', 'http://localhost')?.pathname;
224
226
  if (!version) {
225
- // Tolerate missing header on sessioned requests (expectedVersion set)
226
- // to avoid breaking older clients that don't send it yet.
227
- if (options?.expectedVersion) {
228
- return true;
229
- }
230
227
  logWarn('MCP protocol version rejected', { reason: 'missing_header', path }, 'http');
231
228
  sendError(res, -32600, 'Please include the MCP-Protocol-Version header in your request.');
232
229
  return false;
@@ -507,16 +504,11 @@ class AuthService {
507
504
  }
508
505
  }
509
506
  }
510
- function resolvePublicOrigin(req) {
511
- const host = getHeaderValue(req, 'host');
512
- if (host) {
513
- const protocol = config.server.https.enabled ? 'https' : 'http';
514
- return `${protocol}://${host}`;
515
- }
507
+ function resolvePublicOrigin() {
516
508
  return config.auth.resourceUrl.origin;
517
509
  }
518
- function buildRequestScopedProtectedResourceUrls(req) {
519
- const origin = resolvePublicOrigin(req);
510
+ function buildRequestScopedProtectedResourceUrls() {
511
+ const origin = resolvePublicOrigin();
520
512
  return {
521
513
  resource: new URL('/mcp', `${origin}/`).href,
522
514
  resourceMetadata: new URL(resolveResourceMetadataPath(), `${origin}/`).href,
@@ -525,13 +517,13 @@ function buildRequestScopedProtectedResourceUrls(req) {
525
517
  function resolveResourceMetadataPath() {
526
518
  return '/.well-known/oauth-protected-resource/mcp';
527
519
  }
528
- function buildResourceMetadataUrl(req) {
529
- return buildRequestScopedProtectedResourceUrls(req).resourceMetadata;
520
+ function buildResourceMetadataUrl() {
521
+ return buildRequestScopedProtectedResourceUrls().resourceMetadata;
530
522
  }
531
523
  export function applyUnauthorizedAuthHeaders(req, res) {
532
524
  if (!isOAuthMetadataEnabled())
533
525
  return;
534
- const resourceMetadata = buildResourceMetadataUrl(req);
526
+ const resourceMetadata = buildResourceMetadataUrl();
535
527
  const challengeParts = [`resource_metadata="${resourceMetadata}"`];
536
528
  if (config.auth.requiredScopes.length > 0) {
537
529
  challengeParts.push(`scope="${config.auth.requiredScopes.join(' ')}"`);
@@ -541,7 +533,7 @@ export function applyUnauthorizedAuthHeaders(req, res) {
541
533
  export function applyInsufficientScopeAuthHeaders(req, res, requiredScopes, message = 'Additional authorization scope is required') {
542
534
  if (!isOAuthMetadataEnabled())
543
535
  return;
544
- const resourceMetadata = buildResourceMetadataUrl(req);
536
+ const resourceMetadata = buildResourceMetadataUrl();
545
537
  const challengeParts = [
546
538
  'error="insufficient_scope"',
547
539
  `scope="${requiredScopes.join(' ')}"`,
@@ -550,8 +542,8 @@ export function applyInsufficientScopeAuthHeaders(req, res, requiredScopes, mess
550
542
  ];
551
543
  res.setHeader('WWW-Authenticate', `Bearer ${challengeParts.join(', ')}`);
552
544
  }
553
- export function buildProtectedResourceMetadataDocument(req) {
554
- const urls = buildRequestScopedProtectedResourceUrls(req);
545
+ export function buildProtectedResourceMetadataDocument() {
546
+ const urls = buildRequestScopedProtectedResourceUrls();
555
547
  if (!config.auth.issuerUrl) {
556
548
  const error = new ServerError('OAuth issuer URL is required for protected resource metadata');
557
549
  throw error;
@@ -21,7 +21,7 @@ export interface AuthenticatedContext extends RequestContext {
21
21
  }
22
22
  export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
23
23
  export declare function sendEmpty(res: ServerResponse, status: number): void;
24
- export declare function sendError(res: ServerResponse, _code: number, message: string, status?: number, _id?: JsonRpcId | null): void;
24
+ export declare function sendError(res: ServerResponse, code: number, message: string, status?: number, id?: JsonRpcId | null): void;
25
25
  export declare function getHeaderValue(req: IncomingMessage, name: string): string | null;
26
26
  export declare function getMcpSessionId(req: IncomingMessage): string | null;
27
27
  export declare function findDuplicateSingleValueHeader(req: IncomingMessage): string | null;
@@ -45,7 +45,10 @@ export declare class JsonBodyError extends Error {
45
45
  export declare function isJsonBodyError(error: unknown): error is JsonBodyError;
46
46
  export declare const DEFAULT_BODY_LIMIT_BYTES: number;
47
47
  declare class JsonBodyReader {
48
+ private validateContentLength;
48
49
  read(req: IncomingMessage, limit?: number, signal?: AbortSignal): Promise<unknown>;
50
+ private setupAbortListener;
51
+ private cleanupAbortListener;
49
52
  private readBody;
50
53
  private collectChunks;
51
54
  private normalizeChunk;
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
- import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
2
+ import { ErrorCode, isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { once } from 'node:events';
5
5
  import { readFileSync } from 'node:fs';
@@ -11,17 +11,20 @@ import process from 'node:process';
11
11
  import { Writable } from 'node:stream';
12
12
  import { pipeline } from 'node:stream/promises';
13
13
  import { config, enableHttpMode, serverVersion } from '../lib/config.js';
14
- import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, logDebug, logError, Loggers, logInfo, logWarn, registerMcpSessionOwnerKey, registerMcpSessionServer, reserveSessionSlot, resolveMcpSessionIdByServer, runWithRequestContext, startSessionCleanupLoop, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
14
+ import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, logDebug, logError, Loggers, logInfo, logWarn, registerMcpSessionOwnerKey, registerMcpSessionServer, reserveSessionSlot, resolveMcpSessionIdByServer, runWithRequestContext, runWithTraceContext, startSessionCleanupLoop, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
15
15
  import { getErrorMessage, toError } from '../lib/error/index.js';
16
- import { acceptsEventStream, acceptsJsonAndEventStream, isMcpRequestBody, } from '../lib/mcp-interop.js';
16
+ import { acceptsEventStream, acceptsJsonAndEventStream, isMcpRequestBody, sendJsonRpcError, } from '../lib/mcp-interop.js';
17
17
  import { createDefaultBlockList, normalizeIpForBlockList, } from '../lib/net/index.js';
18
18
  import { isObject } from '../lib/utils.js';
19
19
  import { createMcpServerForHttpSession } from '../server.js';
20
20
  import { buildAuthenticatedOwnerKey } from '../tasks/index.js';
21
21
  import { getTransformPoolStats } from '../transform/index.js';
22
- import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, DEFAULT_MCP_PROTOCOL_VERSION, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
23
- import { createRateLimitManagerImpl, } from './rate-limit.js';
22
+ import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
23
+ import { RateLimiter } from './rate-limit.js';
24
24
  const DROP_LOG_INTERVAL_MS = 10_000;
25
+ const MISSING_SESSION_ID_MESSAGE = "We couldn't find a session ID for your request. Please ensure you have an active session.";
26
+ const SESSION_NOT_INITIALIZED_MESSAGE = "Your session hasn't been initialized yet. Please wait a moment and try again.";
27
+ const SESSION_NOT_FOUND_MESSAGE = "We couldn't find your session. It might have expired or been closed.";
25
28
  function abortControllerBestEffort(controller) {
26
29
  if (!controller.signal.aborted)
27
30
  controller.abort();
@@ -93,10 +96,8 @@ export function sendEmpty(res, status) {
93
96
  res.setHeader('Content-Length', '0');
94
97
  res.end();
95
98
  }
96
- export function sendError(res, _code, message, status = 400,
97
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- kept for call-site compat
98
- _id) {
99
- sendJson(res, status, { error: message });
99
+ export function sendError(res, code, message, status = 400, id) {
100
+ sendJsonRpcError(res, status, code, message, id ?? null);
100
101
  }
101
102
  // ---------------------------------------------------------------------------
102
103
  // Request helpers
@@ -305,18 +306,20 @@ function isRequestReadAborted(req) {
305
306
  return req.destroyed && !req.complete;
306
307
  }
307
308
  class JsonBodyReader {
309
+ validateContentLength(req, limit) {
310
+ const contentLengthHeader = getHeaderValue(req, 'content-length');
311
+ if (!contentLengthHeader)
312
+ return;
313
+ const contentLength = Number.parseInt(contentLengthHeader, 10);
314
+ if (Number.isFinite(contentLength) && contentLength > limit) {
315
+ throw new JsonBodyError('payload-too-large', 'Payload too large');
316
+ }
317
+ }
308
318
  async read(req, limit = DEFAULT_BODY_LIMIT_BYTES, signal) {
309
319
  const contentType = getHeaderValue(req, 'content-type');
310
320
  if (!contentType?.includes('application/json'))
311
321
  return undefined;
312
- const contentLengthHeader = getHeaderValue(req, 'content-length');
313
- if (contentLengthHeader) {
314
- const contentLength = Number.parseInt(contentLengthHeader, 10);
315
- if (Number.isFinite(contentLength) && contentLength > limit) {
316
- const error = new JsonBodyError('payload-too-large', 'Payload too large');
317
- throw error;
318
- }
319
- }
322
+ this.validateContentLength(req, limit);
320
323
  if (signal?.aborted || isRequestReadAborted(req)) {
321
324
  const error = new JsonBodyError('read-failed', 'Request aborted');
322
325
  throw error;
@@ -328,24 +331,35 @@ class JsonBodyReader {
328
331
  return JSON.parse(body);
329
332
  }
330
333
  catch (err) {
331
- const error = new JsonBodyError('invalid-json', getErrorMessage(err));
332
- throw error;
334
+ throw new JsonBodyError('invalid-json', getErrorMessage(err));
333
335
  }
334
336
  }
335
- async readBody(req, limit, signal) {
336
- const abortListener = signal != null
337
- ? () => {
338
- destroyRequestBestEffort(req);
339
- }
340
- : null;
341
- if (signal != null && abortListener) {
342
- if (signal.aborted) {
343
- abortListener();
344
- }
345
- else {
346
- signal.addEventListener('abort', abortListener, { once: true });
347
- }
337
+ setupAbortListener(req, signal) {
338
+ if (signal == null)
339
+ return null;
340
+ const listener = () => {
341
+ destroyRequestBestEffort(req);
342
+ };
343
+ if (signal.aborted) {
344
+ listener();
345
+ }
346
+ else {
347
+ signal.addEventListener('abort', listener, { once: true });
348
348
  }
349
+ return listener;
350
+ }
351
+ cleanupAbortListener(signal, listener) {
352
+ if (!signal || !listener)
353
+ return;
354
+ try {
355
+ signal.removeEventListener('abort', listener);
356
+ }
357
+ catch {
358
+ // Best-effort cleanup.
359
+ }
360
+ }
361
+ async readBody(req, limit, signal) {
362
+ const abortListener = this.setupAbortListener(req, signal);
349
363
  try {
350
364
  const { chunks, size } = await this.collectChunks(req, limit, signal);
351
365
  if (chunks.length === 0)
@@ -360,14 +374,7 @@ class JsonBodyReader {
360
374
  return text;
361
375
  }
362
376
  finally {
363
- if (signal && abortListener) {
364
- try {
365
- signal.removeEventListener('abort', abortListener);
366
- }
367
- catch {
368
- // Best-effort cleanup.
369
- }
370
- }
377
+ this.cleanupAbortListener(signal, abortListener);
371
378
  }
372
379
  }
373
380
  async collectChunks(req, limit, signal) {
@@ -429,7 +436,7 @@ function unregisterSessionTaskScope(server) {
429
436
  unregisterMcpSessionServer(sessionId);
430
437
  return sessionId;
431
438
  }
432
- async function closeSessionResources(session, options) {
439
+ function buildCloseTasks(session, options) {
433
440
  const closeTasks = [];
434
441
  if (options.closeTransportReason) {
435
442
  closeTasks.push(closeTransportBestEffort(session.transport, options.closeTransportReason));
@@ -437,6 +444,10 @@ async function closeSessionResources(session, options) {
437
444
  if (options.closeServerReason) {
438
445
  closeTasks.push(closeMcpServerBestEffort(session.server, options.closeServerReason));
439
446
  }
447
+ return closeTasks;
448
+ }
449
+ async function closeSessionResources(session, options) {
450
+ const closeTasks = buildCloseTasks(session, options);
440
451
  if (options.awaitClose && closeTasks.length > 0) {
441
452
  await Promise.all(closeTasks);
442
453
  }
@@ -607,19 +618,15 @@ export function sendHealthRouteResponse(store, ctx, authPresent) {
607
618
  }
608
619
  function resolveRequestedProtocolVersion(body) {
609
620
  if (!isObject(body))
610
- return DEFAULT_MCP_PROTOCOL_VERSION;
621
+ return '';
611
622
  const { params } = body;
612
623
  if (!isObject(params))
613
- return DEFAULT_MCP_PROTOCOL_VERSION;
624
+ return '';
614
625
  const { protocolVersion: value } = params;
615
626
  if (typeof value !== 'string')
616
- return DEFAULT_MCP_PROTOCOL_VERSION;
627
+ return '';
617
628
  const normalized = value.trim();
618
- if (normalized.length === 0)
619
- return DEFAULT_MCP_PROTOCOL_VERSION;
620
- return SUPPORTED_MCP_PROTOCOL_VERSIONS.has(normalized)
621
- ? normalized
622
- : DEFAULT_MCP_PROTOCOL_VERSION;
629
+ return normalized;
623
630
  }
624
631
  function resolveProtocolVersionHeader(req) {
625
632
  const header = getHeaderValue(req, 'mcp-protocol-version');
@@ -631,6 +638,15 @@ function resolveProtocolVersionHeader(req) {
631
638
  function isInitializedNotification(method) {
632
639
  return method === 'notifications/initialized';
633
640
  }
641
+ function getBodyMeta(body) {
642
+ if (!isObject(body))
643
+ return undefined;
644
+ const { params } = body;
645
+ if (!isObject(params))
646
+ return undefined;
647
+ const meta = params['_meta'];
648
+ return isObject(meta) ? meta : undefined;
649
+ }
634
650
  function isPingRequest(method) {
635
651
  return method === 'ping';
636
652
  }
@@ -747,9 +763,7 @@ class McpSessionGateway {
747
763
  status: 406,
748
764
  sessionId,
749
765
  });
750
- sendJson(ctx.res, 406, {
751
- error: 'We need you to use "text/event-stream" for this connection.',
752
- });
766
+ sendError(ctx.res, ErrorCode.InvalidRequest, 'We need you to use "text/event-stream" for this connection.', 406);
753
767
  return;
754
768
  }
755
769
  logDebug('MCP GET received', { sessionId }, Loggers.LOG_HTTP);
@@ -777,9 +791,7 @@ class McpSessionGateway {
777
791
  reason: 'accept_missing_json_or_event_stream',
778
792
  status: 406,
779
793
  });
780
- sendJson(ctx.res, 406, {
781
- error: 'We need the request to accept both "application/json" and "text/event-stream".',
782
- });
794
+ sendError(ctx.res, ErrorCode.InvalidRequest, 'We need the request to accept both "application/json" and "text/event-stream".', 406);
783
795
  return null;
784
796
  }
785
797
  const { body } = ctx;
@@ -843,7 +855,7 @@ class McpSessionGateway {
843
855
  mcpCode: -32600,
844
856
  rpcId: requestId,
845
857
  });
846
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
858
+ sendError(ctx.res, -32600, MISSING_SESSION_ID_MESSAGE, 400, requestId);
847
859
  return false;
848
860
  }
849
861
  return true;
@@ -866,7 +878,7 @@ class McpSessionGateway {
866
878
  sessionId,
867
879
  rpcId: requestId,
868
880
  });
869
- sendError(ctx.res, -32600, "Your session hasn't been initialized yet. Please wait a moment and try again.", 400, requestId);
881
+ sendError(ctx.res, -32600, SESSION_NOT_INITIALIZED_MESSAGE, 400, requestId);
870
882
  return false;
871
883
  }
872
884
  async getOrCreateTransport(ctx, requestId) {
@@ -880,40 +892,39 @@ class McpSessionGateway {
880
892
  return null;
881
893
  return this.createNewSession(ctx, requestId, negotiatedProtocolVersion);
882
894
  }
895
+ rejectInitializeRequest(ctx, requestId, reason, mcpCode, message) {
896
+ logGatewayRejection({
897
+ message: 'Rejected MCP initialize request',
898
+ method: ctx.method,
899
+ path: ctx.url.pathname,
900
+ reason,
901
+ status: 400,
902
+ mcpCode,
903
+ rpcId: requestId,
904
+ });
905
+ sendError(ctx.res, mcpCode, message, 400, requestId);
906
+ return null;
907
+ }
883
908
  getInitializeProtocolVersion(ctx, requestId) {
884
909
  if (!isMcpRequestBody(ctx.body)) {
885
- logGatewayRejection({
886
- message: 'Rejected MCP initialize request',
887
- method: ctx.method,
888
- path: ctx.url.pathname,
889
- reason: 'missing_session_id',
890
- status: 400,
891
- mcpCode: -32600,
892
- rpcId: requestId,
893
- });
894
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
895
- return null;
910
+ return this.rejectInitializeRequest(ctx, requestId, 'missing_session_id', -32600, MISSING_SESSION_ID_MESSAGE);
896
911
  }
897
912
  if (!isInitializeRequest(ctx.body)) {
898
913
  const invalidInitialize = ctx.body.method === 'initialize';
899
- logGatewayRejection({
900
- message: 'Rejected MCP initialize request',
901
- method: ctx.method,
902
- path: ctx.url.pathname,
903
- reason: invalidInitialize
904
- ? 'invalid_initialize_request'
905
- : 'missing_session_id',
906
- status: 400,
907
- mcpCode: invalidInitialize ? -32602 : -32600,
908
- rpcId: requestId,
909
- });
910
- sendError(ctx.res, invalidInitialize ? -32602 : -32600, invalidInitialize
914
+ return this.rejectInitializeRequest(ctx, requestId, invalidInitialize ? 'invalid_initialize_request' : 'missing_session_id', invalidInitialize ? -32602 : -32600, invalidInitialize
911
915
  ? 'The initialize request format is invalid. Please double-check your parameters.'
912
- : "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
913
- return null;
916
+ : MISSING_SESSION_ID_MESSAGE);
914
917
  }
915
918
  const negotiatedProtocolVersion = resolveRequestedProtocolVersion(ctx.body);
919
+ if (negotiatedProtocolVersion.length === 0 ||
920
+ !SUPPORTED_MCP_PROTOCOL_VERSIONS.has(negotiatedProtocolVersion)) {
921
+ return this.rejectInitializeRequest(ctx, requestId, 'unsupported_protocol_version', -32600, `The protocol version '${negotiatedProtocolVersion || '(missing)'}' isn't supported right now. Please check and try again.`);
922
+ }
916
923
  const headerProtocolVersion = resolveProtocolVersionHeader(ctx.req);
924
+ if (headerProtocolVersion &&
925
+ !SUPPORTED_MCP_PROTOCOL_VERSIONS.has(headerProtocolVersion)) {
926
+ return this.rejectInitializeRequest(ctx, requestId, 'unsupported_protocol_version_header', -32600, `The protocol version '${headerProtocolVersion}' isn't supported right now. Please check and try again.`);
927
+ }
917
928
  if (headerProtocolVersion &&
918
929
  headerProtocolVersion !== negotiatedProtocolVersion) {
919
930
  logGatewayRejection({
@@ -951,20 +962,30 @@ class McpSessionGateway {
951
962
  return null;
952
963
  return { sessionId, session };
953
964
  }
965
+ sendMissingSessionId(res, requestId = null) {
966
+ sendError(res, -32600, MISSING_SESSION_ID_MESSAGE, 400, requestId);
967
+ return null;
968
+ }
969
+ sendSessionNotInitialized(res, requestId = null) {
970
+ sendError(res, -32600, SESSION_NOT_INITIALIZED_MESSAGE, 400, requestId);
971
+ return null;
972
+ }
973
+ sendSessionUnavailable(res, requestId = null, status = 404) {
974
+ sendError(res, -32600, SESSION_NOT_FOUND_MESSAGE, status, requestId);
975
+ return null;
976
+ }
954
977
  getRequiredAuthenticatedSession(ctx, requestId = null, options) {
955
978
  const state = this.getOptionalAuthenticatedSession(ctx, requestId);
956
979
  if (!state)
957
980
  return null;
958
981
  const { sessionId, session } = state;
959
982
  if (!sessionId || !session) {
960
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
961
- return null;
983
+ return this.sendMissingSessionId(ctx.res, requestId);
962
984
  }
963
985
  if (!this.ensureSessionProtocolVersion(ctx, session))
964
986
  return null;
965
987
  if (options?.requireInitialized && !session.protocolInitialized) {
966
- sendError(ctx.res, -32600, "Your session hasn't been initialized yet. Please wait a moment and try again.", 400, requestId);
967
- return null;
988
+ return this.sendSessionNotInitialized(ctx.res, requestId);
968
989
  }
969
990
  return { sessionId, session };
970
991
  }
@@ -980,7 +1001,7 @@ class McpSessionGateway {
980
1001
  sessionId,
981
1002
  rpcId: requestId,
982
1003
  });
983
- sendError(res, -32600, "We couldn't find your session. It might have expired or been closed.", 404, requestId);
1004
+ sendError(res, -32600, SESSION_NOT_FOUND_MESSAGE, 404, requestId);
984
1005
  return null;
985
1006
  }
986
1007
  if (!authFingerprint || session.authFingerprint !== authFingerprint) {
@@ -993,8 +1014,7 @@ class McpSessionGateway {
993
1014
  sessionId,
994
1015
  rpcId: requestId,
995
1016
  });
996
- sendError(res, -32600, "We couldn't find your session. It might have expired or been closed.", 404, requestId);
997
- return null;
1017
+ return this.sendSessionUnavailable(res, requestId);
998
1018
  }
999
1019
  return session;
1000
1020
  }
@@ -1177,6 +1197,13 @@ class HttpDispatcher {
1177
1197
  this.store = store;
1178
1198
  this.mcpGateway = mcpGateway;
1179
1199
  }
1200
+ sendAuthFailure(ctx, status, message) {
1201
+ if (isMcpRoute(ctx.url.pathname)) {
1202
+ sendError(ctx.res, -32000, message, status);
1203
+ return;
1204
+ }
1205
+ sendJson(ctx.res, status, { error: message });
1206
+ }
1180
1207
  async tryHandleHealthRoute(ctx) {
1181
1208
  if (!shouldHandleHealthRoute(ctx))
1182
1209
  return false;
@@ -1198,7 +1225,7 @@ class HttpDispatcher {
1198
1225
  return false;
1199
1226
  if (!isProtectedResourceMetadataPath(ctx.url.pathname))
1200
1227
  return false;
1201
- const document = buildProtectedResourceMetadataDocument(ctx.req);
1228
+ const document = buildProtectedResourceMetadataDocument();
1202
1229
  sendJson(ctx.res, 200, document);
1203
1230
  return true;
1204
1231
  }
@@ -1260,11 +1287,11 @@ class HttpDispatcher {
1260
1287
  logWarn('Authentication failed', { message, method: ctx.method, path: ctx.url.pathname }, Loggers.LOG_AUTH);
1261
1288
  if (isInsufficientScopeError(err)) {
1262
1289
  applyInsufficientScopeAuthHeaders(ctx.req, ctx.res, err.requiredScopes, message);
1263
- sendError(ctx.res, -32000, message, 403);
1290
+ this.sendAuthFailure(ctx, 403, message);
1264
1291
  return null;
1265
1292
  }
1266
1293
  applyUnauthorizedAuthHeaders(ctx.req, ctx.res);
1267
- sendError(ctx.res, -32000, message, 401);
1294
+ this.sendAuthFailure(ctx, 401, message);
1268
1295
  return null;
1269
1296
  }
1270
1297
  }
@@ -1343,7 +1370,7 @@ class HttpRequestPipeline {
1343
1370
  return;
1344
1371
  if (!(await this.populateRequestBody(ctx, rawReq)))
1345
1372
  return;
1346
- await this.dispatcher.dispatch(ctx);
1373
+ await runWithTraceContext(getBodyMeta(ctx.body), () => this.dispatcher.dispatch(ctx));
1347
1374
  });
1348
1375
  }
1349
1376
  finally {
@@ -1385,9 +1412,6 @@ class HttpRequestPipeline {
1385
1412
  return false;
1386
1413
  }
1387
1414
  if (!this.rateLimiter.check(ctx)) {
1388
- sendJson(ctx.res, 429, {
1389
- error: "You're sending requests a bit too quickly. Please slow down and try again.",
1390
- });
1391
1415
  drainRequest(rawReq);
1392
1416
  return false;
1393
1417
  }
@@ -1502,7 +1526,7 @@ export async function startHttpServer() {
1502
1526
  assertHttpModeConfiguration();
1503
1527
  enableHttpMode();
1504
1528
  resetEventLoopMonitoring();
1505
- const rateLimiter = createRateLimitManagerImpl(config.rateLimit);
1529
+ const rateLimiter = new RateLimiter(config.rateLimit);
1506
1530
  const sessionStore = createSessionStore(config.server.sessionTtlMs);
1507
1531
  const sessionCleanup = startSessionCleanupLoop(sessionStore, config.server.sessionTtlMs, {
1508
1532
  onEvictSession: (session) => {
@@ -1522,6 +1546,12 @@ export async function startHttpServer() {
1522
1546
  await listen(server, config.server.host, config.server.port);
1523
1547
  const port = resolveListeningPort(server, config.server.port);
1524
1548
  const protocol = config.server.https.enabled ? 'https' : 'http';
1549
+ if (!config.auth.publicBaseUrl) {
1550
+ const resolvedResourceUrl = new URL(config.auth.resourceUrl);
1551
+ resolvedResourceUrl.port = String(port);
1552
+ resolvedResourceUrl.protocol = `${protocol}:`;
1553
+ config.auth.resourceUrl = resolvedResourceUrl;
1554
+ }
1525
1555
  logInfo(`${protocol.toUpperCase()} server listening on port ${port}`, {
1526
1556
  platform: process.platform,
1527
1557
  arch: process.arch,