gogcli-mcp 2.0.7 → 2.0.9
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 +257 -157
- package/dist/lib.js +266 -157
- package/manifest.json +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
- package/src/lib.ts +10 -1
- package/src/runner.ts +72 -5
- package/src/tools/auth.ts +6 -10
- package/src/tools/calendar.ts +2 -12
- package/src/tools/classroom.ts +5 -11
- package/src/tools/contacts.ts +2 -12
- package/src/tools/docs.ts +2 -12
- package/src/tools/drive.ts +2 -12
- package/src/tools/gmail.ts +2 -12
- package/src/tools/sheets.ts +2 -12
- package/src/tools/slides.ts +2 -12
- package/src/tools/tasks.ts +2 -12
- package/src/tools/utils.ts +80 -0
- package/tests/helpers/{extras-harness.ts → test-harness.ts} +4 -8
- package/tests/runner.test.ts +147 -1
- package/tests/tools/auth.test.ts +2 -13
- package/tests/tools/calendar.test.ts +2 -13
- package/tests/tools/classroom.test.ts +2 -13
- package/tests/tools/contacts.test.ts +2 -13
- package/tests/tools/docs.test.ts +2 -13
- package/tests/tools/drive.test.ts +2 -13
- package/tests/tools/gmail.test.ts +2 -13
- package/tests/tools/sheets.test.ts +2 -13
- package/tests/tools/slides.test.ts +2 -13
- package/tests/tools/tasks.test.ts +2 -13
- package/tests/tools/utils.test.ts +35 -1
package/tests/runner.test.ts
CHANGED
|
@@ -24,7 +24,7 @@ describe('run', () => {
|
|
|
24
24
|
expect(spawner).toHaveBeenCalledWith(
|
|
25
25
|
'gog',
|
|
26
26
|
['--json', '--color=never', '--no-input', 'sheets', 'get', 'id1', 'A1'],
|
|
27
|
-
expect.objectContaining({ env:
|
|
27
|
+
expect.objectContaining({ env: expect.any(Object) }),
|
|
28
28
|
);
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -206,6 +206,93 @@ describe('run', () => {
|
|
|
206
206
|
.rejects.toThrow('gog not found');
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
+
it('wraps ENOENT spawn errors with an install-or-set-GOG_PATH hint', async () => {
|
|
210
|
+
const spawner = vi.fn(() => {
|
|
211
|
+
const proc = new EventEmitter() as ReturnType<Spawner>;
|
|
212
|
+
(proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stdout = new EventEmitter();
|
|
213
|
+
(proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stderr = new EventEmitter();
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
const err = new Error('spawn gog ENOENT') as NodeJS.ErrnoException;
|
|
216
|
+
err.code = 'ENOENT';
|
|
217
|
+
proc.emit('error', err);
|
|
218
|
+
}, 0);
|
|
219
|
+
return proc;
|
|
220
|
+
}) as unknown as Spawner;
|
|
221
|
+
await expect(run(['sheets', 'get', 'id', 'A1'], { spawner }))
|
|
222
|
+
.rejects.toThrow(/gog executable not found.*Install gogcli.*GOG_PATH/s);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('augments child PATH with common gogcli install dirs', async () => {
|
|
226
|
+
const spawner = makeSpawner(0, '{}');
|
|
227
|
+
const originalHome = process.env.HOME;
|
|
228
|
+
const originalPath = process.env.PATH;
|
|
229
|
+
process.env.HOME = '/Users/test';
|
|
230
|
+
process.env.PATH = '/usr/bin:/bin';
|
|
231
|
+
try {
|
|
232
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
233
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
234
|
+
const passedPath = passedEnv.PATH!;
|
|
235
|
+
expect(passedPath).toContain('/usr/bin');
|
|
236
|
+
expect(passedPath).toContain('/opt/homebrew/bin');
|
|
237
|
+
expect(passedPath).toContain('/usr/local/bin');
|
|
238
|
+
expect(passedPath).toContain('/Users/test/.local/bin');
|
|
239
|
+
expect(passedPath).toContain('/Users/test/go/bin');
|
|
240
|
+
} finally {
|
|
241
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
242
|
+
else process.env.HOME = originalHome;
|
|
243
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
244
|
+
else process.env.PATH = originalPath;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('does not duplicate dirs that are already on PATH', async () => {
|
|
249
|
+
const spawner = makeSpawner(0, '{}');
|
|
250
|
+
const originalPath = process.env.PATH;
|
|
251
|
+
process.env.PATH = '/opt/homebrew/bin:/usr/bin';
|
|
252
|
+
try {
|
|
253
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
254
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
255
|
+
const passedPath = passedEnv.PATH!;
|
|
256
|
+
const homebrewCount = passedPath.split(':').filter(d => d === '/opt/homebrew/bin').length;
|
|
257
|
+
expect(homebrewCount).toBe(1);
|
|
258
|
+
} finally {
|
|
259
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
260
|
+
else process.env.PATH = originalPath;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('augments PATH even when HOME is unset', async () => {
|
|
265
|
+
const spawner = makeSpawner(0, '{}');
|
|
266
|
+
const originalHome = process.env.HOME;
|
|
267
|
+
const originalPath = process.env.PATH;
|
|
268
|
+
delete process.env.HOME;
|
|
269
|
+
process.env.PATH = '/usr/bin';
|
|
270
|
+
try {
|
|
271
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
272
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
273
|
+
const passedPath = passedEnv.PATH!;
|
|
274
|
+
expect(passedPath).toContain('/opt/homebrew/bin');
|
|
275
|
+
expect(passedPath).not.toContain('.local/bin');
|
|
276
|
+
} finally {
|
|
277
|
+
if (originalHome !== undefined) process.env.HOME = originalHome;
|
|
278
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
279
|
+
else process.env.PATH = originalPath;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('handles empty PATH gracefully', async () => {
|
|
284
|
+
const spawner = makeSpawner(0, '{}');
|
|
285
|
+
const originalPath = process.env.PATH;
|
|
286
|
+
delete process.env.PATH;
|
|
287
|
+
try {
|
|
288
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
289
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
290
|
+
expect(passedEnv.PATH).toContain('/opt/homebrew/bin');
|
|
291
|
+
} finally {
|
|
292
|
+
if (originalPath !== undefined) process.env.PATH = originalPath;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
209
296
|
it('ignores close event if error event already settled the promise', async () => {
|
|
210
297
|
const spawner = vi.fn(() => {
|
|
211
298
|
const proc = new EventEmitter() as ReturnType<Spawner>;
|
|
@@ -396,6 +483,65 @@ describe('run', () => {
|
|
|
396
483
|
}
|
|
397
484
|
});
|
|
398
485
|
|
|
486
|
+
it('strips GOOGLE_APPLICATION_CREDENTIALS and *_TOKEN/*_SECRET/*_API_KEY/*_PRIVATE_KEY vars', async () => {
|
|
487
|
+
const spawner = makeSpawner(0, '{}');
|
|
488
|
+
const snapshot = {
|
|
489
|
+
GOOGLE_APPLICATION_CREDENTIALS: process.env.GOOGLE_APPLICATION_CREDENTIALS,
|
|
490
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
491
|
+
DB_PASSWORD_TOKEN: process.env.DB_PASSWORD_TOKEN,
|
|
492
|
+
AWS_SECRET: process.env.AWS_SECRET,
|
|
493
|
+
MY_PRIVATE_KEY: process.env.MY_PRIVATE_KEY,
|
|
494
|
+
BENIGN_VAR: process.env.BENIGN_VAR,
|
|
495
|
+
};
|
|
496
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/path/to/sa.json';
|
|
497
|
+
process.env.ANTHROPIC_API_KEY = 'sk-ant-secret';
|
|
498
|
+
process.env.DB_PASSWORD_TOKEN = 'secret';
|
|
499
|
+
process.env.AWS_SECRET = 'secret';
|
|
500
|
+
process.env.MY_PRIVATE_KEY = 'secret';
|
|
501
|
+
process.env.BENIGN_VAR = 'hello';
|
|
502
|
+
try {
|
|
503
|
+
await run(['docs', 'cat', 'id'], { spawner });
|
|
504
|
+
const envPassed = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
505
|
+
expect(envPassed.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined();
|
|
506
|
+
expect(envPassed.ANTHROPIC_API_KEY).toBeUndefined();
|
|
507
|
+
expect(envPassed.DB_PASSWORD_TOKEN).toBeUndefined();
|
|
508
|
+
expect(envPassed.AWS_SECRET).toBeUndefined();
|
|
509
|
+
expect(envPassed.MY_PRIVATE_KEY).toBeUndefined();
|
|
510
|
+
expect(envPassed.BENIGN_VAR).toBe('hello');
|
|
511
|
+
} finally {
|
|
512
|
+
for (const [k, v] of Object.entries(snapshot)) {
|
|
513
|
+
if (v === undefined) delete process.env[k];
|
|
514
|
+
else process.env[k] = v;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('redacts Bearer with quoted/encoded characters', async () => {
|
|
520
|
+
const stderrLeak = 'http 401: header was Bearer eyJ.test+slash/equal=padding more text';
|
|
521
|
+
const spawner = makeSpawner(1, '', stderrLeak);
|
|
522
|
+
try {
|
|
523
|
+
await run(['gmail', 'get', 'm1'], { spawner });
|
|
524
|
+
} catch (e) {
|
|
525
|
+
const msg = (e as Error).message;
|
|
526
|
+
expect(msg).not.toContain('eyJ.test+slash/equal=padding');
|
|
527
|
+
expect(msg).toContain('[REDACTED]');
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('redacts bearer/refresh tokens and Google API keys from stderr surfaced to the client', async () => {
|
|
532
|
+
const stderrLeak = 'request failed: Authorization: Bearer ya29.a0Ad52N3-LEAKED-TOKEN-VALUE refresh 1//0eLEAKED-REFRESH key AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI extra';
|
|
533
|
+
const spawner = makeSpawner(1, '', stderrLeak);
|
|
534
|
+
try {
|
|
535
|
+
await run(['gmail', 'get', 'm1'], { spawner });
|
|
536
|
+
} catch (e) {
|
|
537
|
+
const msg = (e as Error).message;
|
|
538
|
+
expect(msg).not.toContain('ya29.a0Ad52N3-LEAKED-TOKEN-VALUE');
|
|
539
|
+
expect(msg).not.toContain('1//0eLEAKED-REFRESH');
|
|
540
|
+
expect(msg).not.toContain('AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI');
|
|
541
|
+
expect(msg).toContain('[REDACTED]');
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
399
545
|
it('ignores timeout if close event already settled the promise', async () => {
|
|
400
546
|
vi.useFakeTimers();
|
|
401
547
|
const spawner = vi.fn(() => {
|
package/tests/tools/auth.test.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerAuthTools } from '../../src/tools/auth.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerAuthTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerAuthTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerCalendarTools } from '../../src/tools/calendar.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerCalendarTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerCalendarTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerClassroomTools } from '../../src/tools/classroom.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerClassroomTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerClassroomTools);
|
|
20
9
|
|
|
21
10
|
let handlers: Map<string, ToolHandler>;
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerContactsTools } from '../../src/tools/contacts.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerContactsTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerContactsTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
package/tests/tools/docs.test.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerDocsTools } from '../../src/tools/docs.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerDocsTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerDocsTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerDriveTools } from '../../src/tools/drive.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerDriveTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerDriveTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerGmailTools } from '../../src/tools/gmail.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerGmailTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerGmailTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerSheetsTools } from '../../src/tools/sheets.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerSheetsTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerSheetsTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerSlidesTools } from '../../src/tools/slides.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerSlidesTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerSlidesTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { registerTasksTools } from '../../src/tools/tasks.js';
|
|
4
3
|
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { setupHandlers as setupHandlersBase, type ToolHandler } from '../helpers/test-harness.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('../../src/runner.js');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setupHandlers(): Map<string, ToolHandler> {
|
|
11
|
-
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
12
|
-
const handlers = new Map<string, ToolHandler>();
|
|
13
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
14
|
-
handlers.set(name, cb as ToolHandler);
|
|
15
|
-
return undefined as never;
|
|
16
|
-
});
|
|
17
|
-
registerTasksTools(server);
|
|
18
|
-
return handlers;
|
|
19
|
-
}
|
|
8
|
+
const setupHandlers = () => setupHandlersBase(registerTasksTools);
|
|
20
9
|
|
|
21
10
|
beforeEach(() => vi.clearAllMocks());
|
|
22
11
|
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import * as runner from '../../src/runner.js';
|
|
3
|
-
import { runOrDiagnose } from '../../src/tools/utils.js';
|
|
3
|
+
import { runOrDiagnose, pushPaginationFlags } from '../../src/tools/utils.js';
|
|
4
4
|
|
|
5
5
|
vi.mock('../../src/runner.js');
|
|
6
6
|
|
|
7
7
|
beforeEach(() => vi.clearAllMocks());
|
|
8
8
|
|
|
9
|
+
describe('pushPaginationFlags', () => {
|
|
10
|
+
it('does nothing when no fields are provided', () => {
|
|
11
|
+
const args: string[] = ['cmd'];
|
|
12
|
+
pushPaginationFlags(args, {});
|
|
13
|
+
expect(args).toEqual(['cmd']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('pushes --max when a number is provided (including 0)', () => {
|
|
17
|
+
const args: string[] = ['cmd'];
|
|
18
|
+
pushPaginationFlags(args, { max: 0 });
|
|
19
|
+
expect(args).toEqual(['cmd', '--max=0']);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('pushes --page when a string is provided', () => {
|
|
23
|
+
const args: string[] = ['cmd'];
|
|
24
|
+
pushPaginationFlags(args, { page: 'tok' });
|
|
25
|
+
expect(args).toEqual(['cmd', '--page=tok']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('pushes --all only when true', () => {
|
|
29
|
+
const args: string[] = ['cmd'];
|
|
30
|
+
pushPaginationFlags(args, { all: false });
|
|
31
|
+
expect(args).toEqual(['cmd']);
|
|
32
|
+
pushPaginationFlags(args, { all: true });
|
|
33
|
+
expect(args).toEqual(['cmd', '--all']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('pushes all three in canonical order', () => {
|
|
37
|
+
const args: string[] = ['cmd'];
|
|
38
|
+
pushPaginationFlags(args, { max: 50, page: 'tok', all: true });
|
|
39
|
+
expect(args).toEqual(['cmd', '--max=50', '--page=tok', '--all']);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
9
43
|
describe('runOrDiagnose', () => {
|
|
10
44
|
it('returns output text on success', async () => {
|
|
11
45
|
vi.mocked(runner.run).mockResolvedValue('{"ok":true}');
|