gogcli-mcp 1.0.0 → 1.0.2

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.
Binary file
Binary file
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.0",
6
+ "version": "1.0.2",
7
7
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -36,7 +36,9 @@
36
36
  "${__dirname}/dist/index.js"
37
37
  ],
38
38
  "env": {
39
- "GOG_ACCOUNT": "${user_config.gog_account}"
39
+ "GOG_ACCOUNT": "${user_config.gog_account}",
40
+ "GOG_PATH": "${user_config.gog_path}",
41
+ "GOG_ACCESS_TOKEN": "${user_config.gog_access_token}"
40
42
  }
41
43
  }
42
44
  },
@@ -46,9 +48,194 @@
46
48
  "title": "Google Account",
47
49
  "description": "Default Google account email (optional — uses gogcli's configured default if omitted)",
48
50
  "required": false
51
+ },
52
+ "gog_path": {
53
+ "type": "string",
54
+ "title": "gog Executable Path",
55
+ "description": "Path to the gog executable (optional — defaults to 'gog' on your PATH)",
56
+ "required": false
57
+ },
58
+ "gog_access_token": {
59
+ "type": "string",
60
+ "title": "Google Access Token",
61
+ "description": "Short-lived OAuth access token (optional — bypasses stored refresh tokens, expires in ~1h). Useful for CI or automated contexts.",
62
+ "required": false,
63
+ "sensitive": true
49
64
  }
50
65
  },
51
66
  "tools": [
67
+ {
68
+ "name": "gog_auth_list",
69
+ "description": "List all Google accounts stored in gogcli"
70
+ },
71
+ {
72
+ "name": "gog_auth_status",
73
+ "description": "Show gogcli auth configuration (keyring, credential files)"
74
+ },
75
+ {
76
+ "name": "gog_auth_services",
77
+ "description": "List Google services and OAuth scopes supported by gogcli"
78
+ },
79
+ {
80
+ "name": "gog_auth_run",
81
+ "description": "Run any gog auth subcommand (escape hatch)"
82
+ },
83
+ {
84
+ "name": "gog_gmail_search",
85
+ "description": "Search Gmail threads by query"
86
+ },
87
+ {
88
+ "name": "gog_gmail_get",
89
+ "description": "Get a Gmail message by ID"
90
+ },
91
+ {
92
+ "name": "gog_gmail_send",
93
+ "description": "Send an email"
94
+ },
95
+ {
96
+ "name": "gog_gmail_run",
97
+ "description": "Run any gog gmail subcommand (escape hatch)"
98
+ },
99
+ {
100
+ "name": "gog_calendar_events",
101
+ "description": "List calendar events with optional filters"
102
+ },
103
+ {
104
+ "name": "gog_calendar_get",
105
+ "description": "Get a specific calendar event"
106
+ },
107
+ {
108
+ "name": "gog_calendar_create",
109
+ "description": "Create a calendar event"
110
+ },
111
+ {
112
+ "name": "gog_calendar_update",
113
+ "description": "Update an existing calendar event"
114
+ },
115
+ {
116
+ "name": "gog_calendar_delete",
117
+ "description": "Delete a calendar event"
118
+ },
119
+ {
120
+ "name": "gog_calendar_respond",
121
+ "description": "Respond to a calendar event invitation"
122
+ },
123
+ {
124
+ "name": "gog_calendar_run",
125
+ "description": "Run any gog calendar subcommand (escape hatch)"
126
+ },
127
+ {
128
+ "name": "gog_drive_ls",
129
+ "description": "List files in a Google Drive folder"
130
+ },
131
+ {
132
+ "name": "gog_drive_search",
133
+ "description": "Search Google Drive files"
134
+ },
135
+ {
136
+ "name": "gog_drive_get",
137
+ "description": "Get Google Drive file metadata"
138
+ },
139
+ {
140
+ "name": "gog_drive_mkdir",
141
+ "description": "Create a folder in Google Drive"
142
+ },
143
+ {
144
+ "name": "gog_drive_rename",
145
+ "description": "Rename a file or folder in Google Drive"
146
+ },
147
+ {
148
+ "name": "gog_drive_move",
149
+ "description": "Move a file to a different folder"
150
+ },
151
+ {
152
+ "name": "gog_drive_delete",
153
+ "description": "Move a file to trash"
154
+ },
155
+ {
156
+ "name": "gog_drive_share",
157
+ "description": "Share a file or folder"
158
+ },
159
+ {
160
+ "name": "gog_drive_run",
161
+ "description": "Run any gog drive subcommand (escape hatch)"
162
+ },
163
+ {
164
+ "name": "gog_tasks_lists",
165
+ "description": "List all Google Task lists"
166
+ },
167
+ {
168
+ "name": "gog_tasks_list",
169
+ "description": "List tasks in a task list"
170
+ },
171
+ {
172
+ "name": "gog_tasks_get",
173
+ "description": "Get a specific task"
174
+ },
175
+ {
176
+ "name": "gog_tasks_add",
177
+ "description": "Add a task to a task list"
178
+ },
179
+ {
180
+ "name": "gog_tasks_done",
181
+ "description": "Mark a task as completed"
182
+ },
183
+ {
184
+ "name": "gog_tasks_delete",
185
+ "description": "Delete a task"
186
+ },
187
+ {
188
+ "name": "gog_tasks_run",
189
+ "description": "Run any gog tasks subcommand (escape hatch)"
190
+ },
191
+ {
192
+ "name": "gog_contacts_search",
193
+ "description": "Search Google Contacts"
194
+ },
195
+ {
196
+ "name": "gog_contacts_list",
197
+ "description": "List all Google Contacts"
198
+ },
199
+ {
200
+ "name": "gog_contacts_get",
201
+ "description": "Get a contact by resource name"
202
+ },
203
+ {
204
+ "name": "gog_contacts_create",
205
+ "description": "Create a new Google Contact"
206
+ },
207
+ {
208
+ "name": "gog_contacts_run",
209
+ "description": "Run any gog contacts subcommand (escape hatch)"
210
+ },
211
+ {
212
+ "name": "gog_docs_info",
213
+ "description": "Get Google Doc metadata (title, ID, properties)"
214
+ },
215
+ {
216
+ "name": "gog_docs_cat",
217
+ "description": "Read a Google Doc as plain text"
218
+ },
219
+ {
220
+ "name": "gog_docs_create",
221
+ "description": "Create a new Google Doc"
222
+ },
223
+ {
224
+ "name": "gog_docs_write",
225
+ "description": "Write or append text content to a Google Doc"
226
+ },
227
+ {
228
+ "name": "gog_docs_find_replace",
229
+ "description": "Find and replace text in a Google Doc"
230
+ },
231
+ {
232
+ "name": "gog_docs_structure",
233
+ "description": "Show document structure with numbered paragraphs"
234
+ },
235
+ {
236
+ "name": "gog_docs_run",
237
+ "description": "Run any gog docs subcommand (escape hatch)"
238
+ },
52
239
  {
53
240
  "name": "gog_sheets_get",
54
241
  "description": "Read values from a Google Sheets range"
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server wrapping gogcli for Google service access",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/chrischall/gogcli-mcp"
8
+ },
5
9
  "type": "module",
6
10
  "bin": {
7
11
  "gogcli-mcp": "dist/index.js"
package/src/index.ts CHANGED
@@ -1,11 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  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';
4
10
  import { registerSheetsTools } from './tools/sheets.js';
11
+ import { registerTasksTools } from './tools/tasks.js';
5
12
 
6
- const server = new McpServer({ name: 'gogcli', version: '1.0.0' });
13
+ const server = new McpServer({ name: 'gogcli', version: '1.0.2' });
7
14
 
15
+ registerAuthTools(server);
16
+ registerCalendarTools(server);
17
+ registerContactsTools(server);
18
+ registerDocsTools(server);
19
+ registerDriveTools(server);
20
+ registerGmailTools(server);
8
21
  registerSheetsTools(server);
22
+ registerTasksTools(server);
9
23
 
10
24
  // To add more services: import registerXxxTools and call them here.
11
25
  // Example: registerGmailTools(server);
package/src/runner.ts CHANGED
@@ -26,7 +26,7 @@ export async function run(args: string[], options: RunOptions = {}): Promise<str
26
26
  fullArgs.push(...args);
27
27
 
28
28
  return new Promise((resolve, reject) => {
29
- const child = spawner('gog', fullArgs, { env: process.env });
29
+ const child = spawner(process.env.GOG_PATH ?? 'gog', fullArgs, { env: process.env });
30
30
  let stdout = '';
31
31
  let stderr = '';
32
32
  let settled = false;
@@ -0,0 +1,57 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { run } from '../runner.js';
4
+ import { toText, toError } from './utils.js';
5
+
6
+ export function registerAuthTools(server: McpServer): void {
7
+ server.registerTool('gog_auth_list', {
8
+ description: 'List all Google accounts stored in gogcli. Use this to check which accounts are configured and available.',
9
+ annotations: { readOnlyHint: true },
10
+ inputSchema: {},
11
+ }, async () => {
12
+ try {
13
+ return toText(await run(['auth', 'list']));
14
+ } catch (err) {
15
+ return toError(err);
16
+ }
17
+ });
18
+
19
+ server.registerTool('gog_auth_status', {
20
+ description: 'Show gogcli auth configuration: keyring backend, credential files, and auth setup.',
21
+ annotations: { readOnlyHint: true },
22
+ inputSchema: {},
23
+ }, async () => {
24
+ try {
25
+ return toText(await run(['auth', 'status']));
26
+ } catch (err) {
27
+ return toError(err);
28
+ }
29
+ });
30
+
31
+ server.registerTool('gog_auth_services', {
32
+ description: 'List all Google services supported by gogcli and the OAuth scopes each requires.',
33
+ annotations: { readOnlyHint: true },
34
+ inputSchema: {},
35
+ }, async () => {
36
+ try {
37
+ return toText(await run(['auth', 'services']));
38
+ } catch (err) {
39
+ return toError(err);
40
+ }
41
+ });
42
+
43
+ server.registerTool('gog_auth_run', {
44
+ description: 'Run any gog auth subcommand. Run `gog auth --help` to see all available subcommands and flags. Note: gog auth add requires interactive browser auth and cannot be completed over MCP — run it in your terminal instead: gog auth add <email> --services <service>',
45
+ annotations: { destructiveHint: true },
46
+ inputSchema: {
47
+ subcommand: z.string().describe('The gog auth subcommand, e.g. "remove", "alias", "tokens"'),
48
+ args: z.array(z.string()).describe('Additional positional args and flags'),
49
+ },
50
+ }, async ({ subcommand, args }) => {
51
+ try {
52
+ return toText(await run(['auth', subcommand, ...args]));
53
+ } catch (err) {
54
+ return toError(err);
55
+ }
56
+ });
57
+ }
@@ -0,0 +1,128 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { accountParam, runOrDiagnose } from './utils.js';
4
+
5
+ export function registerCalendarTools(server: McpServer): void {
6
+ server.registerTool('gog_calendar_events', {
7
+ description: 'List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today).',
8
+ annotations: { readOnlyHint: true },
9
+ inputSchema: {
10
+ calendarId: z.string().optional().describe('Calendar ID (default: primary calendar)'),
11
+ from: z.string().optional().describe('Start time filter (RFC3339, date, or natural language)'),
12
+ to: z.string().optional().describe('End time filter (RFC3339, date, or natural language)'),
13
+ today: z.boolean().optional().describe('Only show today\'s events'),
14
+ query: z.string().optional().describe('Free text search within events'),
15
+ all: z.boolean().optional().describe('Fetch events from all calendars'),
16
+ account: accountParam,
17
+ },
18
+ }, async ({ calendarId, from, to, today, query, all, account }) => {
19
+ const args = ['calendar', 'events'];
20
+ if (calendarId) args.push(calendarId);
21
+ if (from) args.push(`--from=${from}`);
22
+ if (to) args.push(`--to=${to}`);
23
+ if (today) args.push('--today');
24
+ if (query) args.push(`--query=${query}`);
25
+ if (all) args.push('--all');
26
+ return runOrDiagnose(args, { account });
27
+ });
28
+
29
+ server.registerTool('gog_calendar_get', {
30
+ description: 'Get a specific calendar event by ID.',
31
+ annotations: { readOnlyHint: true },
32
+ inputSchema: {
33
+ calendarId: z.string().describe('Calendar ID'),
34
+ eventId: z.string().describe('Event ID'),
35
+ account: accountParam,
36
+ },
37
+ }, async ({ calendarId, eventId, account }) => {
38
+ return runOrDiagnose(['calendar', 'event', calendarId, eventId], { account });
39
+ });
40
+
41
+ server.registerTool('gog_calendar_create', {
42
+ description: 'Create a calendar event.',
43
+ annotations: { destructiveHint: false },
44
+ inputSchema: {
45
+ calendarId: z.string().describe('Calendar ID (use "primary" for the default calendar)'),
46
+ summary: z.string().describe('Event title'),
47
+ from: z.string().describe('Start time (RFC3339 or date for all-day events)'),
48
+ to: z.string().describe('End time (RFC3339 or date for all-day events)'),
49
+ description: z.string().optional().describe('Event description'),
50
+ location: z.string().optional().describe('Event location'),
51
+ attendees: z.string().optional().describe('Attendee emails, comma-separated'),
52
+ allDay: z.boolean().optional().describe('All-day event (use date-only in from/to)'),
53
+ account: accountParam,
54
+ },
55
+ }, async ({ calendarId, summary, from, to, description, location, attendees, allDay, account }) => {
56
+ const args = ['calendar', 'create', calendarId, `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
57
+ if (description) args.push(`--description=${description}`);
58
+ if (location) args.push(`--location=${location}`);
59
+ if (attendees) args.push(`--attendees=${attendees}`);
60
+ if (allDay) args.push('--all-day');
61
+ return runOrDiagnose(args, { account });
62
+ });
63
+
64
+ server.registerTool('gog_calendar_update', {
65
+ description: 'Update an existing calendar event.',
66
+ annotations: { destructiveHint: false },
67
+ inputSchema: {
68
+ calendarId: z.string().describe('Calendar ID'),
69
+ eventId: z.string().describe('Event ID'),
70
+ summary: z.string().optional().describe('New event title'),
71
+ from: z.string().optional().describe('New start time (RFC3339)'),
72
+ to: z.string().optional().describe('New end time (RFC3339)'),
73
+ description: z.string().optional().describe('New description'),
74
+ location: z.string().optional().describe('New location'),
75
+ attendees: z.string().optional().describe('New attendee emails, comma-separated (replaces existing)'),
76
+ account: accountParam,
77
+ },
78
+ }, async ({ calendarId, eventId, summary, from, to, description, location, attendees, account }) => {
79
+ const args = ['calendar', 'update', calendarId, eventId];
80
+ if (summary !== undefined) args.push(`--summary=${summary}`);
81
+ if (from !== undefined) args.push(`--from=${from}`);
82
+ if (to !== undefined) args.push(`--to=${to}`);
83
+ if (description !== undefined) args.push(`--description=${description}`);
84
+ if (location !== undefined) args.push(`--location=${location}`);
85
+ if (attendees !== undefined) args.push(`--attendees=${attendees}`);
86
+ return runOrDiagnose(args, { account });
87
+ });
88
+
89
+ server.registerTool('gog_calendar_delete', {
90
+ description: 'Delete a calendar event.',
91
+ annotations: { destructiveHint: true },
92
+ inputSchema: {
93
+ calendarId: z.string().describe('Calendar ID'),
94
+ eventId: z.string().describe('Event ID'),
95
+ account: accountParam,
96
+ },
97
+ }, async ({ calendarId, eventId, account }) => {
98
+ return runOrDiagnose(['calendar', 'delete', calendarId, eventId], { account });
99
+ });
100
+
101
+ server.registerTool('gog_calendar_respond', {
102
+ description: 'Respond to a calendar event invitation.',
103
+ annotations: { destructiveHint: true },
104
+ inputSchema: {
105
+ calendarId: z.string().describe('Calendar ID'),
106
+ eventId: z.string().describe('Event ID'),
107
+ status: z.enum(['accepted', 'declined', 'tentative']).describe('Response status'),
108
+ comment: z.string().optional().describe('Optional comment to include with response'),
109
+ account: accountParam,
110
+ },
111
+ }, async ({ calendarId, eventId, status, comment, account }) => {
112
+ const args = ['calendar', 'respond', calendarId, eventId, `--status=${status}`];
113
+ if (comment) args.push(`--comment=${comment}`);
114
+ return runOrDiagnose(args, { account });
115
+ });
116
+
117
+ server.registerTool('gog_calendar_run', {
118
+ description: 'Run any gog calendar subcommand not covered by the other tools. Run `gog calendar --help` for the full list of subcommands, or `gog calendar <subcommand> --help` for flags on a specific subcommand.',
119
+ annotations: { destructiveHint: true },
120
+ inputSchema: {
121
+ subcommand: z.string().describe('The gog calendar subcommand to run, e.g. "calendars", "freebusy"'),
122
+ args: z.array(z.string()).describe('Additional positional args and flags'),
123
+ account: accountParam,
124
+ },
125
+ }, async ({ subcommand, args, account }) => {
126
+ return runOrDiagnose(['calendar', subcommand, ...args], { account });
127
+ });
128
+ }
@@ -0,0 +1,71 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { accountParam, runOrDiagnose } from './utils.js';
4
+
5
+ export function registerContactsTools(server: McpServer): void {
6
+ server.registerTool('gog_contacts_search', {
7
+ description: 'Search Google Contacts by name, email, or phone.',
8
+ annotations: { readOnlyHint: true },
9
+ inputSchema: {
10
+ query: z.string().describe('Search query (name, email, or phone)'),
11
+ account: accountParam,
12
+ },
13
+ }, async ({ query, account }) => {
14
+ return runOrDiagnose(['contacts', 'search', query], { account });
15
+ });
16
+
17
+ server.registerTool('gog_contacts_list', {
18
+ description: 'List all Google Contacts.',
19
+ annotations: { readOnlyHint: true },
20
+ inputSchema: {
21
+ account: accountParam,
22
+ },
23
+ }, async ({ account }) => {
24
+ return runOrDiagnose(['contacts', 'list'], { account });
25
+ });
26
+
27
+ server.registerTool('gog_contacts_get', {
28
+ description: 'Get a contact by resource name.',
29
+ annotations: { readOnlyHint: true },
30
+ inputSchema: {
31
+ resourceName: z.string().describe('Contact resource name (e.g. people/c12345)'),
32
+ account: accountParam,
33
+ },
34
+ }, async ({ resourceName, account }) => {
35
+ return runOrDiagnose(['contacts', 'get', resourceName], { account });
36
+ });
37
+
38
+ server.registerTool('gog_contacts_create', {
39
+ description: 'Create a new Google Contact.',
40
+ annotations: { destructiveHint: false },
41
+ inputSchema: {
42
+ givenName: z.string().describe('Given (first) name'),
43
+ familyName: z.string().optional().describe('Family (last) name'),
44
+ email: z.string().optional().describe('Email address'),
45
+ phone: z.string().optional().describe('Phone number'),
46
+ org: z.string().optional().describe('Organization/company name'),
47
+ title: z.string().optional().describe('Job title'),
48
+ account: accountParam,
49
+ },
50
+ }, async ({ givenName, familyName, email, phone, org, title, account }) => {
51
+ const args = ['contacts', 'create', `--given=${givenName}`];
52
+ if (familyName) args.push(`--family=${familyName}`);
53
+ if (email) args.push(`--email=${email}`);
54
+ if (phone) args.push(`--phone=${phone}`);
55
+ if (org) args.push(`--org=${org}`);
56
+ if (title) args.push(`--title=${title}`);
57
+ return runOrDiagnose(args, { account });
58
+ });
59
+
60
+ server.registerTool('gog_contacts_run', {
61
+ description: 'Run any gog contacts subcommand not covered by the other tools. Run `gog contacts --help` for the full list of subcommands, or `gog contacts <subcommand> --help` for flags on a specific subcommand.',
62
+ annotations: { destructiveHint: true },
63
+ inputSchema: {
64
+ subcommand: z.string().describe('The gog contacts subcommand to run, e.g. "update", "delete", "directory"'),
65
+ args: z.array(z.string()).describe('Additional positional args and flags'),
66
+ account: accountParam,
67
+ },
68
+ }, async ({ subcommand, args, account }) => {
69
+ return runOrDiagnose(['contacts', subcommand, ...args], { account });
70
+ });
71
+ }
@@ -0,0 +1,89 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { accountParam, runOrDiagnose } from './utils.js';
4
+
5
+ export function registerDocsTools(server: McpServer): void {
6
+ server.registerTool('gog_docs_info', {
7
+ description: 'Get Google Doc metadata: title, ID, and other properties.',
8
+ annotations: { readOnlyHint: true },
9
+ inputSchema: {
10
+ docId: z.string().describe('Doc ID (from the URL)'),
11
+ account: accountParam,
12
+ },
13
+ }, async ({ docId, account }) => {
14
+ return runOrDiagnose(['docs', 'info', docId], { account });
15
+ });
16
+
17
+ server.registerTool('gog_docs_cat', {
18
+ description: 'Read a Google Doc as plain text.',
19
+ annotations: { readOnlyHint: true },
20
+ inputSchema: {
21
+ docId: z.string().describe('Doc ID (from the URL)'),
22
+ account: accountParam,
23
+ },
24
+ }, async ({ docId, account }) => {
25
+ return runOrDiagnose(['docs', 'cat', docId], { account });
26
+ });
27
+
28
+ server.registerTool('gog_docs_create', {
29
+ description: 'Create a new Google Doc. Returns JSON with the new docId and URL.',
30
+ annotations: { destructiveHint: false },
31
+ inputSchema: {
32
+ title: z.string().describe('Title for the new document'),
33
+ account: accountParam,
34
+ },
35
+ }, async ({ title, account }) => {
36
+ return runOrDiagnose(['docs', 'create', title], { account });
37
+ });
38
+
39
+ server.registerTool('gog_docs_write', {
40
+ description: 'Write text content to a Google Doc, replacing existing body content by default. Set append=true to add after existing content.',
41
+ annotations: { destructiveHint: true },
42
+ inputSchema: {
43
+ docId: z.string().describe('Doc ID (from the URL)'),
44
+ text: z.string().describe('Text content to write'),
45
+ append: z.boolean().optional().describe('Append to existing content instead of replacing (default: false)'),
46
+ account: accountParam,
47
+ },
48
+ }, async ({ docId, text, append, account }) => {
49
+ const args = ['docs', 'write', docId, `--text=${text}`];
50
+ if (append) args.push('--append');
51
+ return runOrDiagnose(args, { account });
52
+ });
53
+
54
+ server.registerTool('gog_docs_find_replace', {
55
+ description: 'Find and replace text in a Google Doc.',
56
+ annotations: { destructiveHint: true },
57
+ inputSchema: {
58
+ docId: z.string().describe('Doc ID (from the URL)'),
59
+ find: z.string().describe('Text to find'),
60
+ replace: z.string().describe('Replacement text'),
61
+ account: accountParam,
62
+ },
63
+ }, async ({ docId, find, replace, account }) => {
64
+ return runOrDiagnose(['docs', 'find-replace', docId, find, replace], { account });
65
+ });
66
+
67
+ server.registerTool('gog_docs_structure', {
68
+ description: 'Show a Google Doc\'s structure with numbered paragraphs. Useful for understanding the document layout before making index-based edits.',
69
+ annotations: { readOnlyHint: true },
70
+ inputSchema: {
71
+ docId: z.string().describe('Doc ID (from the URL)'),
72
+ account: accountParam,
73
+ },
74
+ }, async ({ docId, account }) => {
75
+ return runOrDiagnose(['docs', 'structure', docId], { account });
76
+ });
77
+
78
+ server.registerTool('gog_docs_run', {
79
+ 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.',
80
+ annotations: { destructiveHint: true },
81
+ inputSchema: {
82
+ subcommand: z.string().describe('The gog docs subcommand to run, e.g. "copy", "clear", "insert", "sed", "export"'),
83
+ args: z.array(z.string()).describe('Additional positional args and flags'),
84
+ account: accountParam,
85
+ },
86
+ }, async ({ subcommand, args, account }) => {
87
+ return runOrDiagnose(['docs', subcommand, ...args], { account });
88
+ });
89
+ }