@shivaduke28/gmail-mcp 1.1.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 +103 -18
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -3,31 +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, resolvePath } 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
14
|
const rawCredentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;
|
|
16
|
-
|
|
17
|
-
console.error("GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください");
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
const credentialsPath = resolvePath(rawCredentialsPath);
|
|
21
|
-
if (!existsSync(credentialsPath)) {
|
|
22
|
-
console.error(`credentials.json が見つかりません: ${credentialsPath}`);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
const resolvedCredentialsPath = credentialsPath;
|
|
15
|
+
const resolvedCredentialsPath = rawCredentialsPath ? resolvePath(rawCredentialsPath) : null;
|
|
26
16
|
const resolvedTokensPath = process.env.GOOGLE_OAUTH_TOKENS ? resolvePath(process.env.GOOGLE_OAUTH_TOKENS) : join(homedir(), ".config", "gmail-mcp", "tokens.json");
|
|
27
17
|
// lazy auth: ツール呼び出し時に初めて認証する
|
|
28
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
|
+
}
|
|
29
29
|
async function getGmail() {
|
|
30
30
|
if (!gmailClient) {
|
|
31
|
+
if (!resolvedCredentialsPath) {
|
|
32
|
+
throw new AuthError("GOOGLE_OAUTH_CREDENTIALS 環境変数が未設定です", "MCPサーバーの設定で GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください。");
|
|
33
|
+
}
|
|
31
34
|
const auth = await authorize(resolvedCredentialsPath, resolvedTokensPath, SCOPES);
|
|
32
35
|
gmailClient = googleGmail({ version: "v1", auth });
|
|
33
36
|
}
|
|
@@ -45,7 +48,15 @@ server.registerTool("search-messages", {
|
|
|
45
48
|
maxResults: z.number().optional().default(20).describe("最大取得件数"),
|
|
46
49
|
},
|
|
47
50
|
}, async ({ query, maxResults }) => {
|
|
48
|
-
|
|
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
|
+
}
|
|
49
60
|
const res = await gmail.users.messages.list({
|
|
50
61
|
userId: "me",
|
|
51
62
|
q: query,
|
|
@@ -93,7 +104,15 @@ server.registerTool("get-messages", {
|
|
|
93
104
|
messageIds: z.array(z.string()).describe("メッセージIDの配列"),
|
|
94
105
|
},
|
|
95
106
|
}, async ({ messageIds }) => {
|
|
96
|
-
|
|
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
|
+
}
|
|
97
116
|
const details = await Promise.all(messageIds.map((id) => gmail.users.messages.get({
|
|
98
117
|
userId: "me",
|
|
99
118
|
id,
|
|
@@ -128,7 +147,15 @@ server.registerTool("get-threads", {
|
|
|
128
147
|
threadIds: z.array(z.string()).describe("スレッドIDの配列"),
|
|
129
148
|
},
|
|
130
149
|
}, async ({ threadIds }) => {
|
|
131
|
-
|
|
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
|
+
}
|
|
132
159
|
const threads = await Promise.all(threadIds.map((id) => gmail.users.threads.get({
|
|
133
160
|
userId: "me",
|
|
134
161
|
id,
|
|
@@ -173,7 +200,15 @@ server.registerTool("create-draft", {
|
|
|
173
200
|
inReplyToMessageId: z.string().optional().describe("返信先メッセージID(返信時に指定。Referencesヘッダー構築用)"),
|
|
174
201
|
},
|
|
175
202
|
}, async ({ to, cc, subject, body, threadId, inReplyToMessageId }) => {
|
|
176
|
-
|
|
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
|
+
}
|
|
177
212
|
// 返信時のヘッダー構築
|
|
178
213
|
let inReplyTo;
|
|
179
214
|
let references;
|
|
@@ -223,7 +258,15 @@ server.registerTool("modify-labels", {
|
|
|
223
258
|
removeLabelIds: z.array(z.string()).optional().default([]).describe("削除するラベルIDの配列"),
|
|
224
259
|
},
|
|
225
260
|
}, async ({ messageIds, addLabelIds, removeLabelIds }) => {
|
|
226
|
-
|
|
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
|
+
}
|
|
227
270
|
await gmail.users.messages.batchModify({
|
|
228
271
|
userId: "me",
|
|
229
272
|
requestBody: {
|
|
@@ -244,7 +287,15 @@ server.registerTool("list-labels", {
|
|
|
244
287
|
description: "利用可能なラベル一覧を取得する。レスポンスはTOON形式で返す。",
|
|
245
288
|
inputSchema: {},
|
|
246
289
|
}, async () => {
|
|
247
|
-
|
|
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
|
+
}
|
|
248
299
|
const res = await gmail.users.labels.list({ userId: "me" });
|
|
249
300
|
const labels = (res.data.labels ?? []).map((label) => ({
|
|
250
301
|
id: label.id ?? "",
|
|
@@ -258,5 +309,39 @@ server.registerTool("list-labels", {
|
|
|
258
309
|
}],
|
|
259
310
|
};
|
|
260
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
|
+
});
|
|
261
346
|
const transport = new StdioServerTransport();
|
|
262
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.1.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
|
+
}
|