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,34 +1,34 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { registerAuthTools } from '../../src/tools/auth.js';
2
+ import { registerAuthTools, authToolsFor } from '../../src/tools/auth.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(registerAuthTools);
8
+ const setupHandlers = () => createTestHarness(registerAuthTools);
9
9
 
10
10
  beforeEach(() => vi.clearAllMocks());
11
11
 
12
12
  describe('gog_auth_list', () => {
13
13
  it('calls run with auth list args', async () => {
14
14
  vi.mocked(runner.run).mockResolvedValue('user@gmail.com\nadmin@example.com');
15
- const handlers = setupHandlers();
16
- const result = await handlers.get('gog_auth_list')!({});
15
+ const harness = await setupHandlers();
16
+ const result = await harness.callTool('gog_auth_list', {});
17
17
  expect(runner.run).toHaveBeenCalledWith(['auth', 'list']);
18
18
  expect(result.content[0].text).toBe('user@gmail.com\nadmin@example.com');
19
19
  });
20
20
 
21
21
  it('returns error text on failure', async () => {
22
22
  vi.mocked(runner.run).mockRejectedValue(new Error('No accounts configured'));
23
- const handlers = setupHandlers();
24
- const result = await handlers.get('gog_auth_list')!({});
23
+ const harness = await setupHandlers();
24
+ const result = await harness.callTool('gog_auth_list', {});
25
25
  expect(result.content[0].text).toBe('Error: No accounts configured');
26
26
  });
27
27
 
28
28
  it('handles non-Error rejection', async () => {
29
29
  vi.mocked(runner.run).mockRejectedValue('something went wrong');
30
- const handlers = setupHandlers();
31
- const result = await handlers.get('gog_auth_list')!({});
30
+ const harness = await setupHandlers();
31
+ const result = await harness.callTool('gog_auth_list', {});
32
32
  expect(result.content[0].text).toBe('something went wrong');
33
33
  });
34
34
  });
@@ -36,16 +36,16 @@ describe('gog_auth_list', () => {
36
36
  describe('gog_auth_status', () => {
37
37
  it('calls run with auth status args', async () => {
38
38
  vi.mocked(runner.run).mockResolvedValue('keyring: system\ncredentials: ~/.config/gog');
39
- const handlers = setupHandlers();
40
- const result = await handlers.get('gog_auth_status')!({});
39
+ const harness = await setupHandlers();
40
+ const result = await harness.callTool('gog_auth_status', {});
41
41
  expect(runner.run).toHaveBeenCalledWith(['auth', 'status']);
42
42
  expect(result.content[0].text).toContain('keyring');
43
43
  });
44
44
 
45
45
  it('returns error text on failure', async () => {
46
46
  vi.mocked(runner.run).mockRejectedValue(new Error('Status failed'));
47
- const handlers = setupHandlers();
48
- const result = await handlers.get('gog_auth_status')!({});
47
+ const harness = await setupHandlers();
48
+ const result = await harness.callTool('gog_auth_status', {});
49
49
  expect(result.content[0].text).toBe('Error: Status failed');
50
50
  });
51
51
  });
@@ -53,16 +53,16 @@ describe('gog_auth_status', () => {
53
53
  describe('gog_auth_services', () => {
54
54
  it('calls run with auth services args', async () => {
55
55
  vi.mocked(runner.run).mockResolvedValue('sheets: spreadsheets.readonly');
56
- const handlers = setupHandlers();
57
- const result = await handlers.get('gog_auth_services')!({});
56
+ const harness = await setupHandlers();
57
+ const result = await harness.callTool('gog_auth_services', {});
58
58
  expect(runner.run).toHaveBeenCalledWith(['auth', 'services']);
59
59
  expect(result.content[0].text).toContain('sheets');
60
60
  });
61
61
 
62
62
  it('returns error text on failure', async () => {
63
63
  vi.mocked(runner.run).mockRejectedValue(new Error('Services failed'));
64
- const handlers = setupHandlers();
65
- const result = await handlers.get('gog_auth_services')!({});
64
+ const harness = await setupHandlers();
65
+ const result = await harness.callTool('gog_auth_services', {});
66
66
  expect(result.content[0].text).toBe('Error: Services failed');
67
67
  });
68
68
  });
@@ -70,8 +70,8 @@ describe('gog_auth_services', () => {
70
70
  describe('gog_auth_add', () => {
71
71
  it('calls run with correct args, interactive true, and 5-minute timeout', async () => {
72
72
  vi.mocked(runner.run).mockResolvedValue('Authorization successful for user@gmail.com');
73
- const handlers = setupHandlers();
74
- const result = await handlers.get('gog_auth_add')!({ email: 'user@gmail.com' });
73
+ const harness = await setupHandlers();
74
+ const result = await harness.callTool('gog_auth_add', { email: 'user@gmail.com' });
75
75
  expect(runner.run).toHaveBeenCalledWith(
76
76
  ['auth', 'add', 'user@gmail.com', '--services', 'all'],
77
77
  { interactive: true, timeout: 300_000 },
@@ -81,8 +81,8 @@ describe('gog_auth_add', () => {
81
81
 
82
82
  it('passes custom services when provided', async () => {
83
83
  vi.mocked(runner.run).mockResolvedValue('Authorization successful');
84
- const handlers = setupHandlers();
85
- await handlers.get('gog_auth_add')!({ email: 'user@gmail.com', services: 'sheets,gmail' });
84
+ const harness = await setupHandlers();
85
+ await harness.callTool('gog_auth_add', { email: 'user@gmail.com', services: 'sheets,gmail' });
86
86
  expect(runner.run).toHaveBeenCalledWith(
87
87
  ['auth', 'add', 'user@gmail.com', '--services', 'sheets,gmail'],
88
88
  { interactive: true, timeout: 300_000 },
@@ -91,40 +91,196 @@ describe('gog_auth_add', () => {
91
91
 
92
92
  it('returns error text on failure', async () => {
93
93
  vi.mocked(runner.run).mockRejectedValue(new Error('Auth cancelled by user'));
94
- const handlers = setupHandlers();
95
- const result = await handlers.get('gog_auth_add')!({ email: 'user@gmail.com' });
94
+ const harness = await setupHandlers();
95
+ const result = await harness.callTool('gog_auth_add', { email: 'user@gmail.com' });
96
96
  expect(result.content[0].text).toBe('Error: Auth cancelled by user');
97
97
  });
98
98
 
99
99
  it('returns error text on timeout', async () => {
100
100
  vi.mocked(runner.run).mockRejectedValue(new Error('gog timed out after 300000ms (5 minutes)'));
101
- const handlers = setupHandlers();
102
- const result = await handlers.get('gog_auth_add')!({ email: 'user@gmail.com' });
101
+ const harness = await setupHandlers();
102
+ const result = await harness.callTool('gog_auth_add', { email: 'user@gmail.com' });
103
103
  expect(result.content[0].text).toContain('timed out');
104
104
  expect(result.content[0].text).toContain('5 minutes');
105
105
  });
106
106
  });
107
107
 
108
+ describe('gog_auth_health', () => {
109
+ const CHECK_JSON = JSON.stringify({
110
+ accounts: [
111
+ { email: 'chris.c.hall@gmail.com', created_at: '2026-07-17T15:08:39Z', valid: true },
112
+ ],
113
+ });
114
+
115
+ it('calls run with auth list --check and formats a health summary', async () => {
116
+ vi.mocked(runner.run).mockResolvedValue(CHECK_JSON);
117
+ const harness = await setupHandlers();
118
+ const result = await harness.callTool('gog_auth_health', {});
119
+ expect(runner.run).toHaveBeenCalledWith(['auth', 'list', '--check']);
120
+ expect(result.content[0].text).toContain('chris.c.hall@gmail.com');
121
+ expect(result.content[0].text).toContain('token valid');
122
+ });
123
+
124
+ it('surfaces a dead-token account with re-auth guidance', async () => {
125
+ vi.mocked(runner.run).mockResolvedValue(JSON.stringify({
126
+ accounts: [{
127
+ email: 'chris.c.hall@gmail.com',
128
+ created_at: '2026-07-17T15:08:39Z',
129
+ valid: false,
130
+ error: 'refresh access token: oauth2: "invalid_grant" "Token has been expired or revoked."',
131
+ }],
132
+ }));
133
+ const harness = await setupHandlers();
134
+ const result = await harness.callTool('gog_auth_health', {});
135
+ expect(result.content[0].text).toContain('NEEDS RE-AUTH');
136
+ expect(result.content[0].text).toContain('gog_auth_add_url');
137
+ });
138
+
139
+ it('returns error text on failure', async () => {
140
+ vi.mocked(runner.run).mockRejectedValue(new Error('keyring locked'));
141
+ const harness = await setupHandlers();
142
+ const result = await harness.callTool('gog_auth_health', {});
143
+ expect(result.content[0].text).toBe('Error: keyring locked');
144
+ });
145
+ });
146
+
147
+ describe('gog_auth_add_url', () => {
148
+ it('runs remote step 1 with force-consent and tokens-only redaction', async () => {
149
+ vi.mocked(runner.run).mockResolvedValue('{"auth_url":"https://accounts.google.com/o/oauth2/auth?...","state_reused":false}');
150
+ const harness = await setupHandlers();
151
+ const result = await harness.callTool('gog_auth_add_url', { email: 'user@gmail.com' });
152
+ expect(runner.run).toHaveBeenCalledWith(
153
+ ['auth', 'add', 'user@gmail.com', '--remote', '--step', '1', '--services', 'all', '--force-consent'],
154
+ { redactMode: 'tokens' },
155
+ );
156
+ expect(result.content[0].text).toContain('accounts.google.com');
157
+ });
158
+
159
+ it('passes custom services', async () => {
160
+ vi.mocked(runner.run).mockResolvedValue('{"auth_url":"https://x"}');
161
+ const harness = await setupHandlers();
162
+ await harness.callTool('gog_auth_add_url', { email: 'user@gmail.com', services: 'gmail,drive' });
163
+ expect(runner.run).toHaveBeenCalledWith(
164
+ ['auth', 'add', 'user@gmail.com', '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
165
+ { redactMode: 'tokens' },
166
+ );
167
+ });
168
+
169
+ it('returns error text on failure', async () => {
170
+ vi.mocked(runner.run).mockRejectedValue(new Error('client not configured'));
171
+ const harness = await setupHandlers();
172
+ const result = await harness.callTool('gog_auth_add_url', { email: 'user@gmail.com' });
173
+ expect(result.content[0].text).toBe('Error: client not configured');
174
+ });
175
+ });
176
+
177
+ describe('gog_auth_add_complete', () => {
178
+ it('runs remote step 2 with the pasted redirect URL', async () => {
179
+ vi.mocked(runner.run).mockResolvedValue('{"email":"user@gmail.com","stored":true}');
180
+ const harness = await setupHandlers();
181
+ const result = await harness.callTool('gog_auth_add_complete', {
182
+ email: 'user@gmail.com',
183
+ redirectUrl: 'http://127.0.0.1:59436/oauth2/callback?code=abc&state=xyz',
184
+ });
185
+ expect(runner.run).toHaveBeenCalledWith(
186
+ ['auth', 'add', 'user@gmail.com', '--remote', '--step', '2', '--auth-url',
187
+ 'http://127.0.0.1:59436/oauth2/callback?code=abc&state=xyz', '--services', 'all', '--force-consent'],
188
+ );
189
+ expect(result.content[0].text).toContain('stored');
190
+ });
191
+
192
+ it('passes custom services matching step 1', async () => {
193
+ vi.mocked(runner.run).mockResolvedValue('{"stored":true}');
194
+ const harness = await setupHandlers();
195
+ await harness.callTool('gog_auth_add_complete', {
196
+ email: 'user@gmail.com',
197
+ redirectUrl: 'http://127.0.0.1/cb?code=c&state=s',
198
+ services: 'gmail,drive',
199
+ });
200
+ expect(runner.run).toHaveBeenCalledWith(
201
+ ['auth', 'add', 'user@gmail.com', '--remote', '--step', '2', '--auth-url',
202
+ 'http://127.0.0.1/cb?code=c&state=s', '--services', 'gmail,drive', '--force-consent'],
203
+ );
204
+ });
205
+
206
+ it('returns error text on failure (e.g. expired state)', async () => {
207
+ vi.mocked(runner.run).mockRejectedValue(new Error('no matching manual auth state'));
208
+ const harness = await setupHandlers();
209
+ const result = await harness.callTool('gog_auth_add_complete', {
210
+ email: 'user@gmail.com',
211
+ redirectUrl: 'http://127.0.0.1/cb?code=c&state=s',
212
+ });
213
+ expect(result.content[0].text).toBe('Error: no matching manual auth state');
214
+ });
215
+ });
216
+
217
+ describe('authToolsFor (least-privilege default services)', () => {
218
+ const gmailHarness = () => createTestHarness(authToolsFor('gmail'));
219
+
220
+ it('gog_auth_add_url defaults services to the bound value, not "all"', async () => {
221
+ vi.mocked(runner.run).mockResolvedValue('{"auth_url":"https://accounts.google.com/x"}');
222
+ const harness = await gmailHarness();
223
+ await harness.callTool('gog_auth_add_url', { email: 'u@x.com' });
224
+ expect(runner.run).toHaveBeenCalledWith(
225
+ ['auth', 'add', 'u@x.com', '--remote', '--step', '1', '--services', 'gmail', '--force-consent'],
226
+ { redactMode: 'tokens' },
227
+ );
228
+ });
229
+
230
+ it('gog_auth_add defaults services to the bound value', async () => {
231
+ vi.mocked(runner.run).mockResolvedValue('ok');
232
+ const harness = await gmailHarness();
233
+ await harness.callTool('gog_auth_add', { email: 'u@x.com' });
234
+ expect(runner.run).toHaveBeenCalledWith(
235
+ ['auth', 'add', 'u@x.com', '--services', 'gmail'],
236
+ { interactive: true, timeout: 300_000 },
237
+ );
238
+ });
239
+
240
+ it('gog_auth_add_complete defaults services to the bound value', async () => {
241
+ vi.mocked(runner.run).mockResolvedValue('{"stored":true}');
242
+ const harness = await gmailHarness();
243
+ await harness.callTool('gog_auth_add_complete', {
244
+ email: 'u@x.com',
245
+ redirectUrl: 'http://127.0.0.1/cb?code=c&state=s',
246
+ });
247
+ expect(runner.run).toHaveBeenCalledWith(
248
+ ['auth', 'add', 'u@x.com', '--remote', '--step', '2', '--auth-url',
249
+ 'http://127.0.0.1/cb?code=c&state=s', '--services', 'gmail', '--force-consent'],
250
+ );
251
+ });
252
+
253
+ it('still allows overriding services per call (widen when needed)', async () => {
254
+ vi.mocked(runner.run).mockResolvedValue('{"auth_url":"x"}');
255
+ const harness = await gmailHarness();
256
+ await harness.callTool('gog_auth_add_url', { email: 'u@x.com', services: 'gmail,drive' });
257
+ expect(runner.run).toHaveBeenCalledWith(
258
+ ['auth', 'add', 'u@x.com', '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
259
+ { redactMode: 'tokens' },
260
+ );
261
+ });
262
+ });
263
+
108
264
  describe('gog_auth_run', () => {
109
265
  it('passes subcommand and args to runner', async () => {
110
266
  vi.mocked(runner.run).mockResolvedValue('removed user@gmail.com');
111
- const handlers = setupHandlers();
112
- const result = await handlers.get('gog_auth_run')!({ subcommand: 'remove', args: ['user@gmail.com'] });
267
+ const harness = await setupHandlers();
268
+ const result = await harness.callTool('gog_auth_run', { subcommand: 'remove', args: ['user@gmail.com'] });
113
269
  expect(runner.run).toHaveBeenCalledWith(['auth', 'remove', 'user@gmail.com'], {});
114
270
  expect(result.content[0].text).toBe('removed user@gmail.com');
115
271
  });
116
272
 
117
273
  it('works with empty args array', async () => {
118
274
  vi.mocked(runner.run).mockResolvedValue('token info');
119
- const handlers = setupHandlers();
120
- await handlers.get('gog_auth_run')!({ subcommand: 'tokens', args: [] });
275
+ const harness = await setupHandlers();
276
+ await harness.callTool('gog_auth_run', { subcommand: 'tokens', args: [] });
121
277
  expect(runner.run).toHaveBeenCalledWith(['auth', 'tokens'], {});
122
278
  });
123
279
 
124
280
  it('returns error text on failure', async () => {
125
281
  vi.mocked(runner.run).mockRejectedValue(new Error('Remove failed'));
126
- const handlers = setupHandlers();
127
- const result = await handlers.get('gog_auth_run')!({ subcommand: 'remove', args: ['x@y.com'] });
282
+ const harness = await setupHandlers();
283
+ const result = await harness.callTool('gog_auth_run', { subcommand: 'remove', args: ['x@y.com'] });
128
284
  expect(result.content[0].text).toBe('Error: Remove failed');
129
285
  });
130
286
  });