@j0hanz/fetch-url-mcp 1.12.0 → 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +34 -17
  2. package/dist/http/auth.d.ts.map +1 -1
  3. package/dist/http/auth.js +61 -20
  4. package/dist/http/helpers.d.ts +1 -1
  5. package/dist/http/helpers.d.ts.map +1 -1
  6. package/dist/http/helpers.js +7 -9
  7. package/dist/http/native.d.ts.map +1 -1
  8. package/dist/http/native.js +271 -54
  9. package/dist/http/rate-limit.d.ts.map +1 -1
  10. package/dist/http/rate-limit.js +2 -1
  11. package/dist/index.js +5 -4
  12. package/dist/lib/config.d.ts +1 -1
  13. package/dist/lib/config.d.ts.map +1 -1
  14. package/dist/lib/config.js +8 -1
  15. package/dist/lib/core.d.ts +8 -4
  16. package/dist/lib/core.d.ts.map +1 -1
  17. package/dist/lib/core.js +240 -73
  18. package/dist/lib/fetch-pipeline.d.ts.map +1 -1
  19. package/dist/lib/fetch-pipeline.js +15 -2
  20. package/dist/lib/http.d.ts.map +1 -1
  21. package/dist/lib/http.js +1 -1
  22. package/dist/lib/mcp-interop.d.ts +15 -3
  23. package/dist/lib/mcp-interop.d.ts.map +1 -1
  24. package/dist/lib/mcp-interop.js +92 -23
  25. package/dist/lib/url.d.ts.map +1 -1
  26. package/dist/lib/url.js +1 -1
  27. package/dist/lib/utils.d.ts.map +1 -1
  28. package/dist/lib/utils.js +2 -2
  29. package/dist/resources/index.d.ts +4 -0
  30. package/dist/resources/index.d.ts.map +1 -1
  31. package/dist/resources/index.js +39 -4
  32. package/dist/schemas.d.ts +5 -5
  33. package/dist/schemas.d.ts.map +1 -1
  34. package/dist/schemas.js +7 -9
  35. package/dist/server.d.ts +3 -1
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +20 -11
  38. package/dist/tasks/execution.d.ts +1 -1
  39. package/dist/tasks/execution.d.ts.map +1 -1
  40. package/dist/tasks/execution.js +72 -25
  41. package/dist/tasks/handlers.d.ts.map +1 -1
  42. package/dist/tasks/handlers.js +31 -24
  43. package/dist/tasks/manager.d.ts +5 -2
  44. package/dist/tasks/manager.d.ts.map +1 -1
  45. package/dist/tasks/manager.js +58 -19
  46. package/dist/tasks/owner.d.ts +5 -0
  47. package/dist/tasks/owner.d.ts.map +1 -1
  48. package/dist/tasks/owner.js +15 -7
  49. package/dist/tasks/registry.d.ts +10 -8
  50. package/dist/tasks/registry.d.ts.map +1 -1
  51. package/dist/tasks/registry.js +27 -15
  52. package/dist/tools/fetch-url.d.ts +2 -0
  53. package/dist/tools/fetch-url.d.ts.map +1 -1
  54. package/dist/tools/fetch-url.js +76 -21
  55. package/dist/transform/dom-prep.d.ts.map +1 -1
  56. package/dist/transform/dom-prep.js +6 -6
  57. package/dist/transform/transform.d.ts.map +1 -1
  58. package/dist/transform/transform.js +17 -14
  59. package/dist/transform/worker-pool.d.ts.map +1 -1
  60. package/dist/transform/worker-pool.js +43 -3
  61. package/package.json +2 -2
package/README.md CHANGED
@@ -18,7 +18,7 @@ By default it runs over stdio. Pass `--http` if you need a proper HTTP endpoint
18
18
 
19
19
  - **HTML to Markdown** — Turns any public web page into clean, readable Markdown with metadata like `title`, `url`, `contentSize`, and `truncated`.
20
20
  - **Smart URL handling** — Recognizes GitHub, GitLab, Bitbucket, and Gist page URLs and rewrites them to raw-content endpoints before fetching.
21
- - **Task mode** — Big or slow pages can run as async MCP tasks with progress updates, instead of blocking.
21
+ - **Task mode** — Big or slow pages can run as async MCP tasks with progress updates, instead of blocking. In HTTP mode, tasks are bound to the authenticated caller rather than a single MCP session, so they can be resumed after reconnecting with the same credentials. Polling task state also exposes `progress` and `total` when available.
22
22
  - **Self-documenting** — Includes an `internal://instructions` resource and a `get-help` prompt so clients know how to use it.
23
23
  - **HTTP mode** — Optionally serves over Streamable HTTP with host/origin validation, bearer or OAuth auth, rate limiting, health checks, and TLS.
24
24
 
@@ -484,7 +484,7 @@ For more info, see [Kilo Code docs](https://kilocode.ai/docs).
484
484
 
485
485
  - **Documentation for LLMs** — Grab a docs page, blog post, or reference article as Markdown and pass it straight into a context window.
486
486
  - **Repository content** — Hand it a GitHub, GitLab, or Bitbucket URL and it resolves the raw content endpoint. Works with Gists too.
487
- - **Slow or large pages** — Task mode lets big fetches run in the background while sending progress updates back to the client.
487
+ - **Slow or large pages** — Task mode lets big fetches run in the background while sending monotonic progress updates back to the client, while `tasks/get` exposes the latest `statusMessage`, `progress`, and `total`.
488
488
 
489
489
  ## Architecture
490
490
 
@@ -530,7 +530,7 @@ For more info, see [Kilo Code docs](https://kilocode.ai/docs).
530
530
 
531
531
  #### `fetch-url`
532
532
 
533
- Takes a URL and returns Markdown. Read-only — no JavaScript execution. Supports running as a background MCP task for large or slow pages.
533
+ Takes a URL and returns Markdown. Read-only — no JavaScript execution. Supports running as a background MCP task for large or slow pages. When task mode is used, `tasks/get` and `tasks/list` include `statusMessage`, `progress`, and `total` whenever progress has been reported.
534
534
 
535
535
  | Parameter | Type | Required | Description |
536
536
  | --------- | -------- | -------- | --------------------------- |
@@ -538,6 +538,23 @@ Takes a URL and returns Markdown. Read-only — no JavaScript execution. Support
538
538
 
539
539
  You get text content back by default. If output validation passes, the response also includes `structuredContent` with typed fields: `url`, `resolvedUrl`, `finalUrl`, `title`, `metadata`, `markdown`, `fetchedAt`, `contentSize`, and `truncated`. A `true` value for `truncated` means the content hit a server-side size limit.
540
540
 
541
+ To opt into progress updates, include `_meta.progressToken` in the tool call. The token may be a string or number. The server may then emit monotonic `notifications/progress` updates, and task mode reuses the same token until the task reaches a terminal state.
542
+
543
+ ```json
544
+ {
545
+ "method": "tools/call",
546
+ "params": {
547
+ "name": "fetch-url",
548
+ "arguments": {
549
+ "url": "https://example.com/docs"
550
+ },
551
+ "_meta": {
552
+ "progressToken": 7
553
+ }
554
+ }
555
+ }
556
+ ```
557
+
541
558
  ```text
542
559
  1. [Client] -- tools/call {name: "fetch-url", arguments} --> [Server]
543
560
  2. [Server] -- dispatch("fetch-url") --> [src/tools/fetch-url.ts]
@@ -560,14 +577,14 @@ You get text content back by default. If output validation passes, the response
560
577
 
561
578
  ## MCP Capabilities
562
579
 
563
- | Capability | Status | Notes |
564
- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------ |
565
- | completions | confirmed | Advertised in `createServerCapabilities()`. |
566
- | logging | confirmed | Advertised in `createServerCapabilities()` and handled through `SetLevelRequestSchema`. |
567
- | resources subscribe/listChanged | confirmed | Advertised in `createServerCapabilities()`. |
568
- | prompts | confirmed | `get-help` is registered during server startup. |
569
- | tasks | confirmed | Advertised in `createServerCapabilities()` and backed by registered task handlers plus optional tool task support. |
570
- | progress notifications | confirmed | Tool execution reports `notifications/progress` updates during fetch and transform stages. |
580
+ | Capability | Status | Notes |
581
+ | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
582
+ | completions | confirmed | Advertised in `createServerCapabilities()`. |
583
+ | logging | confirmed | Advertised in `createServerCapabilities()` and handled through `SetLevelRequestSchema`. |
584
+ | resources subscribe/listChanged | confirmed | Advertised in `createServerCapabilities()`. |
585
+ | prompts | confirmed | `get-help` is registered during server startup. |
586
+ | tasks | confirmed | Advertised in `createServerCapabilities()` and backed by registered task handlers plus optional tool task support. |
587
+ | progress notifications | confirmed | Opt-in via `_meta.progressToken`. Tool execution reports monotonic `notifications/progress` updates during fetch and transform stages, and task-mode progress reuses the caller's token for the task lifetime. |
571
588
 
572
589
  ### Tool Annotations
573
590
 
@@ -642,12 +659,12 @@ All configuration is through environment variables. For basic stdio usage, nothi
642
659
 
643
660
  ### Tasks
644
661
 
645
- | Variable | Default | Notes |
646
- | ---------------------------- | ------- | ------------------------------------------------------ |
647
- | `TASKS_MAX_TOTAL` | `5000` | Total task capacity. |
648
- | `TASKS_MAX_PER_OWNER` | `1000` | Per-owner task cap, clamped to the total cap. |
649
- | `TASKS_STATUS_NOTIFICATIONS` | `false` | Enables status notifications for tasks. |
650
- | `TASKS_REQUIRE_INTERCEPTION` | `true` | Requires interception for task-capable tool execution. |
662
+ | Variable | Default | Notes |
663
+ | ---------------------------- | ------- | ------------------------------------------------------------------------------------ |
664
+ | `TASKS_MAX_TOTAL` | `5000` | Total retained task capacity, including completed/cancelled tasks until they expire. |
665
+ | `TASKS_MAX_PER_OWNER` | `1000` | Per-owner retained task cap, clamped to the total cap. |
666
+ | `TASKS_STATUS_NOTIFICATIONS` | `false` | Enables status notifications for tasks. |
667
+ | `TASKS_REQUIRE_INTERCEPTION` | `true` | Requires interception for task-capable tool execution. |
651
668
 
652
669
  ### Transform Workers
653
670
 
@@ -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;AAY/E,OAAO,EAEL,KAAK,cAAc,EAIpB,MAAM,cAAc,CAAC;AAMtB,cAAM,UAAU;IAId,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;CAuBrC;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC;AAS3C,cAAM,sBAAuB,SAAQ,iBAAiB;IAElD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE;gBAAjC,cAAc,EAAE,SAAS,MAAM,EAAE,EAC1C,OAAO,SAAuB;CAKjC;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,sBAAsB,CAEjC;AAwCD,cAAM,gBAAgB;IACpB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;IA2BtC,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,oBAAoB;IAqB5B,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,MAAM;CAQf;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC;AAMvD,wBAAgB,2BAA2B,IAAI,IAAI,CA2BlD;AAMD,eAAO,MAAM,4BAA4B,eAAe,CAAC;AACzD,eAAO,MAAM,+BAA+B,aAE1C,CAAC;AAEH,UAAU,8BAA8B;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAwBT;AAED,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAQD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,GAAG,SAAS,GACzB,MAAM,GAAG,IAAI,CAWf;AAiBD,cAAM,WAAW;IACf,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEjC;IAEF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA0C;IAEvE,YAAY,CAChB,GAAG,EAAE,eAAe,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,QAAQ,CAAC;IAUpB,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,yBAAyB;YA0BnB,oBAAoB;IAyBlC,OAAO,CAAC,0BAA0B;IAmBlC,OAAO,CAAC,oBAAoB;YAWd,uBAAuB;IAiDrC,OAAO,CAAC,iBAAiB;CAa1B;AA+BD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GAClB,IAAI,CAUN;AAED,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,OAAO,SAA+C,GACrD,IAAI,CAYN;AAED,wBAAgB,sCAAsC,CAAC,GAAG,EAAE,eAAe,GAAG;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAeA;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKzE;AAED,eAAO,MAAM,WAAW,aAAoB,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,6 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import { InvalidTokenError, ServerError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
3
- import { config } from '../lib/core.js';
3
+ import { config, logDebug, logWarn } from '../lib/core.js';
4
4
  import { normalizeHost } from '../lib/url.js';
5
5
  import { composeAbortSignal, hmacSha256Hex, isObject, parseUrlOrNull, timingSafeEqualUtf8, } from '../lib/utils.js';
6
6
  import { getHeaderValue, sendEmpty, sendError, sendJson, } from './helpers.js';
@@ -74,26 +74,28 @@ function buildAllowedHosts() {
74
74
  const ALLOWED_HOSTS = buildAllowedHosts();
75
75
  class HostOriginPolicy {
76
76
  validate(ctx) {
77
- const { req, res } = ctx;
77
+ const { req } = ctx;
78
78
  const host = this.resolveHostHeader(req);
79
79
  if (!host)
80
- return this.reject(res, 400, 'Missing or invalid Host header');
80
+ return this.reject(ctx, 400, 'Missing or invalid Host header');
81
81
  if (!ALLOWED_HOSTS.has(host))
82
- return this.reject(res, 403, 'Host not allowed');
82
+ return this.reject(ctx, 403, 'Host not allowed');
83
83
  const originHeader = getHeaderValue(req, 'origin');
84
84
  if (!originHeader)
85
85
  return true;
86
86
  const requestOrigin = this.resolveRequestOrigin(req);
87
87
  const origin = this.resolveOrigin(originHeader);
88
- if (!requestOrigin || !origin)
89
- return this.reject(res, 403, 'Invalid Origin header');
90
- if (!ALLOWED_HOSTS.has(origin.host))
91
- return this.reject(res, 403, 'Origin not allowed');
88
+ if (!requestOrigin || !origin) {
89
+ return this.reject(ctx, 403, 'Invalid Origin header');
90
+ }
91
+ if (!ALLOWED_HOSTS.has(origin.host)) {
92
+ return this.reject(ctx, 403, 'Origin not allowed');
93
+ }
92
94
  const isSameOrigin = requestOrigin.scheme === origin.scheme &&
93
95
  requestOrigin.host === origin.host &&
94
96
  requestOrigin.port === origin.port;
95
97
  if (!isSameOrigin)
96
- return this.reject(res, 403, 'Origin not allowed');
98
+ return this.reject(ctx, 403, 'Origin not allowed');
97
99
  return true;
98
100
  }
99
101
  resolveHostHeader(req) {
@@ -142,8 +144,16 @@ class HostOriginPolicy {
142
144
  defaultPortForScheme(scheme) {
143
145
  return scheme === 'https' ? '443' : '80';
144
146
  }
145
- reject(res, status, message) {
146
- sendJson(res, status, { error: message });
147
+ reject(ctx, status, message) {
148
+ logWarn('Host/Origin policy rejection', {
149
+ status,
150
+ reason: message,
151
+ method: ctx.method,
152
+ path: ctx.url.pathname,
153
+ host: getHeaderValue(ctx.req, 'host'),
154
+ origin: getHeaderValue(ctx.req, 'origin'),
155
+ }, 'http');
156
+ sendJson(ctx.res, status, { error: message });
147
157
  return false;
148
158
  }
149
159
  }
@@ -184,17 +194,31 @@ function resolveMcpProtocolVersion(req) {
184
194
  }
185
195
  export function ensureMcpProtocolVersion(req, res, options) {
186
196
  const version = resolveMcpProtocolVersion(req);
197
+ const path = URL.parse(req.url ?? '', 'http://localhost')?.pathname;
187
198
  if (!version) {
188
- sendError(res, -32600, 'Missing MCP-Protocol-Version header');
199
+ // Tolerate missing header on sessioned requests (expectedVersion set)
200
+ // to avoid breaking older clients that don't send it yet.
201
+ if (options?.expectedVersion) {
202
+ return true;
203
+ }
204
+ logWarn('MCP protocol version rejected', { reason: 'missing_header', path }, 'http');
205
+ sendError(res, -32600, 'Please include the MCP-Protocol-Version header in your request.');
189
206
  return false;
190
207
  }
191
208
  if (!SUPPORTED_MCP_PROTOCOL_VERSIONS.has(version)) {
192
- sendError(res, -32600, `Unsupported MCP-Protocol-Version: ${version}`);
209
+ logWarn('MCP protocol version rejected', { reason: 'unsupported_version', version, path }, 'http');
210
+ sendError(res, -32600, `The protocol version '${version}' isn't supported right now. Please check and try again.`);
193
211
  return false;
194
212
  }
195
213
  const expectedVersion = options?.expectedVersion;
196
214
  if (expectedVersion && version !== expectedVersion) {
197
- sendError(res, -32600, `MCP-Protocol-Version mismatch: expected ${expectedVersion}, got ${version}`);
215
+ logWarn('MCP protocol version rejected', {
216
+ reason: 'version_mismatch',
217
+ version,
218
+ expectedVersion,
219
+ path,
220
+ }, 'http');
221
+ sendError(res, -32600, `There's a protocol version mismatch. We expected '${expectedVersion}', but received '${version}'.`);
198
222
  return false;
199
223
  }
200
224
  return true;
@@ -227,11 +251,17 @@ class AuthService {
227
251
  introspectionCache = new Map();
228
252
  async authenticate(req, signal) {
229
253
  const authHeader = getHeaderValue(req, 'authorization');
230
- if (!authHeader) {
231
- return this.authenticateWithApiKey(req);
232
- }
233
- const token = this.resolveBearerToken(authHeader);
234
- return this.authenticateWithToken(token, signal);
254
+ const source = authHeader ? 'authorization' : 'api-key';
255
+ const info = authHeader
256
+ ? await this.authenticateWithToken(this.resolveBearerToken(authHeader), signal)
257
+ : this.authenticateWithApiKey(req);
258
+ logDebug('Authentication succeeded', {
259
+ mode: config.auth.mode,
260
+ source,
261
+ clientId: info.clientId,
262
+ scopeCount: info.scopes.length,
263
+ }, 'auth');
264
+ return info;
235
265
  }
236
266
  authenticateWithToken(token, signal) {
237
267
  return config.auth.mode === 'oauth'
@@ -244,8 +274,10 @@ class AuthService {
244
274
  return this.verifyStaticToken(apiKey);
245
275
  }
246
276
  if (apiKey && config.auth.mode === 'oauth') {
277
+ logWarn('Auth failed: X-API-Key not supported for OAuth', {}, 'auth');
247
278
  throw new InvalidTokenError('X-API-Key not supported for OAuth');
248
279
  }
280
+ logWarn('Auth failed: missing credentials', { authMode: config.auth.mode }, 'auth');
249
281
  throw new InvalidTokenError(config.auth.mode === 'static'
250
282
  ? 'Missing Authorization or X-API-Key header'
251
283
  : 'Missing Authorization header');
@@ -275,8 +307,10 @@ class AuthService {
275
307
  }
276
308
  const tokenDigest = hmacSha256Hex(STATIC_TOKEN_HMAC_KEY, token);
277
309
  const matched = hasConstantTimeMatch(this.staticTokenDigests, tokenDigest);
278
- if (!matched)
310
+ if (!matched) {
311
+ logWarn('Auth failed: invalid static token', {}, 'auth');
279
312
  throw new InvalidTokenError('Invalid token');
313
+ }
280
314
  return this.buildStaticAuthInfo(token);
281
315
  }
282
316
  stripHash(url) {
@@ -320,9 +354,11 @@ class AuthService {
320
354
  .map((value) => this.canonicalizeResourceUri(value))
321
355
  .filter((value) => value !== null);
322
356
  if (audiences.length === 0) {
357
+ logWarn('Auth failed: token missing audience binding', {}, 'auth');
323
358
  throw new InvalidTokenError('Token missing audience binding');
324
359
  }
325
360
  if (!audiences.includes(expected)) {
361
+ logWarn('Auth failed: audience mismatch', {}, 'auth');
326
362
  throw new InvalidTokenError('Token audience does not match this MCP server');
327
363
  }
328
364
  }
@@ -357,6 +393,7 @@ class AuthService {
357
393
  if (response.body) {
358
394
  await response.body.cancel();
359
395
  }
396
+ logWarn('Token introspection HTTP error', { status: response.status }, 'auth');
360
397
  throw new ServerError(`Token introspection failed: ${response.status}`);
361
398
  }
362
399
  return response.json();
@@ -382,6 +419,7 @@ class AuthService {
382
419
  const tokenScopeSet = new Set(tokenScopes);
383
420
  const missing = requiredScopes.filter((s) => !tokenScopeSet.has(s));
384
421
  if (missing.length > 0) {
422
+ logWarn('Auth failed: insufficient scopes', { missingCount: missing.length }, 'auth');
385
423
  throw new InsufficientScopeError(missing);
386
424
  }
387
425
  }
@@ -394,17 +432,20 @@ class AuthService {
394
432
  if (cached && cached.expiresAt > Date.now()) {
395
433
  this.introspectionCache.delete(cacheKey);
396
434
  this.introspectionCache.set(cacheKey, cached);
435
+ logDebug('Token introspection cache hit', {}, 'auth');
397
436
  return cached.info;
398
437
  }
399
438
  const req = this.buildIntrospectionRequest(token, config.auth.resourceUrl, config.auth.clientId, config.auth.clientSecret);
400
439
  const payload = await this.requestIntrospection(config.auth.introspectionUrl, req, config.auth.introspectionTimeoutMs, signal);
401
440
  if (!isObject(payload) || payload['active'] !== true) {
402
441
  this.introspectionCache.delete(cacheKey);
442
+ logWarn('Auth failed: token inactive', {}, 'auth');
403
443
  throw new InvalidTokenError('Token is inactive');
404
444
  }
405
445
  this.assertTokenAudience(payload);
406
446
  const info = this.buildIntrospectionAuthInfo(token, payload);
407
447
  this.assertRequiredScopes(info.scopes);
448
+ logDebug('Token introspection successful', { clientId: info.clientId }, 'auth');
408
449
  this.evictStaleEntries();
409
450
  this.introspectionCache.set(cacheKey, {
410
451
  info,
@@ -63,6 +63,6 @@ interface SessionTeardownOptions {
63
63
  }
64
64
  export declare function teardownSessionResources(session: SessionRecordLike, options: SessionTeardownOptions): Promise<void>;
65
65
  export declare function teardownUnregisteredSessionResources(session: SessionRecordLike, context: string): Promise<void>;
66
- export declare function teardownSessionRegistration(server: McpServer, cancelMessage: string): void;
66
+ export declare function teardownSessionRegistration(server: McpServer): void;
67
67
  export {};
68
68
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/http/helpers.ts"],"names":[],"mappings":"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"}
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;AAQ/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,CAAC;AAcjD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC1D,IAAI,EAAE,QAAQ,CAAC;CAChB;AAWD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,GACZ,IAAI,CAKN;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAInE;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,SAAM,EACZ,EAAE,GAAE,SAAgB,GACnB,IAAI,CAMN;AAMD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,IAAI,CAIf;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAKnE;AAkBD,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GACnB,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAOvD;AAMD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,eAAe,GAAG;IAC9D,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAwCA;AAgBD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAwBpE;AAMD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,GAAG,IAAI,CAgBvB;AAMD,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAC5C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,6BAA6B,GAC3C,SAAS,CA4CX;AAMD,KAAK,iBAAiB,GAAG,mBAAmB,GAAG,cAAc,GAAG,aAAa,CAAC;AAE9E,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;gBAErB,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM;CAKrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,eAAO,MAAM,wBAAwB,QAAc,CAAC;AAMpD,cAAM,cAAc;IACZ,IAAI,CACR,GAAG,EAAE,eAAe,EACpB,KAAK,SAA2B,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC;YA0BL,QAAQ;YAyCR,aAAa;IAkD3B,OAAO,CAAC,cAAc;CAIvB;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC;AAEnD,UAAU,iBAAiB;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,6BAA6B,CAAC;CAC1C;AAED,UAAU,sBAAsB;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAoCD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,oCAAoC,CACxD,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAEnE"}
@@ -4,7 +4,6 @@ import { composeCloseHandlers, config, logWarn } from '../lib/core.js';
4
4
  import { resolveMcpSessionIdByServer, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
5
5
  import { createDefaultBlockList, normalizeIpForBlockList } from '../lib/url.js';
6
6
  import { getErrorMessage, toError } from '../lib/utils.js';
7
- import { cancelTasksForOwner } from '../tasks/handlers.js';
8
7
  function abortControllerBestEffort(controller) {
9
8
  if (!controller.signal.aborted)
10
9
  controller.abort();
@@ -155,7 +154,7 @@ export function registerInboundBlockList(server) {
155
154
  logWarn('Blocked inbound connection', {
156
155
  remoteAddress: normalized.ip,
157
156
  family: normalized.family,
158
- });
157
+ }, 'http');
159
158
  socket.destroy();
160
159
  }
161
160
  });
@@ -187,7 +186,7 @@ export async function closeTransportBestEffort(transport, context) {
187
186
  await transport.close();
188
187
  }
189
188
  catch (error) {
190
- logWarn('Transport close failed', { context, error });
189
+ logWarn('Transport close failed', { context, error }, 'http');
191
190
  }
192
191
  }
193
192
  export async function closeMcpServerBestEffort(server, context) {
@@ -195,7 +194,7 @@ export async function closeMcpServerBestEffort(server, context) {
195
194
  await server.close();
196
195
  }
197
196
  catch (error) {
198
- logWarn('MCP server close failed', { context, error });
197
+ logWarn('MCP server close failed', { context, error }, 'http');
199
198
  }
200
199
  }
201
200
  export function createTransportAdapter(transportImpl) {
@@ -357,11 +356,10 @@ class JsonBodyReader {
357
356
  }
358
357
  }
359
358
  export const jsonBodyReader = new JsonBodyReader();
360
- function cancelSessionTasks(server, message) {
359
+ function unregisterSessionTaskScope(server) {
361
360
  const sessionId = resolveMcpSessionIdByServer(server);
362
361
  if (!sessionId)
363
362
  return null;
364
- cancelTasksForOwner(`session:${sessionId}`, message);
365
363
  unregisterMcpSessionServer(sessionId);
366
364
  return sessionId;
367
365
  }
@@ -378,7 +376,7 @@ async function closeSessionResources(session, options) {
378
376
  }
379
377
  }
380
378
  export async function teardownSessionResources(session, options) {
381
- cancelSessionTasks(session.server, options.cancelMessage);
379
+ unregisterSessionTaskScope(session.server);
382
380
  if (options.unregisterByServer) {
383
381
  unregisterMcpSessionServerByServer(session.server);
384
382
  }
@@ -391,6 +389,6 @@ export async function teardownUnregisteredSessionResources(session, context) {
391
389
  awaitClose: true,
392
390
  });
393
391
  }
394
- export function teardownSessionRegistration(server, cancelMessage) {
395
- cancelSessionTasks(server, cancelMessage);
392
+ export function teardownSessionRegistration(server) {
393
+ unregisterSessionTaskScope(server);
396
394
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AAqmCA,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,CA0DD"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/http/native.ts"],"names":[],"mappings":"AAm+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"}