Skip to content

Commit 6db7be4

Browse files
committed
2 parents a20cf7c + 32cba5b commit 6db7be4

15 files changed

+631
-136
lines changed

CHANGELOG.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## [1.1.0] - 2024-12-23
4+
5+
### Added
6+
7+
- New text file manipulation operations:
8+
- `insert_text_file_contents`: Insert content at specific positions
9+
- `create_text_file`: Create new text files
10+
- `append_text_file_contents`: Append content to existing files
11+
- `delete_text_file_contents`: Delete specified ranges of text
12+
- `patch_text_file_contents`: Apply multiple patches to text files
13+
- Enhanced error messages with useful suggestions for alternative editing methods
14+
15+
### Changed
16+
17+
- Unified parameter naming: renamed 'path' to 'file_path' across all APIs
18+
- Improved handler organization by moving them to separate directory
19+
- Made 'end' parameter required when not in append mode
20+
- Enhanced validation for required parameters and file path checks
21+
- Removed 'edit_text_file_contents' tool in favor of more specific operations
22+
- Improved JSON serialization for handler responses
23+
24+
### Fixed
25+
26+
- Delete operation now uses dedicated deletion method instead of empty content replacement
27+
- Improved range validation in delete operations
28+
- Enhanced error handling across all operations
29+
- Removed file hash from error responses for better clarity
30+
- Fixed concurrency control with proper hash validation
31+
332
## [1.0.2] - 2024-12-22
433

534
### Fixed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# MCP Text Editor Server
22

33
[![codecov](https://codecov.io/gh/tumf/mcp-text-editor/branch/main/graph/badge.svg?token=52D51U0ZUR)](https://codecov.io/gh/tumf/mcp-text-editor)
4+
[![smithery badge](https://smithery.ai/badge/mcp-text-editor)](https://smithery.ai/server/mcp-text-editor)
45

56
A Model Context Protocol (MCP) server that provides line-oriented text file editing capabilities through a standardized API. Optimized for LLM tools with efficient partial file access to minimize token usage.
67

@@ -84,6 +85,15 @@ uv pip install -e ".[dev]"
8485

8586
## Installation
8687

88+
### Installing via Smithery
89+
90+
To install Text Editor Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-text-editor):
91+
92+
```bash
93+
npx -y @smithery/cli install mcp-text-editor --client claude
94+
```
95+
96+
### Manual Installation
8797
```bash
8898
pip install -e .
8999
```

src/mcp_text_editor/handlers/append_text_file_contents.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get_tool_description(self) -> Tool:
2727
inputSchema={
2828
"type": "object",
2929
"properties": {
30-
"path": {
30+
"file_path": {
3131
"type": "string",
3232
"description": "Path to the text file. File path must be absolute.",
3333
},
@@ -45,7 +45,7 @@ def get_tool_description(self) -> Tool:
4545
"default": "utf-8",
4646
},
4747
},
48-
"required": ["path", "contents", "file_hash"],
48+
"required": ["file_path", "contents", "file_hash"],
4949
},
5050
)
5151

@@ -87,7 +87,7 @@ async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
8787
# Create patch for append operation
8888
result = await self.editor.edit_file_contents(
8989
file_path,
90-
expected_hash=arguments["file_hash"],
90+
expected_file_hash=arguments["file_hash"],
9191
patches=[
9292
{
9393
"start": total_lines + 1,

src/mcp_text_editor/handlers/create_text_file.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def get_tool_description(self) -> Tool:
4343
"default": "utf-8",
4444
},
4545
},
46-
"required": ["path", "contents"],
46+
"required": ["file_path", "contents"],
4747
},
4848
)
4949

@@ -68,7 +68,7 @@ async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
6868
# Create new file using edit_file_contents with empty expected_hash
6969
result = await self.editor.edit_file_contents(
7070
file_path,
71-
expected_hash="", # Empty hash for new file
71+
expected_file_hash="", # Empty hash for new file
7272
patches=[
7373
{
7474
"start": 1,

src/mcp_text_editor/handlers/delete_text_file_contents.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from mcp.types import TextContent, Tool
1010

11+
from ..models import DeleteTextFileContentsRequest, FileRange
1112
from .base import BaseHandler
1213

1314
logger = logging.getLogger("mcp-text-editor")
@@ -88,26 +89,33 @@ async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
8889

8990
encoding = arguments.get("encoding", "utf-8")
9091

91-
# Create patches for deletion (replacing content with empty string)
92-
patches = [
93-
{
94-
"start": r["start"],
95-
"end": r["end"],
96-
"contents": "",
97-
"range_hash": r["range_hash"],
98-
}
92+
# Create file ranges for deletion
93+
ranges = [
94+
FileRange(
95+
start=r["start"], end=r.get("end"), range_hash=r["range_hash"]
96+
)
9997
for r in arguments["ranges"]
10098
]
10199

102-
# Use the existing edit_file_contents method
103-
result = await self.editor.edit_file_contents(
104-
file_path,
105-
expected_hash=arguments["file_hash"],
106-
patches=patches,
100+
# Create delete request
101+
request = DeleteTextFileContentsRequest(
102+
file_path=file_path,
103+
file_hash=arguments["file_hash"],
104+
ranges=ranges,
107105
encoding=encoding,
108106
)
109107

110-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
108+
# Execute deletion using the service
109+
result_dict = self.editor.service.delete_text_file_contents(request)
110+
111+
# Convert EditResults to dictionaries
112+
serializable_result = {}
113+
for file_path, edit_result in result_dict.items():
114+
serializable_result[file_path] = edit_result.to_dict()
115+
116+
return [
117+
TextContent(type="text", text=json.dumps(serializable_result, indent=2))
118+
]
111119

112120
except Exception as e:
113121
logger.error(f"Error processing request: {str(e)}")

src/mcp_text_editor/handlers/patch_text_file_contents.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_tool_description(self) -> Tool:
4646
"description": "Starting line number (1-based).it should match the range hash.",
4747
},
4848
"end": {
49-
"type": ["integer", "null"],
49+
"type": "integer",
5050
"description": "Ending line number (null for end of file).it should match the range hash.",
5151
},
5252
"contents": {
@@ -58,7 +58,7 @@ def get_tool_description(self) -> Tool:
5858
"description": "Hash of the content being replaced. it should get from get_text_file_contents tool with the same start and end.",
5959
},
6060
},
61-
"required": ["start", "contents", "range_hash"],
61+
"required": ["start", "end", "contents", "range_hash"],
6262
},
6363
},
6464
"encoding": {
@@ -94,7 +94,7 @@ async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
9494
# Apply patches using editor.edit_file_contents
9595
result = await self.editor.edit_file_contents(
9696
file_path=file_path,
97-
expected_hash=arguments["file_hash"],
97+
expected_file_hash=arguments["file_hash"],
9898
patches=arguments["patches"],
9999
encoding=encoding,
100100
)

src/mcp_text_editor/models.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,17 @@ class EditPatch(BaseModel):
3434
)
3535

3636
@model_validator(mode="after")
37-
def validate_end_line(self) -> "EditPatch":
38-
"""Validate that end line is present when not in append mode."""
37+
def validate_range_hash(self) -> "EditPatch":
38+
"""Validate that range_hash is set and handle end field validation."""
3939
# range_hash must be explicitly set
4040
if self.range_hash is None:
4141
raise ValueError("range_hash is required")
4242

43-
# For modifications (non-empty range_hash), end is required
44-
if self.range_hash != "" and self.end is None:
45-
raise ValueError("end line is required when not in append mode")
43+
# For safety, convert None to the special range hash value
44+
if self.end is None and self.range_hash != "":
45+
# Special case: patch with end=None is allowed
46+
pass
47+
4648
return self
4749

4850

0 commit comments

Comments
 (0)