@j0hanz/filesystem-mcp 1.16.2 → 1.16.3

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 (69) hide show
  1. package/README.md +18 -14
  2. package/dist/assets/logo.svg +26 -11
  3. package/dist/cli.js +1 -1
  4. package/dist/completions.d.ts +1 -1
  5. package/dist/completions.js +1 -2
  6. package/dist/lib/constants.d.ts +2 -0
  7. package/dist/lib/constants.js +2 -3
  8. package/dist/lib/fs-helpers.d.ts +1 -1
  9. package/dist/lib/fs-helpers.js +0 -1
  10. package/dist/lib/logger.d.ts +3 -4
  11. package/dist/lib/logger.js +1 -1
  12. package/dist/lib/paths.d.ts +1 -1
  13. package/dist/prompts.d.ts +1 -1
  14. package/dist/prompts.js +11 -11
  15. package/dist/resources/workflows.js +12 -0
  16. package/dist/resources.d.ts +1 -1
  17. package/dist/resources.js +1 -1
  18. package/dist/schemas.d.ts +4 -4
  19. package/dist/server/bootstrap.d.ts +1 -10
  20. package/dist/server/bootstrap.js +165 -95
  21. package/dist/server/roots-manager.d.ts +3 -2
  22. package/dist/server/roots-manager.js +30 -4
  23. package/dist/server/task-store.d.ts +2 -3
  24. package/dist/server/task-store.js +1 -1
  25. package/dist/tools/apply-patch.d.ts +1 -1
  26. package/dist/tools/apply-patch.js +16 -12
  27. package/dist/tools/calculate-hash.d.ts +1 -1
  28. package/dist/tools/calculate-hash.js +8 -12
  29. package/dist/tools/contract.d.ts +5 -0
  30. package/dist/tools/create-directory.d.ts +1 -1
  31. package/dist/tools/create-directory.js +7 -10
  32. package/dist/tools/delete-file.d.ts +1 -1
  33. package/dist/tools/delete-file.js +12 -11
  34. package/dist/tools/diff-files.d.ts +1 -1
  35. package/dist/tools/diff-files.js +8 -11
  36. package/dist/tools/edit-file.d.ts +1 -1
  37. package/dist/tools/edit-file.js +12 -11
  38. package/dist/tools/icons.d.ts +15 -0
  39. package/dist/tools/icons.js +24 -0
  40. package/dist/tools/list-directory.d.ts +1 -1
  41. package/dist/tools/list-directory.js +7 -10
  42. package/dist/tools/move-file.d.ts +1 -1
  43. package/dist/tools/move-file.js +12 -11
  44. package/dist/tools/read-multiple.d.ts +1 -1
  45. package/dist/tools/read-multiple.js +8 -12
  46. package/dist/tools/read.d.ts +1 -1
  47. package/dist/tools/read.js +7 -10
  48. package/dist/tools/replace-in-files.d.ts +1 -1
  49. package/dist/tools/replace-in-files.js +11 -13
  50. package/dist/tools/roots.d.ts +1 -1
  51. package/dist/tools/roots.js +7 -10
  52. package/dist/tools/search-content.d.ts +1 -1
  53. package/dist/tools/search-content.js +8 -13
  54. package/dist/tools/search-files.d.ts +1 -1
  55. package/dist/tools/search-files.js +11 -16
  56. package/dist/tools/shared.d.ts +15 -12
  57. package/dist/tools/shared.js +84 -56
  58. package/dist/tools/stat-many.d.ts +1 -1
  59. package/dist/tools/stat-many.js +8 -12
  60. package/dist/tools/stat.d.ts +1 -1
  61. package/dist/tools/stat.js +7 -10
  62. package/dist/tools/task-support.d.ts +14 -12
  63. package/dist/tools/task-support.js +98 -92
  64. package/dist/tools/tree.d.ts +1 -1
  65. package/dist/tools/tree.js +11 -15
  66. package/dist/tools/write-file.d.ts +1 -1
  67. package/dist/tools/write-file.js +12 -11
  68. package/dist/tools.d.ts +1 -1
  69. package/package.json +6 -4
@@ -2,8 +2,9 @@ import { ErrorCode } from '../lib/errors.js';
2
2
  import { getAllowedDirectories } from '../lib/paths.js';
3
3
  import { joinLines } from '../config.js';
4
4
  import { ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, } from '../schemas.js';
5
- import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
6
- import { registerToolTaskIfAvailable } from './task-support.js';
5
+ import { DIRECTORY_ICONS } from './icons.js';
6
+ import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, } from './shared.js';
7
+ import { registerStandardTool } from './task-support.js';
7
8
  export const LIST_ALLOWED_DIRECTORIES_TOOL = {
8
9
  name: 'roots',
9
10
  title: 'Workspace Roots',
@@ -11,6 +12,7 @@ export const LIST_ALLOWED_DIRECTORIES_TOOL = {
11
12
  inputSchema: ListAllowedDirectoriesInputSchema,
12
13
  outputSchema: ListAllowedDirectoriesOutputSchema,
13
14
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
15
+ icons: DIRECTORY_ICONS,
14
16
  taskSupport: 'forbidden',
15
17
  };
16
18
  function buildTextRoots(dirs) {
@@ -31,15 +33,14 @@ function handleListAllowedDirectories() {
31
33
  return buildToolResponse(buildTextRoots(dirs), structured);
32
34
  }
33
35
  export function registerListAllowedDirectoriesTool(server, options = {}) {
34
- const handler = (_args, extra) => executeToolWithDiagnostics({
36
+ const handler = (_args, ctx) => executeToolWithDiagnostics({
35
37
  toolName: 'roots',
36
- extra,
38
+ ctx,
37
39
  outputSchema: ListAllowedDirectoriesOutputSchema,
38
40
  run: () => handleListAllowedDirectories(),
39
41
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN),
40
42
  });
41
- const wrappedHandler = wrapToolHandler(handler, {
42
- guard: options.isInitialized,
43
+ registerStandardTool(server, LIST_ALLOWED_DIRECTORIES_TOOL, handler, options, {
43
44
  progressMessage: () => '≣ roots',
44
45
  completionMessage: (_args, result) => {
45
46
  if (result.isError)
@@ -49,8 +50,4 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
49
50
  return `≣ roots • ${count} ${count === 1 ? 'root' : 'roots'}`;
50
51
  },
51
52
  });
52
- const validatedHandler = withValidatedArgs(ListAllowedDirectoriesInputSchema, wrappedHandler);
53
- if (registerToolTaskIfAvailable(server, 'roots', LIST_ALLOWED_DIRECTORIES_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
54
- return;
55
- server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), validatedHandler);
56
53
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const SEARCH_CONTENT_TOOL: ToolContract;
4
4
  export declare function registerSearchContentTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -5,8 +5,9 @@ import { ErrorCode, formatUnknownErrorMessage, McpError, } from '../lib/errors.j
5
5
  import { searchContent, } from '../lib/file-operations/search.js';
6
6
  import { formatOperationSummary } from '../config.js';
7
7
  import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
8
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
9
- import { registerToolTaskIfAvailable } from './task-support.js';
8
+ import { SEARCH_ICONS } from './icons.js';
9
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, } from './shared.js';
10
+ import { registerStandardTool } from './task-support.js';
10
11
  /**
11
12
  * Configuration constants for the Search Content tool.
12
13
  */
@@ -70,6 +71,7 @@ export const SEARCH_CONTENT_TOOL = {
70
71
  inputSchema: SearchContentInputSchema,
71
72
  outputSchema: SearchContentOutputSchema,
72
73
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
74
+ icons: SEARCH_ICONS,
73
75
  nuances: [
74
76
  'Inline results capped at 50 matches; full results via `resourceUri`.',
75
77
  ],
@@ -259,15 +261,15 @@ async function handleSearchContent(args, signal, resourceStore, onProgress) {
259
261
  return buildToolResponse(text, fullStructured);
260
262
  }
261
263
  export function registerSearchContentTool(server, options = {}) {
262
- const handler = (args, extra) => executeToolWithDiagnostics({
264
+ const handler = (args, ctx) => executeToolWithDiagnostics({
263
265
  toolName: 'grep',
264
- extra,
266
+ ctx,
265
267
  outputSchema: SearchContentOutputSchema,
266
268
  context: { path: args.path ?? '.' },
267
269
  run: async (signal) => {
268
270
  const { pattern, filePattern: scope } = args;
269
271
  const progressLabel = `🔎︎ grep: ${truncateProgressPattern(pattern)}`;
270
- const progress = createToolProgressSession(extra, progressLabel);
272
+ const progress = createToolProgressSession(ctx, progressLabel);
271
273
  const progressWithMessage = ({ current, total, }) => {
272
274
  progress.update({
273
275
  current,
@@ -291,12 +293,5 @@ export function registerSearchContentTool(server, options = {}) {
291
293
  },
292
294
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path ?? '.'),
293
295
  });
294
- const { isInitialized } = options;
295
- const wrappedHandler = wrapToolHandler(handler, {
296
- guard: isInitialized,
297
- });
298
- const validatedHandler = withValidatedArgs(SearchContentInputSchema, wrappedHandler);
299
- if (registerToolTaskIfAvailable(server, 'grep', SEARCH_CONTENT_TOOL, validatedHandler, options.iconInfo, isInitialized))
300
- return;
301
- server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo), validatedHandler);
296
+ registerStandardTool(server, SEARCH_CONTENT_TOOL, handler, options);
302
297
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const SEARCH_FILES_TOOL: ToolContract;
4
4
  export declare function registerSearchFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -4,8 +4,9 @@ import { ErrorCode } from '../lib/errors.js';
4
4
  import { searchFiles } from '../lib/file-operations/search.js';
5
5
  import { formatOperationSummary, joinLines } from '../config.js';
6
6
  import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
7
- import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
8
- import { registerToolTaskIfAvailable } from './task-support.js';
7
+ import { DIRECTORY_ICONS } from './icons.js';
8
+ import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, truncateProgressPattern, } from './shared.js';
9
+ import { registerStandardTool } from './task-support.js';
9
10
  export const SEARCH_FILES_TOOL = {
10
11
  name: 'find',
11
12
  title: 'Find Files',
@@ -14,6 +15,7 @@ export const SEARCH_FILES_TOOL = {
14
15
  inputSchema: SearchFilesInputSchema,
15
16
  outputSchema: SearchFilesOutputSchema,
16
17
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
18
+ icons: DIRECTORY_ICONS,
17
19
  nuances: ['Respects `.gitignore` unless `includeIgnored=true`.'],
18
20
  taskSupport: 'optional',
19
21
  };
@@ -109,9 +111,9 @@ async function handleSearchFiles(args, signal, onProgress) {
109
111
  return buildToolResponse(text, structured);
110
112
  }
111
113
  export function registerSearchFilesTool(server, options = {}) {
112
- const handler = (args, extra) => executeToolWithDiagnostics({
114
+ const handler = (args, ctx) => executeToolWithDiagnostics({
113
115
  toolName: 'find',
114
- extra,
116
+ ctx,
115
117
  outputSchema: SearchFilesOutputSchema,
116
118
  timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
117
119
  context: { path: args.path ?? '.' },
@@ -122,11 +124,11 @@ export function registerSearchFilesTool(server, options = {}) {
122
124
  const truncatedPattern = truncateProgressPattern(pattern);
123
125
  const context = `${truncatedPattern} in ${scopeLabel}`;
124
126
  let progressCursor = 0;
125
- notifyProgress(extra, {
127
+ notifyProgress(ctx, {
126
128
  current: 0,
127
129
  message: `🔎︎ find: ${truncatedPattern}`,
128
130
  });
129
- const baseReporter = createProgressReporter(extra);
131
+ const baseReporter = createProgressReporter(ctx);
130
132
  const progressWithMessage = ({ current, total, }) => {
131
133
  if (current > progressCursor)
132
134
  progressCursor = current;
@@ -157,7 +159,7 @@ export function registerSearchFilesTool(server, options = {}) {
157
159
  }
158
160
  }
159
161
  const finalCurrent = Math.max((sc.filesScanned ?? 0) + 1, progressCursor + 1);
160
- notifyProgress(extra, {
162
+ notifyProgress(ctx, {
161
163
  current: finalCurrent,
162
164
  total: finalCurrent,
163
165
  message: `🔎︎ find: ${context} • ${suffix}`,
@@ -166,7 +168,7 @@ export function registerSearchFilesTool(server, options = {}) {
166
168
  }
167
169
  catch (error) {
168
170
  const finalCurrent = Math.max(progressCursor + 1, 1);
169
- notifyProgress(extra, {
171
+ notifyProgress(ctx, {
170
172
  current: finalCurrent,
171
173
  total: finalCurrent,
172
174
  message: `🔎︎ find: ${context} • failed`,
@@ -176,12 +178,5 @@ export function registerSearchFilesTool(server, options = {}) {
176
178
  },
177
179
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
178
180
  });
179
- const { isInitialized } = options;
180
- const wrappedHandler = wrapToolHandler(handler, {
181
- guard: isInitialized,
182
- });
183
- const validatedHandler = withValidatedArgs(SearchFilesInputSchema, wrappedHandler);
184
- if (registerToolTaskIfAvailable(server, 'find', SEARCH_FILES_TOOL, validatedHandler, options.iconInfo, isInitialized))
185
- return;
186
- server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo), validatedHandler);
181
+ registerStandardTool(server, SEARCH_FILES_TOOL, handler, options);
187
182
  }
@@ -1,4 +1,4 @@
1
- import type { ContentBlock, Icon, ProgressNotificationParams } from '@modelcontextprotocol/sdk/types.js';
1
+ import type { ContentBlock, Icon, LoggingLevel, ProgressNotificationParams, ServerContext } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import { ErrorCode } from '../lib/errors.js';
4
4
  import type { ResourceStore } from '../lib/resource-store.js';
@@ -22,6 +22,8 @@ export declare const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS: {
22
22
  readonly destructiveHint: false;
23
23
  readonly openWorldHint: false;
24
24
  };
25
+ export type TaskSupportLevel = 'optional' | 'required' | 'forbidden';
26
+ export declare function resolveToolTaskSupportLevel(topLevelTaskSupport: unknown, executionTaskSupport: unknown): TaskSupportLevel | undefined;
25
27
  export declare function maybeStripStructuredContentFromResult<T extends object>(result: T): T;
26
28
  type ResourceEntry = ReturnType<ResourceStore['putText']>;
27
29
  export declare function maybeExternalizeTextContent(resourceStore: ResourceStore | undefined, content: string, params: {
@@ -54,23 +56,24 @@ export type ToolResponse<T> = ReturnType<typeof buildToolResponse<T>> & {
54
56
  interface ToolErrorResponse extends Record<string, unknown> {
55
57
  content: ContentBlock[];
56
58
  isError: true;
57
- errorCode?: string;
59
+ errorCode?: ErrorCode;
58
60
  }
59
61
  export type ToolResult<T> = ToolResponse<T> | ToolErrorResponse;
60
- export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>): (args: unknown, extra: ToolExtra) => Promise<ToolResult<Result>>;
62
+ export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, ctx: ToolContext) => Promise<ToolResult<Result>>): (args: unknown, ctx: ToolContext | ServerContext) => Promise<ToolResult<Result>>;
61
63
  type ProgressToken = string | number;
62
- export interface ToolExtra {
64
+ export interface ToolContext {
63
65
  signal?: AbortSignal;
64
66
  _meta?: {
65
67
  progressToken?: ProgressToken | undefined;
66
68
  traceparent?: string | undefined;
67
69
  tracestate?: string | undefined;
68
70
  baggage?: string | undefined;
69
- };
71
+ } | undefined;
70
72
  sendNotification?: (notification: {
71
73
  method: 'notifications/progress';
72
74
  params: ProgressNotificationParams;
73
75
  }) => Promise<void>;
76
+ log?: (level: LoggingLevel, data: unknown, logger?: string) => Promise<void>;
74
77
  }
75
78
  export interface IconInfo {
76
79
  src: string;
@@ -102,7 +105,7 @@ interface FileInfoPayload {
102
105
  export declare function buildFileInfoPayload(info: FileInfo): FileInfoPayload;
103
106
  interface ToolExecutionOptions<T> {
104
107
  toolName: string;
105
- extra: ToolExtra;
108
+ ctx: ToolContext;
106
109
  outputSchema?: z.ZodType<T>;
107
110
  run: (signal: AbortSignal | undefined) => ToolResponse<T> | Promise<ToolResponse<T>>;
108
111
  onError: (error: unknown) => ToolResult<T>;
@@ -113,12 +116,12 @@ interface ToolExecutionOptions<T> {
113
116
  }
114
117
  export declare function executeToolWithDiagnostics<T>(options: ToolExecutionOptions<T>): Promise<ToolResult<T>>;
115
118
  export declare function buildToolErrorResponse(error: unknown, defaultCode: ErrorCode, path?: string): ToolErrorResponse;
116
- export declare function createProgressReporter(extra: ToolExtra): (progress: {
119
+ export declare function createProgressReporter(ctx: ToolContext): (progress: {
117
120
  total?: number;
118
121
  current: number;
119
122
  message?: string;
120
123
  }) => void;
121
- export declare function notifyProgress(extra: ToolExtra, progress: {
124
+ export declare function notifyProgress(ctx: ToolContext, progress: {
122
125
  current: number;
123
126
  total?: number;
124
127
  message?: string;
@@ -138,19 +141,19 @@ interface BatchProgressCallbacks {
138
141
  progress: ToolProgressSession;
139
142
  onItemComplete: () => void;
140
143
  }
141
- export declare function createToolProgressSession(extra: ToolExtra, startMessage: string, initialTotal?: number): ToolProgressSession;
142
- export declare function createBatchProgressCallbacks(extra: ToolExtra, params: {
144
+ export declare function createToolProgressSession(ctx: ToolContext, startMessage: string, initialTotal?: number): ToolProgressSession;
145
+ export declare function createBatchProgressCallbacks(ctx: ToolContext, params: {
143
146
  toolLabel: string;
144
147
  context: string;
145
148
  totalItems: number;
146
149
  itemVerb: string;
147
150
  }): BatchProgressCallbacks;
148
151
  export declare function resolveFinalProgressCurrent(progress: ToolProgressSession, ...candidates: number[]): number;
149
- export declare function wrapToolHandler<Args, Result>(handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>, options: {
152
+ export declare function wrapToolHandler<Args, Result>(handler: (args: Args, ctx: ToolContext) => Promise<ToolResult<Result>>, options: {
150
153
  guard?: (() => boolean) | undefined;
151
154
  progressMessage?: (args: Args) => string;
152
155
  completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
153
- }): (args: Args, extra?: ToolExtra) => Promise<ToolResult<Result>>;
156
+ }): (args: Args, ctx?: ToolContext | ServerContext) => Promise<ToolResult<Result>>;
154
157
  /**
155
158
  * Returns `pathValue` if non-empty; otherwise resolves to the single allowed
156
159
  * directory from module-level state managed by `RootsManager`. Throws when the
@@ -53,32 +53,43 @@ export const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS = {
53
53
  function isTaskSupportLevel(value) {
54
54
  return value === 'optional' || value === 'required' || value === 'forbidden';
55
55
  }
56
- function normalizeToolExecution(tool) {
57
- const candidate = tool;
58
- const topLevelTaskSupport = candidate['taskSupport'];
59
- const existingExecution = candidate['execution'] && typeof candidate['execution'] === 'object'
60
- ? candidate['execution']
56
+ function getExecutionConfig(candidate) {
57
+ const { execution } = candidate;
58
+ return execution && typeof execution === 'object'
59
+ ? execution
61
60
  : undefined;
62
- const executionTaskSupport = existingExecution?.['taskSupport'];
63
- let resolvedTaskSupport;
61
+ }
62
+ export function resolveToolTaskSupportLevel(topLevelTaskSupport, executionTaskSupport) {
64
63
  if (isTaskSupportLevel(topLevelTaskSupport)) {
65
- resolvedTaskSupport = topLevelTaskSupport;
64
+ return topLevelTaskSupport;
65
+ }
66
+ if (isTaskSupportLevel(executionTaskSupport)) {
67
+ return executionTaskSupport;
66
68
  }
67
- else if (isTaskSupportLevel(executionTaskSupport)) {
68
- resolvedTaskSupport = executionTaskSupport;
69
+ return undefined;
70
+ }
71
+ function buildNormalizedExecution(existingExecution, taskSupport) {
72
+ if (taskSupport === undefined && existingExecution === undefined) {
73
+ return undefined;
69
74
  }
75
+ return {
76
+ ...(existingExecution ?? {}),
77
+ ...(taskSupport !== undefined ? { taskSupport } : {}),
78
+ };
79
+ }
80
+ function normalizeToolExecution(tool) {
81
+ const candidate = tool;
82
+ const topLevelTaskSupport = candidate['taskSupport'];
83
+ const existingExecution = getExecutionConfig(candidate);
84
+ const resolvedTaskSupport = resolveToolTaskSupportLevel(topLevelTaskSupport, existingExecution?.['taskSupport']);
70
85
  if (resolvedTaskSupport === undefined && topLevelTaskSupport === undefined) {
71
86
  return tool;
72
87
  }
73
88
  const normalized = { ...candidate };
74
89
  delete normalized['taskSupport'];
75
- if (resolvedTaskSupport !== undefined || existingExecution !== undefined) {
76
- normalized['execution'] = {
77
- ...(existingExecution ?? {}),
78
- ...(resolvedTaskSupport !== undefined
79
- ? { taskSupport: resolvedTaskSupport }
80
- : {}),
81
- };
90
+ const execution = buildNormalizedExecution(existingExecution, resolvedTaskSupport);
91
+ if (execution !== undefined) {
92
+ normalized['execution'] = execution;
82
93
  }
83
94
  return normalized;
84
95
  }
@@ -199,10 +210,10 @@ function parseToolArgs(schema, args) {
199
210
  throw new McpError(ErrorCode.INVALID_INPUT, `Invalid tool arguments:\n${z.prettifyError(parsed.error)}`);
200
211
  }
201
212
  export function withValidatedArgs(schema, handler) {
202
- return async (args, extra) => {
213
+ return async (args, ctx) => {
203
214
  try {
204
215
  const normalizedArgs = parseToolArgs(schema, args);
205
- return await handler(normalizedArgs, extra);
216
+ return await handler(normalizedArgs, toToolContext(ctx));
206
217
  }
207
218
  catch (error) {
208
219
  if (error instanceof McpError && error.code === ErrorCode.INVALID_INPUT) {
@@ -212,14 +223,31 @@ export function withValidatedArgs(schema, handler) {
212
223
  }
213
224
  };
214
225
  }
215
- function canSendProgress(extra) {
216
- return (extra._meta?.progressToken !== undefined &&
217
- extra.sendNotification !== undefined);
226
+ function toToolContext(ctx) {
227
+ if (!ctx)
228
+ return {};
229
+ if ('mcpReq' in ctx) {
230
+ return {
231
+ signal: ctx.mcpReq.signal,
232
+ ...(ctx.mcpReq._meta
233
+ ? { _meta: ctx.mcpReq._meta }
234
+ : {}),
235
+ sendNotification: async (notification) => ctx.mcpReq.notify(notification),
236
+ log: async (level, data, logger) => ctx.mcpReq.notify({
237
+ method: 'notifications/message',
238
+ params: { level, data, ...(logger ? { logger } : {}) },
239
+ }),
240
+ };
241
+ }
242
+ return ctx;
243
+ }
244
+ function canSendProgress(ctx) {
245
+ return (ctx._meta?.progressToken !== undefined && ctx.sendNotification !== undefined);
218
246
  }
219
- function canReportProgress(extra) {
220
- const taskExtra = extra;
247
+ function canReportProgress(ctx) {
248
+ const taskExtra = ctx;
221
249
  const hasTask = taskExtra.taskId !== undefined && taskExtra.taskStore !== undefined;
222
- return canSendProgress(extra) || hasTask;
250
+ return canSendProgress(ctx) || hasTask;
223
251
  }
224
252
  export function withDefaultIcons(tool, iconInfo) {
225
253
  const normalizedTool = normalizeToolExecution(tool);
@@ -278,9 +306,9 @@ function getToolSignal(extraSignal, timedSignal) {
278
306
  return { signal, cleanup };
279
307
  }
280
308
  export async function executeToolWithDiagnostics(options) {
281
- const traceContext = extractTraceContext(options.extra._meta);
309
+ const traceContext = extractTraceContext(options.ctx._meta);
282
310
  return withToolDiagnostics(options.toolName, () => withToolErrorHandling(async () => {
283
- const { signal, cleanup } = getToolSignal(options.extra.signal, options.timedSignal);
311
+ const { signal, cleanup } = getToolSignal(options.ctx.signal, options.timedSignal);
284
312
  try {
285
313
  return validateToolResponse(options.toolName, await options.run(signal), options.outputSchema);
286
314
  }
@@ -304,9 +332,9 @@ export function buildToolErrorResponse(error, defaultCode, path) {
304
332
  function buildNotInitializedResult() {
305
333
  return buildToolErrorResponse(NOT_INITIALIZED_ERROR, ErrorCode.INVALID_INPUT);
306
334
  }
307
- async function reportProgress(extra, progress) {
308
- await updateTaskStoreProgress(extra, progress);
309
- await sendMcpProgressNotification(extra, progress);
335
+ async function reportProgress(ctx, progress) {
336
+ await updateTaskStoreProgress(ctx, progress);
337
+ await sendMcpProgressNotification(ctx, progress);
310
338
  }
311
339
  function formatTaskStatusMessage(progress) {
312
340
  if (progress.total !== undefined) {
@@ -320,8 +348,8 @@ function isBenignTaskStatusUpdateError(error) {
320
348
  return (error instanceof Error &&
321
349
  /Task .*not found|terminal status/iu.test(error.message));
322
350
  }
323
- async function updateTaskStoreProgress(extra, progress) {
324
- const taskExtra = extra;
351
+ async function updateTaskStoreProgress(ctx, progress) {
352
+ const taskExtra = ctx;
325
353
  if (typeof taskExtra.taskId === 'string' &&
326
354
  taskExtra.taskStore !== undefined &&
327
355
  taskExtra.taskStore !== null) {
@@ -338,13 +366,13 @@ async function updateTaskStoreProgress(extra, progress) {
338
366
  }
339
367
  }
340
368
  }
341
- async function sendMcpProgressNotification(extra, progress) {
342
- if (canSendProgress(extra)) {
369
+ async function sendMcpProgressNotification(ctx, progress) {
370
+ if (canSendProgress(ctx)) {
343
371
  try {
344
- await extra.sendNotification({
372
+ await ctx.sendNotification({
345
373
  method: 'notifications/progress',
346
374
  params: {
347
- progressToken: extra._meta.progressToken,
375
+ progressToken: ctx._meta.progressToken,
348
376
  progress: progress.current,
349
377
  ...(progress.total !== undefined ? { total: progress.total } : {}),
350
378
  ...(progress.message !== undefined
@@ -358,8 +386,8 @@ async function sendMcpProgressNotification(extra, progress) {
358
386
  }
359
387
  }
360
388
  }
361
- export function createProgressReporter(extra) {
362
- if (!canReportProgress(extra)) {
389
+ export function createProgressReporter(ctx) {
390
+ if (!canReportProgress(ctx)) {
363
391
  return () => { };
364
392
  }
365
393
  // State for monotonic enforcement and rate-limiting.
@@ -379,26 +407,26 @@ export function createProgressReporter(extra) {
379
407
  return;
380
408
  lastProgress = current;
381
409
  lastSentMs = now;
382
- void reportProgress(extra, {
410
+ void reportProgress(ctx, {
383
411
  current,
384
412
  ...(total !== undefined ? { total } : {}),
385
413
  ...(message !== undefined ? { message } : {}),
386
414
  });
387
415
  };
388
416
  }
389
- export function notifyProgress(extra, progress) {
390
- if (!canReportProgress(extra))
417
+ export function notifyProgress(ctx, progress) {
418
+ if (!canReportProgress(ctx))
391
419
  return;
392
- void reportProgress(extra, progress);
420
+ void reportProgress(ctx, progress);
393
421
  }
394
- export function createToolProgressSession(extra, startMessage, initialTotal) {
395
- notifyProgress(extra, {
422
+ export function createToolProgressSession(ctx, startMessage, initialTotal) {
423
+ notifyProgress(ctx, {
396
424
  current: 0,
397
425
  ...(initialTotal !== undefined ? { total: initialTotal } : {}),
398
426
  message: startMessage,
399
427
  });
400
428
  let cursor = 0;
401
- const baseReporter = createProgressReporter(extra);
429
+ const baseReporter = createProgressReporter(ctx);
402
430
  const setCursor = (value) => {
403
431
  if (value > cursor)
404
432
  cursor = value;
@@ -406,7 +434,7 @@ export function createToolProgressSession(extra, startMessage, initialTotal) {
406
434
  };
407
435
  const finishProgress = (message, minimumCurrent) => {
408
436
  const finalCurrent = Math.max(cursor + 1, minimumCurrent ?? 1, 1);
409
- notifyProgress(extra, {
437
+ notifyProgress(ctx, {
410
438
  current: finalCurrent,
411
439
  total: finalCurrent,
412
440
  ...(message !== undefined ? { message } : {}),
@@ -434,8 +462,8 @@ export function createToolProgressSession(extra, startMessage, initialTotal) {
434
462
  getCurrent: () => cursor,
435
463
  };
436
464
  }
437
- export function createBatchProgressCallbacks(extra, params) {
438
- const progress = createToolProgressSession(extra, `${params.toolLabel}: ${params.context}`, params.totalItems);
465
+ export function createBatchProgressCallbacks(ctx, params) {
466
+ const progress = createToolProgressSession(ctx, `${params.toolLabel}: ${params.context}`, params.totalItems);
439
467
  let itemsDone = 0;
440
468
  const onItemComplete = () => {
441
469
  itemsDone++;
@@ -456,21 +484,21 @@ export function resolveFinalProgressCurrent(progress, ...candidates) {
456
484
  }
457
485
  return finalCurrent;
458
486
  }
459
- async function withProgress(message, extra, run, getCompletionMessage) {
460
- if (!canReportProgress(extra)) {
487
+ async function withProgress(message, ctx, run, getCompletionMessage) {
488
+ if (!canReportProgress(ctx)) {
461
489
  return run();
462
490
  }
463
491
  const total = 1;
464
492
  // Emit the start notification only when a progressToken is present; for
465
493
  // task-only mode the task status is already 'working' — a zero-progress
466
494
  // notification would add unnecessary overhead without client value.
467
- if (canSendProgress(extra)) {
468
- await reportProgress(extra, { current: 0, total, message });
495
+ if (canSendProgress(ctx)) {
496
+ await reportProgress(ctx, { current: 0, total, message });
469
497
  }
470
498
  try {
471
499
  const result = await run();
472
500
  const endMessage = getCompletionMessage?.(result) ?? message;
473
- await reportProgress(extra, {
501
+ await reportProgress(ctx, {
474
502
  current: total,
475
503
  total,
476
504
  message: endMessage,
@@ -478,7 +506,7 @@ async function withProgress(message, extra, run, getCompletionMessage) {
478
506
  return result;
479
507
  }
480
508
  catch (error) {
481
- void reportProgress(extra, {
509
+ void reportProgress(ctx, {
482
510
  current: total,
483
511
  total,
484
512
  message: `${message} • failed`,
@@ -487,8 +515,8 @@ async function withProgress(message, extra, run, getCompletionMessage) {
487
515
  }
488
516
  }
489
517
  export function wrapToolHandler(handler, options) {
490
- return async (args, extra) => {
491
- const resolvedExtra = extra ?? {};
518
+ return async (args, ctx) => {
519
+ const resolvedExtra = toToolContext(ctx);
492
520
  if (options.guard && !options.guard()) {
493
521
  return maybeStripStructuredContentFromResult(buildNotInitializedResult());
494
522
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const GET_MULTIPLE_FILE_INFO_TOOL: ToolContract;
4
4
  export declare function registerGetMultipleFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -3,8 +3,9 @@ import { ErrorCode } from '../lib/errors.js';
3
3
  import { getMultipleFileInfo } from '../lib/file-operations/metadata.js';
4
4
  import { formatBytes, joinLines } from '../config.js';
5
5
  import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
6
- import { buildBatchCompletionSuffix, buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
7
- import { registerToolTaskIfAvailable } from './task-support.js';
6
+ import { FILE_READ_ICONS } from './icons.js';
7
+ import { buildBatchCompletionSuffix, buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
8
+ import { registerStandardTool } from './task-support.js';
8
9
  export const GET_MULTIPLE_FILE_INFO_TOOL = {
9
10
  name: 'stat_many',
10
11
  title: 'Get Multiple File Info',
@@ -13,6 +14,7 @@ export const GET_MULTIPLE_FILE_INFO_TOOL = {
13
14
  inputSchema: GetMultipleFileInfoInputSchema,
14
15
  outputSchema: GetMultipleFileInfoOutputSchema,
15
16
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
17
+ icons: FILE_READ_ICONS,
16
18
  taskSupport: 'optional',
17
19
  };
18
20
  function formatFileInfoDetail(info) {
@@ -64,17 +66,17 @@ async function handleGetMultipleFileInfo(args, signal, onProgress) {
64
66
  return buildToolResponse(text, structured);
65
67
  }
66
68
  export function registerGetMultipleFileInfoTool(server, options = {}) {
67
- const handler = (args, extra) => {
69
+ const handler = (args, ctx) => {
68
70
  const primaryPath = args.paths[0] ?? '';
69
71
  return executeToolWithDiagnostics({
70
72
  toolName: 'stat_many',
71
- extra,
73
+ ctx,
72
74
  outputSchema: GetMultipleFileInfoOutputSchema,
73
75
  timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
74
76
  context: { path: primaryPath },
75
77
  run: async (signal) => {
76
78
  const context = buildBatchPathContext(args.paths);
77
- const { progress, onItemComplete } = createBatchProgressCallbacks(extra, {
79
+ const { progress, onItemComplete } = createBatchProgressCallbacks(ctx, {
78
80
  toolLabel: '🕮 stat_many',
79
81
  context,
80
82
  totalItems: args.paths.length,
@@ -97,11 +99,5 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
97
99
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, primaryPath),
98
100
  });
99
101
  };
100
- const wrappedHandler = wrapToolHandler(handler, {
101
- guard: options.isInitialized,
102
- });
103
- const validatedHandler = withValidatedArgs(GetMultipleFileInfoInputSchema, wrappedHandler);
104
- if (registerToolTaskIfAvailable(server, 'stat_many', GET_MULTIPLE_FILE_INFO_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
105
- return;
106
- server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
102
+ registerStandardTool(server, GET_MULTIPLE_FILE_INFO_TOOL, handler, options);
107
103
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const GET_FILE_INFO_TOOL: ToolContract;
4
4
  export declare function registerGetFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;