@shivaduke28/gmail-mcp 0.1.0 → 0.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/auth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { OAuth2Client } from "google-auth-library";
2
- import { readFile, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
3
4
  import { createServer } from "node:http";
4
5
  import { exec } from "node:child_process";
5
6
  const SCOPES = [
@@ -10,17 +11,34 @@ export async function authorize(credentialsPath, tokensPath) {
10
11
  const credentials = JSON.parse(content);
11
12
  const { client_id, client_secret } = credentials.installed;
12
13
  const oauth2Client = new OAuth2Client(client_id, client_secret, "http://localhost:3000/callback");
13
- // 保存済みトークンがあれば読み込む
14
+ // 保存済みトークンがあれば読み込み、有効性を確認
15
+ let hasTokens = false;
14
16
  try {
15
17
  const tokens = JSON.parse(await readFile(tokensPath, "utf-8"));
16
18
  oauth2Client.setCredentials(tokens);
17
- return oauth2Client;
19
+ hasTokens = true;
18
20
  }
19
21
  catch {
20
- // トークンがなければブラウザ認証
22
+ // ファイルなしまたは破損
23
+ }
24
+ if (hasTokens) {
25
+ try {
26
+ await oauth2Client.getAccessToken();
27
+ return oauth2Client;
28
+ }
29
+ catch (err) {
30
+ const status = err.status ?? err.code;
31
+ if (status === 401 || status === 400) {
32
+ // トークン失効 → ブラウザ認証へ
33
+ }
34
+ else {
35
+ throw err;
36
+ }
37
+ }
21
38
  }
22
39
  const tokens = await authenticateWithBrowser(oauth2Client);
23
- await writeFile(tokensPath, JSON.stringify(tokens, null, 2));
40
+ await mkdir(dirname(tokensPath), { recursive: true });
41
+ await writeFile(tokensPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
24
42
  oauth2Client.setCredentials(tokens);
25
43
  return oauth2Client;
26
44
  }
package/dist/index.js CHANGED
@@ -7,26 +7,31 @@ import { authorize } from "./auth.js";
7
7
  import { gmail as googleGmail } from "@googleapis/gmail";
8
8
  import { extractHeaders, extractBody, buildRawMessage } from "./gmail.js";
9
9
  import { existsSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
10
12
  const credentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;
11
- const tokensPath = process.env.GOOGLE_OAUTH_TOKENS;
12
13
  if (!credentialsPath) {
13
14
  console.error("GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください");
14
15
  process.exit(1);
15
16
  }
16
- if (!tokensPath) {
17
- console.error("GOOGLE_OAUTH_TOKENS 環境変数を設定してください");
18
- process.exit(1);
19
- }
20
17
  if (!existsSync(credentialsPath)) {
21
18
  console.error(`credentials.json が見つかりません: ${credentialsPath}`);
22
19
  process.exit(1);
23
20
  }
24
- // OAuth2認証
25
- const auth = await authorize(credentialsPath, tokensPath);
26
- const gmail = googleGmail({ version: "v1", auth });
21
+ const resolvedCredentialsPath = credentialsPath;
22
+ const resolvedTokensPath = process.env.GOOGLE_OAUTH_TOKENS ?? join(homedir(), ".config", "gmail-mcp", "tokens.json");
23
+ // lazy auth: ツール呼び出し時に初めて認証する
24
+ let gmailClient = null;
25
+ async function getGmail() {
26
+ if (!gmailClient) {
27
+ const auth = await authorize(resolvedCredentialsPath, resolvedTokensPath);
28
+ gmailClient = googleGmail({ version: "v1", auth });
29
+ }
30
+ return gmailClient;
31
+ }
27
32
  const server = new McpServer({
28
33
  name: "gmail-mcp",
29
- version: "0.1.0",
34
+ version: "0.2.0",
30
35
  });
31
36
  // 1. search-messages
32
37
  server.registerTool("search-messages", {
@@ -36,6 +41,7 @@ server.registerTool("search-messages", {
36
41
  maxResults: z.number().optional().default(20).describe("最大取得件数"),
37
42
  },
38
43
  }, async ({ query, maxResults }) => {
44
+ const gmail = await getGmail();
39
45
  const res = await gmail.users.messages.list({
40
46
  userId: "me",
41
47
  q: query,
@@ -83,6 +89,7 @@ server.registerTool("get-messages", {
83
89
  messageIds: z.array(z.string()).describe("メッセージIDの配列"),
84
90
  },
85
91
  }, async ({ messageIds }) => {
92
+ const gmail = await getGmail();
86
93
  const details = await Promise.all(messageIds.map((id) => gmail.users.messages.get({
87
94
  userId: "me",
88
95
  id,
@@ -117,6 +124,7 @@ server.registerTool("get-threads", {
117
124
  threadIds: z.array(z.string()).describe("スレッドIDの配列"),
118
125
  },
119
126
  }, async ({ threadIds }) => {
127
+ const gmail = await getGmail();
120
128
  const threads = await Promise.all(threadIds.map((id) => gmail.users.threads.get({
121
129
  userId: "me",
122
130
  id,
@@ -161,6 +169,7 @@ server.registerTool("create-draft", {
161
169
  inReplyToMessageId: z.string().optional().describe("返信先メッセージID(返信時に指定。Referencesヘッダー構築用)"),
162
170
  },
163
171
  }, async ({ to, cc, subject, body, threadId, inReplyToMessageId }) => {
172
+ const gmail = await getGmail();
164
173
  // 返信時のヘッダー構築
165
174
  let inReplyTo;
166
175
  let references;
@@ -210,6 +219,7 @@ server.registerTool("modify-labels", {
210
219
  removeLabelIds: z.array(z.string()).optional().default([]).describe("削除するラベルIDの配列"),
211
220
  },
212
221
  }, async ({ messageIds, addLabelIds, removeLabelIds }) => {
222
+ const gmail = await getGmail();
213
223
  await gmail.users.messages.batchModify({
214
224
  userId: "me",
215
225
  requestBody: {
@@ -230,6 +240,7 @@ server.registerTool("list-labels", {
230
240
  description: "利用可能なラベル一覧を取得する。レスポンスはTOON形式で返す。",
231
241
  inputSchema: {},
232
242
  }, async () => {
243
+ const gmail = await getGmail();
233
244
  const res = await gmail.users.labels.list({ userId: "me" });
234
245
  const labels = (res.data.labels ?? []).map((label) => ({
235
246
  id: label.id ?? "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaduke28/gmail-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {