@j0hanz/fetch-url-mcp 1.12.4 → 1.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/auth.js +44 -29
- package/dist/http/helpers.d.ts.map +1 -1
- package/dist/http/helpers.js +22 -12
- package/dist/http/native.d.ts.map +1 -1
- package/dist/http/native.js +30 -29
- package/dist/http/rate-limit.d.ts.map +1 -1
- package/dist/http/rate-limit.js +5 -3
- package/dist/index.js +3 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +11 -7
- package/dist/lib/core.d.ts.map +1 -1
- package/dist/lib/core.js +12 -9
- package/dist/lib/error-codes.d.ts +11 -0
- package/dist/lib/error-codes.d.ts.map +1 -0
- package/dist/lib/error-codes.js +15 -0
- package/dist/lib/error-messages.d.ts +13 -0
- package/dist/lib/error-messages.d.ts.map +1 -0
- package/dist/lib/error-messages.js +51 -0
- package/dist/lib/fetch-pipeline.d.ts.map +1 -1
- package/dist/lib/fetch-pipeline.js +5 -4
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +74 -41
- package/dist/lib/logger-names.d.ts +14 -0
- package/dist/lib/logger-names.d.ts.map +1 -0
- package/dist/lib/logger-names.js +13 -0
- package/dist/lib/mcp-interop.d.ts +1 -11
- package/dist/lib/mcp-interop.d.ts.map +1 -1
- package/dist/lib/mcp-interop.js +10 -73
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/session.js +2 -1
- package/dist/lib/tool-errors.d.ts +39 -0
- package/dist/lib/tool-errors.d.ts.map +1 -0
- package/dist/lib/tool-errors.js +252 -0
- package/dist/lib/url.d.ts.map +1 -1
- package/dist/lib/url.js +18 -15
- package/dist/lib/utils.d.ts +4 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +18 -9
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -6
- package/dist/tasks/call-contract.d.ts.map +1 -1
- package/dist/tasks/call-contract.js +8 -10
- package/dist/tasks/execution.d.ts.map +1 -1
- package/dist/tasks/execution.js +17 -14
- package/dist/tasks/handlers.d.ts.map +1 -1
- package/dist/tasks/handlers.js +9 -8
- package/dist/tasks/manager.d.ts.map +1 -1
- package/dist/tasks/manager.js +14 -13
- package/dist/tasks/owner.d.ts +0 -1
- package/dist/tasks/owner.d.ts.map +1 -1
- package/dist/tasks/owner.js +0 -25
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +14 -26
- package/dist/transform/dom-prep.d.ts.map +1 -1
- package/dist/transform/dom-prep.js +10 -8
- package/dist/transform/shared.d.ts.map +1 -1
- package/dist/transform/shared.js +2 -1
- package/dist/transform/transform.d.ts.map +1 -1
- package/dist/transform/transform.js +29 -21
- package/dist/transform/worker-pool.d.ts.map +1 -1
- package/dist/transform/worker-pool.js +16 -12
- package/package.json +1 -1
package/dist/http/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EACL,iBAAiB,EAElB,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;
|
|
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { InvalidTokenError, ServerError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
3
3
|
import { config, logDebug, logWarn } from '../lib/core.js';
|
|
4
|
+
import { LOG_AUTH } from '../lib/logger-names.js';
|
|
4
5
|
import { normalizeHost } from '../lib/url.js';
|
|
5
6
|
import { composeAbortSignal, hmacSha256Hex, isObject, parseUrlOrNull, timingSafeEqualUtf8, } from '../lib/utils.js';
|
|
6
7
|
import { getHeaderValue, sendEmpty, sendError, sendJson, } from './helpers.js';
|
|
@@ -166,16 +167,16 @@ export function assertHttpModeConfiguration() {
|
|
|
166
167
|
const isLoopback = configuredHost !== null && LOOPBACK_HOSTS.has(configuredHost);
|
|
167
168
|
const isRemoteBinding = !isLoopback;
|
|
168
169
|
if (isRemoteBinding && !config.security.allowRemote) {
|
|
169
|
-
throw
|
|
170
|
+
throw Error('ALLOW_REMOTE must be true to bind to non-loopback interfaces');
|
|
170
171
|
}
|
|
171
172
|
if (isRemoteBinding && config.auth.mode !== 'oauth') {
|
|
172
|
-
throw
|
|
173
|
+
throw Error('OAuth authentication is required for remote bindings');
|
|
173
174
|
}
|
|
174
175
|
if (config.auth.mode === 'oauth' && !config.auth.issuerUrl) {
|
|
175
|
-
throw
|
|
176
|
+
throw Error('OAuth mode requires OAUTH_ISSUER_URL to serve RFC9728 metadata');
|
|
176
177
|
}
|
|
177
178
|
if (config.auth.mode === 'static' && config.auth.staticTokens.length === 0) {
|
|
178
|
-
throw
|
|
179
|
+
throw Error('Static auth requires ACCESS_TOKENS or API_KEY to be configured');
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
// ---------------------------------------------------------------------------
|
|
@@ -260,7 +261,7 @@ class AuthService {
|
|
|
260
261
|
source,
|
|
261
262
|
clientId: info.clientId,
|
|
262
263
|
scopeCount: info.scopes.length,
|
|
263
|
-
},
|
|
264
|
+
}, LOG_AUTH);
|
|
264
265
|
return info;
|
|
265
266
|
}
|
|
266
267
|
authenticateWithToken(token, signal) {
|
|
@@ -274,21 +275,25 @@ class AuthService {
|
|
|
274
275
|
return this.verifyStaticToken(apiKey);
|
|
275
276
|
}
|
|
276
277
|
if (apiKey && config.auth.mode === 'oauth') {
|
|
277
|
-
logWarn('Auth failed: X-API-Key not supported for OAuth', {},
|
|
278
|
-
|
|
278
|
+
logWarn('Auth failed: X-API-Key not supported for OAuth', {}, LOG_AUTH);
|
|
279
|
+
const error = new InvalidTokenError('X-API-Key not supported for OAuth');
|
|
280
|
+
throw error;
|
|
279
281
|
}
|
|
280
|
-
logWarn('Auth failed: missing credentials', { authMode: config.auth.mode },
|
|
281
|
-
|
|
282
|
+
logWarn('Auth failed: missing credentials', { authMode: config.auth.mode }, LOG_AUTH);
|
|
283
|
+
const error = new InvalidTokenError(config.auth.mode === 'static'
|
|
282
284
|
? 'Missing Authorization or X-API-Key header'
|
|
283
285
|
: 'Missing Authorization header');
|
|
286
|
+
throw error;
|
|
284
287
|
}
|
|
285
288
|
resolveBearerToken(authHeader) {
|
|
286
289
|
if (!authHeader.startsWith('Bearer ')) {
|
|
287
|
-
|
|
290
|
+
const error = new InvalidTokenError('Invalid Authorization header format');
|
|
291
|
+
throw error;
|
|
288
292
|
}
|
|
289
293
|
const token = authHeader.substring(7);
|
|
290
294
|
if (!token) {
|
|
291
|
-
|
|
295
|
+
const error = new InvalidTokenError('Invalid Authorization header format');
|
|
296
|
+
throw error;
|
|
292
297
|
}
|
|
293
298
|
return token;
|
|
294
299
|
}
|
|
@@ -303,13 +308,15 @@ class AuthService {
|
|
|
303
308
|
}
|
|
304
309
|
verifyStaticToken(token) {
|
|
305
310
|
if (this.staticTokenDigests.length === 0) {
|
|
306
|
-
|
|
311
|
+
const error = new InvalidTokenError('No static tokens configured');
|
|
312
|
+
throw error;
|
|
307
313
|
}
|
|
308
314
|
const tokenDigest = hmacSha256Hex(STATIC_TOKEN_HMAC_KEY, token);
|
|
309
315
|
const matched = hasConstantTimeMatch(this.staticTokenDigests, tokenDigest);
|
|
310
316
|
if (!matched) {
|
|
311
|
-
logWarn('Auth failed: invalid static token', {},
|
|
312
|
-
|
|
317
|
+
logWarn('Auth failed: invalid static token', {}, LOG_AUTH);
|
|
318
|
+
const error = new InvalidTokenError('Invalid token');
|
|
319
|
+
throw error;
|
|
313
320
|
}
|
|
314
321
|
return this.buildStaticAuthInfo(token);
|
|
315
322
|
}
|
|
@@ -348,18 +355,21 @@ class AuthService {
|
|
|
348
355
|
assertTokenAudience(payload) {
|
|
349
356
|
const expected = this.canonicalizeResourceUri(this.stripHash(config.auth.resourceUrl));
|
|
350
357
|
if (!expected) {
|
|
351
|
-
|
|
358
|
+
const error = new ServerError('Configured resource URL is invalid');
|
|
359
|
+
throw error;
|
|
352
360
|
}
|
|
353
361
|
const audiences = this.readAudienceValues(payload)
|
|
354
362
|
.map((value) => this.canonicalizeResourceUri(value))
|
|
355
363
|
.filter((value) => value !== null);
|
|
356
364
|
if (audiences.length === 0) {
|
|
357
|
-
logWarn('Auth failed: token missing audience binding', {},
|
|
358
|
-
|
|
365
|
+
logWarn('Auth failed: token missing audience binding', {}, LOG_AUTH);
|
|
366
|
+
const error = new InvalidTokenError('Token missing audience binding');
|
|
367
|
+
throw error;
|
|
359
368
|
}
|
|
360
369
|
if (!audiences.includes(expected)) {
|
|
361
|
-
logWarn('Auth failed: audience mismatch', {},
|
|
362
|
-
|
|
370
|
+
logWarn('Auth failed: audience mismatch', {}, LOG_AUTH);
|
|
371
|
+
const error = new InvalidTokenError('Token audience does not match this MCP server');
|
|
372
|
+
throw error;
|
|
363
373
|
}
|
|
364
374
|
}
|
|
365
375
|
buildBasicAuthHeader(clientId, clientSecret) {
|
|
@@ -393,8 +403,9 @@ class AuthService {
|
|
|
393
403
|
if (response.body) {
|
|
394
404
|
await response.body.cancel();
|
|
395
405
|
}
|
|
396
|
-
logWarn('Token introspection HTTP error', { status: response.status },
|
|
397
|
-
|
|
406
|
+
logWarn('Token introspection HTTP error', { status: response.status }, LOG_AUTH);
|
|
407
|
+
const error = new ServerError(`Token introspection failed: ${response.status}`);
|
|
408
|
+
throw error;
|
|
398
409
|
}
|
|
399
410
|
return response.json();
|
|
400
411
|
}
|
|
@@ -419,33 +430,36 @@ class AuthService {
|
|
|
419
430
|
const tokenScopeSet = new Set(tokenScopes);
|
|
420
431
|
const missing = requiredScopes.filter((s) => !tokenScopeSet.has(s));
|
|
421
432
|
if (missing.length > 0) {
|
|
422
|
-
logWarn('Auth failed: insufficient scopes', { missingCount: missing.length },
|
|
423
|
-
|
|
433
|
+
logWarn('Auth failed: insufficient scopes', { missingCount: missing.length }, LOG_AUTH);
|
|
434
|
+
const error = new InsufficientScopeError(missing);
|
|
435
|
+
throw error;
|
|
424
436
|
}
|
|
425
437
|
}
|
|
426
438
|
async verifyWithIntrospection(token, signal) {
|
|
427
439
|
if (!config.auth.introspectionUrl) {
|
|
428
|
-
|
|
440
|
+
const error = new ServerError('Introspection not configured');
|
|
441
|
+
throw error;
|
|
429
442
|
}
|
|
430
443
|
const cacheKey = hmacSha256Hex(STATIC_TOKEN_HMAC_KEY, token);
|
|
431
444
|
const cached = this.introspectionCache.get(cacheKey);
|
|
432
445
|
if (cached && cached.expiresAt > Date.now()) {
|
|
433
446
|
this.introspectionCache.delete(cacheKey);
|
|
434
447
|
this.introspectionCache.set(cacheKey, cached);
|
|
435
|
-
logDebug('Token introspection cache hit', {},
|
|
448
|
+
logDebug('Token introspection cache hit', {}, LOG_AUTH);
|
|
436
449
|
return cached.info;
|
|
437
450
|
}
|
|
438
451
|
const req = this.buildIntrospectionRequest(token, config.auth.resourceUrl, config.auth.clientId, config.auth.clientSecret);
|
|
439
452
|
const payload = await this.requestIntrospection(config.auth.introspectionUrl, req, config.auth.introspectionTimeoutMs, signal);
|
|
440
453
|
if (!isObject(payload) || payload['active'] !== true) {
|
|
441
454
|
this.introspectionCache.delete(cacheKey);
|
|
442
|
-
logWarn('Auth failed: token inactive', {},
|
|
443
|
-
|
|
455
|
+
logWarn('Auth failed: token inactive', {}, LOG_AUTH);
|
|
456
|
+
const error = new InvalidTokenError('Token is inactive');
|
|
457
|
+
throw error;
|
|
444
458
|
}
|
|
445
459
|
this.assertTokenAudience(payload);
|
|
446
460
|
const info = this.buildIntrospectionAuthInfo(token, payload);
|
|
447
461
|
this.assertRequiredScopes(info.scopes);
|
|
448
|
-
logDebug('Token introspection successful', { clientId: info.clientId },
|
|
462
|
+
logDebug('Token introspection successful', { clientId: info.clientId }, LOG_AUTH);
|
|
449
463
|
this.evictStaleEntries();
|
|
450
464
|
this.introspectionCache.set(cacheKey, {
|
|
451
465
|
info,
|
|
@@ -514,7 +528,8 @@ export function applyInsufficientScopeAuthHeaders(req, res, requiredScopes, mess
|
|
|
514
528
|
export function buildProtectedResourceMetadataDocument(req) {
|
|
515
529
|
const urls = buildRequestScopedProtectedResourceUrls(req);
|
|
516
530
|
if (!config.auth.issuerUrl) {
|
|
517
|
-
|
|
531
|
+
const error = new ServerError('OAuth issuer URL is required for protected resource metadata');
|
|
532
|
+
throw error;
|
|
518
533
|
}
|
|
519
534
|
return {
|
|
520
535
|
resource: urls.resource,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,YAAY,CAAC;AAKxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACxG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/http/helpers.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Writable } from 'node:stream';
|
|
|
2
2
|
import { pipeline } from 'node:stream/promises';
|
|
3
3
|
import { composeCloseHandlers, config, logWarn } from '../lib/core.js';
|
|
4
4
|
import { resolveMcpSessionIdByServer, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
|
|
5
|
+
import { LOG_HTTP } from '../lib/logger-names.js';
|
|
5
6
|
import { createDefaultBlockList, normalizeIpForBlockList } from '../lib/url.js';
|
|
6
7
|
import { getErrorMessage, toError } from '../lib/utils.js';
|
|
7
8
|
function abortControllerBestEffort(controller) {
|
|
@@ -154,7 +155,7 @@ export function registerInboundBlockList(server) {
|
|
|
154
155
|
logWarn('Blocked inbound connection', {
|
|
155
156
|
remoteAddress: normalized.ip,
|
|
156
157
|
family: normalized.family,
|
|
157
|
-
},
|
|
158
|
+
}, LOG_HTTP);
|
|
158
159
|
socket.destroy();
|
|
159
160
|
}
|
|
160
161
|
});
|
|
@@ -186,7 +187,7 @@ export async function closeTransportBestEffort(transport, context) {
|
|
|
186
187
|
await transport.close();
|
|
187
188
|
}
|
|
188
189
|
catch (error) {
|
|
189
|
-
logWarn('Transport close failed', { context, error },
|
|
190
|
+
logWarn('Transport close failed', { context, error }, LOG_HTTP);
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
export async function closeMcpServerBestEffort(server, context) {
|
|
@@ -194,7 +195,7 @@ export async function closeMcpServerBestEffort(server, context) {
|
|
|
194
195
|
await server.close();
|
|
195
196
|
}
|
|
196
197
|
catch (error) {
|
|
197
|
-
logWarn('MCP server close failed', { context, error },
|
|
198
|
+
logWarn('MCP server close failed', { context, error }, LOG_HTTP);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
export function createTransportAdapter(transportImpl) {
|
|
@@ -256,11 +257,13 @@ class JsonBodyReader {
|
|
|
256
257
|
if (contentLengthHeader) {
|
|
257
258
|
const contentLength = Number.parseInt(contentLengthHeader, 10);
|
|
258
259
|
if (Number.isFinite(contentLength) && contentLength > limit) {
|
|
259
|
-
|
|
260
|
+
const error = new JsonBodyError('payload-too-large', 'Payload too large');
|
|
261
|
+
throw error;
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
if (signal?.aborted || isRequestReadAborted(req)) {
|
|
263
|
-
|
|
265
|
+
const error = new JsonBodyError('read-failed', 'Request aborted');
|
|
266
|
+
throw error;
|
|
264
267
|
}
|
|
265
268
|
const body = await this.readBody(req, limit, signal);
|
|
266
269
|
if (!body)
|
|
@@ -269,7 +272,8 @@ class JsonBodyReader {
|
|
|
269
272
|
return JSON.parse(body);
|
|
270
273
|
}
|
|
271
274
|
catch (err) {
|
|
272
|
-
|
|
275
|
+
const error = new JsonBodyError('invalid-json', getErrorMessage(err));
|
|
276
|
+
throw error;
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
279
|
async readBody(req, limit, signal) {
|
|
@@ -296,7 +300,8 @@ class JsonBodyReader {
|
|
|
296
300
|
combined.set(chunk, offset);
|
|
297
301
|
offset += chunk.byteLength;
|
|
298
302
|
}
|
|
299
|
-
|
|
303
|
+
const text = new TextDecoder().decode(combined);
|
|
304
|
+
return text;
|
|
300
305
|
}
|
|
301
306
|
finally {
|
|
302
307
|
if (signal && abortListener) {
|
|
@@ -335,7 +340,8 @@ class JsonBodyReader {
|
|
|
335
340
|
});
|
|
336
341
|
try {
|
|
337
342
|
if (signal?.aborted || isRequestReadAborted(req)) {
|
|
338
|
-
|
|
343
|
+
const error = new JsonBodyError('read-failed', 'Request aborted');
|
|
344
|
+
throw error;
|
|
339
345
|
}
|
|
340
346
|
await pipeline(req, sink, signal ? { signal } : undefined);
|
|
341
347
|
return { chunks, size };
|
|
@@ -344,14 +350,18 @@ class JsonBodyReader {
|
|
|
344
350
|
if (err instanceof JsonBodyError)
|
|
345
351
|
throw err;
|
|
346
352
|
if (signal?.aborted || isRequestReadAborted(req)) {
|
|
347
|
-
|
|
353
|
+
const error = new JsonBodyError('read-failed', 'Request aborted');
|
|
354
|
+
throw error;
|
|
348
355
|
}
|
|
349
|
-
|
|
356
|
+
const error = new JsonBodyError('read-failed', getErrorMessage(err));
|
|
357
|
+
throw error;
|
|
350
358
|
}
|
|
351
359
|
}
|
|
352
360
|
normalizeChunk(chunk) {
|
|
353
|
-
if (typeof chunk === 'string')
|
|
354
|
-
|
|
361
|
+
if (typeof chunk === 'string') {
|
|
362
|
+
const encoded = new TextEncoder().encode(chunk);
|
|
363
|
+
return encoded;
|
|
364
|
+
}
|
|
355
365
|
return chunk;
|
|
356
366
|
}
|
|
357
367
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AAo+CA,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,CA2DD"}
|
package/dist/http/native.js
CHANGED
|
@@ -8,6 +8,7 @@ import process from 'node:process';
|
|
|
8
8
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
9
9
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
10
10
|
import { composeCloseHandlers, config, createSessionStore, createSlotTracker, enableHttpMode, ensureSessionCapacity, logDebug, logError, logInfo, logWarn, registerMcpSessionOwnerKey, registerMcpSessionServer, reserveSessionSlot, runWithRequestContext, startSessionCleanupLoop, } from '../lib/core.js';
|
|
11
|
+
import { LOG_AUTH, LOG_HTTP, LOG_SESSION } from '../lib/logger-names.js';
|
|
11
12
|
import { acceptsEventStream, acceptsJsonAndEventStream, isJsonRpcBatchRequest, isMcpMessageBody, isMcpRequestBody, } from '../lib/mcp-interop.js';
|
|
12
13
|
import { applyHttpServerTuning, drainConnectionsOnShutdown, isObject, toError, } from '../lib/utils.js';
|
|
13
14
|
import { createMcpServerForHttpSession } from '../server.js';
|
|
@@ -55,7 +56,7 @@ function logGatewayRejection(params) {
|
|
|
55
56
|
...rest,
|
|
56
57
|
...(rpcId === null || rpcId === undefined ? {} : { rpcId }),
|
|
57
58
|
...(details ?? {}),
|
|
58
|
-
},
|
|
59
|
+
}, LOG_HTTP);
|
|
59
60
|
}
|
|
60
61
|
function resolveRequestPath(req) {
|
|
61
62
|
return URL.parse(req.url ?? '', 'http://localhost')?.pathname ?? '/';
|
|
@@ -70,14 +71,14 @@ function logRequestCompletion(params) {
|
|
|
70
71
|
...(params.sessionId ? { sessionId: params.sessionId } : {}),
|
|
71
72
|
};
|
|
72
73
|
if (params.statusCode >= 500) {
|
|
73
|
-
logError('HTTP request
|
|
74
|
+
logError('HTTP request failed with server error', meta, LOG_HTTP);
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
if (params.statusCode >= 400) {
|
|
77
|
-
logWarn('HTTP
|
|
78
|
+
logWarn('HTTP client error', meta, LOG_HTTP);
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
|
-
logDebug('HTTP request completed', meta,
|
|
81
|
+
logDebug('HTTP request completed', meta, LOG_HTTP);
|
|
81
82
|
}
|
|
82
83
|
function createSessionTeardownOptions(mode, context) {
|
|
83
84
|
switch (mode) {
|
|
@@ -136,7 +137,7 @@ class McpSessionGateway {
|
|
|
136
137
|
method: method ?? 'response',
|
|
137
138
|
rpcId: body.id,
|
|
138
139
|
sessionId,
|
|
139
|
-
},
|
|
140
|
+
}, LOG_HTTP);
|
|
140
141
|
const transport = await this.getOrCreateTransport(ctx, requestId);
|
|
141
142
|
if (!transport)
|
|
142
143
|
return;
|
|
@@ -164,7 +165,7 @@ class McpSessionGateway {
|
|
|
164
165
|
});
|
|
165
166
|
return;
|
|
166
167
|
}
|
|
167
|
-
logDebug('MCP GET received', { sessionId },
|
|
168
|
+
logDebug('MCP GET received', { sessionId }, LOG_HTTP);
|
|
168
169
|
this.store.touch(sessionId);
|
|
169
170
|
await session.transport.handleRequest(ctx.req, ctx.res);
|
|
170
171
|
}
|
|
@@ -176,7 +177,7 @@ class McpSessionGateway {
|
|
|
176
177
|
return;
|
|
177
178
|
const { sessionId, session } = sessionState;
|
|
178
179
|
await session.transport.close();
|
|
179
|
-
logDebug('MCP DELETE received', { sessionId },
|
|
180
|
+
logDebug('MCP DELETE received', { sessionId }, LOG_HTTP);
|
|
180
181
|
this.cleanupSessionRecord(sessionId, createSessionTeardownOptions('ended', 'session-delete'));
|
|
181
182
|
sendJson(ctx.res, 200, { status: 'closed' });
|
|
182
183
|
}
|
|
@@ -443,7 +444,7 @@ class McpSessionGateway {
|
|
|
443
444
|
this.clearSessionInitTimeout(sessionId);
|
|
444
445
|
if (sessionId)
|
|
445
446
|
this.store.touch(sessionId);
|
|
446
|
-
logDebug('Session initialized', { sessionId },
|
|
447
|
+
logDebug('Session initialized', { sessionId }, LOG_SESSION);
|
|
447
448
|
}
|
|
448
449
|
createSessionInitTimeout(sessionId, tracker, unpublishedSession) {
|
|
449
450
|
const initTimeout = setTimeout(() => {
|
|
@@ -453,11 +454,11 @@ class McpSessionGateway {
|
|
|
453
454
|
this.clearSessionInitTimeout(sessionId);
|
|
454
455
|
return;
|
|
455
456
|
}
|
|
456
|
-
logWarn('Session init timeout', { sessionId },
|
|
457
|
+
logWarn('Session init timeout', { sessionId }, LOG_SESSION);
|
|
457
458
|
this.cleanupSessionRecord(sessionId, createSessionTeardownOptions('init-timeout'));
|
|
458
459
|
return;
|
|
459
460
|
}
|
|
460
|
-
logWarn('Session init timeout before registration completed', { sessionId },
|
|
461
|
+
logWarn('Session init timeout before registration completed', { sessionId }, LOG_SESSION);
|
|
461
462
|
tracker.releaseSlot();
|
|
462
463
|
void teardownUnregisteredSessionResources(unpublishedSession, 'session-init-timeout');
|
|
463
464
|
}, config.server.sessionInitTimeoutMs);
|
|
@@ -480,7 +481,7 @@ class McpSessionGateway {
|
|
|
480
481
|
logWarn('Session transport connect failed', {
|
|
481
482
|
sessionId,
|
|
482
483
|
error: toError(err).message,
|
|
483
|
-
},
|
|
484
|
+
}, LOG_SESSION);
|
|
484
485
|
clearTimeout(initTimeout);
|
|
485
486
|
tracker.releaseSlot();
|
|
486
487
|
void teardownUnregisteredSessionResources(unpublishedSession, 'session-connect-failed');
|
|
@@ -494,7 +495,7 @@ class McpSessionGateway {
|
|
|
494
495
|
logError('Session creation failed: missing auth context', {
|
|
495
496
|
path: ctx.url.pathname,
|
|
496
497
|
method: ctx.method,
|
|
497
|
-
},
|
|
498
|
+
}, LOG_SESSION);
|
|
498
499
|
sendError(ctx.res, -32603, "We're missing some authorization details to process this request.", 500, requestId);
|
|
499
500
|
return null;
|
|
500
501
|
}
|
|
@@ -503,7 +504,7 @@ class McpSessionGateway {
|
|
|
503
504
|
logError('Session creation failed: missing task owner context', {
|
|
504
505
|
path: ctx.url.pathname,
|
|
505
506
|
method: ctx.method,
|
|
506
|
-
},
|
|
507
|
+
}, LOG_SESSION);
|
|
507
508
|
sendError(ctx.res, -32603, "We're missing the owner information needed to authorize this request.", 500, requestId);
|
|
508
509
|
return null;
|
|
509
510
|
}
|
|
@@ -516,7 +517,7 @@ class McpSessionGateway {
|
|
|
516
517
|
sessionServer = await this.createSessionServer();
|
|
517
518
|
}
|
|
518
519
|
catch (error) {
|
|
519
|
-
logError('Session server creation failed', { sessionId: newSessionId, error: toError(error).message },
|
|
520
|
+
logError('Session server creation failed', { sessionId: newSessionId, error: toError(error).message }, LOG_SESSION);
|
|
520
521
|
tracker.releaseSlot();
|
|
521
522
|
throw error;
|
|
522
523
|
}
|
|
@@ -531,7 +532,7 @@ class McpSessionGateway {
|
|
|
531
532
|
const isConnected = await this.connectTransport(sessionServer, transportImpl, initTimeout, tracker, unpublishedSession, newSessionId);
|
|
532
533
|
tracker.releaseSlot();
|
|
533
534
|
if (!isConnected) {
|
|
534
|
-
logWarn('Session closed before registration completed', { sessionId: newSessionId },
|
|
535
|
+
logWarn('Session closed before registration completed', { sessionId: newSessionId }, LOG_SESSION);
|
|
535
536
|
void teardownUnregisteredSessionResources(unpublishedSession, 'session-closed-during-connect');
|
|
536
537
|
return null;
|
|
537
538
|
}
|
|
@@ -547,7 +548,7 @@ class McpSessionGateway {
|
|
|
547
548
|
this.sessionInitTimeouts.set(newSessionId, initTimeout);
|
|
548
549
|
registerMcpSessionOwnerKey(newSessionId, ownerKey);
|
|
549
550
|
registerMcpSessionServer(newSessionId, sessionServer);
|
|
550
|
-
logInfo('Session created', { sessionId: newSessionId, negotiatedProtocolVersion },
|
|
551
|
+
logInfo('Session created', { sessionId: newSessionId, negotiatedProtocolVersion }, LOG_SESSION);
|
|
551
552
|
transportImpl.onclose = composeCloseHandlers(transportImpl.onclose, () => {
|
|
552
553
|
this.cleanupSessionRecord(newSessionId, createSessionTeardownOptions('ended', 'session-close'));
|
|
553
554
|
});
|
|
@@ -557,7 +558,7 @@ class McpSessionGateway {
|
|
|
557
558
|
const context = teardownOptions.closeTransportReason ??
|
|
558
559
|
teardownOptions.closeServerReason ??
|
|
559
560
|
'session';
|
|
560
|
-
logDebug('Session cleanup', { sessionId, context },
|
|
561
|
+
logDebug('Session cleanup', { sessionId, context }, LOG_SESSION);
|
|
561
562
|
this.clearSessionInitTimeout(sessionId);
|
|
562
563
|
const session = this.store.remove(sessionId);
|
|
563
564
|
if (!session)
|
|
@@ -587,13 +588,13 @@ class McpSessionGateway {
|
|
|
587
588
|
},
|
|
588
589
|
});
|
|
589
590
|
if (!allowed) {
|
|
590
|
-
logWarn('Session capacity exhausted', { maxSessions: config.server.maxSessions },
|
|
591
|
+
logWarn('Session capacity exhausted', { maxSessions: config.server.maxSessions }, LOG_SESSION);
|
|
591
592
|
sendError(res, -32000, 'The server is currently too busy to handle your request. Please try again in a little while.', 503, requestId);
|
|
592
593
|
return false;
|
|
593
594
|
}
|
|
594
595
|
// Double-check: capacity may have changed during the async eviction window above.
|
|
595
596
|
if (!reserveSessionSlot(this.store, config.server.maxSessions)) {
|
|
596
|
-
logWarn('Session capacity exhausted (post-eviction)', { maxSessions: config.server.maxSessions },
|
|
597
|
+
logWarn('Session capacity exhausted (post-eviction)', { maxSessions: config.server.maxSessions }, LOG_SESSION);
|
|
597
598
|
sendError(res, -32000, 'The server is currently too busy to handle your request. Please try again in a little while.', 503, requestId);
|
|
598
599
|
return false;
|
|
599
600
|
}
|
|
@@ -661,7 +662,7 @@ class HttpDispatcher {
|
|
|
661
662
|
}
|
|
662
663
|
catch (err) {
|
|
663
664
|
const error = toError(err);
|
|
664
|
-
logError('Request failed', error,
|
|
665
|
+
logError('Request failed', error, LOG_HTTP);
|
|
665
666
|
if (!ctx.res.writableEnded) {
|
|
666
667
|
sendJson(ctx.res, 500, {
|
|
667
668
|
error: "Something went wrong on our end. We're looking into it!",
|
|
@@ -690,7 +691,7 @@ class HttpDispatcher {
|
|
|
690
691
|
}
|
|
691
692
|
catch (err) {
|
|
692
693
|
const message = err instanceof Error ? err.message : 'Unauthorized';
|
|
693
|
-
logWarn('Authentication failed', { message, method: ctx.method, path: ctx.url.pathname },
|
|
694
|
+
logWarn('Authentication failed', { message, method: ctx.method, path: ctx.url.pathname }, LOG_AUTH);
|
|
694
695
|
if (isInsufficientScopeError(err)) {
|
|
695
696
|
applyInsufficientScopeAuthHeaders(ctx.req, ctx.res, err.requiredScopes, message);
|
|
696
697
|
sendError(ctx.res, -32000, message, 403);
|
|
@@ -838,10 +839,10 @@ class HttpRequestPipeline {
|
|
|
838
839
|
catch (error) {
|
|
839
840
|
const bodyErrorKind = isJsonBodyError(error) ? error.kind : null;
|
|
840
841
|
if (bodyErrorKind === 'payload-too-large') {
|
|
841
|
-
logWarn('The request body is too large. Please send a smaller payload.', { method: ctx.method, path: ctx.url.pathname },
|
|
842
|
+
logWarn('The request body is too large. Please send a smaller payload.', { method: ctx.method, path: ctx.url.pathname }, LOG_HTTP);
|
|
842
843
|
}
|
|
843
844
|
else if (bodyErrorKind === 'read-failed' || bodyErrorKind === null) {
|
|
844
|
-
logError('Request body parsing failed', toError(error),
|
|
845
|
+
logError('Request body parsing failed', toError(error), LOG_HTTP);
|
|
845
846
|
}
|
|
846
847
|
sendBodyParseError(ctx, bodyErrorKind, rawReq);
|
|
847
848
|
return false;
|
|
@@ -863,7 +864,7 @@ class HttpRequestPipeline {
|
|
|
863
864
|
// Server bootstrap
|
|
864
865
|
// ---------------------------------------------------------------------------
|
|
865
866
|
function handlePipelineError(error, res) {
|
|
866
|
-
logError('Request pipeline failed', toError(error),
|
|
867
|
+
logError('Request pipeline failed', toError(error), LOG_HTTP);
|
|
867
868
|
if (res.writableEnded)
|
|
868
869
|
return;
|
|
869
870
|
if (!res.headersSent) {
|
|
@@ -881,7 +882,7 @@ function createNetworkServer(listener) {
|
|
|
881
882
|
}
|
|
882
883
|
const { keyFile, certFile, caFile } = https;
|
|
883
884
|
if (!keyFile || !certFile) {
|
|
884
|
-
throw
|
|
885
|
+
throw Error('HTTPS enabled but SERVER_TLS_KEY_FILE / SERVER_TLS_CERT_FILE are missing');
|
|
885
886
|
}
|
|
886
887
|
let tlsOptions;
|
|
887
888
|
try {
|
|
@@ -894,7 +895,7 @@ function createNetworkServer(listener) {
|
|
|
894
895
|
}
|
|
895
896
|
}
|
|
896
897
|
catch (err) {
|
|
897
|
-
throw
|
|
898
|
+
throw Error(`Failed to read TLS files (key=${keyFile}, cert=${certFile}): ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
898
899
|
}
|
|
899
900
|
return createHttpsServer({ ...tlsOptions, keepAlive: true, noDelay: true }, listener);
|
|
900
901
|
}
|
|
@@ -911,7 +912,7 @@ function resolveListeningPort(server, fallback) {
|
|
|
911
912
|
function createShutdownHandler(options) {
|
|
912
913
|
const closeBatchSize = 10;
|
|
913
914
|
return async (signal) => {
|
|
914
|
-
logInfo(`Stopping HTTP server (${signal})...`, undefined,
|
|
915
|
+
logInfo(`Stopping HTTP server (${signal})...`, undefined, LOG_HTTP);
|
|
915
916
|
options.rateLimiter.stop();
|
|
916
917
|
options.sessionCleanup.abort();
|
|
917
918
|
drainConnectionsOnShutdown(options.server);
|
|
@@ -924,7 +925,7 @@ function createShutdownHandler(options) {
|
|
|
924
925
|
}));
|
|
925
926
|
for (const r of results) {
|
|
926
927
|
if (r.status === 'rejected') {
|
|
927
|
-
logError('Session teardown failed during shutdown', r.reason instanceof Error ? r.reason : undefined,
|
|
928
|
+
logError('Session teardown failed during shutdown', r.reason instanceof Error ? r.reason : undefined, LOG_HTTP);
|
|
928
929
|
}
|
|
929
930
|
}
|
|
930
931
|
}
|
|
@@ -960,7 +961,7 @@ export async function startHttpServer() {
|
|
|
960
961
|
arch: process.arch,
|
|
961
962
|
hostname: hostname(),
|
|
962
963
|
nodeVersion: process.version,
|
|
963
|
-
},
|
|
964
|
+
}, LOG_HTTP);
|
|
964
965
|
return {
|
|
965
966
|
port,
|
|
966
967
|
host: config.server.host,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/http/rate-limit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/http/rate-limit.ts"],"names":[],"mappings":"AAKA,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;AA2FD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,GACvB,oBAAoB,CAGtB"}
|
package/dist/http/rate-limit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logWarn } from '../lib/core.js';
|
|
2
|
+
import { LOG_RATE_LIMIT } from '../lib/logger-names.js';
|
|
2
3
|
import { isAbortError } from '../lib/utils.js';
|
|
3
4
|
import { startAbortableIntervalLoop } from '../lib/utils.js';
|
|
4
5
|
import { sendJson } from './helpers.js';
|
|
@@ -21,7 +22,7 @@ class RateLimiter {
|
|
|
21
22
|
},
|
|
22
23
|
onError: (err) => {
|
|
23
24
|
if (!isAbortError(err)) {
|
|
24
|
-
logWarn('Rate limit cleanup failed', { error: err },
|
|
25
|
+
logWarn('Rate limit cleanup failed', { error: err }, LOG_RATE_LIMIT);
|
|
25
26
|
}
|
|
26
27
|
},
|
|
27
28
|
});
|
|
@@ -71,7 +72,7 @@ class RateLimiter {
|
|
|
71
72
|
this.store.set(key, entry);
|
|
72
73
|
}
|
|
73
74
|
if (entry.count > this.options.maxRequests) {
|
|
74
|
-
logWarn('Rate limit exceeded', { ip: key },
|
|
75
|
+
logWarn('Rate limit exceeded', { ip: key }, LOG_RATE_LIMIT);
|
|
75
76
|
const retryAfter = Math.max(1, Math.ceil((entry.resetTime - now) / 1000));
|
|
76
77
|
ctx.res.setHeader('Retry-After', String(retryAfter));
|
|
77
78
|
sendJson(ctx.res, 429, { error: 'Rate limit exceeded', retryAfter });
|
|
@@ -84,5 +85,6 @@ class RateLimiter {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
export function createRateLimitManagerImpl(options) {
|
|
87
|
-
|
|
88
|
+
const limiter = new RateLimiter(options);
|
|
89
|
+
return limiter;
|
|
88
90
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import { serverVersion } from './lib/core.js';
|
|
4
4
|
import { logError } from './lib/core.js';
|
|
5
|
+
import { LOG_SERVER } from './lib/logger-names.js';
|
|
5
6
|
import { toError } from './lib/utils.js';
|
|
6
7
|
import { parseCliArgs, renderCliUsage } from './cli.js';
|
|
7
8
|
import { startHttpServer } from './http/native.js';
|
|
@@ -60,13 +61,13 @@ function registerHttpSignalHandlers() {
|
|
|
60
61
|
registerSignalHandlers(['SIGINT', 'SIGTERM'], tryShutdown);
|
|
61
62
|
}
|
|
62
63
|
function writeStartupError(error) {
|
|
63
|
-
logError('Failed to start server', error,
|
|
64
|
+
logError('Failed to start server', error, LOG_SERVER);
|
|
64
65
|
process.stderr.write(`Failed to start server: ${error.message}\n`);
|
|
65
66
|
process.exitCode = 1;
|
|
66
67
|
scheduleForcedExit('Startup failure');
|
|
67
68
|
}
|
|
68
69
|
function handleFatalError(label, error, signal) {
|
|
69
|
-
logError(label, error,
|
|
70
|
+
logError(label, error, LOG_SERVER);
|
|
70
71
|
process.stderr.write(`${label}: ${error.message}\n`);
|
|
71
72
|
process.exitCode = 1;
|
|
72
73
|
if (shouldAttemptShutdown()) {
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAgDA,eAAO,MAAM,aAAa,EAAE,MAA2C,CAAC;AAIxE,MAAM,MAAM,QAAQ,GAChB,OAAO,GACP,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,CAAC;AAkCf,KAAK,mBAAmB,GAAG,SAAS,GAAG,SAAS,CAAC;AACjD,KAAK,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|