Skip to main content
Glama

MCP Files

by OrionPotter
searchReplace.tsโ€ข4.32 kB
import _ from 'lodash' import { z } from 'zod' import env from '../env.js' import { defineTool } from '../tools.js' import util from '../util.js' const CONTEXT_LINES = 2 const ID = 'search_replace' const searchReplace = defineTool({ id: ID, name: `${env.OVERRIDE_S_R ? '': 'better_'}${ID}`, schema: z.object({ file_path: z.string().min(1).describe('Path to the file (supports relative and absolute paths)'), old_string: z.string().min(1).describe('Exact text to replace (must be unique in file)'), new_string: z.string().describe('Replacement text'), allow_multiple_matches: z.boolean().optional().describe('Allow multiple matches to be replaced. If false, throws error when multiple matches found (default: true)'), }), description: 'Search and replace with intelligent whitespace handling and automation-friendly multiple match resolution. Tries exact match first, falls back to flexible whitespace matching only when no matches found.', isReadOnly: false, isEnabled: env.DEBUG, fromArgs: ([filePath, oldString, newString]) => ({ file_path: filePath, old_string: oldString, new_string: newString, }), handler: (args) => { const { file_path: filePath, old_string: oldString, new_string: newString, allow_multiple_matches: allowMultiple = true } = args const fullPath = util.resolve(filePath) const content = util.readFile(fullPath) // Validate that the replacement would actually change something if (oldString.includes(newString)) { throw new Error(`Redundant replacement: old_string already contains new_string. Old: "${oldString}", New: "${newString}"`) } // Core strategies: exact match + whitespace flexibility (matching Cursor's capability) const patterns = [oldString, createCursorLikePattern(oldString)] // Try each strategy until one works for (const pattern of patterns) { const parts = content.split(pattern) const matches = parts.length - 1 if (!matches) { continue } if (matches > 1 && !allowMultiple) { throw new Error(`Multiple matches found (${matches}) for "${oldString}" in ${filePath}. Set allow_multiple_matches=true to allow replacing first occurrence, or make your search string more specific.`) } const newContent = parts.join(newString) util.writeFile(fullPath, newContent) return formatDiff(content, newContent) } throw new Error(`Could not find the specified text in ${filePath}`) }, }) // Helper function for Cursor-like whitespace handling function createCursorLikePattern(text: string) { // Match Cursor's whitespace handling: flexible with spaces/tabs, preserve structure return new RegExp(_.escapeRegExp(text) .replace(/\s+/g, '\\s+') // Any whitespace sequence becomes flexible .replace(/^\s*/, '\\s*') // Optional leading whitespace .replace(/\s*$/, '\\s*') // Optional trailing whitespace , 'gm') } export default searchReplace function formatDiff(original: string, updated: string): string { const originalLines = original.split('\n') const newLines = updated.split('\n') // Find the range of lines that changed let startLine = -1 let endLine = -1 for (let i = 0; i < Math.max(originalLines.length, newLines.length); i++) { if (originalLines[i] !== newLines[i]) { if (startLine === -1) startLine = i endLine = i } } if (startLine === -1) { throw new Error('Could not generate a diff for changes to the file') } // Generate diff with context const displayStart = Math.max(0, startLine - CONTEXT_LINES) const displayEnd = Math.min(originalLines.length - 1, endLine + CONTEXT_LINES) const diffLines: string[] = [] for (let i = displayStart; i <= displayEnd; i++) { const oldLine = originalLines[i] || '' const newLine = newLines[i] || '' if (i < startLine || i > endLine) { // Context line diffLines.push(` ${oldLine}`) } else if (oldLine !== newLine) { // Changed line if (oldLine) diffLines.push(`- ${oldLine}`) if (newLine) diffLines.push(`+ ${newLine}`) } else { // Unchanged line within change range diffLines.push(` ${oldLine}`) } } return `The following diff was applied to the file: \`\`\`\n${diffLines.join('\n')}\n\`\`\`` }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OrionPotter/flesler-mcp-tools'

If you have feedback or need assistance with the MCP directory API, please join our Discord server