mcp-edit-file-lines
by: oakenai
MCP Server to make line-based edits to a file.
📌Overview
Purpose: To offer a robust TypeScript-based MCP server for making precise line-based edits to text files in designated directories.
Overview: The framework enables developers to perform line-based modifications on text files through multiple sophisticated tools. It provides the ability to replace entire lines or specific text within lines, using both string matches and regex pattern matching. Dry run capabilities allow for previewing changes before applying them, ensuring accuracy and safety.
Key Features:
-
Line-Based Editing (
edit_file_lines
): Allows for precise modifications using string or regex pattern matching, enabling replacements of full lines or specific text in a way that preserves original formatting. -
Approval Process (
approve_edit
): Implements a two-step process where changes can be reviewed before final application, enhancing safety and control over modifications. -
File Inspection (
get_file_lines
): Enables users to view specific lines within files along with surrounding context, helping in verification before edits are made. -
Search Functionality (
search_file
): Facilitates finding text or regex patterns within files, returning relevant line numbers and contextual content, which aids in targeting specific points for editing.
Edit File Lines MCP Server
A TypeScript-based MCP server providing tools to make precise line-based edits to text files within allowed directories.
Features
Main Editing Tool
edit_file_lines
Make line-based edits to a file using string or regex pattern matching. Each edit can:
- Replace entire lines
- Replace specific text matches while preserving line formatting
- Use regex patterns for complex matches
- Handle multiple lines and edits
- Preview changes with dry run mode
Example file (src/components/App.tsx
):
// Basic component with props
const Button = ({ color = "blue", size = "md" }) => {
return <button className={`btn-${color} size-${size}`}>Click me</button>;
};
// Component with multiple props and nested structure
export const Card = ({
title,
subtitle = "Default subtitle",
theme = "light",
size = "lg",
}) => {
const cardClass = `card-${theme} size-${size}`;
return (
<div className={cardClass}>
<h2>{title}</h2>
<p>{subtitle}</p>
</div>
);
};
// Constants and configurations
const THEME = {
light: { bg: "#ffffff", text: "#000000" },
dark: { bg: "#000000", text: "#ffffff" },
};
const CONFIG = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
Example Use Cases
- Simple String Replacement
{
"p": "src/components/App.tsx",
"e": [{
"startLine": 2,
"endLine": 2,
"content": "primary",
"strMatch": "blue"
}],
"dryRun": true
}
Output:
Index: src/components/App.tsx
===================================================================
--- src/components/App.tsx original
+++ src/components/App.tsx modified
@@ -1,6 +1,6 @@
// Basic component with props
-const Button = ({ color = "blue", size = "md" }) => {
+const Button = ({ color = "primary", size = "md" }) => {
return Click me;
};
// Component with multiple props and nested structure
State ID: fcbf740a
Use this ID with approve_edit to apply the changes.
- Multi-line Content with Preserved Structure
{
"p": "src/components/App.tsx",
"e": [{
"startLine": 16,
"endLine": 19,
"content": " <div className={cardClass}>\n <h2 className=\"title\">{title}</h2>\n <p className=\"subtitle\">{subtitle}</p>\n </div>",
"regexMatch": "<div[^>]*>[\\s\\S]*?</div>"
}],
"dryRun": true
}
Output:
Index: src/components/App.tsx
===================================================================
--- src/components/App.tsx original
+++ src/components/App.tsx modified
@@ -13,10 +13,10 @@
const cardClass = `card-${theme} size-${size}`;
return (
<div className={cardClass}>
- <h2>{title}</h2>
- <p>{subtitle}</p>
+ <h2 className="title">{title}</h2>
+ <p className="subtitle">{subtitle}</p>
</div>
);
};
State ID: f2ce973f
Use this ID with approve_edit to apply the changes.
- Complex JSX Structure Modification
{
"p": "src/components/App.tsx",
"e": [{
"startLine": 7,
"endLine": 12,
"content": "export const Card = ({\n title,\n subtitle = \"New default\",\n theme = \"modern\",\n size = \"responsive\"\n}) => {",
"regexMatch": "export const Card[\\s\\S]*?\\) => \\{"
}],
"dryRun": true
}
Output:
Index: src/components/App.tsx
===================================================================
--- src/components/App.tsx original
+++ src/components/App.tsx modified
@@ -5,11 +5,11 @@
// Component with multiple props and nested structure
export const Card = ({
title,
- subtitle = "Default subtitle",
- theme = "light",
- size = "lg",
+ subtitle = "New default",
+ theme = "modern",
+ size = "responsive"
}) => {
const cardClass = `card-${theme} size-${size}`;
return (
State ID: f1f1d27b
Use this ID with approve_edit to apply the changes.
- Configuration Update with Whitespace Preservation
{
"p": "src/components/App.tsx",
"e": [{
"startLine": 29,
"endLine": 32,
"content": "const CONFIG = {\n baseUrl: \"https://api.newexample.com\",\n timeout: 10000,\n maxRetries: 5",
"regexMatch": "const CONFIG[\\s\\S]*?retries: \\d+"
}],
"dryRun": true
}
Output:
Index: src/components/App.tsx
===================================================================
--- src/components/App.tsx original
+++ src/components/App.tsx modified
@@ -26,8 +26,8 @@
dark: { bg: "#000000", text: "#ffffff" },
};
const CONFIG = {
- apiUrl: "https://api.example.com",
- timeout: 5000,
- retries: 3,
+ baseUrl: "https://api.newexample.com",
+ timeout: 10000,
+ maxRetries: 5
};
State ID: 20e93c34
Use this ID with approve_edit to apply the changes.
- Flexible Whitespace Matching
{
"p": "src/components/App.tsx",
"e": [{
"startLine": 9,
"endLine": 9,
"content": "description",
"strMatch": "subtitle = \"Default subtitle\"" // Extra spaces are handled
}],
"dryRun": true
}
Output:
Index: src/components/App.tsx
===================================================================
--- src/components/App.tsx original
+++ src/components/App.tsx modified
@@ -5,9 +5,9 @@
// Component with multiple props and nested structure
export const Card = ({
title,
- subtitle = "Default subtitle",
+ description
theme = "light",
size = "lg",
}) => {
const cardClass = `card-${theme} size-${size}`;
Additional Tools
approve_edit
Apply changes from a previous dry run of edit_file_lines
. This two-step editing process helps ensure safety.
Example workflow:
-
Make a dry run edit with
edit_file_lines
(see example above). -
Approve the changes using the state ID provided:
{
"stateId": "fcbf740a"
}
- Verify the changes by inspecting the affected lines.
Note: State IDs expire after a short time for security.
get_file_lines
Inspect specific lines in a file with optional surrounding context to verify content before editing.
Example:
{
"path": "src/components/App.tsx",
"lineNumbers": [1, 2, 3],
"context": 1
}
search_file
Search a file for text patterns or regular expressions to find specific line numbers and their surrounding context.
Features:
- Simple text and regex search
- Case sensitivity options
- Whole word matching
- Configurable context lines
- Returns match positions and context
Arguments example:
{
path: string; // File path
pattern: string; // Search pattern (text or regex)
type?: "text" | "regex"; // Default is "text"
caseSensitive?: boolean; // Default is false
contextLines?: number; // Default is 2
maxMatches?: number; // Default is 100
wholeWord?: boolean; // Default is false
multiline?: boolean; // Default is false
}
Important Notes
-
Whitespace Handling
- Whitespace is intelligently handled in matches and replacements
- Original indentation is preserved
- Multiple spaces are normalized for matching
-
Pattern Matching
- String matches (
strMatch
) are whitespace-normalized - Regex patterns (
regexMatch
) support advanced constructs including look-ahead and look-behind - Cannot use both
strMatch
andregexMatch
in the same edit - Overlapping regex patterns are prevented
- String matches (
-
Best Practices
- Always perform a dry run to verify changes
- Review diff output before approval
- Keep edits focused and atomic
- Use appropriate pattern matching based on the use case
Development
Install dependencies:
npm install
Build the server:
npm run build
For development with auto-rebuild:
npm run watch
Testing
Run the test suite:
npm run test
Additional utilities:
- Test Tools Script — test MCP tools directly against sample files:
npm run test:tools
- Reset Fixtures Script — reset test fixtures to a known state:
npm run reset:fixtures
Usage
Start the server specifying allowed directories; all file ops are restricted to these for security:
node build/index.js <allowed-directory> [additional-directories...]
Environment Variables
MCP_EDIT_STATE_TTL
: TTL in milliseconds for edit states (default 60000 ms). Edit states expire after this duration.
Installation
To use with Claude Desktop, configure the MCP server by adding:
MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%/Claude/claude_desktop_config.json
Example config:
{
"mcpServers": {
"edit-file-lines": {
"command": "node",
"args": [
"/path/to/edit-file-lines/build/index.js",
"<allowed-directory>"
],
"env": {
"MCP_EDIT_STATE_TTL": "300000" // Optional TTL in milliseconds
}
}
}
}
Error Handling
Common error messages:
- Match Not Found
Error: No string match found for "oldValue" on line 5
- Invalid Regex
Error: Invalid regex pattern "([": Unterminated group
- Multiple Edits on Same Line
Error: Line 5 is affected by multiple edits
Security Considerations
- Operations restricted to explicitly allowed directories
- Symlink validation to prevent directory escapes
- Parent directory traversal blocked
- Path normalization for consistent security checks
- Invalid line and character positions rejected
- Line ending normalization for cross-platform consistency
- Edit states expire after 60 seconds
- Approvals require exact match of file path and edits
Debugging
Use the Test Tools script to test MCP tools on sample files. The MCP Inspector may also help but currently does not support non-string input.