@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.
- package/README.md +24 -21
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +15 -8
- package/dist/http/auth.d.ts +6 -6
- package/dist/http/auth.js +78 -23
- package/dist/http/health.d.ts +1 -2
- package/dist/http/health.js +7 -18
- package/dist/http/helpers.d.ts +3 -11
- package/dist/http/helpers.js +28 -26
- package/dist/http/native.d.ts +0 -1
- package/dist/http/native.js +63 -41
- package/dist/http/rate-limit.d.ts +2 -2
- package/dist/http/rate-limit.js +11 -16
- package/dist/index.d.ts +0 -1
- package/dist/index.js +17 -20
- package/dist/{markdown-cleanup.d.ts → lib/content.d.ts} +4 -2
- package/dist/lib/content.js +1356 -0
- package/dist/lib/core.d.ts +253 -0
- package/dist/lib/core.js +1228 -0
- package/dist/{tool-pipeline.d.ts → lib/fetch-pipeline.d.ts} +1 -3
- package/dist/{tool-pipeline.js → lib/fetch-pipeline.js} +18 -44
- package/dist/{fetch.d.ts → lib/http.d.ts} +7 -9
- package/dist/{fetch.js → lib/http.js} +721 -1004
- package/dist/lib/mcp-tools.d.ts +28 -0
- package/dist/lib/mcp-tools.js +107 -0
- package/dist/{tool-progress.d.ts → lib/progress.d.ts} +0 -2
- package/dist/{tool-progress.js → lib/progress.js} +9 -14
- package/dist/lib/task-handlers.d.ts +5 -0
- package/dist/{mcp.js → lib/task-handlers.js} +95 -31
- package/dist/lib/url.d.ts +70 -0
- package/dist/lib/url.js +686 -0
- package/dist/lib/utils.d.ts +58 -0
- package/dist/lib/utils.js +304 -0
- package/dist/{prompts.d.ts → prompts/index.d.ts} +0 -1
- package/dist/{prompts.js → prompts/index.js} +1 -2
- package/dist/{resources.d.ts → resources/index.d.ts} +0 -1
- package/dist/{resources.js → resources/index.js} +87 -64
- package/dist/{instructions.d.ts → resources/instructions.d.ts} +0 -1
- package/dist/{instructions.js → resources/instructions.js} +5 -3
- package/dist/schemas/inputs.d.ts +7 -0
- package/dist/schemas/inputs.js +24 -0
- package/dist/schemas/outputs.d.ts +23 -0
- package/dist/schemas/outputs.js +77 -0
- package/dist/server.d.ts +0 -1
- package/dist/server.js +26 -25
- package/dist/tasks/execution.d.ts +0 -1
- package/dist/tasks/execution.js +106 -70
- package/dist/tasks/manager.d.ts +11 -3
- package/dist/tasks/manager.js +97 -73
- package/dist/tasks/owner.d.ts +3 -3
- package/dist/tasks/owner.js +2 -2
- package/dist/tasks/tool-registry.d.ts +11 -0
- package/dist/tasks/tool-registry.js +13 -0
- package/dist/tools/fetch-url.d.ts +28 -0
- package/dist/{tools.js → tools/fetch-url.js} +95 -147
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +4 -0
- package/dist/transform/html-translators.d.ts +1 -0
- package/dist/transform/html-translators.js +454 -0
- package/dist/transform/metadata.d.ts +4 -0
- package/dist/transform/metadata.js +183 -0
- package/dist/transform/transform.d.ts +0 -1
- package/dist/transform/transform.js +44 -679
- package/dist/transform/types.d.ts +9 -12
- package/dist/transform/types.js +0 -1
- package/dist/transform/worker-pool.d.ts +0 -1
- package/dist/transform/worker-pool.js +7 -16
- package/dist/transform/workers/shared.d.ts +7 -0
- package/dist/transform/workers/shared.js +130 -0
- package/dist/transform/workers/transform-child.d.ts +0 -1
- package/dist/transform/workers/transform-child.js +5 -135
- package/dist/transform/workers/transform-worker.d.ts +0 -1
- package/dist/transform/workers/transform-worker.js +7 -128
- package/package.json +11 -7
- package/dist/cache.d.ts +0 -54
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -261
- package/dist/cache.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -141
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -473
- package/dist/config.js.map +0 -1
- package/dist/crypto.d.ts +0 -4
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js +0 -56
- package/dist/crypto.js.map +0 -1
- package/dist/dom-noise-removal.d.ts +0 -2
- package/dist/dom-noise-removal.d.ts.map +0 -1
- package/dist/dom-noise-removal.js +0 -494
- package/dist/dom-noise-removal.js.map +0 -1
- package/dist/download.d.ts +0 -4
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js +0 -106
- package/dist/download.js.map +0 -1
- package/dist/errors.d.ts +0 -11
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -65
- package/dist/errors.js.map +0 -1
- package/dist/examples/mcp-fetch-url-client.js +0 -329
- package/dist/examples/mcp-fetch-url-client.js.map +0 -1
- package/dist/fetch-content.d.ts +0 -5
- package/dist/fetch-content.d.ts.map +0 -1
- package/dist/fetch-content.js +0 -164
- package/dist/fetch-content.js.map +0 -1
- package/dist/fetch-stream.d.ts +0 -5
- package/dist/fetch-stream.d.ts.map +0 -1
- package/dist/fetch-stream.js +0 -29
- package/dist/fetch-stream.js.map +0 -1
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js.map +0 -1
- package/dist/host-normalization.d.ts +0 -2
- package/dist/host-normalization.d.ts.map +0 -1
- package/dist/host-normalization.js +0 -91
- package/dist/host-normalization.js.map +0 -1
- package/dist/http/auth.d.ts.map +0 -1
- package/dist/http/auth.js.map +0 -1
- package/dist/http/health.d.ts.map +0 -1
- package/dist/http/health.js.map +0 -1
- package/dist/http/helpers.d.ts.map +0 -1
- package/dist/http/helpers.js.map +0 -1
- package/dist/http/native.d.ts.map +0 -1
- package/dist/http/native.js.map +0 -1
- package/dist/http/rate-limit.d.ts.map +0 -1
- package/dist/http/rate-limit.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instructions.d.ts.map +0 -1
- package/dist/instructions.js.map +0 -1
- package/dist/ip-blocklist.d.ts +0 -9
- package/dist/ip-blocklist.d.ts.map +0 -1
- package/dist/ip-blocklist.js +0 -79
- package/dist/ip-blocklist.js.map +0 -1
- package/dist/json.d.ts +0 -2
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js +0 -45
- package/dist/json.js.map +0 -1
- package/dist/language-detection.d.ts +0 -3
- package/dist/language-detection.d.ts.map +0 -1
- package/dist/language-detection.js +0 -355
- package/dist/language-detection.js.map +0 -1
- package/dist/markdown-cleanup.d.ts.map +0 -1
- package/dist/markdown-cleanup.js +0 -534
- package/dist/markdown-cleanup.js.map +0 -1
- package/dist/mcp-validator.d.ts +0 -17
- package/dist/mcp-validator.d.ts.map +0 -1
- package/dist/mcp-validator.js +0 -45
- package/dist/mcp-validator.js.map +0 -1
- package/dist/mcp.d.ts +0 -4
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/observability.d.ts +0 -23
- package/dist/observability.d.ts.map +0 -1
- package/dist/observability.js +0 -238
- package/dist/observability.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/resources.d.ts.map +0 -1
- package/dist/resources.js.map +0 -1
- package/dist/server-tuning.d.ts +0 -15
- package/dist/server-tuning.d.ts.map +0 -1
- package/dist/server-tuning.js +0 -49
- package/dist/server-tuning.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/session.d.ts +0 -42
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -255
- package/dist/session.js.map +0 -1
- package/dist/tasks/execution.d.ts.map +0 -1
- package/dist/tasks/execution.js.map +0 -1
- package/dist/tasks/manager.d.ts.map +0 -1
- package/dist/tasks/manager.js.map +0 -1
- package/dist/tasks/owner.d.ts.map +0 -1
- package/dist/tasks/owner.js.map +0 -1
- package/dist/timer-utils.d.ts +0 -6
- package/dist/timer-utils.d.ts.map +0 -1
- package/dist/timer-utils.js +0 -27
- package/dist/timer-utils.js.map +0 -1
- package/dist/tool-errors.d.ts +0 -12
- package/dist/tool-errors.d.ts.map +0 -1
- package/dist/tool-errors.js +0 -55
- package/dist/tool-errors.js.map +0 -1
- package/dist/tool-pipeline.d.ts.map +0 -1
- package/dist/tool-pipeline.js.map +0 -1
- package/dist/tool-progress.d.ts.map +0 -1
- package/dist/tool-progress.js.map +0 -1
- package/dist/tools.d.ts +0 -54
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/transform/transform.d.ts.map +0 -1
- package/dist/transform/transform.js.map +0 -1
- package/dist/transform/types.d.ts.map +0 -1
- package/dist/transform/types.js.map +0 -1
- package/dist/transform/worker-pool.d.ts.map +0 -1
- package/dist/transform/worker-pool.js.map +0 -1
- package/dist/transform/workers/transform-child.d.ts.map +0 -1
- package/dist/transform/workers/transform-child.js.map +0 -1
- package/dist/transform/workers/transform-worker.d.ts.map +0 -1
- package/dist/transform/workers/transform-worker.js.map +0 -1
- package/dist/type-guards.d.ts +0 -16
- package/dist/type-guards.d.ts.map +0 -1
- package/dist/type-guards.js +0 -13
- 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
|
-
| `--
|
|
157
|
-
| `--
|
|
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
|
|
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
|
|
166
|
-
|
|
|
167
|
-
| `HOST`
|
|
168
|
-
| `PORT`
|
|
169
|
-
| `LOG_LEVEL`
|
|
170
|
-
| `FETCH_TIMEOUT_MS`
|
|
171
|
-
| `CACHE_ENABLED`
|
|
172
|
-
| `USER_AGENT`
|
|
173
|
-
| `ALLOW_REMOTE`
|
|
174
|
-
| `ALLOWED_HOSTS`
|
|
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
|
|
179
|
-
|
|
|
180
|
-
| `TASKS_MAX_TOTAL`
|
|
181
|
-
| `TASKS_MAX_PER_OWNER`
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
56
|
+
values: cliValues,
|
|
49
57
|
};
|
|
50
58
|
}
|
|
51
59
|
catch (error) {
|
|
52
60
|
return {
|
|
53
61
|
ok: false,
|
|
54
|
-
message:
|
|
62
|
+
message: getErrorMessage(error),
|
|
55
63
|
};
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/http/auth.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import { normalizeHost } from '../
|
|
7
|
-
import {
|
|
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
|
|
80
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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
|
|
384
|
+
const urls = buildRequestScopedProtectedResourceUrls(req);
|
|
329
385
|
return {
|
|
330
|
-
resource:
|
|
331
|
-
resource_metadata:
|
|
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
|
package/dist/http/health.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { SessionStore } from '../
|
|
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
|
package/dist/http/health.js
CHANGED
|
@@ -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 '../
|
|
5
|
-
import { config, serverVersion } from '../
|
|
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
|
|
113
|
+
function isHealthRoute(ctx) {
|
|
114
114
|
return ctx.method === 'GET' && ctx.url.pathname === '/health';
|
|
115
115
|
}
|
|
116
116
|
function isVerboseHealthRoute(ctx) {
|
|
117
|
-
return
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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
|
package/dist/http/helpers.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
package/dist/http/helpers.js
CHANGED
|
@@ -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 '../
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
|
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(
|
|
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',
|
|
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
|
package/dist/http/native.d.ts
CHANGED