mcp-google-multi 4.3.0-alpha.2 → 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/README.md +2 -33
- package/dist/tools/gmail-mime.d.ts +0 -9
- package/dist/tools/gmail-mime.js +0 -35
- package/dist/tools/gmail.js +11 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
A local [MCP](https://modelcontextprotocol.io) server that gives Claude Code (and any MCP client) access to **Gmail**, **Google Drive**, **Google Calendar**, **Google Sheets**, **Google Docs**, **Google Contacts**, **Google Search Console**, **Google Tasks**, **Google Meet**, and optionally **Google Forms**, **Google Chat**, and **Google Workspace Admin** APIs across multiple Google accounts simultaneously.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/mcp-google-multi)
|
|
6
|
-
|
|
7
5
|
## Features
|
|
8
6
|
|
|
9
7
|
- **Multi-account** — manage any number of Google accounts from a single server
|
|
@@ -251,25 +249,14 @@ Enable with `GOOGLE_OPTIONAL_SCOPES=alertcenter` in `.env`.
|
|
|
251
249
|
|
|
252
250
|
### 2. Install & Configure
|
|
253
251
|
|
|
254
|
-
**Option A — from npm (no clone):**
|
|
255
|
-
|
|
256
|
-
```bash
|
|
257
|
-
npm install -g mcp-google-multi # installs the `mcp-google-multi` CLI
|
|
258
|
-
# …or run on demand without installing: npx mcp-google-multi
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Published with channel dist-tags — `@latest` (stable), `@dev` (alpha), `@staging` (beta); e.g. `npm i -g mcp-google-multi@dev` for the latest alpha.
|
|
262
|
-
|
|
263
|
-
**Option B — from source:**
|
|
264
|
-
|
|
265
252
|
```bash
|
|
266
253
|
git clone https://github.com/bakissation/mcp-google-multi.git
|
|
267
254
|
cd mcp-google-multi
|
|
268
|
-
npm install
|
|
255
|
+
npm install
|
|
269
256
|
cp .env.example .env
|
|
270
257
|
```
|
|
271
258
|
|
|
272
|
-
|
|
259
|
+
Edit `.env`:
|
|
273
260
|
|
|
274
261
|
```env
|
|
275
262
|
GOOGLE_CLIENT_ID=your_client_id_here
|
|
@@ -323,24 +310,6 @@ Or add it manually to your Claude Code MCP config:
|
|
|
323
310
|
}
|
|
324
311
|
```
|
|
325
312
|
|
|
326
|
-
If you installed from npm, point at the package instead and pass config via `env` (authenticate first with `mcp-google-multi auth --account <alias>`):
|
|
327
|
-
|
|
328
|
-
```json
|
|
329
|
-
{
|
|
330
|
-
"mcpServers": {
|
|
331
|
-
"google-multi": {
|
|
332
|
-
"command": "npx",
|
|
333
|
-
"args": ["-y", "mcp-google-multi"],
|
|
334
|
-
"env": {
|
|
335
|
-
"GOOGLE_CLIENT_ID": "your_client_id_here",
|
|
336
|
-
"GOOGLE_CLIENT_SECRET": "your_client_secret_here",
|
|
337
|
-
"GOOGLE_ACCOUNTS": "work:you@company.com,personal:you@gmail.com"
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
313
|
## Migrating from v3
|
|
345
314
|
|
|
346
315
|
v4.0.0 grows the base OAuth scope set (Tasks and Meet are now included by default). Existing v3 tokens lack those scopes and the corresponding tools will 403.
|
|
@@ -14,12 +14,3 @@ export declare function encodeAddressHeader(value: string): string;
|
|
|
14
14
|
* Normalize bare `\n` or `\r` to CRLF.
|
|
15
15
|
*/
|
|
16
16
|
export declare function normalizeBodyLineEndings(body: string): string;
|
|
17
|
-
/**
|
|
18
|
-
* Build a multipart/alternative body so HTML-capable clients render the rich
|
|
19
|
-
* version and plain clients fall back. Returns the header value AND the body.
|
|
20
|
-
* Caller composes the full message: headers (including this Content-Type) + CRLF + body.
|
|
21
|
-
*/
|
|
22
|
-
export declare function buildMultipartAlternative(plainBody: string, htmlBody: string): {
|
|
23
|
-
contentType: string;
|
|
24
|
-
body: string;
|
|
25
|
-
};
|
package/dist/tools/gmail-mime.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
/**
|
|
3
2
|
* RFC 2047 encoded-word (`=?utf-8?B?...?=`) for non-ASCII header text.
|
|
4
3
|
* Long values are split into multiple <=75-char encoded-words separated
|
|
@@ -63,37 +62,3 @@ export function encodeAddressHeader(value) {
|
|
|
63
62
|
export function normalizeBodyLineEndings(body) {
|
|
64
63
|
return body.replace(/\r\n|\r|\n/g, '\r\n');
|
|
65
64
|
}
|
|
66
|
-
/**
|
|
67
|
-
* RFC 2046 §5.1.1 boundary token: 1-70 chars from a restricted set, no trailing space.
|
|
68
|
-
* randomBytes hex output is only [0-9a-f], all of which are bcharsnospace.
|
|
69
|
-
*/
|
|
70
|
-
function generateMimeBoundary() {
|
|
71
|
-
// 5-char prefix + 32 hex chars = 37 chars, well under the 70-char limit.
|
|
72
|
-
return `=_gm_${randomBytes(16).toString('hex')}`;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Build a multipart/alternative body so HTML-capable clients render the rich
|
|
76
|
-
* version and plain clients fall back. Returns the header value AND the body.
|
|
77
|
-
* Caller composes the full message: headers (including this Content-Type) + CRLF + body.
|
|
78
|
-
*/
|
|
79
|
-
export function buildMultipartAlternative(plainBody, htmlBody) {
|
|
80
|
-
const boundary = generateMimeBoundary();
|
|
81
|
-
const parts = [
|
|
82
|
-
`--${boundary}`,
|
|
83
|
-
'Content-Type: text/plain; charset="UTF-8"',
|
|
84
|
-
'Content-Transfer-Encoding: 8bit',
|
|
85
|
-
'',
|
|
86
|
-
normalizeBodyLineEndings(plainBody),
|
|
87
|
-
`--${boundary}`,
|
|
88
|
-
'Content-Type: text/html; charset="UTF-8"',
|
|
89
|
-
'Content-Transfer-Encoding: 8bit',
|
|
90
|
-
'',
|
|
91
|
-
normalizeBodyLineEndings(htmlBody),
|
|
92
|
-
`--${boundary}--`,
|
|
93
|
-
'',
|
|
94
|
-
];
|
|
95
|
-
return {
|
|
96
|
-
contentType: `multipart/alternative; boundary="${boundary}"`,
|
|
97
|
-
body: parts.join('\r\n'),
|
|
98
|
-
};
|
|
99
|
-
}
|
package/dist/tools/gmail.js
CHANGED
|
@@ -3,7 +3,7 @@ import { google } from 'googleapis';
|
|
|
3
3
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
4
|
import { getClient } from '../client.js';
|
|
5
5
|
import { handleGoogleApiError } from './_errors.js';
|
|
6
|
-
import {
|
|
6
|
+
import { encodeAddressHeader, encodeHeaderValue, normalizeBodyLineEndings } from './gmail-mime.js';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
const accountEnum = z.enum(ACCOUNTS);
|
|
@@ -163,16 +163,14 @@ export function registerGmailTools(server) {
|
|
|
163
163
|
account: accountEnum.describe('Google account alias'),
|
|
164
164
|
to: z.string().describe('Recipient(s), comma-separated'),
|
|
165
165
|
subject: z.string().describe('Email subject'),
|
|
166
|
-
body: z.string().describe('Plain text body
|
|
167
|
-
htmlBody: z.string().optional()
|
|
168
|
-
.describe('Optional HTML body. When set, sends multipart/alternative so HTML-capable clients render the rich version. Use bare tags only: <p>, <a>, <br>, <strong>, <em>, <ul><li>.'),
|
|
166
|
+
body: z.string().describe('Plain text body'),
|
|
169
167
|
cc: z.string().optional().describe('CC recipients, comma-separated'),
|
|
170
168
|
replyToMessageId: z.string().optional()
|
|
171
169
|
.describe('Message ID to reply to (sets In-Reply-To and References headers)'),
|
|
172
170
|
replyToThreadId: z.string().optional()
|
|
173
171
|
.describe('Thread ID to send the message in'),
|
|
174
172
|
},
|
|
175
|
-
}, async ({ account, to, subject, body,
|
|
173
|
+
}, async ({ account, to, subject, body, cc, replyToMessageId, replyToThreadId }) => {
|
|
176
174
|
try {
|
|
177
175
|
const auth = await getClient(account);
|
|
178
176
|
const gmail = google.gmail({ version: 'v1', auth });
|
|
@@ -182,25 +180,16 @@ export function registerGmailTools(server) {
|
|
|
182
180
|
`To: ${encodeAddressHeader(to)}`,
|
|
183
181
|
`Subject: ${encodeHeaderValue(subject)}`,
|
|
184
182
|
'MIME-Version: 1.0',
|
|
183
|
+
'Content-Type: text/plain; charset="UTF-8"',
|
|
184
|
+
'Content-Transfer-Encoding: 8bit',
|
|
185
185
|
];
|
|
186
|
-
let bodyText;
|
|
187
|
-
if (htmlBody) {
|
|
188
|
-
const { contentType, body: mp } = buildMultipartAlternative(body, htmlBody);
|
|
189
|
-
headers.push(`Content-Type: ${contentType}`);
|
|
190
|
-
bodyText = mp;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
headers.push('Content-Type: text/plain; charset="UTF-8"');
|
|
194
|
-
headers.push('Content-Transfer-Encoding: 8bit');
|
|
195
|
-
bodyText = normalizeBodyLineEndings(body);
|
|
196
|
-
}
|
|
197
186
|
if (cc)
|
|
198
187
|
headers.push(`Cc: ${encodeAddressHeader(cc)}`);
|
|
199
188
|
if (replyToMessageId) {
|
|
200
189
|
headers.push(`In-Reply-To: ${replyToMessageId}`);
|
|
201
190
|
headers.push(`References: ${replyToMessageId}`);
|
|
202
191
|
}
|
|
203
|
-
const rawMessage = [...headers, '',
|
|
192
|
+
const rawMessage = [...headers, '', normalizeBodyLineEndings(body)].join('\r\n');
|
|
204
193
|
const encoded = Buffer.from(rawMessage, 'utf-8').toString('base64url');
|
|
205
194
|
const sendParams = {
|
|
206
195
|
userId: 'me',
|
|
@@ -260,14 +249,12 @@ export function registerGmailTools(server) {
|
|
|
260
249
|
account: accountEnum.describe('Google account alias'),
|
|
261
250
|
to: z.string().describe('Recipient(s), comma-separated'),
|
|
262
251
|
subject: z.string().describe('Email subject'),
|
|
263
|
-
body: z.string().describe('Plain text body
|
|
264
|
-
htmlBody: z.string().optional()
|
|
265
|
-
.describe('Optional HTML body. When set, drafts as multipart/alternative so HTML-capable clients render the rich version. Use bare tags only: <p>, <a>, <br>, <strong>, <em>, <ul><li>.'),
|
|
252
|
+
body: z.string().describe('Plain text body'),
|
|
266
253
|
cc: z.string().optional().describe('CC recipients, comma-separated'),
|
|
267
254
|
replyToThreadId: z.string().optional()
|
|
268
255
|
.describe('Thread ID to associate the draft with'),
|
|
269
256
|
},
|
|
270
|
-
}, async ({ account, to, subject, body,
|
|
257
|
+
}, async ({ account, to, subject, body, cc, replyToThreadId }) => {
|
|
271
258
|
try {
|
|
272
259
|
const auth = await getClient(account);
|
|
273
260
|
const gmail = google.gmail({ version: 'v1', auth });
|
|
@@ -277,21 +264,12 @@ export function registerGmailTools(server) {
|
|
|
277
264
|
`To: ${encodeAddressHeader(to)}`,
|
|
278
265
|
`Subject: ${encodeHeaderValue(subject)}`,
|
|
279
266
|
'MIME-Version: 1.0',
|
|
267
|
+
'Content-Type: text/plain; charset="UTF-8"',
|
|
268
|
+
'Content-Transfer-Encoding: 8bit',
|
|
280
269
|
];
|
|
281
|
-
let bodyText;
|
|
282
|
-
if (htmlBody) {
|
|
283
|
-
const { contentType, body: mp } = buildMultipartAlternative(body, htmlBody);
|
|
284
|
-
headers.push(`Content-Type: ${contentType}`);
|
|
285
|
-
bodyText = mp;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
headers.push('Content-Type: text/plain; charset="UTF-8"');
|
|
289
|
-
headers.push('Content-Transfer-Encoding: 8bit');
|
|
290
|
-
bodyText = normalizeBodyLineEndings(body);
|
|
291
|
-
}
|
|
292
270
|
if (cc)
|
|
293
271
|
headers.push(`Cc: ${encodeAddressHeader(cc)}`);
|
|
294
|
-
const rawMessage = [...headers, '',
|
|
272
|
+
const rawMessage = [...headers, '', normalizeBodyLineEndings(body)].join('\r\n');
|
|
295
273
|
const encoded = Buffer.from(rawMessage, 'utf-8').toString('base64url');
|
|
296
274
|
const draftParams = {
|
|
297
275
|
userId: 'me',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "4.3.0
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "MCP server for Gmail, Google Drive, Calendar, Sheets, Docs, and Contacts with multi-account support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
"semantic-release": "^25.0.3",
|
|
68
68
|
"typescript": "^6.0.3",
|
|
69
69
|
"typescript-eslint": "^8.59.1",
|
|
70
|
-
"vitest": "^
|
|
70
|
+
"vitest": "^4.1.5"
|
|
71
71
|
}
|
|
72
72
|
}
|