mcp-google-multi 4.2.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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTasksTools(server: McpServer): void;
@@ -0,0 +1,316 @@
1
+ import { z } from 'zod';
2
+ import { google } from 'googleapis';
3
+ import { ACCOUNTS } from '../accounts.js';
4
+ import { getClient } from '../client.js';
5
+ import { handleGoogleApiError } from './_errors.js';
6
+ const accountEnum = z.enum(ACCOUNTS);
7
+ export function registerTasksTools(server) {
8
+ // ─── Tasklists ─────────────────────────────────────────────────────────
9
+ server.registerTool('tasks_lists_list', {
10
+ description: 'List all tasklists (the user\'s top-level task containers)',
11
+ inputSchema: {
12
+ account: accountEnum.describe('Google account alias'),
13
+ maxResults: z.number().min(1).max(100).optional(),
14
+ pageToken: z.string().optional(),
15
+ },
16
+ }, async ({ account, maxResults, pageToken }) => {
17
+ try {
18
+ const auth = await getClient(account);
19
+ const tasks = google.tasks({ version: 'v1', auth });
20
+ const res = await tasks.tasklists.list({
21
+ maxResults: maxResults ?? 100,
22
+ pageToken,
23
+ });
24
+ return {
25
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
26
+ };
27
+ }
28
+ catch (error) {
29
+ return handleTasksError(error, account);
30
+ }
31
+ });
32
+ server.registerTool('tasks_list_get', {
33
+ description: 'Get a single tasklist by ID',
34
+ inputSchema: {
35
+ account: accountEnum.describe('Google account alias'),
36
+ tasklistId: z.string().describe('Tasklist ID'),
37
+ },
38
+ }, async ({ account, tasklistId }) => {
39
+ try {
40
+ const auth = await getClient(account);
41
+ const tasks = google.tasks({ version: 'v1', auth });
42
+ const res = await tasks.tasklists.get({ tasklist: tasklistId });
43
+ return {
44
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
45
+ };
46
+ }
47
+ catch (error) {
48
+ return handleTasksError(error, account);
49
+ }
50
+ });
51
+ server.registerTool('tasks_list_insert', {
52
+ description: 'Create a new tasklist',
53
+ inputSchema: {
54
+ account: accountEnum.describe('Google account alias'),
55
+ title: z.string().describe('Tasklist title'),
56
+ },
57
+ }, async ({ account, title }) => {
58
+ try {
59
+ const auth = await getClient(account);
60
+ const tasks = google.tasks({ version: 'v1', auth });
61
+ const res = await tasks.tasklists.insert({
62
+ requestBody: { title },
63
+ });
64
+ return {
65
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
66
+ };
67
+ }
68
+ catch (error) {
69
+ return handleTasksError(error, account);
70
+ }
71
+ });
72
+ server.registerTool('tasks_list_update', {
73
+ description: 'Rename a tasklist (PATCH semantics)',
74
+ inputSchema: {
75
+ account: accountEnum.describe('Google account alias'),
76
+ tasklistId: z.string().describe('Tasklist ID'),
77
+ title: z.string().describe('New title'),
78
+ },
79
+ }, async ({ account, tasklistId, title }) => {
80
+ try {
81
+ const auth = await getClient(account);
82
+ const tasks = google.tasks({ version: 'v1', auth });
83
+ const res = await tasks.tasklists.patch({
84
+ tasklist: tasklistId,
85
+ requestBody: { title },
86
+ });
87
+ return {
88
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
89
+ };
90
+ }
91
+ catch (error) {
92
+ return handleTasksError(error, account);
93
+ }
94
+ });
95
+ server.registerTool('tasks_list_delete', {
96
+ description: 'Delete a tasklist and every task inside it',
97
+ inputSchema: {
98
+ account: accountEnum.describe('Google account alias'),
99
+ tasklistId: z.string().describe('Tasklist ID'),
100
+ },
101
+ }, async ({ account, tasklistId }) => {
102
+ try {
103
+ const auth = await getClient(account);
104
+ const tasks = google.tasks({ version: 'v1', auth });
105
+ await tasks.tasklists.delete({ tasklist: tasklistId });
106
+ return {
107
+ content: [{ type: 'text', text: JSON.stringify({ deleted: true, tasklistId }, null, 2) }],
108
+ };
109
+ }
110
+ catch (error) {
111
+ return handleTasksError(error, account);
112
+ }
113
+ });
114
+ // ─── Tasks ─────────────────────────────────────────────────────────────
115
+ server.registerTool('tasks_list', {
116
+ description: 'List tasks within a tasklist with rich filters',
117
+ inputSchema: {
118
+ account: accountEnum.describe('Google account alias'),
119
+ tasklistId: z.string().describe('Tasklist ID'),
120
+ maxResults: z.number().min(1).max(100).optional(),
121
+ pageToken: z.string().optional(),
122
+ showCompleted: z.boolean().optional().describe('Include completed tasks (default: true)'),
123
+ showDeleted: z.boolean().optional(),
124
+ showHidden: z.boolean().optional(),
125
+ showAssigned: z.boolean().optional(),
126
+ completedMax: z.string().optional().describe('RFC 3339 upper bound on completion date'),
127
+ completedMin: z.string().optional(),
128
+ dueMax: z.string().optional(),
129
+ dueMin: z.string().optional(),
130
+ updatedMin: z.string().optional(),
131
+ },
132
+ }, async ({ account, tasklistId, ...params }) => {
133
+ try {
134
+ const auth = await getClient(account);
135
+ const tasks = google.tasks({ version: 'v1', auth });
136
+ const res = await tasks.tasks.list({
137
+ tasklist: tasklistId,
138
+ ...params,
139
+ });
140
+ return {
141
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
142
+ };
143
+ }
144
+ catch (error) {
145
+ return handleTasksError(error, account);
146
+ }
147
+ });
148
+ server.registerTool('tasks_get', {
149
+ description: 'Get a single task',
150
+ inputSchema: {
151
+ account: accountEnum.describe('Google account alias'),
152
+ tasklistId: z.string().describe('Tasklist ID'),
153
+ taskId: z.string().describe('Task ID'),
154
+ },
155
+ }, async ({ account, tasklistId, taskId }) => {
156
+ try {
157
+ const auth = await getClient(account);
158
+ const tasks = google.tasks({ version: 'v1', auth });
159
+ const res = await tasks.tasks.get({ tasklist: tasklistId, task: taskId });
160
+ return {
161
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
162
+ };
163
+ }
164
+ catch (error) {
165
+ return handleTasksError(error, account);
166
+ }
167
+ });
168
+ server.registerTool('tasks_insert', {
169
+ description: 'Create a new task. Use parent to nest under another task, previous to position after a sibling.',
170
+ inputSchema: {
171
+ account: accountEnum.describe('Google account alias'),
172
+ tasklistId: z.string().describe('Tasklist ID'),
173
+ title: z.string().describe('Task title'),
174
+ notes: z.string().optional().describe('Task description'),
175
+ due: z.string().optional().describe('Due date in RFC 3339 (e.g. 2026-06-15T00:00:00.000Z)'),
176
+ status: z.enum(['needsAction', 'completed']).optional(),
177
+ parent: z.string().optional().describe('Parent task ID for nesting'),
178
+ previous: z.string().optional().describe('Position this task immediately after this sibling task ID'),
179
+ },
180
+ }, async ({ account, tasklistId, title, notes, due, status, parent, previous }) => {
181
+ try {
182
+ const auth = await getClient(account);
183
+ const tasks = google.tasks({ version: 'v1', auth });
184
+ const requestBody = { title };
185
+ if (notes !== undefined)
186
+ requestBody.notes = notes;
187
+ if (due !== undefined)
188
+ requestBody.due = due;
189
+ if (status !== undefined)
190
+ requestBody.status = status;
191
+ const res = await tasks.tasks.insert({
192
+ tasklist: tasklistId,
193
+ parent,
194
+ previous,
195
+ requestBody,
196
+ });
197
+ return {
198
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
199
+ };
200
+ }
201
+ catch (error) {
202
+ return handleTasksError(error, account);
203
+ }
204
+ });
205
+ server.registerTool('tasks_update', {
206
+ description: 'Update a task (PATCH semantics — only supplied fields change)',
207
+ inputSchema: {
208
+ account: accountEnum.describe('Google account alias'),
209
+ tasklistId: z.string().describe('Tasklist ID'),
210
+ taskId: z.string().describe('Task ID'),
211
+ title: z.string().optional(),
212
+ notes: z.string().optional(),
213
+ due: z.string().optional().describe('RFC 3339'),
214
+ status: z.enum(['needsAction', 'completed']).optional(),
215
+ },
216
+ }, async ({ account, tasklistId, taskId, title, notes, due, status }) => {
217
+ try {
218
+ const auth = await getClient(account);
219
+ const tasks = google.tasks({ version: 'v1', auth });
220
+ const requestBody = {};
221
+ if (title !== undefined)
222
+ requestBody.title = title;
223
+ if (notes !== undefined)
224
+ requestBody.notes = notes;
225
+ if (due !== undefined)
226
+ requestBody.due = due;
227
+ if (status !== undefined)
228
+ requestBody.status = status;
229
+ if (Object.keys(requestBody).length === 0) {
230
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'No fields to update' }) }], isError: true };
231
+ }
232
+ const res = await tasks.tasks.patch({
233
+ tasklist: tasklistId,
234
+ task: taskId,
235
+ requestBody,
236
+ });
237
+ return {
238
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
239
+ };
240
+ }
241
+ catch (error) {
242
+ return handleTasksError(error, account);
243
+ }
244
+ });
245
+ server.registerTool('tasks_delete', {
246
+ description: 'Delete a task',
247
+ inputSchema: {
248
+ account: accountEnum.describe('Google account alias'),
249
+ tasklistId: z.string().describe('Tasklist ID'),
250
+ taskId: z.string().describe('Task ID'),
251
+ },
252
+ }, async ({ account, tasklistId, taskId }) => {
253
+ try {
254
+ const auth = await getClient(account);
255
+ const tasks = google.tasks({ version: 'v1', auth });
256
+ await tasks.tasks.delete({ tasklist: tasklistId, task: taskId });
257
+ return {
258
+ content: [{ type: 'text', text: JSON.stringify({ deleted: true, taskId }, null, 2) }],
259
+ };
260
+ }
261
+ catch (error) {
262
+ return handleTasksError(error, account);
263
+ }
264
+ });
265
+ server.registerTool('tasks_move', {
266
+ description: 'Reposition a task: change its parent, move it after a sibling, or move it to another tasklist',
267
+ inputSchema: {
268
+ account: accountEnum.describe('Google account alias'),
269
+ tasklistId: z.string().describe('Source tasklist ID'),
270
+ taskId: z.string().describe('Task ID'),
271
+ parent: z.string().optional().describe('New parent task ID'),
272
+ previous: z.string().optional().describe('New sibling to position after'),
273
+ destinationTasklist: z.string().optional().describe('Move to a different tasklist'),
274
+ },
275
+ }, async ({ account, tasklistId, taskId, parent, previous, destinationTasklist }) => {
276
+ try {
277
+ const auth = await getClient(account);
278
+ const tasks = google.tasks({ version: 'v1', auth });
279
+ const res = await tasks.tasks.move({
280
+ tasklist: tasklistId,
281
+ task: taskId,
282
+ parent,
283
+ previous,
284
+ destinationTasklist,
285
+ });
286
+ return {
287
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
288
+ };
289
+ }
290
+ catch (error) {
291
+ return handleTasksError(error, account);
292
+ }
293
+ });
294
+ server.registerTool('tasks_clear', {
295
+ description: 'Permanently delete every completed task in a tasklist',
296
+ inputSchema: {
297
+ account: accountEnum.describe('Google account alias'),
298
+ tasklistId: z.string().describe('Tasklist ID'),
299
+ },
300
+ }, async ({ account, tasklistId }) => {
301
+ try {
302
+ const auth = await getClient(account);
303
+ const tasks = google.tasks({ version: 'v1', auth });
304
+ await tasks.tasks.clear({ tasklist: tasklistId });
305
+ return {
306
+ content: [{ type: 'text', text: JSON.stringify({ cleared: true, tasklistId }, null, 2) }],
307
+ };
308
+ }
309
+ catch (error) {
310
+ return handleTasksError(error, account);
311
+ }
312
+ });
313
+ }
314
+ function handleTasksError(error, account) {
315
+ return handleGoogleApiError(error, account);
316
+ }
@@ -0,0 +1,39 @@
1
+ export interface TokenData {
2
+ access_token: string;
3
+ refresh_token: string;
4
+ scope: string;
5
+ token_type: string;
6
+ expiry_date: number;
7
+ }
8
+ export interface GmailMessageHeader {
9
+ id: string;
10
+ threadId: string;
11
+ subject: string;
12
+ from: string;
13
+ date: string;
14
+ snippet: string;
15
+ }
16
+ export interface GmailAttachment {
17
+ filename: string;
18
+ attachmentId: string;
19
+ mimeType: string;
20
+ }
21
+ export interface GmailMessageFull {
22
+ id: string;
23
+ threadId: string;
24
+ subject: string;
25
+ from: string;
26
+ to: string;
27
+ cc: string;
28
+ date: string;
29
+ body: string;
30
+ attachments: GmailAttachment[];
31
+ }
32
+ export interface DriveFile {
33
+ id: string;
34
+ name: string;
35
+ mimeType: string;
36
+ modifiedTime: string;
37
+ webViewLink: string;
38
+ size: string;
39
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "mcp-google-multi",
3
+ "version": "4.2.0",
4
+ "description": "MCP server for Gmail, Google Drive, Calendar, Sheets, Docs, and Contacts with multi-account support",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "mcp-google-multi": "dist/index.js"
9
+ },
10
+ "main": "dist/index.js",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/bakissation/mcp-google-multi.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/bakissation/mcp-google-multi/issues"
23
+ },
24
+ "homepage": "https://github.com/bakissation/mcp-google-multi#readme",
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "provenance": true
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "google",
33
+ "gmail",
34
+ "google-drive",
35
+ "google-calendar",
36
+ "google-sheets",
37
+ "google-docs",
38
+ "google-contacts",
39
+ "claude",
40
+ "claude-code"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc && chmod +x dist/index.js",
44
+ "prepublishOnly": "npm run build",
45
+ "watch": "tsc --watch",
46
+ "auth": "node dist/index.js auth --account",
47
+ "lint": "eslint .",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest"
51
+ },
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.29.0",
54
+ "dotenv": "^17.4.0",
55
+ "googleapis": "^171.4.0",
56
+ "mime-types": "^3.0.2",
57
+ "open": "^11.0.0",
58
+ "server-destroy": "^1.0.1",
59
+ "zod": "^4.3.6"
60
+ },
61
+ "devDependencies": {
62
+ "@eslint/js": "^10.0.1",
63
+ "@types/mime-types": "^3.0.1",
64
+ "@types/node": "^25.6.0",
65
+ "@types/server-destroy": "^1.0.3",
66
+ "eslint": "^10.2.1",
67
+ "semantic-release": "^25.0.3",
68
+ "typescript": "^6.0.3",
69
+ "typescript-eslint": "^8.59.1",
70
+ "vitest": "^4.1.5"
71
+ }
72
+ }