gogcli-mcp 1.0.5 → 1.0.7
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/README.md +23 -125
- package/dist/index.js +5332 -5395
- package/dist/lib.js +30827 -0
- package/manifest.json +1 -1
- package/package.json +6 -2
- package/src/index.ts +2 -23
- package/src/lib.ts +5 -0
- package/src/server.ts +31 -0
- package/src/tools/docs.ts +0 -83
- package/tests/tools/docs.test.ts +0 -187
- package/tsconfig.json +2 -8
- package/.github/workflows/ci.yml +0 -22
- package/.github/workflows/release.yml +0 -76
- package/.github/workflows/tag-and-bump.yml +0 -67
- package/.mcp.json +0 -13
- package/CLAUDE.md +0 -61
- package/docs/superpowers/plans/2026-04-12-gogcli-mcp.md +0 -758
- package/docs/superpowers/plans/2026-04-13-browser-auth.md +0 -450
- package/docs/superpowers/specs/2026-04-12-gogcli-mcp-design.md +0 -102
- package/docs/superpowers/specs/2026-04-13-browser-auth-design.md +0 -88
- package/gogcli-mcp-1.0.5.skill +0 -0
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp",
|
|
5
5
|
"display_name": "gogcli",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.7",
|
|
7
7
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "MCP server wrapping gogcli for Google service access",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,9 +10,13 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"gogcli-mcp": "dist/index.js"
|
|
12
12
|
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./dist/index.js",
|
|
15
|
+
"./lib": "./dist/lib.js"
|
|
16
|
+
},
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "tsc --noEmit && npm run bundle",
|
|
15
|
-
"bundle": "
|
|
19
|
+
"bundle": "node ../../scripts/bundle.js src/index.ts dist/index.js && node ../../scripts/bundle.js src/lib.ts dist/lib.js",
|
|
16
20
|
"typecheck": "tsc --noEmit",
|
|
17
21
|
"test": "vitest run",
|
|
18
22
|
"test:watch": "vitest",
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import {
|
|
5
|
-
import { registerCalendarTools } from './tools/calendar.js';
|
|
6
|
-
import { registerContactsTools } from './tools/contacts.js';
|
|
7
|
-
import { registerDocsTools } from './tools/docs.js';
|
|
8
|
-
import { registerDriveTools } from './tools/drive.js';
|
|
9
|
-
import { registerGmailTools } from './tools/gmail.js';
|
|
10
|
-
import { registerSheetsTools } from './tools/sheets.js';
|
|
11
|
-
import { registerTasksTools } from './tools/tasks.js';
|
|
12
|
-
|
|
13
|
-
const server = new McpServer({ name: 'gogcli', version: '1.0.5' });
|
|
14
|
-
|
|
15
|
-
registerAuthTools(server);
|
|
16
|
-
registerCalendarTools(server);
|
|
17
|
-
registerContactsTools(server);
|
|
18
|
-
registerDocsTools(server);
|
|
19
|
-
registerDriveTools(server);
|
|
20
|
-
registerGmailTools(server);
|
|
21
|
-
registerSheetsTools(server);
|
|
22
|
-
registerTasksTools(server);
|
|
23
|
-
|
|
24
|
-
// To add more services: import registerXxxTools and call them here.
|
|
25
|
-
// Example: registerGmailTools(server);
|
|
3
|
+
import { createBaseServer } from './server.js';
|
|
26
4
|
|
|
5
|
+
const server = createBaseServer();
|
|
27
6
|
const transport = new StdioServerTransport();
|
|
28
7
|
await server.connect(transport);
|
package/src/lib.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBaseServer, VERSION } from './server.js';
|
|
2
|
+
export { run } from './runner.js';
|
|
3
|
+
export type { RunOptions, Spawner } from './runner.js';
|
|
4
|
+
export { accountParam, runOrDiagnose, toText, toError } from './tools/utils.js';
|
|
5
|
+
export type { ToolResult } from './tools/utils.js';
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerAuthTools } from './tools/auth.js';
|
|
3
|
+
import { registerCalendarTools } from './tools/calendar.js';
|
|
4
|
+
import { registerContactsTools } from './tools/contacts.js';
|
|
5
|
+
import { registerDocsTools } from './tools/docs.js';
|
|
6
|
+
import { registerDriveTools } from './tools/drive.js';
|
|
7
|
+
import { registerGmailTools } from './tools/gmail.js';
|
|
8
|
+
import { registerSheetsTools } from './tools/sheets.js';
|
|
9
|
+
import { registerTasksTools } from './tools/tasks.js';
|
|
10
|
+
|
|
11
|
+
// Injected at build time by esbuild --define:GOGCLI_VERSION=...
|
|
12
|
+
declare const GOGCLI_VERSION: string;
|
|
13
|
+
export const VERSION = typeof GOGCLI_VERSION !== 'undefined' ? GOGCLI_VERSION : '0.0.0';
|
|
14
|
+
|
|
15
|
+
export function createBaseServer(options?: { name?: string; version?: string }): McpServer {
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: options?.name ?? 'gogcli',
|
|
18
|
+
version: options?.version ?? VERSION,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerAuthTools(server);
|
|
22
|
+
registerCalendarTools(server);
|
|
23
|
+
registerContactsTools(server);
|
|
24
|
+
registerDocsTools(server);
|
|
25
|
+
registerDriveTools(server);
|
|
26
|
+
registerGmailTools(server);
|
|
27
|
+
registerSheetsTools(server);
|
|
28
|
+
registerTasksTools(server);
|
|
29
|
+
|
|
30
|
+
return server;
|
|
31
|
+
}
|
package/src/tools/docs.ts
CHANGED
|
@@ -74,89 +74,6 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
74
74
|
return runOrDiagnose(['docs', 'structure', docId], { account });
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
// --- Comments tools ---
|
|
78
|
-
|
|
79
|
-
server.registerTool('gog_docs_comments_list', {
|
|
80
|
-
description:
|
|
81
|
-
'List comments on a Google Doc. Returns open comments by default; set includeResolved=true to include resolved comments.',
|
|
82
|
-
annotations: { readOnlyHint: true },
|
|
83
|
-
inputSchema: {
|
|
84
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
85
|
-
includeResolved: z.boolean().optional().describe('Include resolved comments (default: false, open only)'),
|
|
86
|
-
account: accountParam,
|
|
87
|
-
},
|
|
88
|
-
}, async ({ docId, includeResolved, account }) => {
|
|
89
|
-
const args = ['docs', 'comments', 'list', docId];
|
|
90
|
-
if (includeResolved) args.push('--include-resolved');
|
|
91
|
-
return runOrDiagnose(args, { account });
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
server.registerTool('gog_docs_comments_get', {
|
|
95
|
-
description: 'Get a single comment by ID, including its replies.',
|
|
96
|
-
annotations: { readOnlyHint: true },
|
|
97
|
-
inputSchema: {
|
|
98
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
99
|
-
commentId: z.string().describe('Comment ID'),
|
|
100
|
-
account: accountParam,
|
|
101
|
-
},
|
|
102
|
-
}, async ({ docId, commentId, account }) => {
|
|
103
|
-
return runOrDiagnose(['docs', 'comments', 'get', docId, commentId], { account });
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
server.registerTool('gog_docs_comments_add', {
|
|
107
|
-
description:
|
|
108
|
-
'Add a comment to a Google Doc. Optionally attach quoted text that appears as the highlighted passage in the Google Docs UI.',
|
|
109
|
-
inputSchema: {
|
|
110
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
111
|
-
content: z.string().describe('Comment text'),
|
|
112
|
-
quoted: z.string().optional().describe('Quoted text to attach to the comment (shown in UIs when available)'),
|
|
113
|
-
account: accountParam,
|
|
114
|
-
},
|
|
115
|
-
}, async ({ docId, content, quoted, account }) => {
|
|
116
|
-
const args = ['docs', 'comments', 'add', docId, content];
|
|
117
|
-
if (quoted) args.push(`--quoted=${quoted}`);
|
|
118
|
-
return runOrDiagnose(args, { account });
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
server.registerTool('gog_docs_comments_reply', {
|
|
122
|
-
description: 'Reply to an existing comment on a Google Doc.',
|
|
123
|
-
inputSchema: {
|
|
124
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
125
|
-
commentId: z.string().describe('Comment ID to reply to'),
|
|
126
|
-
content: z.string().describe('Reply text'),
|
|
127
|
-
account: accountParam,
|
|
128
|
-
},
|
|
129
|
-
}, async ({ docId, commentId, content, account }) => {
|
|
130
|
-
return runOrDiagnose(['docs', 'comments', 'reply', docId, commentId, content], { account });
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
server.registerTool('gog_docs_comments_resolve', {
|
|
134
|
-
description: 'Resolve a comment (mark as done). Optionally include a closing message.',
|
|
135
|
-
annotations: { destructiveHint: true },
|
|
136
|
-
inputSchema: {
|
|
137
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
138
|
-
commentId: z.string().describe('Comment ID to resolve'),
|
|
139
|
-
message: z.string().optional().describe('Optional message to include when resolving'),
|
|
140
|
-
account: accountParam,
|
|
141
|
-
},
|
|
142
|
-
}, async ({ docId, commentId, message, account }) => {
|
|
143
|
-
const args = ['docs', 'comments', 'resolve', docId, commentId];
|
|
144
|
-
if (message) args.push(`--message=${message}`);
|
|
145
|
-
return runOrDiagnose(args, { account });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
server.registerTool('gog_docs_comments_delete', {
|
|
149
|
-
description: 'Delete a comment from a Google Doc. This action is permanent.',
|
|
150
|
-
annotations: { destructiveHint: true },
|
|
151
|
-
inputSchema: {
|
|
152
|
-
docId: z.string().describe('Doc ID (from the URL)'),
|
|
153
|
-
commentId: z.string().describe('Comment ID to delete'),
|
|
154
|
-
account: accountParam,
|
|
155
|
-
},
|
|
156
|
-
}, async ({ docId, commentId, account }) => {
|
|
157
|
-
return runOrDiagnose(['docs', 'comments', 'delete', docId, commentId], { account });
|
|
158
|
-
});
|
|
159
|
-
|
|
160
77
|
server.registerTool('gog_docs_run', {
|
|
161
78
|
description: 'Run any gog docs subcommand not covered by the other tools. Run `gog docs --help` for the full list of subcommands, or `gog docs <subcommand> --help` for flags on a specific subcommand.',
|
|
162
79
|
annotations: { destructiveHint: true },
|
package/tests/tools/docs.test.ts
CHANGED
|
@@ -161,193 +161,6 @@ describe('gog_docs_structure', () => {
|
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
// --- Comments tools ---
|
|
165
|
-
|
|
166
|
-
describe('gog_docs_comments_list', () => {
|
|
167
|
-
it('calls run with correct args for open comments', async () => {
|
|
168
|
-
vi.mocked(runner.run).mockResolvedValue('[{"id":"c1","content":"Fix this"}]');
|
|
169
|
-
const handlers = setupHandlers();
|
|
170
|
-
const result = await handlers.get('gog_docs_comments_list')!({ docId: 'abc' });
|
|
171
|
-
expect(runner.run).toHaveBeenCalledWith(['docs', 'comments', 'list', 'abc'], { account: undefined });
|
|
172
|
-
expect(result.content[0].text).toContain('Fix this');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('includes --include-resolved when set', async () => {
|
|
176
|
-
vi.mocked(runner.run).mockResolvedValue('[]');
|
|
177
|
-
const handlers = setupHandlers();
|
|
178
|
-
await handlers.get('gog_docs_comments_list')!({ docId: 'abc', includeResolved: true });
|
|
179
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
180
|
-
['docs', 'comments', 'list', 'abc', '--include-resolved'],
|
|
181
|
-
{ account: undefined },
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('omits --include-resolved when false', async () => {
|
|
186
|
-
vi.mocked(runner.run).mockResolvedValue('[]');
|
|
187
|
-
const handlers = setupHandlers();
|
|
188
|
-
await handlers.get('gog_docs_comments_list')!({ docId: 'abc', includeResolved: false });
|
|
189
|
-
expect(runner.run).toHaveBeenCalledWith(['docs', 'comments', 'list', 'abc'], { account: undefined });
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('forwards account override', async () => {
|
|
193
|
-
vi.mocked(runner.run).mockResolvedValue('[]');
|
|
194
|
-
const handlers = setupHandlers();
|
|
195
|
-
await handlers.get('gog_docs_comments_list')!({ docId: 'abc', account: 'other@gmail.com' });
|
|
196
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
197
|
-
['docs', 'comments', 'list', 'abc'],
|
|
198
|
-
{ account: 'other@gmail.com' },
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('returns error text on failure', async () => {
|
|
203
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('List failed'));
|
|
204
|
-
const handlers = setupHandlers();
|
|
205
|
-
const result = await handlers.get('gog_docs_comments_list')!({ docId: 'bad' });
|
|
206
|
-
expect(result.content[0].text).toContain('Error: List failed');
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe('gog_docs_comments_get', () => {
|
|
211
|
-
it('calls run with correct args', async () => {
|
|
212
|
-
vi.mocked(runner.run).mockResolvedValue('{"id":"c1","content":"Fix this","replies":[]}');
|
|
213
|
-
const handlers = setupHandlers();
|
|
214
|
-
const result = await handlers.get('gog_docs_comments_get')!({ docId: 'abc', commentId: 'c1' });
|
|
215
|
-
expect(runner.run).toHaveBeenCalledWith(['docs', 'comments', 'get', 'abc', 'c1'], { account: undefined });
|
|
216
|
-
expect(result.content[0].text).toContain('Fix this');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('returns error text on failure', async () => {
|
|
220
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('Not found'));
|
|
221
|
-
const handlers = setupHandlers();
|
|
222
|
-
const result = await handlers.get('gog_docs_comments_get')!({ docId: 'abc', commentId: 'bad' });
|
|
223
|
-
expect(result.content[0].text).toContain('Error: Not found');
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('gog_docs_comments_add', () => {
|
|
228
|
-
it('calls run with content', async () => {
|
|
229
|
-
vi.mocked(runner.run).mockResolvedValue('{"id":"c2"}');
|
|
230
|
-
const handlers = setupHandlers();
|
|
231
|
-
await handlers.get('gog_docs_comments_add')!({ docId: 'abc', content: 'Please review' });
|
|
232
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
233
|
-
['docs', 'comments', 'add', 'abc', 'Please review'],
|
|
234
|
-
{ account: undefined },
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('includes --quoted when provided', async () => {
|
|
239
|
-
vi.mocked(runner.run).mockResolvedValue('{"id":"c2"}');
|
|
240
|
-
const handlers = setupHandlers();
|
|
241
|
-
await handlers.get('gog_docs_comments_add')!({
|
|
242
|
-
docId: 'abc',
|
|
243
|
-
content: 'Typo here',
|
|
244
|
-
quoted: 'teh',
|
|
245
|
-
});
|
|
246
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
247
|
-
['docs', 'comments', 'add', 'abc', 'Typo here', '--quoted=teh'],
|
|
248
|
-
{ account: undefined },
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('omits --quoted when not provided', async () => {
|
|
253
|
-
vi.mocked(runner.run).mockResolvedValue('{"id":"c2"}');
|
|
254
|
-
const handlers = setupHandlers();
|
|
255
|
-
await handlers.get('gog_docs_comments_add')!({ docId: 'abc', content: 'Nice' });
|
|
256
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
257
|
-
['docs', 'comments', 'add', 'abc', 'Nice'],
|
|
258
|
-
{ account: undefined },
|
|
259
|
-
);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('returns error text on failure', async () => {
|
|
263
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('Add failed'));
|
|
264
|
-
const handlers = setupHandlers();
|
|
265
|
-
const result = await handlers.get('gog_docs_comments_add')!({ docId: 'bad', content: 'x' });
|
|
266
|
-
expect(result.content[0].text).toContain('Error: Add failed');
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
describe('gog_docs_comments_reply', () => {
|
|
271
|
-
it('calls run with correct args', async () => {
|
|
272
|
-
vi.mocked(runner.run).mockResolvedValue('{"id":"r1"}');
|
|
273
|
-
const handlers = setupHandlers();
|
|
274
|
-
await handlers.get('gog_docs_comments_reply')!({ docId: 'abc', commentId: 'c1', content: 'Done' });
|
|
275
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
276
|
-
['docs', 'comments', 'reply', 'abc', 'c1', 'Done'],
|
|
277
|
-
{ account: undefined },
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('returns error text on failure', async () => {
|
|
282
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('Reply failed'));
|
|
283
|
-
const handlers = setupHandlers();
|
|
284
|
-
const result = await handlers.get('gog_docs_comments_reply')!({
|
|
285
|
-
docId: 'abc', commentId: 'c1', content: 'x',
|
|
286
|
-
});
|
|
287
|
-
expect(result.content[0].text).toContain('Error: Reply failed');
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe('gog_docs_comments_resolve', () => {
|
|
292
|
-
it('calls run with correct args', async () => {
|
|
293
|
-
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
294
|
-
const handlers = setupHandlers();
|
|
295
|
-
await handlers.get('gog_docs_comments_resolve')!({ docId: 'abc', commentId: 'c1' });
|
|
296
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
297
|
-
['docs', 'comments', 'resolve', 'abc', 'c1'],
|
|
298
|
-
{ account: undefined },
|
|
299
|
-
);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('includes --message when provided', async () => {
|
|
303
|
-
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
304
|
-
const handlers = setupHandlers();
|
|
305
|
-
await handlers.get('gog_docs_comments_resolve')!({
|
|
306
|
-
docId: 'abc', commentId: 'c1', message: 'Fixed in v2',
|
|
307
|
-
});
|
|
308
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
309
|
-
['docs', 'comments', 'resolve', 'abc', 'c1', '--message=Fixed in v2'],
|
|
310
|
-
{ account: undefined },
|
|
311
|
-
);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('omits --message when not provided', async () => {
|
|
315
|
-
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
316
|
-
const handlers = setupHandlers();
|
|
317
|
-
await handlers.get('gog_docs_comments_resolve')!({ docId: 'abc', commentId: 'c1' });
|
|
318
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
319
|
-
['docs', 'comments', 'resolve', 'abc', 'c1'],
|
|
320
|
-
{ account: undefined },
|
|
321
|
-
);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('returns error text on failure', async () => {
|
|
325
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('Resolve failed'));
|
|
326
|
-
const handlers = setupHandlers();
|
|
327
|
-
const result = await handlers.get('gog_docs_comments_resolve')!({ docId: 'abc', commentId: 'c1' });
|
|
328
|
-
expect(result.content[0].text).toContain('Error: Resolve failed');
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe('gog_docs_comments_delete', () => {
|
|
333
|
-
it('calls run with correct args', async () => {
|
|
334
|
-
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
335
|
-
const handlers = setupHandlers();
|
|
336
|
-
await handlers.get('gog_docs_comments_delete')!({ docId: 'abc', commentId: 'c1' });
|
|
337
|
-
expect(runner.run).toHaveBeenCalledWith(
|
|
338
|
-
['docs', 'comments', 'delete', 'abc', 'c1'],
|
|
339
|
-
{ account: undefined },
|
|
340
|
-
);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('returns error text on failure', async () => {
|
|
344
|
-
vi.mocked(runner.run).mockRejectedValue(new Error('Delete failed'));
|
|
345
|
-
const handlers = setupHandlers();
|
|
346
|
-
const result = await handlers.get('gog_docs_comments_delete')!({ docId: 'abc', commentId: 'c1' });
|
|
347
|
-
expect(result.content[0].text).toContain('Error: Delete failed');
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
164
|
describe('gog_docs_run', () => {
|
|
352
165
|
it('passes raw subcommand and args to runner', async () => {
|
|
353
166
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
package/tsconfig.json
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
4
|
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src"
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"types": ["node"]
|
|
5
|
+
"rootDir": "./src"
|
|
12
6
|
},
|
|
13
7
|
"include": ["src/**/*"],
|
|
14
8
|
"exclude": ["node_modules", "dist"]
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
workflow_call:
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
ci:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v6.0.2
|
|
14
|
-
|
|
15
|
-
- uses: actions/setup-node@v6.3.0
|
|
16
|
-
with:
|
|
17
|
-
node-version: 22
|
|
18
|
-
cache: npm
|
|
19
|
-
|
|
20
|
-
- run: npm ci
|
|
21
|
-
- run: npm run build
|
|
22
|
-
- run: npm test
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
ci:
|
|
10
|
-
uses: ./.github/workflows/ci.yml
|
|
11
|
-
|
|
12
|
-
release:
|
|
13
|
-
needs: ci
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
permissions:
|
|
16
|
-
contents: write
|
|
17
|
-
id-token: write
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v6.0.2
|
|
21
|
-
|
|
22
|
-
- uses: actions/setup-node@v6.3.0
|
|
23
|
-
with:
|
|
24
|
-
node-version: 24
|
|
25
|
-
cache: npm
|
|
26
|
-
registry-url: https://registry.npmjs.org
|
|
27
|
-
|
|
28
|
-
# Strip always-auth from .npmrc (set by setup-node, deprecated in npm 11)
|
|
29
|
-
- run: sed -i '/always-auth/d' "$NPM_CONFIG_USERCONFIG"
|
|
30
|
-
|
|
31
|
-
- run: npm ci
|
|
32
|
-
- run: npm run build
|
|
33
|
-
|
|
34
|
-
- name: Extract version
|
|
35
|
-
run: |
|
|
36
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
37
|
-
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
|
38
|
-
|
|
39
|
-
# Package the .skill file
|
|
40
|
-
- name: Package skill
|
|
41
|
-
run: |
|
|
42
|
-
python3 - <<'EOF'
|
|
43
|
-
import zipfile, pathlib, os
|
|
44
|
-
|
|
45
|
-
version = os.environ["VERSION"]
|
|
46
|
-
skill_name = "gogcli-mcp"
|
|
47
|
-
out = pathlib.Path(f"{skill_name}-{version}.skill")
|
|
48
|
-
|
|
49
|
-
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
50
|
-
zf.write(pathlib.Path("SKILL.md"), f"{skill_name}/SKILL.md")
|
|
51
|
-
|
|
52
|
-
print(f"Packaged {out} ({out.stat().st_size} bytes)")
|
|
53
|
-
EOF
|
|
54
|
-
|
|
55
|
-
# Sync manifest.json version from package.json and build .mcpb
|
|
56
|
-
- name: Build .mcpb bundle
|
|
57
|
-
run: |
|
|
58
|
-
node -e "
|
|
59
|
-
const fs = require('fs');
|
|
60
|
-
const m = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
|
61
|
-
m.version = '$VERSION';
|
|
62
|
-
fs.writeFileSync('manifest.json', JSON.stringify(m, null, 2) + '\n');
|
|
63
|
-
"
|
|
64
|
-
npx @anthropic-ai/mcpb pack
|
|
65
|
-
mv gogcli-mcp.mcpb "gogcli-mcp-${VERSION}.mcpb"
|
|
66
|
-
|
|
67
|
-
- name: Publish to npm
|
|
68
|
-
run: npm publish --access public --provenance
|
|
69
|
-
|
|
70
|
-
- name: Create GitHub Release
|
|
71
|
-
uses: softprops/action-gh-release@v3.0.0
|
|
72
|
-
with:
|
|
73
|
-
files: |
|
|
74
|
-
gogcli-mcp-${{ env.VERSION }}.skill
|
|
75
|
-
gogcli-mcp-${{ env.VERSION }}.mcpb
|
|
76
|
-
generate_release_notes: true
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
name: Tag & Bump
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
|
|
6
|
-
jobs:
|
|
7
|
-
ci:
|
|
8
|
-
uses: ./.github/workflows/ci.yml
|
|
9
|
-
|
|
10
|
-
tag-and-bump:
|
|
11
|
-
needs: ci
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
contents: write
|
|
15
|
-
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v6.0.2
|
|
18
|
-
with:
|
|
19
|
-
# PAT required — GITHUB_TOKEN pushes don't trigger other workflows
|
|
20
|
-
token: ${{ secrets.RELEASE_PAT }}
|
|
21
|
-
|
|
22
|
-
- uses: actions/setup-node@v6.3.0
|
|
23
|
-
with:
|
|
24
|
-
node-version: 22
|
|
25
|
-
cache: npm
|
|
26
|
-
|
|
27
|
-
- run: npm ci
|
|
28
|
-
|
|
29
|
-
- name: Configure git
|
|
30
|
-
run: |
|
|
31
|
-
git config user.name "github-actions[bot]"
|
|
32
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
33
|
-
|
|
34
|
-
# Tag the current commit with the current version
|
|
35
|
-
- name: Tag current version
|
|
36
|
-
run: |
|
|
37
|
-
CURRENT=$(node -p "require('./package.json').version")
|
|
38
|
-
git tag "v${CURRENT}"
|
|
39
|
-
echo "TAGGED_VERSION=${CURRENT}" >> "$GITHUB_ENV"
|
|
40
|
-
|
|
41
|
-
# Bump patch version in all four locations
|
|
42
|
-
- name: Bump patch version
|
|
43
|
-
run: |
|
|
44
|
-
npm version patch --no-git-tag-version
|
|
45
|
-
NEXT=$(node -p "require('./package.json').version")
|
|
46
|
-
echo "NEXT_VERSION=${NEXT}" >> "$GITHUB_ENV"
|
|
47
|
-
|
|
48
|
-
# src/index.ts — MCP server version
|
|
49
|
-
sed -i "s/version: '${TAGGED_VERSION}'/version: '${NEXT}'/" src/index.ts
|
|
50
|
-
|
|
51
|
-
# manifest.json
|
|
52
|
-
node -e "
|
|
53
|
-
const fs = require('fs');
|
|
54
|
-
const m = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
|
55
|
-
m.version = '${NEXT}';
|
|
56
|
-
fs.writeFileSync('manifest.json', JSON.stringify(m, null, 2) + '\n');
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
- name: Rebuild
|
|
60
|
-
run: npm run build
|
|
61
|
-
|
|
62
|
-
- name: Commit and push
|
|
63
|
-
run: |
|
|
64
|
-
git add -A
|
|
65
|
-
git commit -m "chore: bump version to v${NEXT_VERSION}"
|
|
66
|
-
git push origin main
|
|
67
|
-
git push origin "v${TAGGED_VERSION}"
|
package/.mcp.json
DELETED
package/CLAUDE.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# gogcli-mcp
|
|
2
|
-
|
|
3
|
-
MCP server wrapping [gogcli](https://github.com/steipete/gogcli) — provides Claude with read/write access to Google Sheets, with a scaffold for Gmail, Calendar, Drive, and more.
|
|
4
|
-
|
|
5
|
-
## Build & Test
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm run build # tsc --noEmit (type check) + esbuild bundle → dist/index.js
|
|
9
|
-
npm test # vitest run (all tests)
|
|
10
|
-
npm run test:watch # vitest in watch mode
|
|
11
|
-
npm run test:coverage # vitest with 100% coverage enforcement
|
|
12
|
-
npm run typecheck # tsc --noEmit only
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Versioning
|
|
16
|
-
|
|
17
|
-
Version appears in FOUR places — all must match:
|
|
18
|
-
|
|
19
|
-
1. `package.json` → `"version"`
|
|
20
|
-
2. `package-lock.json` → run `npm install --package-lock-only` after changing package.json
|
|
21
|
-
3. `src/index.ts` → `McpServer` constructor `version` field
|
|
22
|
-
4. `manifest.json` → `"version"`
|
|
23
|
-
|
|
24
|
-
### Release workflow
|
|
25
|
-
|
|
26
|
-
Main is always one version ahead of the latest tag. To release, run the **Tag & Bump** GitHub Action (`tag-and-bump.yml`) which:
|
|
27
|
-
|
|
28
|
-
1. Runs CI (build + test)
|
|
29
|
-
2. Tags the current commit with the current version
|
|
30
|
-
3. Bumps patch in all four files
|
|
31
|
-
4. Rebuilds, commits, and pushes main + tag
|
|
32
|
-
5. The tag push triggers the **Release** workflow (CI + npm publish + .mcpb + .skill + GitHub release)
|
|
33
|
-
|
|
34
|
-
Do NOT manually bump versions or create tags unless the user explicitly asks. Always prefer the Tag & Bump action: `gh workflow run tag-and-bump.yml --ref main`. The action handles all four version files, tagging, and triggering the release.
|
|
35
|
-
|
|
36
|
-
## Architecture
|
|
37
|
-
|
|
38
|
-
- `src/runner.ts` — only module touching `child_process`. Exports `run(args, options)` with `Spawner` DI for testing. Injects `--json --no-input --color=never`, handles `--account` from `options.account` → `GOG_ACCOUNT` env var → omit. 30-second timeout kills stalled processes.
|
|
39
|
-
- `src/tools/sheets.ts` — registers 8 Sheets MCP tools via `registerSheetsTools(server)`. Imports `run()` from runner. Errors are caught and returned as text content so the model can read gogcli's error messages.
|
|
40
|
-
- `src/index.ts` — MCP server entry point. Creates `McpServer`, calls `registerXxxTools(server)` for each service, connects via `StdioServerTransport`.
|
|
41
|
-
- `tests/runner.test.ts` — unit tests for runner using mock `Spawner` DI (no real processes)
|
|
42
|
-
- `tests/tools/sheets.test.ts` — unit tests for sheets tools, runner mocked via `vi.mock`
|
|
43
|
-
|
|
44
|
-
## Adding a New Google Service
|
|
45
|
-
|
|
46
|
-
To add Gmail, Calendar, Drive, etc.:
|
|
47
|
-
|
|
48
|
-
1. Create `src/tools/<service>.ts` — export `registerXxxTools(server: McpServer)`
|
|
49
|
-
2. Implement curated tools for common ops + a `gog_<service>_run` escape hatch (see `sheets.ts` for the pattern)
|
|
50
|
-
3. Create `tests/tools/<service>.test.ts` — mock `runner.run` via `vi.mock('../../src/runner.js')`
|
|
51
|
-
4. In `src/index.ts`, import and call `registerXxxTools(server)`
|
|
52
|
-
5. Add tools to `manifest.json` tools list
|
|
53
|
-
6. No changes to `runner.ts` or `.mcp.json` required
|
|
54
|
-
|
|
55
|
-
## gogcli Notes
|
|
56
|
-
|
|
57
|
-
- `gog schema --json` outputs machine-readable command/flag schema for all subcommands
|
|
58
|
-
- `gog sheets update` and `gog sheets append` accept `--values-json=<JSON 2D array>` for structured input
|
|
59
|
-
- All commands support `--account <email>` for multi-account targeting
|
|
60
|
-
- `--no-input` prevents interactive prompts; `--json` ensures parseable output; `--color=never` strips ANSI codes
|
|
61
|
-
- `gog agent exit-codes` documents stable exit codes for automation
|