@wonderwhy-er/desktop-commander 0.1.31 → 0.1.32

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/dist/server.js CHANGED
@@ -1,13 +1,7 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListPromptsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { zodToJsonSchema } from "zod-to-json-schema";
4
- import { commandManager } from './command-manager.js';
5
4
  import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, BlockCommandArgsSchema, UnblockCommandArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, EditBlockArgsSchema, SearchCodeArgsSchema, } from './tools/schemas.js';
6
- import { executeCommand, readOutput, forceTerminate, listSessions } from './tools/execute.js';
7
- import { listProcesses, killProcess } from './tools/process.js';
8
- import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js';
9
- import { parseEditBlock, performSearchReplace } from './tools/edit.js';
10
- import { searchTextInFiles } from './tools/search.js';
11
5
  import { VERSION } from './version.js';
12
6
  import { capture } from "./utils.js";
13
7
  export const server = new Server({
@@ -96,10 +90,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
96
90
  // Filesystem tools
97
91
  {
98
92
  name: "read_file",
99
- description: "Read the complete contents of a file from the file system. " +
93
+ description: "Read the complete contents of a file from the file system or a URL. " +
94
+ "When reading from the file system, only works within allowed directories. " +
95
+ "Can fetch content from URLs when isUrl parameter is set to true. " +
100
96
  "Handles text files normally and image files are returned as viewable images. " +
101
- "Recognized image types: PNG, JPEG, GIF, WebP. " +
102
- "Only works within allowed directories.",
97
+ "Recognized image types: PNG, JPEG, GIF, WebP.",
103
98
  inputSchema: zodToJsonSchema(ReadFileArgsSchema),
104
99
  },
105
100
  {
@@ -142,6 +137,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
142
137
  name: "search_files",
143
138
  description: "Finds files by name using a case-insensitive substring matching. " +
144
139
  "Searches through all subdirectories from the starting path. " +
140
+ "Has a default timeout of 30 seconds which can be customized using the timeoutMs parameter. " +
145
141
  "Only searches within allowed directories.",
146
142
  inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
147
143
  },
@@ -150,6 +146,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
150
146
  description: "Search for text/code patterns within file contents using ripgrep. " +
151
147
  "Fast and powerful search similar to VS Code search functionality. " +
152
148
  "Supports regular expressions, file pattern filtering, and context lines. " +
149
+ "Has a default timeout of 30 seconds which can be customized. " +
153
150
  "Only searches within allowed directories.",
154
151
  inputSchema: zodToJsonSchema(SearchCodeArgsSchema),
155
152
  },
@@ -179,331 +176,61 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
179
176
  ],
180
177
  };
181
178
  });
179
+ import * as handlers from './handlers/index.js';
182
180
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
183
181
  try {
184
182
  const { name, arguments: args } = request.params;
185
183
  capture('server_call_tool');
184
+ // Add a single dynamic capture for the specific tool
185
+ capture('server_' + name);
186
+ // Using a more structured approach with dedicated handlers
186
187
  switch (name) {
187
188
  // Terminal tools
188
- case "execute_command": {
189
- capture('server_execute_command');
190
- const parsed = ExecuteCommandArgsSchema.parse(args);
191
- return executeCommand(parsed);
192
- }
193
- case "read_output": {
194
- capture('server_read_output');
195
- const parsed = ReadOutputArgsSchema.parse(args);
196
- return readOutput(parsed);
197
- }
198
- case "force_terminate": {
199
- capture('server_force_terminate');
200
- const parsed = ForceTerminateArgsSchema.parse(args);
201
- return forceTerminate(parsed);
202
- }
189
+ case "execute_command":
190
+ return handlers.handleExecuteCommand(args);
191
+ case "read_output":
192
+ return handlers.handleReadOutput(args);
193
+ case "force_terminate":
194
+ return handlers.handleForceTerminate(args);
203
195
  case "list_sessions":
204
- capture('server_list_sessions');
205
- return listSessions();
196
+ return handlers.handleListSessions();
197
+ // Process tools
206
198
  case "list_processes":
207
- capture('server_list_processes');
208
- return listProcesses();
209
- case "kill_process": {
210
- capture('server_kill_process');
211
- const parsed = KillProcessArgsSchema.parse(args);
212
- return killProcess(parsed);
213
- }
214
- case "block_command": {
215
- capture('server_block_command');
216
- const parsed = BlockCommandArgsSchema.parse(args);
217
- const blockResult = await commandManager.blockCommand(parsed.command);
218
- return {
219
- content: [{ type: "text", text: blockResult }],
220
- };
221
- }
222
- case "unblock_command": {
223
- capture('server_unblock_command');
224
- const parsed = UnblockCommandArgsSchema.parse(args);
225
- const unblockResult = await commandManager.unblockCommand(parsed.command);
226
- return {
227
- content: [{ type: "text", text: unblockResult }],
228
- };
229
- }
230
- case "list_blocked_commands": {
231
- capture('server_list_blocked_commands');
232
- const blockedCommands = await commandManager.listBlockedCommands();
233
- return {
234
- content: [{ type: "text", text: blockedCommands.join('\n') }],
235
- };
236
- }
199
+ return handlers.handleListProcesses();
200
+ case "kill_process":
201
+ return handlers.handleKillProcess(args);
202
+ // Command management tools
203
+ case "block_command":
204
+ return handlers.handleBlockCommand(args);
205
+ case "unblock_command":
206
+ return handlers.handleUnblockCommand(args);
207
+ case "list_blocked_commands":
208
+ return handlers.handleListBlockedCommands();
237
209
  // Filesystem tools
238
- case "edit_block": {
239
- capture('server_edit_block');
240
- try {
241
- const parsed = EditBlockArgsSchema.parse(args);
242
- const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
243
- await performSearchReplace(filePath, searchReplace);
244
- return {
245
- content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
246
- };
247
- }
248
- catch (error) {
249
- const errorMessage = error instanceof Error ? error.message : String(error);
250
- capture('server_' + name + "_error");
251
- return {
252
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
253
- };
254
- }
255
- }
256
- case "read_file": {
257
- capture('server_read_file');
258
- try {
259
- const parsed = ReadFileArgsSchema.parse(args);
260
- // Explicitly cast the result to FileResult since we're passing true
261
- const fileResult = await readFile(parsed.path, true);
262
- if (fileResult.isImage) {
263
- // For image files, return as an image content type
264
- return {
265
- content: [
266
- {
267
- type: "text",
268
- text: `Image file: ${parsed.path} (${fileResult.mimeType})\n`
269
- },
270
- {
271
- type: "image",
272
- data: fileResult.content,
273
- mimeType: fileResult.mimeType
274
- }
275
- ],
276
- };
277
- }
278
- else {
279
- // For all other files, return as text
280
- capture('server_' + "read_file_error");
281
- return {
282
- content: [{ type: "text", text: fileResult.content }],
283
- };
284
- }
285
- }
286
- catch (error) {
287
- capture('server_' + name + "_error");
288
- const errorMessage = error instanceof Error ? error.message : String(error);
289
- return {
290
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
291
- };
292
- }
293
- }
294
- case "read_multiple_files": {
295
- capture('server_read_multiple_files');
296
- try {
297
- const parsed = ReadMultipleFilesArgsSchema.parse(args);
298
- const fileResults = await readMultipleFiles(parsed.paths);
299
- // Create a text summary of all files
300
- const textSummary = fileResults.map(result => {
301
- if (result.error) {
302
- return `${result.path}: Error - ${result.error}`;
303
- }
304
- else if (result.mimeType) {
305
- return `${result.path}: ${result.mimeType} ${result.isImage ? '(image)' : '(text)'}`;
306
- }
307
- else {
308
- return `${result.path}: Unknown type`;
309
- }
310
- }).join("\n");
311
- // Create content items for each file
312
- const contentItems = [];
313
- // Add the text summary
314
- contentItems.push({ type: "text", text: textSummary });
315
- // Add each file content
316
- for (const result of fileResults) {
317
- if (!result.error && result.content !== undefined) {
318
- if (result.isImage && result.mimeType) {
319
- // For image files, add an image content item
320
- contentItems.push({
321
- type: "image",
322
- data: result.content,
323
- mimeType: result.mimeType
324
- });
325
- }
326
- else {
327
- // For text files, add a text summary
328
- contentItems.push({
329
- type: "text",
330
- text: `\n--- ${result.path} contents: ---\n${result.content}`
331
- });
332
- }
333
- }
334
- }
335
- return { content: contentItems };
336
- }
337
- catch (error) {
338
- capture('server_' + name + "_error");
339
- const errorMessage = error instanceof Error ? error.message : String(error);
340
- return {
341
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
342
- };
343
- }
344
- }
345
- case "write_file": {
346
- capture('server_write_file');
347
- try {
348
- const parsed = WriteFileArgsSchema.parse(args);
349
- await writeFile(parsed.path, parsed.content);
350
- return {
351
- content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
352
- };
353
- }
354
- catch (error) {
355
- capture('server_' + name + "_error");
356
- const errorMessage = error instanceof Error ? error.message : String(error);
357
- return {
358
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
359
- };
360
- }
361
- }
362
- case "create_directory": {
363
- capture('server_create_directory');
364
- try {
365
- const parsed = CreateDirectoryArgsSchema.parse(args);
366
- await createDirectory(parsed.path);
367
- return {
368
- content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
369
- };
370
- }
371
- catch (error) {
372
- capture('server_' + name + "_error");
373
- const errorMessage = error instanceof Error ? error.message : String(error);
374
- return {
375
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
376
- };
377
- }
378
- }
379
- case "list_directory": {
380
- capture('server_list_directory');
381
- try {
382
- const parsed = ListDirectoryArgsSchema.parse(args);
383
- const entries = await listDirectory(parsed.path);
384
- return {
385
- content: [{ type: "text", text: entries.join('\n') }],
386
- };
387
- }
388
- catch (error) {
389
- capture('server_' + name + "_error");
390
- const errorMessage = error instanceof Error ? error.message : String(error);
391
- return {
392
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
393
- };
394
- }
395
- }
396
- case "move_file": {
397
- capture('server_move_file');
398
- try {
399
- const parsed = MoveFileArgsSchema.parse(args);
400
- await moveFile(parsed.source, parsed.destination);
401
- return {
402
- content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
403
- };
404
- }
405
- catch (error) {
406
- capture('server_' + name + "_error");
407
- const errorMessage = error instanceof Error ? error.message : String(error);
408
- return {
409
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
410
- };
411
- }
412
- }
413
- case "search_files": {
414
- capture('server_search_files');
415
- try {
416
- const parsed = SearchFilesArgsSchema.parse(args);
417
- const results = await searchFiles(parsed.path, parsed.pattern);
418
- return {
419
- content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
420
- };
421
- }
422
- catch (error) {
423
- capture('server_' + name + "_error");
424
- const errorMessage = error instanceof Error ? error.message : String(error);
425
- return {
426
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
427
- };
428
- }
429
- }
430
- case "search_code": {
431
- capture('server_search_code');
432
- let results = [];
433
- try {
434
- const parsed = SearchCodeArgsSchema.parse(args);
435
- results = await searchTextInFiles({
436
- rootPath: parsed.path,
437
- pattern: parsed.pattern,
438
- filePattern: parsed.filePattern,
439
- ignoreCase: parsed.ignoreCase,
440
- maxResults: parsed.maxResults,
441
- includeHidden: parsed.includeHidden,
442
- contextLines: parsed.contextLines,
443
- });
444
- if (results.length === 0) {
445
- return {
446
- content: [{ type: "text", text: "No matches found" }],
447
- };
448
- }
449
- }
450
- catch (error) {
451
- capture('server_' + name + "_error");
452
- const errorMessage = error instanceof Error ? error.message : String(error);
453
- return {
454
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
455
- };
456
- }
457
- // Format the results in a VS Code-like format
458
- let currentFile = "";
459
- let formattedResults = "";
460
- results.forEach(result => {
461
- if (result.file !== currentFile) {
462
- formattedResults += `\n${result.file}:\n`;
463
- currentFile = result.file;
464
- }
465
- formattedResults += ` ${result.line}: ${result.match}\n`;
466
- });
467
- return {
468
- content: [{ type: "text", text: formattedResults.trim() }],
469
- };
470
- }
471
- case "get_file_info": {
472
- capture('server_get_file_info');
473
- try {
474
- const parsed = GetFileInfoArgsSchema.parse(args);
475
- const info = await getFileInfo(parsed.path);
476
- return {
477
- content: [{
478
- type: "text",
479
- text: Object.entries(info)
480
- .map(([key, value]) => `${key}: ${value}`)
481
- .join('\n')
482
- }],
483
- };
484
- }
485
- catch (error) {
486
- capture('server_' + name + "_error");
487
- const errorMessage = error instanceof Error ? error.message : String(error);
488
- return {
489
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
490
- };
491
- }
492
- }
493
- case "list_allowed_directories": {
494
- capture('server_list_allowed_directories');
495
- const directories = listAllowedDirectories();
496
- return {
497
- content: [{
498
- type: "text",
499
- text: `Allowed directories:\n${directories.join('\n')}`
500
- }],
501
- };
502
- }
210
+ case "read_file":
211
+ return handlers.handleReadFile(args);
212
+ case "read_multiple_files":
213
+ return handlers.handleReadMultipleFiles(args);
214
+ case "write_file":
215
+ return handlers.handleWriteFile(args);
216
+ case "create_directory":
217
+ return handlers.handleCreateDirectory(args);
218
+ case "list_directory":
219
+ return handlers.handleListDirectory(args);
220
+ case "move_file":
221
+ return handlers.handleMoveFile(args);
222
+ case "search_files":
223
+ return handlers.handleSearchFiles(args);
224
+ case "search_code":
225
+ return handlers.handleSearchCode(args);
226
+ case "get_file_info":
227
+ return handlers.handleGetFileInfo(args);
228
+ case "list_allowed_directories":
229
+ return handlers.handleListAllowedDirectories();
230
+ case "edit_block":
231
+ return handlers.handleEditBlock(args);
503
232
  default:
504
- capture('server_unknow_tool', {
505
- name
506
- });
233
+ capture('server_unknown_tool', { name });
507
234
  throw new Error(`Unknown tool: ${name}`);
508
235
  }
509
236
  }
@@ -15,7 +15,7 @@ try {
15
15
  const machineIdModule = await import('node-machine-id');
16
16
 
17
17
  client = new PostHog(
18
- 'phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063',
18
+ 'phc_BW8KJ0cajzj2v8qfMhvDQ4dtFdgHPzeYcMRvRFGvQdH',
19
19
  {
20
20
  host: 'https://eu.i.posthog.com',
21
21
  flushAt: 1, // send all every time
@@ -13,7 +13,28 @@ export interface FileResult {
13
13
  mimeType: string;
14
14
  isImage: boolean;
15
15
  }
16
- export declare function readFile(filePath: string, returnMetadata?: boolean): Promise<string | FileResult>;
16
+ /**
17
+ * Read file content from a URL
18
+ * @param url URL to fetch content from
19
+ * @param returnMetadata Whether to return metadata with the content
20
+ * @returns File content or file result with metadata
21
+ */
22
+ export declare function readFileFromUrl(url: string, returnMetadata?: boolean): Promise<string | FileResult>;
23
+ /**
24
+ * Read file content from the local filesystem
25
+ * @param filePath Path to the file
26
+ * @param returnMetadata Whether to return metadata with the content
27
+ * @returns File content or file result with metadata
28
+ */
29
+ export declare function readFileFromDisk(filePath: string, returnMetadata?: boolean): Promise<string | FileResult>;
30
+ /**
31
+ * Read a file from either the local filesystem or a URL
32
+ * @param filePath Path to the file or URL
33
+ * @param returnMetadata Whether to return metadata with the content
34
+ * @param isUrl Whether the path is a URL
35
+ * @returns File content or file result with metadata
36
+ */
37
+ export declare function readFile(filePath: string, returnMetadata?: boolean, isUrl?: boolean): Promise<string | FileResult>;
17
38
  export declare function writeFile(filePath: string, content: string): Promise<void>;
18
39
  export interface MultiFileResult {
19
40
  path: string;