@studiometa/productive-mcp 0.8.2 → 0.8.4

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.
@@ -190,6 +190,364 @@ function formatListResponse(data, formatter, meta, options) {
190
190
  });
191
191
  return result;
192
192
  }
193
+ function getTaskHints(taskId, serviceId) {
194
+ const hints = {
195
+ related_resources: [
196
+ {
197
+ resource: "comments",
198
+ description: "Get comments on this task",
199
+ example: {
200
+ resource: "comments",
201
+ action: "list",
202
+ filter: { task_id: taskId }
203
+ }
204
+ },
205
+ {
206
+ resource: "time",
207
+ description: "Get time entries logged on this task",
208
+ example: {
209
+ resource: "time",
210
+ action: "list",
211
+ filter: { task_id: taskId }
212
+ }
213
+ },
214
+ {
215
+ resource: "tasks",
216
+ description: "Get subtasks of this task",
217
+ example: {
218
+ resource: "tasks",
219
+ action: "list",
220
+ filter: { parent_task_id: taskId }
221
+ }
222
+ }
223
+ ],
224
+ common_actions: [
225
+ {
226
+ action: "Add a comment",
227
+ example: {
228
+ resource: "comments",
229
+ action: "create",
230
+ task_id: taskId,
231
+ body: "<your comment>"
232
+ }
233
+ }
234
+ ]
235
+ };
236
+ if (serviceId) {
237
+ hints.common_actions.push({
238
+ action: "Log time on this task",
239
+ example: {
240
+ resource: "time",
241
+ action: "create",
242
+ service_id: serviceId,
243
+ task_id: taskId,
244
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
245
+ time: 60,
246
+ note: "<description of work>"
247
+ }
248
+ });
249
+ }
250
+ return hints;
251
+ }
252
+ function getProjectHints(projectId) {
253
+ return {
254
+ related_resources: [
255
+ {
256
+ resource: "tasks",
257
+ description: "Get tasks in this project",
258
+ example: {
259
+ resource: "tasks",
260
+ action: "list",
261
+ filter: { project_id: projectId }
262
+ }
263
+ },
264
+ {
265
+ resource: "services",
266
+ description: "Get services (budget lines) for this project",
267
+ example: {
268
+ resource: "services",
269
+ action: "list",
270
+ filter: { project_id: projectId }
271
+ }
272
+ },
273
+ {
274
+ resource: "time",
275
+ description: "Get time entries for this project",
276
+ example: {
277
+ resource: "time",
278
+ action: "list",
279
+ filter: { project_id: projectId }
280
+ }
281
+ },
282
+ {
283
+ resource: "comments",
284
+ description: "Get comments on this project",
285
+ example: {
286
+ resource: "comments",
287
+ action: "list",
288
+ filter: { project_id: projectId }
289
+ }
290
+ },
291
+ {
292
+ resource: "deals",
293
+ description: "Get deals/budgets for this project",
294
+ example: {
295
+ resource: "deals",
296
+ action: "list",
297
+ filter: { project_id: projectId }
298
+ }
299
+ }
300
+ ],
301
+ common_actions: [
302
+ {
303
+ action: "Create a task",
304
+ example: {
305
+ resource: "tasks",
306
+ action: "create",
307
+ project_id: projectId,
308
+ task_list_id: "<task_list_id>",
309
+ title: "<task title>"
310
+ }
311
+ }
312
+ ]
313
+ };
314
+ }
315
+ function getDealHints(dealId) {
316
+ return {
317
+ related_resources: [
318
+ {
319
+ resource: "comments",
320
+ description: "Get comments on this deal/budget",
321
+ example: {
322
+ resource: "comments",
323
+ action: "list",
324
+ filter: { deal_id: dealId }
325
+ }
326
+ },
327
+ {
328
+ resource: "services",
329
+ description: "Get services (budget lines) for this deal",
330
+ example: {
331
+ resource: "services",
332
+ action: "list",
333
+ filter: { deal_id: dealId }
334
+ }
335
+ },
336
+ {
337
+ resource: "time",
338
+ description: "Get time entries for this deal/budget",
339
+ example: {
340
+ resource: "time",
341
+ action: "list",
342
+ filter: { deal_id: dealId }
343
+ }
344
+ },
345
+ {
346
+ resource: "bookings",
347
+ description: "Get resource bookings for this deal",
348
+ example: {
349
+ resource: "bookings",
350
+ action: "list",
351
+ filter: { deal_id: dealId }
352
+ }
353
+ }
354
+ ],
355
+ common_actions: [
356
+ {
357
+ action: "Add a comment",
358
+ example: {
359
+ resource: "comments",
360
+ action: "create",
361
+ deal_id: dealId,
362
+ body: "<your comment>"
363
+ }
364
+ }
365
+ ]
366
+ };
367
+ }
368
+ function getPersonHints(personId) {
369
+ return {
370
+ related_resources: [
371
+ {
372
+ resource: "tasks",
373
+ description: "Get tasks assigned to this person",
374
+ example: {
375
+ resource: "tasks",
376
+ action: "list",
377
+ filter: { assignee_id: personId }
378
+ }
379
+ },
380
+ {
381
+ resource: "time",
382
+ description: "Get time entries by this person",
383
+ example: {
384
+ resource: "time",
385
+ action: "list",
386
+ filter: { person_id: personId }
387
+ }
388
+ },
389
+ {
390
+ resource: "bookings",
391
+ description: "Get bookings for this person",
392
+ example: {
393
+ resource: "bookings",
394
+ action: "list",
395
+ filter: { person_id: personId }
396
+ }
397
+ },
398
+ {
399
+ resource: "timers",
400
+ description: "Get active timers for this person",
401
+ example: {
402
+ resource: "timers",
403
+ action: "list",
404
+ filter: { person_id: personId }
405
+ }
406
+ }
407
+ ]
408
+ };
409
+ }
410
+ function getCompanyHints(companyId) {
411
+ return {
412
+ related_resources: [
413
+ {
414
+ resource: "projects",
415
+ description: "Get projects for this company",
416
+ example: {
417
+ resource: "projects",
418
+ action: "list",
419
+ filter: { company_id: companyId }
420
+ }
421
+ },
422
+ {
423
+ resource: "deals",
424
+ description: "Get deals for this company",
425
+ example: {
426
+ resource: "deals",
427
+ action: "list",
428
+ filter: { company_id: companyId }
429
+ }
430
+ },
431
+ {
432
+ resource: "tasks",
433
+ description: "Get tasks for this company",
434
+ example: {
435
+ resource: "tasks",
436
+ action: "list",
437
+ filter: { company_id: companyId }
438
+ }
439
+ },
440
+ {
441
+ resource: "people",
442
+ description: "Get contacts at this company",
443
+ example: {
444
+ resource: "people",
445
+ action: "list",
446
+ filter: { company_id: companyId }
447
+ }
448
+ }
449
+ ]
450
+ };
451
+ }
452
+ function getTimeEntryHints(timeEntryId, taskId, serviceId) {
453
+ const hints = {
454
+ related_resources: [],
455
+ common_actions: [
456
+ {
457
+ action: "Update this time entry",
458
+ example: {
459
+ resource: "time",
460
+ action: "update",
461
+ id: timeEntryId,
462
+ time: 120,
463
+ note: "<updated note>"
464
+ }
465
+ }
466
+ ]
467
+ };
468
+ if (serviceId) {
469
+ hints.related_resources.push({
470
+ resource: "services",
471
+ description: "Get the associated service",
472
+ example: {
473
+ resource: "services",
474
+ action: "get",
475
+ id: serviceId
476
+ }
477
+ });
478
+ }
479
+ return hints;
480
+ }
481
+ function getCommentHints(_commentId, commentableType, commentableId) {
482
+ const hints = {
483
+ related_resources: []
484
+ };
485
+ if (commentableType && commentableId) {
486
+ const resourceMap = {
487
+ task: "tasks",
488
+ deal: "deals",
489
+ project: "projects",
490
+ company: "companies"
491
+ };
492
+ const resource = resourceMap[commentableType];
493
+ if (resource) {
494
+ hints.related_resources.push({
495
+ resource,
496
+ description: `Get the ${commentableType} this comment is on`,
497
+ example: {
498
+ resource,
499
+ action: "get",
500
+ id: commentableId
501
+ }
502
+ });
503
+ }
504
+ }
505
+ return hints;
506
+ }
507
+ function getBookingHints(bookingId, personId) {
508
+ const hints = {
509
+ related_resources: [],
510
+ common_actions: [
511
+ {
512
+ action: "Update this booking",
513
+ example: {
514
+ resource: "bookings",
515
+ action: "update",
516
+ id: bookingId,
517
+ time: 480
518
+ }
519
+ }
520
+ ]
521
+ };
522
+ if (personId) {
523
+ hints.related_resources.push({
524
+ resource: "people",
525
+ description: "Get the person this booking is for",
526
+ example: {
527
+ resource: "people",
528
+ action: "get",
529
+ id: personId
530
+ }
531
+ });
532
+ }
533
+ return hints;
534
+ }
535
+ function getTimerHints(timerId, serviceId) {
536
+ const hints = {
537
+ common_actions: [
538
+ {
539
+ action: "Stop this timer",
540
+ example: {
541
+ resource: "timers",
542
+ action: "stop",
543
+ id: timerId
544
+ }
545
+ }
546
+ ],
547
+ related_resources: []
548
+ };
549
+ return hints;
550
+ }
193
551
  function jsonResult(data) {
194
552
  return {
195
553
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -233,7 +591,18 @@ async function handleBookings(action, args, ctx) {
233
591
  if (action === "get") {
234
592
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
235
593
  const result = await api.getBooking(id, { include });
236
- return jsonResult(formatBooking(result.data, { ...formatOptions, included: result.included }));
594
+ const formatted = formatBooking(result.data, {
595
+ ...formatOptions,
596
+ included: result.included
597
+ });
598
+ if (ctx.includeHints !== false) {
599
+ const personId = result.data.relationships?.person?.data?.id;
600
+ return jsonResult({
601
+ ...formatted,
602
+ _hints: getBookingHints(id, personId)
603
+ });
604
+ }
605
+ return jsonResult(formatted);
237
606
  }
238
607
  if (action === "create") {
239
608
  if (!person_id || !started_on || !ended_on) {
@@ -285,7 +654,26 @@ async function handleComments(action, args, ctx) {
285
654
  if (action === "get") {
286
655
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
287
656
  const result = await api.getComment(id, { include });
288
- return jsonResult(formatComment(result.data, { ...formatOptions, included: result.included }));
657
+ const formatted = formatComment(result.data, {
658
+ ...formatOptions,
659
+ included: result.included
660
+ });
661
+ if (ctx.includeHints !== false) {
662
+ const commentableType = result.data.attributes?.commentable_type;
663
+ let commentableId;
664
+ if (commentableType === "task") {
665
+ commentableId = result.data.relationships?.task?.data?.id;
666
+ } else if (commentableType === "deal") {
667
+ commentableId = result.data.relationships?.deal?.data?.id;
668
+ } else if (commentableType === "company") {
669
+ commentableId = result.data.relationships?.company?.data?.id;
670
+ }
671
+ return jsonResult({
672
+ ...formatted,
673
+ _hints: getCommentHints(id, commentableType, commentableId)
674
+ });
675
+ }
676
+ return jsonResult(formatted);
289
677
  }
290
678
  if (action === "create") {
291
679
  if (!body) return inputErrorResult(ErrorMessages.missingRequiredFields("comment", ["body"]));
@@ -325,7 +713,14 @@ async function handleCompanies(action, args, ctx) {
325
713
  if (action === "get") {
326
714
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
327
715
  const result = await api.getCompany(id);
328
- return jsonResult(formatCompany(result.data, formatOptions));
716
+ const formatted = formatCompany(result.data, formatOptions);
717
+ if (ctx.includeHints !== false) {
718
+ return jsonResult({
719
+ ...formatted,
720
+ _hints: getCompanyHints(id)
721
+ });
722
+ }
723
+ return jsonResult(formatted);
329
724
  }
330
725
  if (action === "create") {
331
726
  if (!name) return inputErrorResult(ErrorMessages.missingRequiredFields("company", ["name"]));
@@ -355,7 +750,14 @@ async function handleDeals(action, args, ctx) {
355
750
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
356
751
  const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_DEAL_INCLUDE_GET, ...userInclude])] : DEFAULT_DEAL_INCLUDE_GET;
357
752
  const result = await api.getDeal(id, { include });
358
- return jsonResult(formatDeal(result.data, { ...formatOptions, included: result.included }));
753
+ const formatted = formatDeal(result.data, { ...formatOptions, included: result.included });
754
+ if (ctx.includeHints !== false) {
755
+ return jsonResult({
756
+ ...formatted,
757
+ _hints: getDealHints(id)
758
+ });
759
+ }
760
+ return jsonResult(formatted);
359
761
  }
360
762
  if (action === "create") {
361
763
  if (!name || !company_id) {
@@ -853,12 +1255,26 @@ async function handlePeople(action, args, ctx, credentials) {
853
1255
  if (action === "get") {
854
1256
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
855
1257
  const result = await api.getPerson(id);
856
- return jsonResult(formatPerson(result.data, formatOptions));
1258
+ const formatted = formatPerson(result.data, formatOptions);
1259
+ if (ctx.includeHints !== false) {
1260
+ return jsonResult({
1261
+ ...formatted,
1262
+ _hints: getPersonHints(id)
1263
+ });
1264
+ }
1265
+ return jsonResult(formatted);
857
1266
  }
858
1267
  if (action === "me") {
859
1268
  if (credentials.userId) {
860
1269
  const result = await api.getPerson(credentials.userId);
861
- return jsonResult(formatPerson(result.data, formatOptions));
1270
+ const formatted = formatPerson(result.data, formatOptions);
1271
+ if (ctx.includeHints !== false) {
1272
+ return jsonResult({
1273
+ ...formatted,
1274
+ _hints: getPersonHints(credentials.userId)
1275
+ });
1276
+ }
1277
+ return jsonResult(formatted);
862
1278
  }
863
1279
  return jsonResult({
864
1280
  message: "User ID not configured. Set userId in credentials to use this action.",
@@ -879,7 +1295,14 @@ async function handleProjects(action, args, ctx) {
879
1295
  if (action === "get") {
880
1296
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
881
1297
  const result = await api.getProject(id);
882
- return jsonResult(formatProject(result.data, formatOptions));
1298
+ const formatted = formatProject(result.data, formatOptions);
1299
+ if (ctx.includeHints !== false) {
1300
+ return jsonResult({
1301
+ ...formatted,
1302
+ _hints: getProjectHints(id)
1303
+ });
1304
+ }
1305
+ return jsonResult(formatted);
883
1306
  }
884
1307
  if (action === "list") {
885
1308
  const result = await api.getProjects({ filter, page, perPage });
@@ -1025,7 +1448,15 @@ async function handleTasks(action, args, ctx) {
1025
1448
  if (action === "get") {
1026
1449
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1027
1450
  const result = await api.getTask(id, { include });
1028
- return jsonResult(formatTask(result.data, { ...formatOptions, included: result.included }));
1451
+ const formatted = formatTask(result.data, { ...formatOptions, included: result.included });
1452
+ if (ctx.includeHints !== false) {
1453
+ const serviceId = result.data.relationships?.service?.data?.id;
1454
+ return jsonResult({
1455
+ ...formatted,
1456
+ _hints: getTaskHints(id, serviceId)
1457
+ });
1458
+ }
1459
+ return jsonResult(formatted);
1029
1460
  }
1030
1461
  if (action === "create") {
1031
1462
  if (!title || !project_id || !task_list_id) {
@@ -1069,7 +1500,15 @@ async function handleTime(action, args, ctx) {
1069
1500
  if (action === "get") {
1070
1501
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1071
1502
  const result = await api.getTimeEntry(id);
1072
- return jsonResult(formatTimeEntry(result.data, formatOptions));
1503
+ const formatted = formatTimeEntry(result.data, formatOptions);
1504
+ if (ctx.includeHints !== false) {
1505
+ const serviceId = result.data.relationships?.service?.data?.id;
1506
+ return jsonResult({
1507
+ ...formatted,
1508
+ _hints: getTimeEntryHints(id, void 0, serviceId)
1509
+ });
1510
+ }
1511
+ return jsonResult(formatted);
1073
1512
  }
1074
1513
  if (action === "create") {
1075
1514
  if (!person_id || !service_id || !time || !date) {
@@ -1114,7 +1553,14 @@ async function handleTimers(action, args, ctx) {
1114
1553
  if (action === "get") {
1115
1554
  if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
1116
1555
  const result = await api.getTimer(id, { include });
1117
- return jsonResult(formatTimer(result.data));
1556
+ const formatted = formatTimer(result.data);
1557
+ if (ctx.includeHints !== false) {
1558
+ return jsonResult({
1559
+ ...formatted,
1560
+ _hints: getTimerHints(id)
1561
+ });
1562
+ }
1563
+ return jsonResult(formatted);
1118
1564
  }
1119
1565
  if (action === "start" || action === "create") {
1120
1566
  if (!service_id && !time_entry_id) {
@@ -1157,7 +1603,18 @@ async function executeToolWithCredentials(name, args, credentials) {
1157
1603
  if (name !== "productive") {
1158
1604
  return errorResult(`Unknown tool: ${name}`);
1159
1605
  }
1160
- const { resource, action, filter, page, per_page, compact, include, query, ...restArgs } = args;
1606
+ const {
1607
+ resource,
1608
+ action,
1609
+ filter,
1610
+ page,
1611
+ per_page,
1612
+ compact,
1613
+ include,
1614
+ query,
1615
+ no_hints,
1616
+ ...restArgs
1617
+ } = args;
1161
1618
  const isCompact = compact ?? action !== "get";
1162
1619
  const formatOptions = { compact: isCompact };
1163
1620
  let stringFilter = toStringFilter(filter);
@@ -1165,13 +1622,15 @@ async function executeToolWithCredentials(name, args, credentials) {
1165
1622
  if (query) {
1166
1623
  stringFilter = { ...stringFilter, query };
1167
1624
  }
1625
+ const includeHints = no_hints !== true && action === "get" && !isCompact;
1168
1626
  const ctx = {
1169
1627
  api,
1170
1628
  formatOptions,
1171
1629
  filter: stringFilter,
1172
1630
  page,
1173
1631
  perPage,
1174
- include
1632
+ include,
1633
+ includeHints
1175
1634
  };
1176
1635
  try {
1177
1636
  if (action === "help") {
@@ -1219,4 +1678,4 @@ async function executeToolWithCredentials(name, args, credentials) {
1219
1678
  export {
1220
1679
  executeToolWithCredentials as e
1221
1680
  };
1222
- //# sourceMappingURL=index-BDke-bMP.js.map
1681
+ //# sourceMappingURL=index-CZpVCEu4.js.map