@j0hanz/filesystem-mcp 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -71
  3. package/dist/cli.js +2 -2
  4. package/dist/completions.js +54 -51
  5. package/dist/config.d.ts +13 -13
  6. package/dist/config.js +12 -12
  7. package/dist/index.js +1 -1
  8. package/dist/lib/abort.d.ts +7 -0
  9. package/dist/lib/abort.js +81 -0
  10. package/dist/lib/constants.d.ts +3 -1
  11. package/dist/lib/constants.js +8 -2
  12. package/dist/lib/errors.d.ts +7 -3
  13. package/dist/lib/errors.js +59 -39
  14. package/dist/lib/file-operations/core.d.ts +3 -3
  15. package/dist/lib/file-operations/core.js +23 -20
  16. package/dist/lib/file-operations/metadata.d.ts +2 -2
  17. package/dist/lib/file-operations/metadata.js +60 -19
  18. package/dist/lib/file-operations/search.js +83 -84
  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 +20 -98
  22. package/dist/lib/globs.js +1 -1
  23. package/dist/lib/logger.d.ts +28 -0
  24. package/dist/lib/logger.js +91 -0
  25. package/dist/lib/observability.d.ts +7 -0
  26. package/dist/lib/observability.js +19 -9
  27. package/dist/lib/paths.js +55 -55
  28. package/dist/lib/resource-store.js +4 -4
  29. package/dist/lib/utils.d.ts +0 -12
  30. package/dist/lib/utils.js +0 -13
  31. package/dist/resources/generated-instructions.js +40 -31
  32. package/dist/resources/tool-catalog.js +32 -26
  33. package/dist/resources/tool-info.js +34 -29
  34. package/dist/resources/workflows.js +39 -18
  35. package/dist/resources.d.ts +1 -1
  36. package/dist/resources.js +4 -4
  37. package/dist/schemas.d.ts +66 -66
  38. package/dist/schemas.js +21 -44
  39. package/dist/server/bootstrap.d.ts +12 -11
  40. package/dist/server/bootstrap.js +95 -86
  41. package/dist/server/roots-manager.d.ts +5 -2
  42. package/dist/server/roots-manager.js +8 -6
  43. package/dist/server/task-store.d.ts +10 -0
  44. package/dist/server/task-store.js +73 -0
  45. package/dist/tools/apply-patch.js +26 -18
  46. package/dist/tools/calculate-hash.js +11 -22
  47. package/dist/tools/create-directory.js +10 -8
  48. package/dist/tools/delete-file.js +17 -15
  49. package/dist/tools/diff-files.js +15 -15
  50. package/dist/tools/edit-file.js +7 -4
  51. package/dist/tools/list-directory.js +6 -6
  52. package/dist/tools/move-file.js +98 -81
  53. package/dist/tools/read-multiple.js +4 -4
  54. package/dist/tools/read.js +5 -5
  55. package/dist/tools/replace-in-files.js +20 -23
  56. package/dist/tools/roots.js +1 -1
  57. package/dist/tools/search-content.js +32 -41
  58. package/dist/tools/search-files.js +54 -41
  59. package/dist/tools/shared.d.ts +3 -0
  60. package/dist/tools/shared.js +70 -28
  61. package/dist/tools/stat-many.js +11 -7
  62. package/dist/tools/stat.js +4 -4
  63. package/dist/tools/task-support.d.ts +10 -9
  64. package/dist/tools/task-support.js +94 -23
  65. package/dist/tools/tree.js +3 -3
  66. package/dist/tools/write-file.js +10 -7
  67. package/package.json +9 -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,15 +204,15 @@ 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');
210
+ throw new McpError(ErrorCode.INVALID_INPUT, 'tail cannot be used together with head/startLine/endLine');
289
211
  }
290
212
  {
291
213
  const effectiveStart = options.startLine ?? 1;
292
214
  if (options.endLine !== undefined && options.endLine < effectiveStart) {
293
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine must be greater than or equal to startLine (default: 1)');
215
+ throw new McpError(ErrorCode.INVALID_INPUT, 'endLine must be greater than or equal to startLine (default: 1)');
294
216
  }
295
217
  }
296
218
  }
@@ -345,7 +267,7 @@ function resolveReadMode(options) {
345
267
  }
346
268
  const STREAM_CHUNK_SIZE = 64 * 1024;
347
269
  function createTooLargeError(bytesRead, maxSize, requestedPath) {
348
- 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 });
349
271
  }
350
272
  async function readFileBufferWithLimit(handle, maxSize, requestedPath, signal) {
351
273
  const stream = handle.createReadStream({
@@ -521,19 +443,19 @@ async function assertNotBinary(validPath, filePath, normalized) {
521
443
  const isBinary = await isProbablyBinary(validPath, undefined, normalized.signal);
522
444
  if (!isBinary)
523
445
  return;
524
- 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);
525
447
  }
526
448
  function assertSizeWithinLimit(size, maxSize, filePath) {
527
449
  if (size <= maxSize)
528
450
  return;
529
- 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 });
530
452
  }
531
453
  function requireReadOption(normalized, key, filePath) {
532
454
  const value = normalized[key];
533
455
  if (value !== undefined) {
534
456
  return value;
535
457
  }
536
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Missing ${key} option`, filePath);
458
+ throw new McpError(ErrorCode.INVALID_INPUT, `Missing ${key} option`, filePath);
537
459
  }
538
460
  async function executeHeadRead(context) {
539
461
  const head = requireReadOption(context.normalized, 'head', context.filePath);
@@ -604,7 +526,7 @@ async function readByMode(context) {
604
526
  }
605
527
  function assertFileStats(filePath, stats) {
606
528
  if (!stats.isFile()) {
607
- throw new McpError(ErrorCode.E_NOT_FILE, `Not a file: ${filePath}`, filePath);
529
+ throw new McpError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);
608
530
  }
609
531
  }
610
532
  async function readFileWithStatsInternal(filePath, validPath, stats, normalized) {
@@ -644,7 +566,7 @@ export async function readFile(filePath, options = {}) {
644
566
  const normalized = prepareReadOptions(options);
645
567
  const validPath = await validateExistingPath(filePath, normalized.signal);
646
568
  assertNotAborted(normalized.signal);
647
- const stats = await withAbort(fsp.stat(validPath), normalized.signal);
569
+ const stats = await withAbort(stat(validPath), normalized.signal);
648
570
  return readFileWithStatsInternal(filePath, validPath, stats, normalized);
649
571
  }
650
572
  export async function atomicWriteFile(filePath, content, options = {}) {
@@ -652,13 +574,13 @@ export async function atomicWriteFile(filePath, content, options = {}) {
652
574
  const tempPath = `${filePath}.${randomUUID()}.tmp`;
653
575
  try {
654
576
  assertNotAborted(signal);
655
- await fsp.writeFile(tempPath, content, { encoding, signal });
656
- await withAbort(fsp.rename(tempPath, filePath), signal);
577
+ await writeFile(tempPath, content, { encoding, signal });
578
+ await withAbort(rename(tempPath, filePath), signal);
657
579
  }
658
580
  catch (error) {
659
581
  // Attempt cleanup on error, but don't overwrite the original error
660
582
  try {
661
- await fsp.unlink(tempPath).catch(() => { });
583
+ await unlink(tempPath).catch(() => { });
662
584
  }
663
585
  catch {
664
586
  // Ignore cleanup errors
package/dist/lib/globs.js CHANGED
@@ -14,6 +14,6 @@ export function isSafeGlobPattern(value) {
14
14
  }
15
15
  export function assertSafeGlobPattern(value, message = 'Invalid glob or unsafe path (absolute/.. forbidden)') {
16
16
  if (!isSafeGlobPattern(value)) {
17
- throw new McpError(ErrorCode.E_INVALID_PATTERN, message);
17
+ throw new McpError(ErrorCode.INVALID_PATTERN, message);
18
18
  }
19
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
  }