@studiometa/productive-mcp 0.8.5 → 0.9.1

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 (72) hide show
  1. package/README.md +84 -412
  2. package/dist/auth.js +37 -36
  3. package/dist/auth.js.map +1 -1
  4. package/dist/crypto.js +100 -61
  5. package/dist/crypto.js.map +1 -1
  6. package/dist/formatters.d.ts +18 -2
  7. package/dist/formatters.d.ts.map +1 -1
  8. package/dist/handlers/attachments.d.ts +6 -0
  9. package/dist/handlers/attachments.d.ts.map +1 -0
  10. package/dist/handlers/bookings.d.ts +1 -1
  11. package/dist/handlers/bookings.d.ts.map +1 -1
  12. package/dist/handlers/budgets.d.ts +9 -0
  13. package/dist/handlers/budgets.d.ts.map +1 -0
  14. package/dist/handlers/comments.d.ts +1 -1
  15. package/dist/handlers/comments.d.ts.map +1 -1
  16. package/dist/handlers/companies.d.ts +6 -2
  17. package/dist/handlers/companies.d.ts.map +1 -1
  18. package/dist/handlers/deals.d.ts +6 -2
  19. package/dist/handlers/deals.d.ts.map +1 -1
  20. package/dist/handlers/discussions.d.ts +13 -0
  21. package/dist/handlers/discussions.d.ts.map +1 -0
  22. package/dist/handlers/help.d.ts.map +1 -1
  23. package/dist/handlers/index.d.ts.map +1 -1
  24. package/dist/handlers/pages.d.ts +13 -0
  25. package/dist/handlers/pages.d.ts.map +1 -0
  26. package/dist/handlers/people.d.ts +6 -2
  27. package/dist/handlers/people.d.ts.map +1 -1
  28. package/dist/handlers/projects.d.ts +6 -2
  29. package/dist/handlers/projects.d.ts.map +1 -1
  30. package/dist/handlers/reports.d.ts +1 -4
  31. package/dist/handlers/reports.d.ts.map +1 -1
  32. package/dist/handlers/resolve.d.ts +24 -0
  33. package/dist/handlers/resolve.d.ts.map +1 -0
  34. package/dist/handlers/services.d.ts +1 -1
  35. package/dist/handlers/services.d.ts.map +1 -1
  36. package/dist/handlers/tasks.d.ts +6 -2
  37. package/dist/handlers/tasks.d.ts.map +1 -1
  38. package/dist/handlers/time.d.ts +10 -2
  39. package/dist/handlers/time.d.ts.map +1 -1
  40. package/dist/handlers/timers.d.ts +1 -1
  41. package/dist/handlers/timers.d.ts.map +1 -1
  42. package/dist/handlers/types.d.ts +42 -3
  43. package/dist/handlers/types.d.ts.map +1 -1
  44. package/dist/handlers-BYE2INiR.js +2681 -0
  45. package/dist/handlers-BYE2INiR.js.map +1 -0
  46. package/dist/handlers.js +2 -5
  47. package/dist/hints.d.ts +16 -0
  48. package/dist/hints.d.ts.map +1 -1
  49. package/dist/http.js +139 -160
  50. package/dist/http.js.map +1 -1
  51. package/dist/index.js +74 -54
  52. package/dist/index.js.map +1 -1
  53. package/dist/oauth.js +285 -255
  54. package/dist/oauth.js.map +1 -1
  55. package/dist/schema.d.ts +17 -0
  56. package/dist/schema.d.ts.map +1 -1
  57. package/dist/server.js +67 -50
  58. package/dist/server.js.map +1 -1
  59. package/dist/stdio.js +85 -105
  60. package/dist/stdio.js.map +1 -1
  61. package/dist/tools.js +155 -145
  62. package/dist/tools.js.map +1 -1
  63. package/dist/version-D3sSBq_j.js +29 -0
  64. package/dist/version-D3sSBq_j.js.map +1 -0
  65. package/package.json +10 -10
  66. package/skills/SKILL.md +209 -13
  67. package/Dockerfile +0 -36
  68. package/dist/handlers.js.map +0 -1
  69. package/dist/index-CZpVCEu4.js +0 -1681
  70. package/dist/index-CZpVCEu4.js.map +0 -1
  71. package/dist/version-BPy06P7x.js +0 -21
  72. package/dist/version-BPy06P7x.js.map +0 -1
@@ -0,0 +1,2681 @@
1
+ import { ProductiveApi, formatAttachment, formatBooking, formatBudget, formatComment, formatCompany, formatDeal, formatDiscussion, formatListResponse, formatPage, formatPerson, formatProject, formatService, formatTask, formatTimeEntry, formatTimer } from "@studiometa/productive-api";
2
+ import { ResolveError, VALID_REPORT_TYPES, createBooking, createComment, createCompany, createDeal, createDiscussion, createPage, createTask, createTimeEntry, deleteAttachment, deleteDiscussion, deletePage, fromHandlerContext, getAttachment, getBooking, getBudget, getComment, getCompany, getDeal, getDiscussion, getPage, getPerson, getProject, getReport, getTask, getTimeEntry, getTimer, listAttachments, listBookings, listBudgets, listComments, listCompanies, listDeals, listDiscussions, listPages, listPeople, listProjects, listServices, listTasks, listTimeEntries, listTimers, reopenDiscussion, resolveDiscussion, resolveResource, startTimer, stopTimer, updateBooking, updateComment, updateCompany, updateDeal, updateDiscussion, updatePage, updateTask, updateTimeEntry } from "@studiometa/productive-core";
3
+ /**
4
+ * Custom error classes for MCP server
5
+ *
6
+ * These provide structured error handling with LLM-friendly messages
7
+ * that include guidance on how to resolve issues.
8
+ */
9
+ /**
10
+ * Error thrown when user input validation fails.
11
+ * These errors should be returned to the user directly.
12
+ *
13
+ * Includes optional hints for how to resolve the issue.
14
+ */
15
+ var UserInputError = class extends Error {
16
+ hints;
17
+ constructor(message, hints) {
18
+ super(message);
19
+ this.name = "UserInputError";
20
+ this.hints = hints;
21
+ }
22
+ /**
23
+ * Format error message with hints for LLM consumption
24
+ */
25
+ toFormattedMessage() {
26
+ let msg = `**Input Error:** ${this.message}`;
27
+ if (this.hints && this.hints.length > 0) msg += "\n\n**Hints:**\n" + this.hints.map((h) => `- ${h}`).join("\n");
28
+ return msg;
29
+ }
30
+ };
31
+ /**
32
+ * Error messages with guidance for common validation failures
33
+ */
34
+ const ErrorMessages = {
35
+ missingId: (action) => new UserInputError(`id is required for ${action} action`, [`Use action="list" first to find the resource ID`, `Then use action="${action}" with the id parameter`]),
36
+ missingRequiredFields: (resource, fields) => new UserInputError(`${fields.join(", ")} ${fields.length === 1 ? "is" : "are"} required for creating ${resource}`, [`Provide all required fields: ${fields.join(", ")}`, `Use action="help" for detailed documentation on ${resource}`]),
37
+ invalidAction: (action, resource, validActions) => new UserInputError(`Invalid action "${action}" for ${resource}`, [`Valid actions are: ${validActions.join(", ")}`, `Use action="help" with resource="${resource}" for detailed documentation`]),
38
+ unknownResource: (resource, validResources) => new UserInputError(`Unknown resource: ${resource}`, [`Valid resources are: ${validResources.join(", ")}`, `Use action="help" without a resource for an overview of all resources`]),
39
+ missingReportType: () => new UserInputError("report_type is required for reports", ["Specify report_type parameter (e.g., \"time_reports\", \"project_reports\")", "Use action=\"help\" with resource=\"reports\" for available report types"]),
40
+ invalidReportType: (reportType, validTypes) => new UserInputError(`Invalid report_type: ${reportType}`, [`Valid report types are: ${validTypes.join(", ")}`, "Use action=\"help\" with resource=\"reports\" for detailed documentation"]),
41
+ missingServiceForTimer: () => new UserInputError("service_id is required to start a timer", ["First find a service using resource=\"services\" action=\"list\"", "Then start the timer with the service_id"]),
42
+ noUserIdConfigured: () => new UserInputError("User ID not configured", ["The \"me\" action requires a user ID to be configured", "Use action=\"list\" to find people, or configure the user ID"]),
43
+ missingCommentTarget: () => new UserInputError("A target is required for creating a comment", ["Provide one of: task_id, deal_id, or company_id", "Find targets using resource=\"tasks\", \"deals\", or \"companies\" with action=\"list\""]),
44
+ missingBookingTarget: () => new UserInputError("A service or event is required for creating a booking", ["Provide either: service_id or event_id", "Find services using resource=\"services\" with action=\"list\""]),
45
+ apiError: (statusCode, message) => {
46
+ const hints = [];
47
+ if (statusCode === 401) {
48
+ hints.push("Check that your API token is valid and not expired");
49
+ hints.push("Verify the organization ID is correct");
50
+ } else if (statusCode === 403) {
51
+ hints.push("You may not have permission to access this resource");
52
+ hints.push("Check your API token permissions");
53
+ } else if (statusCode === 404) {
54
+ hints.push("The resource may not exist or you may not have access");
55
+ hints.push("Verify the resource ID is correct");
56
+ hints.push("Use action=\"list\" to find valid resource IDs");
57
+ } else if (statusCode === 422) {
58
+ hints.push("The request data may be invalid");
59
+ hints.push("Check the field values and types");
60
+ hints.push("Use action=\"help\" for field documentation");
61
+ } else if (statusCode >= 500) hints.push("This is a server error - try again later");
62
+ return new UserInputError(`API error (${statusCode}): ${message}`, hints);
63
+ }
64
+ };
65
+ /**
66
+ * Check if an error is a UserInputError
67
+ */
68
+ function isUserInputError(error) {
69
+ return error instanceof UserInputError;
70
+ }
71
+ /**
72
+ * Response formatters for agent-friendly output
73
+ *
74
+ * This module re-exports formatters from @studiometa/productive-api
75
+ * with MCP-specific defaults (no relationship IDs, no timestamps).
76
+ *
77
+ * Supports compact mode to reduce token usage by omitting verbose fields
78
+ * like descriptions and notes from list responses.
79
+ */
80
+ /**
81
+ * MCP-specific format options
82
+ * - No relationship IDs (cleaner output for agents)
83
+ * - No timestamps (reduce noise)
84
+ * - HTML stripping enabled
85
+ */
86
+ var MCP_FORMAT_OPTIONS = {
87
+ includeRelationshipIds: false,
88
+ includeTimestamps: false,
89
+ stripHtml: true
90
+ };
91
+ /**
92
+ * Remove verbose fields from an object for compact output
93
+ */
94
+ function compactify(obj, fieldsToRemove) {
95
+ const result = { ...obj };
96
+ for (const field of fieldsToRemove) delete result[field];
97
+ return result;
98
+ }
99
+ /**
100
+ * Format time entry for agent consumption
101
+ */
102
+ function formatTimeEntry$1(entry, options) {
103
+ const result = formatTimeEntry(entry, MCP_FORMAT_OPTIONS);
104
+ if (options?.compact) return compactify(result, [
105
+ "note",
106
+ "billable_time",
107
+ "approved"
108
+ ]);
109
+ return result;
110
+ }
111
+ /**
112
+ * Format project for agent consumption
113
+ */
114
+ function formatProject$1(project, options) {
115
+ const result = formatProject(project, MCP_FORMAT_OPTIONS);
116
+ if (options?.compact) return compactify(result, ["budget"]);
117
+ return result;
118
+ }
119
+ /**
120
+ * Format task for agent consumption
121
+ * Tasks use included resources to resolve project/company names
122
+ */
123
+ function formatTask$1(task, options) {
124
+ const result = formatTask(task, {
125
+ ...MCP_FORMAT_OPTIONS,
126
+ included: options?.included
127
+ });
128
+ if (options?.compact) return compactify(result, [
129
+ "description",
130
+ "initial_estimate",
131
+ "worked_time",
132
+ "remaining_time",
133
+ "project",
134
+ "company"
135
+ ]);
136
+ return result;
137
+ }
138
+ /**
139
+ * Format person for agent consumption
140
+ */
141
+ function formatPerson$1(person, options) {
142
+ const result = formatPerson(person, MCP_FORMAT_OPTIONS);
143
+ if (options?.compact) return compactify(result, [
144
+ "title",
145
+ "first_name",
146
+ "last_name"
147
+ ]);
148
+ return result;
149
+ }
150
+ /**
151
+ * Format service for agent consumption
152
+ */
153
+ function formatService$1(service, options) {
154
+ const result = formatService(service, MCP_FORMAT_OPTIONS);
155
+ if (options?.compact) return compactify(result, ["budgeted_time", "worked_time"]);
156
+ return result;
157
+ }
158
+ /**
159
+ * Format budget for agent consumption
160
+ */
161
+ function formatBudget$1(budget, options) {
162
+ const result = formatBudget(budget, MCP_FORMAT_OPTIONS);
163
+ if (options?.compact) return compactify(result, ["budget_type", "currency"]);
164
+ return result;
165
+ }
166
+ /**
167
+ * Format company for agent consumption
168
+ */
169
+ function formatCompany$1(company, options) {
170
+ const result = formatCompany(company, MCP_FORMAT_OPTIONS);
171
+ if (options?.compact) return compactify(result, [
172
+ "billing_name",
173
+ "domain",
174
+ "due_days"
175
+ ]);
176
+ return result;
177
+ }
178
+ /**
179
+ * Format comment for agent consumption
180
+ */
181
+ function formatComment$1(comment, options) {
182
+ return formatComment(comment, {
183
+ ...MCP_FORMAT_OPTIONS,
184
+ included: options?.included
185
+ });
186
+ }
187
+ /**
188
+ * Format timer for agent consumption
189
+ */
190
+ function formatTimer$1(timer, _options) {
191
+ return formatTimer(timer, MCP_FORMAT_OPTIONS);
192
+ }
193
+ /**
194
+ * Format deal for agent consumption
195
+ */
196
+ function formatDeal$1(deal, options) {
197
+ const result = formatDeal(deal, {
198
+ ...MCP_FORMAT_OPTIONS,
199
+ included: options?.included
200
+ });
201
+ if (options?.compact) return compactify(result, ["won_at", "lost_at"]);
202
+ return result;
203
+ }
204
+ /**
205
+ * Format booking for agent consumption
206
+ */
207
+ function formatBooking$1(booking, options) {
208
+ const result = formatBooking(booking, {
209
+ ...MCP_FORMAT_OPTIONS,
210
+ included: options?.included
211
+ });
212
+ if (options?.compact) return compactify(result, [
213
+ "approved_at",
214
+ "rejected_at",
215
+ "rejected_reason"
216
+ ]);
217
+ return result;
218
+ }
219
+ /**
220
+ * Format attachment for agent consumption
221
+ */
222
+ function formatAttachment$1(attachment, options) {
223
+ const result = formatAttachment(attachment, MCP_FORMAT_OPTIONS);
224
+ if (options?.compact) return compactify(result, ["url"]);
225
+ return result;
226
+ }
227
+ /**
228
+ * Format page for agent consumption
229
+ */
230
+ function formatPage$1(page, options) {
231
+ const result = formatPage(page, MCP_FORMAT_OPTIONS);
232
+ if (options?.compact) return compactify(result, ["body", "version_number"]);
233
+ return result;
234
+ }
235
+ /**
236
+ * Format discussion for agent consumption
237
+ */
238
+ function formatDiscussion$1(discussion, options) {
239
+ const result = formatDiscussion(discussion, MCP_FORMAT_OPTIONS);
240
+ if (options?.compact) return compactify(result, ["body"]);
241
+ return result;
242
+ }
243
+ /**
244
+ * Format list response with pagination
245
+ *
246
+ * @param data - Array of JSON:API resources
247
+ * @param formatter - Formatter function (item, options?) => T
248
+ * @param meta - Pagination metadata
249
+ * @param options - MCP format options (compact, included)
250
+ */
251
+ function formatListResponse$1(data, formatter, meta, options) {
252
+ const wrappedFormatter = (item, _cliOptions) => {
253
+ return formatter(item, options);
254
+ };
255
+ return formatListResponse(data, wrappedFormatter, meta, {
256
+ ...MCP_FORMAT_OPTIONS,
257
+ included: options?.included
258
+ });
259
+ }
260
+ /**
261
+ * Generate hints for a task
262
+ */
263
+ function getTaskHints(taskId, serviceId) {
264
+ const hints = {
265
+ related_resources: [
266
+ {
267
+ resource: "comments",
268
+ description: "Get comments on this task",
269
+ example: {
270
+ resource: "comments",
271
+ action: "list",
272
+ filter: { task_id: taskId }
273
+ }
274
+ },
275
+ {
276
+ resource: "time",
277
+ description: "Get time entries logged on this task",
278
+ example: {
279
+ resource: "time",
280
+ action: "list",
281
+ filter: { task_id: taskId }
282
+ }
283
+ },
284
+ {
285
+ resource: "tasks",
286
+ description: "Get subtasks of this task",
287
+ example: {
288
+ resource: "tasks",
289
+ action: "list",
290
+ filter: { parent_task_id: taskId }
291
+ }
292
+ }
293
+ ],
294
+ common_actions: [{
295
+ action: "Add a comment",
296
+ example: {
297
+ resource: "comments",
298
+ action: "create",
299
+ task_id: taskId,
300
+ body: "<your comment>"
301
+ }
302
+ }]
303
+ };
304
+ if (serviceId) hints.common_actions.push({
305
+ action: "Log time on this task",
306
+ example: {
307
+ resource: "time",
308
+ action: "create",
309
+ service_id: serviceId,
310
+ task_id: taskId,
311
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
312
+ time: 60,
313
+ note: "<description of work>"
314
+ }
315
+ });
316
+ return hints;
317
+ }
318
+ /**
319
+ * Generate hints for a project
320
+ */
321
+ function getProjectHints(projectId) {
322
+ return {
323
+ related_resources: [
324
+ {
325
+ resource: "tasks",
326
+ description: "Get tasks in this project",
327
+ example: {
328
+ resource: "tasks",
329
+ action: "list",
330
+ filter: { project_id: projectId }
331
+ }
332
+ },
333
+ {
334
+ resource: "services",
335
+ description: "Get services (budget lines) for this project",
336
+ example: {
337
+ resource: "services",
338
+ action: "list",
339
+ filter: { project_id: projectId }
340
+ }
341
+ },
342
+ {
343
+ resource: "time",
344
+ description: "Get time entries for this project",
345
+ example: {
346
+ resource: "time",
347
+ action: "list",
348
+ filter: { project_id: projectId }
349
+ }
350
+ },
351
+ {
352
+ resource: "comments",
353
+ description: "Get comments on this project",
354
+ example: {
355
+ resource: "comments",
356
+ action: "list",
357
+ filter: { project_id: projectId }
358
+ }
359
+ },
360
+ {
361
+ resource: "deals",
362
+ description: "Get deals/budgets for this project",
363
+ example: {
364
+ resource: "deals",
365
+ action: "list",
366
+ filter: { project_id: projectId }
367
+ }
368
+ }
369
+ ],
370
+ common_actions: [{
371
+ action: "Create a task",
372
+ example: {
373
+ resource: "tasks",
374
+ action: "create",
375
+ project_id: projectId,
376
+ task_list_id: "<task_list_id>",
377
+ title: "<task title>"
378
+ }
379
+ }]
380
+ };
381
+ }
382
+ /**
383
+ * Generate hints for a deal/budget
384
+ */
385
+ function getDealHints(dealId) {
386
+ return {
387
+ related_resources: [
388
+ {
389
+ resource: "comments",
390
+ description: "Get comments on this deal/budget",
391
+ example: {
392
+ resource: "comments",
393
+ action: "list",
394
+ filter: { deal_id: dealId }
395
+ }
396
+ },
397
+ {
398
+ resource: "services",
399
+ description: "Get services (budget lines) for this deal",
400
+ example: {
401
+ resource: "services",
402
+ action: "list",
403
+ filter: { deal_id: dealId }
404
+ }
405
+ },
406
+ {
407
+ resource: "time",
408
+ description: "Get time entries for this deal/budget",
409
+ example: {
410
+ resource: "time",
411
+ action: "list",
412
+ filter: { deal_id: dealId }
413
+ }
414
+ },
415
+ {
416
+ resource: "bookings",
417
+ description: "Get resource bookings for this deal",
418
+ example: {
419
+ resource: "bookings",
420
+ action: "list",
421
+ filter: { deal_id: dealId }
422
+ }
423
+ }
424
+ ],
425
+ common_actions: [{
426
+ action: "Add a comment",
427
+ example: {
428
+ resource: "comments",
429
+ action: "create",
430
+ deal_id: dealId,
431
+ body: "<your comment>"
432
+ }
433
+ }]
434
+ };
435
+ }
436
+ /**
437
+ * Generate hints for a budget
438
+ */
439
+ function getBudgetHints(budgetId) {
440
+ return { related_resources: [
441
+ {
442
+ resource: "services",
443
+ description: "Get services (budget lines) for this budget",
444
+ example: {
445
+ resource: "services",
446
+ action: "list",
447
+ filter: { budget_id: budgetId }
448
+ }
449
+ },
450
+ {
451
+ resource: "time",
452
+ description: "Get time entries for this budget",
453
+ example: {
454
+ resource: "time",
455
+ action: "list",
456
+ filter: { budget_id: budgetId }
457
+ }
458
+ },
459
+ {
460
+ resource: "bookings",
461
+ description: "Get bookings for this budget",
462
+ example: {
463
+ resource: "bookings",
464
+ action: "list",
465
+ filter: { budget_id: budgetId }
466
+ }
467
+ }
468
+ ] };
469
+ }
470
+ /**
471
+ * Generate hints for a person
472
+ */
473
+ function getPersonHints(personId) {
474
+ return { related_resources: [
475
+ {
476
+ resource: "tasks",
477
+ description: "Get tasks assigned to this person",
478
+ example: {
479
+ resource: "tasks",
480
+ action: "list",
481
+ filter: { assignee_id: personId }
482
+ }
483
+ },
484
+ {
485
+ resource: "time",
486
+ description: "Get time entries by this person",
487
+ example: {
488
+ resource: "time",
489
+ action: "list",
490
+ filter: { person_id: personId }
491
+ }
492
+ },
493
+ {
494
+ resource: "bookings",
495
+ description: "Get bookings for this person",
496
+ example: {
497
+ resource: "bookings",
498
+ action: "list",
499
+ filter: { person_id: personId }
500
+ }
501
+ },
502
+ {
503
+ resource: "timers",
504
+ description: "Get active timers for this person",
505
+ example: {
506
+ resource: "timers",
507
+ action: "list",
508
+ filter: { person_id: personId }
509
+ }
510
+ }
511
+ ] };
512
+ }
513
+ /**
514
+ * Generate hints for a company
515
+ */
516
+ function getCompanyHints(companyId) {
517
+ return { related_resources: [
518
+ {
519
+ resource: "projects",
520
+ description: "Get projects for this company",
521
+ example: {
522
+ resource: "projects",
523
+ action: "list",
524
+ filter: { company_id: companyId }
525
+ }
526
+ },
527
+ {
528
+ resource: "deals",
529
+ description: "Get deals for this company",
530
+ example: {
531
+ resource: "deals",
532
+ action: "list",
533
+ filter: { company_id: companyId }
534
+ }
535
+ },
536
+ {
537
+ resource: "tasks",
538
+ description: "Get tasks for this company",
539
+ example: {
540
+ resource: "tasks",
541
+ action: "list",
542
+ filter: { company_id: companyId }
543
+ }
544
+ },
545
+ {
546
+ resource: "people",
547
+ description: "Get contacts at this company",
548
+ example: {
549
+ resource: "people",
550
+ action: "list",
551
+ filter: { company_id: companyId }
552
+ }
553
+ }
554
+ ] };
555
+ }
556
+ /**
557
+ * Generate hints for a time entry
558
+ */
559
+ function getTimeEntryHints(timeEntryId, taskId, serviceId) {
560
+ const hints = {
561
+ related_resources: [],
562
+ common_actions: [{
563
+ action: "Update this time entry",
564
+ example: {
565
+ resource: "time",
566
+ action: "update",
567
+ id: timeEntryId,
568
+ time: 120,
569
+ note: "<updated note>"
570
+ }
571
+ }]
572
+ };
573
+ if (taskId) hints.related_resources.push({
574
+ resource: "tasks",
575
+ description: "Get the associated task",
576
+ example: {
577
+ resource: "tasks",
578
+ action: "get",
579
+ id: taskId
580
+ }
581
+ });
582
+ if (serviceId) hints.related_resources.push({
583
+ resource: "services",
584
+ description: "Get the associated service",
585
+ example: {
586
+ resource: "services",
587
+ action: "get",
588
+ id: serviceId
589
+ }
590
+ });
591
+ return hints;
592
+ }
593
+ /**
594
+ * Generate hints for a comment
595
+ */
596
+ function getCommentHints(_commentId, commentableType, commentableId) {
597
+ const hints = { related_resources: [] };
598
+ if (commentableType && commentableId) {
599
+ const resource = {
600
+ task: "tasks",
601
+ deal: "deals",
602
+ project: "projects",
603
+ company: "companies"
604
+ }[commentableType];
605
+ if (resource) hints.related_resources.push({
606
+ resource,
607
+ description: `Get the ${commentableType} this comment is on`,
608
+ example: {
609
+ resource,
610
+ action: "get",
611
+ id: commentableId
612
+ }
613
+ });
614
+ }
615
+ return hints;
616
+ }
617
+ /**
618
+ * Generate hints for an attachment
619
+ */
620
+ function getAttachmentHints(_attachmentId, attachableType) {
621
+ const hints = {
622
+ related_resources: [],
623
+ common_actions: [{
624
+ action: "Delete this attachment",
625
+ example: {
626
+ resource: "attachments",
627
+ action: "delete",
628
+ id: _attachmentId
629
+ }
630
+ }]
631
+ };
632
+ if (attachableType) {
633
+ const resource = {
634
+ Task: "tasks",
635
+ Comment: "comments",
636
+ Deal: "deals",
637
+ Page: "projects"
638
+ }[attachableType];
639
+ if (resource) hints.related_resources.push({
640
+ resource,
641
+ description: `View the ${attachableType.toLowerCase()} this attachment belongs to`,
642
+ example: {
643
+ resource,
644
+ action: "list"
645
+ }
646
+ });
647
+ }
648
+ return hints;
649
+ }
650
+ /**
651
+ * Generate hints for a booking
652
+ */
653
+ function getBookingHints(bookingId, personId) {
654
+ const hints = {
655
+ related_resources: [],
656
+ common_actions: [{
657
+ action: "Update this booking",
658
+ example: {
659
+ resource: "bookings",
660
+ action: "update",
661
+ id: bookingId,
662
+ time: 480
663
+ }
664
+ }]
665
+ };
666
+ if (personId) hints.related_resources.push({
667
+ resource: "people",
668
+ description: "Get the person this booking is for",
669
+ example: {
670
+ resource: "people",
671
+ action: "get",
672
+ id: personId
673
+ }
674
+ });
675
+ return hints;
676
+ }
677
+ /**
678
+ * Generate hints for a page
679
+ */
680
+ function getPageHints(pageId) {
681
+ return {
682
+ related_resources: [
683
+ {
684
+ resource: "discussions",
685
+ description: "Get discussions on this page",
686
+ example: {
687
+ resource: "discussions",
688
+ action: "list",
689
+ filter: { page_id: pageId }
690
+ }
691
+ },
692
+ {
693
+ resource: "comments",
694
+ description: "Get comments on this page",
695
+ example: {
696
+ resource: "comments",
697
+ action: "list",
698
+ filter: { page_id: pageId }
699
+ }
700
+ },
701
+ {
702
+ resource: "pages",
703
+ description: "Get sub-pages of this page",
704
+ example: {
705
+ resource: "pages",
706
+ action: "list",
707
+ filter: { parent_page_id: pageId }
708
+ }
709
+ }
710
+ ],
711
+ common_actions: [{
712
+ action: "Create a discussion",
713
+ example: {
714
+ resource: "discussions",
715
+ action: "create",
716
+ page_id: pageId,
717
+ body: "<your discussion>"
718
+ }
719
+ }, {
720
+ action: "Create a sub-page",
721
+ example: {
722
+ resource: "pages",
723
+ action: "create",
724
+ parent_page_id: pageId,
725
+ title: "<sub-page title>",
726
+ project_id: "<project_id>"
727
+ }
728
+ }]
729
+ };
730
+ }
731
+ /**
732
+ * Generate hints for a discussion
733
+ */
734
+ function getDiscussionHints(discussionId, pageId) {
735
+ const hints = {
736
+ related_resources: [{
737
+ resource: "comments",
738
+ description: "Get comments on this discussion",
739
+ example: {
740
+ resource: "comments",
741
+ action: "list",
742
+ filter: { discussion_id: discussionId }
743
+ }
744
+ }],
745
+ common_actions: [{
746
+ action: "Resolve this discussion",
747
+ example: {
748
+ resource: "discussions",
749
+ action: "resolve",
750
+ id: discussionId
751
+ }
752
+ }, {
753
+ action: "Add a comment",
754
+ example: {
755
+ resource: "comments",
756
+ action: "create",
757
+ discussion_id: discussionId,
758
+ body: "<your comment>"
759
+ }
760
+ }]
761
+ };
762
+ if (pageId) hints.related_resources.push({
763
+ resource: "pages",
764
+ description: "Get the page this discussion is on",
765
+ example: {
766
+ resource: "pages",
767
+ action: "get",
768
+ id: pageId
769
+ }
770
+ });
771
+ return hints;
772
+ }
773
+ /**
774
+ * Generate hints for a timer
775
+ */
776
+ function getTimerHints(timerId, serviceId) {
777
+ const hints = {
778
+ common_actions: [{
779
+ action: "Stop this timer",
780
+ example: {
781
+ resource: "timers",
782
+ action: "stop",
783
+ id: timerId
784
+ }
785
+ }],
786
+ related_resources: []
787
+ };
788
+ if (serviceId) hints.related_resources.push({
789
+ resource: "services",
790
+ description: "Get the service this timer is running on",
791
+ example: {
792
+ resource: "services",
793
+ action: "get",
794
+ id: serviceId
795
+ }
796
+ });
797
+ return hints;
798
+ }
799
+ /**
800
+ * Helper to create a successful JSON response
801
+ */
802
+ function jsonResult(data) {
803
+ return { content: [{
804
+ type: "text",
805
+ text: JSON.stringify(data, null, 2)
806
+ }] };
807
+ }
808
+ /**
809
+ * Helper to create an error response from a string message
810
+ */
811
+ function errorResult(message) {
812
+ return {
813
+ content: [{
814
+ type: "text",
815
+ text: `**Error:** ${message}`
816
+ }],
817
+ isError: true
818
+ };
819
+ }
820
+ /**
821
+ * Helper to create an error response from a UserInputError
822
+ * Includes formatted hints for LLM consumption
823
+ */
824
+ function inputErrorResult(error) {
825
+ return {
826
+ content: [{
827
+ type: "text",
828
+ text: error.toFormattedMessage()
829
+ }],
830
+ isError: true
831
+ };
832
+ }
833
+ /**
834
+ * Helper to create an error response from any error type
835
+ * Automatically formats UserInputError with hints
836
+ */
837
+ function formatError(error) {
838
+ if (isUserInputError(error)) return inputErrorResult(error);
839
+ return errorResult(error instanceof Error ? error.message : String(error));
840
+ }
841
+ /**
842
+ * Convert unknown filter to string filter for API
843
+ */
844
+ function toStringFilter(filter) {
845
+ if (!filter) return void 0;
846
+ const result = {};
847
+ for (const [key, value] of Object.entries(filter)) if (value !== void 0 && value !== null) result[key] = String(value);
848
+ return Object.keys(result).length > 0 ? result : void 0;
849
+ }
850
+ /**
851
+ * Attachments MCP handler.
852
+ */
853
+ var VALID_ACTIONS$14 = [
854
+ "list",
855
+ "get",
856
+ "delete"
857
+ ];
858
+ async function handleAttachments(action, args, ctx) {
859
+ const { formatOptions, filter, page, perPage } = ctx;
860
+ const { id, task_id, comment_id, deal_id } = args;
861
+ const execCtx = ctx.executor();
862
+ if (action === "get") {
863
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
864
+ const result = await getAttachment({ id }, execCtx);
865
+ const formatted = formatAttachment$1(result.data, formatOptions);
866
+ if (ctx.includeHints !== false) {
867
+ const attachableType = result.data.attributes?.attachable_type;
868
+ return jsonResult({
869
+ ...formatted,
870
+ _hints: getAttachmentHints(id, attachableType)
871
+ });
872
+ }
873
+ return jsonResult(formatted);
874
+ }
875
+ if (action === "delete") {
876
+ if (!id) return inputErrorResult(ErrorMessages.missingId("delete"));
877
+ await deleteAttachment({ id }, execCtx);
878
+ return jsonResult({
879
+ success: true,
880
+ deleted: id
881
+ });
882
+ }
883
+ if (action === "list") {
884
+ const options = { ...filter };
885
+ if (task_id) options.task_id = task_id;
886
+ if (comment_id) options.comment_id = comment_id;
887
+ if (deal_id) options.deal_id = deal_id;
888
+ const result = await listAttachments({
889
+ page,
890
+ perPage,
891
+ additionalFilters: options
892
+ }, execCtx);
893
+ return jsonResult(formatListResponse$1(result.data, formatAttachment$1, result.meta, formatOptions));
894
+ }
895
+ return inputErrorResult(ErrorMessages.invalidAction(action, "attachments", VALID_ACTIONS$14));
896
+ }
897
+ /**
898
+ * Bookings MCP handler.
899
+ */
900
+ var VALID_ACTIONS$13 = [
901
+ "list",
902
+ "get",
903
+ "create",
904
+ "update"
905
+ ];
906
+ async function handleBookings(action, args, ctx) {
907
+ const { formatOptions, filter, page, perPage, include: userInclude } = ctx;
908
+ const { id, person_id, service_id, event_id, started_on, ended_on, time, note } = args;
909
+ const include = userInclude?.length ? [...new Set([
910
+ "person",
911
+ "service",
912
+ ...userInclude
913
+ ])] : ["person", "service"];
914
+ const execCtx = ctx.executor();
915
+ if (action === "get") {
916
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
917
+ const result = await getBooking({
918
+ id,
919
+ include
920
+ }, execCtx);
921
+ const formatted = formatBooking$1(result.data, {
922
+ ...formatOptions,
923
+ included: result.included
924
+ });
925
+ if (ctx.includeHints !== false) {
926
+ const personId = result.data.relationships?.person?.data?.id;
927
+ return jsonResult({
928
+ ...formatted,
929
+ _hints: getBookingHints(id, personId)
930
+ });
931
+ }
932
+ return jsonResult(formatted);
933
+ }
934
+ if (action === "create") {
935
+ if (!person_id || !started_on || !ended_on) return inputErrorResult(ErrorMessages.missingRequiredFields("booking", [
936
+ "person_id",
937
+ "started_on",
938
+ "ended_on"
939
+ ]));
940
+ if (!service_id && !event_id) return inputErrorResult(ErrorMessages.missingBookingTarget());
941
+ return jsonResult({
942
+ success: true,
943
+ ...formatBooking$1((await createBooking({
944
+ personId: person_id,
945
+ serviceId: service_id ?? "",
946
+ startedOn: started_on,
947
+ endedOn: ended_on,
948
+ time,
949
+ note,
950
+ eventId: event_id
951
+ }, execCtx)).data, formatOptions)
952
+ });
953
+ }
954
+ if (action === "update") {
955
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
956
+ return jsonResult({
957
+ success: true,
958
+ ...formatBooking$1((await updateBooking({
959
+ id,
960
+ startedOn: started_on,
961
+ endedOn: ended_on,
962
+ time,
963
+ note
964
+ }, execCtx)).data, formatOptions)
965
+ });
966
+ }
967
+ if (action === "list") {
968
+ const result = await listBookings({
969
+ page,
970
+ perPage,
971
+ additionalFilters: filter,
972
+ include
973
+ }, execCtx);
974
+ return jsonResult(formatListResponse$1(result.data, formatBooking$1, result.meta, formatOptions));
975
+ }
976
+ return inputErrorResult(ErrorMessages.invalidAction(action, "bookings", VALID_ACTIONS$13));
977
+ }
978
+ /**
979
+ * Budgets MCP handler.
980
+ *
981
+ * Thin adapter that delegates business logic to core executors
982
+ * and handles MCP-specific concerns (hints, error formatting, JSON results).
983
+ */
984
+ var VALID_ACTIONS$12 = ["list", "get"];
985
+ async function handleBudgets(action, args, ctx) {
986
+ const { formatOptions, filter, page, perPage } = ctx;
987
+ const { id } = args;
988
+ const execCtx = ctx.executor();
989
+ if (action === "get") {
990
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
991
+ const formatted = formatBudget$1((await getBudget({ id }, execCtx)).data, formatOptions);
992
+ if (ctx.includeHints !== false) return jsonResult({
993
+ ...formatted,
994
+ _hints: getBudgetHints(id)
995
+ });
996
+ return jsonResult(formatted);
997
+ }
998
+ if (action === "list") {
999
+ const result = await listBudgets({
1000
+ page,
1001
+ perPage,
1002
+ additionalFilters: filter
1003
+ }, execCtx);
1004
+ return jsonResult(formatListResponse$1(result.data, formatBudget$1, result.meta, formatOptions));
1005
+ }
1006
+ return inputErrorResult(ErrorMessages.invalidAction(action, "budgets", VALID_ACTIONS$12));
1007
+ }
1008
+ /**
1009
+ * Comments MCP handler.
1010
+ */
1011
+ var VALID_ACTIONS$11 = [
1012
+ "list",
1013
+ "get",
1014
+ "create",
1015
+ "update"
1016
+ ];
1017
+ async function handleComments(action, args, ctx) {
1018
+ const { formatOptions, filter, page, perPage, include: userInclude } = ctx;
1019
+ const { id, body, task_id, deal_id, company_id } = args;
1020
+ const include = userInclude?.length ? [...new Set(["creator", ...userInclude])] : ["creator"];
1021
+ const execCtx = ctx.executor();
1022
+ if (action === "get") {
1023
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1024
+ const result = await getComment({
1025
+ id,
1026
+ include
1027
+ }, execCtx);
1028
+ const formatted = formatComment$1(result.data, {
1029
+ ...formatOptions,
1030
+ included: result.included
1031
+ });
1032
+ if (ctx.includeHints !== false) {
1033
+ const commentableType = result.data.attributes?.commentable_type;
1034
+ let commentableId;
1035
+ if (commentableType === "task") commentableId = result.data.relationships?.task?.data?.id;
1036
+ else if (commentableType === "deal") commentableId = result.data.relationships?.deal?.data?.id;
1037
+ else if (commentableType === "company") commentableId = result.data.relationships?.company?.data?.id;
1038
+ return jsonResult({
1039
+ ...formatted,
1040
+ _hints: getCommentHints(id, commentableType, commentableId)
1041
+ });
1042
+ }
1043
+ return jsonResult(formatted);
1044
+ }
1045
+ if (action === "create") {
1046
+ if (!body) return inputErrorResult(ErrorMessages.missingRequiredFields("comment", ["body"]));
1047
+ if (!task_id && !deal_id && !company_id) return inputErrorResult(ErrorMessages.missingCommentTarget());
1048
+ return jsonResult({
1049
+ success: true,
1050
+ ...formatComment$1((await createComment({
1051
+ body,
1052
+ taskId: task_id,
1053
+ dealId: deal_id,
1054
+ companyId: company_id
1055
+ }, execCtx)).data, formatOptions)
1056
+ });
1057
+ }
1058
+ if (action === "update") {
1059
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
1060
+ if (!body) return inputErrorResult(ErrorMessages.missingRequiredFields("comment update", ["body"]));
1061
+ return jsonResult({
1062
+ success: true,
1063
+ ...formatComment$1((await updateComment({
1064
+ id,
1065
+ body
1066
+ }, execCtx)).data, formatOptions)
1067
+ });
1068
+ }
1069
+ if (action === "list") {
1070
+ const result = await listComments({
1071
+ page,
1072
+ perPage,
1073
+ additionalFilters: filter,
1074
+ include
1075
+ }, execCtx);
1076
+ return jsonResult(formatListResponse$1(result.data, formatComment$1, result.meta, formatOptions));
1077
+ }
1078
+ return inputErrorResult(ErrorMessages.invalidAction(action, "comments", VALID_ACTIONS$11));
1079
+ }
1080
+ /**
1081
+ * Resolve handler for MCP.
1082
+ *
1083
+ * Thin wrapper around core's resource resolver.
1084
+ * Provides handleResolve for the MCP 'resolve' action.
1085
+ */
1086
+ /**
1087
+ * Handle resolve action for a resource.
1088
+ *
1089
+ * Delegates to core's resolveResource function and wraps
1090
+ * errors in MCP-friendly format.
1091
+ */
1092
+ async function handleResolve(args, ctx) {
1093
+ const { query, type, project_id } = args;
1094
+ if (!query) return errorResult("query is required for resolve action");
1095
+ try {
1096
+ const results = await resolveResource(ctx.executor().api, query, {
1097
+ type,
1098
+ projectId: project_id
1099
+ });
1100
+ return jsonResult({
1101
+ query,
1102
+ matches: results,
1103
+ exact: results.length === 1 && results[0].exact
1104
+ });
1105
+ } catch (error) {
1106
+ if (error instanceof ResolveError) return inputErrorResult(new UserInputError(error.message, [`Query: "${error.query}"`, ...error.type ? [`Type: ${error.type}`] : []]));
1107
+ throw error;
1108
+ }
1109
+ }
1110
+ /**
1111
+ * Companies MCP handler.
1112
+ */
1113
+ var VALID_ACTIONS$10 = [
1114
+ "list",
1115
+ "get",
1116
+ "create",
1117
+ "update",
1118
+ "resolve"
1119
+ ];
1120
+ async function handleCompanies(action, args, ctx) {
1121
+ const { formatOptions, filter, page, perPage } = ctx;
1122
+ const { id, name, query, type } = args;
1123
+ if (action === "resolve") return handleResolve({
1124
+ query,
1125
+ type
1126
+ }, ctx);
1127
+ const execCtx = ctx.executor();
1128
+ if (action === "get") {
1129
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1130
+ const formatted = formatCompany$1((await getCompany({ id }, execCtx)).data, formatOptions);
1131
+ if (ctx.includeHints !== false) return jsonResult({
1132
+ ...formatted,
1133
+ _hints: getCompanyHints(id)
1134
+ });
1135
+ return jsonResult(formatted);
1136
+ }
1137
+ if (action === "create") {
1138
+ if (!name) return inputErrorResult(ErrorMessages.missingRequiredFields("company", ["name"]));
1139
+ return jsonResult({
1140
+ success: true,
1141
+ ...formatCompany$1((await createCompany({ name }, execCtx)).data, formatOptions)
1142
+ });
1143
+ }
1144
+ if (action === "update") {
1145
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
1146
+ return jsonResult({
1147
+ success: true,
1148
+ ...formatCompany$1((await updateCompany({
1149
+ id,
1150
+ name
1151
+ }, execCtx)).data, formatOptions)
1152
+ });
1153
+ }
1154
+ if (action === "list") {
1155
+ const result = await listCompanies({
1156
+ page,
1157
+ perPage,
1158
+ additionalFilters: filter
1159
+ }, execCtx);
1160
+ const response = formatListResponse$1(result.data, formatCompany$1, result.meta, formatOptions);
1161
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
1162
+ ...response,
1163
+ _resolved: result.resolved
1164
+ });
1165
+ return jsonResult(response);
1166
+ }
1167
+ return inputErrorResult(ErrorMessages.invalidAction(action, "companies", VALID_ACTIONS$10));
1168
+ }
1169
+ /**
1170
+ * Deals MCP handler.
1171
+ */
1172
+ var VALID_ACTIONS$9 = [
1173
+ "list",
1174
+ "get",
1175
+ "create",
1176
+ "update",
1177
+ "resolve"
1178
+ ];
1179
+ async function handleDeals(action, args, ctx) {
1180
+ const { formatOptions, filter, page, perPage, include: userInclude } = ctx;
1181
+ const { id, name, company_id, query, type } = args;
1182
+ if (action === "resolve") return handleResolve({
1183
+ query,
1184
+ type
1185
+ }, ctx);
1186
+ const execCtx = ctx.executor();
1187
+ if (action === "get") {
1188
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1189
+ const result = await getDeal({
1190
+ id,
1191
+ include: userInclude?.length ? [...new Set([
1192
+ "company",
1193
+ "deal_status",
1194
+ "responsible",
1195
+ ...userInclude
1196
+ ])] : [
1197
+ "company",
1198
+ "deal_status",
1199
+ "responsible"
1200
+ ]
1201
+ }, execCtx);
1202
+ const formatted = formatDeal$1(result.data, {
1203
+ ...formatOptions,
1204
+ included: result.included
1205
+ });
1206
+ if (ctx.includeHints !== false) return jsonResult({
1207
+ ...formatted,
1208
+ _hints: getDealHints(id)
1209
+ });
1210
+ return jsonResult(formatted);
1211
+ }
1212
+ if (action === "create") {
1213
+ if (!name || !company_id) return inputErrorResult(ErrorMessages.missingRequiredFields("deal", ["name", "company_id"]));
1214
+ return jsonResult({
1215
+ success: true,
1216
+ ...formatDeal$1((await createDeal({
1217
+ name,
1218
+ companyId: company_id
1219
+ }, execCtx)).data, formatOptions)
1220
+ });
1221
+ }
1222
+ if (action === "update") {
1223
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
1224
+ return jsonResult({
1225
+ success: true,
1226
+ ...formatDeal$1((await updateDeal({
1227
+ id,
1228
+ name
1229
+ }, execCtx)).data, formatOptions)
1230
+ });
1231
+ }
1232
+ if (action === "list") {
1233
+ const result = await listDeals({
1234
+ page,
1235
+ perPage,
1236
+ additionalFilters: filter,
1237
+ include: userInclude?.length ? [...new Set([
1238
+ "company",
1239
+ "deal_status",
1240
+ ...userInclude
1241
+ ])] : ["company", "deal_status"]
1242
+ }, execCtx);
1243
+ const response = formatListResponse$1(result.data, formatDeal$1, result.meta, {
1244
+ ...formatOptions,
1245
+ included: result.included
1246
+ });
1247
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
1248
+ ...response,
1249
+ _resolved: result.resolved
1250
+ });
1251
+ return jsonResult(response);
1252
+ }
1253
+ return inputErrorResult(ErrorMessages.invalidAction(action, "deals", VALID_ACTIONS$9));
1254
+ }
1255
+ /**
1256
+ * Discussions MCP handler.
1257
+ */
1258
+ var VALID_ACTIONS$8 = [
1259
+ "list",
1260
+ "get",
1261
+ "create",
1262
+ "update",
1263
+ "delete",
1264
+ "resolve",
1265
+ "reopen"
1266
+ ];
1267
+ async function handleDiscussions(action, args, ctx) {
1268
+ const { formatOptions, filter, page, perPage } = ctx;
1269
+ const { id, title, body, page_id, status } = args;
1270
+ const execCtx = ctx.executor();
1271
+ if (action === "get") {
1272
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1273
+ const formatted = formatDiscussion$1((await getDiscussion({ id }, execCtx)).data, formatOptions);
1274
+ if (ctx.includeHints !== false) return jsonResult({
1275
+ ...formatted,
1276
+ _hints: getDiscussionHints(id, page_id)
1277
+ });
1278
+ return jsonResult(formatted);
1279
+ }
1280
+ if (action === "create") {
1281
+ if (!body || !page_id) return inputErrorResult(ErrorMessages.missingRequiredFields("discussion", ["body", "page_id"]));
1282
+ return jsonResult({
1283
+ success: true,
1284
+ ...formatDiscussion$1((await createDiscussion({
1285
+ body,
1286
+ pageId: page_id,
1287
+ title
1288
+ }, execCtx)).data, formatOptions)
1289
+ });
1290
+ }
1291
+ if (action === "update") {
1292
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
1293
+ return jsonResult({
1294
+ success: true,
1295
+ ...formatDiscussion$1((await updateDiscussion({
1296
+ id,
1297
+ title,
1298
+ body
1299
+ }, execCtx)).data, formatOptions)
1300
+ });
1301
+ }
1302
+ if (action === "delete") {
1303
+ if (!id) return inputErrorResult(ErrorMessages.missingId("delete"));
1304
+ await deleteDiscussion({ id }, execCtx);
1305
+ return jsonResult({
1306
+ success: true,
1307
+ deleted: id
1308
+ });
1309
+ }
1310
+ if (action === "resolve") {
1311
+ if (!id) return inputErrorResult(ErrorMessages.missingId("resolve"));
1312
+ return jsonResult({
1313
+ success: true,
1314
+ ...formatDiscussion$1((await resolveDiscussion({ id }, execCtx)).data, formatOptions)
1315
+ });
1316
+ }
1317
+ if (action === "reopen") {
1318
+ if (!id) return inputErrorResult(ErrorMessages.missingId("reopen"));
1319
+ return jsonResult({
1320
+ success: true,
1321
+ ...formatDiscussion$1((await reopenDiscussion({ id }, execCtx)).data, formatOptions)
1322
+ });
1323
+ }
1324
+ if (action === "list") {
1325
+ const listOptions = {
1326
+ page,
1327
+ perPage,
1328
+ additionalFilters: filter
1329
+ };
1330
+ if (status) listOptions.status = status;
1331
+ const result = await listDiscussions(listOptions, execCtx);
1332
+ const response = formatListResponse$1(result.data, formatDiscussion$1, result.meta, formatOptions);
1333
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
1334
+ ...response,
1335
+ _resolved: result.resolved
1336
+ });
1337
+ return jsonResult(response);
1338
+ }
1339
+ return inputErrorResult(ErrorMessages.invalidAction(action, "discussions", VALID_ACTIONS$8));
1340
+ }
1341
+ var RESOURCE_HELP = {
1342
+ projects: {
1343
+ description: "Manage projects in Productive.io",
1344
+ actions: {
1345
+ list: "List all projects with optional filters",
1346
+ get: "Get a single project by ID with full details"
1347
+ },
1348
+ filters: {
1349
+ query: "Text search on project name",
1350
+ project_type: "Filter by project type: 1=internal, 2=client",
1351
+ company_id: "Filter by company",
1352
+ responsible_id: "Filter by project manager",
1353
+ person_id: "Filter by team member",
1354
+ status: "Filter by status: 1=active, 2=archived"
1355
+ },
1356
+ fields: {
1357
+ id: "Unique project identifier",
1358
+ name: "Project name",
1359
+ project_number: "Project reference number",
1360
+ archived: "Whether the project is archived",
1361
+ budget: "Project budget amount"
1362
+ },
1363
+ examples: [
1364
+ {
1365
+ description: "Search projects by name",
1366
+ params: {
1367
+ resource: "projects",
1368
+ action: "list",
1369
+ query: "website"
1370
+ }
1371
+ },
1372
+ {
1373
+ description: "List active projects",
1374
+ params: {
1375
+ resource: "projects",
1376
+ action: "list",
1377
+ filter: { archived: "false" }
1378
+ }
1379
+ },
1380
+ {
1381
+ description: "Get project details",
1382
+ params: {
1383
+ resource: "projects",
1384
+ action: "get",
1385
+ id: "12345"
1386
+ }
1387
+ }
1388
+ ]
1389
+ },
1390
+ tasks: {
1391
+ description: "Manage tasks within projects",
1392
+ actions: {
1393
+ list: "List tasks with optional filters",
1394
+ get: "Get a single task by ID with full details (description, comments, etc.)",
1395
+ create: "Create a new task (requires title, project_id, task_list_id)",
1396
+ update: "Update an existing task"
1397
+ },
1398
+ filters: {
1399
+ query: "Text search on task title",
1400
+ project_id: "Filter by project",
1401
+ company_id: "Filter by company",
1402
+ assignee_id: "Filter by assigned person",
1403
+ creator_id: "Filter by task creator",
1404
+ status: "Filter by status: 1=open, 2=closed (or \"open\", \"closed\", \"all\")",
1405
+ task_list_id: "Filter by task list",
1406
+ board_id: "Filter by board",
1407
+ workflow_status_id: "Filter by workflow status (kanban column)",
1408
+ parent_task_id: "Filter by parent task (for subtasks)",
1409
+ overdue_status: "Filter by overdue: 1=not overdue, 2=overdue",
1410
+ due_date_on: "Filter by exact due date (YYYY-MM-DD)",
1411
+ due_date_before: "Filter by due date before (YYYY-MM-DD)",
1412
+ due_date_after: "Filter by due date after (YYYY-MM-DD)"
1413
+ },
1414
+ includes: [
1415
+ "project",
1416
+ "project.company",
1417
+ "assignee",
1418
+ "workflow_status",
1419
+ "comments",
1420
+ "attachments",
1421
+ "subtasks"
1422
+ ],
1423
+ fields: {
1424
+ id: "Unique task identifier",
1425
+ title: "Task title",
1426
+ description: "Full task description (HTML)",
1427
+ number: "Task number within project",
1428
+ due_date: "Due date (YYYY-MM-DD)",
1429
+ initial_estimate: "Estimated time in minutes",
1430
+ worked_time: "Logged time in minutes",
1431
+ remaining_time: "Remaining time in minutes",
1432
+ closed: "Whether the task is closed"
1433
+ },
1434
+ examples: [
1435
+ {
1436
+ description: "Search tasks by title",
1437
+ params: {
1438
+ resource: "tasks",
1439
+ action: "list",
1440
+ query: "bug fix"
1441
+ }
1442
+ },
1443
+ {
1444
+ description: "List open tasks for a project",
1445
+ params: {
1446
+ resource: "tasks",
1447
+ action: "list",
1448
+ filter: {
1449
+ project_id: "12345",
1450
+ status: "open"
1451
+ }
1452
+ }
1453
+ },
1454
+ {
1455
+ description: "Get task with comments",
1456
+ params: {
1457
+ resource: "tasks",
1458
+ action: "get",
1459
+ id: "67890",
1460
+ include: ["comments", "assignee"]
1461
+ }
1462
+ },
1463
+ {
1464
+ description: "Create a task",
1465
+ params: {
1466
+ resource: "tasks",
1467
+ action: "create",
1468
+ title: "New task",
1469
+ project_id: "12345",
1470
+ task_list_id: "111"
1471
+ }
1472
+ }
1473
+ ]
1474
+ },
1475
+ time: {
1476
+ description: "Track time entries against services/tasks",
1477
+ actions: {
1478
+ list: "List time entries with optional filters",
1479
+ get: "Get a single time entry by ID",
1480
+ create: "Create a new time entry (requires person_id, service_id, date, time)",
1481
+ update: "Update an existing time entry"
1482
+ },
1483
+ filters: {
1484
+ person_id: "Filter by person (use \"me\" for current user)",
1485
+ service_id: "Filter by service",
1486
+ project_id: "Filter by project",
1487
+ task_id: "Filter by task",
1488
+ company_id: "Filter by company",
1489
+ deal_id: "Filter by deal",
1490
+ budget_id: "Filter by budget",
1491
+ after: "Filter entries after date (YYYY-MM-DD)",
1492
+ before: "Filter entries before date (YYYY-MM-DD)",
1493
+ status: "Filter by approval status: 1=approved, 2=unapproved, 3=rejected",
1494
+ billing_type_id: "Filter by billing type: 1=fixed, 2=actuals, 3=non_billable",
1495
+ invoicing_status: "Filter by invoicing: 1=not_invoiced, 2=drafted, 3=finalized"
1496
+ },
1497
+ fields: {
1498
+ id: "Unique time entry identifier",
1499
+ date: "Date of the entry (YYYY-MM-DD)",
1500
+ time: "Time in minutes",
1501
+ note: "Description of work done",
1502
+ billable_time: "Billable time in minutes",
1503
+ approved: "Whether the entry is approved"
1504
+ },
1505
+ examples: [{
1506
+ description: "List my time entries this week",
1507
+ params: {
1508
+ resource: "time",
1509
+ action: "list",
1510
+ filter: {
1511
+ person_id: "me",
1512
+ after: "2024-01-15",
1513
+ before: "2024-01-21"
1514
+ }
1515
+ }
1516
+ }, {
1517
+ description: "Log 2 hours",
1518
+ params: {
1519
+ resource: "time",
1520
+ action: "create",
1521
+ service_id: "12345",
1522
+ date: "2024-01-16",
1523
+ time: 120,
1524
+ note: "Development work"
1525
+ }
1526
+ }]
1527
+ },
1528
+ services: {
1529
+ description: "Budget line items within projects",
1530
+ actions: {
1531
+ list: "List services with optional filters",
1532
+ get: "Get a single service by ID"
1533
+ },
1534
+ filters: {
1535
+ project_id: "Filter by project",
1536
+ deal_id: "Filter by deal",
1537
+ task_id: "Filter by task",
1538
+ person_id: "Filter by person (trackable by)",
1539
+ budget_status: "Filter by budget status: 1=open, 2=delivered",
1540
+ billing_type: "Filter by billing type: 1=fixed, 2=actuals, 3=none",
1541
+ time_tracking_enabled: "Filter by time tracking: true/false"
1542
+ },
1543
+ fields: {
1544
+ id: "Unique service identifier",
1545
+ name: "Service name",
1546
+ budgeted_time: "Budgeted time in minutes",
1547
+ worked_time: "Logged time in minutes"
1548
+ },
1549
+ examples: [{
1550
+ description: "List services for a project",
1551
+ params: {
1552
+ resource: "services",
1553
+ action: "list",
1554
+ filter: { project_id: "12345" }
1555
+ }
1556
+ }]
1557
+ },
1558
+ people: {
1559
+ description: "Team members and contacts",
1560
+ actions: {
1561
+ list: "List people with optional filters",
1562
+ get: "Get a single person by ID",
1563
+ me: "Get the currently authenticated user"
1564
+ },
1565
+ filters: {
1566
+ query: "Text search on name or email",
1567
+ status: "Filter by status: 1=active, 2=deactivated",
1568
+ person_type: "Filter by type: 1=user, 2=contact, 3=placeholder",
1569
+ company_id: "Filter by company",
1570
+ project_id: "Filter by project",
1571
+ role_id: "Filter by role",
1572
+ team: "Filter by team name"
1573
+ },
1574
+ fields: {
1575
+ id: "Unique person identifier",
1576
+ name: "Full name",
1577
+ first_name: "First name",
1578
+ last_name: "Last name",
1579
+ email: "Email address",
1580
+ title: "Job title",
1581
+ active: "Whether the person is active"
1582
+ },
1583
+ examples: [
1584
+ {
1585
+ description: "Get current user",
1586
+ params: {
1587
+ resource: "people",
1588
+ action: "me"
1589
+ }
1590
+ },
1591
+ {
1592
+ description: "Search people by name",
1593
+ params: {
1594
+ resource: "people",
1595
+ action: "list",
1596
+ query: "john"
1597
+ }
1598
+ },
1599
+ {
1600
+ description: "List active team members",
1601
+ params: {
1602
+ resource: "people",
1603
+ action: "list",
1604
+ filter: { status: "active" }
1605
+ }
1606
+ }
1607
+ ]
1608
+ },
1609
+ companies: {
1610
+ description: "Client companies and organizations",
1611
+ actions: {
1612
+ list: "List companies with optional filters",
1613
+ get: "Get a single company by ID",
1614
+ create: "Create a new company (requires name)",
1615
+ update: "Update an existing company"
1616
+ },
1617
+ filters: {
1618
+ query: "Text search on company name",
1619
+ archived: "Filter by archived status (true/false)"
1620
+ },
1621
+ fields: {
1622
+ id: "Unique company identifier",
1623
+ name: "Company name",
1624
+ billing_name: "Legal/billing name",
1625
+ domain: "Website domain",
1626
+ vat: "VAT number"
1627
+ },
1628
+ examples: [{
1629
+ description: "Search companies",
1630
+ params: {
1631
+ resource: "companies",
1632
+ action: "list",
1633
+ query: "acme"
1634
+ }
1635
+ }, {
1636
+ description: "List active companies",
1637
+ params: {
1638
+ resource: "companies",
1639
+ action: "list",
1640
+ filter: { archived: "false" }
1641
+ }
1642
+ }]
1643
+ },
1644
+ attachments: {
1645
+ description: "File attachments on tasks, comments, deals, and pages",
1646
+ actions: {
1647
+ list: "List attachments with optional filters",
1648
+ get: "Get a single attachment by ID",
1649
+ delete: "Delete an attachment by ID"
1650
+ },
1651
+ filters: {
1652
+ task_id: "Filter by task",
1653
+ comment_id: "Filter by comment",
1654
+ deal_id: "Filter by deal",
1655
+ page_id: "Filter by page"
1656
+ },
1657
+ fields: {
1658
+ id: "Unique attachment identifier",
1659
+ name: "File name",
1660
+ content_type: "MIME type (e.g., image/png, application/pdf)",
1661
+ size: "File size in bytes",
1662
+ size_human: "Human-readable file size (e.g., 1.5 MB)",
1663
+ url: "Download URL",
1664
+ attachable_type: "Parent resource type (Task, Comment, Deal, Page)"
1665
+ },
1666
+ examples: [
1667
+ {
1668
+ description: "List attachments on a task",
1669
+ params: {
1670
+ resource: "attachments",
1671
+ action: "list",
1672
+ filter: { task_id: "12345" }
1673
+ }
1674
+ },
1675
+ {
1676
+ description: "Get attachment details",
1677
+ params: {
1678
+ resource: "attachments",
1679
+ action: "get",
1680
+ id: "67890"
1681
+ }
1682
+ },
1683
+ {
1684
+ description: "Delete an attachment",
1685
+ params: {
1686
+ resource: "attachments",
1687
+ action: "delete",
1688
+ id: "67890"
1689
+ }
1690
+ }
1691
+ ]
1692
+ },
1693
+ comments: {
1694
+ description: "Comments on tasks, deals, and other resources",
1695
+ actions: {
1696
+ list: "List comments with optional filters",
1697
+ get: "Get a single comment by ID",
1698
+ create: "Create a new comment (requires body and one of: task_id, deal_id, company_id)",
1699
+ update: "Update an existing comment"
1700
+ },
1701
+ filters: {
1702
+ task_id: "Filter by task",
1703
+ deal_id: "Filter by deal",
1704
+ project_id: "Filter by project",
1705
+ page_id: "Filter by page",
1706
+ discussion_id: "Filter by discussion"
1707
+ },
1708
+ includes: [
1709
+ "creator",
1710
+ "task",
1711
+ "deal"
1712
+ ],
1713
+ fields: {
1714
+ id: "Unique comment identifier",
1715
+ body: "Comment text (may contain HTML)",
1716
+ creator: "Person who created the comment"
1717
+ },
1718
+ examples: [{
1719
+ description: "List comments on a task",
1720
+ params: {
1721
+ resource: "comments",
1722
+ action: "list",
1723
+ filter: { task_id: "12345" }
1724
+ }
1725
+ }, {
1726
+ description: "Add a comment",
1727
+ params: {
1728
+ resource: "comments",
1729
+ action: "create",
1730
+ task_id: "12345",
1731
+ body: "Looking good!"
1732
+ }
1733
+ }]
1734
+ },
1735
+ timers: {
1736
+ description: "Active time tracking timers",
1737
+ actions: {
1738
+ list: "List active timers",
1739
+ get: "Get a single timer by ID",
1740
+ start: "Start a new timer (requires service_id or time_entry_id)",
1741
+ stop: "Stop an active timer by ID"
1742
+ },
1743
+ filters: {
1744
+ person_id: "Filter by person",
1745
+ time_entry_id: "Filter by time entry"
1746
+ },
1747
+ fields: {
1748
+ id: "Unique timer identifier",
1749
+ started_at: "When the timer started (ISO 8601)",
1750
+ total_time: "Elapsed time in seconds"
1751
+ },
1752
+ examples: [
1753
+ {
1754
+ description: "List active timers",
1755
+ params: {
1756
+ resource: "timers",
1757
+ action: "list"
1758
+ }
1759
+ },
1760
+ {
1761
+ description: "Start timer on service",
1762
+ params: {
1763
+ resource: "timers",
1764
+ action: "start",
1765
+ service_id: "12345"
1766
+ }
1767
+ },
1768
+ {
1769
+ description: "Stop timer",
1770
+ params: {
1771
+ resource: "timers",
1772
+ action: "stop",
1773
+ id: "67890"
1774
+ }
1775
+ }
1776
+ ]
1777
+ },
1778
+ deals: {
1779
+ description: "Sales deals and opportunities",
1780
+ actions: {
1781
+ list: "List deals with optional filters",
1782
+ get: "Get a single deal by ID",
1783
+ create: "Create a new deal (requires name, company_id)",
1784
+ update: "Update an existing deal"
1785
+ },
1786
+ filters: {
1787
+ query: "Text search on deal name",
1788
+ company_id: "Filter by company",
1789
+ project_id: "Filter by project",
1790
+ responsible_id: "Filter by responsible person",
1791
+ pipeline_id: "Filter by pipeline",
1792
+ stage_status_id: "Filter by stage: 1=open, 2=won, 3=lost",
1793
+ type: "Filter by type: 1=deal, 2=budget",
1794
+ budget_status: "Filter by budget status: 1=open, 2=closed"
1795
+ },
1796
+ includes: [
1797
+ "company",
1798
+ "deal_status",
1799
+ "responsible",
1800
+ "project"
1801
+ ],
1802
+ fields: {
1803
+ id: "Unique deal identifier",
1804
+ name: "Deal name",
1805
+ number: "Deal number",
1806
+ date: "Deal date",
1807
+ status: "Current status (from deal_status)"
1808
+ },
1809
+ examples: [{
1810
+ description: "Search deals",
1811
+ params: {
1812
+ resource: "deals",
1813
+ action: "list",
1814
+ query: "website redesign"
1815
+ }
1816
+ }, {
1817
+ description: "List deals for a company",
1818
+ params: {
1819
+ resource: "deals",
1820
+ action: "list",
1821
+ filter: { company_id: "12345" }
1822
+ }
1823
+ }]
1824
+ },
1825
+ bookings: {
1826
+ description: "Resource scheduling and capacity planning",
1827
+ actions: {
1828
+ list: "List bookings with optional filters",
1829
+ get: "Get a single booking by ID",
1830
+ create: "Create a new booking (requires person_id, started_on, ended_on, and service_id or event_id)",
1831
+ update: "Update an existing booking"
1832
+ },
1833
+ filters: {
1834
+ person_id: "Filter by person",
1835
+ service_id: "Filter by service",
1836
+ project_id: "Filter by project",
1837
+ company_id: "Filter by company",
1838
+ event_id: "Filter by event",
1839
+ after: "Filter bookings after date (YYYY-MM-DD)",
1840
+ before: "Filter bookings before date (YYYY-MM-DD)",
1841
+ booking_type: "Filter by type: event (absence) or service (budget)",
1842
+ draft: "Filter by tentative status: true/false"
1843
+ },
1844
+ includes: [
1845
+ "person",
1846
+ "service",
1847
+ "event"
1848
+ ],
1849
+ fields: {
1850
+ id: "Unique booking identifier",
1851
+ started_on: "Start date (YYYY-MM-DD)",
1852
+ ended_on: "End date (YYYY-MM-DD)",
1853
+ time: "Time per day in minutes",
1854
+ total_time: "Total booked time in minutes",
1855
+ note: "Booking note"
1856
+ },
1857
+ examples: [{
1858
+ description: "List my bookings",
1859
+ params: {
1860
+ resource: "bookings",
1861
+ action: "list",
1862
+ filter: { person_id: "me" }
1863
+ }
1864
+ }]
1865
+ },
1866
+ budgets: {
1867
+ description: "Budget tracking and financial overview",
1868
+ actions: {
1869
+ list: "List budgets with optional filters",
1870
+ get: "Get a single budget by ID with full details"
1871
+ },
1872
+ filters: {
1873
+ project_id: "Filter by project",
1874
+ company_id: "Filter by company",
1875
+ deal_id: "Filter by deal",
1876
+ billable: "Filter by billable status (true/false)",
1877
+ budget_type: "Filter by budget type"
1878
+ },
1879
+ fields: {
1880
+ id: "Unique budget identifier",
1881
+ name: "Budget name",
1882
+ budget_type: "Type of budget",
1883
+ billable: "Whether the budget is billable",
1884
+ started_on: "Budget start date (YYYY-MM-DD)",
1885
+ ended_on: "Budget end date (YYYY-MM-DD)",
1886
+ currency: "Budget currency code",
1887
+ total_time_budget: "Total time budget in minutes",
1888
+ remaining_time_budget: "Remaining time budget in minutes",
1889
+ total_monetary_budget: "Total monetary budget",
1890
+ remaining_monetary_budget: "Remaining monetary budget"
1891
+ },
1892
+ examples: [
1893
+ {
1894
+ description: "List all budgets",
1895
+ params: {
1896
+ resource: "budgets",
1897
+ action: "list"
1898
+ }
1899
+ },
1900
+ {
1901
+ description: "List budgets for a project",
1902
+ params: {
1903
+ resource: "budgets",
1904
+ action: "list",
1905
+ filter: { project_id: "12345" }
1906
+ }
1907
+ },
1908
+ {
1909
+ description: "Get budget details",
1910
+ params: {
1911
+ resource: "budgets",
1912
+ action: "get",
1913
+ id: "67890"
1914
+ }
1915
+ },
1916
+ {
1917
+ description: "List billable budgets",
1918
+ params: {
1919
+ resource: "budgets",
1920
+ action: "list",
1921
+ filter: { billable: "true" }
1922
+ }
1923
+ }
1924
+ ]
1925
+ },
1926
+ pages: {
1927
+ description: "Manage pages (wiki/docs) within projects",
1928
+ actions: {
1929
+ list: "List pages with optional filters",
1930
+ get: "Get a single page by ID with full details",
1931
+ create: "Create a new page (requires title, project_id)",
1932
+ update: "Update an existing page",
1933
+ delete: "Delete a page"
1934
+ },
1935
+ filters: {
1936
+ project_id: "Filter by project",
1937
+ creator_id: "Filter by creator",
1938
+ parent_page_id: "Filter by parent page (for sub-pages)"
1939
+ },
1940
+ fields: {
1941
+ id: "Unique page identifier",
1942
+ title: "Page title",
1943
+ body: "Page body content (HTML)",
1944
+ public: "Whether the page is publicly accessible",
1945
+ version_number: "Current version number",
1946
+ parent_page_id: "Parent page ID (for sub-pages)"
1947
+ },
1948
+ examples: [
1949
+ {
1950
+ description: "List pages for a project",
1951
+ params: {
1952
+ resource: "pages",
1953
+ action: "list",
1954
+ filter: { project_id: "12345" }
1955
+ }
1956
+ },
1957
+ {
1958
+ description: "Get page details",
1959
+ params: {
1960
+ resource: "pages",
1961
+ action: "get",
1962
+ id: "67890"
1963
+ }
1964
+ },
1965
+ {
1966
+ description: "Create a page",
1967
+ params: {
1968
+ resource: "pages",
1969
+ action: "create",
1970
+ title: "Getting Started",
1971
+ project_id: "12345"
1972
+ }
1973
+ },
1974
+ {
1975
+ description: "Create a sub-page",
1976
+ params: {
1977
+ resource: "pages",
1978
+ action: "create",
1979
+ title: "Sub-section",
1980
+ project_id: "12345",
1981
+ parent_page_id: "67890"
1982
+ }
1983
+ },
1984
+ {
1985
+ description: "Delete a page",
1986
+ params: {
1987
+ resource: "pages",
1988
+ action: "delete",
1989
+ id: "67890"
1990
+ }
1991
+ }
1992
+ ]
1993
+ },
1994
+ discussions: {
1995
+ description: "Manage discussions (comment threads on highlighted page content)",
1996
+ actions: {
1997
+ list: "List discussions with optional filters",
1998
+ get: "Get a single discussion by ID",
1999
+ create: "Create a new discussion (requires body, page_id)",
2000
+ update: "Update an existing discussion",
2001
+ delete: "Delete a discussion",
2002
+ resolve: "Resolve a discussion (mark as resolved)",
2003
+ reopen: "Reopen a resolved discussion"
2004
+ },
2005
+ filters: {
2006
+ page_id: "Filter by page",
2007
+ status: "Filter by status: 1=active, 2=resolved"
2008
+ },
2009
+ fields: {
2010
+ id: "Unique discussion identifier",
2011
+ title: "Discussion title",
2012
+ body: "Discussion body (HTML)",
2013
+ status: "Status: active or resolved",
2014
+ resolved_at: "When the discussion was resolved"
2015
+ },
2016
+ examples: [
2017
+ {
2018
+ description: "List discussions on a page",
2019
+ params: {
2020
+ resource: "discussions",
2021
+ action: "list",
2022
+ filter: { page_id: "12345" }
2023
+ }
2024
+ },
2025
+ {
2026
+ description: "List active discussions",
2027
+ params: {
2028
+ resource: "discussions",
2029
+ action: "list",
2030
+ status: "active"
2031
+ }
2032
+ },
2033
+ {
2034
+ description: "Create a discussion",
2035
+ params: {
2036
+ resource: "discussions",
2037
+ action: "create",
2038
+ page_id: "12345",
2039
+ body: "Review this section"
2040
+ }
2041
+ },
2042
+ {
2043
+ description: "Resolve a discussion",
2044
+ params: {
2045
+ resource: "discussions",
2046
+ action: "resolve",
2047
+ id: "67890"
2048
+ }
2049
+ },
2050
+ {
2051
+ description: "Reopen a discussion",
2052
+ params: {
2053
+ resource: "discussions",
2054
+ action: "reopen",
2055
+ id: "67890"
2056
+ }
2057
+ }
2058
+ ]
2059
+ },
2060
+ reports: {
2061
+ description: "Generate various reports (time, budget, project, etc.)",
2062
+ actions: { get: "Generate a report (requires report_type)" },
2063
+ filters: {
2064
+ person_id: "Filter by person",
2065
+ project_id: "Filter by project",
2066
+ company_id: "Filter by company",
2067
+ after: "Filter from date (YYYY-MM-DD)",
2068
+ before: "Filter to date (YYYY-MM-DD)"
2069
+ },
2070
+ fields: {
2071
+ report_type: "Type of report: time_reports, project_reports, budget_reports, person_reports, invoice_reports, payment_reports, service_reports, task_reports, company_reports, deal_reports, timesheet_reports",
2072
+ group: "Grouping dimension (varies by report type)",
2073
+ from: "Start date for date range",
2074
+ to: "End date for date range"
2075
+ },
2076
+ examples: [{
2077
+ description: "Time report by person",
2078
+ params: {
2079
+ resource: "reports",
2080
+ action: "get",
2081
+ report_type: "time_reports",
2082
+ group: "person",
2083
+ from: "2024-01-01",
2084
+ to: "2024-01-31"
2085
+ }
2086
+ }, {
2087
+ description: "Project budget report",
2088
+ params: {
2089
+ resource: "reports",
2090
+ action: "get",
2091
+ report_type: "budget_reports",
2092
+ filter: { project_id: "12345" }
2093
+ }
2094
+ }]
2095
+ }
2096
+ };
2097
+ /**
2098
+ * Handle help action - returns documentation for a specific resource
2099
+ */
2100
+ function handleHelp(resource) {
2101
+ const help = RESOURCE_HELP[resource];
2102
+ if (!help) return jsonResult({
2103
+ error: `Unknown resource: ${resource}`,
2104
+ available_resources: Object.keys(RESOURCE_HELP)
2105
+ });
2106
+ return jsonResult({
2107
+ resource,
2108
+ ...help
2109
+ });
2110
+ }
2111
+ /**
2112
+ * Get help for all resources (overview)
2113
+ */
2114
+ function handleHelpOverview() {
2115
+ return jsonResult({
2116
+ message: "Use action=\"help\" with a specific resource for detailed documentation",
2117
+ resources: Object.entries(RESOURCE_HELP).map(([resource, help]) => ({
2118
+ resource,
2119
+ description: help.description,
2120
+ actions: Object.keys(help.actions)
2121
+ }))
2122
+ });
2123
+ }
2124
+ /**
2125
+ * Pages MCP handler.
2126
+ */
2127
+ var VALID_ACTIONS$7 = [
2128
+ "list",
2129
+ "get",
2130
+ "create",
2131
+ "update",
2132
+ "delete"
2133
+ ];
2134
+ async function handlePages(action, args, ctx) {
2135
+ const { formatOptions, filter, page, perPage } = ctx;
2136
+ const { id, title, body, project_id, parent_page_id } = args;
2137
+ const execCtx = ctx.executor();
2138
+ if (action === "get") {
2139
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2140
+ const formatted = formatPage$1((await getPage({ id }, execCtx)).data, formatOptions);
2141
+ if (ctx.includeHints !== false) return jsonResult({
2142
+ ...formatted,
2143
+ _hints: getPageHints(id)
2144
+ });
2145
+ return jsonResult(formatted);
2146
+ }
2147
+ if (action === "create") {
2148
+ if (!title || !project_id) return inputErrorResult(ErrorMessages.missingRequiredFields("page", ["title", "project_id"]));
2149
+ return jsonResult({
2150
+ success: true,
2151
+ ...formatPage$1((await createPage({
2152
+ title,
2153
+ projectId: project_id,
2154
+ body,
2155
+ parentPageId: parent_page_id
2156
+ }, execCtx)).data, formatOptions)
2157
+ });
2158
+ }
2159
+ if (action === "update") {
2160
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
2161
+ return jsonResult({
2162
+ success: true,
2163
+ ...formatPage$1((await updatePage({
2164
+ id,
2165
+ title,
2166
+ body
2167
+ }, execCtx)).data, formatOptions)
2168
+ });
2169
+ }
2170
+ if (action === "delete") {
2171
+ if (!id) return inputErrorResult(ErrorMessages.missingId("delete"));
2172
+ await deletePage({ id }, execCtx);
2173
+ return jsonResult({
2174
+ success: true,
2175
+ deleted: id
2176
+ });
2177
+ }
2178
+ if (action === "list") {
2179
+ const result = await listPages({
2180
+ page,
2181
+ perPage,
2182
+ additionalFilters: filter
2183
+ }, execCtx);
2184
+ const response = formatListResponse$1(result.data, formatPage$1, result.meta, formatOptions);
2185
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
2186
+ ...response,
2187
+ _resolved: result.resolved
2188
+ });
2189
+ return jsonResult(response);
2190
+ }
2191
+ return inputErrorResult(ErrorMessages.invalidAction(action, "pages", VALID_ACTIONS$7));
2192
+ }
2193
+ /**
2194
+ * People MCP handler.
2195
+ */
2196
+ var VALID_ACTIONS$6 = [
2197
+ "list",
2198
+ "get",
2199
+ "me",
2200
+ "resolve"
2201
+ ];
2202
+ async function handlePeople(action, args, ctx, credentials) {
2203
+ const { formatOptions, filter, page, perPage } = ctx;
2204
+ const { id, query, type } = args;
2205
+ if (action === "resolve") return handleResolve({
2206
+ query,
2207
+ type
2208
+ }, ctx);
2209
+ const execCtx = ctx.executor();
2210
+ if (action === "get") {
2211
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2212
+ const formatted = formatPerson$1((await getPerson({ id }, execCtx)).data, formatOptions);
2213
+ if (ctx.includeHints !== false) return jsonResult({
2214
+ ...formatted,
2215
+ _hints: getPersonHints(id)
2216
+ });
2217
+ return jsonResult(formatted);
2218
+ }
2219
+ if (action === "me") {
2220
+ if (credentials.userId) {
2221
+ const formatted = formatPerson$1((await getPerson({ id: credentials.userId }, execCtx)).data, formatOptions);
2222
+ if (ctx.includeHints !== false) return jsonResult({
2223
+ ...formatted,
2224
+ _hints: getPersonHints(credentials.userId)
2225
+ });
2226
+ return jsonResult(formatted);
2227
+ }
2228
+ return jsonResult({
2229
+ message: "User ID not configured. Set userId in credentials to use this action.",
2230
+ hint: "Use action=\"list\" to find people, or configure the user ID in your credentials.",
2231
+ organizationId: credentials.organizationId
2232
+ });
2233
+ }
2234
+ if (action === "list") {
2235
+ const result = await listPeople({
2236
+ page,
2237
+ perPage,
2238
+ additionalFilters: filter
2239
+ }, execCtx);
2240
+ const response = formatListResponse$1(result.data, formatPerson$1, result.meta, formatOptions);
2241
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
2242
+ ...response,
2243
+ _resolved: result.resolved
2244
+ });
2245
+ return jsonResult(response);
2246
+ }
2247
+ return inputErrorResult(ErrorMessages.invalidAction(action, "people", VALID_ACTIONS$6));
2248
+ }
2249
+ /**
2250
+ * Projects MCP handler.
2251
+ */
2252
+ var VALID_ACTIONS$5 = [
2253
+ "list",
2254
+ "get",
2255
+ "resolve"
2256
+ ];
2257
+ async function handleProjects(action, args, ctx) {
2258
+ const { formatOptions, filter, page, perPage } = ctx;
2259
+ const { id, query, type } = args;
2260
+ if (action === "resolve") return handleResolve({
2261
+ query,
2262
+ type
2263
+ }, ctx);
2264
+ const execCtx = ctx.executor();
2265
+ if (action === "get") {
2266
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2267
+ const formatted = formatProject$1((await getProject({ id }, execCtx)).data, formatOptions);
2268
+ if (ctx.includeHints !== false) return jsonResult({
2269
+ ...formatted,
2270
+ _hints: getProjectHints(id)
2271
+ });
2272
+ return jsonResult(formatted);
2273
+ }
2274
+ if (action === "list") {
2275
+ const result = await listProjects({
2276
+ page,
2277
+ perPage,
2278
+ additionalFilters: filter
2279
+ }, execCtx);
2280
+ const response = formatListResponse$1(result.data, formatProject$1, result.meta, formatOptions);
2281
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
2282
+ ...response,
2283
+ _resolved: result.resolved
2284
+ });
2285
+ return jsonResult(response);
2286
+ }
2287
+ return inputErrorResult(ErrorMessages.invalidAction(action, "projects", VALID_ACTIONS$5));
2288
+ }
2289
+ /**
2290
+ * Reports MCP handler.
2291
+ */
2292
+ function formatReportData(data) {
2293
+ return data.map((item) => {
2294
+ const record = item;
2295
+ return {
2296
+ id: record.id,
2297
+ type: record.type,
2298
+ ...record.attributes
2299
+ };
2300
+ });
2301
+ }
2302
+ var VALID_ACTIONS$4 = ["get"];
2303
+ async function handleReports(action, args, ctx) {
2304
+ const { filter, page, perPage } = ctx;
2305
+ const { report_type, group, from, to, person_id, project_id, company_id, deal_id, status } = args;
2306
+ if (action !== "get") return inputErrorResult(ErrorMessages.invalidAction(action, "reports", VALID_ACTIONS$4));
2307
+ if (!report_type) return inputErrorResult(ErrorMessages.missingReportType());
2308
+ if (!VALID_REPORT_TYPES.includes(report_type)) return inputErrorResult(ErrorMessages.invalidReportType(report_type, [...VALID_REPORT_TYPES]));
2309
+ const execCtx = ctx.executor();
2310
+ const result = await getReport({
2311
+ reportType: report_type,
2312
+ page,
2313
+ perPage,
2314
+ group,
2315
+ from,
2316
+ to,
2317
+ personId: person_id,
2318
+ projectId: project_id,
2319
+ companyId: company_id,
2320
+ dealId: deal_id,
2321
+ status,
2322
+ additionalFilters: filter
2323
+ }, execCtx);
2324
+ return jsonResult({
2325
+ data: formatReportData(result.data),
2326
+ meta: result.meta
2327
+ });
2328
+ }
2329
+ /**
2330
+ * Services MCP handler.
2331
+ */
2332
+ var VALID_ACTIONS$3 = ["list"];
2333
+ async function handleServices(action, _args, ctx) {
2334
+ const { formatOptions, filter, page, perPage } = ctx;
2335
+ if (action === "list") {
2336
+ const execCtx = ctx.executor();
2337
+ const result = await listServices({
2338
+ page,
2339
+ perPage,
2340
+ additionalFilters: filter
2341
+ }, execCtx);
2342
+ return jsonResult(formatListResponse$1(result.data, formatService$1, result.meta, formatOptions));
2343
+ }
2344
+ return inputErrorResult(ErrorMessages.invalidAction(action, "services", VALID_ACTIONS$3));
2345
+ }
2346
+ /**
2347
+ * Tasks MCP handler.
2348
+ */
2349
+ var DEFAULT_TASK_INCLUDE = ["project", "project.company"];
2350
+ var VALID_ACTIONS$2 = [
2351
+ "list",
2352
+ "get",
2353
+ "create",
2354
+ "update",
2355
+ "resolve"
2356
+ ];
2357
+ async function handleTasks(action, args, ctx) {
2358
+ const { formatOptions, filter, page, perPage, include: userInclude } = ctx;
2359
+ const { id, title, project_id, task_list_id, description, assignee_id, query, type } = args;
2360
+ const include = userInclude?.length ? [...new Set([...DEFAULT_TASK_INCLUDE, ...userInclude])] : DEFAULT_TASK_INCLUDE;
2361
+ if (action === "resolve") return handleResolve({
2362
+ query,
2363
+ type,
2364
+ project_id
2365
+ }, ctx);
2366
+ const execCtx = ctx.executor();
2367
+ if (action === "get") {
2368
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2369
+ const result = await getTask({
2370
+ id,
2371
+ include
2372
+ }, execCtx);
2373
+ const formatted = formatTask$1(result.data, {
2374
+ ...formatOptions,
2375
+ included: result.included
2376
+ });
2377
+ if (ctx.includeHints !== false) {
2378
+ const serviceId = result.data.relationships?.service?.data?.id;
2379
+ return jsonResult({
2380
+ ...formatted,
2381
+ _hints: getTaskHints(id, serviceId)
2382
+ });
2383
+ }
2384
+ return jsonResult(formatted);
2385
+ }
2386
+ if (action === "create") {
2387
+ if (!title || !project_id || !task_list_id) return inputErrorResult(ErrorMessages.missingRequiredFields("task", [
2388
+ "title",
2389
+ "project_id",
2390
+ "task_list_id"
2391
+ ]));
2392
+ return jsonResult({
2393
+ success: true,
2394
+ ...formatTask$1((await createTask({
2395
+ title,
2396
+ projectId: project_id,
2397
+ taskListId: task_list_id,
2398
+ assigneeId: assignee_id,
2399
+ description
2400
+ }, execCtx)).data, formatOptions)
2401
+ });
2402
+ }
2403
+ if (action === "update") {
2404
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
2405
+ return jsonResult({
2406
+ success: true,
2407
+ ...formatTask$1((await updateTask({
2408
+ id,
2409
+ title,
2410
+ description,
2411
+ assigneeId: assignee_id
2412
+ }, execCtx)).data, formatOptions)
2413
+ });
2414
+ }
2415
+ if (action === "list") {
2416
+ const result = await listTasks({
2417
+ page,
2418
+ perPage,
2419
+ additionalFilters: filter,
2420
+ include
2421
+ }, execCtx);
2422
+ const response = formatListResponse$1(result.data, formatTask$1, result.meta, {
2423
+ ...formatOptions,
2424
+ included: result.included
2425
+ });
2426
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
2427
+ ...response,
2428
+ _resolved: result.resolved
2429
+ });
2430
+ return jsonResult(response);
2431
+ }
2432
+ return inputErrorResult(ErrorMessages.invalidAction(action, "tasks", VALID_ACTIONS$2));
2433
+ }
2434
+ /**
2435
+ * Time entries MCP handler.
2436
+ *
2437
+ * Thin adapter that delegates business logic to core executors
2438
+ * and handles MCP-specific concerns (hints, error formatting, JSON results).
2439
+ */
2440
+ var VALID_ACTIONS$1 = [
2441
+ "list",
2442
+ "get",
2443
+ "create",
2444
+ "update",
2445
+ "resolve"
2446
+ ];
2447
+ async function handleTime(action, args, ctx) {
2448
+ const { formatOptions, filter, page, perPage } = ctx;
2449
+ const { id, person_id, service_id, task_id, time, date, note, query, type, project_id } = args;
2450
+ if (action === "resolve") return handleResolve({
2451
+ query,
2452
+ type,
2453
+ project_id
2454
+ }, ctx);
2455
+ const execCtx = ctx.executor();
2456
+ if (action === "get") {
2457
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2458
+ const result = await getTimeEntry({ id }, execCtx);
2459
+ const formatted = formatTimeEntry$1(result.data, formatOptions);
2460
+ if (ctx.includeHints !== false) {
2461
+ const serviceId = result.data.relationships?.service?.data?.id;
2462
+ return jsonResult({
2463
+ ...formatted,
2464
+ _hints: getTimeEntryHints(id, void 0, serviceId)
2465
+ });
2466
+ }
2467
+ return jsonResult(formatted);
2468
+ }
2469
+ if (action === "create") {
2470
+ if (!person_id || !service_id || !time || !date) return inputErrorResult(ErrorMessages.missingRequiredFields("time entry", [
2471
+ "person_id",
2472
+ "service_id",
2473
+ "time",
2474
+ "date"
2475
+ ]));
2476
+ return jsonResult({
2477
+ success: true,
2478
+ ...formatTimeEntry$1((await createTimeEntry({
2479
+ personId: person_id,
2480
+ serviceId: service_id,
2481
+ time,
2482
+ date,
2483
+ note: note ?? void 0,
2484
+ taskId: task_id,
2485
+ projectId: project_id
2486
+ }, execCtx)).data, formatOptions)
2487
+ });
2488
+ }
2489
+ if (action === "update") {
2490
+ if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
2491
+ return jsonResult({
2492
+ success: true,
2493
+ ...formatTimeEntry$1((await updateTimeEntry({
2494
+ id,
2495
+ time: time ?? void 0,
2496
+ date: date ?? void 0,
2497
+ note: note ?? void 0
2498
+ }, execCtx)).data, formatOptions)
2499
+ });
2500
+ }
2501
+ if (action === "list") {
2502
+ const result = await listTimeEntries({
2503
+ page,
2504
+ perPage,
2505
+ additionalFilters: filter
2506
+ }, execCtx);
2507
+ const response = formatListResponse$1(result.data, formatTimeEntry$1, result.meta, formatOptions);
2508
+ if (result.resolved && Object.keys(result.resolved).length > 0) return jsonResult({
2509
+ ...response,
2510
+ _resolved: result.resolved
2511
+ });
2512
+ return jsonResult(response);
2513
+ }
2514
+ return inputErrorResult(ErrorMessages.invalidAction(action, "time", VALID_ACTIONS$1));
2515
+ }
2516
+ /**
2517
+ * Timers MCP handler.
2518
+ */
2519
+ var VALID_ACTIONS = [
2520
+ "list",
2521
+ "get",
2522
+ "start",
2523
+ "stop"
2524
+ ];
2525
+ async function handleTimers(action, args, ctx) {
2526
+ const { formatOptions, filter, page, perPage, include } = ctx;
2527
+ const { id, service_id, time_entry_id } = args;
2528
+ const execCtx = ctx.executor();
2529
+ if (action === "get") {
2530
+ if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
2531
+ const formatted = formatTimer$1((await getTimer({
2532
+ id,
2533
+ include
2534
+ }, execCtx)).data, formatOptions);
2535
+ if (ctx.includeHints !== false) return jsonResult({
2536
+ ...formatted,
2537
+ _hints: getTimerHints(id)
2538
+ });
2539
+ return jsonResult(formatted);
2540
+ }
2541
+ if (action === "start" || action === "create") {
2542
+ if (!service_id && !time_entry_id) return inputErrorResult(ErrorMessages.missingServiceForTimer());
2543
+ return jsonResult({
2544
+ success: true,
2545
+ ...formatTimer$1((await startTimer({
2546
+ serviceId: service_id,
2547
+ timeEntryId: time_entry_id
2548
+ }, execCtx)).data, formatOptions)
2549
+ });
2550
+ }
2551
+ if (action === "stop") {
2552
+ if (!id) return inputErrorResult(ErrorMessages.missingId("stop"));
2553
+ return jsonResult({
2554
+ success: true,
2555
+ ...formatTimer$1((await stopTimer({ id }, execCtx)).data, formatOptions)
2556
+ });
2557
+ }
2558
+ if (action === "list") {
2559
+ const result = await listTimers({
2560
+ page,
2561
+ perPage,
2562
+ additionalFilters: filter,
2563
+ include
2564
+ }, execCtx);
2565
+ return jsonResult(formatListResponse$1(result.data, formatTimer$1, result.meta, formatOptions));
2566
+ }
2567
+ return inputErrorResult(ErrorMessages.invalidAction(action, "timers", VALID_ACTIONS));
2568
+ }
2569
+ /**
2570
+ * Tool execution handlers for Productive MCP server
2571
+ * These are shared between stdio and HTTP transports
2572
+ *
2573
+ * Single consolidated tool for minimal token overhead:
2574
+ * - productive: resource + action based API
2575
+ */
2576
+ /** Valid resources for the productive tool */
2577
+ var VALID_RESOURCES = [
2578
+ "projects",
2579
+ "time",
2580
+ "tasks",
2581
+ "services",
2582
+ "people",
2583
+ "companies",
2584
+ "comments",
2585
+ "attachments",
2586
+ "timers",
2587
+ "deals",
2588
+ "bookings",
2589
+ "budgets",
2590
+ "pages",
2591
+ "discussions",
2592
+ "reports"
2593
+ ];
2594
+ /** Default page size for MCP (smaller than CLI to reduce token usage) */
2595
+ var DEFAULT_PER_PAGE = 20;
2596
+ /**
2597
+ * Execute a tool with the given credentials and arguments
2598
+ */
2599
+ async function executeToolWithCredentials(name, args, credentials) {
2600
+ const api = new ProductiveApi({ config: {
2601
+ apiToken: credentials.apiToken,
2602
+ organizationId: credentials.organizationId,
2603
+ userId: credentials.userId
2604
+ } });
2605
+ if (name !== "productive") return errorResult(`Unknown tool: ${name}`);
2606
+ const { resource, action, filter, page, per_page, compact, include, query, no_hints, type, ...restArgs } = args;
2607
+ const isCompact = compact ?? action !== "get";
2608
+ const formatOptions = { compact: isCompact };
2609
+ let stringFilter = toStringFilter(filter);
2610
+ const perPage = per_page ?? DEFAULT_PER_PAGE;
2611
+ if (query) stringFilter = {
2612
+ ...stringFilter,
2613
+ query
2614
+ };
2615
+ const includeHints = no_hints !== true && action === "get" && !isCompact;
2616
+ const execCtx = fromHandlerContext({ api });
2617
+ const ctx = {
2618
+ formatOptions,
2619
+ filter: stringFilter,
2620
+ page,
2621
+ perPage,
2622
+ include,
2623
+ includeHints,
2624
+ executor: () => execCtx
2625
+ };
2626
+ try {
2627
+ if (action === "help") return resource ? handleHelp(resource) : handleHelpOverview();
2628
+ const resolveArgs = {
2629
+ query,
2630
+ type
2631
+ };
2632
+ switch (resource) {
2633
+ case "projects": return await handleProjects(action, {
2634
+ ...restArgs,
2635
+ ...resolveArgs
2636
+ }, ctx);
2637
+ case "time": return await handleTime(action, {
2638
+ ...restArgs,
2639
+ ...resolveArgs
2640
+ }, ctx);
2641
+ case "tasks": return await handleTasks(action, {
2642
+ ...restArgs,
2643
+ ...resolveArgs
2644
+ }, ctx);
2645
+ case "services": return await handleServices(action, restArgs, ctx);
2646
+ case "people": return await handlePeople(action, {
2647
+ ...restArgs,
2648
+ ...resolveArgs
2649
+ }, ctx, credentials);
2650
+ case "companies": return await handleCompanies(action, {
2651
+ ...restArgs,
2652
+ ...resolveArgs
2653
+ }, ctx);
2654
+ case "comments": return await handleComments(action, restArgs, ctx);
2655
+ case "attachments": return await handleAttachments(action, restArgs, ctx);
2656
+ case "timers": return await handleTimers(action, restArgs, ctx);
2657
+ case "deals": return await handleDeals(action, {
2658
+ ...restArgs,
2659
+ ...resolveArgs
2660
+ }, ctx);
2661
+ case "bookings": return await handleBookings(action, restArgs, ctx);
2662
+ case "budgets": return await handleBudgets(action, restArgs, ctx);
2663
+ case "pages": return await handlePages(action, restArgs, ctx);
2664
+ case "discussions": return await handleDiscussions(action, restArgs, ctx);
2665
+ case "reports": return await handleReports(action, restArgs, ctx);
2666
+ default: return inputErrorResult(ErrorMessages.unknownResource(resource, VALID_RESOURCES));
2667
+ }
2668
+ } catch (error) {
2669
+ if (isUserInputError(error)) return formatError(error);
2670
+ const message = error instanceof Error ? error.message : String(error);
2671
+ const statusMatch = message.match(/(\d{3})/);
2672
+ if (statusMatch) {
2673
+ const statusCode = Number.parseInt(statusMatch[1], 10);
2674
+ return inputErrorResult(ErrorMessages.apiError(statusCode, message));
2675
+ }
2676
+ return errorResult(message);
2677
+ }
2678
+ }
2679
+ export { executeToolWithCredentials as t };
2680
+
2681
+ //# sourceMappingURL=handlers-BYE2INiR.js.map