gogcli-mcp 2.7.1 → 2.18.0
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +20220 -19700
- package/dist/lib.js +15847 -23331
- package/manifest.json +33 -1
- package/package.json +7 -6
- package/server.json +2 -2
- package/src/connector-auth.ts +46 -0
- package/src/connector-runtime.ts +177 -0
- package/src/index.ts +7 -5
- package/src/lib.ts +8 -7
- package/src/runner.ts +225 -30
- package/src/server.ts +21 -25
- package/src/tools/api.ts +65 -0
- package/src/tools/auth.ts +112 -15
- package/src/tools/calendar.ts +24 -9
- package/src/tools/docs.ts +30 -3
- package/src/tools/drive.ts +124 -1
- package/src/tools/gmail.ts +7 -3
- package/src/tools/sheets.ts +6 -3
- package/src/tools/slides.ts +9 -4
- package/src/tools/tasks.ts +3 -1
- package/src/tools/utils.ts +163 -27
- package/src/worker.ts +99 -0
- package/tests/connector-auth.test.ts +28 -0
- package/tests/connector-runtime.test.ts +474 -0
- package/tests/runner-file-args-failure.test.ts +94 -0
- package/tests/runner-file-args.test.ts +232 -0
- package/tests/runner.test.ts +221 -13
- package/tests/server.test.ts +28 -28
- package/tests/tools/api.test.ts +107 -0
- package/tests/tools/auth.test.ts +187 -31
- package/tests/tools/calendar.test.ts +115 -52
- package/tests/tools/classroom.test.ts +77 -77
- package/tests/tools/contacts.test.ts +24 -24
- package/tests/tools/docs.test.ts +93 -44
- package/tests/tools/drive.test.ts +226 -55
- package/tests/tools/gmail.test.ts +61 -28
- package/tests/tools/sheets.test.ts +81 -70
- package/tests/tools/slides.test.ts +56 -36
- package/tests/tools/tasks.test.ts +33 -33
- package/tests/tools/utils.test.ts +116 -2
- package/tests/worker.test.ts +142 -0
- package/tsconfig.json +4 -1
- package/vitest.config.ts +33 -2
- package/tests/helpers/test-harness.ts +0 -27
package/tests/tools/auth.test.ts
CHANGED
|
@@ -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 {
|
|
4
|
+
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
const setupHandlers = () =>
|
|
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
|
|
16
|
-
const result = await
|
|
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
|
|
24
|
-
const result = await
|
|
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
|
|
31
|
-
const result = await
|
|
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
|
|
40
|
-
const result = await
|
|
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
|
|
48
|
-
const result = await
|
|
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
|
|
57
|
-
const result = await
|
|
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
|
|
65
|
-
const result = await
|
|
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
|
|
74
|
-
const result = await
|
|
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
|
|
85
|
-
await
|
|
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
|
|
95
|
-
const result = await
|
|
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
|
|
102
|
-
const result = await
|
|
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
|
|
112
|
-
const result = await
|
|
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
|
|
120
|
-
await
|
|
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
|
|
127
|
-
const result = await
|
|
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
|
});
|