-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.ts
239 lines (213 loc) · 6.85 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
interface ServerConfig {
gas?: {
endpoint: string;
apiKey: string;
};
}
// 環境変数から設定を読み込む
const config: ServerConfig = {
gas:
process.env.GAS_ENDPOINT && process.env.VALID_API_KEY
? {
endpoint: process.env.GAS_ENDPOINT,
apiKey: process.env.VALID_API_KEY,
}
: undefined,
};
// 設定のバリデーション
if (!config.gas?.endpoint || !config.gas?.apiKey) {
console.error(
"GAS configuration is missing. Please check your environment variables."
);
process.exit(1);
}
// スキーマ定義
const GmailSearchSchema = z.object({
query: z.string().nonempty("query is required"),
});
const GmailGetMessageSchema = z.object({
messageId: z.string().nonempty("messageId is required"),
});
const GmailDownloadAttachmentSchema = z.object({
messageId: z.string().nonempty("messageId is required"),
attachmentId: z.string().nonempty("attachmentId is required"),
outputFilename: z.string().optional(),
});
// MCPサーバーインスタンス作成
const server = new Server(
{
name: "mcp-gmail",
version: "0.0.2",
},
{
capabilities: {
tools: {},
},
}
);
// ListToolsハンドラー
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = [
{
name: "gmail_search_messages",
description: `
Gmail内で指定したクエリに一致するメールを検索します。
queryパラメータはGmailの検索クエリ形式で指定します。
例: "subject:Meeting newer_than:1d"
結果はJSONで返り、メール一覧(件名、messageIdなど)を含みます。
`,
inputSchema: zodToJsonSchema(GmailSearchSchema) as ReturnType<
typeof zodToJsonSchema
>,
},
{
name: "gmail_get_message",
description: `
指定したmessageIdのメール本文と詳細を取得します。
引数: messageId (GmailのメッセージID)
`,
inputSchema: zodToJsonSchema(GmailGetMessageSchema) as ReturnType<
typeof zodToJsonSchema
>,
},
{
name: "gmail_download_attachment",
description: `
指定したmessageIdとattachmentIdで添付ファイルを取得します。
ファイルはDownloadsフォルダに保存されます。
attachmentIdはattachmentsの各attachmentのnameでありファイル名となることが多いです(invoice.pdfなど)。
引数:
- messageId: メッセージID(必須)
- attachmentId: 添付ファイルID(必須)
- outputFilename: 保存時のファイル名(オプション)
`,
inputSchema: zodToJsonSchema(GmailDownloadAttachmentSchema) as ReturnType<
typeof zodToJsonSchema
>,
},
];
return { tools };
});
// 共通のFetch関数 (GASエンドポイントへアクセス)
async function callGAS(
action: string,
params: Record<string, string>
): Promise<any> {
if (!config.gas) {
throw new Error("GAS configuration is missing");
}
const url = new URL(config.gas.endpoint);
url.searchParams.set("action", action);
url.searchParams.set("apiKey", config.gas.apiKey);
for (const key of Object.keys(params)) {
url.searchParams.set(key, params[key]);
}
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
}
// Tool呼び出しハンドラー
server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case "gmail_search_messages": {
const parsed = GmailSearchSchema.safeParse(args);
if (!parsed.success) {
throw new Error(
`Invalid arguments for gmail_search_messages: ${parsed.error}`
);
}
const data = await callGAS("search", { query: parsed.data.query });
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
case "gmail_get_message": {
const parsed = GmailGetMessageSchema.safeParse(args);
if (!parsed.success) {
throw new Error(
`Invalid arguments for gmail_get_message: ${parsed.error}`
);
}
const data = await callGAS("getMessage", {
messageId: parsed.data.messageId,
});
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
case "gmail_download_attachment": {
const parsed = GmailDownloadAttachmentSchema.safeParse(args);
if (!parsed.success) {
throw new Error(
`Invalid arguments for gmail_download_attachment: ${parsed.error}`
);
}
const data = await callGAS("downloadAttachment", {
messageId: parsed.data.messageId,
attachmentId: parsed.data.attachmentId,
});
// 添付ファイルをDownloadsフォルダに保存
const attachment = data.attachment;
if (!attachment || !attachment.base64 || !attachment.name) {
throw new Error("Invalid attachment data from API");
}
const downloadsDir = path.join(os.homedir(), "Downloads");
const filePath = path.join(
downloadsDir,
parsed.data.outputFilename || attachment.name
);
const fileBuffer = Buffer.from(attachment.base64, "base64");
fs.writeFileSync(filePath, fileBuffer);
return {
content: [
{
type: "text",
text: `添付ファイルを ${filePath} に保存しました。`,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${errorMessage}` }],
isError: true,
};
}
}
);
// サーバー起動
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Gmail Server running on stdio with API Key auth");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});