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.
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 +20220 -19700
  4. package/dist/lib.js +15847 -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 +30 -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 +93 -44
  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 { registerContactsTools } from '../../src/tools/contacts.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(registerContactsTools);
8
+ const setupHandlers = () => createTestHarness(registerContactsTools);
9
9
 
10
10
  beforeEach(() => vi.clearAllMocks());
11
11
 
12
12
  describe('gog_contacts_search', () => {
13
13
  it('calls run with query', async () => {
14
14
  vi.mocked(runner.run).mockResolvedValue('{"connections":[]}');
15
- const handlers = setupHandlers();
16
- await handlers.get('gog_contacts_search')!({ query: 'alice' });
15
+ const harness = await setupHandlers();
16
+ await harness.callTool('gog_contacts_search', { query: 'alice' });
17
17
  expect(runner.run).toHaveBeenCalledWith(['contacts', 'search', 'alice'], { account: undefined });
18
18
  });
19
19
 
20
20
  it('returns error text on failure', async () => {
21
21
  vi.mocked(runner.run).mockRejectedValue(new Error('Search failed'));
22
- const handlers = setupHandlers();
23
- const result = await handlers.get('gog_contacts_search')!({ query: 'x' });
22
+ const harness = await setupHandlers();
23
+ const result = await harness.callTool('gog_contacts_search', { query: 'x' });
24
24
  expect(result.content[0].text).toBe('Error: Search failed');
25
25
  });
26
26
  });
@@ -28,15 +28,15 @@ describe('gog_contacts_search', () => {
28
28
  describe('gog_contacts_list', () => {
29
29
  it('calls run with contacts list', async () => {
30
30
  vi.mocked(runner.run).mockResolvedValue('{"connections":[]}');
31
- const handlers = setupHandlers();
32
- await handlers.get('gog_contacts_list')!({});
31
+ const harness = await setupHandlers();
32
+ await harness.callTool('gog_contacts_list', {});
33
33
  expect(runner.run).toHaveBeenCalledWith(['contacts', 'list'], { account: undefined });
34
34
  });
35
35
 
36
36
  it('returns error text on failure', async () => {
37
37
  vi.mocked(runner.run).mockRejectedValue(new Error('List failed'));
38
- const handlers = setupHandlers();
39
- const result = await handlers.get('gog_contacts_list')!({});
38
+ const harness = await setupHandlers();
39
+ const result = await harness.callTool('gog_contacts_list', {});
40
40
  expect(result.content[0].text).toBe('Error: List failed');
41
41
  });
42
42
  });
@@ -44,15 +44,15 @@ describe('gog_contacts_list', () => {
44
44
  describe('gog_contacts_get', () => {
45
45
  it('calls run with resourceName', async () => {
46
46
  vi.mocked(runner.run).mockResolvedValue('{"resourceName":"people/c123"}');
47
- const handlers = setupHandlers();
48
- await handlers.get('gog_contacts_get')!({ resourceName: 'people/c123' });
47
+ const harness = await setupHandlers();
48
+ await harness.callTool('gog_contacts_get', { resourceName: 'people/c123' });
49
49
  expect(runner.run).toHaveBeenCalledWith(['contacts', 'get', 'people/c123'], { account: undefined });
50
50
  });
51
51
 
52
52
  it('returns error text on failure', async () => {
53
53
  vi.mocked(runner.run).mockRejectedValue(new Error('Not found'));
54
- const handlers = setupHandlers();
55
- const result = await handlers.get('gog_contacts_get')!({ resourceName: 'bad' });
54
+ const harness = await setupHandlers();
55
+ const result = await harness.callTool('gog_contacts_get', { resourceName: 'bad' });
56
56
  expect(result.content[0].text).toBe('Error: Not found');
57
57
  });
58
58
  });
@@ -60,15 +60,15 @@ describe('gog_contacts_get', () => {
60
60
  describe('gog_contacts_create', () => {
61
61
  it('calls run with required givenName', async () => {
62
62
  vi.mocked(runner.run).mockResolvedValue('{"resourceName":"people/c456"}');
63
- const handlers = setupHandlers();
64
- await handlers.get('gog_contacts_create')!({ givenName: 'Alice' });
63
+ const harness = await setupHandlers();
64
+ await harness.callTool('gog_contacts_create', { givenName: 'Alice' });
65
65
  expect(runner.run).toHaveBeenCalledWith(['contacts', 'create', '--given=Alice'], { account: undefined });
66
66
  });
67
67
 
68
68
  it('appends optional fields when provided', async () => {
69
69
  vi.mocked(runner.run).mockResolvedValue('{}');
70
- const handlers = setupHandlers();
71
- await handlers.get('gog_contacts_create')!({
70
+ const harness = await setupHandlers();
71
+ await harness.callTool('gog_contacts_create', {
72
72
  givenName: 'Alice',
73
73
  familyName: 'Smith',
74
74
  email: 'alice@example.com',
@@ -88,8 +88,8 @@ describe('gog_contacts_create', () => {
88
88
 
89
89
  it('returns error text on failure', async () => {
90
90
  vi.mocked(runner.run).mockRejectedValue(new Error('Create failed'));
91
- const handlers = setupHandlers();
92
- const result = await handlers.get('gog_contacts_create')!({ givenName: 'Bad' });
91
+ const harness = await setupHandlers();
92
+ const result = await harness.callTool('gog_contacts_create', { givenName: 'Bad' });
93
93
  expect(result.content[0].text).toBe('Error: Create failed');
94
94
  });
95
95
  });
@@ -97,15 +97,15 @@ describe('gog_contacts_create', () => {
97
97
  describe('gog_contacts_run', () => {
98
98
  it('passes subcommand and args to runner', async () => {
99
99
  vi.mocked(runner.run).mockResolvedValue('{}');
100
- const handlers = setupHandlers();
101
- await handlers.get('gog_contacts_run')!({ subcommand: 'delete', args: ['people/c123'] });
100
+ const harness = await setupHandlers();
101
+ await harness.callTool('gog_contacts_run', { subcommand: 'delete', args: ['people/c123'] });
102
102
  expect(runner.run).toHaveBeenCalledWith(['contacts', 'delete', 'people/c123'], { account: undefined });
103
103
  });
104
104
 
105
105
  it('returns error text on failure', async () => {
106
106
  vi.mocked(runner.run).mockRejectedValue(new Error('Run failed'));
107
- const handlers = setupHandlers();
108
- const result = await handlers.get('gog_contacts_run')!({ subcommand: 'update', args: [] });
107
+ const harness = await setupHandlers();
108
+ const result = await harness.callTool('gog_contacts_run', { subcommand: 'update', args: [] });
109
109
  expect(result.content[0].text).toBe('Error: Run failed');
110
110
  });
111
111
  });
@@ -1,19 +1,19 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { registerDocsTools } from '../../src/tools/docs.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(registerDocsTools);
8
+ const setupHandlers = () => createTestHarness(registerDocsTools);
9
9
 
10
10
  beforeEach(() => vi.clearAllMocks());
11
11
 
12
12
  describe('gog_docs_info', () => {
13
13
  it('calls run with correct args', async () => {
14
14
  vi.mocked(runner.run).mockResolvedValue('{"title":"My Doc","docId":"abc"}');
15
- const handlers = setupHandlers();
16
- const result = await handlers.get('gog_docs_info')!({ docId: 'abc' });
15
+ const harness = await setupHandlers();
16
+ const result = await harness.callTool('gog_docs_info', { docId: 'abc' });
17
17
  expect(runner.run).toHaveBeenCalledWith(['docs', 'info', 'abc'], { account: undefined });
18
18
  expect(result.content[0].text).toContain('My Doc');
19
19
  });
@@ -22,15 +22,15 @@ describe('gog_docs_info', () => {
22
22
  vi.mocked(runner.run)
23
23
  .mockRejectedValueOnce(new Error('Doc not found'))
24
24
  .mockResolvedValueOnce('user@gmail.com');
25
- const handlers = setupHandlers();
26
- const result = await handlers.get('gog_docs_info')!({ docId: 'bad' });
25
+ const harness = await setupHandlers();
26
+ const result = await harness.callTool('gog_docs_info', { docId: 'bad' });
27
27
  expect(result.content[0].text).toBe('Error: Doc not found\n\nConfigured accounts:\nuser@gmail.com');
28
28
  });
29
29
 
30
30
  it('returns plain error text when auth list also fails', async () => {
31
31
  vi.mocked(runner.run).mockRejectedValue(new Error('Doc not found'));
32
- const handlers = setupHandlers();
33
- const result = await handlers.get('gog_docs_info')!({ docId: 'bad' });
32
+ const harness = await setupHandlers();
33
+ const result = await harness.callTool('gog_docs_info', { docId: 'bad' });
34
34
  expect(result.content[0].text).toBe('Error: Doc not found');
35
35
  });
36
36
 
@@ -38,8 +38,8 @@ describe('gog_docs_info', () => {
38
38
  vi.mocked(runner.run)
39
39
  .mockRejectedValueOnce('raw error string')
40
40
  .mockRejectedValueOnce(new Error('auth list failed'));
41
- const handlers = setupHandlers();
42
- const result = await handlers.get('gog_docs_info')!({ docId: 'bad' });
41
+ const harness = await setupHandlers();
42
+ const result = await harness.callTool('gog_docs_info', { docId: 'bad' });
43
43
  expect(result.content[0].text).toBe('raw error string');
44
44
  });
45
45
  });
@@ -47,23 +47,30 @@ describe('gog_docs_info', () => {
47
47
  describe('gog_docs_cat', () => {
48
48
  it('calls run with correct args', async () => {
49
49
  vi.mocked(runner.run).mockResolvedValue('Hello world');
50
- const handlers = setupHandlers();
51
- const result = await handlers.get('gog_docs_cat')!({ docId: 'abc' });
50
+ const harness = await setupHandlers();
51
+ const result = await harness.callTool('gog_docs_cat', { docId: 'abc' });
52
52
  expect(runner.run).toHaveBeenCalledWith(['docs', 'cat', 'abc'], { account: undefined });
53
53
  expect(result.content[0].text).toBe('Hello world');
54
54
  });
55
55
 
56
56
  it('forwards account override', async () => {
57
57
  vi.mocked(runner.run).mockResolvedValue('text');
58
- const handlers = setupHandlers();
59
- await handlers.get('gog_docs_cat')!({ docId: 'abc', account: 'other@gmail.com' });
58
+ const harness = await setupHandlers();
59
+ await harness.callTool('gog_docs_cat', { docId: 'abc', account: 'other@gmail.com' });
60
60
  expect(runner.run).toHaveBeenCalledWith(['docs', 'cat', 'abc'], { account: 'other@gmail.com' });
61
61
  });
62
62
 
63
+ it('appends --chips when requested', async () => {
64
+ vi.mocked(runner.run).mockResolvedValue('Hello @person');
65
+ const harness = await setupHandlers();
66
+ await harness.callTool('gog_docs_cat', { docId: 'abc', chips: true });
67
+ expect(runner.run).toHaveBeenCalledWith(['docs', 'cat', 'abc', '--chips'], { account: undefined });
68
+ });
69
+
63
70
  it('returns error text on failure', async () => {
64
71
  vi.mocked(runner.run).mockRejectedValue(new Error('Not found'));
65
- const handlers = setupHandlers();
66
- const result = await handlers.get('gog_docs_cat')!({ docId: 'bad' });
72
+ const harness = await setupHandlers();
73
+ const result = await harness.callTool('gog_docs_cat', { docId: 'bad' });
67
74
  expect(result.content[0].text).toBe('Error: Not found');
68
75
  });
69
76
  });
@@ -71,15 +78,15 @@ describe('gog_docs_cat', () => {
71
78
  describe('gog_docs_create', () => {
72
79
  it('calls run with title', async () => {
73
80
  vi.mocked(runner.run).mockResolvedValue('{"docId":"newid","title":"Meeting Notes"}');
74
- const handlers = setupHandlers();
75
- await handlers.get('gog_docs_create')!({ title: 'Meeting Notes' });
81
+ const harness = await setupHandlers();
82
+ await harness.callTool('gog_docs_create', { title: 'Meeting Notes' });
76
83
  expect(runner.run).toHaveBeenCalledWith(['docs', 'create', 'Meeting Notes'], { account: undefined });
77
84
  });
78
85
 
79
86
  it('returns error text on failure', async () => {
80
87
  vi.mocked(runner.run).mockRejectedValue(new Error('Create failed'));
81
- const handlers = setupHandlers();
82
- const result = await handlers.get('gog_docs_create')!({ title: 'Bad' });
88
+ const harness = await setupHandlers();
89
+ const result = await harness.callTool('gog_docs_create', { title: 'Bad' });
83
90
  expect(result.content[0].text).toBe('Error: Create failed');
84
91
  });
85
92
  });
@@ -87,26 +94,37 @@ describe('gog_docs_create', () => {
87
94
  describe('gog_docs_write', () => {
88
95
  it('calls run with text flag', async () => {
89
96
  vi.mocked(runner.run).mockResolvedValue('{}');
90
- const handlers = setupHandlers();
91
- await handlers.get('gog_docs_write')!({ docId: 'abc', text: 'Hello world' });
97
+ const harness = await setupHandlers();
98
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'Hello world' });
92
99
  expect(runner.run).toHaveBeenCalledWith(['docs', 'write', 'abc', '--text=Hello world'], { account: undefined });
93
100
  });
94
101
 
95
102
  it('adds --append flag when append is true', async () => {
96
103
  vi.mocked(runner.run).mockResolvedValue('{}');
97
- const handlers = setupHandlers();
98
- await handlers.get('gog_docs_write')!({ docId: 'abc', text: 'More text', append: true });
104
+ const harness = await setupHandlers();
105
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'More text', append: true });
99
106
  expect(runner.run).toHaveBeenCalledWith(
100
107
  ['docs', 'write', 'abc', '--text=More text', '--append'],
101
108
  { account: undefined },
102
109
  );
103
110
  });
104
111
 
112
+ // gog 0.25.0
113
+ it('appends to a persisted batch via --batch', async () => {
114
+ vi.mocked(runner.run).mockResolvedValue('{}');
115
+ const harness = await setupHandlers();
116
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'T', batch: 'b1' });
117
+ expect(runner.run).toHaveBeenCalledWith(
118
+ ['docs', 'write', 'abc', '--text=T', '--batch=b1'],
119
+ { account: undefined },
120
+ );
121
+ });
122
+
105
123
  // gog 0.24.0
106
124
  it('adds --check-orphans when checkOrphans is true', async () => {
107
125
  vi.mocked(runner.run).mockResolvedValue('{}');
108
- const handlers = setupHandlers();
109
- await handlers.get('gog_docs_write')!({ docId: 'abc', text: 'Rewrite', checkOrphans: true });
126
+ const harness = await setupHandlers();
127
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'Rewrite', checkOrphans: true });
110
128
  expect(runner.run).toHaveBeenCalledWith(
111
129
  ['docs', 'write', 'abc', '--text=Rewrite', '--check-orphans'],
112
130
  { account: undefined },
@@ -115,15 +133,46 @@ describe('gog_docs_write', () => {
115
133
 
116
134
  it('omits --append flag when append is false', async () => {
117
135
  vi.mocked(runner.run).mockResolvedValue('{}');
118
- const handlers = setupHandlers();
119
- await handlers.get('gog_docs_write')!({ docId: 'abc', text: 'text', append: false });
136
+ const harness = await setupHandlers();
137
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'text', append: false });
120
138
  expect(runner.run).toHaveBeenCalledWith(['docs', 'write', 'abc', '--text=text'], { account: undefined });
121
139
  });
122
140
 
141
+ // gog 0.30.0 paragraph list / indentation / spacing / keep controls
142
+ it('adds bullets, numbering, indentation, spacing and keep flags (keep=true)', async () => {
143
+ vi.mocked(runner.run).mockResolvedValue('{}');
144
+ const harness = await setupHandlers();
145
+ await harness.callTool('gog_docs_write', {
146
+ docId: 'abc', text: 'List', bullets: true, bulletPreset: 'BULLET_DISC_CIRCLE_SQUARE',
147
+ indentStart: 18, indentEnd: 6, indentFirstLine: 36, spaceAbove: 4, spaceBelow: 8,
148
+ keepLinesTogether: true, keepWithNext: true,
149
+ });
150
+ expect(runner.run).toHaveBeenCalledWith(
151
+ ['docs', 'write', 'abc', '--text=List', '--bullets', '--bullet-preset=BULLET_DISC_CIRCLE_SQUARE',
152
+ '--indent-start=18', '--indent-end=6', '--indent-first-line=36', '--space-above=4', '--space-below=8',
153
+ '--keep-lines-together', '--keep-with-next'],
154
+ { account: undefined },
155
+ );
156
+ });
157
+
158
+ it('uses numbered list and negated keep flags (keep=false)', async () => {
159
+ vi.mocked(runner.run).mockResolvedValue('{}');
160
+ const harness = await setupHandlers();
161
+ await harness.callTool('gog_docs_write', {
162
+ docId: 'abc', text: 'Steps', ordered: true, noBullets: true,
163
+ keepLinesTogether: false, keepWithNext: false,
164
+ });
165
+ expect(runner.run).toHaveBeenCalledWith(
166
+ ['docs', 'write', 'abc', '--text=Steps', '--ordered', '--no-bullets',
167
+ '--no-keep-lines-together', '--no-keep-with-next'],
168
+ { account: undefined },
169
+ );
170
+ });
171
+
123
172
  it('returns error text on failure', async () => {
124
173
  vi.mocked(runner.run).mockRejectedValue(new Error('Write failed'));
125
- const handlers = setupHandlers();
126
- const result = await handlers.get('gog_docs_write')!({ docId: 'bad', text: 'x' });
174
+ const harness = await setupHandlers();
175
+ const result = await harness.callTool('gog_docs_write', { docId: 'bad', text: 'x' });
127
176
  expect(result.content[0].text).toBe('Error: Write failed');
128
177
  });
129
178
  });
@@ -131,15 +180,15 @@ describe('gog_docs_write', () => {
131
180
  describe('gog_docs_find_replace', () => {
132
181
  it('calls run with find and replace args', async () => {
133
182
  vi.mocked(runner.run).mockResolvedValue('{"occurrencesChanged":2}');
134
- const handlers = setupHandlers();
135
- await handlers.get('gog_docs_find_replace')!({ docId: 'abc', find: 'foo', replace: 'bar' });
183
+ const harness = await setupHandlers();
184
+ await harness.callTool('gog_docs_find_replace', { docId: 'abc', find: 'foo', replace: 'bar' });
136
185
  expect(runner.run).toHaveBeenCalledWith(['docs', 'find-replace', 'abc', 'foo', 'bar'], { account: undefined });
137
186
  });
138
187
 
139
188
  it('returns error text on failure', async () => {
140
189
  vi.mocked(runner.run).mockRejectedValue(new Error('Replace failed'));
141
- const handlers = setupHandlers();
142
- const result = await handlers.get('gog_docs_find_replace')!({ docId: 'bad', find: 'x', replace: 'y' });
190
+ const harness = await setupHandlers();
191
+ const result = await harness.callTool('gog_docs_find_replace', { docId: 'bad', find: 'x', replace: 'y' });
143
192
  expect(result.content[0].text).toBe('Error: Replace failed');
144
193
  });
145
194
  });
@@ -147,16 +196,16 @@ describe('gog_docs_find_replace', () => {
147
196
  describe('gog_docs_structure', () => {
148
197
  it('calls run with correct args', async () => {
149
198
  vi.mocked(runner.run).mockResolvedValue('[1] Heading\n[2] Paragraph');
150
- const handlers = setupHandlers();
151
- const result = await handlers.get('gog_docs_structure')!({ docId: 'abc' });
199
+ const harness = await setupHandlers();
200
+ const result = await harness.callTool('gog_docs_structure', { docId: 'abc' });
152
201
  expect(runner.run).toHaveBeenCalledWith(['docs', 'structure', 'abc'], { account: undefined });
153
202
  expect(result.content[0].text).toContain('Heading');
154
203
  });
155
204
 
156
205
  it('returns error text on failure', async () => {
157
206
  vi.mocked(runner.run).mockRejectedValue(new Error('Structure failed'));
158
- const handlers = setupHandlers();
159
- const result = await handlers.get('gog_docs_structure')!({ docId: 'bad' });
207
+ const harness = await setupHandlers();
208
+ const result = await harness.callTool('gog_docs_structure', { docId: 'bad' });
160
209
  expect(result.content[0].text).toBe('Error: Structure failed');
161
210
  });
162
211
  });
@@ -164,22 +213,22 @@ describe('gog_docs_structure', () => {
164
213
  describe('gog_docs_run', () => {
165
214
  it('passes raw subcommand and args to runner', async () => {
166
215
  vi.mocked(runner.run).mockResolvedValue('{}');
167
- const handlers = setupHandlers();
168
- await handlers.get('gog_docs_run')!({ subcommand: 'copy', args: ['abc', 'My Copy'] });
216
+ const harness = await setupHandlers();
217
+ await harness.callTool('gog_docs_run', { subcommand: 'copy', args: ['abc', 'My Copy'] });
169
218
  expect(runner.run).toHaveBeenCalledWith(['docs', 'copy', 'abc', 'My Copy'], { account: undefined });
170
219
  });
171
220
 
172
221
  it('works with empty args array', async () => {
173
222
  vi.mocked(runner.run).mockResolvedValue('{}');
174
- const handlers = setupHandlers();
175
- await handlers.get('gog_docs_run')!({ subcommand: 'clear', args: [] });
223
+ const harness = await setupHandlers();
224
+ await harness.callTool('gog_docs_run', { subcommand: 'clear', args: [] });
176
225
  expect(runner.run).toHaveBeenCalledWith(['docs', 'clear'], { account: undefined });
177
226
  });
178
227
 
179
228
  it('returns error text on failure', async () => {
180
229
  vi.mocked(runner.run).mockRejectedValue(new Error('Run failed'));
181
- const handlers = setupHandlers();
182
- const result = await handlers.get('gog_docs_run')!({ subcommand: 'clear', args: [] });
230
+ const harness = await setupHandlers();
231
+ const result = await harness.callTool('gog_docs_run', { subcommand: 'clear', args: [] });
183
232
  expect(result.content[0].text).toBe('Error: Run failed');
184
233
  });
185
234
  });