@j0hanz/fetch-url-mcp 1.3.1 → 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 (205) hide show
  1. package/README.md +24 -21
  2. package/dist/cli.d.ts +3 -3
  3. package/dist/cli.js +15 -8
  4. package/dist/http/auth.d.ts +6 -6
  5. package/dist/http/auth.js +78 -23
  6. package/dist/http/health.d.ts +1 -2
  7. package/dist/http/health.js +7 -18
  8. package/dist/http/helpers.d.ts +3 -11
  9. package/dist/http/helpers.js +28 -26
  10. package/dist/http/native.d.ts +0 -1
  11. package/dist/http/native.js +63 -41
  12. package/dist/http/rate-limit.d.ts +2 -2
  13. package/dist/http/rate-limit.js +11 -16
  14. package/dist/index.d.ts +0 -1
  15. package/dist/index.js +17 -20
  16. package/dist/{markdown-cleanup.d.ts → lib/content.d.ts} +4 -2
  17. package/dist/lib/content.js +1356 -0
  18. package/dist/lib/core.d.ts +253 -0
  19. package/dist/lib/core.js +1228 -0
  20. package/dist/{tool-pipeline.d.ts → lib/fetch-pipeline.d.ts} +1 -3
  21. package/dist/{tool-pipeline.js → lib/fetch-pipeline.js} +18 -44
  22. package/dist/{fetch.d.ts → lib/http.d.ts} +7 -9
  23. package/dist/{fetch.js → lib/http.js} +721 -1004
  24. package/dist/lib/mcp-tools.d.ts +28 -0
  25. package/dist/lib/mcp-tools.js +107 -0
  26. package/dist/{tool-progress.d.ts → lib/progress.d.ts} +0 -2
  27. package/dist/{tool-progress.js → lib/progress.js} +9 -14
  28. package/dist/lib/task-handlers.d.ts +5 -0
  29. package/dist/{mcp.js → lib/task-handlers.js} +95 -31
  30. package/dist/lib/url.d.ts +70 -0
  31. package/dist/lib/url.js +686 -0
  32. package/dist/lib/utils.d.ts +58 -0
  33. package/dist/lib/utils.js +304 -0
  34. package/dist/{prompts.d.ts → prompts/index.d.ts} +0 -1
  35. package/dist/{prompts.js → prompts/index.js} +1 -2
  36. package/dist/{resources.d.ts → resources/index.d.ts} +0 -1
  37. package/dist/{resources.js → resources/index.js} +87 -64
  38. package/dist/{instructions.d.ts → resources/instructions.d.ts} +0 -1
  39. package/dist/{instructions.js → resources/instructions.js} +5 -3
  40. package/dist/schemas/inputs.d.ts +7 -0
  41. package/dist/schemas/inputs.js +24 -0
  42. package/dist/schemas/outputs.d.ts +23 -0
  43. package/dist/schemas/outputs.js +77 -0
  44. package/dist/server.d.ts +0 -1
  45. package/dist/server.js +26 -25
  46. package/dist/tasks/execution.d.ts +0 -1
  47. package/dist/tasks/execution.js +106 -70
  48. package/dist/tasks/manager.d.ts +11 -3
  49. package/dist/tasks/manager.js +97 -73
  50. package/dist/tasks/owner.d.ts +3 -3
  51. package/dist/tasks/owner.js +2 -2
  52. package/dist/tasks/tool-registry.d.ts +11 -0
  53. package/dist/tasks/tool-registry.js +13 -0
  54. package/dist/tools/fetch-url.d.ts +28 -0
  55. package/dist/{tools.js → tools/fetch-url.js} +95 -147
  56. package/dist/tools/index.d.ts +2 -0
  57. package/dist/tools/index.js +4 -0
  58. package/dist/transform/html-translators.d.ts +1 -0
  59. package/dist/transform/html-translators.js +454 -0
  60. package/dist/transform/metadata.d.ts +4 -0
  61. package/dist/transform/metadata.js +183 -0
  62. package/dist/transform/transform.d.ts +0 -1
  63. package/dist/transform/transform.js +44 -679
  64. package/dist/transform/types.d.ts +9 -12
  65. package/dist/transform/types.js +0 -1
  66. package/dist/transform/worker-pool.d.ts +0 -1
  67. package/dist/transform/worker-pool.js +7 -16
  68. package/dist/transform/workers/shared.d.ts +7 -0
  69. package/dist/transform/workers/shared.js +130 -0
  70. package/dist/transform/workers/transform-child.d.ts +0 -1
  71. package/dist/transform/workers/transform-child.js +5 -135
  72. package/dist/transform/workers/transform-worker.d.ts +0 -1
  73. package/dist/transform/workers/transform-worker.js +7 -128
  74. package/package.json +11 -7
  75. package/dist/cache.d.ts +0 -54
  76. package/dist/cache.d.ts.map +0 -1
  77. package/dist/cache.js +0 -261
  78. package/dist/cache.js.map +0 -1
  79. package/dist/cli.d.ts.map +0 -1
  80. package/dist/cli.js.map +0 -1
  81. package/dist/config.d.ts +0 -141
  82. package/dist/config.d.ts.map +0 -1
  83. package/dist/config.js +0 -473
  84. package/dist/config.js.map +0 -1
  85. package/dist/crypto.d.ts +0 -4
  86. package/dist/crypto.d.ts.map +0 -1
  87. package/dist/crypto.js +0 -56
  88. package/dist/crypto.js.map +0 -1
  89. package/dist/dom-noise-removal.d.ts +0 -2
  90. package/dist/dom-noise-removal.d.ts.map +0 -1
  91. package/dist/dom-noise-removal.js +0 -494
  92. package/dist/dom-noise-removal.js.map +0 -1
  93. package/dist/download.d.ts +0 -4
  94. package/dist/download.d.ts.map +0 -1
  95. package/dist/download.js +0 -106
  96. package/dist/download.js.map +0 -1
  97. package/dist/errors.d.ts +0 -11
  98. package/dist/errors.d.ts.map +0 -1
  99. package/dist/errors.js +0 -65
  100. package/dist/errors.js.map +0 -1
  101. package/dist/examples/mcp-fetch-url-client.js +0 -329
  102. package/dist/examples/mcp-fetch-url-client.js.map +0 -1
  103. package/dist/fetch-content.d.ts +0 -5
  104. package/dist/fetch-content.d.ts.map +0 -1
  105. package/dist/fetch-content.js +0 -164
  106. package/dist/fetch-content.js.map +0 -1
  107. package/dist/fetch-stream.d.ts +0 -5
  108. package/dist/fetch-stream.d.ts.map +0 -1
  109. package/dist/fetch-stream.js +0 -29
  110. package/dist/fetch-stream.js.map +0 -1
  111. package/dist/fetch.d.ts.map +0 -1
  112. package/dist/fetch.js.map +0 -1
  113. package/dist/host-normalization.d.ts +0 -2
  114. package/dist/host-normalization.d.ts.map +0 -1
  115. package/dist/host-normalization.js +0 -91
  116. package/dist/host-normalization.js.map +0 -1
  117. package/dist/http/auth.d.ts.map +0 -1
  118. package/dist/http/auth.js.map +0 -1
  119. package/dist/http/health.d.ts.map +0 -1
  120. package/dist/http/health.js.map +0 -1
  121. package/dist/http/helpers.d.ts.map +0 -1
  122. package/dist/http/helpers.js.map +0 -1
  123. package/dist/http/native.d.ts.map +0 -1
  124. package/dist/http/native.js.map +0 -1
  125. package/dist/http/rate-limit.d.ts.map +0 -1
  126. package/dist/http/rate-limit.js.map +0 -1
  127. package/dist/index.d.ts.map +0 -1
  128. package/dist/index.js.map +0 -1
  129. package/dist/instructions.d.ts.map +0 -1
  130. package/dist/instructions.js.map +0 -1
  131. package/dist/ip-blocklist.d.ts +0 -9
  132. package/dist/ip-blocklist.d.ts.map +0 -1
  133. package/dist/ip-blocklist.js +0 -79
  134. package/dist/ip-blocklist.js.map +0 -1
  135. package/dist/json.d.ts +0 -2
  136. package/dist/json.d.ts.map +0 -1
  137. package/dist/json.js +0 -45
  138. package/dist/json.js.map +0 -1
  139. package/dist/language-detection.d.ts +0 -3
  140. package/dist/language-detection.d.ts.map +0 -1
  141. package/dist/language-detection.js +0 -355
  142. package/dist/language-detection.js.map +0 -1
  143. package/dist/markdown-cleanup.d.ts.map +0 -1
  144. package/dist/markdown-cleanup.js +0 -534
  145. package/dist/markdown-cleanup.js.map +0 -1
  146. package/dist/mcp-validator.d.ts +0 -17
  147. package/dist/mcp-validator.d.ts.map +0 -1
  148. package/dist/mcp-validator.js +0 -45
  149. package/dist/mcp-validator.js.map +0 -1
  150. package/dist/mcp.d.ts +0 -4
  151. package/dist/mcp.d.ts.map +0 -1
  152. package/dist/mcp.js.map +0 -1
  153. package/dist/observability.d.ts +0 -23
  154. package/dist/observability.d.ts.map +0 -1
  155. package/dist/observability.js +0 -238
  156. package/dist/observability.js.map +0 -1
  157. package/dist/prompts.d.ts.map +0 -1
  158. package/dist/prompts.js.map +0 -1
  159. package/dist/resources.d.ts.map +0 -1
  160. package/dist/resources.js.map +0 -1
  161. package/dist/server-tuning.d.ts +0 -15
  162. package/dist/server-tuning.d.ts.map +0 -1
  163. package/dist/server-tuning.js +0 -49
  164. package/dist/server-tuning.js.map +0 -1
  165. package/dist/server.d.ts.map +0 -1
  166. package/dist/server.js.map +0 -1
  167. package/dist/session.d.ts +0 -42
  168. package/dist/session.d.ts.map +0 -1
  169. package/dist/session.js +0 -255
  170. package/dist/session.js.map +0 -1
  171. package/dist/tasks/execution.d.ts.map +0 -1
  172. package/dist/tasks/execution.js.map +0 -1
  173. package/dist/tasks/manager.d.ts.map +0 -1
  174. package/dist/tasks/manager.js.map +0 -1
  175. package/dist/tasks/owner.d.ts.map +0 -1
  176. package/dist/tasks/owner.js.map +0 -1
  177. package/dist/timer-utils.d.ts +0 -6
  178. package/dist/timer-utils.d.ts.map +0 -1
  179. package/dist/timer-utils.js +0 -27
  180. package/dist/timer-utils.js.map +0 -1
  181. package/dist/tool-errors.d.ts +0 -12
  182. package/dist/tool-errors.d.ts.map +0 -1
  183. package/dist/tool-errors.js +0 -55
  184. package/dist/tool-errors.js.map +0 -1
  185. package/dist/tool-pipeline.d.ts.map +0 -1
  186. package/dist/tool-pipeline.js.map +0 -1
  187. package/dist/tool-progress.d.ts.map +0 -1
  188. package/dist/tool-progress.js.map +0 -1
  189. package/dist/tools.d.ts +0 -54
  190. package/dist/tools.d.ts.map +0 -1
  191. package/dist/tools.js.map +0 -1
  192. package/dist/transform/transform.d.ts.map +0 -1
  193. package/dist/transform/transform.js.map +0 -1
  194. package/dist/transform/types.d.ts.map +0 -1
  195. package/dist/transform/types.js.map +0 -1
  196. package/dist/transform/worker-pool.d.ts.map +0 -1
  197. package/dist/transform/worker-pool.js.map +0 -1
  198. package/dist/transform/workers/transform-child.d.ts.map +0 -1
  199. package/dist/transform/workers/transform-child.js.map +0 -1
  200. package/dist/transform/workers/transform-worker.d.ts.map +0 -1
  201. package/dist/transform/workers/transform-worker.js.map +0 -1
  202. package/dist/type-guards.d.ts +0 -16
  203. package/dist/type-guards.d.ts.map +0 -1
  204. package/dist/type-guards.js +0 -13
  205. package/dist/type-guards.js.map +0 -1
package/README.md CHANGED
@@ -150,35 +150,38 @@ docker compose up --build
150
150
 
151
151
  ### Runtime Modes
152
152
 
153
- | Flag | Description |
154
- | ----------------- | ------------------------------------------- |
155
- | `--stdio`, `-s` | Run in stdio mode (for desktop MCP clients) |
156
- | `--help`, `-h` | Show usage help |
157
- | `--version`, `-v` | Print server version |
153
+ | Flag | Description |
154
+ | ----------------- | ---------------------------------------------------------- |
155
+ | `--stdio`, `-s` | Run in stdio mode (for desktop MCP clients; default) |
156
+ | `--http` | Run in HTTP mode (Streamable HTTP on port 3000 by default) |
157
+ | `--help`, `-h` | Show usage help |
158
+ | `--version`, `-v` | Print server version |
158
159
 
159
- When no `--stdio` flag is passed, the server starts in **HTTP mode** (Streamable HTTP on port 3000 by default).
160
+ When no transport flag is passed, the server starts in **stdio mode**.
160
161
 
161
162
  ### Environment Variables
162
163
 
163
164
  #### Core Settings
164
165
 
165
- | Variable | Default | Description |
166
- | ------------------ | ------------------------- | --------------------------------------------------- |
167
- | `HOST` | `127.0.0.1` | HTTP server bind address |
168
- | `PORT` | `3000` | HTTP server port (1024–65535) |
169
- | `LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
170
- | `FETCH_TIMEOUT_MS` | `15000` | HTTP fetch timeout in ms (1000–60000) |
171
- | `CACHE_ENABLED` | `true` | Enable/disable in-memory content cache |
172
- | `USER_AGENT` | `fetch-url-mcp/{version}` | Custom User-Agent header |
173
- | `ALLOW_REMOTE` | `false` | Allow remote connections in HTTP mode |
174
- | `ALLOWED_HOSTS` | _(empty)_ | Comma-separated host/origin allowlist for HTTP mode |
166
+ | Variable | Default | Description |
167
+ | ------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------- |
168
+ | `HOST` | `127.0.0.1` | HTTP server bind address |
169
+ | `PORT` | `3000` | HTTP server port (1024–65535) |
170
+ | `LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
171
+ | `FETCH_TIMEOUT_MS` | `15000` | HTTP fetch timeout in ms (1000–60000) |
172
+ | `CACHE_ENABLED` | `true` | Enable/disable in-memory content cache |
173
+ | `USER_AGENT` | `fetch-url-mcp/{version}` | Custom User-Agent header |
174
+ | `ALLOW_REMOTE` | `false` | Allow remote connections in HTTP mode |
175
+ | `ALLOWED_HOSTS` | _(empty)_ | Comma-separated host/origin allowlist for HTTP mode |
176
+ | `MCP_STRICT_PROTOCOL_VERSION_HEADER` | `true` | Require `MCP-Protocol-Version` on HTTP session initialize (`false` allows legacy headerless initialize) |
175
177
 
176
178
  #### Task Management
177
179
 
178
- | Variable | Default | Description |
179
- | --------------------- | ------- | ------------------------------------------------ |
180
- | `TASKS_MAX_TOTAL` | `5000` | Maximum retained task records across all owners |
181
- | `TASKS_MAX_PER_OWNER` | `1000` | Maximum retained task records per session/client |
180
+ | Variable | Default | Description |
181
+ | ---------------------------- | ------- | -------------------------------------------------------- |
182
+ | `TASKS_MAX_TOTAL` | `5000` | Maximum retained task records across all owners |
183
+ | `TASKS_MAX_PER_OWNER` | `1000` | Maximum retained task records per session/client |
184
+ | `TASKS_STATUS_NOTIFICATIONS` | `false` | Emit experimental `notifications/tasks/status` extension |
182
185
 
183
186
  #### Authentication (HTTP Mode)
184
187
 
@@ -595,7 +598,7 @@ npm run inspector
595
598
  | `VALIDATION_ERROR` on URL | URL is blocked (private IP/localhost) or malformed. Do not retry. |
596
599
  | `queue_full` error | Worker pool busy. Wait briefly, then retry or use async task mode. |
597
600
  | Garbled output | Binary content (images, PDFs) cannot be converted. Ensure the URL serves HTML. |
598
- | No output in stdio mode | Ensure `--stdio` flag is passed. Without it, the server starts in HTTP mode. |
601
+ | No output in stdio mode | If you intended HTTP mode, pass `--http`. Stdio is the default transport. |
599
602
  | Auth errors in HTTP mode | Set `ACCESS_TOKENS` or `API_KEY` env var and pass as `Authorization: Bearer <token>`. |
600
603
 
601
604
  ### Stdout / Stderr Guidance
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export interface CliValues {
1
+ interface CliValues {
2
2
  readonly stdio: boolean;
3
+ readonly http: boolean;
3
4
  readonly help: boolean;
4
5
  readonly version: boolean;
5
6
  }
@@ -11,8 +12,7 @@ interface CliParseFailure {
11
12
  readonly ok: false;
12
13
  readonly message: string;
13
14
  }
14
- export type CliParseResult = CliParseSuccess | CliParseFailure;
15
+ type CliParseResult = CliParseSuccess | CliParseFailure;
15
16
  export declare function renderCliUsage(): string;
16
17
  export declare function parseCliArgs(args: readonly string[]): CliParseResult;
17
18
  export {};
18
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -1,24 +1,24 @@
1
1
  import { parseArgs } from 'node:util';
2
+ import { getErrorMessage } from './lib/utils.js';
2
3
  const usageLines = [
3
4
  'Fetch URL MCP server',
4
5
  '',
5
6
  'Usage:',
6
- ' fetch-url-mcp [--stdio|-s] [--help|-h] [--version|-v]',
7
+ ' fetch-url-mcp [--stdio|-s | --http] [--help|-h] [--version|-v]',
7
8
  '',
8
9
  'Options:',
9
- ' --stdio, -s Run in stdio mode (no HTTP server).',
10
+ ' --stdio, -s Run in stdio mode (default).',
11
+ ' --http Run in Streamable HTTP mode.',
10
12
  ' --help, -h Show this help message.',
11
13
  ' --version, -v Show server version.',
12
14
  '',
13
15
  ];
14
16
  const optionSchema = {
15
17
  stdio: { type: 'boolean', short: 's', default: false },
18
+ http: { type: 'boolean', default: false },
16
19
  help: { type: 'boolean', short: 'h', default: false },
17
20
  version: { type: 'boolean', short: 'v', default: false },
18
21
  };
19
- function toErrorMessage(error) {
20
- return error instanceof Error ? error.message : String(error);
21
- }
22
22
  function toBoolean(value) {
23
23
  return value === true;
24
24
  }
@@ -28,6 +28,7 @@ function readCliFlag(values, key) {
28
28
  function buildCliValues(values) {
29
29
  return {
30
30
  stdio: readCliFlag(values, 'stdio'),
31
+ http: readCliFlag(values, 'http'),
31
32
  help: readCliFlag(values, 'help'),
32
33
  version: readCliFlag(values, 'version'),
33
34
  };
@@ -43,16 +44,22 @@ export function parseCliArgs(args) {
43
44
  strict: true,
44
45
  allowPositionals: false,
45
46
  });
47
+ const cliValues = buildCliValues(values);
48
+ if (cliValues.stdio && cliValues.http) {
49
+ return {
50
+ ok: false,
51
+ message: 'Choose either --stdio or --http, not both',
52
+ };
53
+ }
46
54
  return {
47
55
  ok: true,
48
- values: buildCliValues(values),
56
+ values: cliValues,
49
57
  };
50
58
  }
51
59
  catch (error) {
52
60
  return {
53
61
  ok: false,
54
- message: toErrorMessage(error),
62
+ message: getErrorMessage(error),
55
63
  };
56
64
  }
57
65
  }
58
- //# 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,17 +8,18 @@ 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;
15
17
  export declare function assertHttpModeConfiguration(): void;
16
18
  export declare const SUPPORTED_MCP_PROTOCOL_VERSIONS: Set<string>;
17
- export interface McpProtocolVersionCheckOptions {
19
+ interface McpProtocolVersionCheckOptions {
18
20
  requireHeader?: boolean;
19
21
  expectedVersion?: string;
20
22
  }
21
- export declare function resolveMcpProtocolVersion(req: IncomingMessage): string | undefined;
22
23
  export declare function ensureMcpProtocolVersion(req: IncomingMessage, res: ServerResponse, options?: McpProtocolVersionCheckOptions): boolean;
23
24
  export declare function buildAuthFingerprint(auth: AuthInfo | undefined): string | null;
24
25
  declare class AuthService {
@@ -34,9 +35,9 @@ declare class AuthService {
34
35
  private buildIntrospectionRequest;
35
36
  private requestIntrospection;
36
37
  private buildIntrospectionAuthInfo;
38
+ private assertRequiredScopes;
37
39
  private verifyWithIntrospection;
38
40
  }
39
- export declare function buildResourceMetadataUrl(req: IncomingMessage): string;
40
41
  export declare function applyUnauthorizedAuthHeaders(req: IncomingMessage, res: ServerResponse): void;
41
42
  export declare function buildProtectedResourceMetadataDocument(req: IncomingMessage): {
42
43
  resource: string;
@@ -48,4 +49,3 @@ export declare function buildProtectedResourceMetadataDocument(req: IncomingMess
48
49
  export declare function isProtectedResourceMetadataPath(pathname: string): boolean;
49
50
  export declare const authService: AuthService;
50
51
  export {};
51
- //# 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 '../config.js';
5
- import { hmacSha256Hex, timingSafeEqualUtf8 } from '../crypto.js';
6
- import { normalizeHost } from '../host-normalization.js';
7
- import { isObject } from '../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,11 +20,9 @@ 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');
25
+ res.setHeader('Access-Control-Expose-Headers', 'MCP-Session-ID, X-MCP-Session-ID, MCP-Protocol-Version, WWW-Authenticate');
28
26
  if (req.method !== 'OPTIONS')
29
27
  return false;
30
28
  sendEmpty(res, 204);
@@ -76,10 +74,16 @@ class HostOriginPolicy {
76
74
  const originHeader = getHeaderValue(req, 'origin');
77
75
  if (!originHeader)
78
76
  return true;
79
- const originHost = this.resolveOriginHost(originHeader);
80
- if (!originHost)
77
+ const requestOrigin = this.resolveRequestOrigin(req);
78
+ const origin = this.resolveOrigin(originHeader);
79
+ if (!requestOrigin || !origin)
81
80
  return this.reject(res, 403, 'Invalid Origin header');
82
- 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)
83
87
  return this.reject(res, 403, 'Origin not allowed');
84
88
  return true;
85
89
  }
@@ -89,17 +93,52 @@ class HostOriginPolicy {
89
93
  return null;
90
94
  return normalizeHost(host);
91
95
  }
92
- 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) {
93
118
  if (origin === 'null')
94
119
  return null;
95
120
  try {
96
121
  const parsed = new URL(origin);
97
- 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
+ };
98
134
  }
99
135
  catch {
100
136
  return null;
101
137
  }
102
138
  }
139
+ defaultPortForScheme(scheme) {
140
+ return scheme === 'https' ? '443' : '80';
141
+ }
103
142
  reject(res, status, message) {
104
143
  sendJson(res, status, { error: message });
105
144
  return false;
@@ -132,7 +171,7 @@ export const SUPPORTED_MCP_PROTOCOL_VERSIONS = new Set([
132
171
  DEFAULT_MCP_PROTOCOL_VERSION,
133
172
  LEGACY_MCP_PROTOCOL_VERSION,
134
173
  ]);
135
- export function resolveMcpProtocolVersion(req) {
174
+ function resolveMcpProtocolVersion(req) {
136
175
  const versionHeader = getHeaderValue(req, 'mcp-protocol-version');
137
176
  if (!versionHeader)
138
177
  return undefined;
@@ -292,6 +331,16 @@ class AuthService {
292
331
  info.expiresAt = expiresAt;
293
332
  return info;
294
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
+ }
295
344
  async verifyWithIntrospection(token, signal) {
296
345
  if (!config.auth.introspectionUrl) {
297
346
  throw new ServerError('Introspection not configured');
@@ -301,7 +350,9 @@ class AuthService {
301
350
  if (!isObject(payload) || payload['active'] !== true) {
302
351
  throw new InvalidTokenError('Token is inactive');
303
352
  }
304
- return this.buildIntrospectionAuthInfo(token, payload);
353
+ const info = this.buildIntrospectionAuthInfo(token, payload);
354
+ this.assertRequiredScopes(info.scopes);
355
+ return info;
305
356
  }
306
357
  }
307
358
  function resolvePublicOrigin(req) {
@@ -312,23 +363,28 @@ function resolvePublicOrigin(req) {
312
363
  }
313
364
  return config.auth.resourceUrl.origin;
314
365
  }
366
+ function buildRequestScopedProtectedResourceUrls(req) {
367
+ const origin = resolvePublicOrigin(req);
368
+ return {
369
+ resource: new URL('/mcp', `${origin}/`).href,
370
+ resourceMetadata: new URL(resolveResourceMetadataPath(), `${origin}/`).href,
371
+ };
372
+ }
315
373
  function resolveResourceMetadataPath() {
316
374
  return '/.well-known/oauth-protected-resource/mcp';
317
375
  }
318
- export function buildResourceMetadataUrl(req) {
319
- const origin = resolvePublicOrigin(req);
320
- const path = resolveResourceMetadataPath();
321
- return new URL(path, `${origin}/`).href;
376
+ function buildResourceMetadataUrl(req) {
377
+ return buildRequestScopedProtectedResourceUrls(req).resourceMetadata;
322
378
  }
323
379
  export function applyUnauthorizedAuthHeaders(req, res) {
324
380
  const resourceMetadata = buildResourceMetadataUrl(req);
325
381
  res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${resourceMetadata}"`);
326
382
  }
327
383
  export function buildProtectedResourceMetadataDocument(req) {
328
- const metadataUrl = buildResourceMetadataUrl(req);
384
+ const urls = buildRequestScopedProtectedResourceUrls(req);
329
385
  return {
330
- resource: config.auth.resourceUrl.href,
331
- resource_metadata: metadataUrl,
386
+ resource: urls.resource,
387
+ resource_metadata: urls.resourceMetadata,
332
388
  authorization_servers: config.auth.issuerUrl
333
389
  ? [config.auth.issuerUrl.href]
334
390
  : [],
@@ -341,4 +397,3 @@ export function isProtectedResourceMetadataPath(pathname) {
341
397
  pathname === '/.well-known/oauth-protected-resource/mcp');
342
398
  }
343
399
  export const authService = new AuthService();
344
- //# sourceMappingURL=auth.js.map
@@ -1,7 +1,6 @@
1
- import type { SessionStore } from '../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 '../cache.js';
5
- import { config, serverVersion } from '../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 '../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;
@@ -18,7 +18,6 @@ export interface RequestContext {
18
18
  export interface AuthenticatedContext extends RequestContext {
19
19
  auth: AuthInfo;
20
20
  }
21
- export declare function setNoStoreHeaders(res: ServerResponse): void;
22
21
  export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
23
22
  export declare function sendText(res: ServerResponse, status: number, body: string): void;
24
23
  export declare function sendEmpty(res: ServerResponse, status: number): void;
@@ -31,7 +30,6 @@ export declare function createRequestAbortSignal(req: IncomingMessage): {
31
30
  signal: AbortSignal;
32
31
  cleanup: () => void;
33
32
  };
34
- export declare function normalizeRemoteAddress(address: string | undefined): string | null;
35
33
  export declare function registerInboundBlockList(server: NetworkServer): void;
36
34
  export declare function buildRequestContext(req: IncomingMessage, res: ServerResponse, signal?: AbortSignal): RequestContext | null;
37
35
  export declare function closeTransportBestEffort(transport: {
@@ -39,11 +37,6 @@ export declare function closeTransportBestEffort(transport: {
39
37
  }, context: string): Promise<void>;
40
38
  export declare function closeMcpServerBestEffort(server: McpServer, context: string): Promise<void>;
41
39
  export declare function createTransportAdapter(transportImpl: StreamableHTTPServerTransport): Transport;
42
- type JsonBodyErrorKind = 'payload-too-large' | 'invalid-json' | 'read-failed';
43
- export declare class JsonBodyError extends Error {
44
- readonly kind: JsonBodyErrorKind;
45
- constructor(kind: JsonBodyErrorKind, message: string);
46
- }
47
40
  export declare const DEFAULT_BODY_LIMIT_BYTES: number;
48
41
  declare class JsonBodyReader {
49
42
  read(req: IncomingMessage, limit?: number, signal?: AbortSignal): Promise<unknown>;
@@ -55,4 +48,3 @@ declare class JsonBodyReader {
55
48
  }
56
49
  export declare const jsonBodyReader: JsonBodyReader;
57
50
  export {};
58
- //# sourceMappingURL=helpers.d.ts.map
@@ -1,14 +1,27 @@
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 '../config.js';
5
- import { createDefaultBlockList, normalizeIpForBlockList, } from '../ip-blocklist.js';
6
- import { logWarn } from '../observability.js';
7
- import { composeCloseHandlers } from '../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
+ }
8
21
  // ---------------------------------------------------------------------------
9
22
  // Response helpers
10
23
  // ---------------------------------------------------------------------------
11
- export function setNoStoreHeaders(res) {
24
+ function setNoStoreHeaders(res) {
12
25
  res.setHeader('X-Content-Type-Options', 'nosniff');
13
26
  res.setHeader('Cache-Control', 'no-store');
14
27
  }
@@ -55,6 +68,7 @@ const SINGLE_VALUE_HEADER_NAMES = [
55
68
  'host',
56
69
  'origin',
57
70
  'content-length',
71
+ 'mcp-protocol-version',
58
72
  'mcp-session-id',
59
73
  'x-mcp-session-id',
60
74
  ];
@@ -88,8 +102,7 @@ export function createRequestAbortSignal(req) {
88
102
  const abortRequest = () => {
89
103
  if (cleanedUp)
90
104
  return;
91
- if (!controller.signal.aborted)
92
- controller.abort();
105
+ abortControllerBestEffort(controller);
93
106
  };
94
107
  if (req.destroyed) {
95
108
  abortRequest();
@@ -126,7 +139,7 @@ export function createRequestAbortSignal(req) {
126
139
  // ---------------------------------------------------------------------------
127
140
  // IP & connection helpers
128
141
  // ---------------------------------------------------------------------------
129
- export function normalizeRemoteAddress(address) {
142
+ function normalizeRemoteAddress(address) {
130
143
  if (!address)
131
144
  return null;
132
145
  const trimmed = address.trim();
@@ -233,7 +246,7 @@ export function createTransportAdapter(transportImpl) {
233
246
  },
234
247
  };
235
248
  }
236
- export class JsonBodyError extends Error {
249
+ class JsonBodyError extends Error {
237
250
  kind;
238
251
  constructor(kind, message) {
239
252
  super(message);
@@ -254,12 +267,7 @@ class JsonBodyReader {
254
267
  if (contentLengthHeader) {
255
268
  const contentLength = Number.parseInt(contentLengthHeader, 10);
256
269
  if (Number.isFinite(contentLength) && contentLength > limit) {
257
- try {
258
- req.destroy();
259
- }
260
- catch {
261
- // Best-effort only.
262
- }
270
+ destroyRequestBestEffort(req);
263
271
  throw new JsonBodyError('payload-too-large', 'Payload too large');
264
272
  }
265
273
  }
@@ -273,7 +281,7 @@ class JsonBodyReader {
273
281
  return JSON.parse(body);
274
282
  }
275
283
  catch (err) {
276
- throw new JsonBodyError('invalid-json', err instanceof Error ? err.message : String(err));
284
+ throw new JsonBodyError('invalid-json', getErrorMessage(err));
277
285
  }
278
286
  }
279
287
  async readBody(req, limit, signal) {
@@ -292,12 +300,7 @@ class JsonBodyReader {
292
300
  if (!signal)
293
301
  return null;
294
302
  const listener = () => {
295
- try {
296
- req.destroy();
297
- }
298
- catch {
299
- // Best-effort only.
300
- }
303
+ destroyRequestBestEffort(req);
301
304
  };
302
305
  if (signal.aborted) {
303
306
  listener();
@@ -330,7 +333,7 @@ class JsonBodyReader {
330
333
  const buf = this.normalizeChunk(chunk);
331
334
  size += buf.length;
332
335
  if (size > limit) {
333
- req.destroy();
336
+ destroyRequestBestEffort(req);
334
337
  callback(new JsonBodyError('payload-too-large', 'Payload too large'));
335
338
  return;
336
339
  }
@@ -338,7 +341,7 @@ class JsonBodyReader {
338
341
  callback();
339
342
  }
340
343
  catch (err) {
341
- callback(err instanceof Error ? err : new Error(String(err)));
344
+ callback(toError(err));
342
345
  }
343
346
  },
344
347
  });
@@ -355,7 +358,7 @@ class JsonBodyReader {
355
358
  if (signal?.aborted || isRequestReadAborted(req)) {
356
359
  throw new JsonBodyError('read-failed', 'Request aborted');
357
360
  }
358
- throw new JsonBodyError('read-failed', err instanceof Error ? err.message : String(err));
361
+ throw new JsonBodyError('read-failed', getErrorMessage(err));
359
362
  }
360
363
  }
361
364
  normalizeChunk(chunk) {
@@ -367,4 +370,3 @@ class JsonBodyReader {
367
370
  }
368
371
  }
369
372
  export const jsonBodyReader = new JsonBodyReader();
370
- //# 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