mcpeasy 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.claudeignore +0 -3
- data/.mcp.json +19 -1
- data/CHANGELOG.md +59 -0
- data/CLAUDE.md +19 -5
- data/README.md +19 -3
- data/lib/mcpeasy/cli.rb +62 -10
- data/lib/mcpeasy/config.rb +22 -1
- data/lib/mcpeasy/setup.rb +1 -0
- data/lib/mcpeasy/version.rb +1 -1
- data/lib/utilities/gcal/README.md +11 -3
- data/lib/utilities/gcal/cli.rb +110 -108
- data/lib/utilities/gcal/mcp.rb +463 -308
- data/lib/utilities/gcal/service.rb +312 -0
- data/lib/utilities/gdrive/README.md +3 -3
- data/lib/utilities/gdrive/cli.rb +98 -96
- data/lib/utilities/gdrive/mcp.rb +290 -288
- data/lib/utilities/gdrive/service.rb +293 -0
- data/lib/utilities/gmail/README.md +278 -0
- data/lib/utilities/gmail/cli.rb +264 -0
- data/lib/utilities/gmail/mcp.rb +846 -0
- data/lib/utilities/gmail/service.rb +547 -0
- data/lib/utilities/gmeet/cli.rb +131 -129
- data/lib/utilities/gmeet/mcp.rb +374 -372
- data/lib/utilities/gmeet/service.rb +411 -0
- data/lib/utilities/notion/README.md +287 -0
- data/lib/utilities/notion/cli.rb +245 -0
- data/lib/utilities/notion/mcp.rb +607 -0
- data/lib/utilities/notion/service.rb +327 -0
- data/lib/utilities/slack/README.md +3 -3
- data/lib/utilities/slack/cli.rb +69 -54
- data/lib/utilities/slack/mcp.rb +277 -226
- data/lib/utilities/slack/service.rb +134 -0
- data/mcpeasy.gemspec +6 -1
- metadata +87 -10
- data/env.template +0 -11
- data/lib/utilities/gcal/gcal_tool.rb +0 -308
- data/lib/utilities/gdrive/gdrive_tool.rb +0 -291
- data/lib/utilities/gmeet/gmeet_tool.rb +0 -407
- data/lib/utilities/slack/slack_tool.rb +0 -119
- data/logs/.keep +0 -0
@@ -0,0 +1,846 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "json"
|
6
|
+
require_relative "service"
|
7
|
+
|
8
|
+
module Gmail
|
9
|
+
class MCPServer
|
10
|
+
def initialize
|
11
|
+
@prompts = [
|
12
|
+
{
|
13
|
+
name: "check_email",
|
14
|
+
description: "Check your email inbox for new messages",
|
15
|
+
arguments: [
|
16
|
+
{
|
17
|
+
name: "filter",
|
18
|
+
description: "Optional filter (e.g., 'unread', 'from:someone@example.com', 'subject:urgent')",
|
19
|
+
required: false
|
20
|
+
}
|
21
|
+
]
|
22
|
+
},
|
23
|
+
{
|
24
|
+
name: "compose_email",
|
25
|
+
description: "Compose and send a new email",
|
26
|
+
arguments: [
|
27
|
+
{
|
28
|
+
name: "to",
|
29
|
+
description: "Recipient email address",
|
30
|
+
required: true
|
31
|
+
},
|
32
|
+
{
|
33
|
+
name: "subject",
|
34
|
+
description: "Email subject line",
|
35
|
+
required: true
|
36
|
+
},
|
37
|
+
{
|
38
|
+
name: "body",
|
39
|
+
description: "Email body content",
|
40
|
+
required: true
|
41
|
+
}
|
42
|
+
]
|
43
|
+
},
|
44
|
+
{
|
45
|
+
name: "email_search",
|
46
|
+
description: "Search through your emails for specific content",
|
47
|
+
arguments: [
|
48
|
+
{
|
49
|
+
name: "query",
|
50
|
+
description: "Search query (e.g., keywords, sender, subject filters)",
|
51
|
+
required: true
|
52
|
+
}
|
53
|
+
]
|
54
|
+
},
|
55
|
+
{
|
56
|
+
name: "email_management",
|
57
|
+
description: "Manage emails (mark as read/unread, archive, label, etc.)",
|
58
|
+
arguments: [
|
59
|
+
{
|
60
|
+
name: "action",
|
61
|
+
description: "Action to perform (read, unread, archive, trash, label)",
|
62
|
+
required: true
|
63
|
+
},
|
64
|
+
{
|
65
|
+
name: "email_id",
|
66
|
+
description: "ID of the email to manage",
|
67
|
+
required: true
|
68
|
+
}
|
69
|
+
]
|
70
|
+
}
|
71
|
+
]
|
72
|
+
|
73
|
+
@tools = {
|
74
|
+
"test_connection" => {
|
75
|
+
name: "test_connection",
|
76
|
+
description: "Test the Gmail API connection",
|
77
|
+
inputSchema: {
|
78
|
+
type: "object",
|
79
|
+
properties: {},
|
80
|
+
required: []
|
81
|
+
}
|
82
|
+
},
|
83
|
+
"list_emails" => {
|
84
|
+
name: "list_emails",
|
85
|
+
description: "List emails with filtering by date range, sender, subject, labels, read/unread status",
|
86
|
+
inputSchema: {
|
87
|
+
type: "object",
|
88
|
+
properties: {
|
89
|
+
start_date: {
|
90
|
+
type: "string",
|
91
|
+
description: "Start date in YYYY-MM-DD format"
|
92
|
+
},
|
93
|
+
end_date: {
|
94
|
+
type: "string",
|
95
|
+
description: "End date in YYYY-MM-DD format"
|
96
|
+
},
|
97
|
+
max_results: {
|
98
|
+
type: "number",
|
99
|
+
description: "Maximum number of emails to return (default: 20)"
|
100
|
+
},
|
101
|
+
sender: {
|
102
|
+
type: "string",
|
103
|
+
description: "Filter by sender email address"
|
104
|
+
},
|
105
|
+
subject: {
|
106
|
+
type: "string",
|
107
|
+
description: "Filter by subject content"
|
108
|
+
},
|
109
|
+
labels: {
|
110
|
+
type: "string",
|
111
|
+
description: "Filter by labels (comma-separated)"
|
112
|
+
},
|
113
|
+
read_status: {
|
114
|
+
type: "string",
|
115
|
+
description: "Filter by read status (read/unread)"
|
116
|
+
}
|
117
|
+
},
|
118
|
+
required: []
|
119
|
+
}
|
120
|
+
},
|
121
|
+
"search_emails" => {
|
122
|
+
name: "search_emails",
|
123
|
+
description: "Search emails using Gmail search syntax",
|
124
|
+
inputSchema: {
|
125
|
+
type: "object",
|
126
|
+
properties: {
|
127
|
+
query: {
|
128
|
+
type: "string",
|
129
|
+
description: "Search query using Gmail search syntax"
|
130
|
+
},
|
131
|
+
max_results: {
|
132
|
+
type: "number",
|
133
|
+
description: "Maximum number of emails to return (default: 10)"
|
134
|
+
}
|
135
|
+
},
|
136
|
+
required: ["query"]
|
137
|
+
}
|
138
|
+
},
|
139
|
+
"get_email_content" => {
|
140
|
+
name: "get_email_content",
|
141
|
+
description: "Get full content of a specific email including body, headers, and attachments info",
|
142
|
+
inputSchema: {
|
143
|
+
type: "object",
|
144
|
+
properties: {
|
145
|
+
email_id: {
|
146
|
+
type: "string",
|
147
|
+
description: "Gmail message ID"
|
148
|
+
}
|
149
|
+
},
|
150
|
+
required: ["email_id"]
|
151
|
+
}
|
152
|
+
},
|
153
|
+
"send_email" => {
|
154
|
+
name: "send_email",
|
155
|
+
description: "Send a new email",
|
156
|
+
inputSchema: {
|
157
|
+
type: "object",
|
158
|
+
properties: {
|
159
|
+
to: {
|
160
|
+
type: "string",
|
161
|
+
description: "Recipient email address"
|
162
|
+
},
|
163
|
+
subject: {
|
164
|
+
type: "string",
|
165
|
+
description: "Email subject"
|
166
|
+
},
|
167
|
+
body: {
|
168
|
+
type: "string",
|
169
|
+
description: "Email body content"
|
170
|
+
},
|
171
|
+
cc: {
|
172
|
+
type: "string",
|
173
|
+
description: "CC email address"
|
174
|
+
},
|
175
|
+
bcc: {
|
176
|
+
type: "string",
|
177
|
+
description: "BCC email address"
|
178
|
+
},
|
179
|
+
reply_to: {
|
180
|
+
type: "string",
|
181
|
+
description: "Reply-to email address"
|
182
|
+
}
|
183
|
+
},
|
184
|
+
required: ["to", "subject", "body"]
|
185
|
+
}
|
186
|
+
},
|
187
|
+
"reply_to_email" => {
|
188
|
+
name: "reply_to_email",
|
189
|
+
description: "Reply to an existing email",
|
190
|
+
inputSchema: {
|
191
|
+
type: "object",
|
192
|
+
properties: {
|
193
|
+
email_id: {
|
194
|
+
type: "string",
|
195
|
+
description: "Gmail message ID to reply to"
|
196
|
+
},
|
197
|
+
body: {
|
198
|
+
type: "string",
|
199
|
+
description: "Reply body content"
|
200
|
+
},
|
201
|
+
include_quoted: {
|
202
|
+
type: "boolean",
|
203
|
+
description: "Include quoted original message (default: true)"
|
204
|
+
}
|
205
|
+
},
|
206
|
+
required: ["email_id", "body"]
|
207
|
+
}
|
208
|
+
},
|
209
|
+
"mark_as_read" => {
|
210
|
+
name: "mark_as_read",
|
211
|
+
description: "Mark an email as read",
|
212
|
+
inputSchema: {
|
213
|
+
type: "object",
|
214
|
+
properties: {
|
215
|
+
email_id: {
|
216
|
+
type: "string",
|
217
|
+
description: "Gmail message ID"
|
218
|
+
}
|
219
|
+
},
|
220
|
+
required: ["email_id"]
|
221
|
+
}
|
222
|
+
},
|
223
|
+
"mark_as_unread" => {
|
224
|
+
name: "mark_as_unread",
|
225
|
+
description: "Mark an email as unread",
|
226
|
+
inputSchema: {
|
227
|
+
type: "object",
|
228
|
+
properties: {
|
229
|
+
email_id: {
|
230
|
+
type: "string",
|
231
|
+
description: "Gmail message ID"
|
232
|
+
}
|
233
|
+
},
|
234
|
+
required: ["email_id"]
|
235
|
+
}
|
236
|
+
},
|
237
|
+
"add_label" => {
|
238
|
+
name: "add_label",
|
239
|
+
description: "Add a label to an email",
|
240
|
+
inputSchema: {
|
241
|
+
type: "object",
|
242
|
+
properties: {
|
243
|
+
email_id: {
|
244
|
+
type: "string",
|
245
|
+
description: "Gmail message ID"
|
246
|
+
},
|
247
|
+
label: {
|
248
|
+
type: "string",
|
249
|
+
description: "Label name to add"
|
250
|
+
}
|
251
|
+
},
|
252
|
+
required: ["email_id", "label"]
|
253
|
+
}
|
254
|
+
},
|
255
|
+
"remove_label" => {
|
256
|
+
name: "remove_label",
|
257
|
+
description: "Remove a label from an email",
|
258
|
+
inputSchema: {
|
259
|
+
type: "object",
|
260
|
+
properties: {
|
261
|
+
email_id: {
|
262
|
+
type: "string",
|
263
|
+
description: "Gmail message ID"
|
264
|
+
},
|
265
|
+
label: {
|
266
|
+
type: "string",
|
267
|
+
description: "Label name to remove"
|
268
|
+
}
|
269
|
+
},
|
270
|
+
required: ["email_id", "label"]
|
271
|
+
}
|
272
|
+
},
|
273
|
+
"archive_email" => {
|
274
|
+
name: "archive_email",
|
275
|
+
description: "Archive an email (remove from inbox)",
|
276
|
+
inputSchema: {
|
277
|
+
type: "object",
|
278
|
+
properties: {
|
279
|
+
email_id: {
|
280
|
+
type: "string",
|
281
|
+
description: "Gmail message ID"
|
282
|
+
}
|
283
|
+
},
|
284
|
+
required: ["email_id"]
|
285
|
+
}
|
286
|
+
},
|
287
|
+
"trash_email" => {
|
288
|
+
name: "trash_email",
|
289
|
+
description: "Move an email to trash",
|
290
|
+
inputSchema: {
|
291
|
+
type: "object",
|
292
|
+
properties: {
|
293
|
+
email_id: {
|
294
|
+
type: "string",
|
295
|
+
description: "Gmail message ID"
|
296
|
+
}
|
297
|
+
},
|
298
|
+
required: ["email_id"]
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
end
|
303
|
+
|
304
|
+
def run
|
305
|
+
# Disable stdout buffering for immediate response
|
306
|
+
$stdout.sync = true
|
307
|
+
|
308
|
+
# Log startup to file instead of stdout to avoid protocol interference
|
309
|
+
Mcpeasy::Config.ensure_config_dirs
|
310
|
+
File.write(Mcpeasy::Config.log_file_path("gmail", "startup"), "#{Time.now}: Gmail MCP Server starting on stdio\n", mode: "a")
|
311
|
+
while (line = $stdin.gets)
|
312
|
+
handle_request(line.strip)
|
313
|
+
end
|
314
|
+
rescue Interrupt
|
315
|
+
# Silent shutdown
|
316
|
+
rescue => e
|
317
|
+
# Log to a file instead of stderr to avoid protocol interference
|
318
|
+
File.write(Mcpeasy::Config.log_file_path("gmail", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
|
323
|
+
def handle_request(line)
|
324
|
+
return if line.empty?
|
325
|
+
|
326
|
+
begin
|
327
|
+
request = JSON.parse(line)
|
328
|
+
response = process_request(request)
|
329
|
+
if response
|
330
|
+
puts JSON.generate(response)
|
331
|
+
$stdout.flush
|
332
|
+
end
|
333
|
+
rescue JSON::ParserError => e
|
334
|
+
error_response = {
|
335
|
+
jsonrpc: "2.0",
|
336
|
+
id: nil,
|
337
|
+
error: {
|
338
|
+
code: -32700,
|
339
|
+
message: "Parse error",
|
340
|
+
data: e.message
|
341
|
+
}
|
342
|
+
}
|
343
|
+
puts JSON.generate(error_response)
|
344
|
+
$stdout.flush
|
345
|
+
rescue => e
|
346
|
+
File.write(Mcpeasy::Config.log_file_path("gmail", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
347
|
+
error_response = {
|
348
|
+
jsonrpc: "2.0",
|
349
|
+
id: request&.dig("id"),
|
350
|
+
error: {
|
351
|
+
code: -32603,
|
352
|
+
message: "Internal error",
|
353
|
+
data: e.message
|
354
|
+
}
|
355
|
+
}
|
356
|
+
puts JSON.generate(error_response)
|
357
|
+
$stdout.flush
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def process_request(request)
|
362
|
+
id = request["id"]
|
363
|
+
method = request["method"]
|
364
|
+
params = request["params"] || {}
|
365
|
+
|
366
|
+
case method
|
367
|
+
when "notifications/initialized"
|
368
|
+
# Client acknowledgment - no response needed
|
369
|
+
nil
|
370
|
+
when "initialize"
|
371
|
+
initialize_response(id, params)
|
372
|
+
when "tools/list"
|
373
|
+
tools_list_response(id, params)
|
374
|
+
when "tools/call"
|
375
|
+
tools_call_response(id, params)
|
376
|
+
when "prompts/list"
|
377
|
+
prompts_list_response(id, params)
|
378
|
+
when "prompts/get"
|
379
|
+
prompts_get_response(id, params)
|
380
|
+
else
|
381
|
+
{
|
382
|
+
jsonrpc: "2.0",
|
383
|
+
id: id,
|
384
|
+
error: {
|
385
|
+
code: -32601,
|
386
|
+
message: "Method not found",
|
387
|
+
data: "Unknown method: #{method}"
|
388
|
+
}
|
389
|
+
}
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def initialize_response(id, params)
|
394
|
+
{
|
395
|
+
jsonrpc: "2.0",
|
396
|
+
id: id,
|
397
|
+
result: {
|
398
|
+
protocolVersion: "2024-11-05",
|
399
|
+
capabilities: {
|
400
|
+
tools: {},
|
401
|
+
prompts: {}
|
402
|
+
},
|
403
|
+
serverInfo: {
|
404
|
+
name: "gmail-mcp-server",
|
405
|
+
version: "1.0.0"
|
406
|
+
}
|
407
|
+
}
|
408
|
+
}
|
409
|
+
end
|
410
|
+
|
411
|
+
def tools_list_response(id, params)
|
412
|
+
{
|
413
|
+
jsonrpc: "2.0",
|
414
|
+
id: id,
|
415
|
+
result: {
|
416
|
+
tools: @tools.values
|
417
|
+
}
|
418
|
+
}
|
419
|
+
end
|
420
|
+
|
421
|
+
def tools_call_response(id, params)
|
422
|
+
tool_name = params["name"]
|
423
|
+
arguments = params["arguments"] || {}
|
424
|
+
|
425
|
+
unless @tools.key?(tool_name)
|
426
|
+
return {
|
427
|
+
jsonrpc: "2.0",
|
428
|
+
id: id,
|
429
|
+
error: {
|
430
|
+
code: -32602,
|
431
|
+
message: "Unknown tool",
|
432
|
+
data: "Tool '#{tool_name}' not found"
|
433
|
+
}
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
begin
|
438
|
+
result = call_tool(tool_name, arguments)
|
439
|
+
{
|
440
|
+
jsonrpc: "2.0",
|
441
|
+
id: id,
|
442
|
+
result: {
|
443
|
+
content: [
|
444
|
+
{
|
445
|
+
type: "text",
|
446
|
+
text: result
|
447
|
+
}
|
448
|
+
],
|
449
|
+
isError: false
|
450
|
+
}
|
451
|
+
}
|
452
|
+
rescue => e
|
453
|
+
File.write(Mcpeasy::Config.log_file_path("gmail", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
454
|
+
{
|
455
|
+
jsonrpc: "2.0",
|
456
|
+
id: id,
|
457
|
+
result: {
|
458
|
+
content: [
|
459
|
+
{
|
460
|
+
type: "text",
|
461
|
+
text: "❌ Error: #{e.message}"
|
462
|
+
}
|
463
|
+
],
|
464
|
+
isError: true
|
465
|
+
}
|
466
|
+
}
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def prompts_list_response(id, params)
|
471
|
+
{
|
472
|
+
jsonrpc: "2.0",
|
473
|
+
id: id,
|
474
|
+
result: {
|
475
|
+
prompts: @prompts
|
476
|
+
}
|
477
|
+
}
|
478
|
+
end
|
479
|
+
|
480
|
+
def prompts_get_response(id, params)
|
481
|
+
prompt_name = params["name"]
|
482
|
+
prompt = @prompts.find { |p| p[:name] == prompt_name }
|
483
|
+
|
484
|
+
unless prompt
|
485
|
+
return {
|
486
|
+
jsonrpc: "2.0",
|
487
|
+
id: id,
|
488
|
+
error: {
|
489
|
+
code: -32602,
|
490
|
+
message: "Unknown prompt",
|
491
|
+
data: "Prompt '#{prompt_name}' not found"
|
492
|
+
}
|
493
|
+
}
|
494
|
+
end
|
495
|
+
|
496
|
+
# Generate messages based on the prompt
|
497
|
+
messages = case prompt_name
|
498
|
+
when "check_email"
|
499
|
+
filter = params["arguments"]&.dig("filter") || ""
|
500
|
+
filter_text = filter.empty? ? "" : " with filter: #{filter}"
|
501
|
+
[
|
502
|
+
{
|
503
|
+
role: "user",
|
504
|
+
content: {
|
505
|
+
type: "text",
|
506
|
+
text: "Check my email inbox#{filter_text}"
|
507
|
+
}
|
508
|
+
}
|
509
|
+
]
|
510
|
+
when "compose_email"
|
511
|
+
to = params["arguments"]&.dig("to") || "recipient@example.com"
|
512
|
+
subject = params["arguments"]&.dig("subject") || "Subject"
|
513
|
+
body = params["arguments"]&.dig("body") || "Email content"
|
514
|
+
[
|
515
|
+
{
|
516
|
+
role: "user",
|
517
|
+
content: {
|
518
|
+
type: "text",
|
519
|
+
text: "Compose and send an email to #{to} with subject '#{subject}' and body: #{body}"
|
520
|
+
}
|
521
|
+
}
|
522
|
+
]
|
523
|
+
when "email_search"
|
524
|
+
query = params["arguments"]&.dig("query") || ""
|
525
|
+
[
|
526
|
+
{
|
527
|
+
role: "user",
|
528
|
+
content: {
|
529
|
+
type: "text",
|
530
|
+
text: "Search my emails for: #{query}"
|
531
|
+
}
|
532
|
+
}
|
533
|
+
]
|
534
|
+
when "email_management"
|
535
|
+
action = params["arguments"]&.dig("action") || "read"
|
536
|
+
email_id = params["arguments"]&.dig("email_id") || "email_id"
|
537
|
+
[
|
538
|
+
{
|
539
|
+
role: "user",
|
540
|
+
content: {
|
541
|
+
type: "text",
|
542
|
+
text: "Perform #{action} action on email #{email_id}"
|
543
|
+
}
|
544
|
+
}
|
545
|
+
]
|
546
|
+
else
|
547
|
+
[]
|
548
|
+
end
|
549
|
+
|
550
|
+
{
|
551
|
+
jsonrpc: "2.0",
|
552
|
+
id: id,
|
553
|
+
result: {
|
554
|
+
description: prompt[:description],
|
555
|
+
messages: messages
|
556
|
+
}
|
557
|
+
}
|
558
|
+
end
|
559
|
+
|
560
|
+
def call_tool(tool_name, arguments)
|
561
|
+
case tool_name
|
562
|
+
when "test_connection"
|
563
|
+
test_connection
|
564
|
+
when "list_emails"
|
565
|
+
list_emails(arguments)
|
566
|
+
when "search_emails"
|
567
|
+
search_emails(arguments)
|
568
|
+
when "get_email_content"
|
569
|
+
get_email_content(arguments)
|
570
|
+
when "send_email"
|
571
|
+
send_email(arguments)
|
572
|
+
when "reply_to_email"
|
573
|
+
reply_to_email(arguments)
|
574
|
+
when "mark_as_read"
|
575
|
+
mark_as_read(arguments)
|
576
|
+
when "mark_as_unread"
|
577
|
+
mark_as_unread(arguments)
|
578
|
+
when "add_label"
|
579
|
+
add_label(arguments)
|
580
|
+
when "remove_label"
|
581
|
+
remove_label(arguments)
|
582
|
+
when "archive_email"
|
583
|
+
archive_email(arguments)
|
584
|
+
when "trash_email"
|
585
|
+
trash_email(arguments)
|
586
|
+
else
|
587
|
+
raise "Unknown tool: #{tool_name}"
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def test_connection
|
592
|
+
tool = Service.new
|
593
|
+
response = tool.test_connection
|
594
|
+
if response[:ok]
|
595
|
+
"✅ Successfully connected to Gmail.\n" \
|
596
|
+
" Email: #{response[:email]}\n" \
|
597
|
+
" Messages: #{response[:messages_total]}\n" \
|
598
|
+
" Threads: #{response[:threads_total]}"
|
599
|
+
else
|
600
|
+
raise "Connection test failed"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def list_emails(arguments)
|
605
|
+
start_date = arguments["start_date"]
|
606
|
+
end_date = arguments["end_date"]
|
607
|
+
max_results = arguments["max_results"]&.to_i || 20
|
608
|
+
sender = arguments["sender"]
|
609
|
+
subject = arguments["subject"]
|
610
|
+
labels = arguments["labels"]&.split(",")&.map(&:strip)
|
611
|
+
read_status = arguments["read_status"]
|
612
|
+
|
613
|
+
tool = Service.new
|
614
|
+
result = tool.list_emails(
|
615
|
+
start_date: start_date,
|
616
|
+
end_date: end_date,
|
617
|
+
max_results: max_results,
|
618
|
+
sender: sender,
|
619
|
+
subject: subject,
|
620
|
+
labels: labels,
|
621
|
+
read_status: read_status
|
622
|
+
)
|
623
|
+
emails = result[:emails]
|
624
|
+
|
625
|
+
if emails.empty?
|
626
|
+
"📧 No emails found for the specified criteria"
|
627
|
+
else
|
628
|
+
output = "📧 Found #{result[:count]} email(s):\n\n"
|
629
|
+
emails.each_with_index do |email, index|
|
630
|
+
output << "#{index + 1}. **#{email[:subject] || "No subject"}**\n"
|
631
|
+
output << " - From: #{email[:from]}\n"
|
632
|
+
output << " - Date: #{email[:date]}\n"
|
633
|
+
output << " - Snippet: #{email[:snippet]}\n" if email[:snippet]
|
634
|
+
output << " - Labels: #{email[:labels].join(", ")}\n" if email[:labels]&.any?
|
635
|
+
output << " - ID: `#{email[:id]}`\n\n"
|
636
|
+
end
|
637
|
+
output
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
def search_emails(arguments)
|
642
|
+
unless arguments["query"]
|
643
|
+
raise "Missing required argument: query"
|
644
|
+
end
|
645
|
+
|
646
|
+
query = arguments["query"].to_s
|
647
|
+
max_results = arguments["max_results"]&.to_i || 10
|
648
|
+
|
649
|
+
tool = Service.new
|
650
|
+
result = tool.search_emails(query, max_results: max_results)
|
651
|
+
emails = result[:emails]
|
652
|
+
|
653
|
+
if emails.empty?
|
654
|
+
"🔍 No emails found matching '#{query}'"
|
655
|
+
else
|
656
|
+
output = "🔍 Found #{result[:count]} email(s) matching '#{query}':\n\n"
|
657
|
+
emails.each_with_index do |email, index|
|
658
|
+
output << "#{index + 1}. **#{email[:subject] || "No subject"}**\n"
|
659
|
+
output << " - From: #{email[:from]}\n"
|
660
|
+
output << " - Date: #{email[:date]}\n"
|
661
|
+
output << " - Snippet: #{email[:snippet]}\n" if email[:snippet]
|
662
|
+
output << " - ID: `#{email[:id]}`\n\n"
|
663
|
+
end
|
664
|
+
output
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def get_email_content(arguments)
|
669
|
+
unless arguments["email_id"]
|
670
|
+
raise "Missing required argument: email_id"
|
671
|
+
end
|
672
|
+
|
673
|
+
email_id = arguments["email_id"].to_s
|
674
|
+
tool = Service.new
|
675
|
+
email = tool.get_email_content(email_id)
|
676
|
+
|
677
|
+
output = "📧 **Email Details:**\n\n"
|
678
|
+
output << "- **ID:** `#{email[:id]}`\n"
|
679
|
+
output << "- **Thread ID:** `#{email[:thread_id]}`\n"
|
680
|
+
output << "- **Subject:** #{email[:subject] || "No subject"}\n"
|
681
|
+
output << "- **From:** #{email[:from]}\n"
|
682
|
+
output << "- **To:** #{email[:to]}\n"
|
683
|
+
output << "- **CC:** #{email[:cc]}\n" if email[:cc]
|
684
|
+
output << "- **BCC:** #{email[:bcc]}\n" if email[:bcc]
|
685
|
+
output << "- **Date:** #{email[:date]}\n"
|
686
|
+
output << "- **Labels:** #{email[:labels].join(", ")}\n" if email[:labels]&.any?
|
687
|
+
|
688
|
+
if email[:attachments]&.any?
|
689
|
+
output << "- **Attachments:**\n"
|
690
|
+
email[:attachments].each do |attachment|
|
691
|
+
output << " - #{attachment[:filename]} (#{attachment[:mime_type]}, #{attachment[:size]} bytes)\n"
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
output << "\n**Body:**\n```\n#{email[:body]}\n```"
|
696
|
+
output
|
697
|
+
end
|
698
|
+
|
699
|
+
def send_email(arguments)
|
700
|
+
required_args = ["to", "subject", "body"]
|
701
|
+
missing_args = required_args.select { |arg| arguments[arg].nil? || arguments[arg].empty? }
|
702
|
+
unless missing_args.empty?
|
703
|
+
raise "Missing required arguments: #{missing_args.join(", ")}"
|
704
|
+
end
|
705
|
+
|
706
|
+
tool = Service.new
|
707
|
+
result = tool.send_email(
|
708
|
+
to: arguments["to"],
|
709
|
+
subject: arguments["subject"],
|
710
|
+
body: arguments["body"],
|
711
|
+
cc: arguments["cc"],
|
712
|
+
bcc: arguments["bcc"],
|
713
|
+
reply_to: arguments["reply_to"]
|
714
|
+
)
|
715
|
+
|
716
|
+
if result[:success]
|
717
|
+
"✅ Email sent successfully\n" \
|
718
|
+
" Message ID: #{result[:message_id]}\n" \
|
719
|
+
" Thread ID: #{result[:thread_id]}"
|
720
|
+
else
|
721
|
+
raise "Failed to send email"
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
def reply_to_email(arguments)
|
726
|
+
required_args = ["email_id", "body"]
|
727
|
+
missing_args = required_args.select { |arg| arguments[arg].nil? || arguments[arg].empty? }
|
728
|
+
unless missing_args.empty?
|
729
|
+
raise "Missing required arguments: #{missing_args.join(", ")}"
|
730
|
+
end
|
731
|
+
|
732
|
+
tool = Service.new
|
733
|
+
result = tool.reply_to_email(
|
734
|
+
email_id: arguments["email_id"],
|
735
|
+
body: arguments["body"],
|
736
|
+
include_quoted: arguments["include_quoted"] != false
|
737
|
+
)
|
738
|
+
|
739
|
+
if result[:success]
|
740
|
+
"✅ Reply sent successfully\n" \
|
741
|
+
" Message ID: #{result[:message_id]}\n" \
|
742
|
+
" Thread ID: #{result[:thread_id]}"
|
743
|
+
else
|
744
|
+
raise "Failed to send reply"
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
def mark_as_read(arguments)
|
749
|
+
unless arguments["email_id"]
|
750
|
+
raise "Missing required argument: email_id"
|
751
|
+
end
|
752
|
+
|
753
|
+
tool = Service.new
|
754
|
+
result = tool.mark_as_read(arguments["email_id"])
|
755
|
+
|
756
|
+
if result[:success]
|
757
|
+
"✅ Email marked as read"
|
758
|
+
else
|
759
|
+
raise "Failed to mark email as read"
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def mark_as_unread(arguments)
|
764
|
+
unless arguments["email_id"]
|
765
|
+
raise "Missing required argument: email_id"
|
766
|
+
end
|
767
|
+
|
768
|
+
tool = Service.new
|
769
|
+
result = tool.mark_as_unread(arguments["email_id"])
|
770
|
+
|
771
|
+
if result[:success]
|
772
|
+
"✅ Email marked as unread"
|
773
|
+
else
|
774
|
+
raise "Failed to mark email as unread"
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
def add_label(arguments)
|
779
|
+
required_args = ["email_id", "label"]
|
780
|
+
missing_args = required_args.select { |arg| arguments[arg].nil? || arguments[arg].empty? }
|
781
|
+
unless missing_args.empty?
|
782
|
+
raise "Missing required arguments: #{missing_args.join(", ")}"
|
783
|
+
end
|
784
|
+
|
785
|
+
tool = Service.new
|
786
|
+
result = tool.add_label(arguments["email_id"], arguments["label"])
|
787
|
+
|
788
|
+
if result[:success]
|
789
|
+
"✅ Label '#{arguments["label"]}' added to email"
|
790
|
+
else
|
791
|
+
raise "Failed to add label to email"
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
def remove_label(arguments)
|
796
|
+
required_args = ["email_id", "label"]
|
797
|
+
missing_args = required_args.select { |arg| arguments[arg].nil? || arguments[arg].empty? }
|
798
|
+
unless missing_args.empty?
|
799
|
+
raise "Missing required arguments: #{missing_args.join(", ")}"
|
800
|
+
end
|
801
|
+
|
802
|
+
tool = Service.new
|
803
|
+
result = tool.remove_label(arguments["email_id"], arguments["label"])
|
804
|
+
|
805
|
+
if result[:success]
|
806
|
+
"✅ Label '#{arguments["label"]}' removed from email"
|
807
|
+
else
|
808
|
+
raise "Failed to remove label from email"
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
def archive_email(arguments)
|
813
|
+
unless arguments["email_id"]
|
814
|
+
raise "Missing required argument: email_id"
|
815
|
+
end
|
816
|
+
|
817
|
+
tool = Service.new
|
818
|
+
result = tool.archive_email(arguments["email_id"])
|
819
|
+
|
820
|
+
if result[:success]
|
821
|
+
"✅ Email archived"
|
822
|
+
else
|
823
|
+
raise "Failed to archive email"
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
def trash_email(arguments)
|
828
|
+
unless arguments["email_id"]
|
829
|
+
raise "Missing required argument: email_id"
|
830
|
+
end
|
831
|
+
|
832
|
+
tool = Service.new
|
833
|
+
result = tool.trash_email(arguments["email_id"])
|
834
|
+
|
835
|
+
if result[:success]
|
836
|
+
"✅ Email moved to trash"
|
837
|
+
else
|
838
|
+
raise "Failed to move email to trash"
|
839
|
+
end
|
840
|
+
end
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
if __FILE__ == $0
|
845
|
+
Gmail::MCPServer.new.run
|
846
|
+
end
|