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,96 @@
|
|
|
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 registerFormsTools(server) {
|
|
8
|
+
server.registerTool('forms_get', {
|
|
9
|
+
description: 'Get a Google Form (definition: questions, sections, settings). Requires forms.body scope.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
account: accountEnum.describe('Google account alias'),
|
|
12
|
+
formId: z.string().describe('Form ID'),
|
|
13
|
+
},
|
|
14
|
+
}, async ({ account, formId }) => {
|
|
15
|
+
try {
|
|
16
|
+
const auth = await getClient(account);
|
|
17
|
+
const forms = google.forms({ version: 'v1', auth });
|
|
18
|
+
const res = await forms.forms.get({ formId });
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return handleFormsError(error, account);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.registerTool('forms_responses_list', {
|
|
28
|
+
description: 'List form responses. Requires forms.responses.readonly scope.',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
account: accountEnum.describe('Google account alias'),
|
|
31
|
+
formId: z.string().describe('Form ID'),
|
|
32
|
+
pageSize: z.number().min(1).max(1000).optional().describe('Default: 100; max 1000. Responses can be large.'),
|
|
33
|
+
pageToken: z.string().optional(),
|
|
34
|
+
filter: z.string().optional().describe('Filter expression (e.g. "timestamp > 2026-01-01T00:00:00Z")'),
|
|
35
|
+
},
|
|
36
|
+
}, async ({ account, formId, pageSize, pageToken, filter }) => {
|
|
37
|
+
try {
|
|
38
|
+
const auth = await getClient(account);
|
|
39
|
+
const forms = google.forms({ version: 'v1', auth });
|
|
40
|
+
const res = await forms.forms.responses.list({
|
|
41
|
+
formId,
|
|
42
|
+
pageSize: pageSize ?? 100,
|
|
43
|
+
pageToken,
|
|
44
|
+
filter,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return handleFormsError(error, account);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
server.registerTool('forms_response_get', {
|
|
55
|
+
description: 'Get a single form response by ID',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
account: accountEnum.describe('Google account alias'),
|
|
58
|
+
formId: z.string().describe('Form ID'),
|
|
59
|
+
responseId: z.string().describe('Response ID'),
|
|
60
|
+
},
|
|
61
|
+
}, async ({ account, formId, responseId }) => {
|
|
62
|
+
try {
|
|
63
|
+
const auth = await getClient(account);
|
|
64
|
+
const forms = google.forms({ version: 'v1', auth });
|
|
65
|
+
const res = await forms.forms.responses.get({ formId, responseId });
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return handleFormsError(error, account);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
server.registerTool('forms_watches_list', {
|
|
75
|
+
description: 'List Pub/Sub watches on a form (notifications for new responses or schema changes)',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
account: accountEnum.describe('Google account alias'),
|
|
78
|
+
formId: z.string().describe('Form ID'),
|
|
79
|
+
},
|
|
80
|
+
}, async ({ account, formId }) => {
|
|
81
|
+
try {
|
|
82
|
+
const auth = await getClient(account);
|
|
83
|
+
const forms = google.forms({ version: 'v1', auth });
|
|
84
|
+
const res = await forms.forms.watches.list({ formId });
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return handleFormsError(error, account);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function handleFormsError(error, account) {
|
|
95
|
+
return handleGoogleApiError(error, account, "Forms tools require the optional \"forms\" scope bundle. Add GOOGLE_OPTIONAL_SCOPES=forms (or include \"forms\" in the list) and re-auth.");
|
|
96
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 2047 encoded-word (`=?utf-8?B?...?=`) for non-ASCII header text.
|
|
3
|
+
* Long values are split into multiple <=75-char encoded-words separated
|
|
4
|
+
* by `\r\n ` (CRLF SPACE) per the RFC's folding rule.
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeHeaderValue(value: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Encode an address-list header (To/Cc/Bcc/From). RFC 2047 forbids
|
|
9
|
+
* encoded-words inside the addr-spec, so only the display name is encoded.
|
|
10
|
+
*/
|
|
11
|
+
export declare function encodeAddressHeader(value: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* RFC 5322 §2.3: CR and LF MUST only occur together as CRLF in bodies.
|
|
14
|
+
* Normalize bare `\n` or `\r` to CRLF.
|
|
15
|
+
*/
|
|
16
|
+
export declare function normalizeBodyLineEndings(body: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 2047 encoded-word (`=?utf-8?B?...?=`) for non-ASCII header text.
|
|
3
|
+
* Long values are split into multiple <=75-char encoded-words separated
|
|
4
|
+
* by `\r\n ` (CRLF SPACE) per the RFC's folding rule.
|
|
5
|
+
*/
|
|
6
|
+
export function encodeHeaderValue(value) {
|
|
7
|
+
// Span includes control chars on purpose — testing for "is this entire
|
|
8
|
+
// string ASCII?", not "is it printable?".
|
|
9
|
+
// eslint-disable-next-line no-control-regex
|
|
10
|
+
if (value === '' || /^[\x00-\x7F]*$/.test(value))
|
|
11
|
+
return value;
|
|
12
|
+
const prefix = '=?utf-8?B?';
|
|
13
|
+
const suffix = '?=';
|
|
14
|
+
const maxInner = 75 - prefix.length - suffix.length;
|
|
15
|
+
// base64 emits 4 output chars per 3 input bytes (always padded to a
|
|
16
|
+
// multiple of 4). Round maxInner DOWN to a multiple of 4 first.
|
|
17
|
+
const maxBytesPerChunk = Math.floor(maxInner / 4) * 3;
|
|
18
|
+
// Iterate by codepoint so each chunk's bytes form a complete UTF-8
|
|
19
|
+
// sequence — many MUAs decode encoded-words individually before joining,
|
|
20
|
+
// so a mid-byte split would surface as U+FFFD in those clients.
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let buffered = [];
|
|
23
|
+
for (const char of value) {
|
|
24
|
+
const charBytes = Array.from(Buffer.from(char, 'utf-8'));
|
|
25
|
+
if (buffered.length + charBytes.length > maxBytesPerChunk && buffered.length > 0) {
|
|
26
|
+
chunks.push(`${prefix}${Buffer.from(buffered).toString('base64')}${suffix}`);
|
|
27
|
+
buffered = [];
|
|
28
|
+
}
|
|
29
|
+
buffered.push(...charBytes);
|
|
30
|
+
}
|
|
31
|
+
if (buffered.length > 0) {
|
|
32
|
+
chunks.push(`${prefix}${Buffer.from(buffered).toString('base64')}${suffix}`);
|
|
33
|
+
}
|
|
34
|
+
return chunks.join('\r\n ');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Encode an address-list header (To/Cc/Bcc/From). RFC 2047 forbids
|
|
38
|
+
* encoded-words inside the addr-spec, so only the display name is encoded.
|
|
39
|
+
*/
|
|
40
|
+
export function encodeAddressHeader(value) {
|
|
41
|
+
if (value === '')
|
|
42
|
+
return '';
|
|
43
|
+
return value.split(',').map((part) => {
|
|
44
|
+
const trimmed = part.trim();
|
|
45
|
+
if (trimmed === '')
|
|
46
|
+
return '';
|
|
47
|
+
const m = trimmed.match(/^(.*?)<([^>]+)>$/);
|
|
48
|
+
if (m) {
|
|
49
|
+
const rawName = m[1].trim().replace(/^"(.*)"$/, '$1').trim();
|
|
50
|
+
const addr = m[2].trim();
|
|
51
|
+
if (rawName === '')
|
|
52
|
+
return `<${addr}>`;
|
|
53
|
+
return `${encodeHeaderValue(rawName)} <${addr}>`;
|
|
54
|
+
}
|
|
55
|
+
return trimmed;
|
|
56
|
+
}).filter(Boolean).join(', ');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* RFC 5322 §2.3: CR and LF MUST only occur together as CRLF in bodies.
|
|
60
|
+
* Normalize bare `\n` or `\r` to CRLF.
|
|
61
|
+
*/
|
|
62
|
+
export function normalizeBodyLineEndings(body) {
|
|
63
|
+
return body.replace(/\r\n|\r|\n/g, '\r\n');
|
|
64
|
+
}
|