@j0hanz/filesystem-mcp 1.2.2 → 1.2.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.
- package/README.md +11 -0
- package/dist/cli.js +2 -4
- package/dist/completions.js +15 -6
- package/dist/lib/file-operations/glob-engine.js +3 -9
- package/dist/lib/file-operations/read-multiple-files.js +4 -23
- package/dist/lib/file-operations/search-content.js +11 -18
- package/dist/lib/observability.js +4 -4
- package/dist/lib/path-validation.js +22 -9
- package/dist/pkg-info.d.ts +6 -0
- package/dist/pkg-info.js +9 -0
- package/dist/schemas.d.ts +28 -28
- package/dist/schemas.js +26 -26
- package/dist/server/bootstrap.d.ts +4 -0
- package/dist/server/bootstrap.js +117 -0
- package/dist/server/capabilities.d.ts +10 -0
- package/dist/server/capabilities.js +40 -0
- package/dist/server/logging.d.ts +7 -0
- package/dist/server/logging.js +41 -0
- package/dist/server/roots-manager.d.ts +19 -0
- package/dist/server/roots-manager.js +173 -0
- package/dist/server/types.d.ts +4 -0
- package/dist/server/types.js +1 -0
- package/dist/server.d.ts +2 -8
- package/dist/server.js +1 -317
- package/dist/tools/apply-patch.js +3 -2
- package/dist/tools/calculate-hash.js +3 -2
- package/dist/tools/create-directory.js +3 -2
- package/dist/tools/delete-file.js +9 -2
- package/dist/tools/diff-files.js +3 -2
- package/dist/tools/edit-file.js +5 -4
- package/dist/tools/list-directory.js +13 -2
- package/dist/tools/move-file.js +11 -3
- package/dist/tools/read-multiple.js +6 -5
- package/dist/tools/read.js +3 -2
- package/dist/tools/replace-in-files.js +3 -2
- package/dist/tools/roots.js +12 -2
- package/dist/tools/search-content.js +10 -15
- package/dist/tools/search-files.js +13 -9
- package/dist/tools/shared.d.ts +3 -1
- package/dist/tools/shared.js +19 -1
- package/dist/tools/stat-many.js +6 -5
- package/dist/tools/stat.js +4 -3
- package/dist/tools/task-support.js +57 -10
- package/dist/tools/tree.js +15 -2
- package/dist/tools/write-file.js +3 -2
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,317 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from '@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js';
|
|
5
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
-
import { InitializedNotificationSchema, RootsListChangedNotificationSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
import packageJsonRaw from '../package.json' with { type: 'json' };
|
|
10
|
-
import { registerCompletions } from './completions.js';
|
|
11
|
-
import { formatUnknownErrorMessage } from './lib/errors.js';
|
|
12
|
-
import { assertNotAborted, createTimedAbortSignal, withAbort, } from './lib/fs-helpers.js';
|
|
13
|
-
import { getAllowedDirectories, getValidRootDirectories, isPathWithinDirectories, normalizePath, setAllowedDirectoriesResolved, } from './lib/path-validation.js';
|
|
14
|
-
import { createInMemoryResourceStore } from './lib/resource-store.js';
|
|
15
|
-
import { isRecord } from './lib/type-guards.js';
|
|
16
|
-
import { registerGetHelpPrompt } from './prompts.js';
|
|
17
|
-
import { registerInstructionResource, registerResultResources, } from './resources.js';
|
|
18
|
-
import { registerAllTools } from './tools.js';
|
|
19
|
-
import { withDefaultIcons } from './tools/shared.js';
|
|
20
|
-
const PackageJsonSchema = z.object({
|
|
21
|
-
version: z.string(),
|
|
22
|
-
description: z.string().optional(),
|
|
23
|
-
homepage: z.string().optional(),
|
|
24
|
-
});
|
|
25
|
-
const { version: SERVER_VERSION, description: SERVER_DESCRIPTION, homepage: SERVER_HOMEPAGE, } = PackageJsonSchema.parse(packageJsonRaw);
|
|
26
|
-
function normalizeCLIDirectories(dirs) {
|
|
27
|
-
const normalized = [];
|
|
28
|
-
for (const dir of dirs) {
|
|
29
|
-
const trimmed = dir.trim();
|
|
30
|
-
if (trimmed.length === 0)
|
|
31
|
-
continue;
|
|
32
|
-
normalized.push(normalizePath(trimmed));
|
|
33
|
-
}
|
|
34
|
-
return normalized;
|
|
35
|
-
}
|
|
36
|
-
const ROOTS_TIMEOUT_MS = 5000;
|
|
37
|
-
const ROOTS_DEBOUNCE_MS = 100;
|
|
38
|
-
const MCP_LOGGER_NAME = 'filesystem-mcp';
|
|
39
|
-
const LOG_LEVEL_ORDER = {
|
|
40
|
-
debug: 0,
|
|
41
|
-
info: 1,
|
|
42
|
-
notice: 2,
|
|
43
|
-
warning: 3,
|
|
44
|
-
error: 4,
|
|
45
|
-
critical: 5,
|
|
46
|
-
alert: 6,
|
|
47
|
-
emergency: 7,
|
|
48
|
-
};
|
|
49
|
-
function canSendMcpLogs(server) {
|
|
50
|
-
const capabilities = server.server.getClientCapabilities();
|
|
51
|
-
if (!isRecord(capabilities))
|
|
52
|
-
return false;
|
|
53
|
-
if (!('logging' in capabilities))
|
|
54
|
-
return false;
|
|
55
|
-
return Boolean(capabilities.logging);
|
|
56
|
-
}
|
|
57
|
-
function logToMcp(server, level, data, minLevel = 'debug') {
|
|
58
|
-
if (LOG_LEVEL_ORDER[level] < LOG_LEVEL_ORDER[minLevel]) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (!server || !canSendMcpLogs(server)) {
|
|
62
|
-
console.error(data);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const params = {
|
|
66
|
-
level,
|
|
67
|
-
logger: MCP_LOGGER_NAME,
|
|
68
|
-
data,
|
|
69
|
-
};
|
|
70
|
-
void server.sendLoggingMessage(params).catch((error) => {
|
|
71
|
-
console.error(`Failed to send MCP log: ${level} │ ${data}`, formatUnknownErrorMessage(error));
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
class RootsManager {
|
|
75
|
-
rootsUpdateTimeout;
|
|
76
|
-
rootDirectories = [];
|
|
77
|
-
clientInitialized = false;
|
|
78
|
-
options;
|
|
79
|
-
loggingState;
|
|
80
|
-
constructor(options, loggingState) {
|
|
81
|
-
this.options = options;
|
|
82
|
-
this.loggingState = loggingState ?? { minimumLevel: 'debug' };
|
|
83
|
-
}
|
|
84
|
-
isInitialized() {
|
|
85
|
-
return this.clientInitialized;
|
|
86
|
-
}
|
|
87
|
-
logMissingDirectoriesIfNeeded(server) {
|
|
88
|
-
if (getAllowedDirectories().length === 0) {
|
|
89
|
-
this.logMissingDirectories(server);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
registerHandlers(server) {
|
|
93
|
-
server.server.setNotificationHandler(InitializedNotificationSchema, async () => {
|
|
94
|
-
this.clientInitialized = true;
|
|
95
|
-
await this.updateRootsFromClient(server);
|
|
96
|
-
});
|
|
97
|
-
server.server.setNotificationHandler(RootsListChangedNotificationSchema, () => {
|
|
98
|
-
if (!this.clientInitialized)
|
|
99
|
-
return;
|
|
100
|
-
this.scheduleRootsUpdate(server);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
scheduleRootsUpdate(server) {
|
|
104
|
-
if (this.rootsUpdateTimeout) {
|
|
105
|
-
this.rootsUpdateTimeout.refresh();
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
this.rootsUpdateTimeout = setTimeout(() => {
|
|
109
|
-
this.rootsUpdateTimeout = undefined;
|
|
110
|
-
void this.updateRootsFromClient(server);
|
|
111
|
-
}, ROOTS_DEBOUNCE_MS);
|
|
112
|
-
this.rootsUpdateTimeout.unref();
|
|
113
|
-
}
|
|
114
|
-
async recomputeAllowedDirectories() {
|
|
115
|
-
const cliAllowedDirs = normalizeCLIDirectories(this.options.cliAllowedDirs ?? []);
|
|
116
|
-
const allowCwd = this.options.allowCwd === true;
|
|
117
|
-
const allowCwdDirs = allowCwd ? [normalizePath(process.cwd())] : [];
|
|
118
|
-
const baseline = [...cliAllowedDirs, ...allowCwdDirs];
|
|
119
|
-
const { signal, cleanup } = createTimedAbortSignal(undefined, ROOTS_TIMEOUT_MS);
|
|
120
|
-
try {
|
|
121
|
-
const rootsToInclude = baseline.length > 0
|
|
122
|
-
? await filterRootsWithinBaseline(this.rootDirectories, baseline, signal)
|
|
123
|
-
: this.rootDirectories;
|
|
124
|
-
const combined = [...baseline, ...rootsToInclude];
|
|
125
|
-
await setAllowedDirectoriesResolved(combined, signal);
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
cleanup();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
logMissingDirectories(server) {
|
|
132
|
-
if (this.options.allowCwd) {
|
|
133
|
-
logToMcp(server, 'notice', 'No allowed directories specified. Using the current working directory as an allowed directory.', this.loggingState.minimumLevel);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
logToMcp(server, 'warning', 'No allowed directories specified. Please provide directories as command-line arguments or enable --allow-cwd to use the current working directory.', this.loggingState.minimumLevel);
|
|
137
|
-
}
|
|
138
|
-
async updateRootsFromClient(server) {
|
|
139
|
-
try {
|
|
140
|
-
const clientCapabilities = server.server.getClientCapabilities();
|
|
141
|
-
if (!clientCapabilities?.roots) {
|
|
142
|
-
this.rootDirectories = [];
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const rootsResult = await server.server.listRoots(undefined, {
|
|
146
|
-
timeout: ROOTS_TIMEOUT_MS,
|
|
147
|
-
});
|
|
148
|
-
const roots = extractRoots(rootsResult);
|
|
149
|
-
this.rootDirectories = await resolveRootDirectories(roots);
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
logToMcp(server, 'debug', `[DEBUG] MCP Roots protocol unavailable or failed: ${formatUnknownErrorMessage(error)}`, this.loggingState.minimumLevel);
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
await this.recomputeAllowedDirectories();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
const rootsManagers = new WeakMap();
|
|
160
|
-
function getRootsManager(server) {
|
|
161
|
-
const manager = rootsManagers.get(server);
|
|
162
|
-
if (!manager) {
|
|
163
|
-
throw new Error('Roots manager not initialized for server instance');
|
|
164
|
-
}
|
|
165
|
-
return manager;
|
|
166
|
-
}
|
|
167
|
-
const RootSchema = z.strictObject({
|
|
168
|
-
uri: z.string(),
|
|
169
|
-
name: z.string().optional(),
|
|
170
|
-
});
|
|
171
|
-
const RootsResponseSchema = z.object({
|
|
172
|
-
roots: z.array(RootSchema).optional(),
|
|
173
|
-
});
|
|
174
|
-
function extractRoots(value) {
|
|
175
|
-
const parsed = RootsResponseSchema.safeParse(value);
|
|
176
|
-
if (!parsed.success || !parsed.data.roots) {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
const roots = [];
|
|
180
|
-
for (const root of parsed.data.roots) {
|
|
181
|
-
if (isRoot(root)) {
|
|
182
|
-
roots.push(normalizeRoot(root));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return roots;
|
|
186
|
-
}
|
|
187
|
-
async function resolveRootDirectories(roots) {
|
|
188
|
-
if (roots.length === 0)
|
|
189
|
-
return [];
|
|
190
|
-
const { signal, cleanup } = createTimedAbortSignal(undefined, ROOTS_TIMEOUT_MS);
|
|
191
|
-
try {
|
|
192
|
-
return await getValidRootDirectories(roots, signal);
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
cleanup();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function isRoot(value) {
|
|
199
|
-
return isRecord(value) && typeof value['uri'] === 'string';
|
|
200
|
-
}
|
|
201
|
-
function normalizeRoot(root) {
|
|
202
|
-
return root.name ? { uri: root.uri, name: root.name } : { uri: root.uri };
|
|
203
|
-
}
|
|
204
|
-
async function filterRootsWithinBaseline(roots, baseline, signal) {
|
|
205
|
-
const normalizedBaseline = normalizeCLIDirectories(baseline);
|
|
206
|
-
const filtered = [];
|
|
207
|
-
for (const root of roots) {
|
|
208
|
-
const normalizedRoot = normalizePath(root);
|
|
209
|
-
const isValid = await isRootWithinBaseline(normalizedRoot, normalizedBaseline, signal);
|
|
210
|
-
if (isValid)
|
|
211
|
-
filtered.push(normalizedRoot);
|
|
212
|
-
}
|
|
213
|
-
return filtered;
|
|
214
|
-
}
|
|
215
|
-
async function isRootWithinBaseline(normalizedRoot, baseline, signal) {
|
|
216
|
-
if (!isPathWithinDirectories(normalizedRoot, baseline)) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
assertNotAborted(signal);
|
|
221
|
-
const realPath = await withAbort(fs.realpath(normalizedRoot), signal);
|
|
222
|
-
const normalizedReal = normalizePath(realPath);
|
|
223
|
-
return isPathWithinDirectories(normalizedReal, baseline);
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
async function loadServerInstructions() {
|
|
230
|
-
const defaultInstructions = `
|
|
231
|
-
Filesystem MCP Instructions
|
|
232
|
-
(Detailed instructions failed to load - check logs)
|
|
233
|
-
`;
|
|
234
|
-
try {
|
|
235
|
-
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
236
|
-
return await fs.readFile(path.join(currentDir, 'instructions.md'), 'utf-8');
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
console.error('[WARNING] Failed to load instructions.md:', formatUnknownErrorMessage(error));
|
|
240
|
-
return defaultInstructions;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async function getLocalIconInfo() {
|
|
244
|
-
const name = 'logo.svg';
|
|
245
|
-
const mime = 'image/svg+xml';
|
|
246
|
-
try {
|
|
247
|
-
const iconPath = new URL(`../assets/${name}`, import.meta.url);
|
|
248
|
-
const buffer = await fs.readFile(iconPath);
|
|
249
|
-
return {
|
|
250
|
-
src: `data:${mime};base64,${buffer.toString('base64')}`,
|
|
251
|
-
mimeType: mime,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
export async function createServer(options = {}) {
|
|
259
|
-
const resourceStore = createInMemoryResourceStore();
|
|
260
|
-
const serverInstructions = await loadServerInstructions();
|
|
261
|
-
const localIcon = await getLocalIconInfo();
|
|
262
|
-
const taskStore = new InMemoryTaskStore();
|
|
263
|
-
const taskMessageQueue = new InMemoryTaskMessageQueue();
|
|
264
|
-
const serverConfig = {
|
|
265
|
-
capabilities: {
|
|
266
|
-
logging: {},
|
|
267
|
-
resources: {},
|
|
268
|
-
tools: {},
|
|
269
|
-
prompts: { listChanged: true },
|
|
270
|
-
completions: {},
|
|
271
|
-
tasks: {
|
|
272
|
-
list: {},
|
|
273
|
-
cancel: {},
|
|
274
|
-
requests: { tools: { call: {} } },
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
taskStore,
|
|
278
|
-
taskMessageQueue,
|
|
279
|
-
};
|
|
280
|
-
if (serverInstructions) {
|
|
281
|
-
serverConfig.instructions = serverInstructions;
|
|
282
|
-
}
|
|
283
|
-
const server = new McpServer(withDefaultIcons({
|
|
284
|
-
name: 'filesystem-mcp',
|
|
285
|
-
title: 'Filesystem MCP',
|
|
286
|
-
version: SERVER_VERSION,
|
|
287
|
-
...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),
|
|
288
|
-
...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),
|
|
289
|
-
}, localIcon), serverConfig);
|
|
290
|
-
const loggingState = {
|
|
291
|
-
minimumLevel: 'debug',
|
|
292
|
-
};
|
|
293
|
-
const rootsManager = new RootsManager(options, loggingState);
|
|
294
|
-
rootsManagers.set(server, rootsManager);
|
|
295
|
-
server.server.setRequestHandler(SetLevelRequestSchema, (req) => {
|
|
296
|
-
loggingState.minimumLevel = req.params.level;
|
|
297
|
-
return {};
|
|
298
|
-
});
|
|
299
|
-
registerInstructionResource(server, serverInstructions, localIcon);
|
|
300
|
-
registerGetHelpPrompt(server, serverInstructions, localIcon);
|
|
301
|
-
registerResultResources(server, resourceStore, localIcon);
|
|
302
|
-
registerCompletions(server);
|
|
303
|
-
registerAllTools(server, {
|
|
304
|
-
resourceStore,
|
|
305
|
-
isInitialized: () => rootsManager.isInitialized(),
|
|
306
|
-
...(localIcon ? { iconInfo: localIcon } : {}),
|
|
307
|
-
});
|
|
308
|
-
return server;
|
|
309
|
-
}
|
|
310
|
-
export async function startServer(server) {
|
|
311
|
-
const transport = new StdioServerTransport();
|
|
312
|
-
const rootsManager = getRootsManager(server);
|
|
313
|
-
rootsManager.registerHandlers(server);
|
|
314
|
-
await rootsManager.recomputeAllowedDirectories();
|
|
315
|
-
await server.connect(transport);
|
|
316
|
-
rootsManager.logMissingDirectoriesIfNeeded(server);
|
|
317
|
-
}
|
|
1
|
+
export { createServer, startServer } from './server/bootstrap.js';
|
|
@@ -6,7 +6,7 @@ import { ErrorCode, McpError } from '../lib/errors.js';
|
|
|
6
6
|
import { atomicWriteFile, withAbort } from '../lib/fs-helpers.js';
|
|
7
7
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
8
8
|
import { ApplyPatchInputSchema, ApplyPatchOutputSchema } from '../schemas.js';
|
|
9
|
-
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
9
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
10
10
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
11
11
|
const APPLY_PATCH_TOOL = {
|
|
12
12
|
title: 'Apply Patch',
|
|
@@ -69,7 +69,8 @@ export function registerApplyPatchTool(server, options = {}) {
|
|
|
69
69
|
run: (signal) => handleApplyPatch(args, signal),
|
|
70
70
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
71
71
|
});
|
|
72
|
-
const
|
|
72
|
+
const validatedHandler = withValidatedArgs(ApplyPatchInputSchema, handler);
|
|
73
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
73
74
|
guard: options.isInitialized,
|
|
74
75
|
progressMessage: (args) => {
|
|
75
76
|
const name = path.basename(args.path);
|
|
@@ -9,7 +9,7 @@ import { globEntries } from '../lib/file-operations/glob-engine.js';
|
|
|
9
9
|
import { assertNotAborted, withAbort } from '../lib/fs-helpers.js';
|
|
10
10
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
11
11
|
import { CalculateHashInputSchema, CalculateHashOutputSchema, } from '../schemas.js';
|
|
12
|
-
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
12
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
13
13
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
14
14
|
const WINDOWS_PATH_SEPARATOR = /\\/gu;
|
|
15
15
|
const CALCULATE_HASH_TOOL = {
|
|
@@ -204,7 +204,8 @@ export function registerCalculateHashTool(server, options = {}) {
|
|
|
204
204
|
},
|
|
205
205
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
206
206
|
});
|
|
207
|
-
const
|
|
207
|
+
const validatedHandler = withValidatedArgs(CalculateHashInputSchema, handler);
|
|
208
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
208
209
|
guard: options.isInitialized,
|
|
209
210
|
});
|
|
210
211
|
if (registerToolTaskIfAvailable(server, 'calculate_hash', CALCULATE_HASH_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
@@ -4,7 +4,7 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
4
4
|
import { withAbort } from '../lib/fs-helpers.js';
|
|
5
5
|
import { validatePathForWrite } from '../lib/path-validation.js';
|
|
6
6
|
import { CreateDirectoryInputSchema, CreateDirectoryOutputSchema, } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, IDEMPOTENT_WRITE_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, IDEMPOTENT_WRITE_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const CREATE_DIRECTORY_TOOL = {
|
|
10
10
|
title: 'Create Directory',
|
|
@@ -30,7 +30,8 @@ export function registerCreateDirectoryTool(server, options = {}) {
|
|
|
30
30
|
run: (signal) => handleCreateDirectory(args, signal),
|
|
31
31
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
32
32
|
});
|
|
33
|
-
const
|
|
33
|
+
const validatedHandler = withValidatedArgs(CreateDirectoryInputSchema, handler);
|
|
34
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
34
35
|
guard: options.isInitialized,
|
|
35
36
|
progressMessage: (args) => {
|
|
36
37
|
const name = path.basename(args.path) || args.path;
|
|
@@ -4,7 +4,7 @@ import { ErrorCode, isNodeError } from '../lib/errors.js';
|
|
|
4
4
|
import { withAbort } from '../lib/fs-helpers.js';
|
|
5
5
|
import { validatePathForWrite } from '../lib/path-validation.js';
|
|
6
6
|
import { DeleteFileInputSchema, DeleteFileOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const DELETE_FILE_TOOL = {
|
|
10
10
|
title: 'Delete File',
|
|
@@ -74,9 +74,16 @@ export function registerDeleteFileTool(server, options = {}) {
|
|
|
74
74
|
return buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path);
|
|
75
75
|
},
|
|
76
76
|
});
|
|
77
|
-
const
|
|
77
|
+
const validatedHandler = withValidatedArgs(DeleteFileInputSchema, handler);
|
|
78
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
78
79
|
guard: options.isInitialized,
|
|
79
80
|
progressMessage: (args) => `🛠 rm: ${path.basename(args.path)}`,
|
|
81
|
+
completionMessage: (args, result) => {
|
|
82
|
+
const name = path.basename(args.path);
|
|
83
|
+
if (result.isError)
|
|
84
|
+
return `🛠 rm: ${name} • failed`;
|
|
85
|
+
return `🛠 rm: ${name} • deleted`;
|
|
86
|
+
},
|
|
80
87
|
});
|
|
81
88
|
if (registerToolTaskIfAvailable(server, 'rm', DELETE_FILE_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
82
89
|
return;
|
package/dist/tools/diff-files.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ErrorCode, McpError } from '../lib/errors.js';
|
|
|
6
6
|
import { withAbort } from '../lib/fs-helpers.js';
|
|
7
7
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
8
8
|
import { DiffFilesInputSchema, DiffFilesOutputSchema } from '../schemas.js';
|
|
9
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
9
|
+
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
10
10
|
const DIFF_FILES_TOOL = {
|
|
11
11
|
title: 'Diff Files',
|
|
12
12
|
description: 'Generate a unified diff between two files. ' +
|
|
@@ -80,7 +80,8 @@ export function registerDiffFilesTool(server, options = {}) {
|
|
|
80
80
|
run: (signal) => handleDiffFiles(args, signal, options.resourceStore),
|
|
81
81
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.original),
|
|
82
82
|
});
|
|
83
|
-
|
|
83
|
+
const validatedHandler = withValidatedArgs(DiffFilesInputSchema, handler);
|
|
84
|
+
server.registerTool('diff_files', withDefaultIcons({ ...DIFF_FILES_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
84
85
|
guard: options.isInitialized,
|
|
85
86
|
progressMessage: (args) => {
|
|
86
87
|
const name1 = path.basename(args.original);
|
package/dist/tools/edit-file.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
4
4
|
import { atomicWriteFile } from '../lib/fs-helpers.js';
|
|
5
5
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
6
6
|
import { EditFileInputSchema, EditFileOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
const EDIT_FILE_TOOL = {
|
|
9
9
|
title: 'Edit File',
|
|
10
10
|
description: 'Edit a file by replacing text. Sequentially applies a list of string replacements. ' +
|
|
@@ -79,7 +79,8 @@ export function registerEditFileTool(server, options = {}) {
|
|
|
79
79
|
run: (signal) => handleEditFile(args, signal),
|
|
80
80
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
81
81
|
});
|
|
82
|
-
|
|
82
|
+
const validatedHandler = withValidatedArgs(EditFileInputSchema, handler);
|
|
83
|
+
server.registerTool('edit', withDefaultIcons({ ...EDIT_FILE_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
83
84
|
guard: options.isInitialized,
|
|
84
85
|
progressMessage: (args) => {
|
|
85
86
|
const name = path.basename(args.path);
|
|
@@ -88,10 +89,10 @@ export function registerEditFileTool(server, options = {}) {
|
|
|
88
89
|
completionMessage: (args, result) => {
|
|
89
90
|
const name = path.basename(args.path);
|
|
90
91
|
if (result.isError)
|
|
91
|
-
return `🛠 edit: ${name} •
|
|
92
|
+
return `🛠 edit: ${name} • failed`;
|
|
92
93
|
const sc = result.structuredContent;
|
|
93
94
|
if (!sc.ok)
|
|
94
|
-
return `🛠 edit: ${name} •
|
|
95
|
+
return `🛠 edit: ${name} • failed`;
|
|
95
96
|
if (sc.lineRange) {
|
|
96
97
|
return `🛠 edit: ${name} • [${sc.lineRange[0]}-${sc.lineRange[1]}]`;
|
|
97
98
|
}
|
|
@@ -4,7 +4,7 @@ import { DEFAULT_EXCLUDE_PATTERNS } from '../lib/constants.js';
|
|
|
4
4
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
5
|
import { listDirectory } from '../lib/file-operations/list-directory.js';
|
|
6
6
|
import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
const LIST_DIRECTORY_TOOL = {
|
|
9
9
|
title: 'List Directory',
|
|
10
10
|
description: 'List the immediate contents of a directory (non-recursive). ' +
|
|
@@ -96,7 +96,8 @@ export function registerListDirectoryTool(server, options = {}) {
|
|
|
96
96
|
run: (signal) => handleListDirectory(args, signal),
|
|
97
97
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.'),
|
|
98
98
|
});
|
|
99
|
-
|
|
99
|
+
const validatedHandler = withValidatedArgs(ListDirectoryInputSchema, handler);
|
|
100
|
+
server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
100
101
|
guard: options.isInitialized,
|
|
101
102
|
progressMessage: (args) => {
|
|
102
103
|
if (args.path) {
|
|
@@ -104,5 +105,15 @@ export function registerListDirectoryTool(server, options = {}) {
|
|
|
104
105
|
}
|
|
105
106
|
return '≣ ls';
|
|
106
107
|
},
|
|
108
|
+
completionMessage: (args, result) => {
|
|
109
|
+
const base = args.path ? path.basename(args.path) : '.';
|
|
110
|
+
if (result.isError)
|
|
111
|
+
return `≣ ls: ${base} • failed`;
|
|
112
|
+
const sc = result.structuredContent;
|
|
113
|
+
if (!sc.ok)
|
|
114
|
+
return `≣ ls: ${base} • failed`;
|
|
115
|
+
const count = sc.totalEntries ?? 0;
|
|
116
|
+
return `≣ ls: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
|
|
117
|
+
},
|
|
107
118
|
}));
|
|
108
119
|
}
|
package/dist/tools/move-file.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ErrorCode, isNodeError } from '../lib/errors.js';
|
|
|
4
4
|
import { withAbort } from '../lib/fs-helpers.js';
|
|
5
5
|
import { validateExistingPath, validatePathForWrite, } from '../lib/path-validation.js';
|
|
6
6
|
import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const MOVE_FILE_TOOL = {
|
|
10
10
|
title: 'Move File',
|
|
@@ -46,9 +46,17 @@ export function registerMoveFileTool(server, options = {}) {
|
|
|
46
46
|
run: (signal) => handleMoveFile(args, signal),
|
|
47
47
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.source),
|
|
48
48
|
});
|
|
49
|
-
const
|
|
49
|
+
const validatedHandler = withValidatedArgs(MoveFileInputSchema, handler);
|
|
50
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
50
51
|
guard: options.isInitialized,
|
|
51
|
-
progressMessage: (args) => `🛠 mv: ${path.basename(args.source)}
|
|
52
|
+
progressMessage: (args) => `🛠 mv: ${path.basename(args.source)} → ${path.basename(args.destination)}`,
|
|
53
|
+
completionMessage: (args, result) => {
|
|
54
|
+
const src = path.basename(args.source);
|
|
55
|
+
const dst = path.basename(args.destination);
|
|
56
|
+
if (result.isError)
|
|
57
|
+
return `🛠 mv: ${src} → ${dst} • failed`;
|
|
58
|
+
return `🛠 mv: ${src} → ${dst} • moved`;
|
|
59
|
+
},
|
|
52
60
|
});
|
|
53
61
|
if (registerToolTaskIfAvailable(server, 'mv', MOVE_FILE_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
54
62
|
return;
|
|
@@ -3,7 +3,7 @@ import { DEFAULT_READ_MANY_MAX_TOTAL_SIZE, DEFAULT_SEARCH_TIMEOUT_MS, } from '..
|
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { readMultipleFiles } from '../lib/file-operations/read-multiple-files.js';
|
|
5
5
|
import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
|
|
6
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
6
|
+
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
8
|
const READ_MULTIPLE_FILES_TOOL = {
|
|
9
9
|
title: 'Read Multiple Files',
|
|
@@ -121,19 +121,20 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
121
121
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, primaryPath),
|
|
122
122
|
});
|
|
123
123
|
};
|
|
124
|
-
const
|
|
124
|
+
const validatedHandler = withValidatedArgs(ReadMultipleFilesInputSchema, handler);
|
|
125
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
125
126
|
guard: options.isInitialized,
|
|
126
127
|
progressMessage: (args) => {
|
|
127
128
|
const first = path.basename(args.paths[0] ?? '');
|
|
128
129
|
const extra = args.paths.length > 1 ? `, ${path.basename(args.paths[1] ?? '')}…` : '';
|
|
129
130
|
return `🕮 read_many: ${args.paths.length} files [${first}${extra}]`;
|
|
130
131
|
},
|
|
131
|
-
completionMessage: (
|
|
132
|
+
completionMessage: (args, result) => {
|
|
132
133
|
if (result.isError)
|
|
133
|
-
return `🕮 read_many • failed`;
|
|
134
|
+
return `🕮 read_many: ${args.paths.length} files • failed`;
|
|
134
135
|
const sc = result.structuredContent;
|
|
135
136
|
if (!sc.ok)
|
|
136
|
-
return `🕮 read_many • failed`;
|
|
137
|
+
return `🕮 read_many: ${args.paths.length} files • failed`;
|
|
137
138
|
const total = sc.summary?.total ?? 0;
|
|
138
139
|
const succeeded = sc.summary?.succeeded ?? 0;
|
|
139
140
|
const failed = sc.summary?.failed ?? 0;
|
package/dist/tools/read.js
CHANGED
|
@@ -3,7 +3,7 @@ import { DEFAULT_SEARCH_TIMEOUT_MS, MAX_TEXT_FILE_SIZE, } from '../lib/constants
|
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { readFile } from '../lib/fs-helpers.js';
|
|
5
5
|
import { ReadFileInputSchema, ReadFileOutputSchema } from '../schemas.js';
|
|
6
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
6
|
+
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
8
|
const READ_FILE_TOOL = {
|
|
9
9
|
title: 'Read File',
|
|
@@ -80,7 +80,8 @@ export function registerReadFileTool(server, options = {}) {
|
|
|
80
80
|
run: (signal) => handleReadFile(args, signal, options.resourceStore),
|
|
81
81
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, args.path),
|
|
82
82
|
});
|
|
83
|
-
const
|
|
83
|
+
const validatedHandler = withValidatedArgs(ReadFileInputSchema, handler);
|
|
84
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
84
85
|
guard: options.isInitialized,
|
|
85
86
|
progressMessage: (args) => {
|
|
86
87
|
const name = path.basename(args.path);
|
|
@@ -8,7 +8,7 @@ import { globEntries } from '../lib/file-operations/glob-engine.js';
|
|
|
8
8
|
import { atomicWriteFile, withAbort } from '../lib/fs-helpers.js';
|
|
9
9
|
import { validateExistingPath, validatePathForWrite, } from '../lib/path-validation.js';
|
|
10
10
|
import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
|
|
11
|
-
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, notifyProgress, resolvePathOrRoot, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
11
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, notifyProgress, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
12
12
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
13
13
|
const SEARCH_AND_REPLACE_TOOL = {
|
|
14
14
|
title: 'Search and Replace',
|
|
@@ -279,7 +279,8 @@ export function registerSearchAndReplaceTool(server, options = {}) {
|
|
|
279
279
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
280
280
|
});
|
|
281
281
|
const { isInitialized } = options;
|
|
282
|
-
const
|
|
282
|
+
const validatedHandler = withValidatedArgs(SearchAndReplaceInputSchema, handler);
|
|
283
|
+
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
283
284
|
guard: isInitialized,
|
|
284
285
|
});
|
|
285
286
|
if (registerToolTaskIfAvailable(server, 'search_and_replace', SEARCH_AND_REPLACE_TOOL, wrappedHandler, options.iconInfo, isInitialized))
|
package/dist/tools/roots.js
CHANGED
|
@@ -2,7 +2,7 @@ import { joinLines } from '../config.js';
|
|
|
2
2
|
import { ErrorCode } from '../lib/errors.js';
|
|
3
3
|
import { getAllowedDirectories } from '../lib/path-validation.js';
|
|
4
4
|
import { ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, } from '../schemas.js';
|
|
5
|
-
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
5
|
+
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
6
|
const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
7
7
|
title: 'Workspace Roots',
|
|
8
8
|
description: 'List the workspace roots this server can access. ' +
|
|
@@ -38,8 +38,18 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
|
38
38
|
run: () => handleListAllowedDirectories(),
|
|
39
39
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN),
|
|
40
40
|
});
|
|
41
|
-
|
|
41
|
+
const validatedHandler = withValidatedArgs(ListAllowedDirectoriesInputSchema, handler);
|
|
42
|
+
server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
42
43
|
guard: options.isInitialized,
|
|
43
44
|
progressMessage: () => '≣ roots',
|
|
45
|
+
completionMessage: (_args, result) => {
|
|
46
|
+
if (result.isError)
|
|
47
|
+
return `≣ roots • failed`;
|
|
48
|
+
const sc = result.structuredContent;
|
|
49
|
+
if (!sc.ok)
|
|
50
|
+
return `≣ roots • failed`;
|
|
51
|
+
const count = sc.rootsCount ?? 0;
|
|
52
|
+
return `≣ roots • ${count} ${count === 1 ? 'root' : 'roots'}`;
|
|
53
|
+
},
|
|
44
54
|
}));
|
|
45
55
|
}
|