myrmidon / python /src /mcp_server /features /developer /file_operation_tools.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
3.88 kB
# python/src/mcp_server/features/developer/file_operation_tools.py
import logging
import aiofiles
from pydantic import BaseModel, Field
# This is a placeholder for the actual service.
# In a real implementation, this would be injected.
from src.server.services.propose_change_service import ProposeChangeService
logger = logging.getLogger(__name__)
# A mock dependency injector for now
class ToolDependencies:
_propose_change_service: ProposeChangeService | None = None
@classmethod
def get_propose_change_service(cls) -> ProposeChangeService:
if cls._propose_change_service is None:
# In a real app, this would be properly initialized and injected.
# For now, we create a mock/placeholder.
from src.server.dependencies import get_supabase_client
db_client = get_supabase_client()
cls._propose_change_service = ProposeChangeService(db_client=db_client)
return cls._propose_change_service
class ProposeFileChangeTool(BaseModel):
"""
Proposes a change to a file by writing new content to it.
This action does not write the file directly but submits a change proposal
for human review.
"""
file_path: str = Field(..., description="The full path of the file to be modified.")
new_content: str = Field(..., description="The new content to be written to the file.")
async def execute(self) -> str:
"""Submits the file change proposal."""
logger.info(f"Proposing a change to file: {self.file_path}")
try:
service = ToolDependencies.get_propose_change_service()
summary = f"Agent Update: {self.file_path.split('/')[-1]}"
proposal = await service.create_file_proposal(
file_path=self.file_path, new_content=self.new_content, summary=summary
)
return (
f"Successfully proposed a change to file '{self.file_path}'. "
f"Proposal ID: {proposal['id']}. Please await human approval."
)
except Exception as e:
logger.error(f"Failed to propose file change for {self.file_path}: {e}", exc_info=True)
return f"Error: Could not propose change for file '{self.file_path}'. Reason: {e}"
# We can also have a simpler tool name for the AI to use
class WriteFileTool(ProposeFileChangeTool):
"""Alias for ProposeFileChangeTool."""
pass
class ApplyFileModificationTool(BaseModel):
"""
Physically modifies a file by overwriting it with new content.
WARNING: This action is destructive and writes directly to disk.
Used for automated self-healing and code updates.
"""
tool_name: str = "apply_modification"
file_path: str = Field(..., description="The relative path of the file to be modified (from workspace root).")
content: str = Field(..., description="The full new content to be written to the file.")
async def execute(self) -> str:
"""Physically modifies the file by direct write (no Git sandbox)."""
logger.info(f"Applying physical modification to file: {self.file_path}")
try:
import os
# Ensure the directory exists
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
async with aiofiles.open(self.file_path, "w", encoding="utf-8") as f:
await f.write(self.content)
return f"Successfully modified file '{self.file_path}' physically via direct write."
except Exception as e:
logger.error(f"Failed to modify file {self.file_path}: {e}", exc_info=True)
return f"Error: Could not modify file '{self.file_path}'. Reason: {e}"
# To be added to the MCP's tool registry
developer_file_tools: list[type[BaseModel]] = [
ProposeFileChangeTool,
WriteFileTool,
ApplyFileModificationTool,
]