UNPKG

markdown-editor-mcp

Version:

MCP server for markdown editing and management

89 lines (88 loc) 4.01 kB
import { searchTextInFiles } from '../tools/search.js'; import { SearchCodeArgsSchema } from '../tools/schemas.js'; import { handleEditBlock } from '../tools/edit.js'; import { capture } from '../utils/capture.js'; import { withTimeout } from '../utils/withTimeout.js'; /** * Handle edit_block command * Uses the enhanced implementation with multiple occurrence support and fuzzy matching */ export { handleEditBlock }; /** * Handle search_code command */ export async function handleSearchCode(args) { const parsed = SearchCodeArgsSchema.parse(args); const timeoutMs = parsed.timeoutMs || 30000; // 30 seconds default // Limit maxResults to prevent overwhelming responses const safeMaxResults = parsed.maxResults ? Math.min(parsed.maxResults, 5000) : 2000; // Default to 2000 instead of 1000 // Apply timeout at the handler level const searchOperation = async () => { return await searchTextInFiles({ rootPath: parsed.path, pattern: parsed.pattern, filePattern: parsed.filePattern, ignoreCase: parsed.ignoreCase, maxResults: safeMaxResults, includeHidden: parsed.includeHidden, contextLines: parsed.contextLines, // Don't pass timeoutMs down to the implementation }); }; // Use withTimeout at the handler level const results = await withTimeout(searchOperation(), timeoutMs, 'Code search operation', [] // Empty array as default on timeout ); // If timeout occurred, try to terminate the ripgrep process if (results.length === 0 && globalThis.currentSearchProcess) { try { console.log(`Terminating timed out search process (PID: ${globalThis.currentSearchProcess.pid})`); globalThis.currentSearchProcess.kill(); delete globalThis.currentSearchProcess; } catch (error) { capture('server_request_error', { error: 'Error terminating search process' }); } } if (results.length === 0) { if (timeoutMs > 0) { return { content: [{ type: "text", text: `No matches found or search timed out after ${timeoutMs}ms.` }], }; } return { content: [{ type: "text", text: "No matches found" }], }; } // Format the results in a VS Code-like format with early truncation let currentFile = ""; let formattedResults = ""; const MAX_RESPONSE_SIZE = 900000; // 900KB limit - well below the 1MB API limit let resultsProcessed = 0; let totalResults = results.length; for (const result of results) { // Check if adding this result would exceed our limit const newFileHeader = result.file !== currentFile ? `\n${result.file}:\n` : ''; const newLine = ` ${result.line}: ${result.match}\n`; const potentialAddition = newFileHeader + newLine; // If adding this would exceed the limit, truncate here if (formattedResults.length + potentialAddition.length > MAX_RESPONSE_SIZE) { const remainingResults = totalResults - resultsProcessed; const avgResultLength = formattedResults.length / Math.max(resultsProcessed, 1); const estimatedRemainingChars = remainingResults * avgResultLength; 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.]`; formattedResults += truncationMessage; break; } if (result.file !== currentFile) { formattedResults += newFileHeader; currentFile = result.file; } formattedResults += newLine; resultsProcessed++; } return { content: [{ type: "text", text: formattedResults.trim() }], }; }