@wonderwhy-er/desktop-commander 0.2.10 → 0.2.11

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 CHANGED
@@ -2,6 +2,7 @@
2
2
  ### Search, update, manage files and run terminal commands with AI
3
3
 
4
4
  [![npm downloads](https://img.shields.io/npm/dw/@wonderwhy-er/desktop-commander)](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
5
+ [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/wonderwhy-er/DesktopCommanderMCP)](https://archestra.ai/mcp-catalog/wonderwhy-er__desktopcommandermcp)
5
6
  [![smithery badge](https://smithery.ai/badge/@wonderwhy-er/desktop-commander)](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
6
7
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow.svg)](https://www.buymeacoffee.com/wonderwhyer)
7
8
 
@@ -130,7 +131,7 @@ Add this entry to your claude_desktop_config.json:
130
131
  "command": "npx",
131
132
  "args": [
132
133
  "-y",
133
- "@wonderwhy-er/desktop-commander"
134
+ "@wonderwhy-er/desktop-commander@latest"
134
135
  ]
135
136
  }
136
137
  }
@@ -400,8 +401,10 @@ The server provides a comprehensive set of tools organized into several categori
400
401
  | | `create_directory` | Create a new directory or ensure it exists |
401
402
  | | `list_directory` | Get detailed listing of files and directories |
402
403
  | | `move_file` | Move or rename files and directories |
403
- | | `search_files` | Find files by name using case-insensitive substring matching |
404
- | | `search_code` | Search for text/code patterns within file contents using ripgrep |
404
+ | | `start_search` | Start streaming search for files by name or content patterns (unified ripgrep-based search) |
405
+ | | `get_more_search_results` | Get paginated results from active search with offset support |
406
+ | | `stop_search` | Stop an active search gracefully |
407
+ | | `list_searches` | List all active search sessions |
405
408
  | | `get_file_info` | Retrieve detailed metadata about a file or directory |
406
409
  | **Text Editing** | `edit_block` | Apply targeted text replacements with enhanced prompting for smaller edits (includes character-level diff feedback) |
407
410
  | **Analytics** | `get_usage_stats` | Get usage statistics for your own insight |
@@ -564,9 +567,15 @@ For commands that may take a while:
564
567
 
565
568
  ### ⚠️ Important Security Warnings
566
569
 
567
- 1. **Always change configuration in a separate chat window** from where you're doing your actual work. Claude may sometimes attempt to modify configuration settings (like `allowedDirectories`) if it encounters filesystem access restrictions.
570
+ > **For comprehensive security information and vulnerability reporting**: See [SECURITY.md](SECURITY.md)
568
571
 
569
- 2. **The `allowedDirectories` setting currently only restricts filesystem operations**, not terminal commands. Terminal commands can still access files outside allowed directories. Full terminal sandboxing is on the roadmap.
572
+ 1. **Known security limitations**: Directory restrictions and command blocking can be bypassed through various methods including symlinks, command substitution, and absolute paths or code execution
573
+
574
+ 2. **Always change configuration in a separate chat window** from where you're doing your actual work. Claude may sometimes attempt to modify configuration settings (like `allowedDirectories`) if it encounters filesystem access restrictions.
575
+
576
+ 3. **The `allowedDirectories` setting currently only restricts filesystem operations**, not terminal commands. Terminal commands can still access files outside allowed directories.
577
+
578
+ 4. **For production security**: Use the [Docker installation](#option-6-docker-installation-🐳-⭐-auto-updates-no-nodejs-required) which provides complete isolation from your host system.
570
579
 
571
580
  ### Configuration Tools
572
581
 
@@ -853,6 +862,9 @@ Yes, when installed through npx or Smithery, Desktop Commander automatically upd
853
862
  ### I'm having trouble installing or using the tool. Where can I get help?
854
863
  Join our [Discord server](https://discord.gg/kQ27sNnZr7) for community support, check the [GitHub issues](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues) for known problems, or review the [full FAQ](FAQ.md) for troubleshooting tips. You can also visit our [website FAQ section](https://desktopcommander.app#faq) for a more user-friendly experience. If you encounter a new issue, please consider [opening a GitHub issue](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/new) with details about your problem.
855
864
 
865
+ ### How do I report security vulnerabilities?
866
+ Please create a [GitHub Issue](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues) with detailed information about any security vulnerabilities you discover. See our [Security Policy](SECURITY.md) for complete guidelines on responsible disclosure.
867
+
856
868
  ## Data Collection & Privacy
857
869
 
858
870
  Desktop Commander collects limited anonymous telemetry data to help improve the tool. No personal information, file contents, file paths, or command arguments are collected.
@@ -6,7 +6,21 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
  export declare class FilteredStdioServerTransport extends StdioServerTransport {
7
7
  private originalConsole;
8
8
  private originalStdoutWrite;
9
+ private isInitialized;
10
+ private messageBuffer;
9
11
  constructor();
12
+ /**
13
+ * Call this method after MCP initialization is complete to enable JSON-RPC notifications
14
+ */
15
+ enableNotifications(): void;
16
+ /**
17
+ * Check if notifications are enabled
18
+ */
19
+ get isNotificationsEnabled(): boolean;
20
+ /**
21
+ * Get the current count of buffered messages
22
+ */
23
+ get bufferedMessageCount(): number;
10
24
  private setupConsoleRedirection;
11
25
  private setupStdoutFiltering;
12
26
  private sendLogNotification;
@@ -7,6 +7,8 @@ import process from "node:process";
7
7
  export class FilteredStdioServerTransport extends StdioServerTransport {
8
8
  constructor() {
9
9
  super();
10
+ this.isInitialized = false;
11
+ this.messageBuffer = [];
10
12
  // Store original methods
11
13
  this.originalConsole = {
12
14
  log: console.log,
@@ -20,24 +22,100 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
20
22
  this.setupConsoleRedirection();
21
23
  // Setup stdout filtering for any other output
22
24
  this.setupStdoutFiltering();
23
- // Log initialization to stderr to avoid polluting the JSON stream
24
- process.stderr.write(`[desktop-commander] Enhanced FilteredStdioServerTransport initialized\n`);
25
+ // Send initialization notification
26
+ this.sendLogNotification('info', ['Enhanced FilteredStdioServerTransport initialized']);
27
+ }
28
+ /**
29
+ * Call this method after MCP initialization is complete to enable JSON-RPC notifications
30
+ */
31
+ enableNotifications() {
32
+ this.isInitialized = true;
33
+ // Replay all buffered messages in chronological order
34
+ if (this.messageBuffer.length > 0) {
35
+ this.sendLogNotification('info', [`Replaying ${this.messageBuffer.length} buffered initialization messages`]);
36
+ this.messageBuffer
37
+ .sort((a, b) => a.timestamp - b.timestamp)
38
+ .forEach(msg => {
39
+ this.sendLogNotification(msg.level, msg.args);
40
+ });
41
+ // Clear the buffer
42
+ this.messageBuffer = [];
43
+ }
44
+ this.sendLogNotification('info', ['JSON-RPC notifications enabled']);
45
+ }
46
+ /**
47
+ * Check if notifications are enabled
48
+ */
49
+ get isNotificationsEnabled() {
50
+ return this.isInitialized;
51
+ }
52
+ /**
53
+ * Get the current count of buffered messages
54
+ */
55
+ get bufferedMessageCount() {
56
+ return this.messageBuffer.length;
25
57
  }
26
58
  setupConsoleRedirection() {
27
59
  console.log = (...args) => {
28
- this.sendLogNotification("info", args);
60
+ if (this.isInitialized) {
61
+ this.sendLogNotification("info", args);
62
+ }
63
+ else {
64
+ // Buffer for later replay to client
65
+ this.messageBuffer.push({
66
+ level: "info",
67
+ args,
68
+ timestamp: Date.now()
69
+ });
70
+ }
29
71
  };
30
72
  console.info = (...args) => {
31
- this.sendLogNotification("info", args);
73
+ if (this.isInitialized) {
74
+ this.sendLogNotification("info", args);
75
+ }
76
+ else {
77
+ this.messageBuffer.push({
78
+ level: "info",
79
+ args,
80
+ timestamp: Date.now()
81
+ });
82
+ }
32
83
  };
33
84
  console.warn = (...args) => {
34
- this.sendLogNotification("warning", args);
85
+ if (this.isInitialized) {
86
+ this.sendLogNotification("warning", args);
87
+ }
88
+ else {
89
+ this.messageBuffer.push({
90
+ level: "warning",
91
+ args,
92
+ timestamp: Date.now()
93
+ });
94
+ }
35
95
  };
36
96
  console.error = (...args) => {
37
- this.sendLogNotification("error", args);
97
+ if (this.isInitialized) {
98
+ this.sendLogNotification("error", args);
99
+ }
100
+ else {
101
+ this.messageBuffer.push({
102
+ level: "error",
103
+ args,
104
+ timestamp: Date.now()
105
+ });
106
+ }
38
107
  };
39
108
  console.debug = (...args) => {
40
- this.sendLogNotification("debug", args);
109
+ if (this.isInitialized) {
110
+ this.sendLogNotification("debug", args);
111
+ }
112
+ else {
113
+ this.messageBuffer.push({
114
+ level: "debug",
115
+ args,
116
+ timestamp: Date.now()
117
+ });
118
+ }
41
119
  };
42
120
  }
43
121
  setupStdoutFiltering() {
@@ -54,7 +132,17 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
54
132
  }
55
133
  else if (trimmed.length > 0) {
56
134
  // Non-JSON-RPC output, wrap it in a log notification
57
- this.sendLogNotification("info", [buffer.replace(/\n$/, '')]);
135
+ if (this.isInitialized) {
136
+ this.sendLogNotification("info", [buffer.replace(/\n$/, '')]);
137
+ }
138
+ else {
139
+ // Buffer for later replay to client
140
+ this.messageBuffer.push({
141
+ level: "info",
142
+ args: [buffer.replace(/\n$/, '')],
143
+ timestamp: Date.now()
144
+ });
145
+ }
58
146
  if (callback)
59
147
  callback();
60
148
  return true;
@@ -99,8 +187,17 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
99
187
  this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
100
188
  }
101
189
  catch (error) {
102
- // Fallback to stderr if JSON serialization fails
103
- process.stderr.write(`[${level.toUpperCase()}] ${args.join(' ')}\n`);
190
+ // Fallback to a simple JSON-RPC error notification if JSON serialization fails
191
+ const fallbackNotification = {
192
+ jsonrpc: "2.0",
193
+ method: "notifications/message",
194
+ params: {
195
+ level: "error",
196
+ logger: "desktop-commander",
197
+ data: `Log serialization failed: ${args.join(' ')}`
198
+ }
199
+ };
200
+ this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
104
201
  }
105
202
  }
106
203
  /**
@@ -120,7 +217,17 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
120
217
  this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
121
218
  }
122
219
  catch (error) {
123
- process.stderr.write(`[${level.toUpperCase()}] ${message}\n`);
220
+ // Fallback to basic JSON-RPC notification
221
+ const fallbackNotification = {
222
+ jsonrpc: "2.0",
223
+ method: "notifications/message",
224
+ params: {
225
+ level: "error",
226
+ logger: "desktop-commander",
227
+ data: `sendLog failed: ${message}`
228
+ }
229
+ };
230
+ this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
124
231
  }
125
232
  }
126
233
  /**
@@ -140,7 +247,17 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
140
247
  this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
141
248
  }
142
249
  catch (error) {
143
- process.stderr.write(`[PROGRESS] ${token}: ${value}${total ? `/${total}` : ''}\n`);
250
+ // Fallback to basic JSON-RPC notification for progress
251
+ const fallbackNotification = {
252
+ jsonrpc: "2.0",
253
+ method: "notifications/message",
254
+ params: {
255
+ level: "info",
256
+ logger: "desktop-commander",
257
+ data: `Progress ${token}: ${value}${total ? `/${total}` : ''}`
258
+ }
259
+ };
260
+ this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
144
261
  }
145
262
  }
146
263
  /**
@@ -156,7 +273,17 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
156
273
  this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
157
274
  }
158
275
  catch (error) {
159
- process.stderr.write(`[NOTIFICATION] ${method}: ${JSON.stringify(params)}\n`);
276
+ // Fallback to basic JSON-RPC notification for custom notifications
277
+ const fallbackNotification = {
278
+ jsonrpc: "2.0",
279
+ method: "notifications/message",
280
+ params: {
281
+ level: "error",
282
+ logger: "desktop-commander",
283
+ data: `Custom notification failed: ${method}: ${JSON.stringify(params)}`
284
+ }
285
+ };
286
+ this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
160
287
  }
161
288
  }
162
289
  /**
@@ -1,11 +1,6 @@
1
1
  import { handleEditBlock } from '../tools/edit.js';
2
- import { ServerResult } from '../types.js';
3
2
  /**
4
3
  * Handle edit_block command
5
4
  * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
6
5
  */
7
6
  export { handleEditBlock };
8
- /**
9
- * Handle search_code command
10
- */
11
- export declare function handleSearchCode(args: unknown): Promise<ServerResult>;
@@ -1,88 +1,6 @@
1
- import { searchTextInFiles } from '../tools/search.js';
2
- import { SearchCodeArgsSchema } from '../tools/schemas.js';
3
1
  import { handleEditBlock } from '../tools/edit.js';
4
- import { capture } from '../utils/capture.js';
5
- import { withTimeout } from '../utils/withTimeout.js';
6
2
  /**
7
3
  * Handle edit_block command
8
4
  * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
9
5
  */
10
6
  export { handleEditBlock };
11
- /**
12
- * Handle search_code command
13
- */
14
- export async function handleSearchCode(args) {
15
- const parsed = SearchCodeArgsSchema.parse(args);
16
- const timeoutMs = parsed.timeoutMs || 30000; // 30 seconds default
17
- // Limit maxResults to prevent overwhelming responses
18
- const safeMaxResults = parsed.maxResults ? Math.min(parsed.maxResults, 5000) : 2000; // Default to 2000 instead of 1000
19
- // Apply timeout at the handler level
20
- const searchOperation = async () => {
21
- return await searchTextInFiles({
22
- rootPath: parsed.path,
23
- pattern: parsed.pattern,
24
- filePattern: parsed.filePattern,
25
- ignoreCase: parsed.ignoreCase,
26
- maxResults: safeMaxResults,
27
- includeHidden: parsed.includeHidden,
28
- contextLines: parsed.contextLines,
29
- // Don't pass timeoutMs down to the implementation
30
- });
31
- };
32
- // Use withTimeout at the handler level
33
- const results = await withTimeout(searchOperation(), timeoutMs, 'Code search operation', [] // Empty array as default on timeout
34
- );
35
- // If timeout occurred, try to terminate the ripgrep process
36
- if (results.length === 0 && globalThis.currentSearchProcess) {
37
- try {
38
- console.log(`Terminating timed out search process (PID: ${globalThis.currentSearchProcess.pid})`);
39
- globalThis.currentSearchProcess.kill();
40
- delete globalThis.currentSearchProcess;
41
- }
42
- catch (error) {
43
- capture('server_request_error', {
44
- error: 'Error terminating search process'
45
- });
46
- }
47
- }
48
- if (results.length === 0) {
49
- if (timeoutMs > 0) {
50
- return {
51
- content: [{ type: "text", text: `No matches found or search timed out after ${timeoutMs}ms.` }],
52
- };
53
- }
54
- return {
55
- content: [{ type: "text", text: "No matches found" }],
56
- };
57
- }
58
- // Format the results in a VS Code-like format with early truncation
59
- let currentFile = "";
60
- let formattedResults = "";
61
- const MAX_RESPONSE_SIZE = 900000; // 900KB limit - well below the 1MB API limit
62
- let resultsProcessed = 0;
63
- let totalResults = results.length;
64
- for (const result of results) {
65
- // Check if adding this result would exceed our limit
66
- const newFileHeader = result.file !== currentFile ? `\n${result.file}:\n` : '';
67
- const newLine = ` ${result.line}: ${result.match}\n`;
68
- const potentialAddition = newFileHeader + newLine;
69
- // If adding this would exceed the limit, truncate here
70
- if (formattedResults.length + potentialAddition.length > MAX_RESPONSE_SIZE) {
71
- const remainingResults = totalResults - resultsProcessed;
72
- const avgResultLength = formattedResults.length / Math.max(resultsProcessed, 1);
73
- const estimatedRemainingChars = remainingResults * avgResultLength;
74
- const truncationMessage = `\n\n[Results truncated - ${remainingResults} more results available (approximately ${Math.round(estimatedRemainingChars).toLocaleString()} more characters). Try refining your search pattern or using a more specific file pattern to get fewer results.]`;
75
- formattedResults += truncationMessage;
76
- break;
77
- }
78
- if (result.file !== currentFile) {
79
- formattedResults += newFileHeader;
80
- currentFile = result.file;
81
- }
82
- formattedResults += newLine;
83
- resultsProcessed++;
84
- }
85
- return {
86
- content: [{ type: "text", text: formattedResults.trim() }],
87
- };
88
- }
@@ -23,10 +23,6 @@ export declare function handleListDirectory(args: unknown): Promise<ServerResult
23
23
  * Handle move_file command
24
24
  */
25
25
  export declare function handleMoveFile(args: unknown): Promise<ServerResult>;
26
- /**
27
- * Handle search_files command
28
- */
29
- export declare function handleSearchFiles(args: unknown): Promise<ServerResult>;
30
26
  /**
31
27
  * Handle get_file_info command
32
28
  */
@@ -1,8 +1,8 @@
1
- import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo } from '../tools/filesystem.js';
1
+ import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo } from '../tools/filesystem.js';
2
2
  import { withTimeout } from '../utils/withTimeout.js';
3
3
  import { createErrorResponse } from '../error-handlers.js';
4
4
  import { configManager } from '../config-manager.js';
5
- import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema } from '../tools/schemas.js';
5
+ import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema } from '../tools/schemas.js';
6
6
  /**
7
7
  * Helper function to check if path contains an error
8
8
  */
@@ -193,40 +193,6 @@ export async function handleMoveFile(args) {
193
193
  return createErrorResponse(errorMessage);
194
194
  }
195
195
  }
196
- /**
197
- * Handle search_files command
198
- */
199
- export async function handleSearchFiles(args) {
200
- try {
201
- const parsed = SearchFilesArgsSchema.parse(args);
202
- const timeoutMs = parsed.timeoutMs || 30000; // 30 seconds default
203
- // Apply timeout at the handler level
204
- const searchOperation = async () => {
205
- return await searchFiles(parsed.path, parsed.pattern);
206
- };
207
- // Use withTimeout at the handler level
208
- const results = await withTimeout(searchOperation(), timeoutMs, 'File search operation', [] // Empty array as default on timeout
209
- );
210
- if (results.length === 0) {
211
- // Similar approach as in handleSearchCode
212
- if (timeoutMs > 0) {
213
- return {
214
- content: [{ type: "text", text: `No matches found or search timed out after ${timeoutMs}ms.` }],
215
- };
216
- }
217
- return {
218
- content: [{ type: "text", text: "No matches found" }],
219
- };
220
- }
221
- return {
222
- content: [{ type: "text", text: results.join('\n') }],
223
- };
224
- }
225
- catch (error) {
226
- const errorMessage = error instanceof Error ? error.message : String(error);
227
- return createErrorResponse(errorMessage);
228
- }
229
- }
230
196
  /**
231
197
  * Handle get_file_info command
232
198
  */
@@ -2,3 +2,4 @@ export * from './filesystem-handlers.js';
2
2
  export * from './terminal-handlers.js';
3
3
  export * from './process-handlers.js';
4
4
  export * from './edit-search-handlers.js';
5
+ export * from './search-handlers.js';
@@ -3,3 +3,4 @@ export * from './filesystem-handlers.js';
3
3
  export * from './terminal-handlers.js';
4
4
  export * from './process-handlers.js';
5
5
  export * from './edit-search-handlers.js';
6
+ export * from './search-handlers.js';
@@ -0,0 +1,17 @@
1
+ import { ServerResult } from '../types.js';
2
+ /**
3
+ * Handle start_search command
4
+ */
5
+ export declare function handleStartSearch(args: unknown): Promise<ServerResult>;
6
+ /**
7
+ * Handle get_more_search_results command
8
+ */
9
+ export declare function handleGetMoreSearchResults(args: unknown): Promise<ServerResult>;
10
+ /**
11
+ * Handle stop_search command
12
+ */
13
+ export declare function handleStopSearch(args: unknown): Promise<ServerResult>;
14
+ /**
15
+ * Handle list_searches command
16
+ */
17
+ export declare function handleListSearches(): Promise<ServerResult>;