Skip to main content
Glama
delete-folder.ts6.68 kB
import BaseTool, { JoplinFolder } from "./base-tool.js" interface DeleteFolderOptions { folder_id: string confirm?: boolean | undefined force?: boolean | undefined } interface FolderContents { items: any[] } class DeleteFolder extends BaseTool { async call(options: DeleteFolderOptions): Promise<string> { if (!options || typeof options !== "object") { return 'Please provide folder deletion options. Example: delete_folder {"folder_id": "abc123", "confirm": true}' } // Validate required folder_id if (!options.folder_id) { return 'Please provide folder deletion options. Example: delete_folder {"folder_id": "abc123", "confirm": true}' } const folderIdError = this.validateId(options.folder_id, "notebook") if (folderIdError) { return folderIdError.replace("notebook ID", "folder ID").replace("notebook_id", "folder_id") } // Require explicit confirmation for safety if (!options.confirm) { return `⚠️ This will permanently delete the notebook/folder!\n\nTo confirm deletion, use:\ndelete_folder {"folder_id": "${options.folder_id}", "confirm": true}\n\n⚠️ This action cannot be undone!` } try { // First, get the folder details to show what's being deleted const folderToDelete = await this.apiClient.get<JoplinFolder>(`/folders/${options.folder_id}`, { query: { fields: "id,title,parent_id" }, }) if (!folderToDelete || !folderToDelete.id) { return `Folder with ID "${options.folder_id}" not found.\n\nUse list_notebooks to see available folders and their IDs.` } // Check if folder contains notes or subfolders const [notes, subfolders] = await Promise.all([ this.apiClient .get<FolderContents>(`/folders/${options.folder_id}/notes`, { query: { fields: "id,title" }, }) .catch(() => ({ items: [] })), this.apiClient .get<FolderContents>("/folders", { query: { fields: "id,title,parent_id" }, }) .then((response) => ({ items: response.items?.filter((folder: any) => folder.parent_id === options.folder_id) || [], })) .catch(() => ({ items: [] })), ]) const noteCount = notes.items?.length || 0 const subfolderCount = subfolders.items?.length || 0 const totalContent = noteCount + subfolderCount // Warn if folder is not empty and force is not specified if (totalContent > 0 && !options.force) { const resultLines: string[] = [] resultLines.push(`⚠️ Cannot delete non-empty notebook!`) resultLines.push("") resultLines.push(`📁 Notebook: "${folderToDelete.title}"`) resultLines.push(` Contains: ${noteCount} notes and ${subfolderCount} subfolders`) if (noteCount > 0) { resultLines.push("") resultLines.push(`📝 Contains ${noteCount} notes:`) notes.items.slice(0, 5).forEach((note: any) => { resultLines.push(` - ${note.title || "Untitled"}`) }) if (noteCount > 5) { resultLines.push(` ... and ${noteCount - 5} more notes`) } } if (subfolderCount > 0) { resultLines.push("") resultLines.push(`📁 Contains ${subfolderCount} subfolders:`) subfolders.items.slice(0, 5).forEach((folder: any) => { resultLines.push(` - ${folder.title}`) }) if (subfolderCount > 5) { resultLines.push(` ... and ${subfolderCount - 5} more folders`) } } resultLines.push("") resultLines.push(`💡 Options:`) resultLines.push(` 1. Move or delete the contents first, then delete the folder`) resultLines.push(` 2. Force delete (⚠️ DESTROYS ALL CONTENT):`) resultLines.push(` delete_folder {"folder_id": "${options.folder_id}", "confirm": true, "force": true}`) resultLines.push("") resultLines.push(`⚠️ Force delete will permanently delete ALL ${totalContent} items inside!`) return resultLines.join("\n") } // Get parent folder info if available let parentInfo = "Top level" if (folderToDelete.parent_id) { try { const parentFolder = await this.apiClient.get(`/folders/${folderToDelete.parent_id}`, { query: { fields: "title" }, }) if (parentFolder?.title) { parentInfo = `Inside "${parentFolder.title}" (notebook_id: "${folderToDelete.parent_id}")` } } catch { parentInfo = `Parent ID: ${folderToDelete.parent_id}` } } // Delete the folder await this.apiClient.delete(`/folders/${options.folder_id}`) // Format success response const resultLines: string[] = [] resultLines.push(`🗑️ Successfully deleted notebook!`) resultLines.push("") resultLines.push(`📁 Deleted Notebook Details:`) resultLines.push(` Title: "${folderToDelete.title}"`) resultLines.push(` Folder ID: ${folderToDelete.id}`) resultLines.push(` Location: ${parentInfo}`) if (totalContent > 0) { resultLines.push(` Deleted Content: ${noteCount} notes and ${subfolderCount} subfolders`) resultLines.push("") resultLines.push(`⚠️ All ${totalContent} items inside have been permanently deleted!`) } resultLines.push("") resultLines.push(`⚠️ This notebook has been permanently deleted and cannot be recovered.`) if (folderToDelete.parent_id) { resultLines.push("") resultLines.push(`🔗 Related actions:`) resultLines.push(` - View parent notebook: read_notebook notebook_id="${folderToDelete.parent_id}"`) resultLines.push(` - View all notebooks: list_notebooks`) } return resultLines.join("\n") } catch (error: any) { if (error.response) { if (error.response.status === 404) { return `Folder with ID "${options.folder_id}" not found.\n\nUse list_notebooks to see available folders and their IDs.` } if (error.response.status === 403) { return `Permission denied: Cannot delete folder with ID "${options.folder_id}".\n\nThis might be a protected system folder.` } if (error.response.status === 409) { return `Cannot delete folder: It may contain items that prevent deletion.\n\nTry moving or deleting the contents first, or use force option.` } } return this.formatError(error, "deleting folder") } } } export default DeleteFolder

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jordanburke/joplin-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server