Skip to content

Commit 51926df

Browse files
committed
feat(server.py): add InsertTextFileContentsHandler for inserting content in text files
fix(test_server.py): update tool count in test to reflect new InsertTextFileContentsHandler addition
1 parent ce8774d commit 51926df

File tree

3 files changed

+813
-1
lines changed

3 files changed

+813
-1
lines changed

src/mcp_text_editor/server.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,12 +521,105 @@ async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
521521
raise RuntimeError(f"Error processing request: {str(e)}") from e
522522

523523

524+
class InsertTextFileContentsHandler:
525+
"""Handler for inserting content before or after a specific line in a text file."""
526+
527+
name = "insert_text_file_contents"
528+
description = "Insert content before or after a specific line in a text file. Uses hash-based validation for concurrency control."
529+
530+
def __init__(self):
531+
self.editor = TextEditor()
532+
533+
def get_tool_description(self) -> Tool:
534+
"""Get the tool description."""
535+
return Tool(
536+
name=self.name,
537+
description=self.description,
538+
inputSchema={
539+
"type": "object",
540+
"properties": {
541+
"path": {
542+
"type": "string",
543+
"description": "Path to the text file. File path must be absolute.",
544+
},
545+
"file_hash": {
546+
"type": "string",
547+
"description": "Hash of the file contents for concurrency control",
548+
},
549+
"contents": {
550+
"type": "string",
551+
"description": "Content to insert",
552+
},
553+
"before": {
554+
"type": "integer",
555+
"description": "Line number before which to insert content (mutually exclusive with 'after')",
556+
},
557+
"after": {
558+
"type": "integer",
559+
"description": "Line number after which to insert content (mutually exclusive with 'before')",
560+
},
561+
"encoding": {
562+
"type": "string",
563+
"description": "Text encoding (default: 'utf-8')",
564+
"default": "utf-8",
565+
},
566+
},
567+
"required": ["path", "file_hash", "contents"],
568+
},
569+
)
570+
571+
async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
572+
"""Execute the tool with given arguments."""
573+
try:
574+
if "path" not in arguments:
575+
raise RuntimeError("Missing required argument: path")
576+
if "file_hash" not in arguments:
577+
raise RuntimeError("Missing required argument: file_hash")
578+
if "contents" not in arguments:
579+
raise RuntimeError("Missing required argument: contents")
580+
581+
file_path = arguments["path"]
582+
if not os.path.isabs(file_path):
583+
raise RuntimeError(f"File path must be absolute: {file_path}")
584+
585+
# Check if exactly one of before/after is specified
586+
if ("before" in arguments) == ("after" in arguments):
587+
raise RuntimeError(
588+
"Exactly one of 'before' or 'after' must be specified"
589+
)
590+
591+
line_number = (
592+
arguments.get("before")
593+
if "before" in arguments
594+
else arguments.get("after")
595+
)
596+
is_before = "before" in arguments
597+
encoding = arguments.get("encoding", "utf-8")
598+
599+
result = await self.editor.insert_text_file_contents(
600+
file_path=file_path,
601+
file_hash=arguments["file_hash"],
602+
contents=arguments["contents"],
603+
before=is_before,
604+
line_number=line_number,
605+
encoding=encoding,
606+
)
607+
608+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
609+
610+
except Exception as e:
611+
logger.error(f"Error processing request: {str(e)}")
612+
logger.error(traceback.format_exc())
613+
raise RuntimeError(f"Error processing request: {str(e)}") from e
614+
615+
524616
# Initialize tool handlers
525617
get_contents_handler = GetTextFileContentsHandler()
526618
edit_contents_handler = EditTextFileContentsHandler()
527619
create_file_handler = CreateTextFileHandler()
528620
append_file_handler = AppendTextFileContentsHandler()
529621
delete_contents_handler = DeleteTextFileContentsHandler()
622+
insert_file_handler = InsertTextFileContentsHandler()
530623

531624

532625
@app.list_tools()
@@ -538,6 +631,7 @@ async def list_tools() -> List[Tool]:
538631
create_file_handler.get_tool_description(),
539632
append_file_handler.get_tool_description(),
540633
delete_contents_handler.get_tool_description(),
634+
insert_file_handler.get_tool_description(),
541635
]
542636

543637

@@ -556,6 +650,8 @@ async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
556650
return await append_file_handler.run_tool(arguments)
557651
elif name == delete_contents_handler.name:
558652
return await delete_contents_handler.run_tool(arguments)
653+
elif name == insert_file_handler.name:
654+
return await insert_file_handler.run_tool(arguments)
559655
else:
560656
raise ValueError(f"Unknown tool: {name}")
561657
except ValueError:

0 commit comments

Comments
 (0)