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/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.5",
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.5",
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": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
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 { registerAuthTools } from './tools/auth.js';
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 },
@@ -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"]
@@ -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
@@ -1,13 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "gogcli": {
4
- "command": "node",
5
- "args": ["dist/index.js"],
6
- "cwd": "/Users/chris/git/gogcli-mcp",
7
- "env": {
8
- "GOG_ACCOUNT": "${GOG_ACCOUNT}",
9
- "GOG_PATH": "${GOG_PATH}"
10
- }
11
- }
12
- }
13
- }
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