gogcli-mcp 4.2.4 → 4.3.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +714 -216
- package/dist/lib.js +751 -217
- package/manifest.json +9 -2
- package/mint.yaml +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/arg-guard.ts +68 -0
- package/src/argv.ts +27 -0
- package/src/attachment-root.ts +111 -0
- package/src/attachments.ts +1 -0
- package/src/blob-upload.ts +4 -2
- package/src/file-roots.ts +66 -0
- package/src/gmail-dispatch-guard.ts +56 -16
- package/src/gmail-results.ts +21 -2
- package/src/lib.ts +6 -0
- package/src/run-path-guard.ts +118 -0
- package/src/runner.ts +87 -19
- package/src/tools/api.ts +39 -7
- package/src/tools/appscript.ts +12 -8
- package/src/tools/auth.ts +13 -3
- package/src/tools/calendar.ts +10 -8
- package/src/tools/chat.ts +16 -13
- package/src/tools/classroom.ts +27 -25
- package/src/tools/contacts.ts +5 -3
- package/src/tools/docs.ts +8 -6
- package/src/tools/drive.ts +42 -18
- package/src/tools/gmail.ts +71 -11
- package/src/tools/sheets.ts +10 -8
- package/src/tools/slides.ts +14 -9
- package/src/tools/tasks.ts +7 -5
- package/src/tools/utils.ts +52 -6
- package/tests/arg-guard.test.ts +80 -0
- package/tests/attachment-root.test.ts +130 -0
- package/tests/attachments.test.ts +8 -0
- package/tests/blob-upload.test.ts +4 -3
- package/tests/file-roots.test.ts +101 -0
- package/tests/gmail-dispatch-guard.test.ts +46 -0
- package/tests/gmail-results.test.ts +35 -1
- package/tests/run-path-guard.test.ts +142 -0
- package/tests/runner.test.ts +136 -0
- package/tests/tools/api.test.ts +74 -8
- package/tests/tools/appscript.test.ts +34 -8
- package/tests/tools/auth.test.ts +44 -17
- package/tests/tools/calendar.test.ts +29 -27
- package/tests/tools/chat.test.ts +46 -20
- package/tests/tools/classroom.test.ts +39 -38
- package/tests/tools/contacts.test.ts +3 -2
- package/tests/tools/docs.test.ts +44 -15
- package/tests/tools/drive.test.ts +106 -19
- package/tests/tools/gmail.test.ts +227 -29
- package/tests/tools/run-tool-examples.test.ts +69 -0
- package/tests/tools/sheets.test.ts +16 -15
- package/tests/tools/slides.test.ts +47 -11
- package/tests/tools/tasks.test.ts +7 -6
- package/tests/tools/utils.test.ts +32 -31
- package/vitest.config.ts +5 -0
package/src/tools/chat.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from './utils.js';
|
|
10
10
|
import type { GogArg } from '../runner.js';
|
|
11
11
|
import { attachInlineParam, inlineAttachmentArgs } from '../attachments.js';
|
|
12
|
+
import { pos } from '../argv.js';
|
|
13
|
+
import { confinePaths } from '../file-roots.js';
|
|
12
14
|
|
|
13
15
|
// Google Chat (gog >= 0.38.0 for the mention/reaction metadata in
|
|
14
16
|
// `messages list`, >= 0.39.0 for `messages search`; the rest is older).
|
|
@@ -54,7 +56,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
54
56
|
account: accountParam,
|
|
55
57
|
}),
|
|
56
58
|
}, async ({ max, pageToken, page, all, account }) => {
|
|
57
|
-
const args = ['chat', 'spaces', 'list'];
|
|
59
|
+
const args: GogArg[] = ['chat', 'spaces', 'list'];
|
|
58
60
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
59
61
|
return runOrDiagnose(args, { account });
|
|
60
62
|
});
|
|
@@ -72,7 +74,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
72
74
|
account: accountParam,
|
|
73
75
|
}),
|
|
74
76
|
}, async ({ displayName, exact, max, account }) => {
|
|
75
|
-
const args = ['chat', 'spaces', 'find', displayName];
|
|
77
|
+
const args: GogArg[] = ['chat', 'spaces', 'find', pos(displayName)];
|
|
76
78
|
if (exact) args.push('--exact');
|
|
77
79
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
78
80
|
return runOrDiagnose(args, { account });
|
|
@@ -89,7 +91,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
89
91
|
account: accountParam,
|
|
90
92
|
}),
|
|
91
93
|
}, async ({ displayName, members, account }) => {
|
|
92
|
-
const args = ['chat', 'spaces', 'create', displayName];
|
|
94
|
+
const args: GogArg[] = ['chat', 'spaces', 'create', pos(displayName)];
|
|
93
95
|
if (members) for (const member of members) args.push(`--member=${member}`);
|
|
94
96
|
return runOrDiagnose(args, { account });
|
|
95
97
|
});
|
|
@@ -105,7 +107,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
105
107
|
account: accountParam,
|
|
106
108
|
}),
|
|
107
109
|
}, async ({ space, max, pageToken, page, all, account }) => {
|
|
108
|
-
const args = ['chat', 'threads', 'list', space];
|
|
110
|
+
const args: GogArg[] = ['chat', 'threads', 'list', pos(space)];
|
|
109
111
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
110
112
|
return runOrDiagnose(args, { account });
|
|
111
113
|
});
|
|
@@ -128,7 +130,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
128
130
|
account: accountParam,
|
|
129
131
|
}),
|
|
130
132
|
}, async ({ space, thread, unread, order, max, pageToken, page, all, account }) => {
|
|
131
|
-
const args = ['chat', 'messages', 'list', space];
|
|
133
|
+
const args: GogArg[] = ['chat', 'messages', 'list', pos(space)];
|
|
132
134
|
if (thread) args.push(`--thread=${thread}`);
|
|
133
135
|
if (unread) args.push('--unread');
|
|
134
136
|
if (order) args.push(`--order=${order}`);
|
|
@@ -163,7 +165,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
163
165
|
account: accountParam,
|
|
164
166
|
}),
|
|
165
167
|
}, async ({ query, order, view, markup, max, pageToken, page, all, account }) => {
|
|
166
|
-
const args = ['chat', 'messages', 'search', query];
|
|
168
|
+
const args: GogArg[] = ['chat', 'messages', 'search', pos(query)];
|
|
167
169
|
if (order) args.push(`--order=${order}`);
|
|
168
170
|
if (view) args.push(`--view=${view}`);
|
|
169
171
|
if (markup) args.push(`--markup=${markup}`);
|
|
@@ -184,16 +186,17 @@ export function registerChatTools(server: McpServer): void {
|
|
|
184
186
|
thread: threadParam,
|
|
185
187
|
attach: z.array(z.string()).optional().describe(
|
|
186
188
|
'Attachment file paths, read WHERE GOG RUNS. On a hosted or remote deployment that is not your machine — use '
|
|
187
|
-
+ 'attachInline there instead.',
|
|
189
|
+
+ 'attachInline there instead. Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).',
|
|
188
190
|
),
|
|
189
191
|
attachInline: attachInlineParam,
|
|
190
192
|
account: accountParam,
|
|
191
193
|
}),
|
|
192
194
|
}, async ({ space, text, thread, attach, attachInline, account }) => {
|
|
195
|
+
confinePaths(attach, 'attach');
|
|
193
196
|
if (text === undefined && !attach?.length && !attachInline?.length) {
|
|
194
197
|
throw new Error('A Chat message needs text, an attachment, or both.');
|
|
195
198
|
}
|
|
196
|
-
const args: GogArg[] = ['chat', 'messages', 'send', space];
|
|
199
|
+
const args: GogArg[] = ['chat', 'messages', 'send', pos(space)];
|
|
197
200
|
if (text !== undefined) args.push(`--text=${text}`);
|
|
198
201
|
if (thread) args.push(`--thread=${thread}`);
|
|
199
202
|
if (attach) for (const path of attach) args.push(`--attach=${path}`);
|
|
@@ -217,7 +220,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
217
220
|
account: accountParam,
|
|
218
221
|
}),
|
|
219
222
|
}, async ({ email, text, thread, account }) => {
|
|
220
|
-
const args = ['chat', 'dm', 'send', email, `--text=${text}`];
|
|
223
|
+
const args: GogArg[] = ['chat', 'dm', 'send', pos(email), `--text=${text}`];
|
|
221
224
|
if (thread) args.push(`--thread=${thread}`);
|
|
222
225
|
return runOrDiagnose(args, { account });
|
|
223
226
|
});
|
|
@@ -232,7 +235,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
232
235
|
account: accountParam,
|
|
233
236
|
}),
|
|
234
237
|
}, async ({ email, account }) => {
|
|
235
|
-
return runOrDiagnose(['chat', 'dm', 'space', email], { account });
|
|
238
|
+
return runOrDiagnose(['chat', 'dm', 'space', pos(email)], { account });
|
|
236
239
|
});
|
|
237
240
|
|
|
238
241
|
server.registerTool('gog_chat_reactions_list', {
|
|
@@ -248,7 +251,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
248
251
|
account: accountParam,
|
|
249
252
|
}),
|
|
250
253
|
}, async ({ message, space, max, pageToken, page, all, account }) => {
|
|
251
|
-
const args = ['chat', 'messages', 'reactions', 'list', message];
|
|
254
|
+
const args: GogArg[] = ['chat', 'messages', 'reactions', 'list', pos(message)];
|
|
252
255
|
if (space) args.push(`--space=${space}`);
|
|
253
256
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
254
257
|
return runOrDiagnose(args, { account });
|
|
@@ -266,7 +269,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
266
269
|
account: accountParam,
|
|
267
270
|
}),
|
|
268
271
|
}, async ({ message, emoji, space, account }) => {
|
|
269
|
-
const args = ['chat', 'messages', 'reactions', 'create', message, emoji];
|
|
272
|
+
const args: GogArg[] = ['chat', 'messages', 'reactions', 'create', pos(message), pos(emoji)];
|
|
270
273
|
if (space) args.push(`--space=${space}`);
|
|
271
274
|
return runOrDiagnose(args, { account });
|
|
272
275
|
});
|
|
@@ -282,7 +285,7 @@ export function registerChatTools(server: McpServer): void {
|
|
|
282
285
|
account: accountParam,
|
|
283
286
|
}),
|
|
284
287
|
}, async ({ reaction, account }) => {
|
|
285
|
-
return runOrDiagnose(['chat', 'messages', 'reactions', 'delete', reaction], { account });
|
|
288
|
+
return runOrDiagnose(['chat', 'messages', 'reactions', 'delete', pos(reaction)], { account });
|
|
286
289
|
});
|
|
287
290
|
|
|
288
291
|
registerRunTool(server, {
|
package/src/tools/classroom.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose, registerRunTool, pageTokenParam, pageAliasParam, resolvePageToken} from './utils.js';
|
|
4
|
+
import { pos } from '../argv.js';
|
|
5
|
+
import type { GogArg } from '../runner.js';
|
|
4
6
|
|
|
5
7
|
export function registerClassroomTools(server: McpServer): void {
|
|
6
8
|
server.registerTool('gog_classroom_courses_list', {
|
|
@@ -17,7 +19,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
17
19
|
account: accountParam,
|
|
18
20
|
}),
|
|
19
21
|
}, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
|
|
20
|
-
const args = ['classroom', 'courses', 'list'];
|
|
22
|
+
const args: GogArg[] = ['classroom', 'courses', 'list'];
|
|
21
23
|
if (state) args.push(`--state=${state}`);
|
|
22
24
|
if (teacher) args.push(`--teacher=${teacher}`);
|
|
23
25
|
if (student) args.push(`--student=${student}`);
|
|
@@ -36,7 +38,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
36
38
|
account: accountParam,
|
|
37
39
|
}),
|
|
38
40
|
}, async ({ courseId, account }) => {
|
|
39
|
-
return runOrDiagnose(['classroom', 'courses', 'get', courseId], { account });
|
|
41
|
+
return runOrDiagnose(['classroom', 'courses', 'get', pos(courseId)], { account });
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
server.registerTool('gog_classroom_students_list', {
|
|
@@ -51,7 +53,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
51
53
|
account: accountParam,
|
|
52
54
|
}),
|
|
53
55
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
54
|
-
const args = ['classroom', 'students', 'list', courseId];
|
|
56
|
+
const args: GogArg[] = ['classroom', 'students', 'list', pos(courseId)];
|
|
55
57
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
56
58
|
const token = resolvePageToken({ pageToken, page });
|
|
57
59
|
if (token) args.push(`--page=${token}`);
|
|
@@ -68,7 +70,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
68
70
|
account: accountParam,
|
|
69
71
|
}),
|
|
70
72
|
}, async ({ courseId, userId, account }) => {
|
|
71
|
-
return runOrDiagnose(['classroom', 'students', 'get', courseId, userId], { account });
|
|
73
|
+
return runOrDiagnose(['classroom', 'students', 'get', pos(courseId), pos(userId)], { account });
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
server.registerTool('gog_classroom_teachers_list', {
|
|
@@ -83,7 +85,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
83
85
|
account: accountParam,
|
|
84
86
|
}),
|
|
85
87
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
86
|
-
const args = ['classroom', 'teachers', 'list', courseId];
|
|
88
|
+
const args: GogArg[] = ['classroom', 'teachers', 'list', pos(courseId)];
|
|
87
89
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
88
90
|
const token = resolvePageToken({ pageToken, page });
|
|
89
91
|
if (token) args.push(`--page=${token}`);
|
|
@@ -100,7 +102,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
100
102
|
account: accountParam,
|
|
101
103
|
}),
|
|
102
104
|
}, async ({ courseId, userId, account }) => {
|
|
103
|
-
return runOrDiagnose(['classroom', 'teachers', 'get', courseId, userId], { account });
|
|
105
|
+
return runOrDiagnose(['classroom', 'teachers', 'get', pos(courseId), pos(userId)], { account });
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
server.registerTool('gog_classroom_roster', {
|
|
@@ -117,7 +119,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
117
119
|
account: accountParam,
|
|
118
120
|
}),
|
|
119
121
|
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
120
|
-
const args = ['classroom', 'roster', courseId];
|
|
122
|
+
const args: GogArg[] = ['classroom', 'roster', pos(courseId)];
|
|
121
123
|
if (students) args.push('--students');
|
|
122
124
|
if (teachers) args.push('--teachers');
|
|
123
125
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -143,7 +145,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
143
145
|
account: accountParam,
|
|
144
146
|
}),
|
|
145
147
|
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
146
|
-
const args = ['classroom', 'coursework', 'list', courseId];
|
|
148
|
+
const args: GogArg[] = ['classroom', 'coursework', 'list', pos(courseId)];
|
|
147
149
|
if (state) args.push(`--state=${state}`);
|
|
148
150
|
if (topic) args.push(`--topic=${topic}`);
|
|
149
151
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
@@ -164,7 +166,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
164
166
|
account: accountParam,
|
|
165
167
|
}),
|
|
166
168
|
}, async ({ courseId, courseworkId, account }) => {
|
|
167
|
-
return runOrDiagnose(['classroom', 'coursework', 'get', courseId, courseworkId], { account });
|
|
169
|
+
return runOrDiagnose(['classroom', 'coursework', 'get', pos(courseId), pos(courseworkId)], { account });
|
|
168
170
|
});
|
|
169
171
|
|
|
170
172
|
server.registerTool('gog_classroom_submissions_list', {
|
|
@@ -183,7 +185,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
183
185
|
account: accountParam,
|
|
184
186
|
}),
|
|
185
187
|
}, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
|
|
186
|
-
const args = ['classroom', 'submissions', 'list', courseId, courseworkId];
|
|
188
|
+
const args: GogArg[] = ['classroom', 'submissions', 'list', pos(courseId), pos(courseworkId)];
|
|
187
189
|
if (state) args.push(`--state=${state}`);
|
|
188
190
|
if (late) args.push(`--late=${late}`);
|
|
189
191
|
if (user) args.push(`--user=${user}`);
|
|
@@ -204,7 +206,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
204
206
|
account: accountParam,
|
|
205
207
|
}),
|
|
206
208
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
207
|
-
return runOrDiagnose(['classroom', 'submissions', 'get', courseId, courseworkId, submissionId], { account });
|
|
209
|
+
return runOrDiagnose(['classroom', 'submissions', 'get', pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
208
210
|
});
|
|
209
211
|
|
|
210
212
|
server.registerTool('gog_classroom_submissions_grade', {
|
|
@@ -219,7 +221,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
219
221
|
account: accountParam,
|
|
220
222
|
}),
|
|
221
223
|
}, async ({ courseId, courseworkId, submissionId, draft, assigned, account }) => {
|
|
222
|
-
const args = ['classroom', 'submissions', 'grade', courseId, courseworkId, submissionId];
|
|
224
|
+
const args: GogArg[] = ['classroom', 'submissions', 'grade', pos(courseId), pos(courseworkId), pos(submissionId)];
|
|
223
225
|
if (draft) args.push(`--draft=${draft}`);
|
|
224
226
|
if (assigned) args.push(`--assigned=${assigned}`);
|
|
225
227
|
return runOrDiagnose(args, { account });
|
|
@@ -235,7 +237,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
235
237
|
account: accountParam,
|
|
236
238
|
}),
|
|
237
239
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
238
|
-
return runOrDiagnose(['classroom', 'submissions', 'return', courseId, courseworkId, submissionId], { account });
|
|
240
|
+
return runOrDiagnose(['classroom', 'submissions', 'return', pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
239
241
|
});
|
|
240
242
|
|
|
241
243
|
server.registerTool('gog_classroom_submissions_turn_in', {
|
|
@@ -248,7 +250,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
248
250
|
account: accountParam,
|
|
249
251
|
}),
|
|
250
252
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
251
|
-
return runOrDiagnose(['classroom', 'submissions', 'turn-in', courseId, courseworkId, submissionId], { account });
|
|
253
|
+
return runOrDiagnose(['classroom', 'submissions', 'turn-in', pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
252
254
|
});
|
|
253
255
|
|
|
254
256
|
server.registerTool('gog_classroom_submissions_reclaim', {
|
|
@@ -261,7 +263,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
261
263
|
account: accountParam,
|
|
262
264
|
}),
|
|
263
265
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
264
|
-
return runOrDiagnose(['classroom', 'submissions', 'reclaim', courseId, courseworkId, submissionId], { account });
|
|
266
|
+
return runOrDiagnose(['classroom', 'submissions', 'reclaim', pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
265
267
|
});
|
|
266
268
|
|
|
267
269
|
server.registerTool('gog_classroom_announcements_list', {
|
|
@@ -278,7 +280,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
278
280
|
account: accountParam,
|
|
279
281
|
}),
|
|
280
282
|
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
281
|
-
const args = ['classroom', 'announcements', 'list', courseId];
|
|
283
|
+
const args: GogArg[] = ['classroom', 'announcements', 'list', pos(courseId)];
|
|
282
284
|
if (state) args.push(`--state=${state}`);
|
|
283
285
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
284
286
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -297,7 +299,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
297
299
|
account: accountParam,
|
|
298
300
|
}),
|
|
299
301
|
}, async ({ courseId, announcementId, account }) => {
|
|
300
|
-
return runOrDiagnose(['classroom', 'announcements', 'get', courseId, announcementId], { account });
|
|
302
|
+
return runOrDiagnose(['classroom', 'announcements', 'get', pos(courseId), pos(announcementId)], { account });
|
|
301
303
|
});
|
|
302
304
|
|
|
303
305
|
server.registerTool('gog_classroom_announcements_create', {
|
|
@@ -311,7 +313,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
311
313
|
account: accountParam,
|
|
312
314
|
}),
|
|
313
315
|
}, async ({ courseId, text, state, scheduled, account }) => {
|
|
314
|
-
const args = ['classroom', 'announcements', 'create', courseId, `--text=${text}`];
|
|
316
|
+
const args: GogArg[] = ['classroom', 'announcements', 'create', pos(courseId), `--text=${text}`];
|
|
315
317
|
if (state) args.push(`--state=${state}`);
|
|
316
318
|
if (scheduled) args.push(`--scheduled=${scheduled}`);
|
|
317
319
|
return runOrDiagnose(args, { account });
|
|
@@ -329,7 +331,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
329
331
|
account: accountParam,
|
|
330
332
|
}),
|
|
331
333
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
332
|
-
const args = ['classroom', 'topics', 'list', courseId];
|
|
334
|
+
const args: GogArg[] = ['classroom', 'topics', 'list', pos(courseId)];
|
|
333
335
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
334
336
|
const token = resolvePageToken({ pageToken, page });
|
|
335
337
|
if (token) args.push(`--page=${token}`);
|
|
@@ -346,7 +348,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
346
348
|
account: accountParam,
|
|
347
349
|
}),
|
|
348
350
|
}, async ({ courseId, topicId, account }) => {
|
|
349
|
-
return runOrDiagnose(['classroom', 'topics', 'get', courseId, topicId], { account });
|
|
351
|
+
return runOrDiagnose(['classroom', 'topics', 'get', pos(courseId), pos(topicId)], { account });
|
|
350
352
|
});
|
|
351
353
|
|
|
352
354
|
server.registerTool('gog_classroom_invitations_list', {
|
|
@@ -362,7 +364,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
362
364
|
account: accountParam,
|
|
363
365
|
}),
|
|
364
366
|
}, async ({ course, user, max, pageToken, page, all, account }) => {
|
|
365
|
-
const args = ['classroom', 'invitations', 'list'];
|
|
367
|
+
const args: GogArg[] = ['classroom', 'invitations', 'list'];
|
|
366
368
|
if (course) args.push(`--course=${course}`);
|
|
367
369
|
if (user) args.push(`--user=${user}`);
|
|
368
370
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -380,7 +382,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
380
382
|
account: accountParam,
|
|
381
383
|
}),
|
|
382
384
|
}, async ({ invitationId, account }) => {
|
|
383
|
-
return runOrDiagnose(['classroom', 'invitations', 'get', invitationId], { account });
|
|
385
|
+
return runOrDiagnose(['classroom', 'invitations', 'get', pos(invitationId)], { account });
|
|
384
386
|
});
|
|
385
387
|
|
|
386
388
|
server.registerTool('gog_classroom_invitations_accept', {
|
|
@@ -391,7 +393,7 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
391
393
|
account: accountParam,
|
|
392
394
|
}),
|
|
393
395
|
}, async ({ invitationId, account }) => {
|
|
394
|
-
return runOrDiagnose(['classroom', 'invitations', 'accept', invitationId], { account });
|
|
396
|
+
return runOrDiagnose(['classroom', 'invitations', 'accept', pos(invitationId)], { account });
|
|
395
397
|
});
|
|
396
398
|
|
|
397
399
|
server.registerTool('gog_classroom_profile_get', {
|
|
@@ -402,8 +404,8 @@ export function registerClassroomTools(server: McpServer): void {
|
|
|
402
404
|
account: accountParam,
|
|
403
405
|
}),
|
|
404
406
|
}, async ({ userId, account }) => {
|
|
405
|
-
const args = ['classroom', 'profile', 'get'];
|
|
406
|
-
if (userId) args.push(userId);
|
|
407
|
+
const args: GogArg[] = ['classroom', 'profile', 'get'];
|
|
408
|
+
if (userId) args.push(pos(userId));
|
|
407
409
|
return runOrDiagnose(args, { account });
|
|
408
410
|
});
|
|
409
411
|
|
package/src/tools/contacts.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose, registerRunTool } from './utils.js';
|
|
4
|
+
import { pos } from '../argv.js';
|
|
5
|
+
import type { GogArg } from '../runner.js';
|
|
4
6
|
|
|
5
7
|
export function registerContactsTools(server: McpServer): void {
|
|
6
8
|
server.registerTool('gog_contacts_search', {
|
|
@@ -11,7 +13,7 @@ export function registerContactsTools(server: McpServer): void {
|
|
|
11
13
|
account: accountParam,
|
|
12
14
|
}),
|
|
13
15
|
}, async ({ query, account }) => {
|
|
14
|
-
return runOrDiagnose(['contacts', 'search', query], { account });
|
|
16
|
+
return runOrDiagnose(['contacts', 'search', pos(query)], { account });
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
server.registerTool('gog_contacts_list', {
|
|
@@ -32,7 +34,7 @@ export function registerContactsTools(server: McpServer): void {
|
|
|
32
34
|
account: accountParam,
|
|
33
35
|
}),
|
|
34
36
|
}, async ({ resourceName, account }) => {
|
|
35
|
-
return runOrDiagnose(['contacts', 'get', resourceName], { account });
|
|
37
|
+
return runOrDiagnose(['contacts', 'get', pos(resourceName)], { account });
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
server.registerTool('gog_contacts_create', {
|
|
@@ -48,7 +50,7 @@ export function registerContactsTools(server: McpServer): void {
|
|
|
48
50
|
account: accountParam,
|
|
49
51
|
}),
|
|
50
52
|
}, async ({ givenName, familyName, email, phone, org, title, account }) => {
|
|
51
|
-
const args = ['contacts', 'create', `--given=${givenName}`];
|
|
53
|
+
const args: GogArg[] = ['contacts', 'create', `--given=${givenName}`];
|
|
52
54
|
if (familyName) args.push(`--family=${familyName}`);
|
|
53
55
|
if (email) args.push(`--email=${email}`);
|
|
54
56
|
if (phone) args.push(`--phone=${phone}`);
|
package/src/tools/docs.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose, registerRunTool } from './utils.js';
|
|
4
|
+
import { pos } from '../argv.js';
|
|
5
|
+
import type { GogArg } from '../runner.js';
|
|
4
6
|
|
|
5
7
|
export function registerDocsTools(server: McpServer): void {
|
|
6
8
|
server.registerTool('gog_docs_info', {
|
|
@@ -11,7 +13,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
11
13
|
account: accountParam,
|
|
12
14
|
}),
|
|
13
15
|
}, async ({ docId, account }) => {
|
|
14
|
-
return runOrDiagnose(['docs', 'info', docId], { account });
|
|
16
|
+
return runOrDiagnose(['docs', 'info', pos(docId)], { account });
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
server.registerTool('gog_docs_cat', {
|
|
@@ -23,7 +25,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
23
25
|
account: accountParam,
|
|
24
26
|
}),
|
|
25
27
|
}, async ({ docId, chips, account }) => {
|
|
26
|
-
const args = ['docs', 'cat', docId];
|
|
28
|
+
const args: GogArg[] = ['docs', 'cat', pos(docId)];
|
|
27
29
|
if (chips) args.push('--chips');
|
|
28
30
|
return runOrDiagnose(args, { account });
|
|
29
31
|
});
|
|
@@ -36,7 +38,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
36
38
|
account: accountParam,
|
|
37
39
|
}),
|
|
38
40
|
}, async ({ title, account }) => {
|
|
39
|
-
return runOrDiagnose(['docs', 'create', title], { account });
|
|
41
|
+
return runOrDiagnose(['docs', 'create', pos(title)], { account });
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
server.registerTool('gog_docs_write', {
|
|
@@ -62,7 +64,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
62
64
|
account: accountParam,
|
|
63
65
|
}),
|
|
64
66
|
}, async ({ docId, text, append, checkOrphans, bullets, bulletPreset, ordered, noBullets, indentStart, indentEnd, indentFirstLine, spaceAbove, spaceBelow, keepLinesTogether, keepWithNext, batch, account }) => {
|
|
65
|
-
const args = ['docs', 'write', docId, `--text=${text}`];
|
|
67
|
+
const args: GogArg[] = ['docs', 'write', pos(docId), `--text=${text}`];
|
|
66
68
|
if (append) args.push('--append');
|
|
67
69
|
if (checkOrphans) args.push('--check-orphans');
|
|
68
70
|
if (bullets) args.push('--bullets');
|
|
@@ -90,7 +92,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
90
92
|
account: accountParam,
|
|
91
93
|
}),
|
|
92
94
|
}, async ({ docId, find, replace, account }) => {
|
|
93
|
-
return runOrDiagnose(['docs', 'find-replace', docId, find, replace], { account });
|
|
95
|
+
return runOrDiagnose(['docs', 'find-replace', pos(docId), pos(find), pos(replace)], { account });
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
server.registerTool('gog_docs_structure', {
|
|
@@ -101,7 +103,7 @@ export function registerDocsTools(server: McpServer): void {
|
|
|
101
103
|
account: accountParam,
|
|
102
104
|
}),
|
|
103
105
|
}, async ({ docId, account }) => {
|
|
104
|
-
return runOrDiagnose(['docs', 'structure', docId], { account });
|
|
106
|
+
return runOrDiagnose(['docs', 'structure', pos(docId)], { account });
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
registerRunTool(server, { service: 'docs', examples: '"copy", "clear", "insert", "sed", "export"' });
|
package/src/tools/drive.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { CallToolResult } from '@modelcontextprotocol/server';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { rawTextResult, viewParam, resolveView } from '@chrischall/mcp-utils';
|
|
4
|
+
import { errorResult, rawTextResult, viewParam, resolveView } from '@chrischall/mcp-utils';
|
|
5
|
+
import { MAX_INLINE_ATTACHMENT_BYTES } from '../attachments.js';
|
|
5
6
|
|
|
6
7
|
import { run, runBinary } from '../runner.js';
|
|
7
8
|
import { accountParam, diagnose, runOrDiagnose, registerRunTool, pageTokenParam, pageAliasParam, resolvePageToken} from './utils.js';
|
|
9
|
+
import { pos } from '../argv.js';
|
|
10
|
+
import type { GogArg } from '../runner.js';
|
|
8
11
|
|
|
9
12
|
// A native Google Doc exports to text directly; anything else (PDF, image,
|
|
10
13
|
// docx, …) is first copied WITH conversion to this type, which makes Drive run
|
|
@@ -13,12 +16,21 @@ const GOOGLE_DOC_MIME = 'application/vnd.google-apps.document';
|
|
|
13
16
|
|
|
14
17
|
// Parse `gog drive get` JSON into { name, mimeType }. gog nests the payload
|
|
15
18
|
// under `file`; fall back to the top level if that ever changes.
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
type DriveMeta = { name?: string; mimeType?: string; size?: string | number };
|
|
20
|
+
|
|
21
|
+
function fileMeta(raw: string): { name?: string; mimeType?: string; size?: number } {
|
|
22
|
+
const parsed = JSON.parse(raw) as { file?: DriveMeta } & DriveMeta;
|
|
18
23
|
const f = parsed.file ?? parsed;
|
|
19
|
-
|
|
24
|
+
// Drive reports int64 fields as strings; native Google formats carry none.
|
|
25
|
+
const size = f.size === undefined ? undefined : Number(f.size);
|
|
26
|
+
return { name: f.name, mimeType: f.mimeType, size: Number.isFinite(size) ? size : undefined };
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
// The most gog_drive_read_bytes returns inline — the same 8 MiB per-file cap
|
|
30
|
+
// the attachment path enforces. The bytes travel base64-encoded (4/3 larger)
|
|
31
|
+
// inside one JSON-RPC message, and past this no MCP client accepts the result.
|
|
32
|
+
export const MAX_DRIVE_READ_BYTES = MAX_INLINE_ATTACHMENT_BYTES;
|
|
33
|
+
|
|
22
34
|
|
|
23
35
|
// The `compact` rung for gog_drive_ls, as a Google Drive field mask.
|
|
24
36
|
//
|
|
@@ -52,7 +64,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
52
64
|
account: accountParam,
|
|
53
65
|
}),
|
|
54
66
|
}, async ({ folderId, max, pageToken, page, query, allDrives, view, account }) => {
|
|
55
|
-
const args = ['drive', 'ls'];
|
|
67
|
+
const args: GogArg[] = ['drive', 'ls'];
|
|
56
68
|
if (folderId) args.push(`--parent=${folderId}`);
|
|
57
69
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
58
70
|
const token = resolvePageToken({ pageToken, page });
|
|
@@ -78,7 +90,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
78
90
|
account: accountParam,
|
|
79
91
|
}),
|
|
80
92
|
}, async ({ query, view, account }) => {
|
|
81
|
-
return runOrDiagnose(['drive', 'search', query], {
|
|
93
|
+
return runOrDiagnose(['drive', 'search', pos(query)], {
|
|
82
94
|
account,
|
|
83
95
|
stripMedia: resolveView(view, ['compact', 'full']) === 'compact',
|
|
84
96
|
});
|
|
@@ -99,7 +111,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
99
111
|
account: accountParam,
|
|
100
112
|
}),
|
|
101
113
|
}, async ({ fileId, view, account }) => {
|
|
102
|
-
return runOrDiagnose(['drive', 'get', fileId], {
|
|
114
|
+
return runOrDiagnose(['drive', 'get', pos(fileId)], {
|
|
103
115
|
account,
|
|
104
116
|
stripMedia: resolveView(view, ['compact', 'full']) === 'compact',
|
|
105
117
|
});
|
|
@@ -113,7 +125,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
113
125
|
account: accountParam,
|
|
114
126
|
}),
|
|
115
127
|
}, async ({ name, account }) => {
|
|
116
|
-
return runOrDiagnose(['drive', 'mkdir', name], { account });
|
|
128
|
+
return runOrDiagnose(['drive', 'mkdir', pos(name)], { account });
|
|
117
129
|
});
|
|
118
130
|
|
|
119
131
|
server.registerTool('gog_drive_rename', {
|
|
@@ -125,7 +137,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
125
137
|
account: accountParam,
|
|
126
138
|
}),
|
|
127
139
|
}, async ({ fileId, newName, account }) => {
|
|
128
|
-
return runOrDiagnose(['drive', 'rename', fileId, newName], { account });
|
|
140
|
+
return runOrDiagnose(['drive', 'rename', pos(fileId), pos(newName)], { account });
|
|
129
141
|
});
|
|
130
142
|
|
|
131
143
|
server.registerTool('gog_drive_move', {
|
|
@@ -137,7 +149,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
137
149
|
account: accountParam,
|
|
138
150
|
}),
|
|
139
151
|
}, async ({ fileId, parentId, account }) => {
|
|
140
|
-
return runOrDiagnose(['drive', 'move', fileId, `--parent=${parentId}`], { account });
|
|
152
|
+
return runOrDiagnose(['drive', 'move', pos(fileId), `--parent=${parentId}`], { account });
|
|
141
153
|
});
|
|
142
154
|
|
|
143
155
|
server.registerTool('gog_drive_delete', {
|
|
@@ -149,7 +161,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
149
161
|
account: accountParam,
|
|
150
162
|
}),
|
|
151
163
|
}, async ({ fileId, permanent, account }) => {
|
|
152
|
-
const args = ['drive', 'delete', fileId];
|
|
164
|
+
const args: GogArg[] = ['drive', 'delete', pos(fileId)];
|
|
153
165
|
if (permanent) args.push('--permanent');
|
|
154
166
|
// gog gates drive delete behind a confirmation; the runner injects
|
|
155
167
|
// --no-input, so without --force it refuses at runtime.
|
|
@@ -169,7 +181,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
169
181
|
account: accountParam,
|
|
170
182
|
}),
|
|
171
183
|
}, async ({ fileId, to, email, domain, role, account }) => {
|
|
172
|
-
const args = ['drive', 'share', fileId, `--to=${to}`];
|
|
184
|
+
const args: GogArg[] = ['drive', 'share', pos(fileId), `--to=${to}`];
|
|
173
185
|
if (email) args.push(`--email=${email}`);
|
|
174
186
|
if (domain) args.push(`--domain=${domain}`);
|
|
175
187
|
if (role) args.push(`--role=${role}`);
|
|
@@ -201,7 +213,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
201
213
|
}, async ({ fileId, ocrLanguage, offset = 0, maxChars, account }) => {
|
|
202
214
|
let tempDocId: string | undefined;
|
|
203
215
|
try {
|
|
204
|
-
const { name, mimeType } = fileMeta(await run(['drive', 'get', fileId], { account }));
|
|
216
|
+
const { name, mimeType } = fileMeta(await run(['drive', 'get', pos(fileId)], { account }));
|
|
205
217
|
|
|
206
218
|
// Native Docs export straight to text; everything else is OCR-converted first.
|
|
207
219
|
let sourceId = fileId;
|
|
@@ -242,7 +254,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
242
254
|
} finally {
|
|
243
255
|
// Always remove the temp Doc — on success, and on a mid-extraction failure.
|
|
244
256
|
if (tempDocId) {
|
|
245
|
-
await run(['drive', 'delete', tempDocId, '--permanent', '--force'], { account }).catch(() => {});
|
|
257
|
+
await run(['drive', 'delete', pos(tempDocId), '--permanent', '--force'], { account }).catch(() => {});
|
|
246
258
|
}
|
|
247
259
|
}
|
|
248
260
|
});
|
|
@@ -251,7 +263,7 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
251
263
|
description:
|
|
252
264
|
'Fetch a Drive file\'s raw bytes and return them base64-encoded as an embedded resource — the ' +
|
|
253
265
|
'generic fallback for callers that want the file itself (to parse locally) rather than extracted ' +
|
|
254
|
-
'text. For readable text from a PDF, prefer gog_drive_extract_text.',
|
|
266
|
+
'text. Files over 8 MiB are refused (checked before downloading). For readable text from a PDF, prefer gog_drive_extract_text.',
|
|
255
267
|
annotations: { readOnlyHint: true },
|
|
256
268
|
inputSchema: z.object({
|
|
257
269
|
fileId: z.string().describe('Drive file ID'),
|
|
@@ -259,9 +271,21 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
259
271
|
}),
|
|
260
272
|
}, async ({ fileId, account }): Promise<CallToolResult> => {
|
|
261
273
|
try {
|
|
262
|
-
const { name, mimeType } = fileMeta(await run(['drive', 'get', fileId], { account }));
|
|
274
|
+
const { name, mimeType, size } = fileMeta(await run(['drive', 'get', pos(fileId)], { account }));
|
|
275
|
+
// Refuse an oversized file BEFORE fetching it (audit BUG-2): buffering a
|
|
276
|
+
// multi-GB file plus its base64 copy can take down the whole server.
|
|
277
|
+
if (size !== undefined && size > MAX_DRIVE_READ_BYTES) {
|
|
278
|
+
return errorResult(
|
|
279
|
+
`${name ?? fileId} is ${size} bytes, over the 8 MiB this tool returns inline. `
|
|
280
|
+
+ 'Use gog_drive_extract_text for its text, or share its webViewLink (gog_drive_get) instead.',
|
|
281
|
+
);
|
|
282
|
+
}
|
|
263
283
|
const params = JSON.stringify({ fileId, alt: 'media' });
|
|
264
|
-
|
|
284
|
+
// The cap also bounds the fetch itself, for files Drive reports no size for.
|
|
285
|
+
const blob = await runBinary(
|
|
286
|
+
['api', 'call', 'drive', 'v3', 'files.get', `--params=${params}`],
|
|
287
|
+
{ account, maxOutputBytes: MAX_DRIVE_READ_BYTES },
|
|
288
|
+
);
|
|
265
289
|
const type = mimeType ?? 'application/octet-stream';
|
|
266
290
|
// Several hosts (claude.ai among them) render embedded IMAGE resources
|
|
267
291
|
// and reject every other type outright — "Resources of type
|
|
@@ -290,5 +314,5 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
290
314
|
}
|
|
291
315
|
});
|
|
292
316
|
|
|
293
|
-
registerRunTool(server, { service: 'drive', examples: '"copy", "
|
|
317
|
+
registerRunTool(server, { service: 'drive', examples: '"copy", "download", "permissions"' });
|
|
294
318
|
}
|