@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.
Files changed (2) hide show
  1. package/dist/index.js +106 -20
  2. 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 credentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;
16
- if (!credentialsPath) {
17
- console.error("GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください");
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.0.0",
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
- 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
+ }
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
- 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
+ }
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
- 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
+ }
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
- 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
+ }
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
- 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
+ }
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
- 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
+ }
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.0.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.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
+ }