@shivaduke28/gmail-mcp 1.0.0 → 1.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/dist/index.js +106 -20
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -3,30 +3,34 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { encode } from "@toon-format/toon";
|
|
6
|
-
import { authorize } from "@shivaduke28/google-mcp-auth";
|
|
6
|
+
import { authorize, exchangeCode, AuthError, resolvePath } from "@shivaduke28/google-mcp-auth";
|
|
7
7
|
import { gmail as googleGmail } from "@googleapis/gmail";
|
|
8
8
|
import { extractHeaders, extractBody, buildRawMessage } from "./gmail.js";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
9
|
import { homedir } from "node:os";
|
|
11
10
|
import { join } from "node:path";
|
|
12
11
|
const SCOPES = [
|
|
13
12
|
"https://www.googleapis.com/auth/gmail.modify",
|
|
14
13
|
];
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
if (!existsSync(credentialsPath)) {
|
|
21
|
-
console.error(`credentials.json が見つかりません: ${credentialsPath}`);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const resolvedCredentialsPath = credentialsPath;
|
|
25
|
-
const resolvedTokensPath = process.env.GOOGLE_OAUTH_TOKENS ?? join(homedir(), ".config", "gmail-mcp", "tokens.json");
|
|
14
|
+
const rawCredentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;
|
|
15
|
+
const resolvedCredentialsPath = rawCredentialsPath ? resolvePath(rawCredentialsPath) : null;
|
|
16
|
+
const resolvedTokensPath = process.env.GOOGLE_OAUTH_TOKENS ? resolvePath(process.env.GOOGLE_OAUTH_TOKENS) : join(homedir(), ".config", "gmail-mcp", "tokens.json");
|
|
26
17
|
// lazy auth: ツール呼び出し時に初めて認証する
|
|
27
18
|
let gmailClient = null;
|
|
19
|
+
let pendingAuth = null;
|
|
20
|
+
function authErrorResponse(err) {
|
|
21
|
+
if (err.pendingAuth) {
|
|
22
|
+
pendingAuth = err.pendingAuth;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: `認証エラー: ${err.message}\n\n${err.details}` }],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
28
29
|
async function getGmail() {
|
|
29
30
|
if (!gmailClient) {
|
|
31
|
+
if (!resolvedCredentialsPath) {
|
|
32
|
+
throw new AuthError("GOOGLE_OAUTH_CREDENTIALS 環境変数が未設定です", "MCPサーバーの設定で GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください。");
|
|
33
|
+
}
|
|
30
34
|
const auth = await authorize(resolvedCredentialsPath, resolvedTokensPath, SCOPES);
|
|
31
35
|
gmailClient = googleGmail({ version: "v1", auth });
|
|
32
36
|
}
|
|
@@ -34,7 +38,7 @@ async function getGmail() {
|
|
|
34
38
|
}
|
|
35
39
|
const server = new McpServer({
|
|
36
40
|
name: "gmail-mcp",
|
|
37
|
-
version: "1.
|
|
41
|
+
version: "1.1.0",
|
|
38
42
|
});
|
|
39
43
|
// 1. search-messages
|
|
40
44
|
server.registerTool("search-messages", {
|
|
@@ -44,7 +48,15 @@ server.registerTool("search-messages", {
|
|
|
44
48
|
maxResults: z.number().optional().default(20).describe("最大取得件数"),
|
|
45
49
|
},
|
|
46
50
|
}, async ({ query, maxResults }) => {
|
|
47
|
-
|
|
51
|
+
let gmail;
|
|
52
|
+
try {
|
|
53
|
+
gmail = await getGmail();
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof AuthError)
|
|
57
|
+
return authErrorResponse(err);
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
48
60
|
const res = await gmail.users.messages.list({
|
|
49
61
|
userId: "me",
|
|
50
62
|
q: query,
|
|
@@ -92,7 +104,15 @@ server.registerTool("get-messages", {
|
|
|
92
104
|
messageIds: z.array(z.string()).describe("メッセージIDの配列"),
|
|
93
105
|
},
|
|
94
106
|
}, async ({ messageIds }) => {
|
|
95
|
-
|
|
107
|
+
let gmail;
|
|
108
|
+
try {
|
|
109
|
+
gmail = await getGmail();
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err instanceof AuthError)
|
|
113
|
+
return authErrorResponse(err);
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
96
116
|
const details = await Promise.all(messageIds.map((id) => gmail.users.messages.get({
|
|
97
117
|
userId: "me",
|
|
98
118
|
id,
|
|
@@ -127,7 +147,15 @@ server.registerTool("get-threads", {
|
|
|
127
147
|
threadIds: z.array(z.string()).describe("スレッドIDの配列"),
|
|
128
148
|
},
|
|
129
149
|
}, async ({ threadIds }) => {
|
|
130
|
-
|
|
150
|
+
let gmail;
|
|
151
|
+
try {
|
|
152
|
+
gmail = await getGmail();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
if (err instanceof AuthError)
|
|
156
|
+
return authErrorResponse(err);
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
131
159
|
const threads = await Promise.all(threadIds.map((id) => gmail.users.threads.get({
|
|
132
160
|
userId: "me",
|
|
133
161
|
id,
|
|
@@ -172,7 +200,15 @@ server.registerTool("create-draft", {
|
|
|
172
200
|
inReplyToMessageId: z.string().optional().describe("返信先メッセージID(返信時に指定。Referencesヘッダー構築用)"),
|
|
173
201
|
},
|
|
174
202
|
}, async ({ to, cc, subject, body, threadId, inReplyToMessageId }) => {
|
|
175
|
-
|
|
203
|
+
let gmail;
|
|
204
|
+
try {
|
|
205
|
+
gmail = await getGmail();
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err instanceof AuthError)
|
|
209
|
+
return authErrorResponse(err);
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
176
212
|
// 返信時のヘッダー構築
|
|
177
213
|
let inReplyTo;
|
|
178
214
|
let references;
|
|
@@ -222,7 +258,15 @@ server.registerTool("modify-labels", {
|
|
|
222
258
|
removeLabelIds: z.array(z.string()).optional().default([]).describe("削除するラベルIDの配列"),
|
|
223
259
|
},
|
|
224
260
|
}, async ({ messageIds, addLabelIds, removeLabelIds }) => {
|
|
225
|
-
|
|
261
|
+
let gmail;
|
|
262
|
+
try {
|
|
263
|
+
gmail = await getGmail();
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
if (err instanceof AuthError)
|
|
267
|
+
return authErrorResponse(err);
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
226
270
|
await gmail.users.messages.batchModify({
|
|
227
271
|
userId: "me",
|
|
228
272
|
requestBody: {
|
|
@@ -243,7 +287,15 @@ server.registerTool("list-labels", {
|
|
|
243
287
|
description: "利用可能なラベル一覧を取得する。レスポンスはTOON形式で返す。",
|
|
244
288
|
inputSchema: {},
|
|
245
289
|
}, async () => {
|
|
246
|
-
|
|
290
|
+
let gmail;
|
|
291
|
+
try {
|
|
292
|
+
gmail = await getGmail();
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
if (err instanceof AuthError)
|
|
296
|
+
return authErrorResponse(err);
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
247
299
|
const res = await gmail.users.labels.list({ userId: "me" });
|
|
248
300
|
const labels = (res.data.labels ?? []).map((label) => ({
|
|
249
301
|
id: label.id ?? "",
|
|
@@ -257,5 +309,39 @@ server.registerTool("list-labels", {
|
|
|
257
309
|
}],
|
|
258
310
|
};
|
|
259
311
|
});
|
|
312
|
+
// 7. submit-auth-code
|
|
313
|
+
server.registerTool("submit-auth-code", {
|
|
314
|
+
description: "OAuth認証コードを送信してGoogle認証を完了する。認証エラーで表示されたURLをブラウザで開き、認証後のリダイレクトURLに含まれる code パラメータの値を入力する。",
|
|
315
|
+
inputSchema: {
|
|
316
|
+
code: z.string().describe("認証後のリダイレクトURLに含まれる code パラメータの値"),
|
|
317
|
+
},
|
|
318
|
+
}, async ({ code }) => {
|
|
319
|
+
if (!pendingAuth) {
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: "text", text: "認証待ち状態ではありません。先にGmail関連のツールを呼び出して認証URLを取得してください。" }],
|
|
322
|
+
isError: true,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (!resolvedCredentialsPath) {
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: "GOOGLE_OAUTH_CREDENTIALS 環境変数が未設定です。" }],
|
|
328
|
+
isError: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
await exchangeCode(resolvedCredentialsPath, resolvedTokensPath, code, pendingAuth.codeVerifier);
|
|
333
|
+
pendingAuth = null;
|
|
334
|
+
gmailClient = null;
|
|
335
|
+
return {
|
|
336
|
+
content: [{ type: "text", text: "認証が完了しました!Gmailのツールが使えるようになりました。" }],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: `認証コードの交換に失敗しました。\n${err instanceof Error ? err.message : String(err)}` }],
|
|
342
|
+
isError: true,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
});
|
|
260
346
|
const transport = new StdioServerTransport();
|
|
261
347
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaduke28/gmail-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
"url": "https://github.com/shivaduke28/google-mcp.git",
|
|
18
18
|
"directory": "packages/gmail"
|
|
19
19
|
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"dev": "tsx src/index.ts",
|
|
24
|
+
"test": "tsx --test test/*.test.ts",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
20
27
|
"keywords": [
|
|
21
28
|
"mcp",
|
|
22
29
|
"gmail",
|
|
@@ -28,20 +35,13 @@
|
|
|
28
35
|
"dependencies": {
|
|
29
36
|
"@googleapis/gmail": "^16.1.1",
|
|
30
37
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
38
|
+
"@shivaduke28/google-mcp-auth": "workspace:*",
|
|
31
39
|
"@toon-format/toon": "^2.1.0",
|
|
32
|
-
"zod": "^4.3.6"
|
|
33
|
-
"@shivaduke28/google-mcp-auth": "1.0.0"
|
|
40
|
+
"zod": "^4.3.6"
|
|
34
41
|
},
|
|
35
42
|
"devDependencies": {
|
|
36
43
|
"@types/node": "^25.2.3",
|
|
37
44
|
"tsx": "^4.21.0",
|
|
38
45
|
"typescript": "^5.9.3"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsc",
|
|
42
|
-
"start": "node dist/index.js",
|
|
43
|
-
"dev": "tsx src/index.ts",
|
|
44
|
-
"test": "tsx --test test/*.test.ts",
|
|
45
|
-
"typecheck": "tsc --noEmit"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|