@j0hanz/fetch-url-mcp 1.4.0 → 1.5.0

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 (215) hide show
  1. package/dist/cli.d.ts +2 -3
  2. package/dist/cli.js +1 -2
  3. package/dist/http/auth.d.ts +5 -3
  4. package/dist/http/auth.js +64 -15
  5. package/dist/http/health.d.ts +1 -2
  6. package/dist/http/health.js +7 -18
  7. package/dist/http/helpers.d.ts +3 -4
  8. package/dist/http/helpers.js +21 -21
  9. package/dist/http/native.d.ts +0 -1
  10. package/dist/http/native.js +34 -26
  11. package/dist/http/rate-limit.d.ts +0 -1
  12. package/dist/http/rate-limit.js +3 -4
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.js +17 -18
  15. package/dist/lib/{markdown-cleanup.d.ts → content.d.ts} +4 -2
  16. package/dist/lib/content.js +1356 -0
  17. package/dist/lib/core.d.ts +253 -0
  18. package/dist/lib/core.js +1228 -0
  19. package/dist/lib/{tool-pipeline.d.ts → fetch-pipeline.d.ts} +1 -2
  20. package/dist/lib/{tool-pipeline.js → fetch-pipeline.js} +10 -19
  21. package/dist/lib/{fetch.d.ts → http.d.ts} +7 -9
  22. package/dist/lib/{fetch.js → http.js} +706 -944
  23. package/dist/lib/mcp-tools.d.ts +28 -0
  24. package/dist/lib/mcp-tools.js +107 -0
  25. package/dist/lib/{tool-progress.d.ts → progress.d.ts} +0 -1
  26. package/dist/lib/{tool-progress.js → progress.js} +8 -13
  27. package/dist/lib/task-handlers.d.ts +5 -0
  28. package/dist/lib/{mcp.js → task-handlers.js} +56 -12
  29. package/dist/lib/url.d.ts +70 -0
  30. package/dist/lib/url.js +686 -0
  31. package/dist/lib/utils.d.ts +58 -0
  32. package/dist/lib/utils.js +304 -0
  33. package/dist/prompts/index.d.ts +0 -1
  34. package/dist/prompts/index.js +0 -1
  35. package/dist/resources/index.d.ts +0 -1
  36. package/dist/resources/index.js +74 -33
  37. package/dist/resources/instructions.d.ts +0 -1
  38. package/dist/resources/instructions.js +2 -2
  39. package/dist/schemas/inputs.d.ts +0 -1
  40. package/dist/schemas/inputs.js +2 -3
  41. package/dist/schemas/outputs.d.ts +0 -1
  42. package/dist/schemas/outputs.js +1 -2
  43. package/dist/server.d.ts +0 -1
  44. package/dist/server.js +16 -26
  45. package/dist/tasks/execution.d.ts +0 -1
  46. package/dist/tasks/execution.js +27 -24
  47. package/dist/tasks/manager.d.ts +7 -3
  48. package/dist/tasks/manager.js +53 -34
  49. package/dist/tasks/owner.d.ts +1 -2
  50. package/dist/tasks/owner.js +1 -2
  51. package/dist/tasks/tool-registry.d.ts +1 -2
  52. package/dist/tasks/tool-registry.js +0 -1
  53. package/dist/tools/fetch-url.d.ts +1 -2
  54. package/dist/tools/fetch-url.js +39 -31
  55. package/dist/tools/index.d.ts +0 -1
  56. package/dist/tools/index.js +0 -1
  57. package/dist/transform/html-translators.d.ts +1 -0
  58. package/dist/transform/html-translators.js +454 -0
  59. package/dist/transform/metadata.d.ts +4 -0
  60. package/dist/transform/metadata.js +183 -0
  61. package/dist/transform/transform.d.ts +0 -1
  62. package/dist/transform/transform.js +24 -641
  63. package/dist/transform/types.d.ts +9 -11
  64. package/dist/transform/types.js +0 -1
  65. package/dist/transform/worker-pool.d.ts +0 -1
  66. package/dist/transform/worker-pool.js +7 -16
  67. package/dist/transform/workers/shared.d.ts +0 -1
  68. package/dist/transform/workers/shared.js +1 -2
  69. package/dist/transform/workers/transform-child.d.ts +0 -1
  70. package/dist/transform/workers/transform-child.js +0 -1
  71. package/dist/transform/workers/transform-worker.d.ts +0 -1
  72. package/dist/transform/workers/transform-worker.js +0 -1
  73. package/package.json +6 -3
  74. package/dist/cli.d.ts.map +0 -1
  75. package/dist/cli.js.map +0 -1
  76. package/dist/http/auth.d.ts.map +0 -1
  77. package/dist/http/auth.js.map +0 -1
  78. package/dist/http/health.d.ts.map +0 -1
  79. package/dist/http/health.js.map +0 -1
  80. package/dist/http/helpers.d.ts.map +0 -1
  81. package/dist/http/helpers.js.map +0 -1
  82. package/dist/http/native.d.ts.map +0 -1
  83. package/dist/http/native.js.map +0 -1
  84. package/dist/http/rate-limit.d.ts.map +0 -1
  85. package/dist/http/rate-limit.js.map +0 -1
  86. package/dist/index.d.ts.map +0 -1
  87. package/dist/index.js.map +0 -1
  88. package/dist/lib/cache.d.ts +0 -54
  89. package/dist/lib/cache.d.ts.map +0 -1
  90. package/dist/lib/cache.js +0 -264
  91. package/dist/lib/cache.js.map +0 -1
  92. package/dist/lib/config.d.ts +0 -143
  93. package/dist/lib/config.d.ts.map +0 -1
  94. package/dist/lib/config.js +0 -476
  95. package/dist/lib/config.js.map +0 -1
  96. package/dist/lib/crypto.d.ts +0 -4
  97. package/dist/lib/crypto.d.ts.map +0 -1
  98. package/dist/lib/crypto.js +0 -56
  99. package/dist/lib/crypto.js.map +0 -1
  100. package/dist/lib/dom-noise-removal.d.ts +0 -2
  101. package/dist/lib/dom-noise-removal.d.ts.map +0 -1
  102. package/dist/lib/dom-noise-removal.js +0 -494
  103. package/dist/lib/dom-noise-removal.js.map +0 -1
  104. package/dist/lib/download.d.ts +0 -4
  105. package/dist/lib/download.d.ts.map +0 -1
  106. package/dist/lib/download.js +0 -106
  107. package/dist/lib/download.js.map +0 -1
  108. package/dist/lib/errors.d.ts +0 -14
  109. package/dist/lib/errors.d.ts.map +0 -1
  110. package/dist/lib/errors.js +0 -72
  111. package/dist/lib/errors.js.map +0 -1
  112. package/dist/lib/fetch-content.d.ts +0 -5
  113. package/dist/lib/fetch-content.d.ts.map +0 -1
  114. package/dist/lib/fetch-content.js +0 -164
  115. package/dist/lib/fetch-content.js.map +0 -1
  116. package/dist/lib/fetch-stream.d.ts +0 -5
  117. package/dist/lib/fetch-stream.d.ts.map +0 -1
  118. package/dist/lib/fetch-stream.js +0 -29
  119. package/dist/lib/fetch-stream.js.map +0 -1
  120. package/dist/lib/fetch.d.ts.map +0 -1
  121. package/dist/lib/fetch.js.map +0 -1
  122. package/dist/lib/host-normalization.d.ts +0 -2
  123. package/dist/lib/host-normalization.d.ts.map +0 -1
  124. package/dist/lib/host-normalization.js +0 -91
  125. package/dist/lib/host-normalization.js.map +0 -1
  126. package/dist/lib/ip-blocklist.d.ts +0 -9
  127. package/dist/lib/ip-blocklist.d.ts.map +0 -1
  128. package/dist/lib/ip-blocklist.js +0 -79
  129. package/dist/lib/ip-blocklist.js.map +0 -1
  130. package/dist/lib/json.d.ts +0 -2
  131. package/dist/lib/json.d.ts.map +0 -1
  132. package/dist/lib/json.js +0 -45
  133. package/dist/lib/json.js.map +0 -1
  134. package/dist/lib/language-detection.d.ts +0 -3
  135. package/dist/lib/language-detection.d.ts.map +0 -1
  136. package/dist/lib/language-detection.js +0 -355
  137. package/dist/lib/language-detection.js.map +0 -1
  138. package/dist/lib/markdown-cleanup.d.ts.map +0 -1
  139. package/dist/lib/markdown-cleanup.js +0 -532
  140. package/dist/lib/markdown-cleanup.js.map +0 -1
  141. package/dist/lib/mcp-lifecycle.d.ts +0 -5
  142. package/dist/lib/mcp-lifecycle.d.ts.map +0 -1
  143. package/dist/lib/mcp-lifecycle.js +0 -51
  144. package/dist/lib/mcp-lifecycle.js.map +0 -1
  145. package/dist/lib/mcp-validator.d.ts +0 -17
  146. package/dist/lib/mcp-validator.d.ts.map +0 -1
  147. package/dist/lib/mcp-validator.js +0 -45
  148. package/dist/lib/mcp-validator.js.map +0 -1
  149. package/dist/lib/mcp.d.ts +0 -4
  150. package/dist/lib/mcp.d.ts.map +0 -1
  151. package/dist/lib/mcp.js.map +0 -1
  152. package/dist/lib/observability.d.ts +0 -23
  153. package/dist/lib/observability.d.ts.map +0 -1
  154. package/dist/lib/observability.js +0 -238
  155. package/dist/lib/observability.js.map +0 -1
  156. package/dist/lib/server-tuning.d.ts +0 -15
  157. package/dist/lib/server-tuning.d.ts.map +0 -1
  158. package/dist/lib/server-tuning.js +0 -49
  159. package/dist/lib/server-tuning.js.map +0 -1
  160. package/dist/lib/session.d.ts +0 -45
  161. package/dist/lib/session.d.ts.map +0 -1
  162. package/dist/lib/session.js +0 -263
  163. package/dist/lib/session.js.map +0 -1
  164. package/dist/lib/timer-utils.d.ts +0 -13
  165. package/dist/lib/timer-utils.d.ts.map +0 -1
  166. package/dist/lib/timer-utils.js +0 -44
  167. package/dist/lib/timer-utils.js.map +0 -1
  168. package/dist/lib/tool-errors.d.ts +0 -12
  169. package/dist/lib/tool-errors.d.ts.map +0 -1
  170. package/dist/lib/tool-errors.js +0 -55
  171. package/dist/lib/tool-errors.js.map +0 -1
  172. package/dist/lib/tool-pipeline.d.ts.map +0 -1
  173. package/dist/lib/tool-pipeline.js.map +0 -1
  174. package/dist/lib/tool-progress.d.ts.map +0 -1
  175. package/dist/lib/tool-progress.js.map +0 -1
  176. package/dist/lib/type-guards.d.ts +0 -16
  177. package/dist/lib/type-guards.d.ts.map +0 -1
  178. package/dist/lib/type-guards.js +0 -13
  179. package/dist/lib/type-guards.js.map +0 -1
  180. package/dist/prompts/index.d.ts.map +0 -1
  181. package/dist/prompts/index.js.map +0 -1
  182. package/dist/resources/index.d.ts.map +0 -1
  183. package/dist/resources/index.js.map +0 -1
  184. package/dist/resources/instructions.d.ts.map +0 -1
  185. package/dist/resources/instructions.js.map +0 -1
  186. package/dist/schemas/inputs.d.ts.map +0 -1
  187. package/dist/schemas/inputs.js.map +0 -1
  188. package/dist/schemas/outputs.d.ts.map +0 -1
  189. package/dist/schemas/outputs.js.map +0 -1
  190. package/dist/server.d.ts.map +0 -1
  191. package/dist/server.js.map +0 -1
  192. package/dist/tasks/execution.d.ts.map +0 -1
  193. package/dist/tasks/execution.js.map +0 -1
  194. package/dist/tasks/manager.d.ts.map +0 -1
  195. package/dist/tasks/manager.js.map +0 -1
  196. package/dist/tasks/owner.d.ts.map +0 -1
  197. package/dist/tasks/owner.js.map +0 -1
  198. package/dist/tasks/tool-registry.d.ts.map +0 -1
  199. package/dist/tasks/tool-registry.js.map +0 -1
  200. package/dist/tools/fetch-url.d.ts.map +0 -1
  201. package/dist/tools/fetch-url.js.map +0 -1
  202. package/dist/tools/index.d.ts.map +0 -1
  203. package/dist/tools/index.js.map +0 -1
  204. package/dist/transform/transform.d.ts.map +0 -1
  205. package/dist/transform/transform.js.map +0 -1
  206. package/dist/transform/types.d.ts.map +0 -1
  207. package/dist/transform/types.js.map +0 -1
  208. package/dist/transform/worker-pool.d.ts.map +0 -1
  209. package/dist/transform/worker-pool.js.map +0 -1
  210. package/dist/transform/workers/shared.d.ts.map +0 -1
  211. package/dist/transform/workers/shared.js.map +0 -1
  212. package/dist/transform/workers/transform-child.d.ts.map +0 -1
  213. package/dist/transform/workers/transform-child.js.map +0 -1
  214. package/dist/transform/workers/transform-worker.d.ts.map +0 -1
  215. package/dist/transform/workers/transform-worker.js.map +0 -1
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export interface CliValues {
1
+ interface CliValues {
2
2
  readonly stdio: boolean;
3
3
  readonly http: boolean;
4
4
  readonly help: boolean;
@@ -12,8 +12,7 @@ interface CliParseFailure {
12
12
  readonly ok: false;
13
13
  readonly message: string;
14
14
  }
15
- export type CliParseResult = CliParseSuccess | CliParseFailure;
15
+ type CliParseResult = CliParseSuccess | CliParseFailure;
16
16
  export declare function renderCliUsage(): string;
17
17
  export declare function parseCliArgs(args: readonly string[]): CliParseResult;
18
18
  export {};
19
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from 'node:util';
2
- import { getErrorMessage } from './lib/errors.js';
2
+ import { getErrorMessage } from './lib/utils.js';
3
3
  const usageLines = [
4
4
  'Fetch URL MCP server',
5
5
  '',
@@ -63,4 +63,3 @@ export function parseCliArgs(args) {
63
63
  };
64
64
  }
65
65
  }
66
- //# sourceMappingURL=cli.js.map
@@ -1,5 +1,5 @@
1
- import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
2
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
3
3
  import { type RequestContext } from './helpers.js';
4
4
  declare class CorsPolicy {
5
5
  handle(ctx: RequestContext): boolean;
@@ -8,7 +8,9 @@ export declare const corsPolicy: CorsPolicy;
8
8
  declare class HostOriginPolicy {
9
9
  validate(ctx: RequestContext): boolean;
10
10
  private resolveHostHeader;
11
- private resolveOriginHost;
11
+ private resolveRequestOrigin;
12
+ private resolveOrigin;
13
+ private defaultPortForScheme;
12
14
  private reject;
13
15
  }
14
16
  export declare const hostOriginPolicy: HostOriginPolicy;
@@ -33,6 +35,7 @@ declare class AuthService {
33
35
  private buildIntrospectionRequest;
34
36
  private requestIntrospection;
35
37
  private buildIntrospectionAuthInfo;
38
+ private assertRequiredScopes;
36
39
  private verifyWithIntrospection;
37
40
  }
38
41
  export declare function applyUnauthorizedAuthHeaders(req: IncomingMessage, res: ServerResponse): void;
@@ -46,4 +49,3 @@ export declare function buildProtectedResourceMetadataDocument(req: IncomingMess
46
49
  export declare function isProtectedResourceMetadataPath(pathname: string): boolean;
47
50
  export declare const authService: AuthService;
48
51
  export {};
49
- //# sourceMappingURL=auth.d.ts.map
package/dist/http/auth.js CHANGED
@@ -1,10 +1,10 @@
1
- import { InvalidTokenError, ServerError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
2
1
  import { Buffer } from 'node:buffer';
3
2
  import { randomBytes } from 'node:crypto';
4
- import { config } from '../lib/config.js';
5
- import { hmacSha256Hex, timingSafeEqualUtf8 } from '../lib/crypto.js';
6
- import { normalizeHost } from '../lib/host-normalization.js';
7
- import { isObject } from '../lib/type-guards.js';
3
+ import { InvalidTokenError, ServerError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
4
+ import { config } from '../lib/core.js';
5
+ import { normalizeHost } from '../lib/url.js';
6
+ import { hmacSha256Hex, timingSafeEqualUtf8 } from '../lib/utils.js';
7
+ import { isObject } from '../lib/utils.js';
8
8
  import { getHeaderValue, sendEmpty, sendError, sendJson, } from './helpers.js';
9
9
  // ---------------------------------------------------------------------------
10
10
  // CORS
@@ -20,9 +20,6 @@ class CorsPolicy {
20
20
  res.setHeader('Access-Control-Allow-Origin', origin);
21
21
  res.setHeader('Vary', 'Origin');
22
22
  }
23
- else {
24
- res.setHeader('Access-Control-Allow-Origin', '*');
25
- }
26
23
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE');
27
24
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, MCP-Protocol-Version, MCP-Session-ID, X-MCP-Session-ID, Last-Event-ID');
28
25
  res.setHeader('Access-Control-Expose-Headers', 'MCP-Session-ID, X-MCP-Session-ID, MCP-Protocol-Version, WWW-Authenticate');
@@ -77,10 +74,16 @@ class HostOriginPolicy {
77
74
  const originHeader = getHeaderValue(req, 'origin');
78
75
  if (!originHeader)
79
76
  return true;
80
- const originHost = this.resolveOriginHost(originHeader);
81
- if (!originHost)
77
+ const requestOrigin = this.resolveRequestOrigin(req);
78
+ const origin = this.resolveOrigin(originHeader);
79
+ if (!requestOrigin || !origin)
82
80
  return this.reject(res, 403, 'Invalid Origin header');
83
- if (!ALLOWED_HOSTS.has(originHost))
81
+ if (!ALLOWED_HOSTS.has(origin.host))
82
+ return this.reject(res, 403, 'Origin not allowed');
83
+ const isSameOrigin = requestOrigin.scheme === origin.scheme &&
84
+ requestOrigin.host === origin.host &&
85
+ requestOrigin.port === origin.port;
86
+ if (!isSameOrigin)
84
87
  return this.reject(res, 403, 'Origin not allowed');
85
88
  return true;
86
89
  }
@@ -90,17 +93,52 @@ class HostOriginPolicy {
90
93
  return null;
91
94
  return normalizeHost(host);
92
95
  }
93
- resolveOriginHost(origin) {
96
+ resolveRequestOrigin(req) {
97
+ const hostHeader = getHeaderValue(req, 'host');
98
+ if (!hostHeader)
99
+ return null;
100
+ const isEncrypted = req.socket.encrypted === true;
101
+ const scheme = isEncrypted ? 'https' : 'http';
102
+ try {
103
+ const parsed = new URL(`${scheme}://${hostHeader}`);
104
+ const normalizedHost = normalizeHost(parsed.host);
105
+ if (!normalizedHost)
106
+ return null;
107
+ return {
108
+ scheme,
109
+ host: normalizedHost,
110
+ port: parsed.port || this.defaultPortForScheme(scheme),
111
+ };
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ resolveOrigin(origin) {
94
118
  if (origin === 'null')
95
119
  return null;
96
120
  try {
97
121
  const parsed = new URL(origin);
98
- return normalizeHost(parsed.host);
122
+ const scheme = parsed.protocol === 'https:' ? 'https' : 'http';
123
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
124
+ return null;
125
+ }
126
+ const normalizedHost = normalizeHost(parsed.host);
127
+ if (!normalizedHost)
128
+ return null;
129
+ return {
130
+ scheme,
131
+ host: normalizedHost,
132
+ port: parsed.port || this.defaultPortForScheme(scheme),
133
+ };
99
134
  }
100
135
  catch {
101
136
  return null;
102
137
  }
103
138
  }
139
+ defaultPortForScheme(scheme) {
140
+ return scheme === 'https' ? '443' : '80';
141
+ }
104
142
  reject(res, status, message) {
105
143
  sendJson(res, status, { error: message });
106
144
  return false;
@@ -293,6 +331,16 @@ class AuthService {
293
331
  info.expiresAt = expiresAt;
294
332
  return info;
295
333
  }
334
+ assertRequiredScopes(tokenScopes) {
335
+ const { requiredScopes } = config.auth;
336
+ if (requiredScopes.length === 0)
337
+ return;
338
+ const tokenScopeSet = new Set(tokenScopes);
339
+ const missing = requiredScopes.filter((s) => !tokenScopeSet.has(s));
340
+ if (missing.length > 0) {
341
+ throw new InvalidTokenError('Insufficient scope');
342
+ }
343
+ }
296
344
  async verifyWithIntrospection(token, signal) {
297
345
  if (!config.auth.introspectionUrl) {
298
346
  throw new ServerError('Introspection not configured');
@@ -302,7 +350,9 @@ class AuthService {
302
350
  if (!isObject(payload) || payload['active'] !== true) {
303
351
  throw new InvalidTokenError('Token is inactive');
304
352
  }
305
- return this.buildIntrospectionAuthInfo(token, payload);
353
+ const info = this.buildIntrospectionAuthInfo(token, payload);
354
+ this.assertRequiredScopes(info.scopes);
355
+ return info;
306
356
  }
307
357
  }
308
358
  function resolvePublicOrigin(req) {
@@ -347,4 +397,3 @@ export function isProtectedResourceMetadataPath(pathname) {
347
397
  pathname === '/.well-known/oauth-protected-resource/mcp');
348
398
  }
349
399
  export const authService = new AuthService();
350
- //# sourceMappingURL=auth.js.map
@@ -1,7 +1,6 @@
1
- import type { SessionStore } from '../lib/session.js';
1
+ 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
5
  export declare function shouldHandleHealthRoute(ctx: RequestContext): boolean;
6
6
  export declare function sendHealthRouteResponse(store: SessionStore, ctx: RequestContext, authPresent: boolean): boolean;
7
- //# sourceMappingURL=health.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { freemem, hostname, totalmem } from 'node:os';
2
2
  import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
3
3
  import process from 'node:process';
4
- import { keys as cacheKeys } from '../lib/cache.js';
5
- import { config, serverVersion } from '../lib/config.js';
4
+ import { keys as cacheKeys } from '../lib/core.js';
5
+ import { config, serverVersion } from '../lib/core.js';
6
6
  import { getTransformPoolStats } from '../transform/transform.js';
7
7
  import { sendJson } from './helpers.js';
8
8
  // ---------------------------------------------------------------------------
@@ -110,20 +110,14 @@ function isVerboseHealthRequest(ctx) {
110
110
  const normalized = value.trim().toLowerCase();
111
111
  return normalized === '1' || normalized === 'true';
112
112
  }
113
- function isGetHealthRoute(ctx) {
113
+ function isHealthRoute(ctx) {
114
114
  return ctx.method === 'GET' && ctx.url.pathname === '/health';
115
115
  }
116
116
  function isVerboseHealthRoute(ctx) {
117
- return isGetHealthRoute(ctx) && isVerboseHealthRequest(ctx);
118
- }
119
- function isHealthRoute(ctx) {
120
- return isGetHealthRoute(ctx);
117
+ return isHealthRoute(ctx) && isVerboseHealthRequest(ctx);
121
118
  }
122
119
  function ensureHealthAuthIfNeeded(ctx, authPresent) {
123
- if (!isHealthRoute(ctx))
124
- return true;
125
- const isVerbose = isVerboseHealthRequest(ctx);
126
- if (!isVerbose)
120
+ if (!isVerboseHealthRoute(ctx))
127
121
  return true;
128
122
  if (!config.security.allowRemote)
129
123
  return true;
@@ -135,14 +129,10 @@ function ensureHealthAuthIfNeeded(ctx, authPresent) {
135
129
  return false;
136
130
  }
137
131
  function resolveHealthDiagnosticsMode(ctx, authPresent) {
138
- if (!isVerboseHealthRoute(ctx))
139
- return false;
140
- if (authPresent)
141
- return true;
142
- return !config.security.allowRemote;
132
+ return (isVerboseHealthRoute(ctx) && (authPresent || !config.security.allowRemote));
143
133
  }
144
134
  export function shouldHandleHealthRoute(ctx) {
145
- return isGetHealthRoute(ctx);
135
+ return isHealthRoute(ctx);
146
136
  }
147
137
  export function sendHealthRouteResponse(store, ctx, authPresent) {
148
138
  if (!shouldHandleHealthRoute(ctx))
@@ -153,4 +143,3 @@ export function sendHealthRouteResponse(store, ctx, authPresent) {
153
143
  sendHealth(store, ctx.res, includeDiagnostics);
154
144
  return true;
155
145
  }
156
- //# sourceMappingURL=health.js.map
@@ -1,10 +1,10 @@
1
+ import type { IncomingMessage, Server, ServerResponse } from 'node:http';
2
+ import type { Server as HttpsServer } from 'node:https';
1
3
  import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
2
4
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
5
  import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
6
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
- import type { IncomingMessage, Server, ServerResponse } from 'node:http';
6
- import type { Server as HttpsServer } from 'node:https';
7
- import type { JsonRpcId } from '../lib/mcp-validator.js';
7
+ import type { JsonRpcId } from '../lib/mcp-tools.js';
8
8
  export type NetworkServer = Server | HttpsServer;
9
9
  export interface RequestContext {
10
10
  req: IncomingMessage;
@@ -48,4 +48,3 @@ declare class JsonBodyReader {
48
48
  }
49
49
  export declare const jsonBodyReader: JsonBodyReader;
50
50
  export {};
51
- //# sourceMappingURL=helpers.d.ts.map
@@ -1,11 +1,23 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { Writable } from 'node:stream';
3
3
  import { pipeline } from 'node:stream/promises';
4
- import { config } from '../lib/config.js';
5
- import { getErrorMessage, toError } from '../lib/errors.js';
6
- import { createDefaultBlockList, normalizeIpForBlockList, } from '../lib/ip-blocklist.js';
7
- import { logWarn } from '../lib/observability.js';
8
- import { composeCloseHandlers } from '../lib/session.js';
4
+ import { config } from '../lib/core.js';
5
+ import { logWarn } from '../lib/core.js';
6
+ import { composeCloseHandlers } from '../lib/core.js';
7
+ import { createDefaultBlockList, normalizeIpForBlockList } from '../lib/url.js';
8
+ import { getErrorMessage, toError } from '../lib/utils.js';
9
+ function abortControllerBestEffort(controller) {
10
+ if (!controller.signal.aborted)
11
+ controller.abort();
12
+ }
13
+ function destroyRequestBestEffort(req) {
14
+ try {
15
+ req.destroy();
16
+ }
17
+ catch {
18
+ // Best-effort only.
19
+ }
20
+ }
9
21
  // ---------------------------------------------------------------------------
10
22
  // Response helpers
11
23
  // ---------------------------------------------------------------------------
@@ -90,8 +102,7 @@ export function createRequestAbortSignal(req) {
90
102
  const abortRequest = () => {
91
103
  if (cleanedUp)
92
104
  return;
93
- if (!controller.signal.aborted)
94
- controller.abort();
105
+ abortControllerBestEffort(controller);
95
106
  };
96
107
  if (req.destroyed) {
97
108
  abortRequest();
@@ -256,12 +267,7 @@ class JsonBodyReader {
256
267
  if (contentLengthHeader) {
257
268
  const contentLength = Number.parseInt(contentLengthHeader, 10);
258
269
  if (Number.isFinite(contentLength) && contentLength > limit) {
259
- try {
260
- req.destroy();
261
- }
262
- catch {
263
- // Best-effort only.
264
- }
270
+ destroyRequestBestEffort(req);
265
271
  throw new JsonBodyError('payload-too-large', 'Payload too large');
266
272
  }
267
273
  }
@@ -294,12 +300,7 @@ class JsonBodyReader {
294
300
  if (!signal)
295
301
  return null;
296
302
  const listener = () => {
297
- try {
298
- req.destroy();
299
- }
300
- catch {
301
- // Best-effort only.
302
- }
303
+ destroyRequestBestEffort(req);
303
304
  };
304
305
  if (signal.aborted) {
305
306
  listener();
@@ -332,7 +333,7 @@ class JsonBodyReader {
332
333
  const buf = this.normalizeChunk(chunk);
333
334
  size += buf.length;
334
335
  if (size > limit) {
335
- req.destroy();
336
+ destroyRequestBestEffort(req);
336
337
  callback(new JsonBodyError('payload-too-large', 'Payload too large'));
337
338
  return;
338
339
  }
@@ -369,4 +370,3 @@ class JsonBodyReader {
369
370
  }
370
371
  }
371
372
  export const jsonBodyReader = new JsonBodyReader();
372
- //# sourceMappingURL=helpers.js.map
@@ -3,4 +3,3 @@ export declare function startHttpServer(): Promise<{
3
3
  port: number;
4
4
  host: string;
5
5
  }>;
6
- //# sourceMappingURL=native.d.ts.map
@@ -1,20 +1,20 @@
1
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
- import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
3
1
  import { randomUUID } from 'node:crypto';
4
2
  import { readFileSync } from 'node:fs';
5
3
  import { createServer, } from 'node:http';
6
4
  import { createServer as createHttpsServer, } from 'node:https';
7
5
  import { hostname } from 'node:os';
8
6
  import process from 'node:process';
9
- import { config, enableHttpMode } from '../lib/config.js';
10
- import { handleDownload } from '../lib/download.js';
11
- import { toError } from '../lib/errors.js';
12
- import { acceptsEventStream, acceptsJsonAndEventStream, isJsonRpcBatchRequest, isMcpRequestBody, } from '../lib/mcp-validator.js';
13
- import { cancelTasksForOwner } from '../lib/mcp.js';
14
- import { logError, logInfo, registerMcpSessionServer, resolveMcpSessionIdByServer, runWithRequestContext, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/observability.js';
15
- import { applyHttpServerTuning, drainConnectionsOnShutdown, } from '../lib/server-tuning.js';
16
- import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, reserveSessionSlot, startSessionCleanupLoop, } from '../lib/session.js';
17
- import { isObject } from '../lib/type-guards.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
9
+ import { config, enableHttpMode } from '../lib/core.js';
10
+ import { logError, logInfo, registerMcpSessionServer, resolveMcpSessionIdByServer, runWithRequestContext, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
11
+ import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, reserveSessionSlot, startSessionCleanupLoop, } from '../lib/core.js';
12
+ import { handleDownload } from '../lib/http.js';
13
+ import { acceptsEventStream, acceptsJsonAndEventStream, isJsonRpcBatchRequest, isMcpRequestBody, } from '../lib/mcp-tools.js';
14
+ import { cancelTasksForOwner } from '../lib/mcp-tools.js';
15
+ import { toError } from '../lib/utils.js';
16
+ import { applyHttpServerTuning, drainConnectionsOnShutdown, } from '../lib/utils.js';
17
+ import { isObject } from '../lib/utils.js';
18
18
  import { createMcpServerForHttpSession } from '../server.js';
19
19
  import { applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
20
20
  import { disableEventLoopMonitoring, resetEventLoopMonitoring, sendHealthRouteResponse, shouldHandleHealthRoute, } from './health.js';
@@ -37,7 +37,7 @@ function resolveRequestedProtocolVersion(body) {
37
37
  if (normalized.length === 0)
38
38
  return DEFAULT_MCP_PROTOCOL_VERSION;
39
39
  if (!SUPPORTED_MCP_PROTOCOL_VERSIONS.has(normalized)) {
40
- return DEFAULT_MCP_PROTOCOL_VERSION;
40
+ return null;
41
41
  }
42
42
  return normalized;
43
43
  }
@@ -71,10 +71,10 @@ class McpSessionGateway {
71
71
  return;
72
72
  }
73
73
  const requestId = body.id ?? null;
74
- const isInitMethod = isInitializedNotification(body.method);
75
- const isInitNotification = isInitMethod && body.id === undefined;
74
+ const isInitializedMethod = isInitializedNotification(body.method);
75
+ const isInitNotification = isInitializedMethod && body.id === undefined;
76
76
  const sessionId = getMcpSessionId(ctx.req);
77
- if (isInitMethod && !isInitNotification) {
77
+ if (isInitializedMethod && !isInitNotification) {
78
78
  sendError(ctx.res, -32600, 'notifications/initialized must be sent as a notification', 400, requestId);
79
79
  return;
80
80
  }
@@ -185,6 +185,10 @@ class McpSessionGateway {
185
185
  return null;
186
186
  }
187
187
  const negotiatedProtocolVersion = resolveRequestedProtocolVersion(ctx.body);
188
+ if (!negotiatedProtocolVersion) {
189
+ sendError(ctx.res, -32602, `Unsupported protocolVersion; supported versions: ${[...SUPPORTED_MCP_PROTOCOL_VERSIONS].join(', ')}`, 400, requestId);
190
+ return null;
191
+ }
188
192
  return this.createNewSession(ctx, requestId, negotiatedProtocolVersion);
189
193
  }
190
194
  getExistingTransport(sessionId, authFingerprint, res, requestId) {
@@ -294,6 +298,7 @@ class McpSessionGateway {
294
298
  sendError(res, -32000, 'Server busy', 503, requestId);
295
299
  return false;
296
300
  }
301
+ // Double-check: capacity may have changed during the async eviction window above.
297
302
  if (!reserveSessionSlot(this.store, config.server.maxSessions)) {
298
303
  sendError(res, -32000, 'Server busy', 503, requestId);
299
304
  return false;
@@ -366,7 +371,7 @@ class HttpDispatcher {
366
371
  if (!auth)
367
372
  return;
368
373
  const authCtx = { ...ctx, auth };
369
- if (this.tryHandleDownloadRoute(ctx))
374
+ if (this.tryHandleDownloadRoute(authCtx))
370
375
  return;
371
376
  if (ctx.url.pathname === '/mcp') {
372
377
  const handled = await this.handleMcpRoutes(authCtx);
@@ -404,9 +409,7 @@ class HttpDispatcher {
404
409
  }
405
410
  catch (err) {
406
411
  applyUnauthorizedAuthHeaders(ctx.req, ctx.res);
407
- sendJson(ctx.res, 401, {
408
- error: err instanceof Error ? err.message : 'Unauthorized',
409
- });
412
+ sendError(ctx.res, -32000, err instanceof Error ? err.message : 'Unauthorized', 401);
410
413
  return null;
411
414
  }
412
415
  }
@@ -524,12 +527,18 @@ function createNetworkServer(listener) {
524
527
  if (!keyFile || !certFile) {
525
528
  throw new Error('HTTPS enabled but SERVER_TLS_KEY_FILE / SERVER_TLS_CERT_FILE are missing');
526
529
  }
527
- const tlsOptions = {
528
- key: readFileSync(keyFile),
529
- cert: readFileSync(certFile),
530
- };
531
- if (caFile) {
532
- tlsOptions.ca = readFileSync(caFile);
530
+ let tlsOptions;
531
+ try {
532
+ tlsOptions = {
533
+ key: readFileSync(keyFile),
534
+ cert: readFileSync(certFile),
535
+ };
536
+ if (caFile) {
537
+ tlsOptions.ca = readFileSync(caFile);
538
+ }
539
+ }
540
+ catch (err) {
541
+ throw new Error(`Failed to read TLS files (key=${keyFile}, cert=${certFile}): ${err instanceof Error ? err.message : String(err)}`, { cause: err });
533
542
  }
534
543
  return createHttpsServer(tlsOptions, listener);
535
544
  }
@@ -629,4 +638,3 @@ export async function startHttpServer() {
629
638
  }),
630
639
  };
631
640
  }
632
- //# sourceMappingURL=native.js.map
@@ -11,4 +11,3 @@ export interface RateLimitManagerImpl {
11
11
  }
12
12
  export declare function createRateLimitManagerImpl(options: RateLimitConfig): RateLimitManagerImpl;
13
13
  export {};
14
- //# sourceMappingURL=rate-limit.d.ts.map
@@ -1,6 +1,6 @@
1
- import { isAbortError } from '../lib/errors.js';
2
- import { logWarn } from '../lib/observability.js';
3
- import { startAbortableIntervalLoop } from '../lib/timer-utils.js';
1
+ import { logWarn } from '../lib/core.js';
2
+ import { isAbortError } from '../lib/utils.js';
3
+ import { startAbortableIntervalLoop } from '../lib/utils.js';
4
4
  import { sendJson } from './helpers.js';
5
5
  // ---------------------------------------------------------------------------
6
6
  // Rate limiter
@@ -85,4 +85,3 @@ class RateLimiter {
85
85
  export function createRateLimitManagerImpl(options) {
86
86
  return new RateLimiter(options);
87
87
  }
88
- //# sourceMappingURL=rate-limit.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import process from 'node:process';
3
+ import { serverVersion } from './lib/core.js';
4
+ import { logError } from './lib/core.js';
5
+ import { toError } from './lib/utils.js';
3
6
  import { parseCliArgs, renderCliUsage } from './cli.js';
4
7
  import { startHttpServer } from './http/native.js';
5
- import { serverVersion } from './lib/config.js';
6
- import { toError } from './lib/errors.js';
7
- import { logError } from './lib/observability.js';
8
8
  import { startStdioServer } from './server.js';
9
9
  const FORCE_EXIT_TIMEOUT_MS = 10_000;
10
10
  let forcedExitTimer;
11
+ function writeAndExit(stream, text, code) {
12
+ stream.write(text);
13
+ process.exit(code);
14
+ }
11
15
  function scheduleForcedExit(reason) {
12
16
  if (forcedExitTimer)
13
17
  return;
@@ -19,18 +23,14 @@ function scheduleForcedExit(reason) {
19
23
  }
20
24
  const parseResult = parseCliArgs(process.argv.slice(2));
21
25
  if (!parseResult.ok) {
22
- process.stderr.write(`Invalid arguments: ${parseResult.message}\n\n`);
23
- process.stderr.write(renderCliUsage());
24
- process.exit(1);
26
+ writeAndExit(process.stderr, `Invalid arguments: ${parseResult.message}\n\n${renderCliUsage()}`, 1);
25
27
  }
26
28
  const { values } = parseResult;
27
29
  if (values.help) {
28
- process.stdout.write(renderCliUsage());
29
- process.exit(0);
30
+ writeAndExit(process.stdout, renderCliUsage(), 0);
30
31
  }
31
32
  if (values.version) {
32
- process.stdout.write(`${serverVersion}\n`);
33
- process.exit(0);
33
+ writeAndExit(process.stdout, `${serverVersion}\n`, 0);
34
34
  }
35
35
  const isStdioMode = !values.http;
36
36
  let isShuttingDown = false;
@@ -45,19 +45,19 @@ function attemptShutdown(signal) {
45
45
  process.stderr.write('Attempting graceful shutdown...\n');
46
46
  void shutdownHandlerRef.current(signal);
47
47
  }
48
- function registerOnceSignal(signal, handler) {
49
- process.once(signal, () => {
50
- handler(signal);
51
- });
48
+ function registerSignalHandlers(signals, handler) {
49
+ for (const signal of signals) {
50
+ process.once(signal, () => {
51
+ handler(signal);
52
+ });
53
+ }
52
54
  }
53
55
  function registerHttpSignalHandlers() {
54
56
  const tryShutdown = (signal) => {
55
57
  if (shouldAttemptShutdown())
56
58
  attemptShutdown(signal);
57
59
  };
58
- for (const signal of ['SIGINT', 'SIGTERM']) {
59
- registerOnceSignal(signal, tryShutdown);
60
- }
60
+ registerSignalHandlers(['SIGINT', 'SIGTERM'], tryShutdown);
61
61
  }
62
62
  function writeStartupError(error) {
63
63
  logError('Failed to start server', error);
@@ -95,4 +95,3 @@ try {
95
95
  catch (error) {
96
96
  writeStartupError(toError(error));
97
97
  }
98
- //# sourceMappingURL=index.js.map
@@ -1,4 +1,7 @@
1
- import type { MetadataBlock } from '../transform/types.js';
1
+ import { type MetadataBlock } from '../transform/types.js';
2
+ export declare function removeNoiseFromHtml(html: string, document?: Document, baseUrl?: string, signal?: AbortSignal): string;
3
+ export declare function resolveLanguageFromAttributes(className: string, dataLang: string): string | undefined;
4
+ export declare function detectLanguageFromCode(code: string): string | undefined;
2
5
  interface CleanupOptions {
3
6
  signal?: AbortSignal;
4
7
  url?: string;
@@ -9,4 +12,3 @@ export declare function addSourceToMarkdown(content: string, url: string): strin
9
12
  export declare function isRawTextContent(content: string): boolean;
10
13
  export declare function buildMetadataFooter(metadata?: MetadataBlock, fallbackUrl?: string): string;
11
14
  export {};
12
- //# sourceMappingURL=markdown-cleanup.d.ts.map