@j0hanz/filesystem-mcp 1.13.2 → 1.14.1

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 (74) hide show
  1. package/README.md +162 -145
  2. package/dist/cli.js +2 -2
  3. package/dist/completions.js +54 -51
  4. package/dist/config.d.ts +13 -14
  5. package/dist/config.js +12 -12
  6. package/dist/index.js +1 -1
  7. package/dist/lib/abort.d.ts +7 -0
  8. package/dist/lib/abort.js +81 -0
  9. package/dist/lib/constants.d.ts +3 -1
  10. package/dist/lib/constants.js +8 -2
  11. package/dist/lib/errors.d.ts +7 -3
  12. package/dist/lib/errors.js +64 -41
  13. package/dist/lib/file-operations/core.d.ts +3 -3
  14. package/dist/lib/file-operations/core.js +23 -20
  15. package/dist/lib/file-operations/metadata.d.ts +2 -2
  16. package/dist/lib/file-operations/metadata.js +69 -22
  17. package/dist/lib/file-operations/search.d.ts +0 -1
  18. package/dist/lib/file-operations/search.js +87 -95
  19. package/dist/lib/file-operations/traversal.js +13 -15
  20. package/dist/lib/fs-helpers.d.ts +3 -10
  21. package/dist/lib/fs-helpers.js +29 -108
  22. package/dist/lib/globs.d.ts +2 -0
  23. package/dist/lib/globs.js +19 -0
  24. package/dist/lib/logger.d.ts +28 -0
  25. package/dist/lib/logger.js +91 -0
  26. package/dist/lib/observability.d.ts +7 -0
  27. package/dist/lib/observability.js +19 -9
  28. package/dist/lib/paths.js +55 -55
  29. package/dist/lib/resource-store.js +4 -4
  30. package/dist/lib/utils.d.ts +0 -12
  31. package/dist/lib/utils.js +0 -13
  32. package/dist/lib/zod-codecs.d.ts +2 -0
  33. package/dist/lib/zod-codecs.js +18 -0
  34. package/dist/pkg-info.d.ts +1 -0
  35. package/dist/pkg-info.js +2 -2
  36. package/dist/prompts.js +3 -3
  37. package/dist/resources/generated-instructions.js +41 -41
  38. package/dist/resources/tool-catalog.js +33 -58
  39. package/dist/resources/tool-info.d.ts +0 -1
  40. package/dist/resources/tool-info.js +44 -67
  41. package/dist/resources/workflows.js +47 -19
  42. package/dist/resources.d.ts +1 -1
  43. package/dist/resources.js +4 -4
  44. package/dist/schemas.d.ts +185 -465
  45. package/dist/schemas.js +174 -206
  46. package/dist/server/bootstrap.d.ts +12 -11
  47. package/dist/server/bootstrap.js +95 -86
  48. package/dist/server/roots-manager.d.ts +5 -2
  49. package/dist/server/roots-manager.js +9 -7
  50. package/dist/server/task-store.d.ts +10 -0
  51. package/dist/server/task-store.js +73 -0
  52. package/dist/tools/apply-patch.js +39 -20
  53. package/dist/tools/calculate-hash.js +14 -27
  54. package/dist/tools/create-directory.js +11 -9
  55. package/dist/tools/delete-file.js +19 -19
  56. package/dist/tools/diff-files.js +16 -18
  57. package/dist/tools/edit-file.js +11 -5
  58. package/dist/tools/list-directory.js +16 -21
  59. package/dist/tools/move-file.js +105 -100
  60. package/dist/tools/read-multiple.js +15 -10
  61. package/dist/tools/read.js +6 -7
  62. package/dist/tools/replace-in-files.js +76 -115
  63. package/dist/tools/roots.js +3 -7
  64. package/dist/tools/search-content.js +158 -203
  65. package/dist/tools/search-files.js +59 -50
  66. package/dist/tools/shared.d.ts +10 -0
  67. package/dist/tools/shared.js +105 -36
  68. package/dist/tools/stat-many.js +15 -9
  69. package/dist/tools/stat.js +6 -6
  70. package/dist/tools/task-support.d.ts +10 -9
  71. package/dist/tools/task-support.js +94 -23
  72. package/dist/tools/tree.js +4 -4
  73. package/dist/tools/write-file.js +11 -12
  74. package/package.json +10 -9
@@ -50,36 +50,16 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import * as fsp from 'node:fs/promises';
54
- import * as path from 'node:path';
55
53
  import { isUtf8 } from 'node:buffer';
56
54
  import { randomUUID } from 'node:crypto';
55
+ import { open, rename, stat, unlink, writeFile, } from 'node:fs/promises';
56
+ import { extname } from 'node:path';
57
+ import { assertNotAborted, withAbort } from './abort.js';
57
58
  import { BINARY_CHECK_BUFFER_SIZE, KNOWN_BINARY_EXTENSIONS, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from './constants.js';
58
59
  import { ErrorCode, McpError, normalizeUnknownError } from './errors.js';
59
60
  import { assertAllowedFileAccess, validateExistingPath } from './paths.js';
60
- function createAbortError(message = 'Operation aborted') {
61
- return new DOMException(message, 'AbortError');
62
- }
61
+ export { assertNotAborted, createTimedAbortSignal, withAbort, withTimedAbortSignal, } from './abort.js';
63
62
  const READ_ONLY_FILE_FLAG = 'r';
64
- const SHARED_NOOP_SIGNAL = new AbortController().signal;
65
- function normalizeAbortReason(reason, message) {
66
- if (reason instanceof Error)
67
- return reason;
68
- return createAbortError(message);
69
- }
70
- function isFiniteNumber(value) {
71
- return typeof value === 'number' && Number.isFinite(value);
72
- }
73
- export function assertNotAborted(signal, message) {
74
- if (!signal)
75
- return;
76
- try {
77
- signal.throwIfAborted();
78
- }
79
- catch (reason) {
80
- throw normalizeAbortReason(reason, message);
81
- }
82
- }
83
63
  function assertPositiveSafeIntegerOption(name, value, message) {
84
64
  if (value === undefined)
85
65
  return;
@@ -87,73 +67,15 @@ function assertPositiveSafeIntegerOption(name, value, message) {
87
67
  !Number.isFinite(value) ||
88
68
  !Number.isSafeInteger(value) ||
89
69
  value < 1) {
90
- throw new McpError(ErrorCode.E_INVALID_INPUT, message ?? `${name} must be a positive integer`);
70
+ throw new McpError(ErrorCode.INVALID_INPUT, message ?? `${name} must be a positive integer`);
91
71
  }
92
72
  }
93
73
  function normalizeConcurrency(concurrency) {
94
74
  assertPositiveSafeIntegerOption('concurrency', concurrency);
95
75
  return concurrency;
96
76
  }
97
- function getAbortError(signal, message) {
98
- try {
99
- signal.throwIfAborted();
100
- }
101
- catch (reason) {
102
- return normalizeAbortReason(reason, message);
103
- }
104
- return createAbortError(message);
105
- }
106
- export function withAbort(promise, signal) {
107
- if (!signal)
108
- return promise;
109
- signal.throwIfAborted();
110
- return new Promise((resolve, reject) => {
111
- const onAbort = () => {
112
- reject(getAbortError(signal));
113
- };
114
- if (signal.aborted) {
115
- onAbort();
116
- return;
117
- }
118
- signal.addEventListener('abort', onAbort, { once: true });
119
- promise.then((value) => {
120
- signal.removeEventListener('abort', onAbort);
121
- resolve(value);
122
- }, (error) => {
123
- signal.removeEventListener('abort', onAbort);
124
- reject(normalizeUnknownError(error));
125
- });
126
- });
127
- }
128
- export function createTimedAbortSignal(baseSignal, timeoutMs) {
129
- const timeoutSignal = isFiniteNumber(timeoutMs)
130
- ? AbortSignal.timeout(timeoutMs)
131
- : undefined;
132
- if (baseSignal && timeoutSignal) {
133
- return {
134
- signal: AbortSignal.any([baseSignal, timeoutSignal]),
135
- cleanup: () => { },
136
- };
137
- }
138
- if (baseSignal) {
139
- return { signal: baseSignal, cleanup: () => { } };
140
- }
141
- if (timeoutSignal) {
142
- return { signal: timeoutSignal, cleanup: () => { } };
143
- }
144
- return { signal: SHARED_NOOP_SIGNAL, cleanup: () => { } };
145
- }
146
- export async function withTimedAbortSignal(baseSignal, timeoutMs, run) {
147
- const { signal, cleanup } = createTimedAbortSignal(baseSignal, timeoutMs);
148
- try {
149
- return await run(signal);
150
- }
151
- finally {
152
- cleanup();
153
- }
154
- }
155
77
  function createParallelAbortError() {
156
- return createAbortError();
78
+ return new DOMException('Operation aborted', 'AbortError');
157
79
  }
158
80
  export async function processInParallel(items, processor, concurrency = PARALLEL_CONCURRENCY, signal) {
159
81
  const itemCount = items.length;
@@ -218,11 +140,11 @@ export function isHidden(name) {
218
140
  return name.startsWith('.');
219
141
  }
220
142
  function hasKnownBinaryExtension(filePath) {
221
- const ext = path.extname(filePath).toLowerCase();
143
+ const ext = extname(filePath).toLowerCase();
222
144
  return KNOWN_BINARY_EXTENSIONS.has(ext);
223
145
  }
224
146
  async function openReadableFileHandle(filePath, signal) {
225
- return withAbort(fsp.open(filePath, READ_ONLY_FILE_FLAG), signal);
147
+ return withAbort(open(filePath, READ_ONLY_FILE_FLAG), signal);
226
148
  }
227
149
  async function readProbe(handle, signal) {
228
150
  const buffer = Buffer.alloc(BINARY_CHECK_BUFFER_SIZE);
@@ -282,18 +204,16 @@ function validateReadOptions(options) {
282
204
  assertPositiveSafeIntegerOption('startLine', options.startLine, 'startLine must be at least 1');
283
205
  assertPositiveSafeIntegerOption('endLine', options.endLine, 'endLine must be at least 1');
284
206
  if (hasHead && (hasStart || hasEnd)) {
285
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'head cannot be used together with startLine/endLine');
207
+ throw new McpError(ErrorCode.INVALID_INPUT, 'head cannot be used together with startLine/endLine');
286
208
  }
287
209
  if (hasTail && (hasHead || hasStart || hasEnd)) {
288
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'tail cannot be used together with head/startLine/endLine');
289
- }
290
- if (hasEnd && !hasStart) {
291
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine requires startLine');
210
+ throw new McpError(ErrorCode.INVALID_INPUT, 'tail cannot be used together with head/startLine/endLine');
292
211
  }
293
- if (options.startLine !== undefined &&
294
- options.endLine !== undefined &&
295
- options.endLine < options.startLine) {
296
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine must be greater than or equal to startLine');
212
+ {
213
+ const effectiveStart = options.startLine ?? 1;
214
+ if (options.endLine !== undefined && options.endLine < effectiveStart) {
215
+ throw new McpError(ErrorCode.INVALID_INPUT, 'endLine must be greater than or equal to startLine (default: 1)');
216
+ }
297
217
  }
298
218
  }
299
219
  function normalizeOptions(options) {
@@ -309,12 +229,13 @@ function normalizeOptions(options) {
309
229
  if (options.tail !== undefined) {
310
230
  normalized.tail = options.tail;
311
231
  }
312
- if (options.startLine !== undefined) {
313
- normalized.startLine = options.startLine;
314
- }
315
232
  if (options.endLine !== undefined) {
233
+ normalized.startLine = options.startLine ?? 1;
316
234
  normalized.endLine = options.endLine;
317
235
  }
236
+ else if (options.startLine !== undefined) {
237
+ normalized.startLine = options.startLine;
238
+ }
318
239
  if (options.signal) {
319
240
  normalized.signal = options.signal;
320
241
  }
@@ -340,13 +261,13 @@ function resolveReadMode(options) {
340
261
  return 'head';
341
262
  if (options.tail !== undefined)
342
263
  return 'tail';
343
- if (options.startLine !== undefined)
264
+ if (options.startLine !== undefined || options.endLine !== undefined)
344
265
  return 'range';
345
266
  return 'full';
346
267
  }
347
268
  const STREAM_CHUNK_SIZE = 64 * 1024;
348
269
  function createTooLargeError(bytesRead, maxSize, requestedPath) {
349
- return new McpError(ErrorCode.E_TOO_LARGE, `File exceeds maximum size (${bytesRead} > ${maxSize}): ${requestedPath}`, requestedPath, { size: bytesRead, maxSize });
270
+ return new McpError(ErrorCode.TOO_LARGE, `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`, requestedPath, { size: bytesRead, maxSize });
350
271
  }
351
272
  async function readFileBufferWithLimit(handle, maxSize, requestedPath, signal) {
352
273
  const stream = handle.createReadStream({
@@ -522,19 +443,19 @@ async function assertNotBinary(validPath, filePath, normalized) {
522
443
  const isBinary = await isProbablyBinary(validPath, undefined, normalized.signal);
523
444
  if (!isBinary)
524
445
  return;
525
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Binary file detected: ${filePath}. Refusing to read as text.`, filePath);
446
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);
526
447
  }
527
448
  function assertSizeWithinLimit(size, maxSize, filePath) {
528
449
  if (size <= maxSize)
529
450
  return;
530
- throw new McpError(ErrorCode.E_TOO_LARGE, `File too large: ${size} bytes (max: ${maxSize} bytes). Use head parameter to preview the first N lines.`, filePath, { size, maxSize });
451
+ throw new McpError(ErrorCode.TOO_LARGE, `File too large (${size} > ${maxSize} bytes). Use head to preview.`, filePath, { size, maxSize });
531
452
  }
532
453
  function requireReadOption(normalized, key, filePath) {
533
454
  const value = normalized[key];
534
455
  if (value !== undefined) {
535
456
  return value;
536
457
  }
537
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Missing ${key} option`, filePath);
458
+ throw new McpError(ErrorCode.INVALID_INPUT, `Missing ${key} option`, filePath);
538
459
  }
539
460
  async function executeHeadRead(context) {
540
461
  const head = requireReadOption(context.normalized, 'head', context.filePath);
@@ -605,7 +526,7 @@ async function readByMode(context) {
605
526
  }
606
527
  function assertFileStats(filePath, stats) {
607
528
  if (!stats.isFile()) {
608
- throw new McpError(ErrorCode.E_NOT_FILE, `Not a file: ${filePath}`, filePath);
529
+ throw new McpError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);
609
530
  }
610
531
  }
611
532
  async function readFileWithStatsInternal(filePath, validPath, stats, normalized) {
@@ -645,7 +566,7 @@ export async function readFile(filePath, options = {}) {
645
566
  const normalized = prepareReadOptions(options);
646
567
  const validPath = await validateExistingPath(filePath, normalized.signal);
647
568
  assertNotAborted(normalized.signal);
648
- const stats = await withAbort(fsp.stat(validPath), normalized.signal);
569
+ const stats = await withAbort(stat(validPath), normalized.signal);
649
570
  return readFileWithStatsInternal(filePath, validPath, stats, normalized);
650
571
  }
651
572
  export async function atomicWriteFile(filePath, content, options = {}) {
@@ -653,13 +574,13 @@ export async function atomicWriteFile(filePath, content, options = {}) {
653
574
  const tempPath = `${filePath}.${randomUUID()}.tmp`;
654
575
  try {
655
576
  assertNotAborted(signal);
656
- await fsp.writeFile(tempPath, content, { encoding, signal });
657
- await withAbort(fsp.rename(tempPath, filePath), signal);
577
+ await writeFile(tempPath, content, { encoding, signal });
578
+ await withAbort(rename(tempPath, filePath), signal);
658
579
  }
659
580
  catch (error) {
660
581
  // Attempt cleanup on error, but don't overwrite the original error
661
582
  try {
662
- await fsp.unlink(tempPath).catch(() => { });
583
+ await unlink(tempPath).catch(() => { });
663
584
  }
664
585
  catch {
665
586
  // Ignore cleanup errors
@@ -0,0 +1,2 @@
1
+ export declare function isSafeGlobPattern(value: string): boolean;
2
+ export declare function assertSafeGlobPattern(value: string, message?: string): void;
@@ -0,0 +1,19 @@
1
+ import { ErrorCode, McpError } from './errors.js';
2
+ const ABSOLUTE_GLOB_RE = /^([/\\]|[A-Za-z]:[/\\]|\\\\)/u;
3
+ const PARENT_SEGMENT_RE = /[\\/]\.\.(?:[/\\]|$)/u;
4
+ export function isSafeGlobPattern(value) {
5
+ if (value.length === 0)
6
+ return false;
7
+ if (value.includes('**/**/**'))
8
+ return false;
9
+ if (ABSOLUTE_GLOB_RE.test(value))
10
+ return false;
11
+ if (value.startsWith('..') || PARENT_SEGMENT_RE.test(value))
12
+ return false;
13
+ return true;
14
+ }
15
+ export function assertSafeGlobPattern(value, message = 'Invalid glob or unsafe path (absolute/.. forbidden)') {
16
+ if (!isSafeGlobPattern(value)) {
17
+ throw new McpError(ErrorCode.INVALID_PATTERN, message);
18
+ }
19
+ }
@@ -0,0 +1,28 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
4
+ export interface SessionContextData {
5
+ sessionId?: string;
6
+ }
7
+ export declare const SessionContext: AsyncLocalStorage<SessionContextData>;
8
+ export interface LogEvent {
9
+ level: LoggingLevel;
10
+ message: string;
11
+ data?: unknown;
12
+ sessionId?: string;
13
+ }
14
+ export interface LoggingState {
15
+ minimumLevel: LoggingLevel;
16
+ }
17
+ export declare function createLoggingState(minimumLevel?: LoggingLevel): LoggingState;
18
+ export declare function canSendMcpLogs(server: McpServer): boolean;
19
+ export declare function logToMcp(server: McpServer | undefined, level: LoggingLevel, data: string, minLevel?: LoggingLevel): void;
20
+ export declare const Logger: {
21
+ emit(level: LoggingLevel, message: string, data?: unknown): void;
22
+ debug(message: string, data?: unknown): void;
23
+ info(message: string, data?: unknown): void;
24
+ notice(message: string, data?: unknown): void;
25
+ warn(message: string, data?: unknown): void;
26
+ error(message: string, data?: unknown): void;
27
+ critical(message: string, data?: unknown): void;
28
+ };
@@ -0,0 +1,91 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { channel } from 'node:diagnostics_channel';
3
+ export const SessionContext = new AsyncLocalStorage();
4
+ const LOG_CHANNEL = channel('filesystem-mcp:log');
5
+ const MCP_LOGGER_NAME = 'filesystem-mcp';
6
+ const LOG_LEVEL_ORDER = {
7
+ debug: 0,
8
+ info: 1,
9
+ notice: 2,
10
+ warning: 3,
11
+ error: 4,
12
+ critical: 5,
13
+ alert: 6,
14
+ emergency: 7,
15
+ };
16
+ export function createLoggingState(minimumLevel = 'debug') {
17
+ return { minimumLevel };
18
+ }
19
+ export function canSendMcpLogs(server) {
20
+ const capabilities = server.server.getClientCapabilities();
21
+ if (!capabilities || typeof capabilities !== 'object')
22
+ return false;
23
+ return 'logging' in capabilities && Boolean(capabilities.logging);
24
+ }
25
+ export function logToMcp(server, level, data, minLevel = 'debug') {
26
+ if (LOG_LEVEL_ORDER[level] < LOG_LEVEL_ORDER[minLevel]) {
27
+ return;
28
+ }
29
+ if (!server || !canSendMcpLogs(server)) {
30
+ console.error(`[${level.toUpperCase()}] ${data}`);
31
+ return;
32
+ }
33
+ const params = {
34
+ level,
35
+ logger: MCP_LOGGER_NAME,
36
+ data,
37
+ };
38
+ void server.sendLoggingMessage(params).catch((error) => {
39
+ console.error(`Failed to send MCP log: ${level} | ${data}`, formatTransportError(error));
40
+ });
41
+ }
42
+ function formatTransportError(error) {
43
+ if (error instanceof Error)
44
+ return error.message;
45
+ if (typeof error === 'string')
46
+ return error;
47
+ try {
48
+ return JSON.stringify(error);
49
+ }
50
+ catch {
51
+ return String(error);
52
+ }
53
+ }
54
+ export const Logger = {
55
+ emit(level, message, data) {
56
+ const session = SessionContext.getStore();
57
+ const event = {
58
+ level,
59
+ message,
60
+ ...(data !== undefined ? { data } : {}),
61
+ ...(session?.sessionId !== undefined
62
+ ? { sessionId: session.sessionId }
63
+ : {}),
64
+ };
65
+ if (LOG_CHANNEL.hasSubscribers) {
66
+ LOG_CHANNEL.publish(event);
67
+ }
68
+ else {
69
+ // Fallback if no subscribers
70
+ console.error(`[${level.toUpperCase()}] ${message}`, data ?? '');
71
+ }
72
+ },
73
+ debug(message, data) {
74
+ this.emit('debug', message, data);
75
+ },
76
+ info(message, data) {
77
+ this.emit('info', message, data);
78
+ },
79
+ notice(message, data) {
80
+ this.emit('notice', message, data);
81
+ },
82
+ warn(message, data) {
83
+ this.emit('warning', message, data);
84
+ },
85
+ error(message, data) {
86
+ this.emit('error', message, data);
87
+ },
88
+ critical(message, data) {
89
+ this.emit('critical', message, data);
90
+ },
91
+ };
@@ -5,6 +5,11 @@ interface OpsTraceContext {
5
5
  path?: string | undefined;
6
6
  [key: string]: unknown;
7
7
  }
8
+ export interface TraceContext {
9
+ traceparent: string;
10
+ tracestate?: string;
11
+ baggage?: string;
12
+ }
8
13
  interface ToolMetrics {
9
14
  calls: number;
10
15
  errors: number;
@@ -19,8 +24,10 @@ export declare function getToolContextSnapshot(): {
19
24
  tool: string;
20
25
  path?: string;
21
26
  } | undefined;
27
+ export declare function getTraceContext(): TraceContext | undefined;
22
28
  export declare function startPerfMeasure(name: string, detail?: Record<string, unknown>): ((ok?: boolean) => void) | undefined;
23
29
  export declare function withToolDiagnostics<T>(tool: string, run: () => Promise<T>, options?: {
24
30
  path?: string;
31
+ traceContext?: TraceContext;
25
32
  }): Promise<T>;
26
33
  export {};
@@ -3,16 +3,18 @@ import { hash } from 'node:crypto';
3
3
  import { channel, tracingChannel } from 'node:diagnostics_channel';
4
4
  import { monitorEventLoopDelay, performance, PerformanceObserver, } from 'node:perf_hooks';
5
5
  import { parseTrueEnvFlag } from './constants.js';
6
+ import { Logger } from './logger.js';
6
7
  import { isRecord } from './utils.js';
7
8
  // --- Configuration ---
8
9
  const ENV = process.env;
9
10
  let _cachedConfig;
10
11
  function readConfig() {
11
- return (_cachedConfig ??= {
12
+ _cachedConfig ??= {
12
13
  enabled: parseTrueEnvFlag(ENV['FS_CONTEXT_DIAGNOSTICS']),
13
14
  detail: parseDetail(ENV['FS_CONTEXT_DIAGNOSTICS_DETAIL']),
14
15
  logToolErrors: parseTrueEnvFlag(ENV['FS_CONTEXT_TOOL_LOG_ERRORS']),
15
- });
16
+ };
17
+ return _cachedConfig;
16
18
  }
17
19
  function parseDetail(val) {
18
20
  if (val === '2')
@@ -201,6 +203,9 @@ function applyToolContext(context) {
201
203
  export function getToolContextSnapshot() {
202
204
  return toolContext.getStore();
203
205
  }
206
+ export function getTraceContext() {
207
+ return toolContext.getStore()?.traceContext;
208
+ }
204
209
  function normalizeContext(ctx) {
205
210
  if (!ctx.path)
206
211
  return ctx;
@@ -254,16 +259,20 @@ export function startPerfMeasure(name, detail) {
254
259
  }
255
260
  };
256
261
  }
257
- function publishToolStart(tool, pathVal) {
262
+ function publishToolStart(tool, pathVal, traceparent) {
258
263
  const event = { phase: 'start', tool };
259
264
  if (pathVal)
260
265
  event.path = pathVal;
266
+ if (traceparent)
267
+ event.traceparent = traceparent;
261
268
  CHANNELS.tool.publish(event);
262
269
  }
263
- function publishToolEnd(tool, ok, durationMs, errorMsg) {
270
+ function publishToolEnd(tool, ok, durationMs, errorMsg, traceparent) {
264
271
  const event = { phase: 'end', tool, ok, durationMs };
265
272
  if (errorMsg)
266
273
  event.error = errorMsg;
274
+ if (traceparent)
275
+ event.traceparent = traceparent;
267
276
  CHANNELS.tool.publish(event);
268
277
  }
269
278
  function publishPerfEnd(tool, durationMs, eluStart, loopMonitor) {
@@ -281,13 +290,13 @@ function publishPerfEnd(tool, durationMs, eluStart, loopMonitor) {
281
290
  }
282
291
  CHANNELS.perf.publish(event);
283
292
  }
284
- async function runAndObserve(tool, run, pubTool, pubPerf, logErrors, pathVal) {
293
+ async function runAndObserve(tool, run, pubTool, pubPerf, logErrors, pathVal, traceparent) {
285
294
  const startMs = performance.now();
286
295
  const eluStart = pubPerf ? performance.eventLoopUtilization() : undefined;
287
296
  const loopMonitor = pubPerf ? monitorEventLoopDelay() : undefined;
288
297
  loopMonitor?.enable();
289
298
  if (pubTool)
290
- publishToolStart(tool, pathVal);
299
+ publishToolStart(tool, pathVal, traceparent);
291
300
  let result;
292
301
  const obs = { ok: false, errorMsg: undefined };
293
302
  try {
@@ -306,7 +315,7 @@ async function runAndObserve(tool, run, pubTool, pubPerf, logErrors, pathVal) {
306
315
  if (pubPerf && eluStart)
307
316
  publishPerfEnd(tool, durationMs, eluStart, loopMonitor);
308
317
  if (pubTool)
309
- publishToolEnd(tool, obs.ok, durationMs, obs.errorMsg);
318
+ publishToolEnd(tool, obs.ok, durationMs, obs.errorMsg, traceparent);
310
319
  updateMetrics(tool, obs.ok, durationMs);
311
320
  if (logErrors && !obs.ok)
312
321
  logError(tool, durationMs, obs.errorMsg);
@@ -319,6 +328,7 @@ export async function withToolDiagnostics(tool, run, options) {
319
328
  const context = {
320
329
  tool,
321
330
  ...(options?.path ? { path: options.path } : {}),
331
+ ...(options?.traceContext ? { traceContext: options.traceContext } : {}),
322
332
  };
323
333
  return toolContext.run(context, async () => {
324
334
  if (!config.enabled) {
@@ -354,10 +364,10 @@ export async function withToolDiagnostics(tool, run, options) {
354
364
  throw e;
355
365
  }
356
366
  }
357
- return runAndObserve(tool, run, pubTool, pubPerf, config.logToolErrors, normalizedPath);
367
+ return runAndObserve(tool, run, pubTool, pubPerf, config.logToolErrors, normalizedPath, options?.traceContext?.traceparent);
358
368
  });
359
369
  }
360
370
  function logError(tool, durationMs, msg) {
361
371
  const suffix = msg ? `: ${msg}` : '';
362
- console.error(`[ToolError] ${tool} failed in ${durationMs.toFixed(1)}ms${suffix}`);
372
+ Logger.error(`[ToolError] ${tool} failed in ${durationMs.toFixed(1)}ms${suffix}`);
363
373
  }