aiquila-mcp 0.3.28 → 0.3.30

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 (50) hide show
  1. package/dist/auth/provider.js +5 -2
  2. package/dist/server.js +2 -0
  3. package/dist/tool-registry.js +22 -5
  4. package/dist/tools/apps/absence.js +21 -0
  5. package/dist/tools/apps/activity.js +14 -0
  6. package/dist/tools/apps/aiquila.js +21 -0
  7. package/dist/tools/apps/announcements.js +21 -0
  8. package/dist/tools/apps/assistant.js +28 -0
  9. package/dist/tools/apps/bookmarks.js +91 -0
  10. package/dist/tools/apps/calendar.js +42 -0
  11. package/dist/tools/apps/circles.js +56 -0
  12. package/dist/tools/apps/contacts.js +42 -0
  13. package/dist/tools/apps/cookbook.js +42 -0
  14. package/dist/tools/apps/cowork.js +67 -6
  15. package/dist/tools/apps/deck.js +84 -0
  16. package/dist/tools/apps/forms.js +175 -0
  17. package/dist/tools/apps/groups.js +28 -0
  18. package/dist/tools/apps/mail.js +70 -0
  19. package/dist/tools/apps/maps.js +175 -0
  20. package/dist/tools/apps/news.js +119 -0
  21. package/dist/tools/apps/notes.js +35 -0
  22. package/dist/tools/apps/notifications.js +28 -0
  23. package/dist/tools/apps/passman.js +21 -0
  24. package/dist/tools/apps/photos.js +77 -0
  25. package/dist/tools/apps/polls.js +147 -0
  26. package/dist/tools/apps/projects.js +49 -0
  27. package/dist/tools/apps/recommendations.js +7 -0
  28. package/dist/tools/apps/registration.js +21 -0
  29. package/dist/tools/apps/shares.js +70 -0
  30. package/dist/tools/apps/social-sharing.js +7 -0
  31. package/dist/tools/apps/talk.js +70 -0
  32. package/dist/tools/apps/tasks.js +42 -0
  33. package/dist/tools/apps/terms-of-service.js +35 -0
  34. package/dist/tools/apps/text.js +35 -0
  35. package/dist/tools/apps/translate.js +7 -0
  36. package/dist/tools/apps/trash.js +21 -0
  37. package/dist/tools/apps/user-status.js +35 -0
  38. package/dist/tools/apps/users.js +28 -0
  39. package/dist/tools/apps/versions.js +14 -0
  40. package/dist/tools/system/apps.js +42 -0
  41. package/dist/tools/system/files.js +105 -0
  42. package/dist/tools/system/occ.js +7 -0
  43. package/dist/tools/system/search.js +14 -0
  44. package/dist/tools/system/security.js +14 -0
  45. package/dist/tools/system/status.js +21 -0
  46. package/dist/tools/system/tags.js +42 -0
  47. package/dist/tools/types.js +0 -3
  48. package/dist/transports/http.js +51 -3
  49. package/dist/transports/lazy-auth.js +63 -0
  50. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { createHash, timingSafeEqual } from 'node:crypto';
3
3
  import { SignJWT, jwtVerify } from 'jose';
4
- import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
4
+ import { InvalidGrantError, InvalidTokenError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
5
5
  import { ClientsStore, CodeStore, RefreshStore } from './store.js';
6
6
  import { logger } from '../logger.js';
7
7
  // --- JWT helpers (HMAC-SHA256 via jose) ---
@@ -196,7 +196,10 @@ export class NextcloudOAuthProvider {
196
196
  const claims = await verifyJwt(token, this.getSecret(), issuer ? { issuer, audience: new URL('/mcp', issuer).toString() } : undefined);
197
197
  if (!claims) {
198
198
  logger.warn('[auth] Access token verification failed: invalid or expired token');
199
- throw new Error('Invalid or expired access token');
199
+ // InvalidTokenError (not a bare Error) so the SDK answers 401 with a
200
+ // WWW-Authenticate challenge. A bare Error becomes a 500, and clients
201
+ // treat that as a server fault rather than a cue to re-authenticate.
202
+ throw new InvalidTokenError('Invalid or expired access token');
200
203
  }
201
204
  return {
202
205
  token,
package/dist/server.js CHANGED
@@ -25,7 +25,9 @@ export async function createServer() {
25
25
  for (const tool of toolSet) {
26
26
  const name = tool.name;
27
27
  server.registerTool(name, {
28
+ title: tool.title,
28
29
  description: tool.description,
30
+ annotations: tool.annotations,
29
31
  inputSchema: tool.inputSchema,
30
32
  }, async (...args) => {
31
33
  const start = Date.now();
@@ -156,7 +156,10 @@ async function detectEnabledApps() {
156
156
  return apps;
157
157
  }
158
158
  catch (err) {
159
- logger.warn({ err: err instanceof Error ? err.message : String(err) }, '[startup] Could not detect enabled apps — registering all tools');
159
+ logger.warn({ err: err instanceof Error ? err.message : String(err) }, '[startup] Could not detect enabled apps — registering ALL tools as a fallback. ' +
160
+ '/ocs/v2.php/cloud/apps requires admin credentials; a non-admin NEXTCLOUD_USER always ' +
161
+ 'lands here. Set MCP_TOOLS to an explicit category/tool whitelist, or use an admin app ' +
162
+ 'password, to register only the tools you need.');
160
163
  return null;
161
164
  }
162
165
  }
@@ -186,6 +189,15 @@ function filterByEnabledApps(enabledApps) {
186
189
  }
187
190
  // Cached result — cleared only on process restart
188
191
  let cachedToolSets = null;
192
+ /**
193
+ * Tool count above which we warn. Not a protocol limit — no MCP client documents one —
194
+ * but very large tool lists are worth flagging, and reaching this number usually means
195
+ * app-detection fell back to registering everything.
196
+ */
197
+ export const HIGH_TOOL_COUNT = 100;
198
+ function countTools(toolSets) {
199
+ return toolSets.reduce((sum, ts) => sum + ts.length, 0);
200
+ }
189
201
  /**
190
202
  * Returns filtered tool arrays based on MCP_TOOLS env var or auto-detection.
191
203
  * Result is cached after first call.
@@ -194,16 +206,21 @@ export async function getFilteredToolSets() {
194
206
  if (cachedToolSets)
195
207
  return cachedToolSets;
196
208
  const mcpToolsFilter = parseMcpTools();
209
+ let source;
197
210
  if (mcpToolsFilter) {
198
211
  cachedToolSets = filterByExplicitList(mcpToolsFilter);
199
- const toolCount = cachedToolSets.reduce((sum, ts) => sum + ts.length, 0);
200
- logger.info({ tools: toolCount, source: 'MCP_TOOLS' }, '[startup] Tool filtering applied');
212
+ source = 'MCP_TOOLS';
201
213
  }
202
214
  else {
203
215
  const enabledApps = await detectEnabledApps();
204
216
  cachedToolSets = filterByEnabledApps(enabledApps);
205
- const toolCount = cachedToolSets.reduce((sum, ts) => sum + ts.length, 0);
206
- logger.info({ tools: toolCount, source: enabledApps ? 'app-detection' : 'all (fallback)' }, '[startup] Tool filtering applied');
217
+ source = enabledApps ? 'app-detection' : 'all (fallback)';
218
+ }
219
+ const toolCount = countTools(cachedToolSets);
220
+ logger.info({ tools: toolCount, source }, '[startup] Tool filtering applied');
221
+ if (toolCount > HIGH_TOOL_COUNT) {
222
+ logger.warn({ tools: toolCount, threshold: HIGH_TOOL_COUNT, source }, `[startup] Registering ${toolCount} tools. Set MCP_TOOLS to a category/tool whitelist to ` +
223
+ 'narrow this down — some clients degrade on very large tool lists.');
207
224
  }
208
225
  return cachedToolSets;
209
226
  }
@@ -11,6 +11,13 @@ import { getNextcloudConfig } from '../types.js';
11
11
  // ---------------------------------------------------------------------------
12
12
  export const getOutOfOfficeTool = {
13
13
  name: 'get_out_of_office',
14
+ title: 'Get Out-of-Office Status',
15
+ annotations: {
16
+ readOnlyHint: true,
17
+ destructiveHint: false,
18
+ idempotentHint: true,
19
+ openWorldHint: false,
20
+ },
14
21
  description: "Get a user's current out-of-office / absence status (NC 28+)",
15
22
  inputSchema: z.object({
16
23
  userId: z
@@ -49,6 +56,13 @@ export const getOutOfOfficeTool = {
49
56
  // ---------------------------------------------------------------------------
50
57
  export const setOutOfOfficeTool = {
51
58
  name: 'set_out_of_office',
59
+ title: 'Set Out-of-Office Status',
60
+ annotations: {
61
+ readOnlyHint: false,
62
+ destructiveHint: true,
63
+ idempotentHint: true,
64
+ openWorldHint: false,
65
+ },
52
66
  description: 'Set an out-of-office / absence period with status message (NC 28+)',
53
67
  inputSchema: z.object({
54
68
  firstDay: z.string().describe('First day of absence (YYYY-MM-DD)'),
@@ -107,6 +121,13 @@ export const setOutOfOfficeTool = {
107
121
  // ---------------------------------------------------------------------------
108
122
  export const clearOutOfOfficeTool = {
109
123
  name: 'clear_out_of_office',
124
+ title: 'Clear Out-of-Office Status',
125
+ annotations: {
126
+ readOnlyHint: false,
127
+ destructiveHint: true,
128
+ idempotentHint: true,
129
+ openWorldHint: false,
130
+ },
110
131
  description: "Clear a user's out-of-office / absence status (NC 28+)",
111
132
  inputSchema: z.object({
112
133
  userId: z
@@ -15,6 +15,13 @@ function formatActivities(activities) {
15
15
  // ---------------------------------------------------------------------------
16
16
  export const listActivityTool = {
17
17
  name: 'list_activity',
18
+ title: 'List Activity',
19
+ annotations: {
20
+ readOnlyHint: true,
21
+ destructiveHint: false,
22
+ idempotentHint: true,
23
+ openWorldHint: false,
24
+ },
18
25
  description: 'List recent entries from the Nextcloud activity feed (file changes, shares, ' +
19
26
  'comments, calendar/contact edits, etc.) for the current user.',
20
27
  inputSchema: z.object({
@@ -77,6 +84,13 @@ export const listActivityTool = {
77
84
  // ---------------------------------------------------------------------------
78
85
  export const getObjectActivityTool = {
79
86
  name: 'get_object_activity',
87
+ title: 'Get Object Activity',
88
+ annotations: {
89
+ readOnlyHint: true,
90
+ destructiveHint: false,
91
+ idempotentHint: true,
92
+ openWorldHint: false,
93
+ },
80
94
  description: 'List the activity history for a single object, e.g. one file. Use object_type ' +
81
95
  "'files' with a file ID to see what happened to that file.",
82
96
  inputSchema: z.object({
@@ -21,6 +21,13 @@ async function runOCC(command, args = []) {
21
21
  */
22
22
  export const showConfigTool = {
23
23
  name: 'aiquila_show_config',
24
+ title: 'Show AIquila Configuration',
25
+ annotations: {
26
+ readOnlyHint: true,
27
+ destructiveHint: false,
28
+ idempotentHint: true,
29
+ openWorldHint: false,
30
+ },
24
31
  description: 'Show current AIquila configuration',
25
32
  inputSchema: z.object({}),
26
33
  handler: async () => {
@@ -52,6 +59,13 @@ export const showConfigTool = {
52
59
  */
53
60
  export const configureTool = {
54
61
  name: 'aiquila_configure',
62
+ title: 'Configure AIquila',
63
+ annotations: {
64
+ readOnlyHint: false,
65
+ destructiveHint: true,
66
+ idempotentHint: true,
67
+ openWorldHint: false,
68
+ },
55
69
  description: 'Configure AIquila settings (API key, model, tokens, timeout)',
56
70
  inputSchema: z.object({
57
71
  apiKey: z.string().optional().describe('Anthropic API key'),
@@ -104,6 +118,13 @@ export const configureTool = {
104
118
  */
105
119
  export const testTool = {
106
120
  name: 'aiquila_test',
121
+ title: 'Test AIquila Claude Integration',
122
+ annotations: {
123
+ readOnlyHint: true,
124
+ destructiveHint: false,
125
+ idempotentHint: true,
126
+ openWorldHint: true,
127
+ },
107
128
  description: 'Test AIquila Claude API integration with a simple prompt',
108
129
  inputSchema: z.object({
109
130
  prompt: z.string().default('Hello, Claude!').describe('Test prompt to send to Claude API'),
@@ -21,6 +21,13 @@ function formatAnnouncements(items) {
21
21
  // ---------------------------------------------------------------------------
22
22
  export const listAnnouncementsTool = {
23
23
  name: 'list_announcements',
24
+ title: 'List Announcements',
25
+ annotations: {
26
+ readOnlyHint: true,
27
+ destructiveHint: false,
28
+ idempotentHint: true,
29
+ openWorldHint: false,
30
+ },
24
31
  description: 'List announcements from the Nextcloud Announcement Center (org-wide notices such as ' +
25
32
  'maintenance windows, events, or news). Returns newest first.',
26
33
  inputSchema: z.object({
@@ -70,6 +77,13 @@ export const listAnnouncementsTool = {
70
77
  // ---------------------------------------------------------------------------
71
78
  export const createAnnouncementTool = {
72
79
  name: 'create_announcement',
80
+ title: 'Create Announcement',
81
+ annotations: {
82
+ readOnlyHint: false,
83
+ destructiveHint: false,
84
+ idempotentHint: false,
85
+ openWorldHint: false,
86
+ },
73
87
  description: 'Create a new announcement in the Announcement Center. This is visible to all or ' +
74
88
  'selected groups and can trigger notifications/emails — use deliberately. Requires the ' +
75
89
  'configured Nextcloud user to be an admin.',
@@ -142,6 +156,13 @@ export const createAnnouncementTool = {
142
156
  // ---------------------------------------------------------------------------
143
157
  export const deleteAnnouncementTool = {
144
158
  name: 'delete_announcement',
159
+ title: 'Delete Announcement',
160
+ annotations: {
161
+ readOnlyHint: false,
162
+ destructiveHint: true,
163
+ idempotentHint: true,
164
+ openWorldHint: false,
165
+ },
145
166
  description: 'Delete an announcement by its ID. Requires the configured Nextcloud user to be an admin.',
146
167
  inputSchema: z.object({
147
168
  id: z.number().describe('The announcement ID to delete'),
@@ -14,6 +14,13 @@ const STATUS_LABELS = {
14
14
  // ---------------------------------------------------------------------------
15
15
  export const listTextTasksTool = {
16
16
  name: 'list_text_tasks',
17
+ title: 'List Text Tasks',
18
+ annotations: {
19
+ readOnlyHint: true,
20
+ destructiveHint: false,
21
+ idempotentHint: true,
22
+ openWorldHint: false,
23
+ },
17
24
  description: "List AI task types available in Nextcloud's TaskProcessing framework (summarize, headline, image-to-text, text-to-image, …). Shows which task types have at least one provider configured.",
18
25
  inputSchema: z.object({}),
19
26
  handler: async () => {
@@ -54,6 +61,13 @@ export const listTextTasksTool = {
54
61
  // ---------------------------------------------------------------------------
55
62
  export const processTextTool = {
56
63
  name: 'process_text',
64
+ title: 'Process Text with AI',
65
+ annotations: {
66
+ readOnlyHint: false,
67
+ destructiveHint: false,
68
+ idempotentHint: false,
69
+ openWorldHint: true,
70
+ },
57
71
  description: "Submit a text-processing task to Nextcloud's AI framework and return the task ID. Use get_task_result to poll until it completes. Common task types: 'core:text2text' (free prompt), 'core:summarize' (summary), 'core:headline' (headline), 'core:extract_topics' (topics).",
58
72
  inputSchema: z.object({
59
73
  taskType: z
@@ -107,6 +121,13 @@ export const processTextTool = {
107
121
  // ---------------------------------------------------------------------------
108
122
  export const getTaskResultTool = {
109
123
  name: 'get_task_result',
124
+ title: 'Get AI Task Result',
125
+ annotations: {
126
+ readOnlyHint: true,
127
+ destructiveHint: false,
128
+ idempotentHint: true,
129
+ openWorldHint: false,
130
+ },
110
131
  description: 'Get the status and result of a Nextcloud AI task previously created by process_text or generate_image. Status: 1=scheduled, 2=running, 3=successful, 4=failed.',
111
132
  inputSchema: z.object({
112
133
  taskId: z.number().describe('The task ID returned by process_text or generate_image'),
@@ -168,6 +189,13 @@ export const getTaskResultTool = {
168
189
  // ---------------------------------------------------------------------------
169
190
  export const generateImageTool = {
170
191
  name: 'generate_image',
192
+ title: 'Generate Image',
193
+ annotations: {
194
+ readOnlyHint: false,
195
+ destructiveHint: false,
196
+ idempotentHint: false,
197
+ openWorldHint: true,
198
+ },
171
199
  description: "Generate an image from a text prompt using Nextcloud's text-to-image AI provider (e.g. Stable Diffusion via LocalAI). Returns the task ID; use get_task_result to check completion.",
172
200
  inputSchema: z.object({
173
201
  prompt: z.string().describe('Text description of the image to generate'),
@@ -48,6 +48,13 @@ function formatFolderTree(folder, indent = 0) {
48
48
  // ── Bookmark Tools ──────────────────────────────────────────────────────────
49
49
  export const listBookmarksTool = {
50
50
  name: 'list_bookmarks',
51
+ title: 'List Bookmarks',
52
+ annotations: {
53
+ readOnlyHint: true,
54
+ destructiveHint: false,
55
+ idempotentHint: true,
56
+ openWorldHint: false,
57
+ },
51
58
  description: 'List bookmarks from Nextcloud Bookmarks app. Supports search, tag filtering, folder filtering, sorting, and pagination.',
52
59
  inputSchema: z.object({
53
60
  search: z
@@ -122,6 +129,13 @@ export const listBookmarksTool = {
122
129
  };
123
130
  export const getBookmarkTool = {
124
131
  name: 'get_bookmark',
132
+ title: 'Get Bookmark',
133
+ annotations: {
134
+ readOnlyHint: true,
135
+ destructiveHint: false,
136
+ idempotentHint: true,
137
+ openWorldHint: false,
138
+ },
125
139
  description: 'Get full details of a single bookmark by its ID.',
126
140
  inputSchema: z.object({
127
141
  id: z.number().describe('The bookmark ID'),
@@ -153,6 +167,13 @@ export const getBookmarkTool = {
153
167
  };
154
168
  export const createBookmarkTool = {
155
169
  name: 'create_bookmark',
170
+ title: 'Create Bookmark',
171
+ annotations: {
172
+ readOnlyHint: false,
173
+ destructiveHint: false,
174
+ idempotentHint: false,
175
+ openWorldHint: false,
176
+ },
156
177
  description: 'Create a new bookmark in Nextcloud Bookmarks. URL is required; title, description, tags, and folders are optional.',
157
178
  inputSchema: z.object({
158
179
  url: z.string().describe('The URL to bookmark'),
@@ -200,6 +221,13 @@ export const createBookmarkTool = {
200
221
  };
201
222
  export const updateBookmarkTool = {
202
223
  name: 'update_bookmark',
224
+ title: 'Update Bookmark',
225
+ annotations: {
226
+ readOnlyHint: false,
227
+ destructiveHint: true,
228
+ idempotentHint: true,
229
+ openWorldHint: false,
230
+ },
203
231
  description: 'Update an existing bookmark. Only provided fields are changed.',
204
232
  inputSchema: z.object({
205
233
  id: z.number().describe('The bookmark ID to update'),
@@ -250,6 +278,13 @@ export const updateBookmarkTool = {
250
278
  };
251
279
  export const deleteBookmarkTool = {
252
280
  name: 'delete_bookmark',
281
+ title: 'Delete Bookmark',
282
+ annotations: {
283
+ readOnlyHint: false,
284
+ destructiveHint: true,
285
+ idempotentHint: true,
286
+ openWorldHint: false,
287
+ },
253
288
  description: 'Delete a bookmark by its ID. This action is irreversible.',
254
289
  inputSchema: z.object({
255
290
  id: z.number().describe('The bookmark ID to delete'),
@@ -282,6 +317,13 @@ export const deleteBookmarkTool = {
282
317
  // ── Folder Tools ────────────────────────────────────────────────────────────
283
318
  export const listBookmarkFoldersTool = {
284
319
  name: 'list_bookmark_folders',
320
+ title: 'List Bookmark Folders',
321
+ annotations: {
322
+ readOnlyHint: true,
323
+ destructiveHint: false,
324
+ idempotentHint: true,
325
+ openWorldHint: false,
326
+ },
285
327
  description: 'List the bookmark folder hierarchy. Returns a tree structure of all folders.',
286
328
  inputSchema: z.object({
287
329
  root: z
@@ -328,6 +370,13 @@ export const listBookmarkFoldersTool = {
328
370
  };
329
371
  export const getBookmarkFolderContentsTool = {
330
372
  name: 'get_bookmark_folder_contents',
373
+ title: 'Get Bookmark Folder Contents',
374
+ annotations: {
375
+ readOnlyHint: true,
376
+ destructiveHint: false,
377
+ idempotentHint: true,
378
+ openWorldHint: false,
379
+ },
331
380
  description: 'Get the contents of a bookmark folder (bookmarks and subfolders).',
332
381
  inputSchema: z.object({
333
382
  id: z.number().describe('The folder ID (-1 for root)'),
@@ -382,6 +431,13 @@ export const getBookmarkFolderContentsTool = {
382
431
  };
383
432
  export const createBookmarkFolderTool = {
384
433
  name: 'create_bookmark_folder',
434
+ title: 'Create Bookmark Folder',
435
+ annotations: {
436
+ readOnlyHint: false,
437
+ destructiveHint: false,
438
+ idempotentHint: false,
439
+ openWorldHint: false,
440
+ },
385
441
  description: 'Create a new bookmark folder.',
386
442
  inputSchema: z.object({
387
443
  title: z.string().describe('Folder name'),
@@ -420,6 +476,13 @@ export const createBookmarkFolderTool = {
420
476
  };
421
477
  export const updateBookmarkFolderTool = {
422
478
  name: 'update_bookmark_folder',
479
+ title: 'Update Bookmark Folder',
480
+ annotations: {
481
+ readOnlyHint: false,
482
+ destructiveHint: true,
483
+ idempotentHint: true,
484
+ openWorldHint: false,
485
+ },
423
486
  description: 'Update a bookmark folder (rename or move to a different parent).',
424
487
  inputSchema: z.object({
425
488
  id: z.number().describe('The folder ID to update'),
@@ -458,6 +521,13 @@ export const updateBookmarkFolderTool = {
458
521
  };
459
522
  export const deleteBookmarkFolderTool = {
460
523
  name: 'delete_bookmark_folder',
524
+ title: 'Delete Bookmark Folder',
525
+ annotations: {
526
+ readOnlyHint: false,
527
+ destructiveHint: true,
528
+ idempotentHint: true,
529
+ openWorldHint: false,
530
+ },
461
531
  description: 'Delete a bookmark folder and all its contents. This action is irreversible.',
462
532
  inputSchema: z.object({
463
533
  id: z.number().describe('The folder ID to delete'),
@@ -490,6 +560,13 @@ export const deleteBookmarkFolderTool = {
490
560
  // ── Tag Tools ───────────────────────────────────────────────────────────────
491
561
  export const listBookmarkTagsTool = {
492
562
  name: 'list_bookmark_tags',
563
+ title: 'List Bookmark Tags',
564
+ annotations: {
565
+ readOnlyHint: true,
566
+ destructiveHint: false,
567
+ idempotentHint: true,
568
+ openWorldHint: false,
569
+ },
493
570
  description: 'List all tags used across bookmarks.',
494
571
  inputSchema: z.object({}),
495
572
  handler: async () => {
@@ -528,6 +605,13 @@ export const listBookmarkTagsTool = {
528
605
  };
529
606
  export const renameBookmarkTagTool = {
530
607
  name: 'rename_bookmark_tag',
608
+ title: 'Rename Bookmark Tag',
609
+ annotations: {
610
+ readOnlyHint: false,
611
+ destructiveHint: true,
612
+ idempotentHint: true,
613
+ openWorldHint: false,
614
+ },
531
615
  description: 'Rename a bookmark tag. All bookmarks with the old tag will be updated.',
532
616
  inputSchema: z.object({
533
617
  old_name: z.string().describe('Current tag name'),
@@ -563,6 +647,13 @@ export const renameBookmarkTagTool = {
563
647
  };
564
648
  export const deleteBookmarkTagTool = {
565
649
  name: 'delete_bookmark_tag',
650
+ title: 'Delete Bookmark Tag',
651
+ annotations: {
652
+ readOnlyHint: false,
653
+ destructiveHint: true,
654
+ idempotentHint: true,
655
+ openWorldHint: false,
656
+ },
566
657
  description: 'Delete a bookmark tag. The tag will be removed from all bookmarks.',
567
658
  inputSchema: z.object({
568
659
  name: z.string().describe('Tag name to delete'),
@@ -628,6 +628,13 @@ async function resolveEventByUid(calendarName, uid) {
628
628
  */
629
629
  export const listCalendarsTool = {
630
630
  name: 'list_calendars',
631
+ title: 'List Calendars',
632
+ annotations: {
633
+ readOnlyHint: true,
634
+ destructiveHint: false,
635
+ idempotentHint: true,
636
+ openWorldHint: false,
637
+ },
631
638
  description: 'List all calendars available to the current user, including their supported component types (events, tasks, journals) and metadata.',
632
639
  inputSchema: z.object({}),
633
640
  handler: async () => {
@@ -691,6 +698,13 @@ export const listCalendarsTool = {
691
698
  */
692
699
  export const listEventsTool = {
693
700
  name: 'list_events',
701
+ title: 'List Calendar Events',
702
+ annotations: {
703
+ readOnlyHint: true,
704
+ destructiveHint: false,
705
+ idempotentHint: true,
706
+ openWorldHint: false,
707
+ },
694
708
  description: 'List events from a Nextcloud calendar within an optional time range. Returns event details including time, location, attendees, recurrence, and UIDs.',
695
709
  inputSchema: z.object({
696
710
  calendarName: z
@@ -805,6 +819,13 @@ export const listEventsTool = {
805
819
  */
806
820
  export const getEventTool = {
807
821
  name: 'get_event',
822
+ title: 'Get Calendar Event',
823
+ annotations: {
824
+ readOnlyHint: true,
825
+ destructiveHint: false,
826
+ idempotentHint: true,
827
+ openWorldHint: false,
828
+ },
808
829
  description: 'Get detailed information about a single calendar event by its UID, including full description, attendees, and recurrence rules.',
809
830
  inputSchema: z.object({
810
831
  uid: z.string().describe('The UID of the event'),
@@ -854,6 +875,13 @@ export const getEventTool = {
854
875
  */
855
876
  export const createEventTool = {
856
877
  name: 'create_event',
878
+ title: 'Create Calendar Event',
879
+ annotations: {
880
+ readOnlyHint: false,
881
+ destructiveHint: false,
882
+ idempotentHint: false,
883
+ openWorldHint: false,
884
+ },
857
885
  description: 'Create a new calendar event in Nextcloud with date/time, location, description, attendees, and optional recurrence.',
858
886
  inputSchema: z.object({
859
887
  summary: z.string().describe('The event title'),
@@ -1075,6 +1103,13 @@ export const createEventTool = {
1075
1103
  */
1076
1104
  export const updateEventTool = {
1077
1105
  name: 'update_event',
1106
+ title: 'Update Calendar Event',
1107
+ annotations: {
1108
+ readOnlyHint: false,
1109
+ destructiveHint: true,
1110
+ idempotentHint: true,
1111
+ openWorldHint: false,
1112
+ },
1078
1113
  description: "Update an existing calendar event's fields by UID. Uses CalDAV ETag-based optimistic concurrency.",
1079
1114
  inputSchema: z.object({
1080
1115
  uid: z.string().describe('The UID of the event to update'),
@@ -1248,6 +1283,13 @@ export const updateEventTool = {
1248
1283
  */
1249
1284
  export const deleteEventTool = {
1250
1285
  name: 'delete_event',
1286
+ title: 'Delete Calendar Event',
1287
+ annotations: {
1288
+ readOnlyHint: false,
1289
+ destructiveHint: true,
1290
+ idempotentHint: true,
1291
+ openWorldHint: false,
1292
+ },
1251
1293
  description: 'Delete a calendar event from Nextcloud by UID. This action is irreversible.',
1252
1294
  inputSchema: z.object({
1253
1295
  uid: z.string().describe('The UID of the event to delete'),
@@ -67,6 +67,13 @@ function formatMember(m) {
67
67
  // ---------------------------------------------------------------------------
68
68
  export const circlesListTool = {
69
69
  name: 'circles_list',
70
+ title: 'List Circles',
71
+ annotations: {
72
+ readOnlyHint: true,
73
+ destructiveHint: false,
74
+ idempotentHint: true,
75
+ openWorldHint: false,
76
+ },
70
77
  description: 'List all circles/teams accessible to the current user. Returns circle IDs, names, owners, and configuration.',
71
78
  inputSchema: z.object({
72
79
  limit: z.number().optional().describe('Maximum number of circles to return'),
@@ -95,6 +102,13 @@ export const circlesListTool = {
95
102
  // ---------------------------------------------------------------------------
96
103
  export const circlesGetTool = {
97
104
  name: 'circles_get',
105
+ title: 'Get Circle',
106
+ annotations: {
107
+ readOnlyHint: true,
108
+ destructiveHint: false,
109
+ idempotentHint: true,
110
+ openWorldHint: false,
111
+ },
98
112
  description: 'Get detailed information about a specific circle/team, including its description, owner, and configuration flags.',
99
113
  inputSchema: z.object({
100
114
  circleId: z.string().describe('The circle ID to retrieve'),
@@ -128,6 +142,13 @@ export const circlesGetTool = {
128
142
  // ---------------------------------------------------------------------------
129
143
  export const circlesCreateTool = {
130
144
  name: 'circles_create',
145
+ title: 'Create Circle',
146
+ annotations: {
147
+ readOnlyHint: false,
148
+ destructiveHint: false,
149
+ idempotentHint: false,
150
+ openWorldHint: false,
151
+ },
131
152
  description: 'Create a new circle/team. The current user becomes the owner. Set personal=true for a private circle only visible to the owner.',
132
153
  inputSchema: z.object({
133
154
  name: z.string().describe('Name for the new circle'),
@@ -161,6 +182,13 @@ export const circlesCreateTool = {
161
182
  // ---------------------------------------------------------------------------
162
183
  export const circlesDeleteTool = {
163
184
  name: 'circles_delete',
185
+ title: 'Delete Circle',
186
+ annotations: {
187
+ readOnlyHint: false,
188
+ destructiveHint: true,
189
+ idempotentHint: true,
190
+ openWorldHint: false,
191
+ },
164
192
  description: 'Delete a circle/team. Requires owner privileges.',
165
193
  inputSchema: z.object({
166
194
  circleId: z.string().describe('The circle ID to delete'),
@@ -182,6 +210,13 @@ export const circlesDeleteTool = {
182
210
  // ---------------------------------------------------------------------------
183
211
  export const circlesListMembersTool = {
184
212
  name: 'circles_list_members',
213
+ title: 'List Circle Members',
214
+ annotations: {
215
+ readOnlyHint: true,
216
+ destructiveHint: false,
217
+ idempotentHint: true,
218
+ openWorldHint: false,
219
+ },
185
220
  description: 'List all members of a circle/team. Returns member IDs (needed for removal), user IDs, types, and permission levels.',
186
221
  inputSchema: z.object({
187
222
  circleId: z.string().describe('The circle ID to list members for'),
@@ -204,6 +239,13 @@ export const circlesListMembersTool = {
204
239
  // ---------------------------------------------------------------------------
205
240
  export const circlesAddMemberTool = {
206
241
  name: 'circles_add_member',
242
+ title: 'Add Circle Member',
243
+ annotations: {
244
+ readOnlyHint: false,
245
+ destructiveHint: false,
246
+ idempotentHint: true,
247
+ openWorldHint: false,
248
+ },
207
249
  description: 'Add a member to a circle/team. Requires moderator privileges or higher. Member type: 1=user (default), 2=group, 4=mail, 16=circle.',
208
250
  inputSchema: z.object({
209
251
  circleId: z.string().describe('The circle ID to add the member to'),
@@ -232,6 +274,13 @@ export const circlesAddMemberTool = {
232
274
  // ---------------------------------------------------------------------------
233
275
  export const circlesRemoveMemberTool = {
234
276
  name: 'circles_remove_member',
277
+ title: 'Remove Circle Member',
278
+ annotations: {
279
+ readOnlyHint: false,
280
+ destructiveHint: true,
281
+ idempotentHint: true,
282
+ openWorldHint: false,
283
+ },
235
284
  description: 'Remove a member from a circle/team. Use circles_list_members to get the memberId. Requires moderator privileges or higher.',
236
285
  inputSchema: z.object({
237
286
  circleId: z.string().describe('The circle ID to remove the member from'),
@@ -252,6 +301,13 @@ export const circlesRemoveMemberTool = {
252
301
  // ---------------------------------------------------------------------------
253
302
  export const circlesSearchTool = {
254
303
  name: 'circles_search',
304
+ title: 'Search Circles',
305
+ annotations: {
306
+ readOnlyHint: true,
307
+ destructiveHint: false,
308
+ idempotentHint: true,
309
+ openWorldHint: false,
310
+ },
255
311
  description: 'Search for circles/teams by name.',
256
312
  inputSchema: z.object({
257
313
  term: z.string().describe('Search term to match against circle names'),