@@ -41,6 +41,7 @@ import {
4141 SetConfigValueArgsSchema ,
4242 ListProcessesArgsSchema ,
4343 EditBlockArgsSchema ,
44+ ReplaceLinesArgsSchema ,
4445 GetUsageStatsArgsSchema ,
4546 GiveFeedbackArgsSchema ,
4647 StartSearchArgsSchema ,
@@ -57,6 +58,7 @@ import { giveFeedbackToDesktopCommander } from './tools/feedback.js';
5758import { getPrompts } from './tools/prompts.js' ;
5859import { trackToolCall } from './utils/trackTools.js' ;
5960import { usageTracker } from './utils/usageTracker.js' ;
61+ import { configManager } from './config-manager.js' ;
6062import { processDockerPrompt } from './utils/dockerPrompt.js' ;
6163import { toolHistory } from './utils/toolHistory.js' ;
6264import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js' ;
@@ -204,16 +206,21 @@ export { currentClient };
204206deferLog ( 'info' , 'Setting up request handlers...' ) ;
205207
206208/**
207- * Check if a tool should be included based on current client
209+ * Check if a tool should be included based on current client and config
208210 */
209- function shouldIncludeTool ( toolName : string ) : boolean {
211+ async function shouldIncludeTool ( toolName : string ) : Promise < boolean > {
210212 // Exclude give_feedback_to_desktop_commander for desktop-commander client
211213 if ( toolName === 'give_feedback_to_desktop_commander' && currentClient ?. name === 'desktop-commander' ) {
212214 return false ;
213215 }
214216
215- // Add more conditional tool logic here as needed
216- // Example: if (toolName === 'some_tool' && currentClient?.name === 'some_client') return false;
217+ // Edit mode controls which editing tools are registered
218+ if ( toolName === 'edit_block' || toolName === 'replace_lines' ) {
219+ const editMode = ( await configManager . getValue ( 'editMode' ) ) || 'string-replace' ;
220+ if ( editMode === 'string-replace' && toolName === 'replace_lines' ) return false ;
221+ if ( editMode === 'line-replace' && toolName === 'edit_block' ) return false ;
222+ // 'both' keeps both tools
223+ }
217224
218225 return true ;
219226}
@@ -788,6 +795,48 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
788795 openWorldHint : false ,
789796 } ,
790797 } ,
798+ {
799+ name : "replace_lines" ,
800+ description : `
801+ Replace lines in a text file by line number range.
802+
803+ Token-efficient alternative to edit_block when you already know line numbers
804+ from a previous read_file call. No need to send old_string - just specify
805+ which lines to replace.
806+
807+ PARAMETERS:
808+ - path: File path
809+ - startLine: First line to replace (1-based, from read_file output)
810+ - endLine: Last line to replace (1-based, inclusive)
811+ - newContent: Replacement text (can be more or fewer lines than removed)
812+
813+ EXAMPLES:
814+ - Replace lines 10-15: startLine=10, endLine=15, newContent="new code here"
815+ - Delete lines 5-8: startLine=5, endLine=8, newContent=""
816+ - Insert after line 3: Use edit_block or write_file instead
817+
818+ WARNING - LINE NUMBER SHIFTING:
819+ After every replace_lines call where newContent has a different number of
820+ lines than the replaced range, ALL subsequent line numbers shift.
821+ ALWAYS re-read the file before making another replace_lines call on the
822+ same file. The response includes context lines to verify correctness.
823+
824+ IMPORTANT: Line numbers must match the read_file output exactly.
825+ If the file has been modified since the last read_file, re-read first.
826+
827+ NOTE: This tool is only available when editMode is set to "line-replace" or "both"
828+ in the configuration. Default editMode is "string-replace" (edit_block only).
829+
830+ ${ PATH_GUIDANCE }
831+ ${ CMD_PREFIX_DESCRIPTION } ` ,
832+ inputSchema : zodToJsonSchema ( ReplaceLinesArgsSchema ) ,
833+ annotations : {
834+ title : "Replace Lines" ,
835+ readOnlyHint : false ,
836+ destructiveHint : true ,
837+ openWorldHint : false ,
838+ } ,
839+ } ,
791840
792841 // Terminal tools
793842 {
@@ -1147,8 +1196,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
11471196 }
11481197 ] ;
11491198
1150- // Filter tools based on current client
1151- const filteredTools = allTools . filter ( tool => shouldIncludeTool ( tool . name ) ) ;
1199+ // Filter tools based on current client and config
1200+ const includeResults = await Promise . all ( allTools . map ( tool => shouldIncludeTool ( tool . name ) ) ) ;
1201+ const filteredTools = allTools . filter ( ( _ , i ) => includeResults [ i ] ) ;
11521202
11531203 // logToStderr('debug', `Returning ${filteredTools.length} tools (filtered from ${allTools.length} total) for client: ${currentClient?.name || 'unknown'}`);
11541204
@@ -1415,6 +1465,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest)
14151465 result = await handlers . handleEditBlock ( args ) ;
14161466 break ;
14171467
1468+ case "replace_lines" :
1469+ result = await handlers . handleReplaceLines ( args ) ;
1470+ break ;
1471+
14181472 default :
14191473 capture ( 'server_unknown_tool' , { name } ) ;
14201474 result = {
0 commit comments