@shivaduke28/gmail-mcp 0.1.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/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # gmail-mcp
2
+
3
+ Gmail API の MCP (Model Context Protocol) サーバー。
4
+
5
+ OAuth スコープは `gmail.modify` のみ。送信・削除はできません。
6
+
7
+ ## Tools
8
+
9
+ | ツール | 説明 |
10
+ |---|---|
11
+ | `search-messages` | Gmail 検索クエリでメール一覧を取得する(TOON形式) |
12
+ | `get-messages` | メッセージIDから本文を含む詳細を一括取得する |
13
+ | `get-threads` | スレッドIDからスレッド全体を一括取得する |
14
+ | `create-draft` | 下書きを作成する(返信にも対応) |
15
+ | `modify-labels` | ラベルの追加・削除(アーカイブ = INBOX 削除) |
16
+ | `list-labels` | 利用可能なラベル一覧を取得する |
17
+
18
+ レスポンスは [TOON](https://github.com/toon-format/toon) 形式で返されます。
19
+
20
+ ## Setup
21
+
22
+ ### 1. GCP プロジェクトの準備
23
+
24
+ 1. [Google Cloud Console](https://console.cloud.google.com/) でプロジェクトを作成
25
+ 2. Gmail API を有効化
26
+ 3. OAuth 同意画面を設定
27
+ 4. OAuth 2.0 クライアント ID を作成(デスクトップアプリ)
28
+ 5. 認証情報の JSON ファイルをダウンロード → `credentials.json` として保存
29
+
30
+ ### 2. 使い方
31
+
32
+ #### npx(推奨)
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "gmail": {
38
+ "command": "npx",
39
+ "args": ["-y", "@shivaduke28/gmail-mcp"],
40
+ "env": {
41
+ "GOOGLE_OAUTH_CREDENTIALS": "/path/to/credentials.json",
42
+ "GOOGLE_OAUTH_TOKENS": "/path/to/tokens.json"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ #### ソースから実行
50
+
51
+ ```bash
52
+ npm install
53
+ npm run build
54
+ ```
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "gmail": {
60
+ "command": "node",
61
+ "args": ["/path/to/gmail-mcp/dist/index.js"],
62
+ "env": {
63
+ "GOOGLE_OAUTH_CREDENTIALS": "/path/to/credentials.json",
64
+ "GOOGLE_OAUTH_TOKENS": "/path/to/tokens.json"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### 3. 環境変数
72
+
73
+ | 変数 | 必須 | 説明 |
74
+ |---|---|---|
75
+ | `GOOGLE_OAUTH_CREDENTIALS` | Yes | OAuth クライアント認証情報の JSON ファイルパス |
76
+ | `GOOGLE_OAUTH_TOKENS` | Yes | ユーザートークンの保存先パス。初回認証時に自動生成 |
77
+
78
+ ### 4. 認証
79
+
80
+ 初回起動時にブラウザが開き、Google アカウントでの認証を求められます。認証後、トークンが `GOOGLE_OAUTH_TOKENS` で指定したパスに自動保存され、以降はブラウザ認証なしで起動できます。
81
+
82
+ ## Gmail 検索クエリの例
83
+
84
+ ```
85
+ from:user@example.com
86
+ to:user@example.com
87
+ subject:会議
88
+ is:unread
89
+ has:attachment
90
+ newer_than:7d
91
+ after:2026/01/01
92
+ label:INBOX
93
+ ```
94
+
95
+ 組み合わせも可能: `from:example.com subject:請求書 after:2026/01/01`
96
+
97
+ ## Development
98
+
99
+ ```bash
100
+ npm install
101
+ npm run dev # tsx で開発実行
102
+ npm run build # tsc でビルド
103
+ npm run test # テスト実行
104
+ npm run typecheck # 型チェック
105
+ ```
106
+
107
+ ## License
108
+
109
+ ISC
package/dist/auth.js ADDED
@@ -0,0 +1,70 @@
1
+ import { OAuth2Client } from "google-auth-library";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { createServer } from "node:http";
4
+ import { exec } from "node:child_process";
5
+ const SCOPES = [
6
+ "https://www.googleapis.com/auth/gmail.modify",
7
+ ];
8
+ export async function authorize(credentialsPath, tokensPath) {
9
+ const content = await readFile(credentialsPath, "utf-8");
10
+ const credentials = JSON.parse(content);
11
+ const { client_id, client_secret } = credentials.installed;
12
+ const oauth2Client = new OAuth2Client(client_id, client_secret, "http://localhost:3000/callback");
13
+ // 保存済みトークンがあれば読み込む
14
+ try {
15
+ const tokens = JSON.parse(await readFile(tokensPath, "utf-8"));
16
+ oauth2Client.setCredentials(tokens);
17
+ return oauth2Client;
18
+ }
19
+ catch {
20
+ // トークンがなければブラウザ認証
21
+ }
22
+ const tokens = await authenticateWithBrowser(oauth2Client);
23
+ await writeFile(tokensPath, JSON.stringify(tokens, null, 2));
24
+ oauth2Client.setCredentials(tokens);
25
+ return oauth2Client;
26
+ }
27
+ function authenticateWithBrowser(oauth2Client) {
28
+ return new Promise((resolve, reject) => {
29
+ const authUrl = oauth2Client.generateAuthUrl({
30
+ access_type: "offline",
31
+ scope: SCOPES,
32
+ });
33
+ const server = createServer(async (req, res) => {
34
+ if (!req.url?.startsWith("/callback"))
35
+ return;
36
+ const url = new URL(req.url, "http://localhost:3000");
37
+ const code = url.searchParams.get("code");
38
+ if (!code) {
39
+ res.writeHead(400);
40
+ res.end("No code received");
41
+ reject(new Error("No authorization code received"));
42
+ server.close();
43
+ return;
44
+ }
45
+ try {
46
+ const { tokens } = await oauth2Client.getToken(code);
47
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
48
+ res.end("<h1>認証成功!このタブを閉じてください。</h1>");
49
+ resolve(tokens);
50
+ }
51
+ catch (err) {
52
+ res.writeHead(500);
53
+ res.end("Token exchange failed");
54
+ reject(err);
55
+ }
56
+ finally {
57
+ server.close();
58
+ }
59
+ });
60
+ server.listen(3000, () => {
61
+ console.error(`\n認証が必要です。ブラウザを開きます...\n`);
62
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
63
+ exec(`${command} '${authUrl}'`, (err) => {
64
+ if (err) {
65
+ console.error(`ブラウザの自動起動に失敗しました。以下のURLを手動で開いてください:\n${authUrl}\n`);
66
+ }
67
+ });
68
+ });
69
+ });
70
+ }
package/dist/gmail.js ADDED
@@ -0,0 +1,67 @@
1
+ export function extractHeaders(headers) {
2
+ const get = (name) => {
3
+ return headers?.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? "";
4
+ };
5
+ return {
6
+ from: get("From"),
7
+ to: get("To"),
8
+ cc: get("Cc"),
9
+ subject: get("Subject"),
10
+ date: get("Date"),
11
+ };
12
+ }
13
+ export function extractBody(payload) {
14
+ if (!payload)
15
+ return "";
16
+ // text/plain を直接持っている場合
17
+ if (payload.mimeType === "text/plain" && payload.body?.data) {
18
+ return decodeBase64Url(payload.body.data);
19
+ }
20
+ // multipart の場合は再帰的にパース
21
+ if (payload.parts) {
22
+ // まず text/plain を探す
23
+ for (const part of payload.parts) {
24
+ if (part.mimeType === "text/plain" && part.body?.data) {
25
+ return decodeBase64Url(part.body.data);
26
+ }
27
+ }
28
+ // text/plain がなければ再帰的に探す
29
+ for (const part of payload.parts) {
30
+ const body = extractBody(part);
31
+ if (body)
32
+ return body;
33
+ }
34
+ }
35
+ return "";
36
+ }
37
+ function decodeBase64Url(data) {
38
+ const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
39
+ return Buffer.from(base64, "base64").toString("utf-8");
40
+ }
41
+ function encodeBase64Url(data) {
42
+ return Buffer.from(data, "utf-8")
43
+ .toString("base64")
44
+ .replace(/\+/g, "-")
45
+ .replace(/\//g, "_")
46
+ .replace(/=+$/, "");
47
+ }
48
+ export function buildRawMessage(to, cc, subject, body, threadId, inReplyTo, references) {
49
+ const lines = [];
50
+ lines.push(`To: ${to.join(", ")}`);
51
+ if (cc.length > 0) {
52
+ lines.push(`Cc: ${cc.join(", ")}`);
53
+ }
54
+ lines.push(`Subject: =?UTF-8?B?${Buffer.from(subject).toString("base64")}?=`);
55
+ lines.push("MIME-Version: 1.0");
56
+ lines.push("Content-Type: text/plain; charset=UTF-8");
57
+ lines.push("Content-Transfer-Encoding: base64");
58
+ if (inReplyTo) {
59
+ lines.push(`In-Reply-To: ${inReplyTo}`);
60
+ }
61
+ if (references) {
62
+ lines.push(`References: ${references}`);
63
+ }
64
+ lines.push("");
65
+ lines.push(Buffer.from(body).toString("base64"));
66
+ return encodeBase64Url(lines.join("\r\n"));
67
+ }
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { encode } from "@toon-format/toon";
6
+ import { authorize } from "./auth.js";
7
+ import { gmail as googleGmail } from "@googleapis/gmail";
8
+ import { extractHeaders, extractBody, buildRawMessage } from "./gmail.js";
9
+ import { existsSync } from "node:fs";
10
+ const credentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;
11
+ const tokensPath = process.env.GOOGLE_OAUTH_TOKENS;
12
+ if (!credentialsPath) {
13
+ console.error("GOOGLE_OAUTH_CREDENTIALS 環境変数を設定してください");
14
+ process.exit(1);
15
+ }
16
+ if (!tokensPath) {
17
+ console.error("GOOGLE_OAUTH_TOKENS 環境変数を設定してください");
18
+ process.exit(1);
19
+ }
20
+ if (!existsSync(credentialsPath)) {
21
+ console.error(`credentials.json が見つかりません: ${credentialsPath}`);
22
+ process.exit(1);
23
+ }
24
+ // OAuth2認証
25
+ const auth = await authorize(credentialsPath, tokensPath);
26
+ const gmail = googleGmail({ version: "v1", auth });
27
+ const server = new McpServer({
28
+ name: "gmail-mcp",
29
+ version: "0.1.0",
30
+ });
31
+ // 1. search-messages
32
+ server.registerTool("search-messages", {
33
+ description: "Gmail検索クエリでメール一覧を取得する。レスポンスはTOON形式で返す。",
34
+ inputSchema: {
35
+ query: z.string().describe("Gmail検索クエリ(例: from:user@example.com, subject:会議, is:unread など)"),
36
+ maxResults: z.number().optional().default(20).describe("最大取得件数"),
37
+ },
38
+ }, async ({ query, maxResults }) => {
39
+ const res = await gmail.users.messages.list({
40
+ userId: "me",
41
+ q: query,
42
+ maxResults,
43
+ });
44
+ const messageIds = res.data.messages ?? [];
45
+ if (messageIds.length === 0) {
46
+ return {
47
+ content: [{ type: "text", text: "メッセージが見つかりませんでした" }],
48
+ };
49
+ }
50
+ const details = await Promise.all(messageIds
51
+ .filter((msg) => msg.id)
52
+ .map((msg) => gmail.users.messages.get({
53
+ userId: "me",
54
+ id: msg.id,
55
+ format: "metadata",
56
+ metadataHeaders: ["From", "To", "Cc", "Subject", "Date"],
57
+ })));
58
+ const rows = details.map((detail) => {
59
+ const headers = extractHeaders(detail.data.payload?.headers);
60
+ return {
61
+ date: headers.date,
62
+ from: headers.from,
63
+ to: headers.to,
64
+ cc: headers.cc,
65
+ subject: headers.subject,
66
+ snippet: detail.data.snippet ?? "",
67
+ id: detail.data.id ?? "",
68
+ threadId: detail.data.threadId ?? "",
69
+ labels: (detail.data.labelIds ?? []).join(", "),
70
+ };
71
+ });
72
+ return {
73
+ content: [{
74
+ type: "text",
75
+ text: encode({ messages: rows }),
76
+ }],
77
+ };
78
+ });
79
+ // 2. get-messages
80
+ server.registerTool("get-messages", {
81
+ description: "複数のメッセージIDで本文を含むメール詳細を一括取得する。",
82
+ inputSchema: {
83
+ messageIds: z.array(z.string()).describe("メッセージIDの配列"),
84
+ },
85
+ }, async ({ messageIds }) => {
86
+ const details = await Promise.all(messageIds.map((id) => gmail.users.messages.get({
87
+ userId: "me",
88
+ id,
89
+ format: "full",
90
+ })));
91
+ const messages = details.map((detail) => {
92
+ const headers = extractHeaders(detail.data.payload?.headers);
93
+ const body = extractBody(detail.data.payload ?? undefined);
94
+ return {
95
+ id: detail.data.id ?? "",
96
+ threadId: detail.data.threadId ?? "",
97
+ labels: (detail.data.labelIds ?? []).join(", "),
98
+ date: headers.date,
99
+ from: headers.from,
100
+ to: headers.to,
101
+ cc: headers.cc,
102
+ subject: headers.subject,
103
+ body,
104
+ };
105
+ });
106
+ return {
107
+ content: [{
108
+ type: "text",
109
+ text: encode({ messages }),
110
+ }],
111
+ };
112
+ });
113
+ // 3. get-threads
114
+ server.registerTool("get-threads", {
115
+ description: "複数のスレッドIDでスレッド全体のメッセージを一括取得する。",
116
+ inputSchema: {
117
+ threadIds: z.array(z.string()).describe("スレッドIDの配列"),
118
+ },
119
+ }, async ({ threadIds }) => {
120
+ const threads = await Promise.all(threadIds.map((id) => gmail.users.threads.get({
121
+ userId: "me",
122
+ id,
123
+ format: "full",
124
+ })));
125
+ const result = threads.map((thread) => {
126
+ const messages = (thread.data.messages ?? []).map((msg) => {
127
+ const headers = extractHeaders(msg.payload?.headers);
128
+ const body = extractBody(msg.payload ?? undefined);
129
+ return {
130
+ id: msg.id ?? "",
131
+ date: headers.date,
132
+ from: headers.from,
133
+ to: headers.to,
134
+ cc: headers.cc,
135
+ subject: headers.subject,
136
+ labels: (msg.labelIds ?? []).join(", "),
137
+ body,
138
+ };
139
+ });
140
+ return {
141
+ threadId: thread.data.id ?? "",
142
+ messages,
143
+ };
144
+ });
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: encode({ threads: result }),
149
+ }],
150
+ };
151
+ });
152
+ // 4. create-draft
153
+ server.registerTool("create-draft", {
154
+ description: "メールの下書きを作成する。返信の場合はthreadIdとinReplyToMessageIdを指定する。",
155
+ inputSchema: {
156
+ to: z.array(z.string()).describe("宛先メールアドレスの配列"),
157
+ cc: z.array(z.string()).optional().default([]).describe("CCメールアドレスの配列"),
158
+ subject: z.string().describe("件名"),
159
+ body: z.string().describe("本文"),
160
+ threadId: z.string().optional().describe("返信先スレッドID(返信時に指定)"),
161
+ inReplyToMessageId: z.string().optional().describe("返信先メッセージID(返信時に指定。Referencesヘッダー構築用)"),
162
+ },
163
+ }, async ({ to, cc, subject, body, threadId, inReplyToMessageId }) => {
164
+ // 返信時のヘッダー構築
165
+ let inReplyTo;
166
+ let references;
167
+ if (inReplyToMessageId && threadId) {
168
+ try {
169
+ const origMsg = await gmail.users.messages.get({
170
+ userId: "me",
171
+ id: inReplyToMessageId,
172
+ format: "metadata",
173
+ metadataHeaders: ["Message-ID", "References"],
174
+ });
175
+ const origHeaders = origMsg.data.payload?.headers ?? [];
176
+ const messageIdHeader = origHeaders.find((h) => h.name?.toLowerCase() === "message-id")?.value ?? "";
177
+ const referencesHeader = origHeaders.find((h) => h.name?.toLowerCase() === "references")?.value ?? "";
178
+ if (messageIdHeader) {
179
+ inReplyTo = messageIdHeader;
180
+ references = referencesHeader ? `${referencesHeader} ${messageIdHeader}` : messageIdHeader;
181
+ }
182
+ }
183
+ catch {
184
+ // 元メッセージの取得に失敗しても下書き作成は続行
185
+ }
186
+ }
187
+ const raw = buildRawMessage(to, cc, subject, body, threadId, inReplyTo, references);
188
+ const draft = await gmail.users.drafts.create({
189
+ userId: "me",
190
+ requestBody: {
191
+ message: {
192
+ raw,
193
+ ...(threadId && { threadId }),
194
+ },
195
+ },
196
+ });
197
+ return {
198
+ content: [{
199
+ type: "text",
200
+ text: `下書きを作成しました (ID: ${draft.data.id})`,
201
+ }],
202
+ };
203
+ });
204
+ // 5. modify-labels
205
+ server.registerTool("modify-labels", {
206
+ description: "メッセージのラベルを追加/削除する。アーカイブはremoveLabelIdsに\"INBOX\"を指定する。",
207
+ inputSchema: {
208
+ messageIds: z.array(z.string()).describe("メッセージIDの配列"),
209
+ addLabelIds: z.array(z.string()).optional().default([]).describe("追加するラベルIDの配列"),
210
+ removeLabelIds: z.array(z.string()).optional().default([]).describe("削除するラベルIDの配列"),
211
+ },
212
+ }, async ({ messageIds, addLabelIds, removeLabelIds }) => {
213
+ await gmail.users.messages.batchModify({
214
+ userId: "me",
215
+ requestBody: {
216
+ ids: messageIds,
217
+ addLabelIds,
218
+ removeLabelIds,
219
+ },
220
+ });
221
+ return {
222
+ content: [{
223
+ type: "text",
224
+ text: `${messageIds.length}件のメッセージのラベルを更新しました。`,
225
+ }],
226
+ };
227
+ });
228
+ // 6. list-labels
229
+ server.registerTool("list-labels", {
230
+ description: "利用可能なラベル一覧を取得する。レスポンスはTOON形式で返す。",
231
+ inputSchema: {},
232
+ }, async () => {
233
+ const res = await gmail.users.labels.list({ userId: "me" });
234
+ const labels = (res.data.labels ?? []).map((label) => ({
235
+ id: label.id ?? "",
236
+ name: label.name ?? "",
237
+ type: label.type ?? "",
238
+ }));
239
+ return {
240
+ content: [{
241
+ type: "text",
242
+ text: encode({ labels }),
243
+ }],
244
+ };
245
+ });
246
+ const transport = new StdioServerTransport();
247
+ await server.connect(transport);
@@ -0,0 +1,94 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ export const PermissionAction = {
4
+ Allow: "allow",
5
+ Deny: "deny",
6
+ };
7
+ export const OperationType = {
8
+ Read: "read",
9
+ CreateDraft: "create_draft",
10
+ ModifyLabels: "modify_labels",
11
+ };
12
+ const DEFAULT_CONFIG = {
13
+ internalDomain: "",
14
+ permissions: {
15
+ read: {
16
+ self_only: PermissionAction.Allow,
17
+ internal: PermissionAction.Allow,
18
+ external: PermissionAction.Allow,
19
+ },
20
+ create_draft: {
21
+ self_only: PermissionAction.Allow,
22
+ internal: PermissionAction.Allow,
23
+ external: PermissionAction.Deny,
24
+ },
25
+ modify_labels: {
26
+ self_only: PermissionAction.Allow,
27
+ internal: PermissionAction.Allow,
28
+ external: PermissionAction.Allow,
29
+ },
30
+ },
31
+ };
32
+ export async function loadPermissionConfig(configPath) {
33
+ if (!configPath)
34
+ return DEFAULT_CONFIG;
35
+ // ファイルがなければデフォルト設定を書き出す
36
+ if (!existsSync(configPath)) {
37
+ try {
38
+ await writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
39
+ console.error(`permissions.json を作成しました: ${configPath}`);
40
+ }
41
+ catch {
42
+ console.error(`permissions.json の作成に失敗しました: ${configPath}`);
43
+ }
44
+ return DEFAULT_CONFIG;
45
+ }
46
+ try {
47
+ const content = await readFile(configPath, "utf-8");
48
+ const parsed = JSON.parse(content);
49
+ return {
50
+ internalDomain: parsed.internalDomain ?? DEFAULT_CONFIG.internalDomain,
51
+ permissions: {
52
+ read: parsed.permissions?.read ?? DEFAULT_CONFIG.permissions.read,
53
+ create_draft: parsed.permissions?.create_draft ?? DEFAULT_CONFIG.permissions.create_draft,
54
+ modify_labels: parsed.permissions?.modify_labels ?? DEFAULT_CONFIG.permissions.modify_labels,
55
+ },
56
+ };
57
+ }
58
+ catch {
59
+ console.error(`設定ファイルの読み込みに失敗しました: ${configPath}`);
60
+ return DEFAULT_CONFIG;
61
+ }
62
+ }
63
+ export const RecipientCondition = {
64
+ SelfOnly: "self_only",
65
+ Internal: "internal",
66
+ External: "external",
67
+ };
68
+ export function classifyRecipients(recipients, selfEmail, internalDomain) {
69
+ const others = recipients.filter((email) => email.toLowerCase() !== selfEmail.toLowerCase());
70
+ if (others.length === 0)
71
+ return RecipientCondition.SelfOnly;
72
+ if (internalDomain && others.every((email) => email.toLowerCase().endsWith(`@${internalDomain.toLowerCase()}`))) {
73
+ return RecipientCondition.Internal;
74
+ }
75
+ return RecipientCondition.External;
76
+ }
77
+ export function checkPermission(config, operation, recipients, selfEmail) {
78
+ const perm = config.permissions[operation];
79
+ const condition = classifyRecipients(recipients, selfEmail, config.internalDomain);
80
+ return { action: perm[condition], condition };
81
+ }
82
+ const CONDITION_LABELS = {
83
+ [RecipientCondition.SelfOnly]: "自分のみ",
84
+ [RecipientCondition.Internal]: "内部メンバー",
85
+ [RecipientCondition.External]: "外部宛先",
86
+ };
87
+ const OPERATION_LABELS = {
88
+ [OperationType.Read]: "読み取り",
89
+ [OperationType.CreateDraft]: "下書き作成",
90
+ [OperationType.ModifyLabels]: "ラベル変更",
91
+ };
92
+ export function denyMessage(operation, condition) {
93
+ return `${CONDITION_LABELS[condition]}への${OPERATION_LABELS[operation]}は許可されていません。`;
94
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@shivaduke28/gmail-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "gmail-mcp": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "dev": "tsx src/index.ts",
16
+ "test": "tsx --test test/*.test.ts",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/shivaduke28/gmail-mcp.git"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "gmail",
26
+ "model-context-protocol"
27
+ ],
28
+ "author": "shivaduke",
29
+ "license": "ISC",
30
+ "bugs": {
31
+ "url": "https://github.com/shivaduke28/gmail-mcp/issues"
32
+ },
33
+ "homepage": "https://github.com/shivaduke28/gmail-mcp#readme",
34
+ "description": "Gmail MCP server with domain-based permission control",
35
+ "dependencies": {
36
+ "@googleapis/gmail": "^16.1.1",
37
+ "@modelcontextprotocol/sdk": "^1.26.0",
38
+ "@toon-format/toon": "^2.1.0",
39
+ "google-auth-library": "^10.5.0",
40
+ "zod": "^4.3.6"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^25.2.3",
44
+ "tsx": "^4.21.0",
45
+ "typescript": "^5.9.3"
46
+ }
47
+ }