gogcli-mcp 1.0.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.
@@ -0,0 +1,35 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//Users/chris/git/ofw-mcp/**)",
5
+ "Read(//Users/chris/git/**)",
6
+ "Bash(gog --help)",
7
+ "Bash(gog agent:*)",
8
+ "Bash(gog schema:*)",
9
+ "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); cmds=[c['name'] for c in d['command'].get\\('subcommands',[]\\)]; print\\('\\\\n'.join\\(cmds\\)\\)\")",
10
+ "Bash(python3 -c ':*)",
11
+ "Bash(gog sheets:*)",
12
+ "Bash(git add:*)",
13
+ "Bash(git commit -m ':*)",
14
+ "Bash(xargs cat:*)",
15
+ "Bash(git:*)",
16
+ "Bash(npm install:*)",
17
+ "Bash(npm list:*)",
18
+ "Bash(npm --version)",
19
+ "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('lockfileVersion:', d.get\\('lockfileVersion'\\)\\); pkgs=d.get\\('packages',{}\\); mcp=pkgs.get\\('node_modules/@modelcontextprotocol/sdk',{}\\); zod=pkgs.get\\('node_modules/zod',{}\\); ts=pkgs.get\\('node_modules/typescript',{}\\); vitest=pkgs.get\\('node_modules/vitest',{}\\); esbuild=pkgs.get\\('node_modules/esbuild',{}\\); print\\('MCP SDK resolved:', mcp.get\\('version'\\)\\); print\\('zod resolved:', zod.get\\('version'\\)\\); print\\('typescript resolved:', ts.get\\('version'\\)\\); print\\('vitest resolved:', vitest.get\\('version'\\)\\); print\\('esbuild resolved:', esbuild.get\\('version'\\)\\)\")",
20
+ "Bash(npm test:*)",
21
+ "Bash(npx tsc:*)",
22
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('version','?'\\)\\)\")",
23
+ "Bash(node_modules/.bin/tsc --version)",
24
+ "Bash(node_modules/.bin/tsc --noEmit)",
25
+ "Bash(cat /Users/chris/git/gogcli-mcp/vitest.config.*)",
26
+ "Bash(node:*)",
27
+ "Bash(npm run:*)",
28
+ "Bash(tsc:*)",
29
+ "Bash(ls /Users/chris/git/gogcli-mcp/*.json)",
30
+ "Bash(ls /Users/chris/git/gogcli-mcp/*.md)",
31
+ "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2\\)\\)\")",
32
+ "Bash(gh repo:*)"
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,76 @@
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
@@ -0,0 +1,67 @@
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 ADDED
@@ -0,0 +1,12 @@
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
+ }
10
+ }
11
+ }
12
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,61 @@
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.
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
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # gogcli-mcp
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that gives Claude natural-language access to Google Sheets (and more) via [gogcli](https://github.com/steipete/gogcli).
4
+
5
+ > [!WARNING]
6
+ > **AI-developed project.** This codebase was entirely built and is actively maintained by [Claude Sonnet 4.6](https://www.anthropic.com/claude). No human has audited the implementation. Review all code and tool permissions before use.
7
+
8
+ ## What you can do
9
+
10
+ Ask Claude things like:
11
+
12
+ - *"Read the data in Sheet1!A1:D20 of my budget spreadsheet"*
13
+ - *"Append this week's expenses to my tracking sheet"*
14
+ - *"Create a new spreadsheet called Q2 Planning"*
15
+ - *"Find all instances of 'TBD' in my project sheet and replace with 'Done'"*
16
+ - *"What tabs are in spreadsheet ID xyz?"*
17
+
18
+ ## Requirements
19
+
20
+ - [gogcli](https://github.com/steipete/gogcli) installed and authenticated (`gog --help` works)
21
+ - [Claude Desktop](https://claude.ai/download) or [Claude Code](https://claude.ai/code)
22
+ - Node.js 18 or later
23
+
24
+ Install gogcli via Homebrew:
25
+
26
+ ```bash
27
+ brew install gogcli
28
+ ```
29
+
30
+ Then authenticate:
31
+
32
+ ```bash
33
+ gog auth add
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ ### 1. Clone and build
39
+
40
+ ```bash
41
+ git clone https://github.com/chrischall/gogcli-mcp.git
42
+ cd gogcli-mcp
43
+ npm install
44
+ npm run build
45
+ ```
46
+
47
+ ### 2. Add to Claude Desktop
48
+
49
+ Edit your Claude Desktop config file:
50
+
51
+ - **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
52
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
53
+
54
+ Add the `gogcli` entry inside `"mcpServers"`:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "gogcli": {
60
+ "command": "node",
61
+ "args": ["/absolute/path/to/gogcli-mcp/dist/index.js"],
62
+ "env": {
63
+ "GOG_ACCOUNT": "you@gmail.com"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ Replace `/absolute/path/to/gogcli-mcp` with the actual path. On Mac, run `pwd` inside the cloned directory to get it.
71
+
72
+ `GOG_ACCOUNT` is optional — omit it to use gogcli's configured default account.
73
+
74
+ ### 3. Add to Claude Code
75
+
76
+ The repo includes `.mcp.json`. From the project directory:
77
+
78
+ ```bash
79
+ # The .mcp.json is already configured — just set your account
80
+ export GOG_ACCOUNT=you@gmail.com
81
+ ```
82
+
83
+ Or add `GOG_ACCOUNT` to your shell profile.
84
+
85
+ ## Tools
86
+
87
+ Read-only tools run automatically. Write tools ask for confirmation first.
88
+
89
+ | Tool | What it does | Permission |
90
+ |------|-------------|------------|
91
+ | `gog_sheets_get` | Read values from a range | Auto |
92
+ | `gog_sheets_metadata` | Get title, tabs, and named ranges | Auto |
93
+ | `gog_sheets_update` | Write values to a range | Confirm |
94
+ | `gog_sheets_append` | Append rows after existing data | Confirm |
95
+ | `gog_sheets_clear` | Clear values in a range | Confirm |
96
+ | `gog_sheets_create` | Create a new spreadsheet | Confirm |
97
+ | `gog_sheets_find_replace` | Find and replace across a spreadsheet | Confirm |
98
+ | `gog_sheets_run` | Run any `gog sheets` subcommand (escape hatch) | Confirm |
99
+
100
+ All tools accept an optional `account` parameter to target a specific Google account for that call, overriding `GOG_ACCOUNT`.
101
+
102
+ ## Multiple Accounts
103
+
104
+ If you use multiple Google accounts with gogcli, you can target a specific account per-call:
105
+
106
+ ```
107
+ Read Sheet1!A1:D10 from spreadsheet abc123 using my work account work@company.com
108
+ ```
109
+
110
+ Claude will pass `account: "work@company.com"` to the tool, which adds `--account work@company.com` to the gogcli command.
111
+
112
+ ## Troubleshooting
113
+
114
+ **"gog not found"** — gogcli is not installed or not in your PATH. Run `gog --help` in your terminal to verify. Install with `brew install gogcli`.
115
+
116
+ **"not authenticated"** — run `gog auth add` to authenticate. Run `gog auth list` to see configured accounts.
117
+
118
+ **"Spreadsheet not found"** — verify the spreadsheet ID (the long string in the URL between `/d/` and `/edit`).
119
+
120
+ **Tools not appearing in Claude Desktop** — go to **Settings → Developer** to see connected servers. Fully quit and relaunch after editing the config.
121
+
122
+ **Can't find the config file on Mac** — in Finder press Cmd+Shift+G and paste `~/Library/Application Support/Claude/`.
123
+
124
+ ## Security
125
+
126
+ - `GOG_ACCOUNT` is optional and only selects which authenticated account to use
127
+ - No credentials are stored or passed by this server — authentication is handled entirely by gogcli's own keyring
128
+ - All gogcli invocations use `--no-input` to prevent interactive prompts
129
+ - All arguments are passed as arrays to `child_process.spawn` — no shell injection risk
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ npm test # run the test suite (33 tests, 100% coverage)
135
+ npm run build # compile TypeScript → dist/index.js
136
+ npm run test:coverage # run with coverage report
137
+ ```
138
+
139
+ ### Project structure
140
+
141
+ ```
142
+ src/
143
+ runner.ts gog CLI executor (auth injection, timeout, error handling)
144
+ index.ts MCP server entry point
145
+ tools/
146
+ sheets.ts 8 Google Sheets tools
147
+ tests/
148
+ runner.test.ts
149
+ tools/
150
+ sheets.test.ts
151
+ ```
152
+
153
+ ### Adding more Google services
154
+
155
+ gogcli supports Gmail, Calendar, Drive, Contacts, Tasks, Docs, Slides, Chat, and more. To add a service:
156
+
157
+ 1. Create `src/tools/<service>.ts` with `registerXxxTools(server: McpServer)`
158
+ 2. Create `tests/tools/<service>.test.ts` (mock runner via `vi.mock`)
159
+ 3. Call `registerXxxTools(server)` in `src/index.ts`
160
+
161
+ See `CLAUDE.md` for the full pattern.
162
+
163
+ ## License
164
+
165
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: gogcli-mcp
3
+ description: Use when the user asks to read, write, or manage Google Sheets. Also triggers for requests involving Google Sheets data like "read my spreadsheet", "update a cell", "append rows", "create a spreadsheet", or "find and replace in Sheets". Broader Google service support (Gmail, Calendar, Drive) can be added via future service modules.
4
+ ---
5
+
6
+ # gogcli-mcp
7
+
8
+ MCP server wrapping [gogcli](https://github.com/steipete/gogcli) — provides Claude with access to Google Sheets (and a scaffold for Gmail, Calendar, Drive, and more).
9
+
10
+ - **Source:** [github.com/chrischall/gogcli-mcp](https://github.com/chrischall/gogcli-mcp)
11
+
12
+ ## Requirements
13
+
14
+ - [gogcli](https://github.com/steipete/gogcli) installed and authenticated (`gog --help` works in your shell)
15
+ - Node.js 18 or later
16
+
17
+ ## Setup
18
+
19
+ ### Option A — Claude Code (direct MCP)
20
+
21
+ Add to `.mcp.json` in your project:
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "gogcli": {
27
+ "command": "node",
28
+ "args": ["/path/to/gogcli-mcp/dist/index.js"],
29
+ "cwd": "/path/to/gogcli-mcp",
30
+ "env": {
31
+ "GOG_ACCOUNT": "you@gmail.com"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### Option B — npx
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "gogcli": {
44
+ "command": "npx",
45
+ "args": ["-y", "gogcli-mcp"],
46
+ "env": {
47
+ "GOG_ACCOUNT": "you@gmail.com"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ `GOG_ACCOUNT` is optional — omit it to use gogcli's configured default account. Pass it per-tool-call to target a specific account dynamically.
55
+
56
+ ## Available Tools
57
+
58
+ | Tool | What it does |
59
+ |------|-------------|
60
+ | `gog_sheets_get` | Read values from a range |
61
+ | `gog_sheets_update` | Write values to a range |
62
+ | `gog_sheets_append` | Append rows after existing data |
63
+ | `gog_sheets_clear` | Clear values in a range |
64
+ | `gog_sheets_metadata` | Get title, tabs, named ranges |
65
+ | `gog_sheets_create` | Create a new spreadsheet |
66
+ | `gog_sheets_find_replace` | Find and replace across a spreadsheet |
67
+ | `gog_sheets_run` | Run any `gog sheets` subcommand (escape hatch) |
68
+
69
+ All tools accept an optional `account` parameter to override the default Google account for that call.