gogcli-mcp 2.8.0 → 2.18.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 (46) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/index.js +20218 -19700
  4. package/dist/lib.js +15845 -23331
  5. package/manifest.json +33 -1
  6. package/package.json +7 -6
  7. package/server.json +2 -2
  8. package/src/connector-auth.ts +46 -0
  9. package/src/connector-runtime.ts +177 -0
  10. package/src/index.ts +7 -5
  11. package/src/lib.ts +8 -7
  12. package/src/runner.ts +225 -30
  13. package/src/server.ts +21 -25
  14. package/src/tools/api.ts +65 -0
  15. package/src/tools/auth.ts +112 -15
  16. package/src/tools/calendar.ts +24 -9
  17. package/src/tools/docs.ts +28 -3
  18. package/src/tools/drive.ts +124 -1
  19. package/src/tools/gmail.ts +7 -3
  20. package/src/tools/sheets.ts +6 -3
  21. package/src/tools/slides.ts +9 -4
  22. package/src/tools/tasks.ts +3 -1
  23. package/src/tools/utils.ts +163 -27
  24. package/src/worker.ts +99 -0
  25. package/tests/connector-auth.test.ts +28 -0
  26. package/tests/connector-runtime.test.ts +474 -0
  27. package/tests/runner-file-args-failure.test.ts +94 -0
  28. package/tests/runner-file-args.test.ts +232 -0
  29. package/tests/runner.test.ts +221 -13
  30. package/tests/server.test.ts +28 -28
  31. package/tests/tools/api.test.ts +107 -0
  32. package/tests/tools/auth.test.ts +187 -31
  33. package/tests/tools/calendar.test.ts +115 -52
  34. package/tests/tools/classroom.test.ts +77 -77
  35. package/tests/tools/contacts.test.ts +24 -24
  36. package/tests/tools/docs.test.ts +84 -46
  37. package/tests/tools/drive.test.ts +226 -55
  38. package/tests/tools/gmail.test.ts +61 -28
  39. package/tests/tools/sheets.test.ts +81 -70
  40. package/tests/tools/slides.test.ts +56 -36
  41. package/tests/tools/tasks.test.ts +33 -33
  42. package/tests/tools/utils.test.ts +116 -2
  43. package/tests/worker.test.ts +142 -0
  44. package/tsconfig.json +4 -1
  45. package/vitest.config.ts +33 -2
  46. package/tests/helpers/test-harness.ts +0 -27
@@ -1,26 +1,26 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { registerCalendarTools } from '../../src/tools/calendar.js';
3
3
  import * as runner from '../../src/runner.js';
4
- import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
4
+ import { createTestHarness } from '@chrischall/mcp-utils/test';
5
5
 
6
6
  vi.mock('../../src/runner.js');
7
7
 
8
- const setupHandlers = () => setupHandlersBase(registerCalendarTools);
8
+ const setupHandlers = () => createTestHarness(registerCalendarTools);
9
9
 
10
10
  beforeEach(() => vi.clearAllMocks());
11
11
 
12
12
  describe('gog_calendar_events', () => {
13
13
  it('calls run with no filters', async () => {
14
14
  vi.mocked(runner.run).mockResolvedValue('{"items":[]}');
15
- const handlers = setupHandlers();
16
- await handlers.get('gog_calendar_events')!({});
15
+ const harness = await setupHandlers();
16
+ await harness.callTool('gog_calendar_events', {});
17
17
  expect(runner.run).toHaveBeenCalledWith(['calendar', 'events'], { account: undefined });
18
18
  });
19
19
 
20
20
  it('appends calendarId and filters when provided', async () => {
21
21
  vi.mocked(runner.run).mockResolvedValue('{}');
22
- const handlers = setupHandlers();
23
- await handlers.get('gog_calendar_events')!({
22
+ const harness = await setupHandlers();
23
+ await harness.callTool('gog_calendar_events', {
24
24
  calendarId: 'primary',
25
25
  from: '2026-01-01',
26
26
  to: '2026-01-31',
@@ -34,10 +34,30 @@ describe('gog_calendar_events', () => {
34
34
  );
35
35
  });
36
36
 
37
+ it('repeats --event-types for each requested type', async () => {
38
+ vi.mocked(runner.run).mockResolvedValue('{}');
39
+ const harness = await setupHandlers();
40
+ await harness.callTool('gog_calendar_events', { eventTypes: ['default', 'out-of-office'] });
41
+ expect(runner.run).toHaveBeenCalledWith(
42
+ ['calendar', 'events', '--event-types=default', '--event-types=out-of-office'],
43
+ { account: undefined },
44
+ );
45
+ });
46
+
47
+ it('appends --timezone when provided', async () => {
48
+ vi.mocked(runner.run).mockResolvedValue('{}');
49
+ const harness = await setupHandlers();
50
+ await harness.callTool('gog_calendar_events', { timezone: 'America/New_York' });
51
+ expect(runner.run).toHaveBeenCalledWith(
52
+ ['calendar', 'events', '--timezone=America/New_York'],
53
+ { account: undefined },
54
+ );
55
+ });
56
+
37
57
  it('returns error text on failure', async () => {
38
58
  vi.mocked(runner.run).mockRejectedValue(new Error('Events failed'));
39
- const handlers = setupHandlers();
40
- const result = await handlers.get('gog_calendar_events')!({});
59
+ const harness = await setupHandlers();
60
+ const result = await harness.callTool('gog_calendar_events', {});
41
61
  expect(result.content[0].text).toBe('Error: Events failed');
42
62
  });
43
63
  });
@@ -45,15 +65,25 @@ describe('gog_calendar_events', () => {
45
65
  describe('gog_calendar_get', () => {
46
66
  it('calls run with calendarId and eventId', async () => {
47
67
  vi.mocked(runner.run).mockResolvedValue('{"id":"evt1"}');
48
- const handlers = setupHandlers();
49
- await handlers.get('gog_calendar_get')!({ calendarId: 'primary', eventId: 'evt1' });
68
+ const harness = await setupHandlers();
69
+ await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1' });
50
70
  expect(runner.run).toHaveBeenCalledWith(['calendar', 'event', 'primary', 'evt1'], { account: undefined });
51
71
  });
52
72
 
73
+ it('appends --timezone when provided', async () => {
74
+ vi.mocked(runner.run).mockResolvedValue('{"id":"evt1"}');
75
+ const harness = await setupHandlers();
76
+ await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1', timezone: 'local' });
77
+ expect(runner.run).toHaveBeenCalledWith(
78
+ ['calendar', 'event', 'primary', 'evt1', '--timezone=local'],
79
+ { account: undefined },
80
+ );
81
+ });
82
+
53
83
  it('returns error text on failure', async () => {
54
84
  vi.mocked(runner.run).mockRejectedValue(new Error('Not found'));
55
- const handlers = setupHandlers();
56
- const result = await handlers.get('gog_calendar_get')!({ calendarId: 'primary', eventId: 'bad' });
85
+ const harness = await setupHandlers();
86
+ const result = await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'bad' });
57
87
  expect(result.content[0].text).toBe('Error: Not found');
58
88
  });
59
89
  });
@@ -61,8 +91,8 @@ describe('gog_calendar_get', () => {
61
91
  describe('gog_calendar_create', () => {
62
92
  it('calls run with required args', async () => {
63
93
  vi.mocked(runner.run).mockResolvedValue('{"id":"evt2"}');
64
- const handlers = setupHandlers();
65
- await handlers.get('gog_calendar_create')!({
94
+ const harness = await setupHandlers();
95
+ await harness.callTool('gog_calendar_create', {
66
96
  calendarId: 'primary',
67
97
  summary: 'Standup',
68
98
  from: '2026-04-14T09:00:00Z',
@@ -76,8 +106,8 @@ describe('gog_calendar_create', () => {
76
106
 
77
107
  it('appends optional flags when provided', async () => {
78
108
  vi.mocked(runner.run).mockResolvedValue('{}');
79
- const handlers = setupHandlers();
80
- await handlers.get('gog_calendar_create')!({
109
+ const harness = await setupHandlers();
110
+ await harness.callTool('gog_calendar_create', {
81
111
  calendarId: 'primary',
82
112
  summary: 'All-day',
83
113
  from: '2026-04-14',
@@ -86,12 +116,14 @@ describe('gog_calendar_create', () => {
86
116
  location: 'NYC',
87
117
  attendees: 'a@b.com,c@d.com',
88
118
  allDay: true,
119
+ timezone: 'America/New_York',
89
120
  });
90
121
  expect(runner.run).toHaveBeenCalledWith(
91
122
  [
92
123
  'calendar', 'create', 'primary',
93
124
  '--summary=All-day', '--from=2026-04-14', '--to=2026-04-15',
94
125
  '--description=Desc', '--location=NYC', '--attendees=a@b.com,c@d.com', '--all-day',
126
+ '--timezone=America/New_York',
95
127
  ],
96
128
  { account: undefined },
97
129
  );
@@ -102,8 +134,8 @@ describe('gog_calendar_create', () => {
102
134
  // OAuth clients).
103
135
  it('passes --with-zoom when withZoom is true', async () => {
104
136
  vi.mocked(runner.run).mockResolvedValue('{}');
105
- const handlers = setupHandlers();
106
- await handlers.get('gog_calendar_create')!({
137
+ const harness = await setupHandlers();
138
+ await harness.callTool('gog_calendar_create', {
107
139
  calendarId: 'primary',
108
140
  summary: 'Sync',
109
141
  from: '2026-04-14T09:00:00Z',
@@ -122,8 +154,8 @@ describe('gog_calendar_create', () => {
122
154
 
123
155
  it('omits --with-zoom when false', async () => {
124
156
  vi.mocked(runner.run).mockResolvedValue('{}');
125
- const handlers = setupHandlers();
126
- await handlers.get('gog_calendar_create')!({
157
+ const harness = await setupHandlers();
158
+ await harness.callTool('gog_calendar_create', {
127
159
  calendarId: 'primary', summary: 's', from: 'f', to: 't', withZoom: false,
128
160
  });
129
161
  expect(runner.run).toHaveBeenCalledWith(
@@ -134,8 +166,8 @@ describe('gog_calendar_create', () => {
134
166
 
135
167
  it('returns error text on failure', async () => {
136
168
  vi.mocked(runner.run).mockRejectedValue(new Error('Create failed'));
137
- const handlers = setupHandlers();
138
- const result = await handlers.get('gog_calendar_create')!({ calendarId: 'p', summary: 's', from: 'f', to: 't' });
169
+ const harness = await setupHandlers();
170
+ const result = await harness.callTool('gog_calendar_create', { calendarId: 'p', summary: 's', from: 'f', to: 't' });
139
171
  expect(result.content[0].text).toBe('Error: Create failed');
140
172
  });
141
173
  });
@@ -143,8 +175,8 @@ describe('gog_calendar_create', () => {
143
175
  describe('gog_calendar_update', () => {
144
176
  it('calls run with only provided fields', async () => {
145
177
  vi.mocked(runner.run).mockResolvedValue('{}');
146
- const handlers = setupHandlers();
147
- await handlers.get('gog_calendar_update')!({
178
+ const harness = await setupHandlers();
179
+ await harness.callTool('gog_calendar_update', {
148
180
  calendarId: 'primary',
149
181
  eventId: 'evt1',
150
182
  summary: 'New Title',
@@ -158,15 +190,15 @@ describe('gog_calendar_update', () => {
158
190
  // gog 0.24.0
159
191
  it('passes repeatable --attachment values, and an empty string to clear', async () => {
160
192
  vi.mocked(runner.run).mockResolvedValue('{}');
161
- const handlers = setupHandlers();
162
- await handlers.get('gog_calendar_update')!({
193
+ const harness = await setupHandlers();
194
+ await harness.callTool('gog_calendar_update', {
163
195
  calendarId: 'primary', eventId: 'evt1', attachments: ['https://drive.google.com/file/d/a', 'https://x.test/b.pdf'],
164
196
  });
165
197
  expect(runner.run).toHaveBeenCalledWith(
166
198
  ['calendar', 'update', 'primary', 'evt1', '--attachment=https://drive.google.com/file/d/a', '--attachment=https://x.test/b.pdf'],
167
199
  { account: undefined },
168
200
  );
169
- await handlers.get('gog_calendar_update')!({ calendarId: 'primary', eventId: 'evt1', attachments: [''] });
201
+ await harness.callTool('gog_calendar_update', { calendarId: 'primary', eventId: 'evt1', attachments: [''] });
170
202
  expect(runner.run).toHaveBeenCalledWith(
171
203
  ['calendar', 'update', 'primary', 'evt1', '--attachment='],
172
204
  { account: undefined },
@@ -175,8 +207,8 @@ describe('gog_calendar_update', () => {
175
207
 
176
208
  it('passes all optional fields when provided', async () => {
177
209
  vi.mocked(runner.run).mockResolvedValue('{}');
178
- const handlers = setupHandlers();
179
- await handlers.get('gog_calendar_update')!({
210
+ const harness = await setupHandlers();
211
+ await harness.callTool('gog_calendar_update', {
180
212
  calendarId: 'primary',
181
213
  eventId: 'evt1',
182
214
  summary: 'New',
@@ -196,11 +228,29 @@ describe('gog_calendar_update', () => {
196
228
  );
197
229
  });
198
230
 
231
+ // gog 0.31.1: --add-attendee preserves existing attendees; --attendees replaces all.
232
+ it('passes --add-attendee with modifiers without touching --attendees', async () => {
233
+ vi.mocked(runner.run).mockResolvedValue('{}');
234
+ const harness = await setupHandlers();
235
+ await harness.callTool('gog_calendar_update', {
236
+ calendarId: 'primary',
237
+ eventId: 'evt1',
238
+ addAttendees: 'room@resource.calendar.google.com;resource,x@y.com;optional',
239
+ });
240
+ expect(runner.run).toHaveBeenCalledWith(
241
+ [
242
+ 'calendar', 'update', 'primary', 'evt1',
243
+ '--add-attendee=room@resource.calendar.google.com;resource,x@y.com;optional',
244
+ ],
245
+ { account: undefined },
246
+ );
247
+ });
248
+
199
249
  // gog 0.18.0 Zoom flags: with-zoom adds, regenerate-zoom replaces, remove-zoom strips.
200
250
  it('passes --with-zoom / --regenerate-zoom / --remove-zoom independently', async () => {
201
251
  vi.mocked(runner.run).mockResolvedValue('{}');
202
- const handlers = setupHandlers();
203
- await handlers.get('gog_calendar_update')!({
252
+ const harness = await setupHandlers();
253
+ await harness.callTool('gog_calendar_update', {
204
254
  calendarId: 'primary', eventId: 'evt1', withZoom: true,
205
255
  });
206
256
  expect(runner.run).toHaveBeenCalledWith(
@@ -210,7 +260,7 @@ describe('gog_calendar_update', () => {
210
260
 
211
261
  vi.clearAllMocks();
212
262
  vi.mocked(runner.run).mockResolvedValue('{}');
213
- await handlers.get('gog_calendar_update')!({
263
+ await harness.callTool('gog_calendar_update', {
214
264
  calendarId: 'primary', eventId: 'evt1', regenerateZoom: true,
215
265
  });
216
266
  expect(runner.run).toHaveBeenCalledWith(
@@ -220,7 +270,7 @@ describe('gog_calendar_update', () => {
220
270
 
221
271
  vi.clearAllMocks();
222
272
  vi.mocked(runner.run).mockResolvedValue('{}');
223
- await handlers.get('gog_calendar_update')!({
273
+ await harness.callTool('gog_calendar_update', {
224
274
  calendarId: 'primary', eventId: 'evt1', removeZoom: true,
225
275
  });
226
276
  expect(runner.run).toHaveBeenCalledWith(
@@ -229,10 +279,23 @@ describe('gog_calendar_update', () => {
229
279
  );
230
280
  });
231
281
 
282
+ // gog 0.34.x (#926): remove-meet clears an event's Google Meet conference data.
283
+ it('passes --remove-meet', async () => {
284
+ vi.mocked(runner.run).mockResolvedValue('{}');
285
+ const harness = await setupHandlers();
286
+ await harness.callTool('gog_calendar_update', {
287
+ calendarId: 'primary', eventId: 'evt1', removeMeet: true,
288
+ });
289
+ expect(runner.run).toHaveBeenCalledWith(
290
+ ['calendar', 'update', 'primary', 'evt1', '--remove-meet'],
291
+ { account: undefined },
292
+ );
293
+ });
294
+
232
295
  it('omits zoom flags when all false', async () => {
233
296
  vi.mocked(runner.run).mockResolvedValue('{}');
234
- const handlers = setupHandlers();
235
- await handlers.get('gog_calendar_update')!({
297
+ const harness = await setupHandlers();
298
+ await harness.callTool('gog_calendar_update', {
236
299
  calendarId: 'primary', eventId: 'evt1',
237
300
  withZoom: false, regenerateZoom: false, removeZoom: false,
238
301
  });
@@ -244,8 +307,8 @@ describe('gog_calendar_update', () => {
244
307
 
245
308
  it('returns error text on failure', async () => {
246
309
  vi.mocked(runner.run).mockRejectedValue(new Error('Update failed'));
247
- const handlers = setupHandlers();
248
- const result = await handlers.get('gog_calendar_update')!({ calendarId: 'p', eventId: 'e' });
310
+ const harness = await setupHandlers();
311
+ const result = await harness.callTool('gog_calendar_update', { calendarId: 'p', eventId: 'e' });
249
312
  expect(result.content[0].text).toBe('Error: Update failed');
250
313
  });
251
314
  });
@@ -253,15 +316,15 @@ describe('gog_calendar_update', () => {
253
316
  describe('gog_calendar_delete', () => {
254
317
  it('calls run with calendarId and eventId', async () => {
255
318
  vi.mocked(runner.run).mockResolvedValue('{}');
256
- const handlers = setupHandlers();
257
- await handlers.get('gog_calendar_delete')!({ calendarId: 'primary', eventId: 'evt1' });
258
- expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', 'primary', 'evt1'], { account: undefined });
319
+ const harness = await setupHandlers();
320
+ await harness.callTool('gog_calendar_delete', { calendarId: 'primary', eventId: 'evt1' });
321
+ expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', 'primary', 'evt1', '--force'], { account: undefined });
259
322
  });
260
323
 
261
324
  it('returns error text on failure', async () => {
262
325
  vi.mocked(runner.run).mockRejectedValue(new Error('Delete failed'));
263
- const handlers = setupHandlers();
264
- const result = await handlers.get('gog_calendar_delete')!({ calendarId: 'p', eventId: 'e' });
326
+ const harness = await setupHandlers();
327
+ const result = await harness.callTool('gog_calendar_delete', { calendarId: 'p', eventId: 'e' });
265
328
  expect(result.content[0].text).toBe('Error: Delete failed');
266
329
  });
267
330
  });
@@ -269,8 +332,8 @@ describe('gog_calendar_delete', () => {
269
332
  describe('gog_calendar_respond', () => {
270
333
  it('calls run with status', async () => {
271
334
  vi.mocked(runner.run).mockResolvedValue('{}');
272
- const handlers = setupHandlers();
273
- await handlers.get('gog_calendar_respond')!({ calendarId: 'primary', eventId: 'evt1', status: 'accepted' });
335
+ const harness = await setupHandlers();
336
+ await harness.callTool('gog_calendar_respond', { calendarId: 'primary', eventId: 'evt1', status: 'accepted' });
274
337
  expect(runner.run).toHaveBeenCalledWith(
275
338
  ['calendar', 'respond', 'primary', 'evt1', '--status=accepted'],
276
339
  { account: undefined },
@@ -279,8 +342,8 @@ describe('gog_calendar_respond', () => {
279
342
 
280
343
  it('appends comment when provided', async () => {
281
344
  vi.mocked(runner.run).mockResolvedValue('{}');
282
- const handlers = setupHandlers();
283
- await handlers.get('gog_calendar_respond')!({
345
+ const harness = await setupHandlers();
346
+ await harness.callTool('gog_calendar_respond', {
284
347
  calendarId: 'primary',
285
348
  eventId: 'evt1',
286
349
  status: 'declined',
@@ -294,8 +357,8 @@ describe('gog_calendar_respond', () => {
294
357
 
295
358
  it('returns error text on failure', async () => {
296
359
  vi.mocked(runner.run).mockRejectedValue(new Error('Respond failed'));
297
- const handlers = setupHandlers();
298
- const result = await handlers.get('gog_calendar_respond')!({ calendarId: 'p', eventId: 'e', status: 'accepted' });
360
+ const harness = await setupHandlers();
361
+ const result = await harness.callTool('gog_calendar_respond', { calendarId: 'p', eventId: 'e', status: 'accepted' });
299
362
  expect(result.content[0].text).toBe('Error: Respond failed');
300
363
  });
301
364
  });
@@ -303,15 +366,15 @@ describe('gog_calendar_respond', () => {
303
366
  describe('gog_calendar_run', () => {
304
367
  it('passes subcommand and args to runner', async () => {
305
368
  vi.mocked(runner.run).mockResolvedValue('{}');
306
- const handlers = setupHandlers();
307
- await handlers.get('gog_calendar_run')!({ subcommand: 'calendars', args: [] });
369
+ const harness = await setupHandlers();
370
+ await harness.callTool('gog_calendar_run', { subcommand: 'calendars', args: [] });
308
371
  expect(runner.run).toHaveBeenCalledWith(['calendar', 'calendars'], { account: undefined });
309
372
  });
310
373
 
311
374
  it('returns error text on failure', async () => {
312
375
  vi.mocked(runner.run).mockRejectedValue(new Error('Run failed'));
313
- const handlers = setupHandlers();
314
- const result = await handlers.get('gog_calendar_run')!({ subcommand: 'freebusy', args: [] });
376
+ const harness = await setupHandlers();
377
+ const result = await harness.callTool('gog_calendar_run', { subcommand: 'freebusy', args: [] });
315
378
  expect(result.content[0].text).toBe('Error: Run failed');
316
379
  });
317
380
  });