@j0hanz/fetch-url-mcp 1.6.0 → 1.7.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 +507 -403
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/http/auth.d.ts +12 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +85 -6
- package/dist/http/health.d.ts +1 -0
- package/dist/http/health.d.ts.map +1 -0
- package/dist/http/helpers.d.ts +1 -0
- package/dist/http/helpers.d.ts.map +1 -0
- package/dist/http/native.d.ts +1 -0
- package/dist/http/native.d.ts.map +1 -0
- package/dist/http/native.js +80 -63
- package/dist/http/rate-limit.d.ts +1 -0
- package/dist/http/rate-limit.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/content.d.ts +3 -0
- package/dist/lib/content.d.ts.map +1 -0
- package/dist/lib/content.js +16 -11
- package/dist/lib/core.d.ts +6 -8
- package/dist/lib/core.d.ts.map +1 -0
- package/dist/lib/core.js +111 -97
- package/dist/lib/fetch-pipeline.d.ts +1 -1
- package/dist/lib/fetch-pipeline.d.ts.map +1 -0
- package/dist/lib/fetch-pipeline.js +54 -44
- package/dist/lib/http.d.ts +1 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/mcp-tools.d.ts +45 -7
- package/dist/lib/mcp-tools.d.ts.map +1 -0
- package/dist/lib/mcp-tools.js +37 -6
- package/dist/lib/net-utils.d.ts +1 -0
- package/dist/lib/net-utils.d.ts.map +1 -0
- package/dist/lib/progress.d.ts +1 -0
- package/dist/lib/progress.d.ts.map +1 -0
- package/dist/lib/task-handlers.d.ts +9 -1
- package/dist/lib/task-handlers.d.ts.map +1 -0
- package/dist/lib/task-handlers.js +30 -38
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +12 -1
- package/dist/lib/url.d.ts +3 -0
- package/dist/lib/url.d.ts.map +1 -0
- package/dist/lib/url.js +78 -151
- package/dist/lib/utils.d.ts +2 -2
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +60 -94
- package/dist/lib/zod.d.ts +3 -0
- package/dist/lib/zod.d.ts.map +1 -0
- package/dist/lib/zod.js +33 -0
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +2 -13
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +5 -19
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.d.ts.map +1 -0
- package/dist/resources/instructions.js +2 -0
- package/dist/schemas/cache.d.ts +18 -0
- package/dist/schemas/cache.d.ts.map +1 -0
- package/dist/schemas/cache.js +19 -0
- package/dist/schemas/inputs.d.ts +1 -0
- package/dist/schemas/inputs.d.ts.map +1 -0
- package/dist/schemas/outputs.d.ts +6 -5
- package/dist/schemas/outputs.d.ts.map +1 -0
- package/dist/schemas/outputs.js +5 -9
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +9 -7
- package/dist/tasks/execution.d.ts +1 -0
- package/dist/tasks/execution.d.ts.map +1 -0
- package/dist/tasks/execution.js +3 -21
- package/dist/tasks/manager.d.ts +2 -6
- package/dist/tasks/manager.d.ts.map +1 -0
- package/dist/tasks/manager.js +2 -4
- package/dist/tasks/owner.d.ts +1 -0
- package/dist/tasks/owner.d.ts.map +1 -0
- package/dist/tasks/tool-registry.d.ts +2 -0
- package/dist/tasks/tool-registry.d.ts.map +1 -0
- package/dist/tasks/tool-registry.js +3 -0
- package/dist/tools/fetch-url.d.ts +4 -6
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +61 -59
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/transform/html-translators.d.ts +1 -0
- package/dist/transform/html-translators.d.ts.map +1 -0
- package/dist/transform/html-translators.js +5 -2
- package/dist/transform/metadata.d.ts +1 -0
- package/dist/transform/metadata.d.ts.map +1 -0
- package/dist/transform/metadata.js +1 -0
- package/dist/transform/{workers/shared.d.ts → shared.d.ts} +2 -1
- package/dist/transform/shared.d.ts.map +1 -0
- package/dist/transform/{workers/shared.js → shared.js} +1 -1
- package/dist/transform/transform.d.ts +1 -0
- package/dist/transform/transform.d.ts.map +1 -0
- package/dist/transform/transform.js +21 -14
- package/dist/transform/types.d.ts +1 -4
- package/dist/transform/types.d.ts.map +1 -0
- package/dist/transform/worker-pool.d.ts +3 -18
- package/dist/transform/worker-pool.d.ts.map +1 -0
- package/dist/transform/worker-pool.js +51 -167
- package/package.json +9 -6
- package/dist/transform/workers/transform-child.d.ts +0 -1
- package/dist/transform/workers/transform-child.js +0 -15
- package/dist/transform/workers/transform-worker.d.ts +0 -1
- package/dist/transform/workers/transform-worker.js +0 -13
package/dist/cli.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,KAAK,cAAc,GAAG,eAAe,GAAG,eAAe,CAAC;AA2CxD,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2BpE"}
|
package/dist/http/auth.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
2
3
|
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
|
|
3
4
|
import { type RequestContext } from './helpers.js';
|
|
4
5
|
declare class CorsPolicy {
|
|
5
6
|
handle(ctx: RequestContext): boolean;
|
|
6
7
|
}
|
|
7
8
|
export declare const corsPolicy: CorsPolicy;
|
|
9
|
+
export declare class InsufficientScopeError extends InvalidTokenError {
|
|
10
|
+
readonly requiredScopes: readonly string[];
|
|
11
|
+
constructor(requiredScopes: readonly string[], message?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function isInsufficientScopeError(error: unknown): error is InsufficientScopeError;
|
|
8
14
|
declare class HostOriginPolicy {
|
|
9
15
|
validate(ctx: RequestContext): boolean;
|
|
10
16
|
private resolveHostHeader;
|
|
@@ -21,6 +27,7 @@ interface McpProtocolVersionCheckOptions {
|
|
|
21
27
|
expectedVersion?: string;
|
|
22
28
|
}
|
|
23
29
|
export declare function ensureMcpProtocolVersion(req: IncomingMessage, res: ServerResponse, options?: McpProtocolVersionCheckOptions): boolean;
|
|
30
|
+
export declare function isOAuthMetadataEnabled(): boolean;
|
|
24
31
|
export declare function buildAuthFingerprint(auth: AuthInfo | undefined): string | null;
|
|
25
32
|
declare class AuthService {
|
|
26
33
|
private readonly staticTokenDigests;
|
|
@@ -31,6 +38,9 @@ declare class AuthService {
|
|
|
31
38
|
private buildStaticAuthInfo;
|
|
32
39
|
private verifyStaticToken;
|
|
33
40
|
private stripHash;
|
|
41
|
+
private canonicalizeResourceUri;
|
|
42
|
+
private readAudienceValues;
|
|
43
|
+
private assertTokenAudience;
|
|
34
44
|
private buildBasicAuthHeader;
|
|
35
45
|
private buildIntrospectionRequest;
|
|
36
46
|
private requestIntrospection;
|
|
@@ -39,6 +49,7 @@ declare class AuthService {
|
|
|
39
49
|
private verifyWithIntrospection;
|
|
40
50
|
}
|
|
41
51
|
export declare function applyUnauthorizedAuthHeaders(req: IncomingMessage, res: ServerResponse): void;
|
|
52
|
+
export declare function applyInsufficientScopeAuthHeaders(req: IncomingMessage, res: ServerResponse, requiredScopes: readonly string[], message?: string): void;
|
|
42
53
|
export declare function buildProtectedResourceMetadataDocument(req: IncomingMessage): {
|
|
43
54
|
resource: string;
|
|
44
55
|
resource_metadata: string;
|
|
@@ -49,3 +60,4 @@ export declare function buildProtectedResourceMetadataDocument(req: IncomingMess
|
|
|
49
60
|
export declare function isProtectedResourceMetadataPath(pathname: string): boolean;
|
|
50
61
|
export declare const authService: AuthService;
|
|
51
62
|
export {};
|
|
63
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/http/auth.js
CHANGED
|
@@ -35,6 +35,17 @@ export const corsPolicy = new CorsPolicy();
|
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
36
|
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
|
|
37
37
|
const WILDCARD_HOSTS = new Set(['0.0.0.0', '::']);
|
|
38
|
+
export class InsufficientScopeError extends InvalidTokenError {
|
|
39
|
+
requiredScopes;
|
|
40
|
+
constructor(requiredScopes, message = 'Insufficient scope') {
|
|
41
|
+
super(message);
|
|
42
|
+
this.requiredScopes = requiredScopes;
|
|
43
|
+
this.name = 'InsufficientScopeError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function isInsufficientScopeError(error) {
|
|
47
|
+
return error instanceof InsufficientScopeError;
|
|
48
|
+
}
|
|
38
49
|
function hasConstantTimeMatch(candidates, input) {
|
|
39
50
|
// Avoid leaking match index via early-return.
|
|
40
51
|
let matched = 0;
|
|
@@ -158,6 +169,9 @@ export function assertHttpModeConfiguration() {
|
|
|
158
169
|
if (isRemoteBinding && config.auth.mode !== 'oauth') {
|
|
159
170
|
throw new Error('OAuth authentication is required for remote bindings');
|
|
160
171
|
}
|
|
172
|
+
if (config.auth.mode === 'oauth' && !config.auth.issuerUrl) {
|
|
173
|
+
throw new Error('OAuth mode requires OAUTH_ISSUER_URL to serve RFC9728 metadata');
|
|
174
|
+
}
|
|
161
175
|
if (config.auth.mode === 'static' && config.auth.staticTokens.length === 0) {
|
|
162
176
|
throw new Error('Static auth requires ACCESS_TOKENS or API_KEY to be configured');
|
|
163
177
|
}
|
|
@@ -180,7 +194,7 @@ function resolveMcpProtocolVersion(req) {
|
|
|
180
194
|
}
|
|
181
195
|
export function ensureMcpProtocolVersion(req, res, options) {
|
|
182
196
|
const version = resolveMcpProtocolVersion(req);
|
|
183
|
-
const requireHeader = options?.requireHeader ??
|
|
197
|
+
const requireHeader = options?.requireHeader ?? config.security.allowRemote;
|
|
184
198
|
if (!version) {
|
|
185
199
|
if (!requireHeader) {
|
|
186
200
|
// Permissive backward-compat fallback: clients predating MCP 2025-03-26 do not
|
|
@@ -205,6 +219,9 @@ export function ensureMcpProtocolVersion(req, res, options) {
|
|
|
205
219
|
}
|
|
206
220
|
return true;
|
|
207
221
|
}
|
|
222
|
+
export function isOAuthMetadataEnabled() {
|
|
223
|
+
return config.auth.mode === 'oauth';
|
|
224
|
+
}
|
|
208
225
|
// ---------------------------------------------------------------------------
|
|
209
226
|
// Auth fingerprint
|
|
210
227
|
// ---------------------------------------------------------------------------
|
|
@@ -282,6 +299,48 @@ class AuthService {
|
|
|
282
299
|
clean.hash = '';
|
|
283
300
|
return clean.href;
|
|
284
301
|
}
|
|
302
|
+
canonicalizeResourceUri(value) {
|
|
303
|
+
if (!URL.canParse(value))
|
|
304
|
+
return null;
|
|
305
|
+
const url = new URL(value);
|
|
306
|
+
url.hash = '';
|
|
307
|
+
url.protocol = url.protocol.toLowerCase();
|
|
308
|
+
url.hostname = url.hostname.toLowerCase();
|
|
309
|
+
const { href } = url;
|
|
310
|
+
if (url.pathname === '/' && !url.search && href.endsWith('/')) {
|
|
311
|
+
return href.slice(0, -1);
|
|
312
|
+
}
|
|
313
|
+
return href;
|
|
314
|
+
}
|
|
315
|
+
readAudienceValues(payload) {
|
|
316
|
+
const values = [];
|
|
317
|
+
for (const key of ['aud', 'resource']) {
|
|
318
|
+
const raw = payload[key];
|
|
319
|
+
if (typeof raw === 'string') {
|
|
320
|
+
values.push(raw);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!Array.isArray(raw))
|
|
324
|
+
continue;
|
|
325
|
+
values.push(...raw.filter((value) => typeof value === 'string'));
|
|
326
|
+
}
|
|
327
|
+
return values;
|
|
328
|
+
}
|
|
329
|
+
assertTokenAudience(payload) {
|
|
330
|
+
const expected = this.canonicalizeResourceUri(this.stripHash(config.auth.resourceUrl));
|
|
331
|
+
if (!expected) {
|
|
332
|
+
throw new ServerError('Configured resource URL is invalid');
|
|
333
|
+
}
|
|
334
|
+
const audiences = this.readAudienceValues(payload)
|
|
335
|
+
.map((value) => this.canonicalizeResourceUri(value))
|
|
336
|
+
.filter((value) => value !== null);
|
|
337
|
+
if (audiences.length === 0) {
|
|
338
|
+
throw new InvalidTokenError('Token missing audience binding');
|
|
339
|
+
}
|
|
340
|
+
if (!audiences.includes(expected)) {
|
|
341
|
+
throw new InvalidTokenError('Token audience does not match this MCP server');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
285
344
|
buildBasicAuthHeader(clientId, clientSecret) {
|
|
286
345
|
// Base64 is only an encoding for header transport; it is NOT encryption.
|
|
287
346
|
const credentials = `${clientId}:${clientSecret ?? ''}`;
|
|
@@ -341,7 +400,7 @@ class AuthService {
|
|
|
341
400
|
const tokenScopeSet = new Set(tokenScopes);
|
|
342
401
|
const missing = requiredScopes.filter((s) => !tokenScopeSet.has(s));
|
|
343
402
|
if (missing.length > 0) {
|
|
344
|
-
throw new
|
|
403
|
+
throw new InsufficientScopeError(missing);
|
|
345
404
|
}
|
|
346
405
|
}
|
|
347
406
|
async verifyWithIntrospection(token, signal) {
|
|
@@ -353,6 +412,7 @@ class AuthService {
|
|
|
353
412
|
if (!isObject(payload) || payload['active'] !== true) {
|
|
354
413
|
throw new InvalidTokenError('Token is inactive');
|
|
355
414
|
}
|
|
415
|
+
this.assertTokenAudience(payload);
|
|
356
416
|
const info = this.buildIntrospectionAuthInfo(token, payload);
|
|
357
417
|
this.assertRequiredScopes(info.scopes);
|
|
358
418
|
return info;
|
|
@@ -380,17 +440,36 @@ function buildResourceMetadataUrl(req) {
|
|
|
380
440
|
return buildRequestScopedProtectedResourceUrls(req).resourceMetadata;
|
|
381
441
|
}
|
|
382
442
|
export function applyUnauthorizedAuthHeaders(req, res) {
|
|
443
|
+
if (!isOAuthMetadataEnabled())
|
|
444
|
+
return;
|
|
383
445
|
const resourceMetadata = buildResourceMetadataUrl(req);
|
|
384
|
-
|
|
446
|
+
const challengeParts = [`resource_metadata="${resourceMetadata}"`];
|
|
447
|
+
if (config.auth.requiredScopes.length > 0) {
|
|
448
|
+
challengeParts.push(`scope="${config.auth.requiredScopes.join(' ')}"`);
|
|
449
|
+
}
|
|
450
|
+
res.setHeader('WWW-Authenticate', `Bearer ${challengeParts.join(', ')}`);
|
|
451
|
+
}
|
|
452
|
+
export function applyInsufficientScopeAuthHeaders(req, res, requiredScopes, message = 'Additional authorization scope is required') {
|
|
453
|
+
if (!isOAuthMetadataEnabled())
|
|
454
|
+
return;
|
|
455
|
+
const resourceMetadata = buildResourceMetadataUrl(req);
|
|
456
|
+
const challengeParts = [
|
|
457
|
+
'error="insufficient_scope"',
|
|
458
|
+
`scope="${requiredScopes.join(' ')}"`,
|
|
459
|
+
`resource_metadata="${resourceMetadata}"`,
|
|
460
|
+
`error_description="${message.replaceAll('"', "'")}"`,
|
|
461
|
+
];
|
|
462
|
+
res.setHeader('WWW-Authenticate', `Bearer ${challengeParts.join(', ')}`);
|
|
385
463
|
}
|
|
386
464
|
export function buildProtectedResourceMetadataDocument(req) {
|
|
387
465
|
const urls = buildRequestScopedProtectedResourceUrls(req);
|
|
466
|
+
if (!config.auth.issuerUrl) {
|
|
467
|
+
throw new ServerError('OAuth issuer URL is required for protected resource metadata');
|
|
468
|
+
}
|
|
388
469
|
return {
|
|
389
470
|
resource: urls.resource,
|
|
390
471
|
resource_metadata: urls.resourceMetadata,
|
|
391
|
-
authorization_servers: config.auth.issuerUrl
|
|
392
|
-
? [config.auth.issuerUrl.href]
|
|
393
|
-
: [],
|
|
472
|
+
authorization_servers: [config.auth.issuerUrl.href],
|
|
394
473
|
bearer_methods_supported: ['header'],
|
|
395
474
|
scopes_supported: config.auth.requiredScopes,
|
|
396
475
|
};
|
package/dist/http/health.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/http/health.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,cAAc,CAAC;AAY7D,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAiMD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAEpE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,OAAO,GACnB,OAAO,CAOT"}
|
package/dist/http/helpers.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/http/native.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AA64BA,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CA8DD"}
|
package/dist/http/native.js
CHANGED
|
@@ -10,15 +10,15 @@ import { config, enableHttpMode } from '../lib/core.js';
|
|
|
10
10
|
import { logError, logInfo, registerMcpSessionServer, resolveMcpSessionIdByServer, runWithRequestContext, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
|
|
11
11
|
import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, reserveSessionSlot, startSessionCleanupLoop, } from '../lib/core.js';
|
|
12
12
|
import { handleDownload } from '../lib/http.js';
|
|
13
|
-
import { acceptsEventStream, acceptsJsonAndEventStream, isJsonRpcBatchRequest, isMcpRequestBody, } from '../lib/mcp-tools.js';
|
|
13
|
+
import { acceptsEventStream, acceptsJsonAndEventStream, isJsonRpcBatchRequest, isMcpMessageBody, isMcpRequestBody, } from '../lib/mcp-tools.js';
|
|
14
14
|
import { cancelTasksForOwner } from '../lib/mcp-tools.js';
|
|
15
15
|
import { toError } from '../lib/utils.js';
|
|
16
16
|
import { applyHttpServerTuning, drainConnectionsOnShutdown, } from '../lib/utils.js';
|
|
17
17
|
import { isObject } from '../lib/utils.js';
|
|
18
18
|
import { createMcpServerForHttpSession } from '../server.js';
|
|
19
|
-
import { applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
|
|
19
|
+
import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
|
|
20
20
|
import { disableEventLoopMonitoring, resetEventLoopMonitoring, sendHealthRouteResponse, shouldHandleHealthRoute, } from './health.js';
|
|
21
|
-
import { buildRequestContext, closeMcpServerBestEffort, closeTransportBestEffort, createRequestAbortSignal, createTransportAdapter, DEFAULT_BODY_LIMIT_BYTES, drainRequest, findDuplicateSingleValueHeader, getHeaderValue, getMcpSessionId, jsonBodyReader, registerInboundBlockList, sendError, sendJson,
|
|
21
|
+
import { buildRequestContext, closeMcpServerBestEffort, closeTransportBestEffort, createRequestAbortSignal, createTransportAdapter, DEFAULT_BODY_LIMIT_BYTES, drainRequest, findDuplicateSingleValueHeader, getHeaderValue, getMcpSessionId, jsonBodyReader, registerInboundBlockList, sendEmpty, sendError, sendJson, } from './helpers.js';
|
|
22
22
|
import { createRateLimitManagerImpl, } from './rate-limit.js';
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// MCP session gateway
|
|
@@ -41,6 +41,13 @@ function resolveRequestedProtocolVersion(body) {
|
|
|
41
41
|
}
|
|
42
42
|
return normalized;
|
|
43
43
|
}
|
|
44
|
+
function resolveProtocolVersionHeader(req) {
|
|
45
|
+
const header = getHeaderValue(req, 'mcp-protocol-version');
|
|
46
|
+
if (!header)
|
|
47
|
+
return undefined;
|
|
48
|
+
const normalized = header.trim();
|
|
49
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
50
|
+
}
|
|
44
51
|
function isInitializedNotification(method) {
|
|
45
52
|
return method === 'notifications/initialized';
|
|
46
53
|
}
|
|
@@ -66,36 +73,33 @@ class McpSessionGateway {
|
|
|
66
73
|
sendError(ctx.res, -32600, 'Batch requests not supported');
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
69
|
-
if (!
|
|
76
|
+
if (!isMcpMessageBody(body)) {
|
|
70
77
|
sendError(ctx.res, -32600, 'Invalid request body');
|
|
71
78
|
return;
|
|
72
79
|
}
|
|
73
80
|
const requestId = body.id ?? null;
|
|
74
|
-
const
|
|
81
|
+
const method = isMcpRequestBody(body) ? body.method : null;
|
|
82
|
+
const isInitializedMethod = method !== null && isInitializedNotification(method);
|
|
75
83
|
const isInitNotification = isInitializedMethod && body.id === undefined;
|
|
76
84
|
const sessionId = getMcpSessionId(ctx.req);
|
|
77
85
|
if (isInitializedMethod && !isInitNotification) {
|
|
78
86
|
sendError(ctx.res, -32600, 'notifications/initialized must be sent as a notification', 400, requestId);
|
|
79
87
|
return;
|
|
80
88
|
}
|
|
81
|
-
const session = sessionId
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
const session = sessionId
|
|
90
|
+
? this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res, requestId)
|
|
91
|
+
: undefined;
|
|
92
|
+
if (sessionId && !session)
|
|
84
93
|
return;
|
|
85
|
-
}
|
|
86
94
|
if (!session && isInitNotification) {
|
|
87
95
|
sendError(ctx.res, -32600, 'Missing session ID', 400, requestId);
|
|
88
96
|
return;
|
|
89
97
|
}
|
|
90
98
|
if (session) {
|
|
91
|
-
if (!
|
|
92
|
-
requireHeader: true,
|
|
93
|
-
expectedVersion: session.negotiatedProtocolVersion,
|
|
94
|
-
})) {
|
|
99
|
+
if (!this.ensureSessionProtocolVersion(ctx, session))
|
|
95
100
|
return;
|
|
96
|
-
}
|
|
97
101
|
if (!session.protocolInitialized) {
|
|
98
|
-
const isPing = isPingRequest(
|
|
102
|
+
const isPing = method !== null && isPingRequest(method);
|
|
99
103
|
if (!isInitNotification && !isPing) {
|
|
100
104
|
sendError(ctx.res, -32600, 'Session not initialized', 400, requestId);
|
|
101
105
|
return;
|
|
@@ -110,16 +114,12 @@ class McpSessionGateway {
|
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
116
|
if (session && isInitNotification) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
if (sessionId)
|
|
117
|
-
this.store.touch(sessionId);
|
|
118
|
-
sendText(ctx.res, 200, '');
|
|
117
|
+
this.markSessionInitialized(sessionId, session);
|
|
118
|
+
sendEmpty(ctx.res, 202);
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
logInfo('[MCP POST]', {
|
|
122
|
-
method:
|
|
122
|
+
method: method ?? 'response',
|
|
123
123
|
id: body.id,
|
|
124
124
|
sessionId,
|
|
125
125
|
});
|
|
@@ -129,27 +129,14 @@ class McpSessionGateway {
|
|
|
129
129
|
await transport.handleRequest(ctx.req, ctx.res, body);
|
|
130
130
|
}
|
|
131
131
|
async handleGet(ctx) {
|
|
132
|
-
const sessionId =
|
|
133
|
-
if (!sessionId)
|
|
134
|
-
sendError(ctx.res, -32600, 'Missing session ID');
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const session = this.store.get(sessionId);
|
|
138
|
-
if (!session) {
|
|
139
|
-
sendError(ctx.res, -32600, 'Session not found', 404);
|
|
132
|
+
const sessionId = this.getRequiredSessionId(ctx.req, ctx.res);
|
|
133
|
+
if (!sessionId)
|
|
140
134
|
return;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!fingerprint || session.authFingerprint !== fingerprint) {
|
|
144
|
-
sendError(ctx.res, -32600, 'Session not found', 404);
|
|
135
|
+
const session = this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res);
|
|
136
|
+
if (!session)
|
|
145
137
|
return;
|
|
146
|
-
|
|
147
|
-
if (!ensureMcpProtocolVersion(ctx.req, ctx.res, {
|
|
148
|
-
requireHeader: true,
|
|
149
|
-
expectedVersion: session.negotiatedProtocolVersion,
|
|
150
|
-
})) {
|
|
138
|
+
if (!this.ensureSessionProtocolVersion(ctx, session))
|
|
151
139
|
return;
|
|
152
|
-
}
|
|
153
140
|
const acceptHeader = getHeaderValue(ctx.req, 'accept');
|
|
154
141
|
if (!acceptsEventStream(acceptHeader)) {
|
|
155
142
|
sendJson(ctx.res, 406, {
|
|
@@ -161,27 +148,14 @@ class McpSessionGateway {
|
|
|
161
148
|
await session.transport.handleRequest(ctx.req, ctx.res);
|
|
162
149
|
}
|
|
163
150
|
async handleDelete(ctx) {
|
|
164
|
-
const sessionId =
|
|
165
|
-
if (!sessionId)
|
|
166
|
-
sendError(ctx.res, -32600, 'Missing session ID');
|
|
151
|
+
const sessionId = this.getRequiredSessionId(ctx.req, ctx.res);
|
|
152
|
+
if (!sessionId)
|
|
167
153
|
return;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!session) {
|
|
171
|
-
sendError(ctx.res, -32600, 'Session not found', 404);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const fingerprint = buildAuthFingerprint(ctx.auth);
|
|
175
|
-
if (!fingerprint || session.authFingerprint !== fingerprint) {
|
|
176
|
-
sendError(ctx.res, -32600, 'Session not found', 404);
|
|
154
|
+
const session = this.getAuthenticatedSessionById(sessionId, ctx.auth, ctx.res);
|
|
155
|
+
if (!session)
|
|
177
156
|
return;
|
|
178
|
-
|
|
179
|
-
if (!ensureMcpProtocolVersion(ctx.req, ctx.res, {
|
|
180
|
-
requireHeader: true,
|
|
181
|
-
expectedVersion: session.negotiatedProtocolVersion,
|
|
182
|
-
})) {
|
|
157
|
+
if (!this.ensureSessionProtocolVersion(ctx, session))
|
|
183
158
|
return;
|
|
184
|
-
}
|
|
185
159
|
await session.transport.close();
|
|
186
160
|
this.cleanupSessionRecord(sessionId, 'session-delete');
|
|
187
161
|
sendJson(ctx.res, 200, { status: 'closed' });
|
|
@@ -192,7 +166,7 @@ class McpSessionGateway {
|
|
|
192
166
|
const fingerprint = buildAuthFingerprint(ctx.auth);
|
|
193
167
|
return this.getExistingTransport(sessionId, fingerprint, ctx.res, requestId);
|
|
194
168
|
}
|
|
195
|
-
if (!isInitializeRequest(ctx.body)) {
|
|
169
|
+
if (!isMcpRequestBody(ctx.body) || !isInitializeRequest(ctx.body)) {
|
|
196
170
|
sendError(ctx.res, -32600, 'Missing session ID', 400, requestId);
|
|
197
171
|
return null;
|
|
198
172
|
}
|
|
@@ -201,20 +175,55 @@ class McpSessionGateway {
|
|
|
201
175
|
sendError(ctx.res, -32602, `Unsupported protocolVersion; supported versions: ${[...SUPPORTED_MCP_PROTOCOL_VERSIONS].join(', ')}`, 400, requestId);
|
|
202
176
|
return null;
|
|
203
177
|
}
|
|
178
|
+
const headerProtocolVersion = resolveProtocolVersionHeader(ctx.req);
|
|
179
|
+
if (headerProtocolVersion &&
|
|
180
|
+
headerProtocolVersion !== negotiatedProtocolVersion) {
|
|
181
|
+
sendError(ctx.res, -32600, `initialize protocolVersion mismatch: header=${headerProtocolVersion}, body=${negotiatedProtocolVersion}`, 400, requestId);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
204
184
|
return this.createNewSession(ctx, requestId, negotiatedProtocolVersion);
|
|
205
185
|
}
|
|
206
186
|
getExistingTransport(sessionId, authFingerprint, res, requestId) {
|
|
187
|
+
const session = this.getAuthenticatedSessionById(sessionId, authFingerprint, res, requestId);
|
|
188
|
+
if (!session)
|
|
189
|
+
return null;
|
|
190
|
+
this.store.touch(sessionId);
|
|
191
|
+
return session.transport;
|
|
192
|
+
}
|
|
193
|
+
getRequiredSessionId(req, res, requestId = null) {
|
|
194
|
+
const sessionId = getMcpSessionId(req);
|
|
195
|
+
if (sessionId)
|
|
196
|
+
return sessionId;
|
|
197
|
+
sendError(res, -32600, 'Missing session ID', 400, requestId);
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
getAuthenticatedSessionById(sessionId, auth, res, requestId = null) {
|
|
207
201
|
const session = this.store.get(sessionId);
|
|
208
202
|
if (!session) {
|
|
209
203
|
sendError(res, -32600, 'Session not found', 404, requestId);
|
|
210
204
|
return null;
|
|
211
205
|
}
|
|
212
|
-
|
|
206
|
+
const fingerprint = typeof auth === 'string' || auth === null
|
|
207
|
+
? auth
|
|
208
|
+
: buildAuthFingerprint(auth);
|
|
209
|
+
if (!fingerprint || session.authFingerprint !== fingerprint) {
|
|
213
210
|
sendError(res, -32600, 'Session not found', 404, requestId);
|
|
214
211
|
return null;
|
|
215
212
|
}
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
return session;
|
|
214
|
+
}
|
|
215
|
+
ensureSessionProtocolVersion(ctx, session) {
|
|
216
|
+
return ensureMcpProtocolVersion(ctx.req, ctx.res, {
|
|
217
|
+
requireHeader: true,
|
|
218
|
+
expectedVersion: session.negotiatedProtocolVersion,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
markSessionInitialized(sessionId, session) {
|
|
222
|
+
if (!session.protocolInitialized) {
|
|
223
|
+
session.protocolInitialized = true;
|
|
224
|
+
}
|
|
225
|
+
if (sessionId)
|
|
226
|
+
this.store.touch(sessionId);
|
|
218
227
|
}
|
|
219
228
|
async createNewSession(ctx, requestId, negotiatedProtocolVersion) {
|
|
220
229
|
const authFingerprint = buildAuthFingerprint(ctx.auth);
|
|
@@ -367,6 +376,8 @@ class HttpDispatcher {
|
|
|
367
376
|
tryHandleProtectedResourceMetadataRoute(ctx) {
|
|
368
377
|
if (ctx.method !== 'GET')
|
|
369
378
|
return false;
|
|
379
|
+
if (!isOAuthMetadataEnabled())
|
|
380
|
+
return false;
|
|
370
381
|
if (!isProtectedResourceMetadataPath(ctx.url.pathname))
|
|
371
382
|
return false;
|
|
372
383
|
const document = buildProtectedResourceMetadataDocument(ctx.req);
|
|
@@ -420,8 +431,14 @@ class HttpDispatcher {
|
|
|
420
431
|
return await authService.authenticate(ctx.req, ctx.signal);
|
|
421
432
|
}
|
|
422
433
|
catch (err) {
|
|
434
|
+
const message = err instanceof Error ? err.message : 'Unauthorized';
|
|
435
|
+
if (isInsufficientScopeError(err)) {
|
|
436
|
+
applyInsufficientScopeAuthHeaders(ctx.req, ctx.res, err.requiredScopes, message);
|
|
437
|
+
sendError(ctx.res, -32000, message, 403);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
423
440
|
applyUnauthorizedAuthHeaders(ctx.req, ctx.res);
|
|
424
|
-
sendError(ctx.res, -32000,
|
|
441
|
+
sendError(ctx.res, -32000, message, 401);
|
|
425
442
|
return null;
|
|
426
443
|
}
|
|
427
444
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/http/rate-limit.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,cAAc,CAAC;AAY7D,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IACpC,IAAI,IAAI,IAAI,CAAC;CACd;AA0FD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,GACvB,oBAAoB,CAEtB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/lib/content.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type MetadataBlock } from '../transform/types.js';
|
|
2
|
+
export declare function serializeDocumentForMarkdown(document: Document, fallback: string): string;
|
|
3
|
+
export declare function prepareDocumentForMarkdown(document: Document, baseUrl?: string, signal?: AbortSignal): void;
|
|
2
4
|
export declare function removeNoiseFromHtml(html: string, document?: Document, baseUrl?: string, signal?: AbortSignal): string;
|
|
3
5
|
export declare function resolveLanguageFromAttributes(className: string, dataLang: string): string | undefined;
|
|
4
6
|
export declare function detectLanguageFromCode(code: string): string | undefined;
|
|
@@ -12,3 +14,4 @@ export declare function addSourceToMarkdown(content: string, url: string): strin
|
|
|
12
14
|
export declare function isRawTextContent(content: string): boolean;
|
|
13
15
|
export declare function buildMetadataFooter(metadata?: MetadataBlock, fallbackUrl?: string): string;
|
|
14
16
|
export {};
|
|
17
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/lib/content.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAghB3D,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAQR;AAWD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,IAAI,CAYN;AACD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,QAAQ,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,MAAM,CAcR;AAkVD,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAKpB;AACD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA6BvE;AA+CD,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA4QD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CA6DR;AA2GD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CAOpB;AACD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAmCxE;AAcD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAczD;AAaD,wBAAgB,mBAAmB,CACjC,QAAQ,CAAC,EAAE,aAAa,EACxB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAmBR"}
|
package/dist/lib/content.js
CHANGED
|
@@ -445,7 +445,7 @@ function resolveUrls(document, baseUrlStr) {
|
|
|
445
445
|
processUrlElement(el, 'srcset', base, true);
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
|
-
function
|
|
448
|
+
export function serializeDocumentForMarkdown(document, fallback) {
|
|
449
449
|
const bodyHtml = document.body.innerHTML;
|
|
450
450
|
if (bodyHtml.trim().length > MIN_BODY_CONTENT_LENGTH)
|
|
451
451
|
return bodyHtml;
|
|
@@ -463,6 +463,17 @@ function mayContainNoise(html) {
|
|
|
463
463
|
: `${html.substring(0, NOISE_SCAN_LIMIT)}\n${html.substring(html.length - NOISE_SCAN_LIMIT)}`;
|
|
464
464
|
return NOISE_PATTERNS.some((re) => re.test(sample));
|
|
465
465
|
}
|
|
466
|
+
export function prepareDocumentForMarkdown(document, baseUrl, signal) {
|
|
467
|
+
const context = getContext();
|
|
468
|
+
if (config.noiseRemoval.debug) {
|
|
469
|
+
logDebug('Noise removal audit enabled', {
|
|
470
|
+
categories: [...(context.flags.navFooter ? ['nav-footer'] : [])],
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
stripNoise(document, context, signal);
|
|
474
|
+
if (baseUrl)
|
|
475
|
+
resolveUrls(document, baseUrl);
|
|
476
|
+
}
|
|
466
477
|
export function removeNoiseFromHtml(html, document, baseUrl, signal) {
|
|
467
478
|
const shouldParse = isFullDocumentHtml(html) ||
|
|
468
479
|
mayContainNoise(html) ||
|
|
@@ -470,17 +481,9 @@ export function removeNoiseFromHtml(html, document, baseUrl, signal) {
|
|
|
470
481
|
if (!shouldParse)
|
|
471
482
|
return html;
|
|
472
483
|
try {
|
|
473
|
-
const context = getContext();
|
|
474
|
-
if (config.noiseRemoval.debug) {
|
|
475
|
-
logDebug('Noise removal audit enabled', {
|
|
476
|
-
categories: [...(context.flags.navFooter ? ['nav-footer'] : [])],
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
484
|
const doc = document ?? parseHTML(html).document;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
resolveUrls(doc, baseUrl);
|
|
483
|
-
return serialize(doc, html);
|
|
485
|
+
prepareDocumentForMarkdown(doc, baseUrl, signal);
|
|
486
|
+
return serializeDocumentForMarkdown(doc, html);
|
|
484
487
|
}
|
|
485
488
|
catch {
|
|
486
489
|
return html;
|
|
@@ -861,6 +864,7 @@ const REGEX = {
|
|
|
861
864
|
SPACING_CODE_DASH: /(`[^`]+`)\s*\\-\s*/g,
|
|
862
865
|
SPACING_ESCAPES: /\\([[\].])/g,
|
|
863
866
|
SPACING_LIST_NUM_COMBINED: /^((?![-*+] |\d+\. |[ \t]).+)\n((?:[-*+]|\d+\.) )/gm,
|
|
867
|
+
PUNCT_ONLY_LIST_ARTIFACT: /^(?:[-*+]|\d+\.)\s*(?:\\[-*+|/]|[-*+|/])(?:\s+(?:\\[-*+|/]|[-*+|/]))*\s*$/gm,
|
|
864
868
|
NESTED_LIST_INDENT: /^( +)((?:[-*+])|\d+\.)\s/gm,
|
|
865
869
|
TYPEDOC_COMMENT: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
|
|
866
870
|
};
|
|
@@ -1093,6 +1097,7 @@ function applyGlobalRegexes(text, options) {
|
|
|
1093
1097
|
.replace(REGEX.SPACING_CODE_DASH, '$1 - ')
|
|
1094
1098
|
.replace(REGEX.SPACING_ESCAPES, '$1')
|
|
1095
1099
|
.replace(REGEX.SPACING_LIST_NUM_COMBINED, '$1\n\n$2')
|
|
1100
|
+
.replace(REGEX.PUNCT_ONLY_LIST_ARTIFACT, '')
|
|
1096
1101
|
.replace(REGEX.DOUBLE_NEWLINE_REDUCER, '\n\n');
|
|
1097
1102
|
result = normalizeNestedListIndentation(result);
|
|
1098
1103
|
checkAbort('markdown:cleanup:properties');
|
package/dist/lib/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
-
import {
|
|
3
|
+
import { type CachedPayload } from '../schemas/cache.js';
|
|
4
4
|
export declare const serverVersion: string;
|
|
5
5
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
6
6
|
type TransformWorkerMode = 'threads' | 'process';
|
|
@@ -81,6 +81,7 @@ export declare const config: {
|
|
|
81
81
|
maxTotal: number;
|
|
82
82
|
maxPerOwner: number;
|
|
83
83
|
emitStatusNotifications: boolean;
|
|
84
|
+
requireInterception: boolean;
|
|
84
85
|
};
|
|
85
86
|
cache: {
|
|
86
87
|
enabled: boolean;
|
|
@@ -143,12 +144,6 @@ export declare const config: {
|
|
|
143
144
|
runtime: RuntimeState;
|
|
144
145
|
};
|
|
145
146
|
export declare function enableHttpMode(): void;
|
|
146
|
-
declare const CachedPayloadSchema: z.ZodObject<{
|
|
147
|
-
content: z.ZodOptional<z.ZodString>;
|
|
148
|
-
markdown: z.ZodOptional<z.ZodString>;
|
|
149
|
-
title: z.ZodOptional<z.ZodString>;
|
|
150
|
-
}, z.core.$strict>;
|
|
151
|
-
type CachedPayload = z.infer<typeof CachedPayloadSchema>;
|
|
152
147
|
interface CacheEntry {
|
|
153
148
|
url: string;
|
|
154
149
|
title?: string;
|
|
@@ -204,12 +199,14 @@ export declare function unregisterMcpSessionServerByServer(server: McpServer): v
|
|
|
204
199
|
export declare function resolveMcpSessionIdByServer(server: McpServer): string | undefined;
|
|
205
200
|
export declare function runWithRequestContext<T>(context: RequestContext, fn: () => T): T;
|
|
206
201
|
export declare function getRequestId(): string | undefined;
|
|
202
|
+
export declare function getSessionId(): string | undefined;
|
|
207
203
|
export declare function getOperationId(): string | undefined;
|
|
208
204
|
export declare function logInfo(message: string, meta?: LogMetadata): void;
|
|
209
205
|
export declare function logDebug(message: string, meta?: LogMetadata): void;
|
|
210
206
|
export declare function logWarn(message: string, meta?: LogMetadata): void;
|
|
211
207
|
export declare function logError(message: string, error?: Error | LogMetadata): void;
|
|
212
|
-
export declare function
|
|
208
|
+
export declare function getMcpLogLevel(sessionId?: string): LogLevel;
|
|
209
|
+
export declare function setLogLevel(level: string, sessionId?: string): void;
|
|
213
210
|
export declare function redactUrl(rawUrl: string): string;
|
|
214
211
|
export interface SessionEntry {
|
|
215
212
|
readonly server: McpServer;
|
|
@@ -253,3 +250,4 @@ export declare function ensureSessionCapacity({ store, maxSessions, evictOldest,
|
|
|
253
250
|
evictOldest: (store: SessionStore) => boolean;
|
|
254
251
|
}): boolean;
|
|
255
252
|
export {};
|
|
253
|
+
//# sourceMappingURL=core.d.ts.map
|