@j0hanz/fetch-url-mcp 1.10.4 → 1.10.5

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 (50) hide show
  1. package/README.md +7 -9
  2. package/dist/http/auth.d.ts +1 -0
  3. package/dist/http/auth.d.ts.map +1 -1
  4. package/dist/http/auth.js +1 -1
  5. package/dist/http/health.d.ts +1 -0
  6. package/dist/http/health.d.ts.map +1 -1
  7. package/dist/http/health.js +1 -1
  8. package/dist/http/helpers.d.ts +6 -0
  9. package/dist/http/helpers.d.ts.map +1 -1
  10. package/dist/http/helpers.js +4 -3
  11. package/dist/http/native.d.ts.map +1 -1
  12. package/dist/http/native.js +177 -112
  13. package/dist/lib/core.d.ts +4 -39
  14. package/dist/lib/core.d.ts.map +1 -1
  15. package/dist/lib/core.js +41 -161
  16. package/dist/lib/dom-prep.d.ts.map +1 -1
  17. package/dist/lib/dom-prep.js +4 -2
  18. package/dist/lib/fetch-pipeline.d.ts +12 -15
  19. package/dist/lib/fetch-pipeline.d.ts.map +1 -1
  20. package/dist/lib/fetch-pipeline.js +77 -36
  21. package/dist/lib/http.d.ts.map +1 -1
  22. package/dist/lib/http.js +4 -0
  23. package/dist/lib/progress.js +2 -2
  24. package/dist/lib/session.d.ts +41 -0
  25. package/dist/lib/session.d.ts.map +1 -0
  26. package/dist/lib/session.js +140 -0
  27. package/dist/lib/task-handlers.d.ts.map +1 -1
  28. package/dist/lib/task-handlers.js +32 -25
  29. package/dist/resources/instructions.js +1 -1
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +16 -5
  32. package/dist/tasks/execution.d.ts +2 -9
  33. package/dist/tasks/execution.d.ts.map +1 -1
  34. package/dist/tasks/execution.js +25 -18
  35. package/dist/tasks/manager.d.ts +3 -1
  36. package/dist/tasks/manager.d.ts.map +1 -1
  37. package/dist/tasks/manager.js +29 -11
  38. package/dist/tasks/tool-registry.d.ts +4 -0
  39. package/dist/tasks/tool-registry.d.ts.map +1 -1
  40. package/dist/tasks/tool-registry.js +13 -1
  41. package/dist/tools/fetch-url.d.ts +2 -2
  42. package/dist/tools/fetch-url.d.ts.map +1 -1
  43. package/dist/tools/fetch-url.js +29 -33
  44. package/dist/transform/shared.d.ts.map +1 -1
  45. package/dist/transform/shared.js +54 -46
  46. package/dist/transform/transform.d.ts.map +1 -1
  47. package/dist/transform/transform.js +72 -51
  48. package/dist/transform/worker-pool.d.ts.map +1 -1
  49. package/dist/transform/worker-pool.js +6 -1
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -477,7 +477,7 @@ For more info, see [Kilo Code docs](https://kilocode.ai/docs).
477
477
  - Fetch documentation pages, blog posts, or reference material into Markdown before sending them to an LLM.
478
478
  - Retrieve repository-hosted content from GitHub, GitLab, Bitbucket, or Gists and let the server rewrite page URLs to raw endpoints when possible.
479
479
  - Reuse cached Markdown through `internal://cache/{namespace}/{hash}` or bypass the cache with `forceRefresh` for time-sensitive pages.
480
- - Use task mode for large pages or slower sites when the inline response would otherwise be truncated or delayed.
480
+ - Use task mode for large pages or slower sites when the fetch might otherwise be delayed, cancelled, or better handled asynchronously.
481
481
 
482
482
  ## Architecture
483
483
 
@@ -525,16 +525,14 @@ For more info, see [Kilo Code docs](https://kilocode.ai/docs).
525
525
 
526
526
  #### `fetch-url`
527
527
 
528
- Fetch public webpages and convert HTML into AI-readable Markdown. The tool is read-only, does not execute page JavaScript, can bypass the cache with `forceRefresh`, and supports optional task mode for larger or slower fetches.
528
+ Fetch public webpages and convert HTML into AI-readable Markdown. The tool is read-only, does not execute page JavaScript, can bypass the cache with `forceRefresh`, and supports optional task mode for larger or slower fetches. `forceRefresh` refreshes cached content only; it does not bypass fetch or inline truncation limits.
529
529
 
530
- | Parameter | Type | Required | Description |
531
- | ------------------ | --------- | -------- | ------------------------------------------------------------------------------------------- |
532
- | `url` | `string` | yes | Target URL. Max 2048 chars. |
533
- | `skipNoiseRemoval` | `boolean` | no | Preserve navigation/footers (disable noise filtering). |
534
- | `forceRefresh` | `boolean` | no | Bypass cache and fetch fresh content. |
535
- | `maxInlineChars` | `integer` | no | Inline markdown limit (0-10485760, 0=unlimited). Lower of this or the global limit applies. |
530
+ | Parameter | Type | Required | Description |
531
+ | -------------- | --------- | -------- | ------------------------------------- |
532
+ | `url` | `string` | yes | Target URL. Max 2048 chars. |
533
+ | `forceRefresh` | `boolean` | no | Bypass cache and fetch fresh content. |
536
534
 
537
- The response is returned as MCP text content and, when validation succeeds, as `structuredContent` containing `url`, `resolvedUrl`, `finalUrl`, `title`, `metadata`, `markdown`, `fromCache`, `fetchedAt`, `contentSize`, and `truncated`.
535
+ The response is returned as MCP text content and, when validation succeeds, as `structuredContent` containing `url`, `resolvedUrl`, `finalUrl`, `title`, `metadata`, `markdown`, `fromCache`, `fetchedAt`, `contentSize`, and `truncated`. A `truncated: true` result means the content hit server-enforced fetch or inline limits; `forceRefresh` does not remove that limit.
538
536
 
539
537
  ```text
540
538
  1. [Client] -- tools/call {name: "fetch-url", arguments} --> [Server]
@@ -21,6 +21,7 @@ declare class HostOriginPolicy {
21
21
  }
22
22
  export declare const hostOriginPolicy: HostOriginPolicy;
23
23
  export declare function assertHttpModeConfiguration(): void;
24
+ export declare const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
24
25
  export declare const SUPPORTED_MCP_PROTOCOL_VERSIONS: Set<string>;
25
26
  interface McpProtocolVersionCheckOptions {
26
27
  expectedVersion?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EACL,iBAAiB,EAElB,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAO/E,OAAO,EAEL,KAAK,cAAc,EAIpB,MAAM,cAAc,CAAC;AAMtB,cAAM,UAAU;IAId,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;CAuBrC;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC;AAS3C,cAAM,sBAAuB,SAAQ,iBAAiB;IAElD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE;gBAAjC,cAAc,EAAE,SAAS,MAAM,EAAE,EAC1C,OAAO,SAAuB;CAKjC;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,sBAAsB,CAEjC;AAwCD,cAAM,gBAAgB;IACpB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;IA2BtC,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,MAAM;CAQf;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC;AAMvD,wBAAgB,2BAA2B,IAAI,IAAI,CA2BlD;AAOD,eAAO,MAAM,+BAA+B,aAE1C,CAAC;AAEH,UAAU,8BAA8B;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAwBT;AAED,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAQD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,GAAG,SAAS,GACzB,MAAM,GAAG,IAAI,CAWf;AASD,cAAM,WAAW;IACf,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEjC;IAEI,YAAY,CAChB,GAAG,EAAE,eAAe,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,QAAQ,CAAC;IAUpB,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,yBAAyB;YA0BnB,oBAAoB;IA4BlC,OAAO,CAAC,0BAA0B;IAmBlC,OAAO,CAAC,oBAAoB;YAWd,uBAAuB;CAgCtC;AA+BD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GAClB,IAAI,CAUN;AAED,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,OAAO,SAA+C,GACrD,IAAI,CAYN;AAED,wBAAgB,sCAAsC,CAAC,GAAG,EAAE,eAAe,GAAG;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAeA;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKzE;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
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
@@ -179,7 +179,7 @@ export function assertHttpModeConfiguration() {
179
179
  // ---------------------------------------------------------------------------
180
180
  // MCP protocol version
181
181
  // ---------------------------------------------------------------------------
182
- const DEFAULT_MCP_PROTOCOL_VERSION = '2025-11-25';
182
+ export const DEFAULT_MCP_PROTOCOL_VERSION = '2025-11-25';
183
183
  export const SUPPORTED_MCP_PROTOCOL_VERSIONS = new Set([
184
184
  DEFAULT_MCP_PROTOCOL_VERSION,
185
185
  ]);
@@ -2,6 +2,7 @@ import type { SessionStore } from '../lib/core.js';
2
2
  import { type RequestContext } from './helpers.js';
3
3
  export declare function resetEventLoopMonitoring(): void;
4
4
  export declare function disableEventLoopMonitoring(): void;
5
+ export declare function isVerboseHealthRequest(ctx: RequestContext): boolean;
5
6
  export declare function shouldHandleHealthRoute(ctx: RequestContext): boolean;
6
7
  export declare function sendHealthRouteResponse(store: SessionStore, ctx: RequestContext, authPresent: boolean): boolean;
7
8
  //# sourceMappingURL=health.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/http/health.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,cAAc,CAAC;AAY7D,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAiMD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAEpE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,OAAO,GACnB,OAAO,CAOT"}
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/http/health.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,cAAc,CAAC;AAY7D,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AA2JD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAKnE;AAiCD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAEpE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,OAAO,GACnB,OAAO,CAOT"}
@@ -103,7 +103,7 @@ function sendHealth(store, res, includeDiagnostics) {
103
103
  // ---------------------------------------------------------------------------
104
104
  // Health route helpers
105
105
  // ---------------------------------------------------------------------------
106
- function isVerboseHealthRequest(ctx) {
106
+ export function isVerboseHealthRequest(ctx) {
107
107
  const value = ctx.url.searchParams.get('verbose');
108
108
  if (!value)
109
109
  return false;
@@ -36,6 +36,12 @@ export declare function closeTransportBestEffort(transport: {
36
36
  }, context: string): Promise<void>;
37
37
  export declare function closeMcpServerBestEffort(server: McpServer, context: string): Promise<void>;
38
38
  export declare function createTransportAdapter(transportImpl: StreamableHTTPServerTransport): Transport;
39
+ export type JsonBodyErrorKind = 'payload-too-large' | 'invalid-json' | 'read-failed';
40
+ export declare class JsonBodyError extends Error {
41
+ readonly kind: JsonBodyErrorKind;
42
+ constructor(kind: JsonBodyErrorKind, message: string);
43
+ }
44
+ export declare function isJsonBodyError(error: unknown): error is JsonBodyError;
39
45
  export declare const DEFAULT_BODY_LIMIT_BYTES: number;
40
46
  declare class JsonBodyReader {
41
47
  read(req: IncomingMessage, limit?: number, signal?: AbortSignal): Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"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"}
@@ -240,7 +240,7 @@ export function createTransportAdapter(transportImpl) {
240
240
  },
241
241
  };
242
242
  }
243
- class JsonBodyError extends Error {
243
+ export class JsonBodyError extends Error {
244
244
  kind;
245
245
  constructor(kind, message) {
246
246
  super(message);
@@ -248,6 +248,9 @@ class JsonBodyError extends Error {
248
248
  this.kind = kind;
249
249
  }
250
250
  }
251
+ export function isJsonBodyError(error) {
252
+ return error instanceof JsonBodyError;
253
+ }
251
254
  export const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
252
255
  function isRequestReadAborted(req) {
253
256
  return req.destroyed && !req.complete;
@@ -261,7 +264,6 @@ class JsonBodyReader {
261
264
  if (contentLengthHeader) {
262
265
  const contentLength = Number.parseInt(contentLengthHeader, 10);
263
266
  if (Number.isFinite(contentLength) && contentLength > limit) {
264
- destroyRequestBestEffort(req);
265
267
  throw new JsonBodyError('payload-too-large', 'Payload too large');
266
268
  }
267
269
  }
@@ -327,7 +329,6 @@ class JsonBodyReader {
327
329
  const buf = this.normalizeChunk(chunk);
328
330
  size += buf.length;
329
331
  if (size > limit) {
330
- destroyRequestBestEffort(req);
331
332
  callback(new JsonBodyError('payload-too-large', 'Payload too large'));
332
333
  return;
333
334
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AAq3BA,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CA0DD"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AA69BA,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CA0DD"}
@@ -15,15 +15,11 @@ import { toError } from '../lib/utils.js';
15
15
  import { applyHttpServerTuning, drainConnectionsOnShutdown, } from '../lib/utils.js';
16
16
  import { isObject } from '../lib/utils.js';
17
17
  import { createMcpServerForHttpSession } from '../server.js';
18
- import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
19
- import { disableEventLoopMonitoring, resetEventLoopMonitoring, sendHealthRouteResponse, shouldHandleHealthRoute, } from './health.js';
20
- import { buildRequestContext, closeMcpServerBestEffort, closeTransportBestEffort, createRequestAbortSignal, createTransportAdapter, DEFAULT_BODY_LIMIT_BYTES, drainRequest, findDuplicateSingleValueHeader, getHeaderValue, getMcpSessionId, jsonBodyReader, registerInboundBlockList, sendEmpty, sendError, sendJson, } from './helpers.js';
18
+ import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, DEFAULT_MCP_PROTOCOL_VERSION, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
19
+ import { disableEventLoopMonitoring, isVerboseHealthRequest, resetEventLoopMonitoring, sendHealthRouteResponse, shouldHandleHealthRoute, } from './health.js';
20
+ import { buildRequestContext, closeMcpServerBestEffort, closeTransportBestEffort, createRequestAbortSignal, createTransportAdapter, DEFAULT_BODY_LIMIT_BYTES, drainRequest, findDuplicateSingleValueHeader, getHeaderValue, getMcpSessionId, isJsonBodyError, jsonBodyReader, registerInboundBlockList, sendEmpty, sendError, sendJson, } from './helpers.js';
21
21
  import { createRateLimitManagerImpl, } from './rate-limit.js';
22
22
  import { teardownSessionRegistration, teardownSessionResources, } from './session-teardown.js';
23
- // ---------------------------------------------------------------------------
24
- // MCP session gateway
25
- // ---------------------------------------------------------------------------
26
- const DEFAULT_MCP_PROTOCOL_VERSION = '2025-11-25';
27
23
  function resolveRequestedProtocolVersion(body) {
28
24
  if (!isObject(body))
29
25
  return DEFAULT_MCP_PROTOCOL_VERSION;
@@ -57,6 +53,7 @@ function isPingRequest(method) {
57
53
  class McpSessionGateway {
58
54
  store;
59
55
  createSessionServer;
56
+ sessionInitTimeouts = new Map();
60
57
  constructor(store, createSessionServer) {
61
58
  this.store = store;
62
59
  this.createSessionServer = createSessionServer;
@@ -89,28 +86,15 @@ class McpSessionGateway {
89
86
  const session = sessionId
90
87
  ? this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res, requestId)
91
88
  : undefined;
92
- if (sessionId && !session)
93
- return;
94
- if (!session && isInitNotification) {
95
- sendError(ctx.res, -32600, 'Missing session ID', 400, requestId);
89
+ if (!this.ensurePostSessionAccess({
90
+ ctx,
91
+ sessionId,
92
+ session,
93
+ requestId,
94
+ method,
95
+ isInitNotification,
96
+ }))
96
97
  return;
97
- }
98
- if (session) {
99
- if (!this.ensureSessionProtocolVersion(ctx, session))
100
- return;
101
- if (!session.protocolInitialized) {
102
- const isPing = method !== null && isPingRequest(method);
103
- if (!isInitNotification && !isPing) {
104
- sendError(ctx.res, -32600, 'Session not initialized', 400, requestId);
105
- return;
106
- }
107
- }
108
- }
109
- else {
110
- if (!ensureMcpProtocolVersion(ctx.req, ctx.res)) {
111
- return;
112
- }
113
- }
114
98
  if (session && isInitNotification) {
115
99
  this.markSessionInitialized(sessionId, session);
116
100
  sendEmpty(ctx.res, 202);
@@ -127,14 +111,12 @@ class McpSessionGateway {
127
111
  await transport.handleRequest(ctx.req, ctx.res, body);
128
112
  }
129
113
  async handleGet(ctx) {
130
- const sessionId = this.getRequiredSessionId(ctx.req, ctx.res);
131
- if (!sessionId)
132
- return;
133
- const session = this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res);
134
- if (!session)
135
- return;
136
- if (!this.ensureSessionProtocolVersion(ctx, session))
114
+ const sessionState = this.getRequiredAuthenticatedSession(ctx, null, {
115
+ requireInitialized: true,
116
+ });
117
+ if (!sessionState)
137
118
  return;
119
+ const { sessionId, session } = sessionState;
138
120
  const acceptHeader = getHeaderValue(ctx.req, 'accept');
139
121
  if (!acceptsEventStream(acceptHeader)) {
140
122
  sendJson(ctx.res, 406, {
@@ -146,28 +128,54 @@ class McpSessionGateway {
146
128
  await session.transport.handleRequest(ctx.req, ctx.res);
147
129
  }
148
130
  async handleDelete(ctx) {
149
- const sessionId = this.getRequiredSessionId(ctx.req, ctx.res);
150
- if (!sessionId)
151
- return;
152
- const session = this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res);
153
- if (!session)
154
- return;
155
- if (!this.ensureSessionProtocolVersion(ctx, session))
131
+ const sessionState = this.getRequiredAuthenticatedSession(ctx, null, {
132
+ requireInitialized: true,
133
+ });
134
+ if (!sessionState)
156
135
  return;
136
+ const { sessionId, session } = sessionState;
157
137
  await session.transport.close();
158
138
  this.cleanupSessionRecord(sessionId, 'session-delete');
159
139
  sendJson(ctx.res, 200, { status: 'closed' });
160
140
  }
141
+ ensurePostSessionAccess(params) {
142
+ const { ctx, sessionId, session, requestId, method, isInitNotification } = params;
143
+ if (sessionId && !session)
144
+ return false;
145
+ if (!session) {
146
+ if (isInitNotification) {
147
+ sendError(ctx.res, -32600, 'Missing session ID', 400, requestId);
148
+ return false;
149
+ }
150
+ return ensureMcpProtocolVersion(ctx.req, ctx.res);
151
+ }
152
+ if (!this.ensureSessionProtocolVersion(ctx, session))
153
+ return false;
154
+ if (session.protocolInitialized)
155
+ return true;
156
+ if (isInitNotification)
157
+ return true;
158
+ if (method !== null && isPingRequest(method))
159
+ return true;
160
+ sendError(ctx.res, -32600, 'Session not initialized', 400, requestId);
161
+ return false;
162
+ }
161
163
  async getOrCreateTransport(ctx, requestId) {
162
164
  const sessionId = getMcpSessionId(ctx.req);
163
165
  if (sessionId) {
164
166
  const fingerprint = buildAuthFingerprint(ctx.auth);
165
167
  return this.getExistingTransport(sessionId, fingerprint, ctx.res, requestId);
166
168
  }
167
- if (!isMcpRequestBody(ctx.body) || !isInitializeRequest(ctx.body)) {
169
+ if (!isMcpRequestBody(ctx.body)) {
168
170
  sendError(ctx.res, -32600, 'Missing session ID', 400, requestId);
169
171
  return null;
170
172
  }
173
+ if (!isInitializeRequest(ctx.body)) {
174
+ sendError(ctx.res, ctx.body.method === 'initialize' ? -32602 : -32600, ctx.body.method === 'initialize'
175
+ ? 'Invalid initialize request'
176
+ : 'Missing session ID', 400, requestId);
177
+ return null;
178
+ }
171
179
  const negotiatedProtocolVersion = resolveRequestedProtocolVersion(ctx.body);
172
180
  if (!negotiatedProtocolVersion) {
173
181
  sendError(ctx.res, -32602, `Unsupported protocolVersion; supported versions: ${[...SUPPORTED_MCP_PROTOCOL_VERSIONS].join(', ')}`, 400, requestId);
@@ -188,6 +196,21 @@ class McpSessionGateway {
188
196
  this.store.touch(sessionId);
189
197
  return session.transport;
190
198
  }
199
+ getRequiredAuthenticatedSession(ctx, requestId = null, options) {
200
+ const sessionId = this.getRequiredSessionId(ctx.req, ctx.res, requestId);
201
+ if (!sessionId)
202
+ return null;
203
+ const session = this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res, requestId);
204
+ if (!session)
205
+ return null;
206
+ if (!this.ensureSessionProtocolVersion(ctx, session))
207
+ return null;
208
+ if (options?.requireInitialized && !session.protocolInitialized) {
209
+ sendError(ctx.res, -32600, 'Session not initialized', 400, requestId);
210
+ return null;
211
+ }
212
+ return { sessionId, session };
213
+ }
191
214
  getRequiredSessionId(req, res, requestId = null) {
192
215
  const sessionId = getMcpSessionId(req);
193
216
  if (sessionId)
@@ -219,6 +242,7 @@ class McpSessionGateway {
219
242
  if (!session.protocolInitialized) {
220
243
  session.protocolInitialized = true;
221
244
  }
245
+ this.clearSessionInitTimeout(sessionId);
222
246
  if (sessionId)
223
247
  this.store.touch(sessionId);
224
248
  }
@@ -244,17 +268,24 @@ class McpSessionGateway {
244
268
  sessionIdGenerator: () => newSessionId,
245
269
  });
246
270
  const initTimeout = setTimeout(() => {
247
- if (!tracker.isInitialized()) {
248
- tracker.releaseSlot();
249
- void closeTransportBestEffort(transportImpl, 'session-init-timeout');
250
- void closeMcpServerBestEffort(sessionServer, 'session-init-timeout');
271
+ const session = this.store.get(newSessionId);
272
+ if (session) {
273
+ if (session.protocolInitialized) {
274
+ this.clearSessionInitTimeout(newSessionId);
275
+ return;
276
+ }
277
+ this.cleanupSessionRecord(newSessionId, 'session-init-timeout');
278
+ return;
251
279
  }
280
+ tracker.releaseSlot();
281
+ void closeTransportBestEffort(transportImpl, 'session-init-timeout');
282
+ void closeMcpServerBestEffort(sessionServer, 'session-init-timeout');
252
283
  }, config.server.sessionInitTimeoutMs);
253
284
  initTimeout.unref();
254
285
  transportImpl.onclose = () => {
255
286
  clearTimeout(initTimeout);
256
- if (!tracker.isInitialized())
257
- tracker.releaseSlot();
287
+ this.sessionInitTimeouts.delete(newSessionId);
288
+ tracker.releaseSlot();
258
289
  };
259
290
  try {
260
291
  const transport = createTransportAdapter(transportImpl);
@@ -267,7 +298,6 @@ class McpSessionGateway {
267
298
  void closeMcpServerBestEffort(sessionServer, 'session-connect-failed');
268
299
  throw err;
269
300
  }
270
- tracker.markInitialized();
271
301
  tracker.releaseSlot();
272
302
  this.store.set(newSessionId, {
273
303
  server: sessionServer,
@@ -278,6 +308,7 @@ class McpSessionGateway {
278
308
  negotiatedProtocolVersion,
279
309
  authFingerprint,
280
310
  });
311
+ this.sessionInitTimeouts.set(newSessionId, initTimeout);
281
312
  registerMcpSessionServer(newSessionId, sessionServer);
282
313
  transportImpl.onclose = composeCloseHandlers(transportImpl.onclose, () => {
283
314
  this.cleanupSessionRecord(newSessionId, 'session-close');
@@ -285,6 +316,7 @@ class McpSessionGateway {
285
316
  return transportImpl;
286
317
  }
287
318
  cleanupSessionRecord(sessionId, context) {
319
+ this.clearSessionInitTimeout(sessionId);
288
320
  const session = this.store.remove(sessionId);
289
321
  if (!session)
290
322
  return;
@@ -293,6 +325,15 @@ class McpSessionGateway {
293
325
  closeServerReason: `${context}-server`,
294
326
  });
295
327
  }
328
+ clearSessionInitTimeout(sessionId) {
329
+ if (!sessionId)
330
+ return;
331
+ const timeout = this.sessionInitTimeouts.get(sessionId);
332
+ if (!timeout)
333
+ return;
334
+ clearTimeout(timeout);
335
+ this.sessionInitTimeouts.delete(sessionId);
336
+ }
296
337
  reserveCapacity(res, requestId) {
297
338
  const allowed = ensureSessionCapacity({
298
339
  store: this.store,
@@ -439,19 +480,6 @@ class HttpDispatcher {
439
480
  }
440
481
  }
441
482
  }
442
- // ---------------------------------------------------------------------------
443
- // Verbose health helper (local to dispatcher)
444
- // ---------------------------------------------------------------------------
445
- function isVerboseHealthRequest(ctx) {
446
- const value = ctx.url.searchParams.get('verbose');
447
- if (!value)
448
- return false;
449
- const normalized = value.trim().toLowerCase();
450
- return normalized === '1' || normalized === 'true';
451
- }
452
- // ---------------------------------------------------------------------------
453
- // Request pipeline
454
- // ---------------------------------------------------------------------------
455
483
  class HttpRequestPipeline {
456
484
  rateLimiter;
457
485
  dispatcher;
@@ -469,59 +497,15 @@ class HttpRequestPipeline {
469
497
  operationId: requestId,
470
498
  ...(sessionId ? { sessionId } : {}),
471
499
  }, async () => {
472
- const duplicateHeader = findDuplicateSingleValueHeader(rawReq);
473
- if (duplicateHeader) {
474
- sendJson(rawRes, 400, {
475
- error: `Duplicate ${duplicateHeader} header is not allowed`,
476
- });
477
- drainRequest(rawReq);
500
+ if (this.rejectDuplicateHeaders(rawReq, rawRes))
478
501
  return;
479
- }
480
- const ctx = buildRequestContext(rawReq, rawRes, signal);
481
- if (!ctx) {
482
- drainRequest(rawReq);
502
+ const ctx = this.buildContext(rawReq, rawRes, signal);
503
+ if (!ctx)
483
504
  return;
484
- }
485
- if (!hostOriginPolicy.validate(ctx)) {
486
- drainRequest(rawReq);
487
- return;
488
- }
489
- if (corsPolicy.handle(ctx)) {
490
- drainRequest(rawReq);
505
+ if (!this.applyRequestGuards(ctx, rawReq))
491
506
  return;
492
- }
493
- if (!this.rateLimiter.check(ctx)) {
494
- drainRequest(rawReq);
507
+ if (!(await this.populateRequestBody(ctx, rawReq)))
495
508
  return;
496
- }
497
- if (ctx.method === 'POST') {
498
- try {
499
- ctx.body = await jsonBodyReader.read(ctx.req, DEFAULT_BODY_LIMIT_BYTES, ctx.signal);
500
- }
501
- catch {
502
- if (ctx.url.pathname === '/mcp') {
503
- sendError(ctx.res, -32700, 'Parse error', 400, null);
504
- }
505
- else {
506
- sendJson(ctx.res, 400, {
507
- error: 'Invalid JSON or Payload too large',
508
- });
509
- }
510
- drainRequest(rawReq);
511
- return;
512
- }
513
- }
514
- else {
515
- const contentLengthHeader = getHeaderValue(rawReq, 'content-length');
516
- const transferEncodingHeader = getHeaderValue(rawReq, 'transfer-encoding');
517
- const hasRequestBody = (contentLengthHeader !== null &&
518
- Number.parseInt(contentLengthHeader, 10) > 0) ||
519
- transferEncodingHeader !== null;
520
- if (hasRequestBody) {
521
- drainRequest(rawReq);
522
- }
523
- ctx.body = undefined;
524
- }
525
509
  await this.dispatcher.dispatch(ctx);
526
510
  });
527
511
  }
@@ -529,6 +513,87 @@ class HttpRequestPipeline {
529
513
  cleanup();
530
514
  }
531
515
  }
516
+ rejectDuplicateHeaders(rawReq, rawRes) {
517
+ const duplicateHeader = findDuplicateSingleValueHeader(rawReq);
518
+ if (!duplicateHeader)
519
+ return false;
520
+ sendJson(rawRes, 400, {
521
+ error: `Duplicate ${duplicateHeader} header is not allowed`,
522
+ });
523
+ drainRequest(rawReq);
524
+ return true;
525
+ }
526
+ buildContext(rawReq, rawRes, signal) {
527
+ const ctx = buildRequestContext(rawReq, rawRes, signal);
528
+ if (ctx)
529
+ return ctx;
530
+ drainRequest(rawReq);
531
+ return null;
532
+ }
533
+ applyRequestGuards(ctx, rawReq) {
534
+ if (!hostOriginPolicy.validate(ctx)) {
535
+ drainRequest(rawReq);
536
+ return false;
537
+ }
538
+ if (corsPolicy.handle(ctx)) {
539
+ drainRequest(rawReq);
540
+ return false;
541
+ }
542
+ if (!this.rateLimiter.check(ctx)) {
543
+ drainRequest(rawReq);
544
+ return false;
545
+ }
546
+ return true;
547
+ }
548
+ async populateRequestBody(ctx, rawReq) {
549
+ if (ctx.method !== 'POST') {
550
+ this.clearUnexpectedRequestBody(ctx, rawReq);
551
+ return true;
552
+ }
553
+ try {
554
+ ctx.body = await jsonBodyReader.read(ctx.req, DEFAULT_BODY_LIMIT_BYTES, ctx.signal);
555
+ return true;
556
+ }
557
+ catch (error) {
558
+ const bodyErrorKind = isJsonBodyError(error) ? error.kind : null;
559
+ if (ctx.url.pathname === '/mcp') {
560
+ switch (bodyErrorKind) {
561
+ case 'payload-too-large':
562
+ sendError(ctx.res, -32600, 'Request body too large', 413, null);
563
+ break;
564
+ case 'read-failed':
565
+ if (!rawReq.destroyed) {
566
+ sendError(ctx.res, -32600, 'Request body read failed', 400, null);
567
+ }
568
+ break;
569
+ case 'invalid-json':
570
+ default:
571
+ sendError(ctx.res, -32700, 'Parse error', 400, null);
572
+ break;
573
+ }
574
+ }
575
+ else {
576
+ sendJson(ctx.res, bodyErrorKind === 'payload-too-large' ? 413 : 400, {
577
+ error: bodyErrorKind === 'payload-too-large'
578
+ ? 'Payload too large'
579
+ : 'Invalid JSON',
580
+ });
581
+ }
582
+ drainRequest(rawReq);
583
+ return false;
584
+ }
585
+ }
586
+ clearUnexpectedRequestBody(ctx, rawReq) {
587
+ const contentLengthHeader = getHeaderValue(rawReq, 'content-length');
588
+ const transferEncodingHeader = getHeaderValue(rawReq, 'transfer-encoding');
589
+ const hasRequestBody = (contentLengthHeader !== null &&
590
+ Number.parseInt(contentLengthHeader, 10) > 0) ||
591
+ transferEncodingHeader !== null;
592
+ if (hasRequestBody) {
593
+ drainRequest(rawReq);
594
+ }
595
+ ctx.body = undefined;
596
+ }
532
597
  }
533
598
  // ---------------------------------------------------------------------------
534
599
  // Server bootstrap
@@ -1,5 +1,6 @@
1
1
  import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { type StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
+ import type { SessionEntry } from './session.js';
3
+ import type { SessionStore } from './session.js';
3
4
  export declare const serverVersion: string;
4
5
  type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5
6
  type McpLogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
@@ -213,46 +214,10 @@ export declare function logError(message: string, error?: Error | LogMetadata):
213
214
  export declare function getMcpLogLevel(sessionId?: string): McpLogLevel;
214
215
  export declare function setLogLevel(level: string, sessionId?: string): void;
215
216
  export declare function redactUrl(rawUrl: string): string;
216
- export interface SessionEntry {
217
- readonly server: McpServer;
218
- readonly transport: StreamableHTTPServerTransport;
219
- createdAt: number;
220
- lastSeen: number;
221
- protocolInitialized: boolean;
222
- negotiatedProtocolVersion: string;
223
- authFingerprint: string;
224
- }
225
- export interface SessionStore {
226
- get: (sessionId: string) => SessionEntry | undefined;
227
- touch: (sessionId: string) => void;
228
- set: (sessionId: string, entry: SessionEntry) => void;
229
- remove: (sessionId: string) => SessionEntry | undefined;
230
- size: () => number;
231
- inFlight: () => number;
232
- incrementInFlight: () => void;
233
- decrementInFlight: () => void;
234
- clear: () => SessionEntry[];
235
- evictExpired: () => SessionEntry[];
236
- evictOldest: () => SessionEntry | undefined;
237
- }
238
- interface SlotTracker {
239
- readonly releaseSlot: () => void;
240
- readonly markInitialized: () => void;
241
- readonly isInitialized: () => boolean;
242
- }
243
- type CloseHandler = (() => void) | undefined;
244
- export declare function composeCloseHandlers(first: CloseHandler, second: CloseHandler): CloseHandler;
217
+ export type { SessionEntry, SessionStore } from './session.js';
218
+ export { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, reserveSessionSlot, } from './session.js';
245
219
  export declare function startSessionCleanupLoop(store: SessionStore, sessionTtlMs: number, options?: {
246
220
  onEvictSession?: (session: SessionEntry) => Promise<void> | void;
247
221
  cleanupIntervalMs?: number;
248
222
  }): AbortController;
249
- export declare function createSessionStore(sessionTtlMs: number): SessionStore;
250
- export declare function createSlotTracker(store: SessionStore): SlotTracker;
251
- export declare function reserveSessionSlot(store: SessionStore, maxSessions: number): boolean;
252
- export declare function ensureSessionCapacity({ store, maxSessions, evictOldest, }: {
253
- store: SessionStore;
254
- maxSessions: number;
255
- evictOldest: (store: SessionStore) => boolean;
256
- }): boolean;
257
- export {};
258
223
  //# sourceMappingURL=core.d.ts.map