@wonderwhy-er/desktop-commander 0.2.23 → 0.2.24

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 (59) hide show
  1. package/README.md +14 -55
  2. package/dist/custom-stdio.d.ts +1 -0
  3. package/dist/custom-stdio.js +19 -0
  4. package/dist/handlers/filesystem-handlers.d.ts +4 -0
  5. package/dist/handlers/filesystem-handlers.js +120 -14
  6. package/dist/handlers/node-handlers.d.ts +6 -0
  7. package/dist/handlers/node-handlers.js +73 -0
  8. package/dist/index.js +5 -3
  9. package/dist/search-manager.d.ts +25 -0
  10. package/dist/search-manager.js +212 -0
  11. package/dist/server.js +160 -73
  12. package/dist/terminal-manager.d.ts +56 -2
  13. package/dist/terminal-manager.js +169 -13
  14. package/dist/tools/edit.d.ts +28 -4
  15. package/dist/tools/edit.js +87 -4
  16. package/dist/tools/filesystem.d.ts +23 -12
  17. package/dist/tools/filesystem.js +201 -416
  18. package/dist/tools/improved-process-tools.d.ts +2 -2
  19. package/dist/tools/improved-process-tools.js +244 -214
  20. package/dist/tools/mime-types.d.ts +1 -0
  21. package/dist/tools/mime-types.js +7 -0
  22. package/dist/tools/pdf/extract-images.d.ts +34 -0
  23. package/dist/tools/pdf/extract-images.js +132 -0
  24. package/dist/tools/pdf/index.d.ts +6 -0
  25. package/dist/tools/pdf/index.js +3 -0
  26. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  27. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  28. package/dist/tools/pdf/manipulations.d.ts +13 -0
  29. package/dist/tools/pdf/manipulations.js +96 -0
  30. package/dist/tools/pdf/markdown.d.ts +7 -0
  31. package/dist/tools/pdf/markdown.js +37 -0
  32. package/dist/tools/pdf/utils.d.ts +12 -0
  33. package/dist/tools/pdf/utils.js +34 -0
  34. package/dist/tools/schemas.d.ts +167 -12
  35. package/dist/tools/schemas.js +54 -5
  36. package/dist/types.d.ts +2 -1
  37. package/dist/utils/feature-flags.js +7 -4
  38. package/dist/utils/files/base.d.ts +167 -0
  39. package/dist/utils/files/base.js +5 -0
  40. package/dist/utils/files/binary.d.ts +21 -0
  41. package/dist/utils/files/binary.js +65 -0
  42. package/dist/utils/files/excel.d.ts +24 -0
  43. package/dist/utils/files/excel.js +416 -0
  44. package/dist/utils/files/factory.d.ts +40 -0
  45. package/dist/utils/files/factory.js +101 -0
  46. package/dist/utils/files/image.d.ts +21 -0
  47. package/dist/utils/files/image.js +78 -0
  48. package/dist/utils/files/index.d.ts +10 -0
  49. package/dist/utils/files/index.js +13 -0
  50. package/dist/utils/files/pdf.d.ts +32 -0
  51. package/dist/utils/files/pdf.js +142 -0
  52. package/dist/utils/files/text.d.ts +63 -0
  53. package/dist/utils/files/text.js +357 -0
  54. package/dist/utils/ripgrep-resolver.js +3 -2
  55. package/dist/utils/system-info.d.ts +5 -0
  56. package/dist/utils/system-info.js +71 -3
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +14 -3
package/README.md CHANGED
@@ -39,7 +39,9 @@ Execute long-running terminal commands on your computer and manage processes thr
39
39
 
40
40
  - **Enhanced terminal commands with interactive process control**
41
41
  - **Execute code in memory (Python, Node.js, R) without saving files**
42
- - **Instant data analysis - just ask to analyze CSV/JSON files**
42
+ - **Instant data analysis - just ask to analyze CSV/JSON/Excel files**
43
+ - **Native Excel file support** - Read, write, edit, and search Excel files (.xlsx, .xls, .xlsm) without external tools
44
+ - **PDF support** - Read PDFs with text extraction, create new PDFs from markdown, modify existing PDFs
43
45
  - **Interact with running processes (SSH, databases, development servers)**
44
46
  - Execute terminal commands with output streaming
45
47
  - Command timeout and background execution support
@@ -50,10 +52,10 @@ Execute long-running terminal commands on your computer and manage processes thr
50
52
  - Update multiple settings at once
51
53
  - Dynamic configuration changes without server restart
52
54
  - Full filesystem operations:
53
- - Read/write files
55
+ - Read/write files (text, Excel, PDF)
54
56
  - Create/list directories
55
57
  - Move files/directories
56
- - Search files
58
+ - Search files and content (including Excel content)
57
59
  - Get file metadata
58
60
  - **Negative offset file reading**: Read from end of files using negative offset values (like Unix tail)
59
61
  - Code editing capabilities:
@@ -420,18 +422,19 @@ The server provides a comprehensive set of tools organized into several categori
420
422
  | | `list_sessions` | List all active terminal sessions |
421
423
  | | `list_processes` | List all running processes with detailed information |
422
424
  | | `kill_process` | Terminate a running process by PID |
423
- | **Filesystem** | `read_file` | Read contents from local filesystem or URLs with line-based pagination (supports positive/negative offset and length parameters) |
425
+ | **Filesystem** | `read_file` | Read contents from local filesystem, URLs, Excel files (.xlsx, .xls, .xlsm), and PDFs with line/page-based pagination |
424
426
  | | `read_multiple_files` | Read multiple files simultaneously |
425
- | | `write_file` | Write file contents with options for rewrite or append mode (uses configurable line limits) |
427
+ | | `write_file` | Write file contents with options for rewrite or append mode. Supports Excel files (JSON 2D array format). For PDFs, use `write_pdf` |
428
+ | | `write_pdf` | Create new PDF files from markdown or modify existing PDFs (insert/delete pages). Supports HTML/CSS styling and SVG graphics |
426
429
  | | `create_directory` | Create a new directory or ensure it exists |
427
430
  | | `list_directory` | Get detailed recursive listing of files and directories (supports depth parameter, default depth=2) |
428
431
  | | `move_file` | Move or rename files and directories |
429
- | | `start_search` | Start streaming search for files by name or content patterns (unified ripgrep-based search) |
432
+ | | `start_search` | Start streaming search for files by name or content patterns (searches text files and Excel content) |
430
433
  | | `get_more_search_results` | Get paginated results from active search with offset support |
431
434
  | | `stop_search` | Stop an active search gracefully |
432
435
  | | `list_searches` | List all active search sessions |
433
- | | `get_file_info` | Retrieve detailed metadata about a file or directory |
434
- | **Text Editing** | `edit_block` | Apply targeted text replacements with enhanced prompting for smaller edits (includes character-level diff feedback) |
436
+ | | `get_file_info` | Retrieve detailed metadata about a file or directory (includes sheet info for Excel files) |
437
+ | **Text Editing** | `edit_block` | Apply targeted text replacements for text files, or range-based cell updates for Excel files |
435
438
  | **Analytics** | `get_usage_stats` | Get usage statistics for your own insight |
436
439
  | | `get_recent_tool_calls` | Get recent tool call history with arguments and outputs for debugging and context recovery |
437
440
  | | `give_feedback_to_desktop_commander` | Open feedback form in browser to provide feedback to Desktop Commander Team |
@@ -760,33 +763,6 @@ This project extends the MCP Filesystem Server to enable:
760
763
 
761
764
  Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
762
765
 
763
- ## DONE
764
- - **20-05-2025 v0.1.40 Release** - Added audit logging for all tool calls, improved line-based file operations, enhanced edit_block with better prompting for smaller edits, added explicit telemetry opt-out prompting
765
- - **05-05-2025 Fuzzy Search Logging** - Added comprehensive logging system for fuzzy search operations with detailed analysis tools, character-level diffs, and performance metrics to help debug edit_block failures
766
- - **29-04-2025 Telemetry Opt Out through configuration** - There is now setting to disable telemetry in config, ask in chat
767
- - **23-04-2025 Enhanced edit functionality** - Improved format, added fuzzy search and multi-occurrence replacements, should fail less and use edit block more often
768
- - **16-04-2025 Better configurations** - Improved settings for allowed paths, commands and shell environments
769
- - **14-04-2025 Windows environment fixes** - Resolved issues specific to Windows platforms
770
- - **14-04-2025 Linux improvements** - Enhanced compatibility with various Linux distributions
771
- - **12-04-2025 Better allowed directories and blocked commands** - Improved security and path validation for file read/write and terminal command restrictions.
772
- Terminal still can access files ignoring allowed directories.
773
- - **11-04-2025 Shell configuration** - Added ability to configure preferred shell for command execution
774
- - **07-04-2025 Added URL support** - `read_file` command can now fetch content from URLs
775
- - **28-03-2025 Fixed "Watching /" JSON error** - Implemented custom stdio transport to handle non-JSON messages and prevent server crashes
776
- - **25-03-2025 Better code search** ([merged](https://github.com/wonderwhy-er/ClaudeServerCommander/pull/17)) - Enhanced code exploration with context-aware results
777
-
778
- ## Roadmap
779
-
780
- The following features are currently being explored:
781
-
782
- - **Support for WSL** - Windows Subsystem for Linux integration
783
- - **Support for SSH** - Remote server command execution
784
- - **Better file support for formats like CSV/PDF**
785
- - **Terminal sandboxing for Mac/Linux/Windows for better security**
786
- - **File reading modes** - For example, allow reading HTML as plain text or markdown
787
- - **Interactive shell support** - ssh, node/python repl
788
- - **Improve large file reading and writing**
789
-
790
766
  ## Support Desktop Commander
791
767
 
792
768
  <div align="center">
@@ -931,28 +907,11 @@ Please create a [GitHub Issue](https://github.com/wonderwhy-er/DesktopCommanderM
931
907
 
932
908
  ## Data Collection & Privacy
933
909
 
934
- Desktop Commander collects limited anonymous telemetry data to help improve the tool. No personal information, file contents, file paths, or command arguments are collected.
935
-
936
- ### Usage Analytics (Local Only)
937
- - **Local usage statistics** are always collected and stored locally on your machine for functionality and the `get_usage_stats` tool
938
- - Use the `get_usage_stats` tool to view your personal usage patterns, success rates, and performance metrics
939
- - **This data is NOT sent anywhere** - it remains on your computer for your personal insights
940
-
941
- ### Feedback System
942
- - Use the `give_feedback_to_desktop_commander` tool to provide feedback about Desktop Commander
943
- - Opens a browser-based feedback form to send suggestions and feedback to the development team
944
- - Only basic usage statistics (tool call count, days using, platform) are pre-filled to provide context but you can remove them
945
-
946
- ### External Telemetry Opt-Out
947
- External telemetry (sent to analytics services) is enabled by default but can be disabled:
948
-
949
- 1. Open the chat and simply ask:
950
- **"Disable telemetry"**
951
- 2. The chatbot will update your settings automatically.
910
+ Desktop Commander collects limited, pseudonymous telemetry to improve the tool. We do not collect file contents, file paths, or command arguments.
952
911
 
953
- **Note:** This only disables external telemetry. Local usage analytics remain active for tool functionality but is not share externally
912
+ **Opt-out:** Ask Claude to "disable Desktop Commander telemetry" or set `"telemetryEnabled": false` in your config.
954
913
 
955
- For complete details about data collection, please see our [Privacy Policy](https://legal.desktopcommander.app/privacy_desktop_commander_mcp).
914
+ For complete details, see our [Privacy Policy](PRIVACY.md).
956
915
 
957
916
  ## Verifications
958
917
  [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/25ff7a06-58bc-40b8-bd79-ebb715140f1a)
@@ -33,6 +33,7 @@ export declare class FilteredStdioServerTransport extends StdioServerTransport {
33
33
  private sendLogNotification;
34
34
  /**
35
35
  * Public method to send log notifications from anywhere in the application
36
+ * Now properly buffers messages before MCP initialization to avoid breaking stdio protocol
36
37
  */
37
38
  sendLog(level: "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug", message: string, data?: any): void;
38
39
  /**
@@ -233,12 +233,23 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
233
233
  }
234
234
  /**
235
235
  * Public method to send log notifications from anywhere in the application
236
+ * Now properly buffers messages before MCP initialization to avoid breaking stdio protocol
236
237
  */
237
238
  sendLog(level, message, data) {
238
239
  // Skip if notifications are disabled (e.g., for Cline)
239
240
  if (this.disableNotifications) {
240
241
  return;
241
242
  }
243
+ // Buffer messages before initialization to avoid breaking MCP protocol
244
+ // MCP requires client to send first message - server cannot write to stdout before that
245
+ if (!this.isInitialized) {
246
+ this.messageBuffer.push({
247
+ level,
248
+ args: [data ? { message, ...data } : message],
249
+ timestamp: Date.now()
250
+ });
251
+ return;
252
+ }
242
253
  try {
243
254
  const notification = {
244
255
  jsonrpc: "2.0",
@@ -269,6 +280,10 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
269
280
  * Send a progress notification (useful for long-running operations)
270
281
  */
271
282
  sendProgress(token, value, total) {
283
+ // Don't send progress before initialization - would break MCP protocol
284
+ if (!this.isInitialized) {
285
+ return;
286
+ }
272
287
  try {
273
288
  const notification = {
274
289
  jsonrpc: "2.0",
@@ -299,6 +314,10 @@ export class FilteredStdioServerTransport extends StdioServerTransport {
299
314
  * Send a custom notification with any method name
300
315
  */
301
316
  sendCustomNotification(method, params) {
317
+ // Don't send custom notifications before initialization - would break MCP protocol
318
+ if (!this.isInitialized) {
319
+ return;
320
+ }
302
321
  try {
303
322
  const notification = {
304
323
  jsonrpc: "2.0",
@@ -27,3 +27,7 @@ export declare function handleMoveFile(args: unknown): Promise<ServerResult>;
27
27
  * Handle get_file_info command
28
28
  */
29
29
  export declare function handleGetFileInfo(args: unknown): Promise<ServerResult>;
30
+ /**
31
+ * Handle write_pdf command
32
+ */
33
+ export declare function handleWritePdf(args: unknown): Promise<ServerResult>;
@@ -1,8 +1,8 @@
1
- import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo } from '../tools/filesystem.js';
1
+ import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo, writePdf } 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, GetFileInfoArgsSchema } from '../tools/schemas.js';
5
+ import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, WritePdfArgsSchema } from '../tools/schemas.js';
6
6
  /**
7
7
  * Helper function to check if path contains an error
8
8
  */
@@ -32,12 +32,52 @@ export async function handleReadFile(args) {
32
32
  return createErrorResponse('Configuration not available');
33
33
  }
34
34
  const defaultLimit = config.fileReadLineLimit ?? 1000;
35
- // Use the provided limits or defaults
36
- const offset = parsed.offset ?? 0;
37
- const length = parsed.length ?? defaultLimit;
38
- const fileResult = await readFile(parsed.path, parsed.isUrl, offset, length);
39
- if (fileResult.isImage) {
35
+ // Convert sheet parameter: numeric strings become numbers for Excel index access
36
+ let sheetParam = parsed.sheet;
37
+ if (parsed.sheet !== undefined && /^\d+$/.test(parsed.sheet)) {
38
+ sheetParam = parseInt(parsed.sheet, 10);
39
+ }
40
+ const options = {
41
+ isUrl: parsed.isUrl,
42
+ offset: parsed.offset ?? 0,
43
+ length: parsed.length ?? defaultLimit,
44
+ sheet: sheetParam,
45
+ range: parsed.range
46
+ };
47
+ const fileResult = await readFile(parsed.path, options);
48
+ // Handle PDF files
49
+ if (fileResult.metadata?.isPdf) {
50
+ const meta = fileResult.metadata;
51
+ const author = meta?.author ? `, Author: ${meta?.author}` : "";
52
+ const title = meta?.title ? `, Title: ${meta?.title}` : "";
53
+ const pdfContent = fileResult.metadata?.pages?.flatMap((p) => [
54
+ ...(p.images?.map((image) => ({
55
+ type: "image",
56
+ data: image.data,
57
+ mimeType: image.mimeType
58
+ })) ?? []),
59
+ {
60
+ type: "text",
61
+ text: `<!-- Page: ${p.pageNumber} -->\n${p.text}`,
62
+ },
63
+ ]) ?? [];
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: `PDF file: ${parsed.path}${author}${title} (${meta?.totalPages} pages) \n`
69
+ },
70
+ ...pdfContent
71
+ ]
72
+ };
73
+ }
74
+ // Handle image files
75
+ if (fileResult.metadata?.isImage) {
40
76
  // For image files, return as an image content type
77
+ // Content should already be base64-encoded string from handler
78
+ const imageData = typeof fileResult.content === 'string'
79
+ ? fileResult.content
80
+ : fileResult.content.toString('base64');
41
81
  return {
42
82
  content: [
43
83
  {
@@ -46,7 +86,7 @@ export async function handleReadFile(args) {
46
86
  },
47
87
  {
48
88
  type: "image",
49
- data: fileResult.content,
89
+ data: imageData,
50
90
  mimeType: fileResult.mimeType
51
91
  }
52
92
  ],
@@ -54,8 +94,11 @@ export async function handleReadFile(args) {
54
94
  }
55
95
  else {
56
96
  // For all other files, return as text
97
+ const textContent = typeof fileResult.content === 'string'
98
+ ? fileResult.content
99
+ : fileResult.content.toString('utf8');
57
100
  return {
58
- content: [{ type: "text", text: fileResult.content }],
101
+ content: [{ type: "text", text: textContent }],
59
102
  };
60
103
  }
61
104
  };
@@ -78,6 +121,9 @@ export async function handleReadMultipleFiles(args) {
78
121
  if (result.error) {
79
122
  return `${result.path}: Error - ${result.error}`;
80
123
  }
124
+ else if (result.isPdf) {
125
+ return `${result.path}: PDF file with ${result.payload?.pages?.length} pages`;
126
+ }
81
127
  else if (result.mimeType) {
82
128
  return `${result.path}: ${result.mimeType} ${result.isImage ? '(image)' : '(text)'}`;
83
129
  }
@@ -92,7 +138,22 @@ export async function handleReadMultipleFiles(args) {
92
138
  // Add each file content
93
139
  for (const result of fileResults) {
94
140
  if (!result.error && result.content !== undefined) {
95
- if (result.isImage && result.mimeType) {
141
+ if (result.isPdf) {
142
+ result.payload?.pages.forEach((page, i) => {
143
+ page.images.forEach((image, i) => {
144
+ contentItems.push({
145
+ type: "image",
146
+ data: image.data,
147
+ mimeType: image.mimeType
148
+ });
149
+ });
150
+ contentItems.push({
151
+ type: "text",
152
+ text: page.text,
153
+ });
154
+ });
155
+ }
156
+ else if (result.isImage && result.mimeType) {
96
157
  // For image files, add an image content item
97
158
  contentItems.push({
98
159
  type: "image",
@@ -196,6 +257,33 @@ export async function handleMoveFile(args) {
196
257
  return createErrorResponse(errorMessage);
197
258
  }
198
259
  }
260
+ /**
261
+ * Format a value for display, handling objects and arrays
262
+ */
263
+ function formatValue(value, indent = '') {
264
+ if (value === null || value === undefined) {
265
+ return String(value);
266
+ }
267
+ if (Array.isArray(value)) {
268
+ if (value.length === 0)
269
+ return '[]';
270
+ // For arrays of objects (like sheets), format each item
271
+ const items = value.map((item, i) => {
272
+ if (typeof item === 'object' && item !== null) {
273
+ const props = Object.entries(item)
274
+ .map(([k, v]) => `${k}: ${v}`)
275
+ .join(', ');
276
+ return `${indent} [${i}] { ${props} }`;
277
+ }
278
+ return `${indent} [${i}] ${item}`;
279
+ });
280
+ return `\n${items.join('\n')}`;
281
+ }
282
+ if (typeof value === 'object') {
283
+ return JSON.stringify(value);
284
+ }
285
+ return String(value);
286
+ }
199
287
  /**
200
288
  * Handle get_file_info command
201
289
  */
@@ -203,12 +291,14 @@ export async function handleGetFileInfo(args) {
203
291
  try {
204
292
  const parsed = GetFileInfoArgsSchema.parse(args);
205
293
  const info = await getFileInfo(parsed.path);
294
+ // Generic formatting for any file type
295
+ const formattedText = Object.entries(info)
296
+ .map(([key, value]) => `${key}: ${formatValue(value)}`)
297
+ .join('\n');
206
298
  return {
207
299
  content: [{
208
300
  type: "text",
209
- text: Object.entries(info)
210
- .map(([key, value]) => `${key}: ${value}`)
211
- .join('\n')
301
+ text: formattedText
212
302
  }],
213
303
  };
214
304
  }
@@ -217,5 +307,21 @@ export async function handleGetFileInfo(args) {
217
307
  return createErrorResponse(errorMessage);
218
308
  }
219
309
  }
220
- // The listAllowedDirectories function has been removed
221
310
  // Use get_config to retrieve the allowedDirectories configuration
311
+ /**
312
+ * Handle write_pdf command
313
+ */
314
+ export async function handleWritePdf(args) {
315
+ try {
316
+ const parsed = WritePdfArgsSchema.parse(args);
317
+ await writePdf(parsed.path, parsed.content, parsed.outputPath, parsed.options);
318
+ const targetPath = parsed.outputPath || parsed.path;
319
+ return {
320
+ content: [{ type: "text", text: `Successfully wrote PDF to ${targetPath}${parsed.outputPath ? `\nOriginal file: ${parsed.path}` : ''}` }],
321
+ };
322
+ }
323
+ catch (error) {
324
+ const errorMessage = error instanceof Error ? error.message : String(error);
325
+ return createErrorResponse(errorMessage);
326
+ }
327
+ }
@@ -0,0 +1,6 @@
1
+ import { ServerResult } from '../types.js';
2
+ /**
3
+ * Handle execute_node command
4
+ * Executes Node.js code using the same Node runtime as the MCP
5
+ */
6
+ export declare function handleExecuteNode(args: unknown): Promise<ServerResult>;
@@ -0,0 +1,73 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { ExecuteNodeArgsSchema } from '../tools/schemas.js';
6
+ // Get the directory where the MCP is installed (for requiring packages like exceljs)
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const mcpRoot = path.resolve(__dirname, '..', '..');
10
+ /**
11
+ * Handle execute_node command
12
+ * Executes Node.js code using the same Node runtime as the MCP
13
+ */
14
+ export async function handleExecuteNode(args) {
15
+ const parsed = ExecuteNodeArgsSchema.parse(args);
16
+ const { code, timeout_ms } = parsed;
17
+ // Create temp file IN THE MCP DIRECTORY so ES module imports resolve correctly
18
+ // (ES modules resolve packages relative to file location, not NODE_PATH or cwd)
19
+ const tempFile = path.join(mcpRoot, `.mcp-exec-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
20
+ // User code runs directly - imports will resolve from mcpRoot/node_modules
21
+ const wrappedCode = code;
22
+ try {
23
+ await fs.writeFile(tempFile, wrappedCode, 'utf8');
24
+ const result = await new Promise((resolve) => {
25
+ const proc = spawn(process.execPath, [tempFile], {
26
+ cwd: mcpRoot,
27
+ timeout: timeout_ms
28
+ });
29
+ let stdout = '';
30
+ let stderr = '';
31
+ proc.stdout.on('data', (data) => {
32
+ stdout += data.toString();
33
+ });
34
+ proc.stderr.on('data', (data) => {
35
+ stderr += data.toString();
36
+ });
37
+ proc.on('close', (exitCode) => {
38
+ resolve({ stdout, stderr, exitCode: exitCode ?? 1 });
39
+ });
40
+ proc.on('error', (err) => {
41
+ resolve({ stdout, stderr: stderr + '\n' + err.message, exitCode: 1 });
42
+ });
43
+ });
44
+ // Clean up temp file
45
+ await fs.unlink(tempFile).catch(() => { });
46
+ if (result.exitCode !== 0) {
47
+ return {
48
+ content: [{
49
+ type: "text",
50
+ text: `Execution failed (exit code ${result.exitCode}):\n${result.stderr}\n${result.stdout}`
51
+ }],
52
+ isError: true
53
+ };
54
+ }
55
+ return {
56
+ content: [{
57
+ type: "text",
58
+ text: result.stdout || '(no output)'
59
+ }]
60
+ };
61
+ }
62
+ catch (error) {
63
+ // Clean up temp file on error
64
+ await fs.unlink(tempFile).catch(() => { });
65
+ return {
66
+ content: [{
67
+ type: "text",
68
+ text: `Failed to execute Node.js code: ${error instanceof Error ? error.message : String(error)}`
69
+ }],
70
+ isError: true
71
+ };
72
+ }
73
+ }
package/dist/index.js CHANGED
@@ -31,6 +31,11 @@ async function runServer() {
31
31
  }
32
32
  // Set global flag for onboarding control
33
33
  global.disableOnboarding = DISABLE_ONBOARDING;
34
+ // Create transport FIRST so all logging gets properly buffered
35
+ // This must happen before any code that might use logger.*
36
+ const transport = new FilteredStdioServerTransport();
37
+ // Export transport for use throughout the application
38
+ global.mcpTransport = transport;
34
39
  try {
35
40
  deferLog('info', 'Loading configuration...');
36
41
  await configManager.loadConfig();
@@ -47,9 +52,6 @@ async function runServer() {
47
52
  deferLog('warning', 'Continuing with in-memory configuration only');
48
53
  // Continue anyway - we'll use an in-memory config
49
54
  }
50
- const transport = new FilteredStdioServerTransport();
51
- // Export transport for use throughout the application
52
- global.mcpTransport = transport;
53
55
  // Handle uncaught exceptions
54
56
  process.on('uncaughtException', async (error) => {
55
57
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -83,6 +83,24 @@ export interface SearchSessionOptions {
83
83
  runtime: number;
84
84
  totalResults: number;
85
85
  }>;
86
+ /**
87
+ * Search Excel files for content matches
88
+ * Called during content search to include Excel files alongside text files
89
+ * Searches ALL sheets in each Excel file (row-wise for cross-column matching)
90
+ *
91
+ * TODO: Refactor - Extract Excel search logic to separate module (src/utils/search/excel-search.ts)
92
+ * and inject into SearchManager, similar to how file handlers are structured in src/utils/files/
93
+ * This would allow adding other file type searches (PDF, etc.) without bloating search-manager.ts
94
+ */
95
+ private searchExcelFiles;
96
+ /**
97
+ * Find all Excel files in a directory recursively
98
+ */
99
+ private findExcelFiles;
100
+ /**
101
+ * Extract context around a match for display (show surrounding text)
102
+ */
103
+ private getMatchContext;
86
104
  /**
87
105
  * Clean up completed sessions older than specified time
88
106
  * Called automatically by cleanup interval
@@ -101,6 +119,13 @@ export interface SearchSessionOptions {
101
119
  * Detect if pattern contains glob wildcards
102
120
  */
103
121
  private isGlobPattern;
122
+ /**
123
+ * Determine if Excel search should be included based on context
124
+ * Only searches Excel files when:
125
+ * - filePattern explicitly targets Excel files (*.xlsx, *.xls, *.xlsm, *.xlsb)
126
+ * - or the rootPath itself is an Excel file
127
+ */
128
+ private shouldIncludeExcelSearch;
104
129
  private buildRipgrepArgs;
105
130
  private setupProcessHandlers;
106
131
  private processBufferedOutput;