Skip to main content
Glama
edit-note.ts7.67 kB
import BaseTool, { JoplinNote } from "./base-tool.js" interface EditNoteOptions { note_id: string title?: string | undefined body?: string | undefined body_html?: string | undefined parent_id?: string | undefined is_todo?: boolean | undefined todo_completed?: boolean | undefined todo_due?: number | undefined } type EditNoteResponse = JoplinNote class EditNote extends BaseTool { async call(options: EditNoteOptions): Promise<string> { if (!options || typeof options !== "object") { return 'Please provide note edit options. Example: edit_note {"note_id": "abc123", "title": "Updated Title"}' } // Validate required note_id if (!options.note_id) { return 'Please provide note edit options. Example: edit_note {"note_id": "abc123", "title": "Updated Title"}' } const noteIdError = this.validateId(options.note_id, "note") if (noteIdError) { return noteIdError } // Validate that we have at least one field to update const updateFields = ["title", "body", "body_html", "parent_id", "is_todo", "todo_completed", "todo_due"] const hasUpdate = updateFields.some((field) => options[field as keyof EditNoteOptions] !== undefined) if (!hasUpdate) { return "Please provide at least one field to update. Available fields: title, body, body_html, parent_id, is_todo, todo_completed, todo_due" } // Validate parent_id if provided if (options.parent_id !== undefined && options.parent_id !== null && options.parent_id !== "") { if (options.parent_id.length < 10 || !options.parent_id.match(/[a-f0-9]/i)) { return `Error: "${options.parent_id}" does not appear to be a valid notebook ID.\n\nNotebook IDs are long alphanumeric strings like "58a0a29f68bc4141b49c99f5d367638a".\n\nUse list_notebooks to see available notebooks and their IDs.` } } try { // First, get the current note to show before/after comparison const currentNote = await this.apiClient.get<JoplinNote>(`/notes/${options.note_id}`, { query: { fields: "id,title,body,parent_id,is_todo,todo_completed,todo_due,updated_time" }, }) if (!currentNote || !currentNote.id) { return `Note with ID "${options.note_id}" not found.\n\nUse search_notes to find notes and their IDs.` } // Prepare the update body - only include fields that are being updated const updateBody: Partial<EditNoteOptions> = {} if (options.title !== undefined) updateBody.title = options.title if (options.body !== undefined) updateBody.body = options.body if (options.body_html !== undefined) updateBody.body_html = options.body_html if (options.parent_id !== undefined) updateBody.parent_id = options.parent_id if (options.is_todo !== undefined) updateBody.is_todo = options.is_todo if (options.todo_completed !== undefined) updateBody.todo_completed = options.todo_completed if (options.todo_due !== undefined) updateBody.todo_due = options.todo_due // Update the note const updatedNote = await this.apiClient.put<EditNoteResponse>(`/notes/${options.note_id}`, updateBody) // Validate response if (!updatedNote || typeof updatedNote !== "object" || !updatedNote.id) { return "Error: Unexpected response format from Joplin API when updating note" } // Get notebook info for both old and new locations if parent_id changed let oldNotebookInfo = "Root level" let newNotebookInfo = "Root level" if (currentNote.parent_id) { try { const oldNotebook = await this.apiClient.get(`/folders/${currentNote.parent_id}`, { query: { fields: "title" }, }) if (oldNotebook?.title) { oldNotebookInfo = `"${oldNotebook.title}"` } } catch { oldNotebookInfo = `Notebook ID: ${currentNote.parent_id}` } } if (updatedNote.parent_id && updatedNote.parent_id !== currentNote.parent_id) { try { const newNotebook = await this.apiClient.get(`/folders/${updatedNote.parent_id}`, { query: { fields: "title" }, }) if (newNotebook?.title) { newNotebookInfo = `"${newNotebook.title}"` } } catch { newNotebookInfo = `Notebook ID: ${updatedNote.parent_id}` } } else if (updatedNote.parent_id) { newNotebookInfo = oldNotebookInfo } // Format success response with before/after comparison const resultLines: string[] = [] resultLines.push(`✅ Successfully updated note!`) resultLines.push("") resultLines.push(`📝 Note: "${updatedNote.title || "Untitled"}"`) resultLines.push(` Note ID: ${updatedNote.id}`) resultLines.push("") // Show what changed resultLines.push(`🔄 Changes made:`) if (options.title !== undefined && currentNote.title !== updatedNote.title) { resultLines.push(` Title: "${currentNote.title}" → "${updatedNote.title}"`) } if (options.parent_id !== undefined && currentNote.parent_id !== updatedNote.parent_id) { resultLines.push(` Location: ${oldNotebookInfo} → ${newNotebookInfo}`) } if (options.is_todo !== undefined && currentNote.is_todo !== updatedNote.is_todo) { const oldType = currentNote.is_todo ? "Todo" : "Regular note" const newType = updatedNote.is_todo ? "Todo" : "Regular note" resultLines.push(` Type: ${oldType} → ${newType}`) } if (options.todo_completed !== undefined && currentNote.todo_completed !== updatedNote.todo_completed) { const oldStatus = currentNote.todo_completed ? "Completed" : "Not completed" const newStatus = updatedNote.todo_completed ? "Completed" : "Not completed" resultLines.push(` Todo Status: ${oldStatus} → ${newStatus}`) } if (options.todo_due !== undefined) { const oldDue = currentNote.todo_due ? this.formatDate(currentNote.todo_due) : "No due date" const newDue = updatedNote.todo_due ? this.formatDate(updatedNote.todo_due) : "No due date" if (oldDue !== newDue) { resultLines.push(` Due Date: ${oldDue} → ${newDue}`) } } if (options.body !== undefined) { resultLines.push(` Content: Updated`) } if (options.body_html !== undefined) { resultLines.push(` HTML Content: Updated`) } const updatedTime = this.formatDate(updatedNote.updated_time) resultLines.push(` Last Updated: ${updatedTime}`) resultLines.push("") resultLines.push(`🔗 Next steps:`) resultLines.push(` - Read the note: read_note note_id="${updatedNote.id}"`) if (updatedNote.parent_id) { resultLines.push(` - View notebook: read_notebook notebook_id="${updatedNote.parent_id}"`) } return resultLines.join("\n") } catch (error: any) { if (error.response) { if (error.response.status === 404) { return `Note with ID "${options.note_id}" not found.\n\nUse search_notes to find notes and their IDs.` } if (error.response.status === 400) { return `Error updating note: Invalid request data.\n\nPlease check your input parameters. ${error.response.data?.error || ""}` } if (error.response.status === 404 && options.parent_id) { return `Error: Notebook with ID "${options.parent_id}" not found.\n\nUse list_notebooks to see available notebooks and their IDs.` } } return this.formatError(error, "updating note") } } } export default EditNote

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