Edit File Lines
STDIOTypeScript-based MCP server for precise line-based text file editing within allowed directories.
TypeScript-based MCP server for precise line-based text file editing within allowed directories.
A TypeScript-based MCP server that provides tools for making precise line-based edits to text files within allowed directories.
edit_file_lines
Make line-based edits to a file using string or regex pattern matching. Each edit can:
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, };
{ "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.
{ "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.
{ "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.
{ "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.
{ "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}`;
approve_edit
Apply changes from a previous dry run of edit_file_lines
. This tool provides a two-step editing process for safety. Here is an example workflow:
{ "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 <button className={`btn-${color} size-${size}`}>Click me</button>; };
State ID: fcbf740a Use this ID with approve_edit to apply the changes.
{ "stateId": "fcbf740a" }
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 <button className={`btn-${color} size-${size}`}>Click me</button>; };
{ "path": "src/components/App.tsx", "lineNumbers": [2], "context": 1 }
Output:
Line 2:
1: // Basic component with props
> 2: const Button = ({ color = "primary", size = "md" }) => {
3: return <button className={`btn-${color} size-${size}`}>Click me</button>;
Note that state IDs expire after a short time for security. Attempting to use an expired or invalid state ID will result in an error:
{ "stateId": "invalid123" }
Output:
Error: Invalid or expired state ID
get_file_lines
Inspect specific lines in a file with optional context lines. This tool is useful for verifying line content before making edits.
{ "path": "src/components/App.tsx", "lineNumbers": [1, 2, 3], "context": 1 }
Output:
Line 1:
> 1: // Basic component with props
2: const Button = ({ color = "blue", size = "md" }) => {
Line 2:
1: // Basic component with props
> 2: const Button = ({ color = "blue", size = "md" }) => {
3: return Click me;
Line 3:
2: const Button = ({ color = "blue", size = "md" }) => {
> 3: return Click me;
4: };
search_file
Search a file for text patterns or regular expressions to find specific line numbers and their surrounding context. This tool is particularly useful for locating the exact lines you want to edit with edit_file_lines
.
Features:
Arguments:
{ path: string; // Path to the file to search pattern: string; // Search pattern (text or regex) type?: "text" | "regex"; // Type of search (default: "text") caseSensitive?: boolean; // Case-sensitive search (default: false) contextLines?: number; // Number of context lines (default: 2, max: 10) maxMatches?: number; // Maximum matches to return (default: 100) wholeWord?: boolean; // Match whole words only (default: false) multiline?: boolean; // Enable multiline regex mode (default: false) }
Example use cases:
{ "path": "src/components/App.tsx", "pattern": "const", "contextLines": 2 }
Output:
Found 6 matches in 0.9ms:
File size: 0.7KB
Match 1: Line 2, Column 1
----------------------------------------
1 | // Basic component with props
> 2 | const Button = ({ color = "blue", size = "md" }) => {
3 | return <button className={`btn-${color} size-${size}`}>Click me</button>;
4 | };
Match 2: Line 7, Column 8
----------------------------------------
5 |
6 | // Component with multiple props and nested structure
> 7 | export const Card = ({
8 | title,
9 | subtitle = "Default subtitle",
Match 3: Line 13, Column 3
----------------------------------------
11 | size = "lg",
12 | }) => {
> 13 | const cardClass = `card-${theme} size-${size}`;
14 |
15 | return (
Match 4: Line 23, Column 4
----------------------------------------
21 | };
22 |
> 23 | // Constants and configurations
24 | const THEME = {
25 | light: { bg: "#ffffff", text: "#000000" },
Match 5: Line 24, Column 1
----------------------------------------
22 |
23 | // Constants and configurations
> 24 | const THEME = {
25 | light: { bg: "#ffffff", text: "#000000" },
26 | dark: { bg: "#000000", text: "#ffffff" },
Match 6: Line 29, Column 1
----------------------------------------
27 | };
28 |
> 29 | const CONFIG = {
30 | apiUrl: "https://api.example.com",
31 | timeout: 5000,
{ "path": "src/components/App.tsx", "pattern": "props", "caseSensitive": true, "wholeWord": true, "contextLines": 1 }
Output:
Found 2 matches in 0.7ms:
File size: 0.7KB
Match 1: Line 1, Column 25
----------------------------------------
> 1 | // Basic component with props
2 | const Button = ({ color = "blue", size = "md" }) => {
Match 2: Line 6, Column 28
----------------------------------------
5 |
> 6 | // Component with multiple props and nested structure
7 | export const Card = ({
{ "path": "src/components/App.tsx", "pattern": "<[A-Z]\\w+\\s", "type": "regex", "contextLines": 1 }
Output:
Found 2 matches in 0.6ms:
File size: 0.7KB
Match 1: Line 3, Column 10
----------------------------------------
2 | const Button = ({ color = "blue", size = "md" }) => {
> 3 | return <button className={`btn-${color} size-${size}`}>Click me</button>;
4 | };
Match 2: Line 16, Column 5
----------------------------------------
15 | return (
> 16 | <div className={cardClass}>
17 | <h2>{title}</h2>
Common workflows:
// First, search for the line { "path": "src/config.ts", "pattern": "API_URL", "wholeWord": true } // Then use the returned line number in edit_file_lines { "p": "src/config.ts", "e": [{ "startLine": 23, // Line number from search result "endLine": 23, "content": "export const API_URL = 'https://new-api.example.com';" }] }
{ "path": "src/components/App.tsx", "pattern": "\\buseMemo\\b", "type": "regex", "contextLines": 2, "maxMatches": 50 }
{ "path": "src/components/App.tsx", "pattern": "className=['\"]([^'\"]+)['\"]", "type": "regex", "contextLines": 1 }
Whitespace Handling
Pattern Matching
strMatch
) are whitespace-normalizedregexMatch
) support look-ahead and look-behindstrMatch
and regexMatch
in the same editBest Practices
Install dependencies:
npm install
Build the server:
npm run build
For development with auto-rebuild:
npm run watch
Run the test suite:
npm run test
Additional testing utilities:
Test the MCP tools directly against sample files:
npm run test:tools
This script:
get_file_lines
edit_file_lines
(dry run)approve_edit
Reset test fixtures to their original state:
npm run reset:fixtures
Use this script to:
The server requires one or more allowed directories to be specified when starting:
node build/index.js <allowed-directory> [additional-directories...]
All file operations will be restricted to these directories for security.
MCP_EDIT_STATE_TTL
: Time-to-live in milliseconds for edit states (default: 60000). Edit states will expire after this duration and must be recreated.To use with Claude Desktop, add the server config:
On MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows: %APPDATA%/Claude/claude_desktop_config.json
{ "mcpServers": { "edit-file-lines": { "command": "node", "args": [ "/path/to/edit-file-lines/build/index.js", "<allowed-directory>" ], "env": { "MCP_EDIT_STATE_TTL": "300000" // Optional: Set custom TTL (in milliseconds) } } } }
The tool provides clear error messages for common issues:
Error: No string match found for "oldValue" on line 5
Error: Invalid regex pattern "([": Unterminated group
Error: Line 5 is affected by multiple edits
Use the Test Tools script to test the MCP tools directly against sample files. The MCP Inspector might help, but it currently does not support handing input that are not string values.