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 { 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,15 +94,15 @@ 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 },
@@ -105,8 +112,8 @@ describe('gog_docs_write', () => {
105
112
  // gog 0.25.0
106
113
  it('appends to a persisted batch via --batch', async () => {
107
114
  vi.mocked(runner.run).mockResolvedValue('{}');
108
- const handlers = setupHandlers();
109
- await handlers.get('gog_docs_write')!({ docId: 'abc', text: 'T', batch: 'b1' });
115
+ const harness = await setupHandlers();
116
+ await harness.callTool('gog_docs_write', { docId: 'abc', text: 'T', batch: 'b1' });
110
117
  expect(runner.run).toHaveBeenCalledWith(
111
118
  ['docs', 'write', 'abc', '--text=T', '--batch=b1'],
112
119
  { account: undefined },
@@ -116,8 +123,8 @@ describe('gog_docs_write', () => {
116
123
  // gog 0.24.0
117
124
  it('adds --check-orphans when checkOrphans is true', async () => {
118
125
  vi.mocked(runner.run).mockResolvedValue('{}');
119
- const handlers = setupHandlers();
120
- 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 });
121
128
  expect(runner.run).toHaveBeenCalledWith(
122
129
  ['docs', 'write', 'abc', '--text=Rewrite', '--check-orphans'],
123
130
  { account: undefined },
@@ -126,15 +133,46 @@ describe('gog_docs_write', () => {
126
133
 
127
134
  it('omits --append flag when append is false', async () => {
128
135
  vi.mocked(runner.run).mockResolvedValue('{}');
129
- const handlers = setupHandlers();
130
- 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 });
131
138
  expect(runner.run).toHaveBeenCalledWith(['docs', 'write', 'abc', '--text=text'], { account: undefined });
132
139
  });
133
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
+
134
172
  it('returns error text on failure', async () => {
135
173
  vi.mocked(runner.run).mockRejectedValue(new Error('Write failed'));
136
- const handlers = setupHandlers();
137
- 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' });
138
176
  expect(result.content[0].text).toBe('Error: Write failed');
139
177
  });
140
178
  });
@@ -142,15 +180,15 @@ describe('gog_docs_write', () => {
142
180
  describe('gog_docs_find_replace', () => {
143
181
  it('calls run with find and replace args', async () => {
144
182
  vi.mocked(runner.run).mockResolvedValue('{"occurrencesChanged":2}');
145
- const handlers = setupHandlers();
146
- 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' });
147
185
  expect(runner.run).toHaveBeenCalledWith(['docs', 'find-replace', 'abc', 'foo', 'bar'], { account: undefined });
148
186
  });
149
187
 
150
188
  it('returns error text on failure', async () => {
151
189
  vi.mocked(runner.run).mockRejectedValue(new Error('Replace failed'));
152
- const handlers = setupHandlers();
153
- 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' });
154
192
  expect(result.content[0].text).toBe('Error: Replace failed');
155
193
  });
156
194
  });
@@ -158,16 +196,16 @@ describe('gog_docs_find_replace', () => {
158
196
  describe('gog_docs_structure', () => {
159
197
  it('calls run with correct args', async () => {
160
198
  vi.mocked(runner.run).mockResolvedValue('[1] Heading\n[2] Paragraph');
161
- const handlers = setupHandlers();
162
- 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' });
163
201
  expect(runner.run).toHaveBeenCalledWith(['docs', 'structure', 'abc'], { account: undefined });
164
202
  expect(result.content[0].text).toContain('Heading');
165
203
  });
166
204
 
167
205
  it('returns error text on failure', async () => {
168
206
  vi.mocked(runner.run).mockRejectedValue(new Error('Structure failed'));
169
- const handlers = setupHandlers();
170
- 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' });
171
209
  expect(result.content[0].text).toBe('Error: Structure failed');
172
210
  });
173
211
  });
@@ -175,22 +213,22 @@ describe('gog_docs_structure', () => {
175
213
  describe('gog_docs_run', () => {
176
214
  it('passes raw subcommand and args to runner', async () => {
177
215
  vi.mocked(runner.run).mockResolvedValue('{}');
178
- const handlers = setupHandlers();
179
- 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'] });
180
218
  expect(runner.run).toHaveBeenCalledWith(['docs', 'copy', 'abc', 'My Copy'], { account: undefined });
181
219
  });
182
220
 
183
221
  it('works with empty args array', async () => {
184
222
  vi.mocked(runner.run).mockResolvedValue('{}');
185
- const handlers = setupHandlers();
186
- await handlers.get('gog_docs_run')!({ subcommand: 'clear', args: [] });
223
+ const harness = await setupHandlers();
224
+ await harness.callTool('gog_docs_run', { subcommand: 'clear', args: [] });
187
225
  expect(runner.run).toHaveBeenCalledWith(['docs', 'clear'], { account: undefined });
188
226
  });
189
227
 
190
228
  it('returns error text on failure', async () => {
191
229
  vi.mocked(runner.run).mockRejectedValue(new Error('Run failed'));
192
- const handlers = setupHandlers();
193
- 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: [] });
194
232
  expect(result.content[0].text).toBe('Error: Run failed');
195
233
  });
196
234
  });