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,264 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "thor"
|
5
|
+
require_relative "service"
|
6
|
+
|
7
|
+
module Gmail
|
8
|
+
class CLI < Thor
|
9
|
+
desc "test", "Test the Gmail API connection"
|
10
|
+
def test
|
11
|
+
response = tool.test_connection
|
12
|
+
|
13
|
+
if response[:ok]
|
14
|
+
puts "✅ Successfully connected to Gmail"
|
15
|
+
puts " Email: #{response[:email]}"
|
16
|
+
puts " Messages: #{response[:messages_total]}"
|
17
|
+
puts " Threads: #{response[:threads_total]}"
|
18
|
+
else
|
19
|
+
warn "❌ Connection test failed"
|
20
|
+
end
|
21
|
+
rescue RuntimeError => e
|
22
|
+
puts "❌ Failed to connect to Gmail: #{e.message}\n\n#{e.backtrace.join("\n")}"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "list", "List recent emails"
|
27
|
+
method_option :start_date, type: :string, aliases: "-s", desc: "Start date (YYYY-MM-DD)"
|
28
|
+
method_option :end_date, type: :string, aliases: "-e", desc: "End date (YYYY-MM-DD)"
|
29
|
+
method_option :max_results, type: :numeric, default: 20, aliases: "-n", desc: "Max number of emails"
|
30
|
+
method_option :sender, type: :string, aliases: "-f", desc: "Filter by sender email"
|
31
|
+
method_option :subject, type: :string, aliases: "-j", desc: "Filter by subject"
|
32
|
+
method_option :labels, type: :string, aliases: "-l", desc: "Filter by labels (comma-separated)"
|
33
|
+
method_option :read_status, type: :string, aliases: "-r", desc: "Filter by read status (read/unread)"
|
34
|
+
def list
|
35
|
+
labels = options[:labels]&.split(",")&.map(&:strip)
|
36
|
+
|
37
|
+
result = tool.list_emails(
|
38
|
+
start_date: options[:start_date],
|
39
|
+
end_date: options[:end_date],
|
40
|
+
max_results: options[:max_results],
|
41
|
+
sender: options[:sender],
|
42
|
+
subject: options[:subject],
|
43
|
+
labels: labels,
|
44
|
+
read_status: options[:read_status]
|
45
|
+
)
|
46
|
+
emails = result[:emails]
|
47
|
+
|
48
|
+
if emails.empty?
|
49
|
+
puts "📧 No emails found for the specified criteria"
|
50
|
+
else
|
51
|
+
puts "📧 Found #{result[:count]} email(s):"
|
52
|
+
emails.each_with_index do |email, index|
|
53
|
+
puts " #{index + 1}. #{email[:subject] || "No subject"}"
|
54
|
+
puts " From: #{email[:from]}"
|
55
|
+
puts " Date: #{email[:date]}"
|
56
|
+
puts " Snippet: #{email[:snippet]}" if email[:snippet]
|
57
|
+
puts " Labels: #{email[:labels].join(", ")}" if email[:labels]&.any?
|
58
|
+
puts " ID: #{email[:id]}"
|
59
|
+
puts
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue RuntimeError => e
|
63
|
+
warn "❌ Failed to list emails: #{e.message}"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "search QUERY", "Search emails by text content"
|
68
|
+
method_option :max_results, type: :numeric, default: 10, aliases: "-n", desc: "Max number of emails"
|
69
|
+
def search(query)
|
70
|
+
result = tool.search_emails(
|
71
|
+
query,
|
72
|
+
max_results: options[:max_results]
|
73
|
+
)
|
74
|
+
emails = result[:emails]
|
75
|
+
|
76
|
+
if emails.empty?
|
77
|
+
puts "🔍 No emails found matching '#{query}'"
|
78
|
+
else
|
79
|
+
puts "🔍 Found #{result[:count]} email(s) matching '#{query}':"
|
80
|
+
emails.each_with_index do |email, index|
|
81
|
+
puts " #{index + 1}. #{email[:subject] || "No subject"}"
|
82
|
+
puts " From: #{email[:from]}"
|
83
|
+
puts " Date: #{email[:date]}"
|
84
|
+
puts " Snippet: #{email[:snippet]}" if email[:snippet]
|
85
|
+
puts " ID: #{email[:id]}"
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue RuntimeError => e
|
90
|
+
warn "❌ Failed to search emails: #{e.message}"
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "read EMAIL_ID", "Read a specific email"
|
95
|
+
def read(email_id)
|
96
|
+
email = tool.get_email_content(email_id)
|
97
|
+
|
98
|
+
puts "📧 Email Details:"
|
99
|
+
puts " ID: #{email[:id]}"
|
100
|
+
puts " Thread ID: #{email[:thread_id]}"
|
101
|
+
puts " Subject: #{email[:subject] || "No subject"}"
|
102
|
+
puts " From: #{email[:from]}"
|
103
|
+
puts " To: #{email[:to]}"
|
104
|
+
puts " CC: #{email[:cc]}" if email[:cc]
|
105
|
+
puts " BCC: #{email[:bcc]}" if email[:bcc]
|
106
|
+
puts " Date: #{email[:date]}"
|
107
|
+
puts " Labels: #{email[:labels].join(", ")}" if email[:labels]&.any?
|
108
|
+
|
109
|
+
if email[:attachments]&.any?
|
110
|
+
puts " Attachments:"
|
111
|
+
email[:attachments].each do |attachment|
|
112
|
+
puts " - #{attachment[:filename]} (#{attachment[:mime_type]}, #{attachment[:size]} bytes)"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
puts "\n📄 Body:"
|
117
|
+
puts email[:body]
|
118
|
+
rescue RuntimeError => e
|
119
|
+
warn "❌ Failed to read email: #{e.message}"
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "send", "Send a new email"
|
124
|
+
method_option :to, type: :string, required: true, aliases: "-t", desc: "Recipient email address"
|
125
|
+
method_option :subject, type: :string, required: true, aliases: "-s", desc: "Email subject"
|
126
|
+
method_option :body, type: :string, required: true, aliases: "-b", desc: "Email body"
|
127
|
+
method_option :cc, type: :string, aliases: "-c", desc: "CC email address"
|
128
|
+
method_option :bcc, type: :string, aliases: "-B", desc: "BCC email address"
|
129
|
+
method_option :reply_to, type: :string, aliases: "-r", desc: "Reply-to email address"
|
130
|
+
def send
|
131
|
+
result = tool.send_email(
|
132
|
+
to: options[:to],
|
133
|
+
subject: options[:subject],
|
134
|
+
body: options[:body],
|
135
|
+
cc: options[:cc],
|
136
|
+
bcc: options[:bcc],
|
137
|
+
reply_to: options[:reply_to]
|
138
|
+
)
|
139
|
+
|
140
|
+
if result[:success]
|
141
|
+
puts "✅ Email sent successfully"
|
142
|
+
puts " Message ID: #{result[:message_id]}"
|
143
|
+
puts " Thread ID: #{result[:thread_id]}"
|
144
|
+
else
|
145
|
+
puts "❌ Failed to send email"
|
146
|
+
end
|
147
|
+
rescue RuntimeError => e
|
148
|
+
warn "❌ Failed to send email: #{e.message}"
|
149
|
+
exit 1
|
150
|
+
end
|
151
|
+
|
152
|
+
desc "reply EMAIL_ID", "Reply to an email"
|
153
|
+
method_option :body, type: :string, required: true, aliases: "-b", desc: "Reply body"
|
154
|
+
method_option :include_quoted, type: :boolean, default: true, aliases: "-q", desc: "Include quoted original message"
|
155
|
+
def reply(email_id)
|
156
|
+
result = tool.reply_to_email(
|
157
|
+
email_id: email_id,
|
158
|
+
body: options[:body],
|
159
|
+
include_quoted: options[:include_quoted]
|
160
|
+
)
|
161
|
+
|
162
|
+
if result[:success]
|
163
|
+
puts "✅ Reply sent successfully"
|
164
|
+
puts " Message ID: #{result[:message_id]}"
|
165
|
+
puts " Thread ID: #{result[:thread_id]}"
|
166
|
+
else
|
167
|
+
puts "❌ Failed to send reply"
|
168
|
+
end
|
169
|
+
rescue RuntimeError => e
|
170
|
+
warn "❌ Failed to send reply: #{e.message}"
|
171
|
+
exit 1
|
172
|
+
end
|
173
|
+
|
174
|
+
desc "mark_read EMAIL_ID", "Mark an email as read"
|
175
|
+
def mark_read(email_id)
|
176
|
+
result = tool.mark_as_read(email_id)
|
177
|
+
|
178
|
+
if result[:success]
|
179
|
+
puts "✅ Email marked as read"
|
180
|
+
else
|
181
|
+
puts "❌ Failed to mark email as read"
|
182
|
+
end
|
183
|
+
rescue RuntimeError => e
|
184
|
+
warn "❌ Failed to mark email as read: #{e.message}"
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
|
188
|
+
desc "mark_unread EMAIL_ID", "Mark an email as unread"
|
189
|
+
def mark_unread(email_id)
|
190
|
+
result = tool.mark_as_unread(email_id)
|
191
|
+
|
192
|
+
if result[:success]
|
193
|
+
puts "✅ Email marked as unread"
|
194
|
+
else
|
195
|
+
puts "❌ Failed to mark email as unread"
|
196
|
+
end
|
197
|
+
rescue RuntimeError => e
|
198
|
+
warn "❌ Failed to mark email as unread: #{e.message}"
|
199
|
+
exit 1
|
200
|
+
end
|
201
|
+
|
202
|
+
desc "add_label EMAIL_ID LABEL", "Add a label to an email"
|
203
|
+
def add_label(email_id, label)
|
204
|
+
result = tool.add_label(email_id, label)
|
205
|
+
|
206
|
+
if result[:success]
|
207
|
+
puts "✅ Label '#{label}' added to email"
|
208
|
+
else
|
209
|
+
puts "❌ Failed to add label to email"
|
210
|
+
end
|
211
|
+
rescue RuntimeError => e
|
212
|
+
warn "❌ Failed to add label to email: #{e.message}"
|
213
|
+
exit 1
|
214
|
+
end
|
215
|
+
|
216
|
+
desc "remove_label EMAIL_ID LABEL", "Remove a label from an email"
|
217
|
+
def remove_label(email_id, label)
|
218
|
+
result = tool.remove_label(email_id, label)
|
219
|
+
|
220
|
+
if result[:success]
|
221
|
+
puts "✅ Label '#{label}' removed from email"
|
222
|
+
else
|
223
|
+
puts "❌ Failed to remove label from email"
|
224
|
+
end
|
225
|
+
rescue RuntimeError => e
|
226
|
+
warn "❌ Failed to remove label from email: #{e.message}"
|
227
|
+
exit 1
|
228
|
+
end
|
229
|
+
|
230
|
+
desc "archive EMAIL_ID", "Archive an email"
|
231
|
+
def archive(email_id)
|
232
|
+
result = tool.archive_email(email_id)
|
233
|
+
|
234
|
+
if result[:success]
|
235
|
+
puts "✅ Email archived"
|
236
|
+
else
|
237
|
+
puts "❌ Failed to archive email"
|
238
|
+
end
|
239
|
+
rescue RuntimeError => e
|
240
|
+
warn "❌ Failed to archive email: #{e.message}"
|
241
|
+
exit 1
|
242
|
+
end
|
243
|
+
|
244
|
+
desc "trash EMAIL_ID", "Move an email to trash"
|
245
|
+
def trash(email_id)
|
246
|
+
result = tool.trash_email(email_id)
|
247
|
+
|
248
|
+
if result[:success]
|
249
|
+
puts "✅ Email moved to trash"
|
250
|
+
else
|
251
|
+
puts "❌ Failed to move email to trash"
|
252
|
+
end
|
253
|
+
rescue RuntimeError => e
|
254
|
+
warn "❌ Failed to move email to trash: #{e.message}"
|
255
|
+
exit 1
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def tool
|
261
|
+
@tool ||= Service.new
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|