@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 +23 -5
- package/dist/index.js +20 -9
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
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.
|
|
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 ?? "",
|