feat(mcp): Add replace_ast_node, semantic_code_search, read_directory_architecture, nvim_send_to_terminal

This commit is contained in:
Riz Ashraf committed 2026-09-30 08:37:26 +01:00
1 parent d4635ab6dc
commit 9787af0f27
5 files changed
+259

No files matched your search

+90
View File
@@ -104,3 +104,93 @@ impl McpTool for ReadFileSkeletonHandler {
Ok(result)
}
}
use crate::tools::ReplaceAstNodeTool;
pub struct ReplaceAstNodeHandler;
#[async_trait]
impl McpTool for ReplaceAstNodeHandler {
fn name(&self) -> &'static str {
"replace_ast_node"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ReplaceAstNodeTool>(
"replace_ast_node",
"Replace a specific AST node (e.g., function, struct) entirely using tree-sitter for robust structural editing.",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: ReplaceAstNodeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let file_path = tool_args.file_path.clone();
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
let code = std::fs::read_to_string(&file_path)
.map_err(|e| format!("Failed to read file: {}", e))?;
let mut parser = Parser::new();
let ext = std::path::Path::new(&file_path)
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
let language = match ext {
"rs" => tree_sitter_rust::LANGUAGE,
"ts" | "tsx" | "js" | "jsx" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
"py" => tree_sitter_python::LANGUAGE,
_ => return Err(format!("Unsupported language for AST replacement: {}", ext)),
};
parser.set_language(&language.into()).map_err(|e| e.to_string())?;
let tree = parser.parse(&code, None).ok_or("Failed to parse code")?;
// Search for the node
fn find_node<'a>(node: Node<'a>, code: &str, target_type: &str, target_name: &str) -> Option<Node<'a>> {
if node.kind() == target_type {
// Try to find the name/identifier
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let kind = child.kind();
if kind == "identifier" || kind == "name" {
let name = child.utf8_text(code.as_bytes()).unwrap_or("");
if name == target_name {
return Some(node);
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(found) = find_node(child, code, target_type, target_name) {
return Some(found);
}
}
None
}
let target_node = find_node(tree.root_node(), &code, &tool_args.node_type, &tool_args.node_name);
if let Some(node) = target_node {
let start_byte = node.start_byte();
let end_byte = node.end_byte();
let mut new_file_content = String::new();
new_file_content.push_str(&code[..start_byte]);
new_file_content.push_str(&tool_args.new_content);
new_file_content.push_str(&code[end_byte..]);
std::fs::write(&file_path, new_file_content).map_err(|e| e.to_string())?;
Ok(format!("Successfully replaced node {} of type {} in {}", tool_args.node_name, tool_args.node_type, file_path))
} else {
Err(format!("Could not find node {} of type {}", tool_args.node_name, tool_args.node_type))
}
})
.await
.map_err(|e| format!("Task panic: {}", e))??;
Ok(result)
}
}