Skip to content

Commit 216723b

Browse files
committed
refactor(server.py): remove legacy format support for file arguments to simplify code and enforce new format
fix(server.py): ensure 'files' argument is mandatory for tool execution to prevent runtime errors refactor(test_server.py): update tests to use new 'files' argument format for consistency and clarity refactor(text_editor.py): streamline file result initialization for better readability test(tests): enhance tests to validate new response structure and error handling for missing arguments
1 parent 79af627 commit 216723b

File tree

4 files changed

+72
-105
lines changed

4 files changed

+72
-105
lines changed

src/mcp_text_editor/server.py

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -76,54 +76,13 @@ def get_tool_description(self) -> Tool:
7676
async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
7777
"""Execute the tool with given arguments."""
7878
try:
79+
# Check for required argument 'files'
7980
if "files" not in arguments:
80-
if "file_path" not in arguments:
81-
raise RuntimeError("Missing required argument: 'files'")
82-
83-
# Legacy format support
84-
file_path = arguments["file_path"]
85-
line_start = arguments.get("line_start", 1)
86-
line_end = arguments.get("line_end")
87-
# Convert to new format with mandatory fields
88-
arguments = {
89-
"files": [
90-
{
91-
"file_path": file_path,
92-
"ranges": [
93-
{
94-
"start": line_start,
95-
"end": line_end if line_end else None,
96-
}
97-
],
98-
}
99-
]
100-
}
101-
# Legacy format request
102-
legacy_format = True
103-
else:
104-
legacy_format = False
81+
raise RuntimeError("Missing required argument: 'files'")
10582

10683
# Handle request
10784
result = await self.editor.read_multiple_ranges(arguments["files"])
108-
109-
# Convert to appropriate format based on request type
110-
if legacy_format:
111-
# Legacy format expects a flat structure
112-
file_path = arguments["files"][0]["file_path"]
113-
file_result = result[file_path]
114-
range_result = file_result["ranges"][0]
115-
response = {
116-
"file_path": file_path,
117-
"contents": range_result["content"],
118-
"line_start": range_result["start_line"],
119-
"line_end": range_result["end_line"],
120-
"file_hash": file_result["file_hash"],
121-
"file_lines": range_result["total_lines"],
122-
"file_size": range_result["content_size"],
123-
}
124-
else:
125-
# New format returns multi-file, multi-range structure
126-
response = result
85+
response = result
12786

12887
return [TextContent(type="text", text=json.dumps(response, indent=2))]
12988

src/mcp_text_editor/text_editor.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,7 @@ async def read_multiple_ranges(
154154
for file_range in ranges:
155155
file_path = file_range["file_path"]
156156
self._validate_file_path(file_path)
157-
result[file_path] = {
158-
"ranges": [],
159-
"file_hash": ""
160-
}
157+
result[file_path] = {"ranges": [], "file_hash": ""}
161158

162159
try:
163160
# Detect the file encoding before reading

tests/test_server.py

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,25 @@ async def test_list_tools():
4444
@pytest.mark.asyncio
4545
async def test_get_contents_handler(test_file):
4646
"""Test GetTextFileContents handler."""
47-
args = {"file_path": test_file, "line_start": 1, "line_end": 3}
47+
args = {"files": [{"file_path": test_file, "ranges": [{"start": 1, "end": 3}]}]}
4848
result = await get_contents_handler.run_tool(args)
4949
assert len(result) == 1
5050
assert isinstance(result[0], TextContent)
5151
content = json.loads(result[0].text)
52-
assert "contents" in content
53-
assert "line_start" in content
54-
assert "line_end" in content
55-
assert "file_hash" in content
56-
assert "file_lines" in content
57-
assert "file_size" in content
52+
assert test_file in content
53+
range_result = content[test_file]["ranges"][0]
54+
assert "content" in range_result
55+
assert "start_line" in range_result
56+
assert "end_line" in range_result
57+
assert "file_hash" in content[test_file]
58+
assert "total_lines" in range_result
59+
assert "content_size" in range_result
5860

5961

6062
@pytest.mark.asyncio
6163
async def test_get_contents_handler_invalid_file(test_file):
6264
"""Test GetTextFileContents handler with invalid file."""
63-
args = {"file_path": "nonexistent.txt"}
65+
args = {"files": [{"file_path": "nonexistent.txt", "ranges": [{"start": 1}]}]}
6466
with pytest.raises(RuntimeError) as exc_info:
6567
await get_contents_handler.run_tool(args)
6668
assert "File not found" in str(exc_info.value)
@@ -70,10 +72,10 @@ async def test_get_contents_handler_invalid_file(test_file):
7072
async def test_edit_contents_handler(test_file):
7173
"""Test EditTextFileContents handler."""
7274
# First, get the current content and hash
73-
get_args = {"file_path": test_file}
75+
get_args = {"files": [{"file_path": test_file, "ranges": [{"start": 1}]}]}
7476
get_result = await get_contents_handler.run_tool(get_args)
7577
content_info = json.loads(get_result[0].text)
76-
initial_hash = content_info["file_hash"]
78+
initial_hash = content_info[test_file]["file_hash"]
7779

7880
# Get range hash for the target lines
7981
get_range_args = {
@@ -82,7 +84,7 @@ async def test_edit_contents_handler(test_file):
8284
range_result = json.loads(
8385
(await get_contents_handler.run_tool(get_range_args))[0].text
8486
)
85-
range_hash = range_result[test_file][0]["range_hash"]
87+
range_hash = range_result[test_file]["ranges"][0]["range_hash"]
8688

8789
# Create edit operation
8890
edit_args = {
@@ -113,17 +115,19 @@ async def test_edit_contents_handler(test_file):
113115
@pytest.mark.asyncio
114116
async def test_call_tool_get_contents(test_file):
115117
"""Test call_tool with GetTextFileContents."""
116-
args = {"file_path": test_file, "line_start": 1, "line_end": 3}
118+
args = {"files": [{"file_path": test_file, "ranges": [{"start": 1, "end": 3}]}]}
117119
result = await call_tool("get_text_file_contents", args)
118120
assert len(result) == 1
119121
assert isinstance(result[0], TextContent)
120122
content = json.loads(result[0].text)
121-
assert "contents" in content
122-
assert "line_start" in content
123-
assert "line_end" in content
124-
assert "file_hash" in content
125-
assert "file_lines" in content
126-
assert "file_size" in content
123+
assert test_file in content
124+
range_result = content[test_file]["ranges"][0]
125+
assert "content" in range_result
126+
assert "start_line" in range_result
127+
assert "end_line" in range_result
128+
assert "file_hash" in content[test_file]
129+
assert "total_lines" in range_result
130+
assert "content_size" in range_result
127131

128132

129133
@pytest.mark.asyncio
@@ -144,7 +148,10 @@ async def test_call_tool_error_handling():
144148

145149
# Test with invalid file path
146150
with pytest.raises(RuntimeError) as exc_info:
147-
await call_tool("get_text_file_contents", {"file_path": "nonexistent.txt"})
151+
await call_tool(
152+
"get_text_file_contents",
153+
{"files": [{"file_path": "nonexistent.txt", "ranges": [{"start": 1}]}]},
154+
)
148155
assert "File not found" in str(exc_info.value)
149156

150157

@@ -162,9 +169,10 @@ async def test_edit_contents_handler_multiple_files(tmp_path):
162169
file_operations = []
163170
for file_path in test_files:
164171
# Get file hash
165-
get_result = await get_contents_handler.run_tool({"file_path": file_path})
172+
get_args = {"files": [{"file_path": file_path, "ranges": [{"start": 1}]}]}
173+
get_result = await get_contents_handler.run_tool(get_args)
166174
content_info = json.loads(get_result[0].text)
167-
file_hash = content_info["file_hash"]
175+
file_hash = content_info[file_path]["file_hash"]
168176

169177
# Get range hash
170178
get_range_args = {
@@ -178,7 +186,7 @@ async def test_edit_contents_handler_multiple_files(tmp_path):
178186
range_result = json.loads(
179187
(await get_contents_handler.run_tool(get_range_args))[0].text
180188
)
181-
range_hash = range_result[file_path][0]["range_hash"]
189+
range_hash = range_result[file_path]["ranges"][0]["range_hash"]
182190

183191
# Create operation for this file
184192
file_operations.append(
@@ -189,7 +197,7 @@ async def test_edit_contents_handler_multiple_files(tmp_path):
189197
{
190198
"line_start": 2,
191199
"line_end": 2,
192-
"contents": f"Modified Line 2 in file {file_path}\n",
200+
"contents": "Modified Line 2\n",
193201
"range_hash": range_hash,
194202
}
195203
],
@@ -218,9 +226,10 @@ async def test_edit_contents_handler_partial_failure(tmp_path):
218226
valid_path = str(valid_file)
219227

220228
# Get hash for valid file
221-
get_result = await get_contents_handler.run_tool({"file_path": valid_path})
229+
get_args = {"files": [{"file_path": valid_path, "ranges": [{"start": 1}]}]}
230+
get_result = await get_contents_handler.run_tool(get_args)
222231
content_info = json.loads(get_result[0].text)
223-
valid_hash = content_info["file_hash"]
232+
valid_hash = content_info[valid_path]["file_hash"]
224233

225234
# Get range hash for the target lines
226235
get_range_args = {
@@ -229,7 +238,7 @@ async def test_edit_contents_handler_partial_failure(tmp_path):
229238
range_result = json.loads(
230239
(await get_contents_handler.run_tool(get_range_args))[0].text
231240
)
232-
valid_range_hash = range_result[valid_path][0]["range_hash"]
241+
valid_range_hash = range_result[valid_path]["ranges"][0]["range_hash"]
233242

234243
# Create edit operations for both valid and invalid files
235244
edit_args = {
@@ -270,14 +279,11 @@ async def test_edit_contents_handler_partial_failure(tmp_path):
270279

271280

272281
@pytest.mark.asyncio
273-
async def test_edit_contents_handler_empty_args():
274-
"""Test EditTextFileContents handler with empty files."""
275-
edit_args = {"files": []}
276-
result = await edit_contents_handler.run_tool(edit_args)
277-
assert len(result) == 1
278-
edit_results = json.loads(result[0].text)
279-
assert isinstance(edit_results, dict)
280-
assert len(edit_results) == 0
282+
async def test_get_contents_handler_missing_args():
283+
"""Test GetTextFileContents handler with missing arguments."""
284+
with pytest.raises(RuntimeError) as exc_info:
285+
await get_contents_handler.run_tool({})
286+
assert "Missing required argument: 'files'" in str(exc_info.value)
281287

282288

283289
@pytest.mark.asyncio
@@ -306,7 +312,7 @@ async def test_get_contents_handler_legacy_missing_args():
306312
"""Test GetTextFileContents handler with legacy single file request missing arguments."""
307313
with pytest.raises(RuntimeError) as exc_info:
308314
await get_contents_handler.run_tool({})
309-
assert "Missing required argument: files" in str(exc_info.value)
315+
assert "Missing required argument: 'files'" in str(exc_info.value)
310316

311317

312318
@pytest.mark.asyncio

0 commit comments

Comments
 (0)