@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.
Files changed (2) hide show
  1. package/dist/index.js +103 -18
  2. 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
- if (!rawCredentialsPath) {
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
- const gmail = await getGmail();
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
- const gmail = await getGmail();
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
- const gmail = await getGmail();
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
- const gmail = await getGmail();
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
- const gmail = await getGmail();
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
- const gmail = await getGmail();
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.1.0",
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
+ }