@vibescope/mcp-server 0.2.1 → 0.2.3

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 (93) hide show
  1. package/README.md +63 -38
  2. package/dist/api-client.d.ts +187 -0
  3. package/dist/api-client.js +53 -1
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +14 -14
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +54 -0
  10. package/dist/handlers/decisions.js +3 -3
  11. package/dist/handlers/deployment.js +35 -19
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +61 -2
  14. package/dist/handlers/fallback.js +5 -4
  15. package/dist/handlers/file-checkouts.d.ts +2 -0
  16. package/dist/handlers/file-checkouts.js +38 -6
  17. package/dist/handlers/findings.js +13 -12
  18. package/dist/handlers/git-issues.js +4 -4
  19. package/dist/handlers/ideas.js +5 -5
  20. package/dist/handlers/index.d.ts +1 -0
  21. package/dist/handlers/index.js +3 -0
  22. package/dist/handlers/milestones.js +5 -5
  23. package/dist/handlers/organizations.js +13 -13
  24. package/dist/handlers/progress.js +2 -2
  25. package/dist/handlers/project.js +6 -6
  26. package/dist/handlers/requests.js +3 -3
  27. package/dist/handlers/session.js +28 -9
  28. package/dist/handlers/sprints.js +17 -17
  29. package/dist/handlers/tasks.d.ts +2 -0
  30. package/dist/handlers/tasks.js +78 -20
  31. package/dist/handlers/types.d.ts +64 -2
  32. package/dist/handlers/types.js +48 -1
  33. package/dist/handlers/validation.js +3 -3
  34. package/dist/index.js +7 -2716
  35. package/dist/token-tracking.d.ts +74 -0
  36. package/dist/token-tracking.js +122 -0
  37. package/dist/tools.js +298 -9
  38. package/dist/utils.d.ts +5 -0
  39. package/dist/utils.js +17 -0
  40. package/docs/TOOLS.md +2053 -0
  41. package/package.json +4 -1
  42. package/scripts/generate-docs.ts +212 -0
  43. package/src/api-client.test.ts +723 -0
  44. package/src/api-client.ts +236 -1
  45. package/src/handlers/__test-setup__.ts +9 -0
  46. package/src/handlers/blockers.test.ts +31 -19
  47. package/src/handlers/blockers.ts +9 -8
  48. package/src/handlers/bodies-of-work.test.ts +55 -32
  49. package/src/handlers/bodies-of-work.ts +14 -14
  50. package/src/handlers/connectors.test.ts +834 -0
  51. package/src/handlers/connectors.ts +229 -0
  52. package/src/handlers/cost.ts +66 -0
  53. package/src/handlers/decisions.test.ts +34 -25
  54. package/src/handlers/decisions.ts +3 -3
  55. package/src/handlers/deployment.ts +39 -19
  56. package/src/handlers/discovery.ts +61 -2
  57. package/src/handlers/fallback.test.ts +26 -22
  58. package/src/handlers/fallback.ts +5 -4
  59. package/src/handlers/file-checkouts.test.ts +242 -49
  60. package/src/handlers/file-checkouts.ts +44 -6
  61. package/src/handlers/findings.test.ts +38 -24
  62. package/src/handlers/findings.ts +13 -12
  63. package/src/handlers/git-issues.test.ts +51 -43
  64. package/src/handlers/git-issues.ts +4 -4
  65. package/src/handlers/ideas.test.ts +28 -23
  66. package/src/handlers/ideas.ts +5 -5
  67. package/src/handlers/index.ts +3 -0
  68. package/src/handlers/milestones.test.ts +33 -28
  69. package/src/handlers/milestones.ts +5 -5
  70. package/src/handlers/organizations.test.ts +104 -83
  71. package/src/handlers/organizations.ts +13 -13
  72. package/src/handlers/progress.test.ts +20 -14
  73. package/src/handlers/progress.ts +2 -2
  74. package/src/handlers/project.test.ts +34 -27
  75. package/src/handlers/project.ts +6 -6
  76. package/src/handlers/requests.test.ts +27 -18
  77. package/src/handlers/requests.ts +3 -3
  78. package/src/handlers/session.test.ts +47 -0
  79. package/src/handlers/session.ts +26 -9
  80. package/src/handlers/sprints.test.ts +71 -50
  81. package/src/handlers/sprints.ts +17 -17
  82. package/src/handlers/tasks.test.ts +77 -15
  83. package/src/handlers/tasks.ts +90 -21
  84. package/src/handlers/tool-categories.test.ts +66 -0
  85. package/src/handlers/types.ts +81 -2
  86. package/src/handlers/validation.test.ts +78 -45
  87. package/src/handlers/validation.ts +3 -3
  88. package/src/index.ts +12 -2732
  89. package/src/token-tracking.test.ts +453 -0
  90. package/src/token-tracking.ts +164 -0
  91. package/src/tools.ts +298 -9
  92. package/src/utils.test.ts +2 -2
  93. package/src/utils.ts +17 -0
@@ -68,14 +68,17 @@ describe('listOrganizations', () => {
68
68
  expect(result.result).toMatchObject({ count: 1 });
69
69
  });
70
70
 
71
- it('should throw error when query fails', async () => {
71
+ it('should return error when query fails', async () => {
72
72
  mockApiClient.listOrganizations.mockResolvedValue({
73
73
  ok: false,
74
74
  error: 'Failed to list organizations',
75
75
  });
76
76
  const ctx = createMockContext();
77
77
 
78
- await expect(listOrganizations({}, ctx)).rejects.toThrow('Failed to list organizations');
78
+ const result = await listOrganizations({}, ctx);
79
+
80
+ expect(result.isError).toBe(true);
81
+ expect(result.result).toMatchObject({ error: 'Failed to list organizations' });
79
82
  });
80
83
  });
81
84
 
@@ -91,16 +94,17 @@ describe('createOrganization', () => {
91
94
  await expect(createOrganization({}, ctx)).rejects.toThrow(ValidationError);
92
95
  });
93
96
 
94
- it('should throw error when slug is taken', async () => {
97
+ it('should return error when slug is taken', async () => {
95
98
  mockApiClient.createOrganization.mockResolvedValue({
96
99
  ok: false,
97
100
  error: 'Organization slug "test-org" is already taken',
98
101
  });
99
102
  const ctx = createMockContext();
100
103
 
101
- await expect(
102
- createOrganization({ name: 'Test Org' }, ctx)
103
- ).rejects.toThrow('Organization slug "test-org" is already taken');
104
+ const result = await createOrganization({ name: 'Test Org' }, ctx);
105
+
106
+ expect(result.isError).toBe(true);
107
+ expect(result.result).toMatchObject({ error: 'Organization slug "test-org" is already taken' });
104
108
  });
105
109
 
106
110
  it('should create organization with auto-generated slug', async () => {
@@ -138,16 +142,17 @@ describe('createOrganization', () => {
138
142
  expect(result.result).toMatchObject({ success: true });
139
143
  });
140
144
 
141
- it('should throw error when insert fails', async () => {
145
+ it('should return error when insert fails', async () => {
142
146
  mockApiClient.createOrganization.mockResolvedValue({
143
147
  ok: false,
144
148
  error: 'Failed to create organization',
145
149
  });
146
150
  const ctx = createMockContext();
147
151
 
148
- await expect(
149
- createOrganization({ name: 'Test Org' }, ctx)
150
- ).rejects.toThrow('Failed to create organization');
152
+ const result = await createOrganization({ name: 'Test Org' }, ctx);
153
+
154
+ expect(result.isError).toBe(true);
155
+ expect(result.result).toMatchObject({ error: 'Failed to create organization' });
151
156
  });
152
157
  });
153
158
 
@@ -174,9 +179,10 @@ describe('updateOrganization', () => {
174
179
 
175
180
  it('should throw error when no updates provided', async () => {
176
181
  const ctx = createMockContext();
182
+
177
183
  await expect(
178
184
  updateOrganization({ organization_id: VALID_UUID }, ctx)
179
- ).rejects.toThrow('No updates provided');
185
+ ).rejects.toThrow(ValidationError);
180
186
  });
181
187
 
182
188
  it('should update organization successfully', async () => {
@@ -194,16 +200,17 @@ describe('updateOrganization', () => {
194
200
  expect(result.result).toMatchObject({ success: true });
195
201
  });
196
202
 
197
- it('should throw error when update fails', async () => {
203
+ it('should return error when update fails', async () => {
198
204
  mockApiClient.updateOrganization.mockResolvedValue({
199
205
  ok: false,
200
206
  error: 'Failed to update organization',
201
207
  });
202
208
  const ctx = createMockContext();
203
209
 
204
- await expect(
205
- updateOrganization({ organization_id: VALID_UUID, name: 'New Name' }, ctx)
206
- ).rejects.toThrow('Failed to update organization');
210
+ const result = await updateOrganization({ organization_id: VALID_UUID, name: 'New Name' }, ctx);
211
+
212
+ expect(result.isError).toBe(true);
213
+ expect(result.result).toMatchObject({ error: 'Failed to update organization' });
207
214
  });
208
215
  });
209
216
 
@@ -241,16 +248,17 @@ describe('deleteOrganization', () => {
241
248
  });
242
249
  });
243
250
 
244
- it('should throw error when delete fails', async () => {
251
+ it('should return error when delete fails', async () => {
245
252
  mockApiClient.deleteOrganization.mockResolvedValue({
246
253
  ok: false,
247
254
  error: 'Failed to delete organization',
248
255
  });
249
256
  const ctx = createMockContext();
250
257
 
251
- await expect(
252
- deleteOrganization({ organization_id: VALID_UUID }, ctx)
253
- ).rejects.toThrow('Failed to delete organization');
258
+ const result = await deleteOrganization({ organization_id: VALID_UUID }, ctx);
259
+
260
+ expect(result.isError).toBe(true);
261
+ expect(result.result).toMatchObject({ error: 'Failed to delete organization' });
254
262
  });
255
263
  });
256
264
 
@@ -303,16 +311,17 @@ describe('listOrgMembers', () => {
303
311
  expect(result.result).toMatchObject({ count: 2 });
304
312
  });
305
313
 
306
- it('should throw error when query fails', async () => {
314
+ it('should return error when query fails', async () => {
307
315
  mockApiClient.listOrgMembers.mockResolvedValue({
308
316
  ok: false,
309
317
  error: 'Failed to list members',
310
318
  });
311
319
  const ctx = createMockContext();
312
320
 
313
- await expect(
314
- listOrgMembers({ organization_id: VALID_UUID }, ctx)
315
- ).rejects.toThrow('Failed to list members');
321
+ const result = await listOrgMembers({ organization_id: VALID_UUID }, ctx);
322
+
323
+ expect(result.isError).toBe(true);
324
+ expect(result.result).toMatchObject({ error: 'Failed to list members' });
316
325
  });
317
326
  });
318
327
 
@@ -337,16 +346,17 @@ describe('inviteMember', () => {
337
346
  ).rejects.toThrow(ValidationError);
338
347
  });
339
348
 
340
- it('should throw error when invite already exists', async () => {
349
+ it('should return error when invite already exists', async () => {
341
350
  mockApiClient.inviteMember.mockResolvedValue({
342
351
  ok: false,
343
352
  error: 'A pending invite already exists for test@example.com',
344
353
  });
345
354
  const ctx = createMockContext();
346
355
 
347
- await expect(
348
- inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx)
349
- ).rejects.toThrow('A pending invite already exists for test@example.com');
356
+ const result = await inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx);
357
+
358
+ expect(result.isError).toBe(true);
359
+ expect(result.result).toMatchObject({ error: 'A pending invite already exists for test@example.com' });
350
360
  });
351
361
 
352
362
  it('should create invite successfully with default role', async () => {
@@ -371,16 +381,17 @@ describe('inviteMember', () => {
371
381
  });
372
382
  });
373
383
 
374
- it('should throw error when insert fails', async () => {
384
+ it('should return error when insert fails', async () => {
375
385
  mockApiClient.inviteMember.mockResolvedValue({
376
386
  ok: false,
377
387
  error: 'Failed to create invite',
378
388
  });
379
389
  const ctx = createMockContext();
380
390
 
381
- await expect(
382
- inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx)
383
- ).rejects.toThrow('Failed to create invite');
391
+ const result = await inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx);
392
+
393
+ expect(result.isError).toBe(true);
394
+ expect(result.result).toMatchObject({ error: 'Failed to create invite' });
384
395
  });
385
396
  });
386
397
 
@@ -412,20 +423,21 @@ describe('updateMemberRole', () => {
412
423
  ).rejects.toThrow(ValidationError);
413
424
  });
414
425
 
415
- it('should throw error when changing own role', async () => {
426
+ it('should return error when changing own role', async () => {
416
427
  mockApiClient.updateMemberRole.mockResolvedValue({
417
428
  ok: false,
418
429
  error: 'Cannot change your own role',
419
430
  });
420
431
  const ctx = createMockContext({ userId: VALID_UUID });
421
432
 
422
- await expect(
423
- updateMemberRole({
424
- organization_id: OTHER_UUID,
425
- user_id: VALID_UUID,
426
- role: 'admin',
427
- }, ctx)
428
- ).rejects.toThrow('Cannot change your own role');
433
+ const result = await updateMemberRole({
434
+ organization_id: OTHER_UUID,
435
+ user_id: VALID_UUID,
436
+ role: 'admin',
437
+ }, ctx);
438
+
439
+ expect(result.isError).toBe(true);
440
+ expect(result.result).toMatchObject({ error: 'Cannot change your own role' });
429
441
  });
430
442
 
431
443
  it('should update member role successfully', async () => {
@@ -444,20 +456,21 @@ describe('updateMemberRole', () => {
444
456
  expect(result.result).toMatchObject({ success: true });
445
457
  });
446
458
 
447
- it('should throw error when update fails', async () => {
459
+ it('should return error when update fails', async () => {
448
460
  mockApiClient.updateMemberRole.mockResolvedValue({
449
461
  ok: false,
450
462
  error: 'Failed to update member role',
451
463
  });
452
464
  const ctx = createMockContext();
453
465
 
454
- await expect(
455
- updateMemberRole({
456
- organization_id: VALID_UUID,
457
- user_id: OTHER_UUID,
458
- role: 'admin',
459
- }, ctx)
460
- ).rejects.toThrow('Failed to update member role');
466
+ const result = await updateMemberRole({
467
+ organization_id: VALID_UUID,
468
+ user_id: OTHER_UUID,
469
+ role: 'admin',
470
+ }, ctx);
471
+
472
+ expect(result.isError).toBe(true);
473
+ expect(result.result).toMatchObject({ error: 'Failed to update member role' });
461
474
  });
462
475
  });
463
476
 
@@ -503,16 +516,17 @@ describe('removeMember', () => {
503
516
  });
504
517
  });
505
518
 
506
- it('should throw error when delete fails', async () => {
519
+ it('should return error when delete fails', async () => {
507
520
  mockApiClient.removeMember.mockResolvedValue({
508
521
  ok: false,
509
522
  error: 'Failed to remove member',
510
523
  });
511
524
  const ctx = createMockContext();
512
525
 
513
- await expect(
514
- removeMember({ organization_id: VALID_UUID, user_id: OTHER_UUID }, ctx)
515
- ).rejects.toThrow('Failed to remove member');
526
+ const result = await removeMember({ organization_id: VALID_UUID, user_id: OTHER_UUID }, ctx);
527
+
528
+ expect(result.isError).toBe(true);
529
+ expect(result.result).toMatchObject({ error: 'Failed to remove member' });
516
530
  });
517
531
  });
518
532
 
@@ -528,16 +542,17 @@ describe('leaveOrganization', () => {
528
542
  await expect(leaveOrganization({}, ctx)).rejects.toThrow(ValidationError);
529
543
  });
530
544
 
531
- it('should throw error when user is owner', async () => {
545
+ it('should return error when user is owner', async () => {
532
546
  mockApiClient.leaveOrganization.mockResolvedValue({
533
547
  ok: false,
534
548
  error: 'Owner cannot leave. Transfer ownership first or delete the organization.',
535
549
  });
536
550
  const ctx = createMockContext();
537
551
 
538
- await expect(
539
- leaveOrganization({ organization_id: VALID_UUID }, ctx)
540
- ).rejects.toThrow('Owner cannot leave. Transfer ownership first or delete the organization.');
552
+ const result = await leaveOrganization({ organization_id: VALID_UUID }, ctx);
553
+
554
+ expect(result.isError).toBe(true);
555
+ expect(result.result).toMatchObject({ error: 'Owner cannot leave. Transfer ownership first or delete the organization.' });
541
556
  });
542
557
 
543
558
  it('should leave organization successfully', async () => {
@@ -555,16 +570,17 @@ describe('leaveOrganization', () => {
555
570
  });
556
571
  });
557
572
 
558
- it('should throw error when delete fails', async () => {
573
+ it('should return error when delete fails', async () => {
559
574
  mockApiClient.leaveOrganization.mockResolvedValue({
560
575
  ok: false,
561
576
  error: 'Failed to leave organization',
562
577
  });
563
578
  const ctx = createMockContext();
564
579
 
565
- await expect(
566
- leaveOrganization({ organization_id: VALID_UUID }, ctx)
567
- ).rejects.toThrow('Failed to leave organization');
580
+ const result = await leaveOrganization({ organization_id: VALID_UUID }, ctx);
581
+
582
+ expect(result.isError).toBe(true);
583
+ expect(result.result).toMatchObject({ error: 'Failed to leave organization' });
568
584
  });
569
585
  });
570
586
 
@@ -589,16 +605,17 @@ describe('shareProjectWithOrg', () => {
589
605
  ).rejects.toThrow(ValidationError);
590
606
  });
591
607
 
592
- it('should throw error when project not found or not owned', async () => {
608
+ it('should return error when project not found or not owned', async () => {
593
609
  mockApiClient.shareProjectWithOrg.mockResolvedValue({
594
610
  ok: false,
595
611
  error: 'Project not found or you are not the owner',
596
612
  });
597
613
  const ctx = createMockContext();
598
614
 
599
- await expect(
600
- shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
601
- ).rejects.toThrow('Project not found or you are not the owner');
615
+ const result = await shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx);
616
+
617
+ expect(result.isError).toBe(true);
618
+ expect(result.result).toMatchObject({ error: 'Project not found or you are not the owner' });
602
619
  });
603
620
 
604
621
  it('should share project successfully with default permission', async () => {
@@ -616,16 +633,17 @@ describe('shareProjectWithOrg', () => {
616
633
  expect(result.result).toMatchObject({ success: true });
617
634
  });
618
635
 
619
- it('should throw error when share already exists', async () => {
636
+ it('should return error when share already exists', async () => {
620
637
  mockApiClient.shareProjectWithOrg.mockResolvedValue({
621
638
  ok: false,
622
639
  error: 'Project is already shared with this organization',
623
640
  });
624
641
  const ctx = createMockContext();
625
642
 
626
- await expect(
627
- shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
628
- ).rejects.toThrow('Project is already shared with this organization');
643
+ const result = await shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx);
644
+
645
+ expect(result.isError).toBe(true);
646
+ expect(result.result).toMatchObject({ error: 'Project is already shared with this organization' });
629
647
  });
630
648
  });
631
649
 
@@ -673,20 +691,21 @@ describe('updateProjectShare', () => {
673
691
  expect(result.result).toMatchObject({ success: true });
674
692
  });
675
693
 
676
- it('should throw error when update fails', async () => {
694
+ it('should return error when update fails', async () => {
677
695
  mockApiClient.updateProjectShare.mockResolvedValue({
678
696
  ok: false,
679
697
  error: 'Failed to update share',
680
698
  });
681
699
  const ctx = createMockContext();
682
700
 
683
- await expect(
684
- updateProjectShare({
685
- project_id: VALID_UUID,
686
- organization_id: OTHER_UUID,
687
- permission: 'write',
688
- }, ctx)
689
- ).rejects.toThrow('Failed to update share');
701
+ const result = await updateProjectShare({
702
+ project_id: VALID_UUID,
703
+ organization_id: OTHER_UUID,
704
+ permission: 'write',
705
+ }, ctx);
706
+
707
+ expect(result.isError).toBe(true);
708
+ expect(result.result).toMatchObject({ error: 'Failed to update share' });
690
709
  });
691
710
  });
692
711
 
@@ -732,16 +751,17 @@ describe('unshareProject', () => {
732
751
  });
733
752
  });
734
753
 
735
- it('should throw error when delete fails', async () => {
754
+ it('should return error when delete fails', async () => {
736
755
  mockApiClient.unshareProject.mockResolvedValue({
737
756
  ok: false,
738
757
  error: 'Failed to unshare project',
739
758
  });
740
759
  const ctx = createMockContext();
741
760
 
742
- await expect(
743
- unshareProject({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
744
- ).rejects.toThrow('Failed to unshare project');
761
+ const result = await unshareProject({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx);
762
+
763
+ expect(result.isError).toBe(true);
764
+ expect(result.result).toMatchObject({ error: 'Failed to unshare project' });
745
765
  });
746
766
  });
747
767
 
@@ -791,15 +811,16 @@ describe('listProjectShares', () => {
791
811
  expect(result.result).toMatchObject({ count: 1 });
792
812
  });
793
813
 
794
- it('should throw error when query fails', async () => {
814
+ it('should return error when query fails', async () => {
795
815
  mockApiClient.listProjectShares.mockResolvedValue({
796
816
  ok: false,
797
817
  error: 'Failed to list shares',
798
818
  });
799
819
  const ctx = createMockContext();
800
820
 
801
- await expect(
802
- listProjectShares({ project_id: VALID_UUID }, ctx)
803
- ).rejects.toThrow('Failed to list shares');
821
+ const result = await listProjectShares({ project_id: VALID_UUID }, ctx);
822
+
823
+ expect(result.isError).toBe(true);
824
+ expect(result.result).toMatchObject({ error: 'Failed to list shares' });
804
825
  });
805
826
  });
@@ -107,7 +107,7 @@ export const listOrganizations: Handler = async (_args, _ctx) => {
107
107
  const response = await apiClient.listOrganizations();
108
108
 
109
109
  if (!response.ok) {
110
- throw new Error(response.error || 'Failed to list organizations');
110
+ return { result: { error: response.error || 'Failed to list organizations' }, isError: true };
111
111
  }
112
112
 
113
113
  return { result: response.data };
@@ -124,7 +124,7 @@ export const createOrganization: Handler = async (args, _ctx) => {
124
124
  });
125
125
 
126
126
  if (!response.ok) {
127
- throw new Error(response.error || 'Failed to create organization');
127
+ return { result: { error: response.error || 'Failed to create organization' }, isError: true };
128
128
  }
129
129
 
130
130
  return { result: response.data };
@@ -147,7 +147,7 @@ export const updateOrganization: Handler = async (args, _ctx) => {
147
147
  const response = await apiClient.updateOrganization(organization_id, updates);
148
148
 
149
149
  if (!response.ok) {
150
- throw new Error(response.error || 'Failed to update organization');
150
+ return { result: { error: response.error || 'Failed to update organization' }, isError: true };
151
151
  }
152
152
 
153
153
  return { result: response.data };
@@ -160,7 +160,7 @@ export const deleteOrganization: Handler = async (args, _ctx) => {
160
160
  const response = await apiClient.deleteOrganization(organization_id);
161
161
 
162
162
  if (!response.ok) {
163
- throw new Error(response.error || 'Failed to delete organization');
163
+ return { result: { error: response.error || 'Failed to delete organization' }, isError: true };
164
164
  }
165
165
 
166
166
  return { result: response.data };
@@ -177,7 +177,7 @@ export const listOrgMembers: Handler = async (args, _ctx) => {
177
177
  const response = await apiClient.listOrgMembers(organization_id);
178
178
 
179
179
  if (!response.ok) {
180
- throw new Error(response.error || 'Failed to list members');
180
+ return { result: { error: response.error || 'Failed to list members' }, isError: true };
181
181
  }
182
182
 
183
183
  return { result: response.data };
@@ -190,7 +190,7 @@ export const inviteMember: Handler = async (args, _ctx) => {
190
190
  const response = await apiClient.inviteMember(organization_id, email, role as AssignableRole);
191
191
 
192
192
  if (!response.ok) {
193
- throw new Error(response.error || 'Failed to create invite');
193
+ return { result: { error: response.error || 'Failed to create invite' }, isError: true };
194
194
  }
195
195
 
196
196
  return { result: response.data };
@@ -207,7 +207,7 @@ export const updateMemberRole: Handler = async (args, _ctx) => {
207
207
  const response = await apiClient.updateMemberRole(organization_id, user_id, role as Role);
208
208
 
209
209
  if (!response.ok) {
210
- throw new Error(response.error || 'Failed to update member role');
210
+ return { result: { error: response.error || 'Failed to update member role' }, isError: true };
211
211
  }
212
212
 
213
213
  return { result: response.data };
@@ -220,7 +220,7 @@ export const removeMember: Handler = async (args, _ctx) => {
220
220
  const response = await apiClient.removeMember(organization_id, user_id);
221
221
 
222
222
  if (!response.ok) {
223
- throw new Error(response.error || 'Failed to remove member');
223
+ return { result: { error: response.error || 'Failed to remove member' }, isError: true };
224
224
  }
225
225
 
226
226
  return { result: response.data };
@@ -233,7 +233,7 @@ export const leaveOrganization: Handler = async (args, _ctx) => {
233
233
  const response = await apiClient.leaveOrganization(organization_id);
234
234
 
235
235
  if (!response.ok) {
236
- throw new Error(response.error || 'Failed to leave organization');
236
+ return { result: { error: response.error || 'Failed to leave organization' }, isError: true };
237
237
  }
238
238
 
239
239
  return { result: response.data };
@@ -250,7 +250,7 @@ export const shareProjectWithOrg: Handler = async (args, _ctx) => {
250
250
  const response = await apiClient.shareProjectWithOrg(project_id, organization_id, permission as Permission);
251
251
 
252
252
  if (!response.ok) {
253
- throw new Error(response.error || 'Failed to share project');
253
+ return { result: { error: response.error || 'Failed to share project' }, isError: true };
254
254
  }
255
255
 
256
256
  return { result: response.data };
@@ -263,7 +263,7 @@ export const updateProjectShare: Handler = async (args, _ctx) => {
263
263
  const response = await apiClient.updateProjectShare(project_id, organization_id, permission as Permission);
264
264
 
265
265
  if (!response.ok) {
266
- throw new Error(response.error || 'Failed to update share');
266
+ return { result: { error: response.error || 'Failed to update share' }, isError: true };
267
267
  }
268
268
 
269
269
  return { result: response.data };
@@ -276,7 +276,7 @@ export const unshareProject: Handler = async (args, _ctx) => {
276
276
  const response = await apiClient.unshareProject(project_id, organization_id);
277
277
 
278
278
  if (!response.ok) {
279
- throw new Error(response.error || 'Failed to unshare project');
279
+ return { result: { error: response.error || 'Failed to unshare project' }, isError: true };
280
280
  }
281
281
 
282
282
  return { result: response.data };
@@ -289,7 +289,7 @@ export const listProjectShares: Handler = async (args, _ctx) => {
289
289
  const response = await apiClient.listProjectShares(project_id);
290
290
 
291
291
  if (!response.ok) {
292
- throw new Error(response.error || 'Failed to list shares');
292
+ return { result: { error: response.error || 'Failed to list shares' }, isError: true };
293
293
  }
294
294
 
295
295
  return { result: response.data };
@@ -103,19 +103,22 @@ describe('logProgress', () => {
103
103
  expect(mockApiClient.logProgress).toHaveBeenCalled();
104
104
  });
105
105
 
106
- it('should throw error when API call fails', async () => {
106
+ it('should return error when API call fails', async () => {
107
107
  mockApiClient.logProgress.mockResolvedValue({
108
108
  ok: false,
109
109
  error: 'Insert failed',
110
110
  });
111
111
  const ctx = createMockContext();
112
112
 
113
- await expect(
114
- logProgress({
115
- project_id: '123e4567-e89b-12d3-a456-426614174000',
116
- summary: 'Progress',
117
- }, ctx)
118
- ).rejects.toThrow('Failed to log progress: Insert failed');
113
+ const result = await logProgress({
114
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
115
+ summary: 'Progress',
116
+ }, ctx);
117
+
118
+ expect(result.isError).toBe(true);
119
+ expect(result.result).toMatchObject({
120
+ error: 'Insert failed',
121
+ });
119
122
  });
120
123
 
121
124
  it('should throw error for missing project_id', async () => {
@@ -246,18 +249,21 @@ describe('getActivityFeed', () => {
246
249
  );
247
250
  });
248
251
 
249
- it('should throw error when API call fails', async () => {
252
+ it('should return error when API call fails', async () => {
250
253
  mockApiClient.getActivityFeed.mockResolvedValue({
251
254
  ok: false,
252
255
  error: 'Query failed',
253
256
  });
254
257
  const ctx = createMockContext();
255
258
 
256
- await expect(
257
- getActivityFeed(
258
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
259
- ctx
260
- )
261
- ).rejects.toThrow('Failed to fetch activity feed: Query failed');
259
+ const result = await getActivityFeed(
260
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
261
+ ctx
262
+ );
263
+
264
+ expect(result.isError).toBe(true);
265
+ expect(result.result).toMatchObject({
266
+ error: 'Query failed',
267
+ });
262
268
  });
263
269
  });
@@ -42,7 +42,7 @@ export const logProgress: Handler = async (args, ctx) => {
42
42
  });
43
43
 
44
44
  if (!response.ok) {
45
- throw new Error(`Failed to log progress: ${response.error}`);
45
+ return { result: { error: response.error || 'Failed to log progress' }, isError: true };
46
46
  }
47
47
 
48
48
  return { result: { success: true, progress_id: response.data?.progress_id } };
@@ -62,7 +62,7 @@ export const getActivityFeed: Handler = async (args, _ctx) => {
62
62
  });
63
63
 
64
64
  if (!response.ok) {
65
- throw new Error(`Failed to fetch activity feed: ${response.error}`);
65
+ return { result: { error: response.error || 'Failed to fetch activity feed' }, isError: true };
66
66
  }
67
67
 
68
68
  return { result: { activities: response.data?.activities || [] } };