@noteplanco/noteplan-mcp 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/noteplan/file-writer.d.ts.map +1 -1
  2. package/dist/noteplan/file-writer.js +73 -16
  3. package/dist/noteplan/file-writer.js.map +1 -1
  4. package/dist/noteplan/file-writer.test.d.ts +2 -0
  5. package/dist/noteplan/file-writer.test.d.ts.map +1 -0
  6. package/dist/noteplan/file-writer.test.js +892 -0
  7. package/dist/noteplan/file-writer.test.js.map +1 -0
  8. package/dist/noteplan/filter-store.d.ts.map +1 -1
  9. package/dist/noteplan/filter-store.js +13 -1
  10. package/dist/noteplan/filter-store.js.map +1 -1
  11. package/dist/noteplan/frontmatter-parser.d.ts +10 -1
  12. package/dist/noteplan/frontmatter-parser.d.ts.map +1 -1
  13. package/dist/noteplan/frontmatter-parser.js +66 -10
  14. package/dist/noteplan/frontmatter-parser.js.map +1 -1
  15. package/dist/noteplan/frontmatter-parser.test.js +484 -1
  16. package/dist/noteplan/frontmatter-parser.test.js.map +1 -1
  17. package/dist/noteplan/markdown-parser.d.ts +6 -1
  18. package/dist/noteplan/markdown-parser.d.ts.map +1 -1
  19. package/dist/noteplan/markdown-parser.js +21 -44
  20. package/dist/noteplan/markdown-parser.js.map +1 -1
  21. package/dist/noteplan/markdown-parser.test.d.ts +2 -0
  22. package/dist/noteplan/markdown-parser.test.d.ts.map +1 -0
  23. package/dist/noteplan/markdown-parser.test.js +653 -0
  24. package/dist/noteplan/markdown-parser.test.js.map +1 -0
  25. package/dist/server.d.ts.map +1 -1
  26. package/dist/server.js +405 -205
  27. package/dist/server.js.map +1 -1
  28. package/dist/tools/attachments.d.ts +151 -0
  29. package/dist/tools/attachments.d.ts.map +1 -0
  30. package/dist/tools/attachments.js +421 -0
  31. package/dist/tools/attachments.js.map +1 -0
  32. package/dist/tools/attachments.test.d.ts +2 -0
  33. package/dist/tools/attachments.test.d.ts.map +1 -0
  34. package/dist/tools/attachments.test.js +561 -0
  35. package/dist/tools/attachments.test.js.map +1 -0
  36. package/dist/tools/calendar.d.ts +5 -5
  37. package/dist/tools/notes.d.ts +67 -26
  38. package/dist/tools/notes.d.ts.map +1 -1
  39. package/dist/tools/notes.js +124 -33
  40. package/dist/tools/notes.js.map +1 -1
  41. package/dist/tools/notes.test.d.ts +2 -0
  42. package/dist/tools/notes.test.d.ts.map +1 -0
  43. package/dist/tools/notes.test.js +598 -0
  44. package/dist/tools/notes.test.js.map +1 -0
  45. package/dist/tools/reminders.d.ts +4 -4
  46. package/dist/tools/tasks.d.ts +10 -10
  47. package/dist/tools/tasks.d.ts.map +1 -1
  48. package/dist/tools/tasks.js +14 -27
  49. package/dist/tools/tasks.js.map +1 -1
  50. package/dist/tools/templates.d.ts +69 -0
  51. package/dist/tools/templates.d.ts.map +1 -0
  52. package/dist/tools/templates.js +145 -0
  53. package/dist/tools/templates.js.map +1 -0
  54. package/dist/tools/templates.test.d.ts +2 -0
  55. package/dist/tools/templates.test.d.ts.map +1 -0
  56. package/dist/tools/templates.test.js +48 -0
  57. package/dist/tools/templates.test.js.map +1 -0
  58. package/dist/tools/ui.d.ts +2 -0
  59. package/dist/tools/ui.d.ts.map +1 -1
  60. package/dist/tools/ui.js +24 -0
  61. package/dist/tools/ui.js.map +1 -1
  62. package/dist/utils/applescript.d.ts.map +1 -1
  63. package/dist/utils/applescript.js +21 -0
  64. package/dist/utils/applescript.js.map +1 -1
  65. package/dist/utils/confirmation-tokens.test.d.ts +2 -0
  66. package/dist/utils/confirmation-tokens.test.d.ts.map +1 -0
  67. package/dist/utils/confirmation-tokens.test.js +159 -0
  68. package/dist/utils/confirmation-tokens.test.js.map +1 -0
  69. package/dist/utils/version.d.ts +2 -0
  70. package/dist/utils/version.d.ts.map +1 -1
  71. package/dist/utils/version.js +4 -0
  72. package/dist/utils/version.js.map +1 -1
  73. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -19,8 +19,10 @@ import * as memoryTools from './tools/memory.js';
19
19
  import * as uiTools from './tools/ui.js';
20
20
  import * as pluginTools from './tools/plugins.js';
21
21
  import * as themeTools from './tools/themes.js';
22
+ import * as templateTools from './tools/templates.js';
23
+ import * as attachmentTools from './tools/attachments.js';
22
24
  import { parseFlexibleDate } from './utils/date-utils.js';
23
- import { upgradeMessage, getNotePlanVersion, getMcpServerVersion, MIN_BUILD_ADVANCED_FEATURES } from './utils/version.js';
25
+ import { upgradeMessage, getNotePlanVersion, getMcpServerVersion, MIN_BUILD_ADVANCED_FEATURES, MIN_BUILD_CREATE_BACKUP } from './utils/version.js';
24
26
  import { initSqlite } from './noteplan/sqlite-loader.js';
25
27
  const __filename = fileURLToPath(import.meta.url);
26
28
  const __dirname = path.dirname(__filename);
@@ -200,6 +202,8 @@ function getToolOutputSchema(toolName) {
200
202
  case 'noteplan_plugins':
201
203
  case 'noteplan_themes':
202
204
  case 'noteplan_embeddings':
205
+ case 'noteplan_templates':
206
+ case 'noteplan_attachments':
203
207
  return GENERIC_TOOL_OUTPUT_SCHEMA;
204
208
  default:
205
209
  return GENERIC_TOOL_OUTPUT_SCHEMA;
@@ -371,6 +375,7 @@ function getToolAnnotations(toolName) {
371
375
  'noteplan_plugins',
372
376
  'noteplan_themes',
373
377
  'noteplan_embeddings',
378
+ 'noteplan_attachments',
374
379
  ]);
375
380
  const nonIdempotentTools = new Set([
376
381
  'noteplan_manage_note',
@@ -384,11 +389,14 @@ function getToolAnnotations(toolName) {
384
389
  'noteplan_plugins',
385
390
  'noteplan_themes',
386
391
  'noteplan_embeddings',
392
+ 'noteplan_templates',
393
+ 'noteplan_attachments',
387
394
  ]);
388
395
  const openWorldTools = new Set([
389
396
  'noteplan_eventkit',
390
397
  'noteplan_embeddings',
391
398
  'noteplan_plugins',
399
+ 'noteplan_templates',
392
400
  ]);
393
401
  return {
394
402
  readOnlyHint: readOnlyTools.has(toolName),
@@ -459,6 +467,12 @@ function getToolSearchAliases(toolName) {
459
467
  case 'noteplan_embeddings':
460
468
  aliases.push('embeddings', 'semantic search', 'vector search', 'similarity');
461
469
  break;
470
+ case 'noteplan_templates':
471
+ aliases.push('template', 'templates', 'render template', 'list templates', 'template types', 'meeting template', 'project template', 'debug template', 'test template');
472
+ break;
473
+ case 'noteplan_attachments':
474
+ aliases.push('attachment', 'attachments', 'image', 'file', 'upload', 'add image', 'add file', 'add attachment', 'list attachments', 'get attachment', 'base64', 'photo', 'screenshot');
475
+ break;
462
476
  }
463
477
  return aliases;
464
478
  }
@@ -516,7 +530,7 @@ function searchToolDefinitions(tools, query, limit) {
516
530
  function inferToolErrorMeta(toolName, errorMessage, registeredToolNames) {
517
531
  const message = errorMessage.toLowerCase();
518
532
  if (message.includes('unknown tool')) {
519
- const toolList = registeredToolNames?.join(', ') ?? 'noteplan_get_notes, noteplan_search, noteplan_manage_note, noteplan_edit_content, noteplan_paragraphs, noteplan_folders, noteplan_filters, noteplan_eventkit, noteplan_memory';
533
+ const toolList = registeredToolNames?.join(', ') ?? 'noteplan_get_notes, noteplan_search, noteplan_manage_note, noteplan_edit_content, noteplan_paragraphs, noteplan_folders, noteplan_filters, noteplan_eventkit, noteplan_memory, noteplan_templates, noteplan_attachments';
520
534
  return {
521
535
  code: 'ERR_UNKNOWN_TOOL',
522
536
  hint: `Check tool name spelling. Available tools: ${toolList}.`,
@@ -737,6 +751,12 @@ function withSuggestedNextTools(result, toolName, availableToolNames) {
737
751
  case 'noteplan_themes':
738
752
  suggestedNextTools = ['noteplan_themes'];
739
753
  break;
754
+ case 'noteplan_templates':
755
+ suggestedNextTools = ['noteplan_templates', 'noteplan_manage_note', 'noteplan_edit_content'];
756
+ break;
757
+ case 'noteplan_attachments':
758
+ suggestedNextTools = ['noteplan_attachments', 'noteplan_edit_content', 'noteplan_get_notes'];
759
+ break;
740
760
  default:
741
761
  suggestedNextTools = [];
742
762
  }
@@ -977,14 +997,14 @@ export function createServer() {
977
997
  },
978
998
  {
979
999
  name: 'noteplan_manage_note',
980
- description: 'Manage notes: create, update, delete, move, restore, rename, or manage frontmatter properties.\n\nActions:\n- create: Create a project note (requires title)\n- update: Replace note content (requires filename, content, fullReplace + confirmationToken)\n- delete/move/restore/rename: Lifecycle ops (requires id or filename + dryRun/confirmationToken)\n- set_property/remove_property: Frontmatter (requires filename + key)',
1000
+ description: 'Manage notes: create, update, delete, move, restore, rename, or manage frontmatter properties.\n\nActions:\n- create: Create a project note (requires title). Set noteType="template" to create in @Templates with proper frontmatter. After creating a template, verify it with noteplan_templates(action: "render").\n- update: Replace note content (requires filename, content, fullReplace + confirmationToken)\n- delete/move/restore: Lifecycle ops (requires id or filename + dryRun/confirmationToken)\n- rename: Rename a note (accepts id, filename, title, or query to find the note + newTitle for the new name + dryRun/confirmationToken)\n- set_property/remove_property: Frontmatter (requires filename + key)',
981
1001
  inputSchema: {
982
1002
  type: 'object',
983
1003
  properties: {
984
1004
  action: {
985
1005
  type: 'string',
986
1006
  enum: ['create', 'update', 'delete', 'move', 'restore', 'rename', 'set_property', 'remove_property'],
987
- description: 'Action to perform',
1007
+ description: 'Action: create | update | delete | move | restore | rename | set_property | remove_property',
988
1008
  },
989
1009
  id: {
990
1010
  type: 'string',
@@ -996,7 +1016,11 @@ export function createServer() {
996
1016
  },
997
1017
  title: {
998
1018
  type: 'string',
999
- description: 'Note title — required for create',
1019
+ description: 'Note title — required for create. Also used by rename to find the note by title (fuzzy matched).',
1020
+ },
1021
+ query: {
1022
+ type: 'string',
1023
+ description: 'Fuzzy search query to find the note — used by rename',
1000
1024
  },
1001
1025
  content: {
1002
1026
  type: 'string',
@@ -1010,6 +1034,16 @@ export function createServer() {
1010
1034
  type: 'boolean',
1011
1035
  description: 'Bypass smart matching and create exact folder name — used by create',
1012
1036
  },
1037
+ noteType: {
1038
+ type: 'string',
1039
+ enum: ['note', 'template'],
1040
+ description: 'Type of note to create. Use "template" to create in @Templates with proper frontmatter — used by create',
1041
+ },
1042
+ templateTypes: {
1043
+ type: 'array',
1044
+ items: { type: 'string', enum: ['empty-note', 'meeting-note', 'project-note', 'calendar-note'] },
1045
+ description: 'Template type tags — used by create with noteType="template"',
1046
+ },
1013
1047
  space: {
1014
1048
  type: 'string',
1015
1049
  description: 'Space name or ID scope',
@@ -1036,11 +1070,11 @@ export function createServer() {
1036
1070
  },
1037
1071
  newFilename: {
1038
1072
  type: 'string',
1039
- description: 'New filename for local notes — used by rename',
1073
+ description: 'New filename for local notes — used by rename. Prefer newTitle instead.',
1040
1074
  },
1041
1075
  newTitle: {
1042
1076
  type: 'string',
1043
- description: 'New title for TeamSpace notes — used by rename',
1077
+ description: 'New title for the note — used by rename. Works for both local and TeamSpace notes. For local notes this renames the file and updates the # heading.',
1044
1078
  },
1045
1079
  keepExtension: {
1046
1080
  type: 'boolean',
@@ -1060,14 +1094,14 @@ export function createServer() {
1060
1094
  },
1061
1095
  {
1062
1096
  name: 'noteplan_edit_content',
1063
- description: 'Edit note content: insert, append, delete lines, edit a line, or replace a range of lines.\n\nActions:\n- insert: Insert at position (start/end/after-heading/in-section/at-line)\n- append: Append to end (shorthand for insert at end). Use date="today" to append to today\'s daily note (replaces noteplan_add_to_today).\n- delete_lines: Delete line range (requires startLine, endLine + dryRun/confirmationToken)\n- edit_line: Edit one line (requires line + content)\n- replace_lines: Replace line range (requires startLine, endLine, content + dryRun/confirmationToken)\n\nTarget note via id, filename, title, date, or query. Calendar notes (date param) are auto-created if they don\'t exist yet — no need to create them first. Always use tab characters for indentation.\n\nNotePlan syntax: Tasks use "- [ ] text" (open), "- [x]" (done), "- [-]" (cancelled), "- [>]" (scheduled). The task marker (*/- with or without checkbox) follows user settings prefer noteplan_paragraphs(action=add) for auto-formatted tasks. Schedule tasks to dates with >YYYY-MM-DD. Link to notes with [[Note Name]]. Never add block IDs (^id) — only the NotePlan app creates these.\n\nUse scheduleDate to auto-append >YYYY-MM-DD scheduling to content.',
1097
+ description: 'Edit note content. IMPORTANT: action values use snake_case.\n\nValid actions (exactly these strings):\n- "insert": Insert at position. Combine any position with heading="Section Name" to scope insertion to that section. position="start" + heading inserts right after the heading. position="end" + heading appends at end of the heading\'s section. position="after-heading" inserts right after a heading. position="in-section" appends at end of a heading\'s section. position="start" (no heading) inserts after frontmatter. position="at-line" inserts at a specific line number. position="end" (no heading) appends to note.\n- "append": Shorthand for insert at end. Supports heading="Section Name" to append at end of that section. Use date="today" to append to today\'s daily note.\n- "delete_lines": Delete a line range. Requires startLine + endLine (1-indexed). Use dryRun=true first, then confirmationToken to execute.\n- "edit_line": Edit a single line. Requires line (1-indexed) + content. Set content="" to clear a line.\n- "replace_lines": Replace a line range. Requires startLine + endLine + content. Use dryRun=true first, then confirmationToken to execute.\n\nTarget note via id, filename, title, date, or query. Calendar notes (date param) are auto-created if they don\'t exist yet. Always use tab characters for indentation.\n\nAdding tasks: When inserting a task, set type="task" and pass only the task text as content (e.g. content="Buy groceries", type="task"). Do NOT include raw markers like "* [ ]" or "- [ ]" in content the type parameter handles formatting to match the user\'s configured style. For checklists use type="checklist". For task lifecycle (complete, update, search), use noteplan_paragraphs instead.\n\nSchedule tasks with >YYYY-MM-DD. Link to notes with [[Note Name]]. Never add block IDs (^id).\n\nUse scheduleDate to auto-append >YYYY-MM-DD to content.',
1064
1098
  inputSchema: {
1065
1099
  type: 'object',
1066
1100
  properties: {
1067
1101
  action: {
1068
1102
  type: 'string',
1069
1103
  enum: ['insert', 'append', 'delete_lines', 'edit_line', 'replace_lines'],
1070
- description: 'Action to perform',
1104
+ description: 'Action: insert | append | delete_lines | edit_line | replace_lines',
1071
1105
  },
1072
1106
  id: {
1073
1107
  type: 'string',
@@ -1095,16 +1129,16 @@ export function createServer() {
1095
1129
  },
1096
1130
  content: {
1097
1131
  type: 'string',
1098
- description: 'Content to insert/append/replace, or new line content for edit_line',
1132
+ description: 'Content to insert/append/replace, or new line content for edit_line. NEVER include task markers like "- [ ]", "* [ ]", or "* " — instead set type="task" and pass only the task text (e.g. content="Buy groceries", NOT content="- [ ] Buy groceries").',
1099
1133
  },
1100
1134
  position: {
1101
1135
  type: 'string',
1102
1136
  enum: ['start', 'end', 'after-heading', 'at-line', 'in-section'],
1103
- description: 'Where to insert: start (after frontmatter), end, after-heading (right after heading/marker line), in-section (at end of section, before next heading/marker), or at-line — used by insert',
1137
+ description: 'Where to insert. With heading: start=right after heading, end/in-section=end of section. Without heading: start=after frontmatter, end=bottom of note. at-line=specific line number — used by insert',
1104
1138
  },
1105
1139
  heading: {
1106
1140
  type: 'string',
1107
- description: 'Heading or section marker text (required for after-heading and in-section; matches both ## headings and **bold:** section markers) — used by insert',
1141
+ description: 'Heading or section marker text (required for after-heading and in-section; matches both ## headings and **bold:** section markers) — used by insert, append',
1108
1142
  },
1109
1143
  line: {
1110
1144
  type: 'number',
@@ -1167,14 +1201,14 @@ export function createServer() {
1167
1201
  },
1168
1202
  {
1169
1203
  name: 'noteplan_paragraphs',
1170
- description: 'Paragraph and task operations on notes.\n\nParagraph actions:\n- get: Get note lines with metadata (requires filename). Returns line, lineIndex, content, type, etc.\n- search: Search for matching lines in a note (requires query + note ref via id/filename/title/date)\n\nTask actions:\n- search_global: Search tasks across all notes (requires query, supports "*" wildcard)\n- add: Add a task (requires target + content). Task marker format auto-matches user settings. Use scheduleDate to auto-append >YYYY-MM-DD scheduling to content, [[Note Name]] to link, #tag for tags, @person for mentions.\n- complete: Mark task done (requires filename + lineIndex or line)\n- update: Update task content/status (requires filename + lineIndex or line)',
1204
+ description: 'Task lifecycle and paragraph inspection.\n\nParagraph actions:\n- get: Get note lines with metadata (requires filename). Returns line, lineIndex, content, type, etc.\n- search: Search for matching lines in a note (requires query + note ref via id/filename/title/date)\n\nTask actions:\n- search_global: Search tasks across all notes (requires query, supports "*" wildcard)\n- add: Add a task (requires target + content). Target is a date ("today", "tomorrow", "YYYY-MM-DD") for daily notes or a filename for project notes. Pass only the task text as content — formatting auto-matches user settings. Position+heading combos: position="start"+heading inserts right after the heading, position="end"+heading appends at end of that section, position="after-heading" inserts right after heading, position="in-section" appends at end of section. Default position is "end" (bottom of note). Use scheduleDate for >YYYY-MM-DD, [[Note Name]] to link, #tag for tags, @person for mentions.\n- complete: Mark task done (requires filename + lineIndex or line)\n- update: Update task content/status (requires filename + lineIndex or line)',
1171
1205
  inputSchema: {
1172
1206
  type: 'object',
1173
1207
  properties: {
1174
1208
  action: {
1175
1209
  type: 'string',
1176
1210
  enum: ['get', 'search', 'search_global', 'add', 'complete', 'update'],
1177
- description: 'Action to perform',
1211
+ description: 'Action: get | search | search_global | add | complete | update',
1178
1212
  },
1179
1213
  id: {
1180
1214
  type: 'string',
@@ -1236,16 +1270,16 @@ export function createServer() {
1236
1270
  },
1237
1271
  target: {
1238
1272
  type: 'string',
1239
- description: 'Target for add: date (today/tomorrow/YYYY-MM-DD) for daily notes or filename for project notes',
1273
+ description: 'Target note for add: use a date string (today, tomorrow, yesterday, YYYY-MM-DD, YYYYMMDD) for daily/calendar notes, or a filename path for project notes. Daily notes are auto-created if they don\'t exist.',
1240
1274
  },
1241
1275
  position: {
1242
1276
  type: 'string',
1243
1277
  enum: ['start', 'end', 'after-heading', 'in-section'],
1244
- description: 'Where to add task (default: end) — used by add',
1278
+ description: 'Where to add task (default: end). With heading: start=right after heading, end/in-section=end of section. Without heading: start=top of note, end=bottom of note — used by add',
1245
1279
  },
1246
1280
  heading: {
1247
1281
  type: 'string',
1248
- description: 'Heading or section marker text (matches both ## headings and **bold:** section markers) — used by add',
1282
+ description: 'Heading or section marker text to scope insertion (matches both ## headings and **bold:** section markers). Combine with position to control placement — used by add',
1249
1283
  },
1250
1284
  lineIndex: {
1251
1285
  type: 'number',
@@ -1321,7 +1355,7 @@ export function createServer() {
1321
1355
  action: {
1322
1356
  type: 'string',
1323
1357
  enum: ['list', 'find', 'resolve', 'create', 'move', 'rename', 'delete', 'list_spaces'],
1324
- description: 'Action to perform',
1358
+ description: 'Action: list | find | resolve | create | move | rename | delete | list_spaces',
1325
1359
  },
1326
1360
  path: {
1327
1361
  type: 'string',
@@ -1424,7 +1458,7 @@ export function createServer() {
1424
1458
  action: {
1425
1459
  type: 'string',
1426
1460
  enum: ['list', 'get', 'get_tasks', 'list_parameters', 'save', 'rename'],
1427
- description: 'Action to perform',
1461
+ description: 'Action: list | get | get_tasks | list_parameters | save | rename',
1428
1462
  },
1429
1463
  name: {
1430
1464
  type: 'string',
@@ -1509,7 +1543,7 @@ export function createServer() {
1509
1543
  action: {
1510
1544
  type: 'string',
1511
1545
  enum: ['get_events', 'list_calendars', 'create_event', 'update_event', 'delete_event', 'get', 'list_lists', 'create', 'complete', 'update', 'delete'],
1512
- description: 'Action to perform',
1546
+ description: 'Action: get_events | list_calendars | create_event | update_event | delete_event | get | list_lists | create | complete | update | delete',
1513
1547
  },
1514
1548
  // Calendar params
1515
1549
  eventId: {
@@ -1611,7 +1645,7 @@ export function createServer() {
1611
1645
  action: {
1612
1646
  type: 'string',
1613
1647
  enum: ['list', 'save', 'update', 'delete'],
1614
- description: 'Action to perform',
1648
+ description: 'Action: list | save | update | delete',
1615
1649
  },
1616
1650
  id: {
1617
1651
  type: 'string',
@@ -1747,7 +1781,7 @@ export function createServer() {
1747
1781
  action: {
1748
1782
  type: 'string',
1749
1783
  enum: ['status', 'search', 'sync', 'reset'],
1750
- description: 'Action to perform',
1784
+ description: 'Action: status | search | sync | reset',
1751
1785
  },
1752
1786
  query: {
1753
1787
  type: 'string',
@@ -1824,196 +1858,266 @@ export function createServer() {
1824
1858
  },
1825
1859
  });
1826
1860
  }
1827
- if (advancedFeaturesEnabled) {
1828
- toolDefinitions.push({
1829
- name: 'noteplan_ui',
1830
- description: 'NotePlan UI control via AppleScript.\n\nActions:\n- open_note: Open a note (title or filename)\n- open_today: Open today\'s note\n- search: Search in UI\n- run_plugin: Run a plugin command (requires pluginId + command)\n- open_view: Open a named view\n- toggle_sidebar: Toggle sidebar visibility\n- close_plugin_window: Close plugin window (by windowID/title, or omit both to close all)\n- list_plugin_windows: List open plugin windows',
1831
- inputSchema: {
1832
- type: 'object',
1833
- properties: {
1834
- action: {
1835
- type: 'string',
1836
- enum: ['open_note', 'open_today', 'search', 'run_plugin', 'open_view', 'toggle_sidebar', 'close_plugin_window', 'list_plugin_windows'],
1837
- description: 'Action to perform',
1838
- },
1839
- title: {
1840
- type: 'string',
1841
- description: 'Note title — used by open_note; window title — used by close_plugin_window',
1842
- },
1843
- filename: {
1844
- type: 'string',
1845
- description: 'Filename — used by open_note',
1846
- },
1847
- inNewWindow: {
1848
- type: 'boolean',
1849
- description: 'Open in new window — used by open_note',
1850
- },
1851
- inSplitView: {
1852
- type: 'boolean',
1853
- description: 'Open in split view — used by open_note',
1854
- },
1855
- query: {
1856
- type: 'string',
1857
- description: 'Search text — used by search',
1858
- },
1859
- pluginId: {
1860
- type: 'string',
1861
- description: 'Plugin ID — used by run_plugin',
1862
- },
1863
- command: {
1864
- type: 'string',
1865
- description: 'Command name — used by run_plugin',
1866
- },
1867
- arguments: {
1868
- type: 'string',
1869
- description: 'JSON arguments string — used by run_plugin',
1870
- },
1871
- name: {
1872
- type: 'string',
1873
- description: 'View name — used by open_view',
1874
- },
1875
- windowID: {
1876
- type: 'string',
1877
- description: 'Window ID — used by close_plugin_window',
1878
- },
1861
+ // noteplan_ui is always available — basic AppleScript commands work on all NotePlan versions
1862
+ toolDefinitions.push({
1863
+ name: 'noteplan_ui',
1864
+ description: 'NotePlan UI control via AppleScript.\n\nActions:\n- open_note: Open a note (title or filename)\n- open_today: Open today\'s note\n- search: Search in UI\n- run_plugin: Run a plugin command (requires pluginId + command)\n- open_view: Open a named view\n- toggle_sidebar: Toggle sidebar visibility\n- close_plugin_window: Close plugin window (by windowID/title, or omit both to close all)\n- list_plugin_windows: List open plugin windows\n- backup: Create a full backup of all notes, calendars, themes, filters, and plugin data. Old backups are pruned automatically.',
1865
+ inputSchema: {
1866
+ type: 'object',
1867
+ properties: {
1868
+ action: {
1869
+ type: 'string',
1870
+ enum: ['open_note', 'open_today', 'search', 'run_plugin', 'open_view', 'toggle_sidebar', 'close_plugin_window', 'list_plugin_windows', 'backup'],
1871
+ description: 'Action: open_note | open_today | search | run_plugin | open_view | toggle_sidebar | close_plugin_window | list_plugin_windows | backup',
1879
1872
  },
1880
- required: ['action'],
1881
- },
1882
- }, {
1883
- name: 'noteplan_plugins',
1884
- description: 'Plugin management: list, create, delete, install, read source/log, update HTML, screenshot.\n\nActions:\n- list: List installed plugins\n- list_available: List plugins from online repository\n- create: Create plugin with HTML view (requires pluginId, pluginName, commandName, html)\n- delete: Delete plugin (requires pluginId + confirmationToken)\n- install: Install from repository (requires pluginId)\n- log: Read plugin console log (requires pluginId)\n- source: Read plugin source (requires pluginId)\n- update_html: Apply find/replace patches (requires pluginId + patches)\n- screenshot: Capture plugin WebView screenshot (requires pluginId)',
1885
- inputSchema: {
1886
- type: 'object',
1887
- properties: {
1888
- action: {
1889
- type: 'string',
1890
- enum: ['list', 'list_available', 'create', 'delete', 'install', 'log', 'source', 'update_html', 'screenshot'],
1891
- description: 'Action to perform',
1892
- },
1893
- pluginId: {
1894
- type: 'string',
1895
- description: 'Plugin ID — used by create, delete, install, log, source, update_html, screenshot',
1896
- },
1897
- pluginName: {
1898
- type: 'string',
1899
- description: 'Display name — used by create',
1900
- },
1901
- commandName: {
1902
- type: 'string',
1903
- description: 'Command name — used by create',
1904
- },
1905
- html: {
1906
- type: 'string',
1907
- description: 'HTML content — used by create',
1908
- },
1909
- icon: {
1910
- type: 'string',
1911
- description: 'Font Awesome icon name — used by create',
1912
- },
1913
- iconColor: {
1914
- type: 'string',
1915
- description: 'Tailwind color — used by create',
1916
- },
1917
- displayMode: {
1918
- type: 'string',
1919
- enum: ['main', 'split', 'window'],
1920
- description: 'Display mode (default: main) — used by create',
1921
- },
1922
- autoLaunch: {
1923
- type: 'boolean',
1924
- description: 'Auto-reload and run — used by create, update_html',
1925
- },
1926
- patches: {
1927
- type: 'array',
1928
- items: {
1929
- type: 'object',
1930
- properties: {
1931
- find: { type: 'string' },
1932
- replace: { type: 'string' },
1933
- },
1934
- required: ['find', 'replace'],
1935
- },
1936
- description: 'Find/replace patches — used by update_html',
1937
- },
1938
- query: {
1939
- type: 'string',
1940
- description: 'Filter/search — used by list, list_available, source',
1941
- },
1942
- includeBeta: {
1943
- type: 'boolean',
1944
- description: 'Include beta plugins — used by list_available',
1945
- },
1946
- tail: {
1947
- type: 'integer',
1948
- description: 'Return last N lines — used by log',
1949
- },
1950
- clear: {
1951
- type: 'boolean',
1952
- description: 'Clear log after reading — used by log',
1953
- },
1954
- startLine: {
1955
- type: 'integer',
1956
- description: 'Start line (1-based) — used by source',
1957
- },
1958
- endLine: {
1959
- type: 'integer',
1960
- description: 'End line (1-based) — used by source',
1961
- },
1962
- contextLines: {
1963
- type: 'integer',
1964
- description: 'Context lines around matches — used by source',
1965
- },
1966
- confirmationToken: {
1967
- type: 'string',
1968
- description: 'Token for confirmation — used by delete',
1969
- },
1873
+ title: {
1874
+ type: 'string',
1875
+ description: 'Note title — used by open_note; window title — used by close_plugin_window',
1876
+ },
1877
+ filename: {
1878
+ type: 'string',
1879
+ description: 'Filename — used by open_note',
1880
+ },
1881
+ inNewWindow: {
1882
+ type: 'boolean',
1883
+ description: 'Open in new window used by open_note',
1884
+ },
1885
+ inSplitView: {
1886
+ type: 'boolean',
1887
+ description: 'Open in split view — used by open_note',
1888
+ },
1889
+ query: {
1890
+ type: 'string',
1891
+ description: 'Search text — used by search',
1892
+ },
1893
+ pluginId: {
1894
+ type: 'string',
1895
+ description: 'Plugin ID — used by run_plugin',
1896
+ },
1897
+ command: {
1898
+ type: 'string',
1899
+ description: 'Command name — used by run_plugin',
1900
+ },
1901
+ arguments: {
1902
+ type: 'string',
1903
+ description: 'JSON arguments string — used by run_plugin',
1904
+ },
1905
+ name: {
1906
+ type: 'string',
1907
+ description: 'View name — used by open_view',
1908
+ },
1909
+ windowID: {
1910
+ type: 'string',
1911
+ description: 'Window ID — used by close_plugin_window',
1970
1912
  },
1971
- required: ['action'],
1972
1913
  },
1973
- }, {
1974
- name: 'noteplan_themes',
1975
- description: 'Theme management: list, get, save, set active.\n\nActions:\n- list: List all themes and active theme names\n- get: Read a custom theme JSON (requires filename)\n- save: Create/update a custom theme (requires filename + theme)\n- set_active: Activate a theme (requires name)',
1976
- inputSchema: {
1977
- type: 'object',
1978
- properties: {
1979
- action: {
1980
- type: 'string',
1981
- enum: ['list', 'get', 'save', 'set_active'],
1982
- description: 'Action to perform',
1983
- },
1984
- filename: {
1985
- type: 'string',
1986
- description: 'Theme filename used by get, save',
1987
- },
1988
- name: {
1989
- type: 'string',
1990
- description: 'Theme name — used by set_active',
1991
- },
1992
- theme: {
1914
+ required: ['action'],
1915
+ },
1916
+ });
1917
+ // Always register all tools — if NotePlan is too old for a specific action,
1918
+ // the action handler returns a helpful upgrade message instead of hiding the tool entirely.
1919
+ toolDefinitions.push({
1920
+ name: 'noteplan_plugins',
1921
+ description: 'Plugin management: list, create, delete, install, read source/log, update HTML, screenshot.\n\nActions:\n- list: List installed plugins\n- list_available: List plugins from online repository\n- create: Create plugin with HTML view (requires pluginId, pluginName, commandName, html)\n- delete: Delete plugin (requires pluginId + confirmationToken)\n- install: Install from repository (requires pluginId)\n- log: Read plugin console log (requires pluginId)\n- source: Read plugin source (requires pluginId)\n- update_html: Apply find/replace patches (requires pluginId + patches)\n- screenshot: Capture plugin WebView screenshot (requires pluginId)',
1922
+ inputSchema: {
1923
+ type: 'object',
1924
+ properties: {
1925
+ action: {
1926
+ type: 'string',
1927
+ enum: ['list', 'list_available', 'create', 'delete', 'install', 'log', 'source', 'update_html', 'screenshot'],
1928
+ description: 'Action: list | list_available | create | delete | install | log | source | update_html | screenshot',
1929
+ },
1930
+ pluginId: {
1931
+ type: 'string',
1932
+ description: 'Plugin ID — used by create, delete, install, log, source, update_html, screenshot',
1933
+ },
1934
+ pluginName: {
1935
+ type: 'string',
1936
+ description: 'Display name — used by create',
1937
+ },
1938
+ commandName: {
1939
+ type: 'string',
1940
+ description: 'Command name — used by create',
1941
+ },
1942
+ html: {
1943
+ type: 'string',
1944
+ description: 'HTML content — used by create',
1945
+ },
1946
+ icon: {
1947
+ type: 'string',
1948
+ description: 'Font Awesome icon name — used by create',
1949
+ },
1950
+ iconColor: {
1951
+ type: 'string',
1952
+ description: 'Tailwind color — used by create',
1953
+ },
1954
+ displayMode: {
1955
+ type: 'string',
1956
+ enum: ['main', 'split', 'window'],
1957
+ description: 'Display mode (default: main) — used by create',
1958
+ },
1959
+ autoLaunch: {
1960
+ type: 'boolean',
1961
+ description: 'Auto-reload and run — used by create, update_html',
1962
+ },
1963
+ patches: {
1964
+ type: 'array',
1965
+ items: {
1993
1966
  type: 'object',
1994
- description: 'Theme object — used by save',
1995
1967
  properties: {
1996
- name: { type: 'string' },
1997
- style: { type: 'string', enum: ['Light', 'Dark'] },
1998
- author: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } },
1999
- editor: { type: 'object' },
2000
- styles: { type: 'object' },
1968
+ find: { type: 'string' },
1969
+ replace: { type: 'string' },
2001
1970
  },
1971
+ required: ['find', 'replace'],
2002
1972
  },
2003
- setActive: {
2004
- type: 'boolean',
2005
- description: 'Apply theme immediately — used by save (default: true)',
2006
- },
2007
- mode: {
2008
- type: 'string',
2009
- enum: ['light', 'dark', 'auto'],
2010
- description: 'Mode to apply for — used by save, set_active',
1973
+ description: 'Find/replace patches — used by update_html',
1974
+ },
1975
+ query: {
1976
+ type: 'string',
1977
+ description: 'Filter/search — used by list, list_available, source',
1978
+ },
1979
+ includeBeta: {
1980
+ type: 'boolean',
1981
+ description: 'Include beta plugins — used by list_available',
1982
+ },
1983
+ tail: {
1984
+ type: 'integer',
1985
+ description: 'Return last N lines — used by log',
1986
+ },
1987
+ clear: {
1988
+ type: 'boolean',
1989
+ description: 'Clear log after reading — used by log',
1990
+ },
1991
+ startLine: {
1992
+ type: 'integer',
1993
+ description: 'Start line (1-based) — used by source',
1994
+ },
1995
+ endLine: {
1996
+ type: 'integer',
1997
+ description: 'End line (1-based) — used by source',
1998
+ },
1999
+ contextLines: {
2000
+ type: 'integer',
2001
+ description: 'Context lines around matches — used by source',
2002
+ },
2003
+ confirmationToken: {
2004
+ type: 'string',
2005
+ description: 'Token for confirmation — used by delete',
2006
+ },
2007
+ },
2008
+ required: ['action'],
2009
+ },
2010
+ }, {
2011
+ name: 'noteplan_themes',
2012
+ description: 'Theme management: list, get, save, set active.\n\nActions:\n- list: List all themes and active theme names\n- get: Read a custom theme JSON (requires filename)\n- save: Create/update a custom theme (requires filename + theme)\n- set_active: Activate a theme (requires name)',
2013
+ inputSchema: {
2014
+ type: 'object',
2015
+ properties: {
2016
+ action: {
2017
+ type: 'string',
2018
+ enum: ['list', 'get', 'save', 'set_active'],
2019
+ description: 'Action: list | get | save | set_active',
2020
+ },
2021
+ filename: {
2022
+ type: 'string',
2023
+ description: 'Theme filename — used by get, save',
2024
+ },
2025
+ name: {
2026
+ type: 'string',
2027
+ description: 'Theme name — used by set_active',
2028
+ },
2029
+ theme: {
2030
+ type: 'object',
2031
+ description: 'Theme object — used by save',
2032
+ properties: {
2033
+ name: { type: 'string' },
2034
+ style: { type: 'string', enum: ['Light', 'Dark'] },
2035
+ author: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } },
2036
+ editor: { type: 'object' },
2037
+ styles: { type: 'object' },
2011
2038
  },
2012
2039
  },
2013
- required: ['action'],
2040
+ setActive: {
2041
+ type: 'boolean',
2042
+ description: 'Apply theme immediately — used by save (default: true)',
2043
+ },
2044
+ mode: {
2045
+ type: 'string',
2046
+ enum: ['light', 'dark', 'auto'],
2047
+ description: 'Mode to apply for — used by save, set_active',
2048
+ },
2014
2049
  },
2015
- });
2016
- }
2050
+ required: ['action'],
2051
+ },
2052
+ }, {
2053
+ name: 'noteplan_templates',
2054
+ description: 'Template operations: list available templates or render a template.\n\nActions:\n- list: List templates from @Templates folder with their types and preview\n- render: Render a template by title (saved template) or raw content string (for debugging). Rendering requires a recent NotePlan build.\n\nDebugging workflow: After creating or editing a template, use render with its title or raw content to verify the output. Check variables, date formatting, and logic. If rendering fails or produces unexpected output, read the plugin log via noteplan_plugins(action: "log", pluginId: "np.Templating") for error details.\n\nTemplate syntax: <%- expr %> (output), <% code %> (logic), <%= expr %> (escaped output). Common helpers: date.now("YYYY-MM-DD"), web.weather(), date.tomorrow("format").',
2055
+ inputSchema: {
2056
+ type: 'object',
2057
+ properties: {
2058
+ action: {
2059
+ type: 'string',
2060
+ enum: ['list', 'render'],
2061
+ description: 'Action: list | render',
2062
+ },
2063
+ templateTitle: {
2064
+ type: 'string',
2065
+ description: 'Template title — used by render (loads a saved template by title)',
2066
+ },
2067
+ content: {
2068
+ type: 'string',
2069
+ description: 'Raw template content string — used by render (renders arbitrary template code for debugging)',
2070
+ },
2071
+ folder: {
2072
+ type: 'string',
2073
+ description: 'Template subfolder — used by list (default: @Templates)',
2074
+ },
2075
+ limit: {
2076
+ type: 'number',
2077
+ description: 'Maximum results — used by list',
2078
+ },
2079
+ offset: {
2080
+ type: 'number',
2081
+ description: 'Pagination offset — used by list',
2082
+ },
2083
+ cursor: {
2084
+ type: 'string',
2085
+ description: 'Cursor from previous page — used by list',
2086
+ },
2087
+ },
2088
+ required: ['action'],
2089
+ },
2090
+ }, {
2091
+ name: 'noteplan_attachments',
2092
+ description: 'Attachment operations: add files/images to notes, list attachments, get attachment data, or move between notes.\n\nActions:\n- add: Write a file to the note\'s _attachments folder. Requires data (base64) + attachmentFilename. Returns markdownLink — use noteplan_edit_content to place it in the note (insertLink defaults to false).\n- list: List all attachments for a note with filenames, sizes, and markdown links.\n- get: Get attachment metadata. Set includeData=true for base64 content. Use maxDataSize to cap large images.\n- move: Move an attachment between notes. Source note via id/filename/title/date, destination via destinationId/destinationFilename/destinationTitle/destinationDate. Moves file, removes old link from source, returns new markdownLink.\n\nAttachments are stored in {notename}_attachments/ sibling folder. Images: ![image](path), files: ![file](path). Use attachmentFilename for both add, get, and move.',
2093
+ inputSchema: {
2094
+ type: 'object',
2095
+ properties: {
2096
+ action: {
2097
+ type: 'string',
2098
+ enum: ['add', 'list', 'get', 'move'],
2099
+ description: 'Action: add | list | get | move',
2100
+ },
2101
+ id: { type: 'string', description: 'Source note ID — used by all actions' },
2102
+ filename: { type: 'string', description: 'Source note filename/path — used by all actions' },
2103
+ title: { type: 'string', description: 'Source note title — used by all actions' },
2104
+ date: { type: 'string', description: 'Source calendar note date (YYYYMMDD or YYYY-MM-DD) — used by all actions' },
2105
+ query: { type: 'string', description: 'Search query to find the source note' },
2106
+ space: { type: 'string', description: 'Space name or ID' },
2107
+ data: { type: 'string', description: 'Base64-encoded file data — required for add' },
2108
+ attachmentFilename: { type: 'string', description: 'Attachment filename (e.g. "photo.png") — required for add, get, and move' },
2109
+ mimeType: { type: 'string', description: 'MIME type hint (e.g. "image/png") — used by add' },
2110
+ insertLink: { type: 'boolean', description: 'Auto-insert markdown link into note — used by add (default: false). Prefer placing links yourself via noteplan_edit_content.' },
2111
+ destinationId: { type: 'string', description: 'Destination note ID — used by move' },
2112
+ destinationFilename: { type: 'string', description: 'Destination note filename — used by move' },
2113
+ destinationTitle: { type: 'string', description: 'Destination note title — used by move' },
2114
+ destinationDate: { type: 'string', description: 'Destination calendar note date — used by move' },
2115
+ includeData: { type: 'boolean', description: 'Include base64 data in response — used by get (default: false)' },
2116
+ maxDataSize: { type: 'number', description: 'Max file size in bytes for data inclusion — used by get. Files exceeding this are skipped.' },
2117
+ },
2118
+ required: ['action'],
2119
+ },
2120
+ });
2017
2121
  const annotatedToolDefinitions = toolDefinitions.map((tool) => ({
2018
2122
  ...tool,
2019
2123
  inputSchema: withDebugTimingsInputSchema(tool.inputSchema),
@@ -2121,7 +2225,7 @@ export function createServer() {
2121
2225
  case 'remove_property':
2122
2226
  result = noteTools.removeProperty(args);
2123
2227
  break;
2124
- default: throw new Error(`Unknown action: ${action}`);
2228
+ default: throw new Error(`Unknown action: "${action}". Valid actions: create, update, delete, move, restore, rename, set_property, remove_property`);
2125
2229
  }
2126
2230
  break;
2127
2231
  }
@@ -2152,7 +2256,7 @@ export function createServer() {
2152
2256
  case 'replace_lines':
2153
2257
  result = noteTools.replaceLines(a);
2154
2258
  break;
2155
- default: throw new Error(`Unknown action: ${action}`);
2259
+ default: throw new Error(`Unknown action: "${action}". Valid actions: insert, append, delete_lines, edit_line, replace_lines (snake_case required)`);
2156
2260
  }
2157
2261
  break;
2158
2262
  }
@@ -2166,6 +2270,10 @@ export function createServer() {
2166
2270
  if (a.scheduleDate && a.content) {
2167
2271
  a.content = appendScheduleDate(a.content, a.scheduleDate);
2168
2272
  }
2273
+ // Derive `target` for addTaskToNote from date/filename when not provided
2274
+ if (!a.target && (a.date || a.filename)) {
2275
+ a.target = a.date || a.filename;
2276
+ }
2169
2277
  const action = a?.action;
2170
2278
  switch (action) {
2171
2279
  case 'get':
@@ -2346,7 +2454,67 @@ export function createServer() {
2346
2454
  case 'list_plugin_windows':
2347
2455
  result = uiTools.listPluginWindows(args);
2348
2456
  break;
2349
- default: throw new Error(`Unknown action: ${action}`);
2457
+ case 'backup': {
2458
+ if (versionInfo.build < MIN_BUILD_CREATE_BACKUP) {
2459
+ result = { success: false, error: `Backup requires NotePlan build ${MIN_BUILD_CREATE_BACKUP}+. Current: ${versionInfo.build}.`, code: 'ERR_VERSION_GATE' };
2460
+ }
2461
+ else {
2462
+ result = uiTools.createBackup(args);
2463
+ }
2464
+ break;
2465
+ }
2466
+ default: {
2467
+ // Help the LLM recover from common mistakes (camelCase, missing underscore, etc.)
2468
+ const UI_ACTION_ALIASES = {
2469
+ open: 'open_note', opennote: 'open_note', open_notes: 'open_note',
2470
+ opentoday: 'open_today', today: 'open_today',
2471
+ openview: 'open_view', view: 'open_view',
2472
+ runplugin: 'run_plugin', plugin: 'run_plugin',
2473
+ togglesidebar: 'toggle_sidebar', sidebar: 'toggle_sidebar',
2474
+ closepluginwindow: 'close_plugin_window',
2475
+ listpluginwindows: 'list_plugin_windows',
2476
+ navigate: 'open_note', show: 'open_note', select: 'open_note',
2477
+ };
2478
+ const normalized = (action || '').toLowerCase().replace(/[\s_-]/g, '');
2479
+ const resolved = UI_ACTION_ALIASES[normalized];
2480
+ if (resolved) {
2481
+ args.action = resolved;
2482
+ // Re-dispatch with corrected action
2483
+ switch (resolved) {
2484
+ case 'open_note':
2485
+ result = uiTools.openNote(args);
2486
+ break;
2487
+ case 'open_today':
2488
+ result = uiTools.openToday(args);
2489
+ break;
2490
+ case 'search':
2491
+ result = uiTools.searchNotes(args);
2492
+ break;
2493
+ case 'run_plugin':
2494
+ result = uiTools.runPlugin(args);
2495
+ break;
2496
+ case 'open_view':
2497
+ result = uiTools.openView(args);
2498
+ break;
2499
+ case 'toggle_sidebar':
2500
+ result = uiTools.toggleSidebar(args);
2501
+ break;
2502
+ case 'close_plugin_window':
2503
+ result = uiTools.closePluginWindow(args);
2504
+ break;
2505
+ case 'list_plugin_windows':
2506
+ result = uiTools.listPluginWindows(args);
2507
+ break;
2508
+ case 'backup':
2509
+ result = uiTools.createBackup(args);
2510
+ break;
2511
+ default: throw new Error(`Unknown action: ${action}. Valid actions: open_note, open_today, search, run_plugin, open_view, toggle_sidebar, close_plugin_window, list_plugin_windows, backup`);
2512
+ }
2513
+ }
2514
+ else {
2515
+ throw new Error(`Unknown action: "${action}". Valid actions: open_note, open_today, search, run_plugin, open_view, toggle_sidebar, close_plugin_window, list_plugin_windows, backup`);
2516
+ }
2517
+ }
2350
2518
  }
2351
2519
  break;
2352
2520
  }
@@ -2422,6 +2590,38 @@ export function createServer() {
2422
2590
  }
2423
2591
  break;
2424
2592
  }
2593
+ case 'noteplan_templates': {
2594
+ const action = args?.action;
2595
+ switch (action) {
2596
+ case 'list':
2597
+ result = templateTools.listTemplates(args);
2598
+ break;
2599
+ case 'render':
2600
+ result = templateTools.renderTemplate(args);
2601
+ break;
2602
+ default: throw new Error(`Unknown action: ${action}`);
2603
+ }
2604
+ break;
2605
+ }
2606
+ case 'noteplan_attachments': {
2607
+ const action = args?.action;
2608
+ switch (action) {
2609
+ case 'add':
2610
+ result = attachmentTools.addAttachment(args);
2611
+ break;
2612
+ case 'list':
2613
+ result = attachmentTools.listAttachments(args);
2614
+ break;
2615
+ case 'get':
2616
+ result = attachmentTools.getAttachment(args);
2617
+ break;
2618
+ case 'move':
2619
+ result = attachmentTools.moveAttachment(args);
2620
+ break;
2621
+ default: throw new Error(`Unknown action: ${action}. Valid actions: add, list, get, move`);
2622
+ }
2623
+ break;
2624
+ }
2425
2625
  default:
2426
2626
  throw new Error(`Unknown tool: ${name} (normalized: ${normalizedName})`);
2427
2627
  }