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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.claudeignore +0 -3
  3. data/.mcp.json +19 -1
  4. data/CHANGELOG.md +59 -0
  5. data/CLAUDE.md +19 -5
  6. data/README.md +19 -3
  7. data/lib/mcpeasy/cli.rb +62 -10
  8. data/lib/mcpeasy/config.rb +22 -1
  9. data/lib/mcpeasy/setup.rb +1 -0
  10. data/lib/mcpeasy/version.rb +1 -1
  11. data/lib/utilities/gcal/README.md +11 -3
  12. data/lib/utilities/gcal/cli.rb +110 -108
  13. data/lib/utilities/gcal/mcp.rb +463 -308
  14. data/lib/utilities/gcal/service.rb +312 -0
  15. data/lib/utilities/gdrive/README.md +3 -3
  16. data/lib/utilities/gdrive/cli.rb +98 -96
  17. data/lib/utilities/gdrive/mcp.rb +290 -288
  18. data/lib/utilities/gdrive/service.rb +293 -0
  19. data/lib/utilities/gmail/README.md +278 -0
  20. data/lib/utilities/gmail/cli.rb +264 -0
  21. data/lib/utilities/gmail/mcp.rb +846 -0
  22. data/lib/utilities/gmail/service.rb +547 -0
  23. data/lib/utilities/gmeet/cli.rb +131 -129
  24. data/lib/utilities/gmeet/mcp.rb +374 -372
  25. data/lib/utilities/gmeet/service.rb +411 -0
  26. data/lib/utilities/notion/README.md +287 -0
  27. data/lib/utilities/notion/cli.rb +245 -0
  28. data/lib/utilities/notion/mcp.rb +607 -0
  29. data/lib/utilities/notion/service.rb +327 -0
  30. data/lib/utilities/slack/README.md +3 -3
  31. data/lib/utilities/slack/cli.rb +69 -54
  32. data/lib/utilities/slack/mcp.rb +277 -226
  33. data/lib/utilities/slack/service.rb +134 -0
  34. data/mcpeasy.gemspec +6 -1
  35. metadata +87 -10
  36. data/env.template +0 -11
  37. data/lib/utilities/gcal/gcal_tool.rb +0 -308
  38. data/lib/utilities/gdrive/gdrive_tool.rb +0 -291
  39. data/lib/utilities/gmeet/gmeet_tool.rb +0 -407
  40. data/lib/utilities/slack/slack_tool.rb +0 -119
  41. 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