From 3f22521b012855d8304f183fa0cfaf1ad22c7e2e Mon Sep 17 00:00:00 2001 From: Nando Thomassen Date: Sat, 29 Mar 2025 22:29:01 +0100 Subject: [PATCH] Implement 'Delete file/directory' functionality The MCP server now supports safe deletion of files and directories from the Obsidian vault. A required confirmation parameter prevents accidental deletions. --- README.md | 1 + src/mcp_obsidian/obsidian.py | 18 +++++++++++++++ src/mcp_obsidian/server.py | 1 + src/mcp_obsidian/tools.py | 43 ++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/README.md b/README.md index 869f924..ff85b10 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The server implements multiple tools to interact with Obsidian: - search: Search for documents matching a specified text query across all files in the vault - patch_content: Insert content into an existing note relative to a heading, block reference, or frontmatter field. - append_content: Append content to a new or existing file in the vault. +- delete_file: Delete a file or directory from your vault. ### Example prompts diff --git a/src/mcp_obsidian/obsidian.py b/src/mcp_obsidian/obsidian.py index 3c85d6e..a2c235f 100644 --- a/src/mcp_obsidian/obsidian.py +++ b/src/mcp_obsidian/obsidian.py @@ -140,6 +140,24 @@ class Obsidian(): return self._safe_call(call_fn) + def delete_file(self, filepath: str) -> Any: + """Delete a file or directory from the vault. + + Args: + filepath: Path to the file to delete (relative to vault root) + + Returns: + None on success + """ + url = f"{self.get_base_url()}/vault/{filepath}" + + def call_fn(): + response = requests.delete(url, headers=self._get_headers(), verify=self.verify_ssl, timeout=self.timeout) + response.raise_for_status() + return None + + return self._safe_call(call_fn) + def search_json(self, query: dict) -> Any: url = f"{self.get_base_url()}/search/" diff --git a/src/mcp_obsidian/server.py b/src/mcp_obsidian/server.py index d619cc2..79cb2a6 100644 --- a/src/mcp_obsidian/server.py +++ b/src/mcp_obsidian/server.py @@ -47,6 +47,7 @@ add_tool_handler(tools.GetFileContentsToolHandler()) add_tool_handler(tools.SearchToolHandler()) add_tool_handler(tools.PatchContentToolHandler()) add_tool_handler(tools.AppendContentToolHandler()) +add_tool_handler(tools.DeleteFileToolHandler()) add_tool_handler(tools.ComplexSearchToolHandler()) add_tool_handler(tools.BatchGetFileContentsToolHandler()) add_tool_handler(tools.PeriodicNotesToolHandler()) diff --git a/src/mcp_obsidian/tools.py b/src/mcp_obsidian/tools.py index 128d3f9..b7cad84 100644 --- a/src/mcp_obsidian/tools.py +++ b/src/mcp_obsidian/tools.py @@ -286,6 +286,49 @@ class PatchContentToolHandler(ToolHandler): text=f"Successfully patched content in {args['filepath']}" ) ] + +class DeleteFileToolHandler(ToolHandler): + def __init__(self): + super().__init__("obsidian_delete_file") + + def get_tool_description(self): + return Tool( + name=self.name, + description="Delete a file or directory from the vault.", + inputSchema={ + "type": "object", + "properties": { + "filepath": { + "type": "string", + "description": "Path to the file or directory to delete (relative to vault root)", + "format": "path" + }, + "confirm": { + "type": "boolean", + "description": "Confirmation to delete the file (must be true)", + "default": False + } + }, + "required": ["filepath", "confirm"] + } + ) + + def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]: + if "filepath" not in args: + raise RuntimeError("filepath argument missing in arguments") + + if not args.get("confirm", False): + raise RuntimeError("confirm must be set to true to delete a file") + + api = obsidian.Obsidian(api_key=api_key) + api.delete_file(args["filepath"]) + + return [ + TextContent( + type="text", + text=f"Successfully deleted {args['filepath']}" + ) + ] class ComplexSearchToolHandler(ToolHandler): def __init__(self):