UNPKG

obsidian-mcp-server

Version:

MCP server for Obsidian vaults — read, write, search, and surgically edit notes, tags, and frontmatter via the Local REST API plugin. STDIO or Streamable HTTP.

97 lines 5 kB
/** * @fileoverview obsidian_delete_note — permanently delete a note. Calls * `ctx.elicit` to confirm with the user when the client supports it; falls * back to the destructive-hint annotation otherwise. * @module mcp-server/tools/definitions/obsidian-delete-note.tool */ import { tool, z } from '@cyanheads/mcp-ts-core'; import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors'; import { getObsidianService } from '../../../services/obsidian/obsidian-service.js'; import { TargetSchema } from './_shared/schemas.js'; export const obsidianDeleteNote = tool('obsidian_delete_note', { description: 'Permanently delete a note from the vault. Confirms with the user before deleting when the client supports interactive confirmation. Recovery requires the local trash in Obsidian — there is no API-level undo.', annotations: { destructiveHint: true }, input: z.object({ target: TargetSchema.describe('Which note to delete.'), }), output: z.object({ path: z.string().describe('Resolved vault-relative path of the deleted note.'), deleted: z.boolean().describe('True when the file was removed.'), previousSizeInBytes: z .number() .describe('Byte size of the note immediately before deletion. The destructive blast radius — useful for confirming the agent destroyed what it expected.'), currentSizeInBytes: z .number() .describe('Always 0 after a successful delete — the file no longer exists.'), }), auth: ['tool:obsidian_delete_note:write'], errors: [ { reason: 'path_forbidden', code: JsonRpcErrorCode.Forbidden, when: 'The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.', recovery: 'Use a path inside the configured write scope. The error data echoes the active scope.', }, { reason: 'cancelled', code: JsonRpcErrorCode.InvalidRequest, when: 'User declined the deletion via interactive elicitation.', recovery: 'Re-run the tool when the user is ready to confirm deletion.', }, { reason: 'note_missing', code: JsonRpcErrorCode.NotFound, when: 'The vault path does not resolve to an existing note.', recovery: 'Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.', }, { reason: 'no_active_file', code: JsonRpcErrorCode.NotFound, when: 'Target was `active` but no file is currently open in Obsidian.', recovery: 'Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.', }, { reason: 'periodic_not_found', code: JsonRpcErrorCode.NotFound, when: 'Target was `periodic` but no matching periodic note exists.', recovery: 'Pass an explicit path target — periodic notes must already exist.', }, { reason: 'periodic_disabled', code: JsonRpcErrorCode.ValidationError, when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.", recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.", }, ], async handler(input, ctx) { const svc = getObsidianService(); const path = await svc.resolvePath(ctx, input.target); const pathTarget = { type: 'path', path }; /** * Probe size before showing the elicitation prompt so the user sees how * much they're about to destroy. Throws `note_missing` if the file is * already gone — preempts a confusing post-elicit DELETE 404. */ const previousSizeInBytes = await svc.getSize(ctx, pathTarget); if (ctx.elicit) { const confirmed = await ctx.elicit(`Permanently delete '${path}' (${previousSizeInBytes} bytes)? This cannot be undone via the API; recovery would require Obsidian's local trash.`, z.object({ confirm: z.boolean().describe('Set to true to delete the note. Any other value cancels.'), })); if (confirmed.action !== 'accept' || confirmed.content?.confirm !== true) { throw ctx.fail('cancelled', 'Deletion cancelled by user.', { path, ...ctx.recoveryFor('cancelled'), }); } } await svc.deleteNote(ctx, pathTarget); return { path, deleted: true, previousSizeInBytes, currentSizeInBytes: 0 }; }, format: (result) => [ { type: 'text', text: `**Deleted ${result.path}** (size: ${result.previousSizeInBytes}${result.currentSizeInBytes} bytes)`, }, ], }); //# sourceMappingURL=obsidian-delete-note.tool.js.map