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.
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/dist/accounts.d.ts +10 -0
- package/dist/accounts.js +53 -0
- package/dist/auth.d.ts +15 -0
- package/dist/auth.js +178 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +33 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +58 -0
- package/dist/tools/_errors.d.ts +9 -0
- package/dist/tools/_errors.js +42 -0
- package/dist/tools/admin.d.ts +17 -0
- package/dist/tools/admin.js +307 -0
- package/dist/tools/calendar.d.ts +2 -0
- package/dist/tools/calendar.js +399 -0
- package/dist/tools/chat.d.ts +2 -0
- package/dist/tools/chat.js +121 -0
- package/dist/tools/contacts.d.ts +2 -0
- package/dist/tools/contacts.js +368 -0
- package/dist/tools/docs.d.ts +36 -0
- package/dist/tools/docs.js +1123 -0
- package/dist/tools/drive.d.ts +2 -0
- package/dist/tools/drive.js +1082 -0
- package/dist/tools/forms.d.ts +2 -0
- package/dist/tools/forms.js +96 -0
- package/dist/tools/gmail-mime.d.ts +16 -0
- package/dist/tools/gmail-mime.js +64 -0
- package/dist/tools/gmail.d.ts +2 -0
- package/dist/tools/gmail.js +651 -0
- package/dist/tools/meet.d.ts +2 -0
- package/dist/tools/meet.js +133 -0
- package/dist/tools/searchconsole.d.ts +2 -0
- package/dist/tools/searchconsole.js +258 -0
- package/dist/tools/sheets.d.ts +34 -0
- package/dist/tools/sheets.js +1207 -0
- package/dist/tools/tasks.d.ts +2 -0
- package/dist/tools/tasks.js +316 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,368 @@
|
|
|
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
|
+
const PERSON_FIELDS = 'names,emailAddresses,phoneNumbers,organizations,addresses,photos,memberships';
|
|
8
|
+
function formatContact(person) {
|
|
9
|
+
return {
|
|
10
|
+
resourceName: person.resourceName,
|
|
11
|
+
name: person.names?.[0]?.displayName ?? '',
|
|
12
|
+
givenName: person.names?.[0]?.givenName ?? '',
|
|
13
|
+
familyName: person.names?.[0]?.familyName ?? '',
|
|
14
|
+
emails: (person.emailAddresses ?? []).map((e) => ({
|
|
15
|
+
value: e.value,
|
|
16
|
+
type: e.type ?? '',
|
|
17
|
+
})),
|
|
18
|
+
phones: (person.phoneNumbers ?? []).map((p) => ({
|
|
19
|
+
value: p.value,
|
|
20
|
+
type: p.type ?? '',
|
|
21
|
+
})),
|
|
22
|
+
organizations: (person.organizations ?? []).map((o) => ({
|
|
23
|
+
name: o.name ?? '',
|
|
24
|
+
title: o.title ?? '',
|
|
25
|
+
department: o.department ?? '',
|
|
26
|
+
})),
|
|
27
|
+
addresses: (person.addresses ?? []).map((a) => ({
|
|
28
|
+
formattedValue: a.formattedValue ?? '',
|
|
29
|
+
type: a.type ?? '',
|
|
30
|
+
})),
|
|
31
|
+
photo: person.photos?.[0]?.url ?? '',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function registerContactsTools(server) {
|
|
35
|
+
server.registerTool('contacts_search', {
|
|
36
|
+
description: 'Search contacts by name, email, phone, or organization',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
account: accountEnum.describe('Google account alias'),
|
|
39
|
+
query: z.string().describe('Search query (prefix matching on names, emails, phones, organizations)'),
|
|
40
|
+
pageSize: z.number().min(1).max(30).default(10).optional()
|
|
41
|
+
.describe('Max results (1-30, default: 10)'),
|
|
42
|
+
},
|
|
43
|
+
}, async ({ account, query, pageSize }) => {
|
|
44
|
+
try {
|
|
45
|
+
const auth = await getClient(account);
|
|
46
|
+
const people = google.people({ version: 'v1', auth });
|
|
47
|
+
// Warmup request required by the People API
|
|
48
|
+
await people.people.searchContacts({
|
|
49
|
+
query: '',
|
|
50
|
+
readMask: 'names',
|
|
51
|
+
});
|
|
52
|
+
const res = await people.people.searchContacts({
|
|
53
|
+
query,
|
|
54
|
+
readMask: PERSON_FIELDS,
|
|
55
|
+
pageSize: pageSize ?? 10,
|
|
56
|
+
});
|
|
57
|
+
const contacts = (res.data.results ?? []).map((r) => formatContact(r.person));
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: 'text', text: JSON.stringify(contacts, null, 2) }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return handleContactsError(error, account);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.registerTool('contacts_get', {
|
|
67
|
+
description: 'Get a single contact by resource name',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
account: accountEnum.describe('Google account alias'),
|
|
70
|
+
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
71
|
+
},
|
|
72
|
+
}, async ({ account, resourceName }) => {
|
|
73
|
+
try {
|
|
74
|
+
const auth = await getClient(account);
|
|
75
|
+
const people = google.people({ version: 'v1', auth });
|
|
76
|
+
const res = await people.people.get({
|
|
77
|
+
resourceName,
|
|
78
|
+
personFields: PERSON_FIELDS,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: JSON.stringify(formatContact(res.data), null, 2) }],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return handleContactsError(error, account);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
server.registerTool('contacts_list', {
|
|
89
|
+
description: 'List all contacts (paginated)',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
account: accountEnum.describe('Google account alias'),
|
|
92
|
+
pageSize: z.number().min(1).max(1000).default(100).optional()
|
|
93
|
+
.describe('Number of contacts per page (default: 100)'),
|
|
94
|
+
pageToken: z.string().optional().describe('Page token for pagination'),
|
|
95
|
+
sortOrder: z.enum([
|
|
96
|
+
'LAST_MODIFIED_ASCENDING',
|
|
97
|
+
'LAST_MODIFIED_DESCENDING',
|
|
98
|
+
'FIRST_NAME_ASCENDING',
|
|
99
|
+
'LAST_NAME_ASCENDING',
|
|
100
|
+
]).default('FIRST_NAME_ASCENDING').optional()
|
|
101
|
+
.describe('Sort order (default: FIRST_NAME_ASCENDING)'),
|
|
102
|
+
},
|
|
103
|
+
}, async ({ account, pageSize, pageToken, sortOrder }) => {
|
|
104
|
+
try {
|
|
105
|
+
const auth = await getClient(account);
|
|
106
|
+
const people = google.people({ version: 'v1', auth });
|
|
107
|
+
const res = await people.people.connections.list({
|
|
108
|
+
resourceName: 'people/me',
|
|
109
|
+
personFields: PERSON_FIELDS,
|
|
110
|
+
pageSize: pageSize ?? 100,
|
|
111
|
+
pageToken: pageToken ?? undefined,
|
|
112
|
+
sortOrder: sortOrder ?? 'FIRST_NAME_ASCENDING',
|
|
113
|
+
});
|
|
114
|
+
const contacts = (res.data.connections ?? []).map(formatContact);
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
117
|
+
contacts,
|
|
118
|
+
totalItems: res.data.totalItems,
|
|
119
|
+
nextPageToken: res.data.nextPageToken ?? null,
|
|
120
|
+
}, null, 2) }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return handleContactsError(error, account);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
server.registerTool('contacts_create', {
|
|
128
|
+
description: 'Create a new contact',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
account: accountEnum.describe('Google account alias'),
|
|
131
|
+
givenName: z.string().describe('First name'),
|
|
132
|
+
familyName: z.string().optional().describe('Last name'),
|
|
133
|
+
email: z.string().optional().describe('Email address'),
|
|
134
|
+
emailType: z.enum(['home', 'work', 'other']).default('work').optional()
|
|
135
|
+
.describe('Email type (default: work)'),
|
|
136
|
+
phone: z.string().optional().describe('Phone number'),
|
|
137
|
+
phoneType: z.enum(['home', 'work', 'mobile', 'other']).default('mobile').optional()
|
|
138
|
+
.describe('Phone type (default: mobile)'),
|
|
139
|
+
organization: z.string().optional().describe('Company/organization name'),
|
|
140
|
+
jobTitle: z.string().optional().describe('Job title'),
|
|
141
|
+
},
|
|
142
|
+
}, async ({ account, givenName, familyName, email, emailType, phone, phoneType, organization, jobTitle }) => {
|
|
143
|
+
try {
|
|
144
|
+
const auth = await getClient(account);
|
|
145
|
+
const people = google.people({ version: 'v1', auth });
|
|
146
|
+
const requestBody = {
|
|
147
|
+
names: [{ givenName, familyName: familyName ?? '' }],
|
|
148
|
+
};
|
|
149
|
+
if (email) {
|
|
150
|
+
requestBody.emailAddresses = [{ value: email, type: emailType ?? 'work' }];
|
|
151
|
+
}
|
|
152
|
+
if (phone) {
|
|
153
|
+
requestBody.phoneNumbers = [{ value: phone, type: phoneType ?? 'mobile' }];
|
|
154
|
+
}
|
|
155
|
+
if (organization || jobTitle) {
|
|
156
|
+
requestBody.organizations = [{
|
|
157
|
+
name: organization ?? '',
|
|
158
|
+
title: jobTitle ?? '',
|
|
159
|
+
}];
|
|
160
|
+
}
|
|
161
|
+
const res = await people.people.createContact({
|
|
162
|
+
personFields: PERSON_FIELDS,
|
|
163
|
+
requestBody,
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: 'text', text: JSON.stringify(formatContact(res.data), null, 2) }],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return handleContactsError(error, account);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
server.registerTool('contacts_update', {
|
|
174
|
+
description: 'Update an existing contact (reads current etag automatically)',
|
|
175
|
+
inputSchema: {
|
|
176
|
+
account: accountEnum.describe('Google account alias'),
|
|
177
|
+
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
178
|
+
givenName: z.string().optional().describe('Updated first name'),
|
|
179
|
+
familyName: z.string().optional().describe('Updated last name'),
|
|
180
|
+
email: z.string().optional().describe('Updated email address'),
|
|
181
|
+
emailType: z.enum(['home', 'work', 'other']).default('work').optional()
|
|
182
|
+
.describe('Email type'),
|
|
183
|
+
phone: z.string().optional().describe('Updated phone number'),
|
|
184
|
+
phoneType: z.enum(['home', 'work', 'mobile', 'other']).default('mobile').optional()
|
|
185
|
+
.describe('Phone type'),
|
|
186
|
+
organization: z.string().optional().describe('Updated company/organization name'),
|
|
187
|
+
jobTitle: z.string().optional().describe('Updated job title'),
|
|
188
|
+
},
|
|
189
|
+
}, async ({ account, resourceName, givenName, familyName, email, emailType, phone, phoneType, organization, jobTitle }) => {
|
|
190
|
+
try {
|
|
191
|
+
const auth = await getClient(account);
|
|
192
|
+
const people = google.people({ version: 'v1', auth });
|
|
193
|
+
// Fetch current contact to get etag
|
|
194
|
+
const current = await people.people.get({
|
|
195
|
+
resourceName,
|
|
196
|
+
personFields: PERSON_FIELDS,
|
|
197
|
+
});
|
|
198
|
+
const etag = current.data.etag;
|
|
199
|
+
const requestBody = { etag };
|
|
200
|
+
const updateFields = [];
|
|
201
|
+
if (givenName !== undefined || familyName !== undefined) {
|
|
202
|
+
requestBody.names = [{
|
|
203
|
+
givenName: givenName ?? current.data.names?.[0]?.givenName ?? '',
|
|
204
|
+
familyName: familyName ?? current.data.names?.[0]?.familyName ?? '',
|
|
205
|
+
}];
|
|
206
|
+
updateFields.push('names');
|
|
207
|
+
}
|
|
208
|
+
if (email !== undefined) {
|
|
209
|
+
requestBody.emailAddresses = [{ value: email, type: emailType ?? 'work' }];
|
|
210
|
+
updateFields.push('emailAddresses');
|
|
211
|
+
}
|
|
212
|
+
if (phone !== undefined) {
|
|
213
|
+
requestBody.phoneNumbers = [{ value: phone, type: phoneType ?? 'mobile' }];
|
|
214
|
+
updateFields.push('phoneNumbers');
|
|
215
|
+
}
|
|
216
|
+
if (organization !== undefined || jobTitle !== undefined) {
|
|
217
|
+
requestBody.organizations = [{
|
|
218
|
+
name: organization ?? current.data.organizations?.[0]?.name ?? '',
|
|
219
|
+
title: jobTitle ?? current.data.organizations?.[0]?.title ?? '',
|
|
220
|
+
}];
|
|
221
|
+
updateFields.push('organizations');
|
|
222
|
+
}
|
|
223
|
+
if (updateFields.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
226
|
+
error: 'No fields to update. Provide at least one of: givenName, familyName, email, phone, organization, jobTitle',
|
|
227
|
+
}, null, 2) }],
|
|
228
|
+
isError: true,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const res = await people.people.updateContact({
|
|
232
|
+
resourceName,
|
|
233
|
+
updatePersonFields: updateFields.join(','),
|
|
234
|
+
personFields: PERSON_FIELDS,
|
|
235
|
+
requestBody,
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: 'text', text: JSON.stringify(formatContact(res.data), null, 2) }],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
return handleContactsError(error, account);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
server.registerTool('contacts_delete', {
|
|
246
|
+
description: 'Delete a contact (permanent, cannot be undone)',
|
|
247
|
+
inputSchema: {
|
|
248
|
+
account: accountEnum.describe('Google account alias'),
|
|
249
|
+
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
250
|
+
},
|
|
251
|
+
}, async ({ account, resourceName }) => {
|
|
252
|
+
try {
|
|
253
|
+
const auth = await getClient(account);
|
|
254
|
+
const people = google.people({ version: 'v1', auth });
|
|
255
|
+
await people.people.deleteContact({ resourceName });
|
|
256
|
+
return {
|
|
257
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
258
|
+
deleted: resourceName,
|
|
259
|
+
}, null, 2) }],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
return handleContactsError(error, account);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
server.registerTool('contacts_groups_list', {
|
|
267
|
+
description: 'List all contact groups (labels)',
|
|
268
|
+
inputSchema: {
|
|
269
|
+
account: accountEnum.describe('Google account alias'),
|
|
270
|
+
pageSize: z.number().min(1).max(1000).default(100).optional()
|
|
271
|
+
.describe('Max groups to return (default: 100)'),
|
|
272
|
+
},
|
|
273
|
+
}, async ({ account, pageSize }) => {
|
|
274
|
+
try {
|
|
275
|
+
const auth = await getClient(account);
|
|
276
|
+
const people = google.people({ version: 'v1', auth });
|
|
277
|
+
const res = await people.contactGroups.list({
|
|
278
|
+
pageSize: pageSize ?? 100,
|
|
279
|
+
groupFields: 'name,groupType,memberCount',
|
|
280
|
+
});
|
|
281
|
+
const groups = (res.data.contactGroups ?? []).map((g) => ({
|
|
282
|
+
resourceName: g.resourceName,
|
|
283
|
+
name: g.name,
|
|
284
|
+
groupType: g.groupType,
|
|
285
|
+
memberCount: g.memberCount ?? 0,
|
|
286
|
+
}));
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: 'text', text: JSON.stringify(groups, null, 2) }],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
return handleContactsError(error, account);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
server.registerTool('contacts_group_members', {
|
|
296
|
+
description: 'List members of a contact group',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
account: accountEnum.describe('Google account alias'),
|
|
299
|
+
groupResourceName: z.string().describe('Contact group resource name (e.g. "contactGroups/abc123")'),
|
|
300
|
+
maxMembers: z.number().min(1).max(1000).default(100).optional()
|
|
301
|
+
.describe('Max member resource names to return (default: 100)'),
|
|
302
|
+
},
|
|
303
|
+
}, async ({ account, groupResourceName, maxMembers }) => {
|
|
304
|
+
try {
|
|
305
|
+
const auth = await getClient(account);
|
|
306
|
+
const people = google.people({ version: 'v1', auth });
|
|
307
|
+
const groupRes = await people.contactGroups.get({
|
|
308
|
+
resourceName: groupResourceName,
|
|
309
|
+
maxMembers: maxMembers ?? 100,
|
|
310
|
+
});
|
|
311
|
+
const memberResourceNames = groupRes.data.memberResourceNames ?? [];
|
|
312
|
+
if (memberResourceNames.length === 0) {
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
315
|
+
group: groupRes.data.name,
|
|
316
|
+
members: [],
|
|
317
|
+
}, null, 2) }],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const membersRes = await people.people.getBatchGet({
|
|
321
|
+
resourceNames: memberResourceNames,
|
|
322
|
+
personFields: PERSON_FIELDS,
|
|
323
|
+
});
|
|
324
|
+
const members = (membersRes.data.responses ?? [])
|
|
325
|
+
.filter((r) => r.person)
|
|
326
|
+
.map((r) => formatContact(r.person));
|
|
327
|
+
return {
|
|
328
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
329
|
+
group: groupRes.data.name,
|
|
330
|
+
members,
|
|
331
|
+
}, null, 2) }],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return handleContactsError(error, account);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
server.registerTool('contacts_group_create', {
|
|
339
|
+
description: 'Create a new contact group (label)',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
account: accountEnum.describe('Google account alias'),
|
|
342
|
+
name: z.string().describe('Name for the contact group'),
|
|
343
|
+
},
|
|
344
|
+
}, async ({ account, name }) => {
|
|
345
|
+
try {
|
|
346
|
+
const auth = await getClient(account);
|
|
347
|
+
const people = google.people({ version: 'v1', auth });
|
|
348
|
+
const res = await people.contactGroups.create({
|
|
349
|
+
requestBody: {
|
|
350
|
+
contactGroup: { name },
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
355
|
+
resourceName: res.data.resourceName,
|
|
356
|
+
name: res.data.name,
|
|
357
|
+
groupType: res.data.groupType,
|
|
358
|
+
}, null, 2) }],
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return handleContactsError(error, account);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function handleContactsError(error, account) {
|
|
367
|
+
return handleGoogleApiError(error, account);
|
|
368
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export declare function registerDocsTools(server: McpServer): void;
|
|
3
|
+
export declare function buildParagraphStyle(input: {
|
|
4
|
+
namedStyleType?: string;
|
|
5
|
+
alignment?: string;
|
|
6
|
+
lineSpacing?: number;
|
|
7
|
+
spaceAbove?: number;
|
|
8
|
+
spaceBelow?: number;
|
|
9
|
+
indentStart?: number;
|
|
10
|
+
indentEnd?: number;
|
|
11
|
+
indentFirstLine?: number;
|
|
12
|
+
direction?: string;
|
|
13
|
+
keepLinesTogether?: boolean;
|
|
14
|
+
keepWithNext?: boolean;
|
|
15
|
+
avoidWidowAndOrphan?: boolean;
|
|
16
|
+
}): {
|
|
17
|
+
paragraphStyle: any;
|
|
18
|
+
fields: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function buildDocumentStyle(input: {
|
|
21
|
+
pageWidth?: number;
|
|
22
|
+
pageHeight?: number;
|
|
23
|
+
marginTop?: number;
|
|
24
|
+
marginBottom?: number;
|
|
25
|
+
marginLeft?: number;
|
|
26
|
+
marginRight?: number;
|
|
27
|
+
marginHeader?: number;
|
|
28
|
+
marginFooter?: number;
|
|
29
|
+
pageNumberStart?: number;
|
|
30
|
+
useFirstPageHeaderFooter?: boolean;
|
|
31
|
+
useEvenPageHeaderFooter?: boolean;
|
|
32
|
+
useCustomHeaderFooterMargins?: boolean;
|
|
33
|
+
}): {
|
|
34
|
+
documentStyle: any;
|
|
35
|
+
fields: string;
|
|
36
|
+
};
|