@j0hanz/fetch-url-mcp 1.1.0 → 1.1.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.
@@ -1,4 +1,5 @@
1
1
  import { config } from './config.js';
2
+ import { FetchError } from './errors.js';
2
3
  // --- Constants & Regex ---
3
4
  const MAX_LINE_LENGTH = 80;
4
5
  const REGEX = {
@@ -24,20 +25,39 @@ const REGEX = {
24
25
  SPACING_ESCAPES: /\\([[\].])/g,
25
26
  SPACING_LIST_NUM_COMBINED: /^((?![-*+] |\d+\. |[ \t]).+)\n((?:[-*+]|\d+\.) )/gm,
26
27
  NESTED_LIST_INDENT: /^( +)((?:[-*+])|\d+\.)\s/gm,
27
- TYPEDOC: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
28
+ TYPEDOC_COMMENT: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
28
29
  };
29
30
  const HEADING_KEYWORDS = new Set(config.markdownCleanup.headingKeywords.map((value) => value.toLocaleLowerCase(config.i18n.locale)));
30
31
  const SPECIAL_PREFIXES = /^(?:example|note|tip|warning|important|caution):\s+\S/i;
32
+ const TOC_SCAN_LIMIT = 20;
33
+ const TOC_MAX_NON_EMPTY = 12;
34
+ const TOC_LINK_RATIO_THRESHOLD = 0.8;
35
+ const TYPEDOC_PREFIXES = [
36
+ 'Defined in:',
37
+ 'Returns:',
38
+ 'Since:',
39
+ 'See also:',
40
+ ];
41
+ function throwIfAborted(signal, url, stage) {
42
+ if (!signal?.aborted)
43
+ return;
44
+ throw new FetchError('Request was canceled', url, 499, {
45
+ reason: 'aborted',
46
+ stage,
47
+ });
48
+ }
31
49
  // --- Helper Functions ---
32
50
  function getLineEnding(content) {
33
51
  return content.includes('\r\n') ? '\r\n' : '\n';
34
52
  }
53
+ function isBlank(line) {
54
+ return line === undefined || line.trim().length === 0;
55
+ }
35
56
  function hasFollowingContent(lines, startIndex) {
36
57
  // Optimization: Bound lookahead to avoid checking too many lines in huge files
37
58
  const max = Math.min(lines.length, startIndex + 50);
38
59
  for (let i = startIndex + 1; i < max; i++) {
39
- const line = lines[i];
40
- if (line && line.trim().length > 0)
60
+ if (!isBlank(lines[i]))
41
61
  return true;
42
62
  }
43
63
  return false;
@@ -100,29 +120,55 @@ function getHeadingPrefix(trimmed) {
100
120
  return isTitleCaseOrKeyword(trimmed) ? '## ' : null;
101
121
  }
102
122
  // Optimized TOC detection
103
- function hasTocBlock(lines, headingIndex) {
104
- const lookaheadMax = Math.min(lines.length, headingIndex + 8);
123
+ function getTocBlockStats(lines, headingIndex) {
124
+ let total = 0;
125
+ let linkCount = 0;
126
+ let nonLinkCount = 0;
127
+ const lookaheadMax = Math.min(lines.length, headingIndex + TOC_SCAN_LIMIT);
105
128
  for (let i = headingIndex + 1; i < lookaheadMax; i++) {
106
129
  const line = lines[i];
107
- if (!line || line.trim().length === 0)
130
+ if (!line)
108
131
  continue;
109
- if (REGEX.TOC_LINK.test(line))
110
- return true;
132
+ const trimmed = line.trim();
133
+ if (!trimmed)
134
+ continue;
135
+ if (REGEX.HEADING_MARKER.test(trimmed))
136
+ break;
137
+ total += 1;
138
+ if (REGEX.TOC_LINK.test(trimmed))
139
+ linkCount += 1;
140
+ else
141
+ nonLinkCount += 1;
142
+ if (total >= TOC_MAX_NON_EMPTY)
143
+ break;
111
144
  }
112
- return false;
145
+ return { total, linkCount, nonLinkCount };
113
146
  }
114
147
  function skipTocLines(lines, startIndex) {
115
148
  for (let i = startIndex; i < lines.length; i++) {
116
149
  const line = lines[i];
117
- if (!line)
150
+ if (line === undefined)
118
151
  continue;
119
- if (line.trim().length === 0)
152
+ const trimmed = line.trim();
153
+ if (!trimmed)
120
154
  continue;
121
- if (!REGEX.TOC_LINK.test(line))
155
+ if (!REGEX.TOC_LINK.test(trimmed))
122
156
  return i;
123
157
  }
124
158
  return lines.length;
125
159
  }
160
+ function isTypeDocArtifactLine(line) {
161
+ const trimmed = line.trim();
162
+ for (const prefix of TYPEDOC_PREFIXES) {
163
+ if (!trimmed.startsWith(prefix))
164
+ continue;
165
+ const rest = trimmed.slice(prefix.length).trimStart();
166
+ if (!rest.startsWith('**`'))
167
+ return false;
168
+ return rest.includes('`**');
169
+ }
170
+ return false;
171
+ }
126
172
  // --- Main Processing Logic ---
127
173
  function tryPromoteOrphan(lines, i, trimmed) {
128
174
  const prevLine = lines[i - 1];
@@ -137,13 +183,19 @@ function tryPromoteOrphan(lines, i, trimmed) {
137
183
  return null;
138
184
  return `${prefix}${trimmed}`;
139
185
  }
140
- function shouldSkipAsToc(lines, i, trimmed, removeToc) {
141
- if (removeToc && REGEX.TOC_HEADING.test(trimmed) && hasTocBlock(lines, i)) {
142
- return skipTocLines(lines, i + 1);
143
- }
144
- return null;
186
+ function shouldSkipAsToc(lines, i, trimmed, removeToc, options) {
187
+ if (!removeToc || !REGEX.TOC_HEADING.test(trimmed))
188
+ return null;
189
+ const { total, linkCount, nonLinkCount } = getTocBlockStats(lines, i);
190
+ if (total === 0 || nonLinkCount > 0)
191
+ return null;
192
+ const ratio = linkCount / total;
193
+ if (ratio <= TOC_LINK_RATIO_THRESHOLD)
194
+ return null;
195
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:toc');
196
+ return skipTocLines(lines, i + 1);
145
197
  }
146
- function preprocessLines(lines) {
198
+ function preprocessLines(lines, options) {
147
199
  const processedLines = [];
148
200
  const len = lines.length;
149
201
  const promote = config.markdownCleanup.promoteOrphanHeadings;
@@ -158,12 +210,13 @@ function preprocessLines(lines) {
158
210
  const trimmed = line.trim();
159
211
  if (REGEX.EMPTY_HEADING_LINE.test(trimmed))
160
212
  continue;
161
- const tocSkip = shouldSkipAsToc(lines, i, trimmed, removeToc);
213
+ const tocSkip = shouldSkipAsToc(lines, i, trimmed, removeToc, options);
162
214
  if (tocSkip !== null) {
163
215
  skipUntil = tocSkip;
164
216
  continue;
165
217
  }
166
218
  if (promote && trimmed.length > 0) {
219
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:promote');
167
220
  const promoted = tryPromoteOrphan(lines, i, trimmed);
168
221
  if (promoted)
169
222
  line = promoted;
@@ -173,28 +226,35 @@ function preprocessLines(lines) {
173
226
  return processedLines.join('\n');
174
227
  }
175
228
  // Process a block of non-fence lines
176
- function processTextBuffer(lines) {
229
+ function processTextBuffer(lines, options) {
177
230
  if (lines.length === 0)
178
231
  return '';
179
- const text = preprocessLines(lines);
180
- return applyGlobalRegexes(text);
232
+ const text = preprocessLines(lines, options);
233
+ return applyGlobalRegexes(text, options);
181
234
  }
182
- function applyGlobalRegexes(text) {
235
+ function applyGlobalRegexes(text, options) {
183
236
  let result = text;
237
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:headings');
184
238
  // fixAndSpaceHeadings
185
239
  result = result
186
240
  .replace(REGEX.HEADING_SPACING, '$1\n\n$2')
187
241
  .replace(REGEX.HEADING_CODE_BLOCK, '$1\n\n```')
188
242
  .replace(REGEX.HEADING_CAMEL_CASE, '$1\n\n$2');
189
- // removeTypeDocComments
190
243
  if (config.markdownCleanup.removeTypeDocComments) {
191
- result = result.replace(REGEX.TYPEDOC, (match) => match.startsWith('`') ? match : '');
244
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:typedoc');
245
+ result = result
246
+ .split('\n')
247
+ .filter((line) => !isTypeDocArtifactLine(line))
248
+ .join('\n');
249
+ result = result.replace(REGEX.TYPEDOC_COMMENT, (match) => match.startsWith('`') ? match : '');
192
250
  }
193
251
  if (config.markdownCleanup.removeSkipLinks) {
252
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:skip-links');
194
253
  result = result
195
254
  .replace(REGEX.ZERO_WIDTH_ANCHOR, '')
196
255
  .replace(REGEX.COMBINED_LINE_REMOVALS, '');
197
256
  }
257
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:spacing');
198
258
  // normalizeSpacing
199
259
  result = result
200
260
  .replace(REGEX.SPACING_LINK_FIX, ']($1)\n\n[')
@@ -204,6 +264,7 @@ function applyGlobalRegexes(text) {
204
264
  .replace(REGEX.SPACING_LIST_NUM_COMBINED, '$1\n\n$2')
205
265
  .replace(REGEX.DOUBLE_NEWLINE_REDUCER, '\n\n');
206
266
  result = normalizeNestedListIndentation(result);
267
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:properties');
207
268
  // fixProperties
208
269
  for (let k = 0; k < 3; k++) {
209
270
  const next = result.replace(REGEX.CONCATENATED_PROPS, '$1$2\n\n$3');
@@ -251,22 +312,23 @@ function handleFencedLine(line, trimmed, fenceMarker, segments) {
251
312
  segments.push(line);
252
313
  return isFenceClosure(trimmed, fenceMarker) ? null : fenceMarker;
253
314
  }
254
- function handleUnfencedLine(line, segments, buffer) {
315
+ function handleUnfencedLine(line, segments, buffer, options) {
255
316
  const newMarker = checkFenceStart(line);
256
317
  if (!newMarker) {
257
318
  buffer.push(line);
258
319
  return { fenceMarker: null, buffer };
259
320
  }
260
321
  if (buffer.length > 0) {
261
- segments.push(processTextBuffer(buffer));
322
+ segments.push(processTextBuffer(buffer, options));
262
323
  buffer = [];
263
324
  }
264
325
  segments.push(line);
265
326
  return { fenceMarker: newMarker, buffer };
266
327
  }
267
- export function cleanupMarkdownArtifacts(content) {
328
+ export function cleanupMarkdownArtifacts(content, options) {
268
329
  if (!content)
269
330
  return '';
331
+ throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:begin');
270
332
  const len = content.length;
271
333
  let lastIndex = 0;
272
334
  let fenceMarker = null;
@@ -279,12 +341,12 @@ export function cleanupMarkdownArtifacts(content) {
279
341
  fenceMarker = handleFencedLine(line, trimmed, fenceMarker, segments);
280
342
  }
281
343
  else {
282
- ({ fenceMarker, buffer } = handleUnfencedLine(line, segments, buffer));
344
+ ({ fenceMarker, buffer } = handleUnfencedLine(line, segments, buffer, options));
283
345
  }
284
346
  lastIndex = nextIndex;
285
347
  }
286
348
  if (buffer.length > 0) {
287
- segments.push(processTextBuffer(buffer));
349
+ segments.push(processTextBuffer(buffer, options));
288
350
  }
289
351
  return segments.join('\n').trim();
290
352
  }
@@ -1,9 +1,9 @@
1
1
  export type JsonRpcId = string | number | null;
2
- export interface McpRequestParams {
2
+ interface McpRequestParams {
3
3
  _meta?: Record<string, unknown>;
4
4
  [key: string]: unknown;
5
5
  }
6
- export interface McpRequestBody {
6
+ interface McpRequestBody {
7
7
  jsonrpc: '2.0';
8
8
  method: string;
9
9
  id?: JsonRpcId;
@@ -13,3 +13,4 @@ export declare function isJsonRpcBatchRequest(body: unknown): boolean;
13
13
  export declare function isMcpRequestBody(body: unknown): body is McpRequestBody;
14
14
  export declare function acceptsEventStream(header: string | null | undefined): boolean;
15
15
  export declare function acceptsJsonAndEventStream(header: string | null | undefined): boolean;
16
+ export {};
@@ -13,17 +13,16 @@ export function isJsonRpcBatchRequest(body) {
13
13
  export function isMcpRequestBody(body) {
14
14
  return mcpRequestSchema.safeParse(body).success;
15
15
  }
16
- export function acceptsEventStream(header) {
16
+ function parseAcceptHeader(header) {
17
17
  if (!header)
18
- return false;
19
- return header
20
- .split(',')
21
- .some((value) => value.trim().toLowerCase().startsWith('text/event-stream'));
18
+ return [];
19
+ return header.split(',').map((value) => value.trim());
20
+ }
21
+ export function acceptsEventStream(header) {
22
+ return parseAcceptHeader(header).some((value) => value.trim().toLowerCase().startsWith('text/event-stream'));
22
23
  }
23
24
  function hasAcceptedMediaType(header, exact, wildcardPrefix) {
24
- if (!header)
25
- return false;
26
- return header.split(',').some((rawPart) => {
25
+ return parseAcceptHeader(header).some((rawPart) => {
27
26
  const mediaType = rawPart.trim().split(';', 1)[0]?.trim().toLowerCase();
28
27
  if (!mediaType)
29
28
  return false;
package/dist/mcp.js CHANGED
@@ -8,26 +8,35 @@ import { isObject } from './type-guards.js';
8
8
  /* -------------------------------------------------------------------------------------------------
9
9
  * Tasks API schemas
10
10
  * ------------------------------------------------------------------------------------------------- */
11
- const TaskGetSchema = z.strictObject({
11
+ const TaskGetSchema = z
12
+ .object({
12
13
  method: z.literal('tasks/get'),
13
- params: z.strictObject({ taskId: z.string() }),
14
- });
15
- const TaskListSchema = z.strictObject({
14
+ params: z.object({ taskId: z.string() }).loose(),
15
+ })
16
+ .loose();
17
+ const TaskListSchema = z
18
+ .object({
16
19
  method: z.literal('tasks/list'),
17
20
  params: z
18
- .strictObject({
21
+ .object({
19
22
  cursor: z.string().optional(),
20
23
  })
24
+ .loose()
21
25
  .optional(),
22
- });
23
- const TaskCancelSchema = z.strictObject({
26
+ })
27
+ .loose();
28
+ const TaskCancelSchema = z
29
+ .object({
24
30
  method: z.literal('tasks/cancel'),
25
- params: z.strictObject({ taskId: z.string() }),
26
- });
27
- const TaskResultSchema = z.strictObject({
31
+ params: z.object({ taskId: z.string() }).loose(),
32
+ })
33
+ .loose();
34
+ const TaskResultSchema = z
35
+ .object({
28
36
  method: z.literal('tasks/result'),
29
- params: z.strictObject({ taskId: z.string() }),
30
- });
37
+ params: z.object({ taskId: z.string() }).loose(),
38
+ })
39
+ .loose();
31
40
  const MIN_TASK_TTL_MS = 1_000;
32
41
  const MAX_TASK_TTL_MS = 86_400_000;
33
42
  const ExtendedCallToolRequestSchema = z
@@ -71,6 +80,14 @@ function parseExtendedCallToolRequest(request) {
71
80
  function isRecord(value) {
72
81
  return isObject(value);
73
82
  }
83
+ function normalizeSendNotification(sendNotification) {
84
+ if (typeof sendNotification !== 'function')
85
+ return undefined;
86
+ const notify = sendNotification;
87
+ return async (notification) => {
88
+ await Promise.resolve(notify(notification));
89
+ };
90
+ }
74
91
  function parseHandlerExtra(extra) {
75
92
  if (!isObject(extra))
76
93
  return undefined;
@@ -93,11 +110,9 @@ function parseHandlerExtra(extra) {
93
110
  if (typeof requestId === 'string' || typeof requestId === 'number') {
94
111
  parsed.requestId = requestId;
95
112
  }
96
- if (typeof sendNotification === 'function') {
97
- const notify = sendNotification;
98
- parsed.sendNotification = async (notification) => {
99
- await Promise.resolve(notify(notification));
100
- };
113
+ const normalizedSendNotification = normalizeSendNotification(sendNotification);
114
+ if (normalizedSendNotification) {
115
+ parsed.sendNotification = normalizedSendNotification;
101
116
  }
102
117
  return parsed;
103
118
  }
@@ -1,5 +1,5 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export type LogMetadata = Record<string, unknown>;
2
+ type LogMetadata = Record<string, unknown>;
3
3
  interface RequestContext {
4
4
  readonly requestId: string;
5
5
  readonly sessionId?: string;
@@ -128,6 +128,13 @@ function mapToMcpLevel(level) {
128
128
  return 'info';
129
129
  }
130
130
  }
131
+ function resolveErrorText(err) {
132
+ if (err instanceof Error)
133
+ return err.message;
134
+ if (typeof err === 'string')
135
+ return err;
136
+ return 'unknown error';
137
+ }
131
138
  function safeWriteStderr(line) {
132
139
  if (!stderrAvailable)
133
140
  return;
@@ -164,20 +171,14 @@ function writeLog(level, message, meta) {
164
171
  .catch((err) => {
165
172
  if (!isDebugEnabled())
166
173
  return;
167
- let errorText = 'unknown error';
168
- if (err instanceof Error) {
169
- errorText = err.message;
170
- }
171
- else if (typeof err === 'string') {
172
- errorText = err;
173
- }
174
+ const errorText = resolveErrorText(err);
174
175
  safeWriteStderr(`[${createTimestamp()}] WARN: Failed to forward log to MCP${sessionId ? ` (sessionId=${sessionId})` : ''}: ${errorText}\n`);
175
176
  });
176
177
  }
177
178
  catch (err) {
178
179
  if (!isDebugEnabled())
179
180
  return;
180
- const errorText = err instanceof Error ? err.message : 'unknown error';
181
+ const errorText = resolveErrorText(err);
181
182
  safeWriteStderr(`[${createTimestamp()}] WARN: Failed to forward log to MCP (sync error): ${errorText}\n`);
182
183
  }
183
184
  }
package/dist/prompts.js CHANGED
@@ -1,18 +1,10 @@
1
1
  export function registerGetHelpPrompt(server, instructions, iconInfo) {
2
2
  const description = 'Return the Fetch URL usage instructions.';
3
+ const iconConfig = iconInfo ? { icons: [{ ...iconInfo }] } : {};
3
4
  server.registerPrompt('get-help', {
4
5
  title: 'Get Help',
5
6
  description,
6
- ...(iconInfo
7
- ? {
8
- icons: [
9
- {
10
- src: iconInfo.src,
11
- mimeType: iconInfo.mimeType,
12
- },
13
- ],
14
- }
15
- : {}),
7
+ ...iconConfig,
16
8
  }, () => ({
17
9
  description,
18
10
  messages: [
package/dist/resources.js CHANGED
@@ -21,17 +21,19 @@ function decodeSegment(value) {
21
21
  return value;
22
22
  }
23
23
  }
24
+ function trimToValue(value) {
25
+ const trimmed = value.trim();
26
+ return trimmed.length > 0 ? trimmed : undefined;
27
+ }
24
28
  function firstVariableValue(value) {
25
29
  if (typeof value === 'string') {
26
- const trimmed = value.trim();
27
- return trimmed.length > 0 ? trimmed : undefined;
30
+ return trimToValue(value);
28
31
  }
29
32
  if (Array.isArray(value)) {
30
33
  const first = value[0];
31
34
  if (typeof first !== 'string')
32
35
  return undefined;
33
- const trimmed = first.trim();
34
- return trimmed.length > 0 ? trimmed : undefined;
36
+ return trimToValue(first);
35
37
  }
36
38
  return undefined;
37
39
  }
@@ -1,4 +1,4 @@
1
- export interface HttpServerTuningTarget {
1
+ interface HttpServerTuningTarget {
2
2
  headersTimeout?: number;
3
3
  requestTimeout?: number;
4
4
  keepAliveTimeout?: number;
@@ -11,3 +11,4 @@ export interface HttpServerTuningTarget {
11
11
  }
12
12
  export declare function applyHttpServerTuning(server: HttpServerTuningTarget): void;
13
13
  export declare function drainConnectionsOnShutdown(server: HttpServerTuningTarget): void;
14
+ export {};
@@ -1,23 +1,28 @@
1
1
  import { config } from './config.js';
2
2
  import { logDebug, logWarn } from './observability.js';
3
3
  const DROP_LOG_INTERVAL_MS = 10_000;
4
+ function setIfDefined(value, setter) {
5
+ if (value === undefined)
6
+ return;
7
+ setter(value);
8
+ }
4
9
  export function applyHttpServerTuning(server) {
5
10
  const { headersTimeoutMs, requestTimeoutMs, keepAliveTimeoutMs, keepAliveTimeoutBufferMs, maxHeadersCount, maxConnections, } = config.server.http;
6
- if (headersTimeoutMs !== undefined) {
7
- server.headersTimeout = headersTimeoutMs;
8
- }
9
- if (requestTimeoutMs !== undefined) {
10
- server.requestTimeout = requestTimeoutMs;
11
- }
12
- if (keepAliveTimeoutMs !== undefined) {
13
- server.keepAliveTimeout = keepAliveTimeoutMs;
14
- }
15
- if (keepAliveTimeoutBufferMs !== undefined) {
16
- server.keepAliveTimeoutBuffer = keepAliveTimeoutBufferMs;
17
- }
18
- if (maxHeadersCount !== undefined) {
19
- server.maxHeadersCount = maxHeadersCount;
20
- }
11
+ setIfDefined(headersTimeoutMs, (value) => {
12
+ server.headersTimeout = value;
13
+ });
14
+ setIfDefined(requestTimeoutMs, (value) => {
15
+ server.requestTimeout = value;
16
+ });
17
+ setIfDefined(keepAliveTimeoutMs, (value) => {
18
+ server.keepAliveTimeout = value;
19
+ });
20
+ setIfDefined(keepAliveTimeoutBufferMs, (value) => {
21
+ server.keepAliveTimeoutBuffer = value;
22
+ });
23
+ setIfDefined(maxHeadersCount, (value) => {
24
+ server.maxHeadersCount = value;
25
+ });
21
26
  if (typeof maxConnections === 'number' && maxConnections > 0) {
22
27
  server.maxConnections = maxConnections;
23
28
  if (typeof server.on === 'function') {
package/dist/server.js CHANGED
@@ -70,6 +70,9 @@ function createServerInfo(icons) {
70
70
  ...(icons ? { icons } : {}),
71
71
  };
72
72
  }
73
+ function toIconList(icon) {
74
+ return icon ? [icon] : undefined;
75
+ }
73
76
  /* -------------------------------------------------------------------------------------------------
74
77
  * Server lifecycle
75
78
  * ------------------------------------------------------------------------------------------------- */
@@ -88,14 +91,7 @@ async function createMcpServerWithOptions(options) {
88
91
  if (serverInstructions) {
89
92
  serverConfig.instructions = serverInstructions;
90
93
  }
91
- const serverInfo = createServerInfo(localIcon
92
- ? [
93
- {
94
- src: localIcon.src,
95
- mimeType: localIcon.mimeType,
96
- },
97
- ]
98
- : undefined);
94
+ const serverInfo = createServerInfo(toIconList(localIcon));
99
95
  const server = new McpServer(serverInfo, serverConfig);
100
96
  if (options?.registerObservabilityServer ?? true) {
101
97
  setMcpServer(server);
package/dist/session.d.ts CHANGED
@@ -21,12 +21,12 @@ export interface SessionStore {
21
21
  evictExpired: () => SessionEntry[];
22
22
  evictOldest: () => SessionEntry | undefined;
23
23
  }
24
- export interface SlotTracker {
24
+ interface SlotTracker {
25
25
  readonly releaseSlot: () => void;
26
26
  readonly markInitialized: () => void;
27
27
  readonly isInitialized: () => boolean;
28
28
  }
29
- export type CloseHandler = (() => void) | undefined;
29
+ type CloseHandler = (() => void) | undefined;
30
30
  export declare function composeCloseHandlers(first: CloseHandler, second: CloseHandler): CloseHandler;
31
31
  export declare function startSessionCleanupLoop(store: SessionStore, sessionTtlMs: number): AbortController;
32
32
  export declare function createSessionStore(sessionTtlMs: number): SessionStore;
@@ -37,3 +37,4 @@ export declare function ensureSessionCapacity({ store, maxSessions, evictOldest,
37
37
  maxSessions: number;
38
38
  evictOldest: (store: SessionStore) => boolean;
39
39
  }): boolean;
40
+ export {};
package/dist/session.js CHANGED
@@ -96,6 +96,9 @@ function moveSessionToEnd(sessions, sessionId, session) {
96
96
  sessions.delete(sessionId);
97
97
  sessions.set(sessionId, session);
98
98
  }
99
+ function isBlankSessionId(sessionId) {
100
+ return sessionId.length === 0;
101
+ }
99
102
  class InMemorySessionStore {
100
103
  sessionTtlMs;
101
104
  sessions = new Map();
@@ -104,12 +107,12 @@ class InMemorySessionStore {
104
107
  this.sessionTtlMs = sessionTtlMs;
105
108
  }
106
109
  get(sessionId) {
107
- if (!sessionId)
110
+ if (isBlankSessionId(sessionId))
108
111
  return undefined;
109
112
  return this.sessions.get(sessionId);
110
113
  }
111
114
  touch(sessionId) {
112
- if (!sessionId)
115
+ if (isBlankSessionId(sessionId))
113
116
  return;
114
117
  const session = this.sessions.get(sessionId);
115
118
  if (!session)
@@ -118,12 +121,12 @@ class InMemorySessionStore {
118
121
  moveSessionToEnd(this.sessions, sessionId, session);
119
122
  }
120
123
  set(sessionId, entry) {
121
- if (!sessionId)
124
+ if (isBlankSessionId(sessionId))
122
125
  return;
123
126
  moveSessionToEnd(this.sessions, sessionId, entry);
124
127
  }
125
128
  remove(sessionId) {
126
- if (!sessionId)
129
+ if (isBlankSessionId(sessionId))
127
130
  return undefined;
128
131
  const session = this.sessions.get(sessionId);
129
132
  this.sessions.delete(sessionId);
package/dist/tasks.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface TaskState {
16
16
  result?: unknown;
17
17
  error?: TaskError;
18
18
  }
19
- export interface CreateTaskOptions {
19
+ interface CreateTaskOptions {
20
20
  ttl?: number;
21
21
  }
22
22
  export interface CreateTaskResult {
package/dist/tasks.js CHANGED
@@ -41,7 +41,7 @@ class TaskManager {
41
41
  removeExpiredTasks() {
42
42
  const now = Date.now();
43
43
  for (const [id, task] of this.tasks) {
44
- if (now - task._createdAtMs > task.ttl) {
44
+ if (this.isExpired(task, now)) {
45
45
  this.tasks.delete(id);
46
46
  }
47
47
  }
@@ -146,7 +146,7 @@ class TaskManager {
146
146
  for (const task of this.tasks.values()) {
147
147
  if (task.ownerKey !== ownerKey)
148
148
  continue;
149
- if (now - task._createdAtMs > task.ttl) {
149
+ if (this.isExpired(task, now)) {
150
150
  this.tasks.delete(task.taskId);
151
151
  continue;
152
152
  }
@@ -284,8 +284,8 @@ class TaskManager {
284
284
  for (const waiter of waiters)
285
285
  waiter(task);
286
286
  }
287
- isExpired(task) {
288
- return Date.now() - task._createdAtMs > task.ttl;
287
+ isExpired(task, now = Date.now()) {
288
+ return now - task._createdAtMs > task.ttl;
289
289
  }
290
290
  encodeCursor(index) {
291
291
  return Buffer.from(String(index), 'utf8').toString('base64url');
@@ -1,12 +1,15 @@
1
1
  import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
2
2
  import { isError } from './type-guards.js';
3
+ function isAbortError(error) {
4
+ return isError(error) && error.name === 'AbortError';
5
+ }
3
6
  export function createUnrefTimeout(timeoutMs, value) {
4
7
  const controller = new AbortController();
5
8
  const promise = setTimeoutPromise(timeoutMs, value, {
6
9
  ref: false,
7
10
  signal: controller.signal,
8
11
  }).catch((err) => {
9
- if (isError(err) && err.name === 'AbortError') {
12
+ if (isAbortError(err)) {
10
13
  return new Promise(() => { });
11
14
  }
12
15
  throw err;