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,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "google/apis/drive_v3"
5
+ require "googleauth"
6
+ require "signet/oauth_2/client"
7
+ require "fileutils"
8
+ require "json"
9
+ require "time"
10
+ require_relative "../_google/auth_server"
11
+ require_relative "../../mcpeasy/config"
12
+
13
+ module Gdrive
14
+ class Service
15
+ SCOPES = [
16
+ "https://www.googleapis.com/auth/calendar.readonly",
17
+ "https://www.googleapis.com/auth/drive.readonly"
18
+ ]
19
+ SCOPE = SCOPES.join(" ")
20
+
21
+ # MIME type mappings for Google Workspace documents
22
+ EXPORT_FORMATS = {
23
+ "application/vnd.google-apps.document" => {
24
+ format: "text/markdown",
25
+ extension: ".md"
26
+ },
27
+ "application/vnd.google-apps.spreadsheet" => {
28
+ format: "text/csv",
29
+ extension: ".csv"
30
+ },
31
+ "application/vnd.google-apps.presentation" => {
32
+ format: "text/plain",
33
+ extension: ".txt"
34
+ },
35
+ "application/vnd.google-apps.drawing" => {
36
+ format: "image/png",
37
+ extension: ".png"
38
+ }
39
+ }.freeze
40
+
41
+ def initialize(skip_auth: false)
42
+ ensure_env!
43
+ @service = Google::Apis::DriveV3::DriveService.new
44
+ @service.authorization = authorize unless skip_auth
45
+ end
46
+
47
+ def search_files(query, max_results: 10)
48
+ results = @service.list_files(
49
+ q: "fullText contains '#{query.gsub("'", "\\'")}' and trashed=false",
50
+ page_size: max_results,
51
+ fields: "files(id,name,mimeType,size,modifiedTime,webViewLink)"
52
+ )
53
+
54
+ files = results.files.map do |file|
55
+ {
56
+ id: file.id,
57
+ name: file.name,
58
+ mime_type: file.mime_type,
59
+ size: file.size&.to_i,
60
+ modified_time: file.modified_time,
61
+ web_view_link: file.web_view_link
62
+ }
63
+ end
64
+
65
+ {files: files, count: files.length}
66
+ rescue Google::Apis::Error => e
67
+ raise "Google Drive API Error: #{e.message}"
68
+ rescue => e
69
+ log_error("search_files", e)
70
+ raise e
71
+ end
72
+
73
+ def get_file_content(file_id)
74
+ # First get file metadata
75
+ file = @service.get_file(file_id, fields: "id,name,mimeType,size")
76
+
77
+ content = if EXPORT_FORMATS.key?(file.mime_type)
78
+ # Export Google Workspace document
79
+ export_format = EXPORT_FORMATS[file.mime_type][:format]
80
+ @service.export_file(file_id, export_format)
81
+ else
82
+ # Download regular file
83
+ @service.get_file(file_id, download_dest: StringIO.new)
84
+ end
85
+
86
+ {
87
+ id: file.id,
88
+ name: file.name,
89
+ mime_type: file.mime_type,
90
+ size: file.size&.to_i,
91
+ content: content.is_a?(StringIO) ? content.string : content
92
+ }
93
+ rescue Google::Apis::Error => e
94
+ raise "Google Drive API Error: #{e.message}"
95
+ rescue => e
96
+ log_error("get_file_content", e)
97
+ raise e
98
+ end
99
+
100
+ def list_files(max_results: 20)
101
+ results = @service.list_files(
102
+ q: "trashed=false",
103
+ page_size: max_results,
104
+ order_by: "modifiedTime desc",
105
+ fields: "files(id,name,mimeType,size,modifiedTime,webViewLink)"
106
+ )
107
+
108
+ files = results.files.map do |file|
109
+ {
110
+ id: file.id,
111
+ name: file.name,
112
+ mime_type: file.mime_type,
113
+ size: file.size&.to_i,
114
+ modified_time: file.modified_time,
115
+ web_view_link: file.web_view_link
116
+ }
117
+ end
118
+
119
+ {files: files, count: files.length}
120
+ rescue Google::Apis::Error => e
121
+ raise "Google Drive API Error: #{e.message}"
122
+ rescue => e
123
+ log_error("list_files", e)
124
+ raise e
125
+ end
126
+
127
+ def test_connection
128
+ about = @service.get_about(fields: "user,storageQuota")
129
+ {
130
+ ok: true,
131
+ user: about.user.display_name,
132
+ email: about.user.email_address,
133
+ storage_used: about.storage_quota&.usage,
134
+ storage_limit: about.storage_quota&.limit
135
+ }
136
+ rescue Google::Apis::Error => e
137
+ raise "Google Drive API Error: #{e.message}"
138
+ rescue => e
139
+ log_error("test_connection", e)
140
+ raise e
141
+ end
142
+
143
+ def authenticate
144
+ perform_auth_flow
145
+ {success: true}
146
+ rescue => e
147
+ {success: false, error: e.message}
148
+ end
149
+
150
+ def perform_auth_flow
151
+ client_id = Mcpeasy::Config.google_client_id
152
+ client_secret = Mcpeasy::Config.google_client_secret
153
+
154
+ unless client_id && client_secret
155
+ raise "Google credentials not found. Please save your credentials.json file using: mcpz config set_google_credentials <path_to_credentials.json>"
156
+ end
157
+
158
+ # Create credentials using OAuth2 flow with localhost redirect
159
+ redirect_uri = "http://localhost:8080"
160
+ client = Signet::OAuth2::Client.new(
161
+ client_id: client_id,
162
+ client_secret: client_secret,
163
+ scope: SCOPE,
164
+ redirect_uri: redirect_uri,
165
+ authorization_uri: "https://accounts.google.com/o/oauth2/auth",
166
+ token_credential_uri: "https://oauth2.googleapis.com/token"
167
+ )
168
+
169
+ # Generate authorization URL
170
+ url = client.authorization_uri.to_s
171
+
172
+ puts "DEBUG: Client ID: #{client_id[0..20]}..."
173
+ puts "DEBUG: Scope: #{SCOPE}"
174
+ puts "DEBUG: Redirect URI: #{redirect_uri}"
175
+ puts
176
+
177
+ # Start callback server to capture OAuth code
178
+ puts "Starting temporary web server to capture OAuth callback..."
179
+ puts "Opening authorization URL in your default browser..."
180
+ puts url
181
+ puts
182
+
183
+ # Automatically open URL in default browser on macOS/Unix
184
+ if system("which open > /dev/null 2>&1")
185
+ system("open", url)
186
+ else
187
+ puts "Could not automatically open browser. Please copy the URL above manually."
188
+ end
189
+ puts
190
+ puts "Waiting for OAuth callback... (will timeout in 60 seconds)"
191
+
192
+ # Wait for the authorization code with timeout
193
+ code = GoogleAuthServer.capture_auth_code
194
+
195
+ unless code
196
+ raise "Failed to receive authorization code. Please try again."
197
+ end
198
+
199
+ puts "✅ Authorization code received!"
200
+ client.code = code
201
+ client.fetch_access_token!
202
+
203
+ # Save credentials to config
204
+ credentials_data = {
205
+ client_id: client.client_id,
206
+ client_secret: client.client_secret,
207
+ scope: client.scope,
208
+ refresh_token: client.refresh_token,
209
+ access_token: client.access_token,
210
+ expires_at: client.expires_at
211
+ }
212
+
213
+ Mcpeasy::Config.save_google_token(credentials_data)
214
+ puts "✅ Authentication successful! Token saved to config"
215
+
216
+ client
217
+ rescue => e
218
+ log_error("perform_auth_flow", e)
219
+ raise "Authentication flow failed: #{e.message}"
220
+ end
221
+
222
+ private
223
+
224
+ def authorize
225
+ credentials_data = Mcpeasy::Config.google_token
226
+ unless credentials_data
227
+ raise <<~ERROR
228
+ Google Drive authentication required!
229
+ Run the auth command first:
230
+ mcpz gdrive auth
231
+ ERROR
232
+ end
233
+
234
+ client = Signet::OAuth2::Client.new(
235
+ client_id: credentials_data.client_id,
236
+ client_secret: credentials_data.client_secret,
237
+ scope: credentials_data.scope.respond_to?(:to_a) ? credentials_data.scope.to_a.join(" ") : credentials_data.scope.to_s,
238
+ refresh_token: credentials_data.refresh_token,
239
+ access_token: credentials_data.access_token,
240
+ token_credential_uri: "https://oauth2.googleapis.com/token"
241
+ )
242
+
243
+ # Check if token needs refresh
244
+ if credentials_data.expires_at
245
+ expires_at = if credentials_data.expires_at.is_a?(String)
246
+ Time.parse(credentials_data.expires_at)
247
+ else
248
+ Time.at(credentials_data.expires_at)
249
+ end
250
+
251
+ if Time.now >= expires_at
252
+ client.refresh!
253
+ # Update saved credentials with new access token
254
+ updated_data = {
255
+ client_id: credentials_data.client_id,
256
+ client_secret: credentials_data.client_secret,
257
+ scope: credentials_data.scope.respond_to?(:to_a) ? credentials_data.scope.to_a.join(" ") : credentials_data.scope.to_s,
258
+ refresh_token: credentials_data.refresh_token,
259
+ access_token: client.access_token,
260
+ expires_at: client.expires_at
261
+ }
262
+ Mcpeasy::Config.save_google_token(updated_data)
263
+ end
264
+ end
265
+
266
+ client
267
+ rescue JSON::ParserError
268
+ raise "Invalid token data. Please re-run: mcpz gdrive auth"
269
+ rescue => e
270
+ log_error("authorize", e)
271
+ raise "Authentication failed: #{e.message}"
272
+ end
273
+
274
+ def ensure_env!
275
+ unless Mcpeasy::Config.google_client_id && Mcpeasy::Config.google_client_secret
276
+ raise <<~ERROR
277
+ Google API credentials not configured!
278
+ Please save your Google credentials.json file using:
279
+ mcpz config set_google_credentials <path_to_credentials.json>
280
+ ERROR
281
+ end
282
+ end
283
+
284
+ def log_error(method, error)
285
+ Mcpeasy::Config.ensure_config_dirs
286
+ File.write(
287
+ Mcpeasy::Config.log_file_path("gdrive", "error"),
288
+ "#{Time.now}: #{method} error: #{error.class}: #{error.message}\n#{error.backtrace.join("\n")}\n",
289
+ mode: "a"
290
+ )
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,278 @@
1
+ # Gmail MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for Gmail integration with AI assistants.
4
+
5
+ ## Features
6
+
7
+ - **List emails** with filtering by date range, sender, subject, labels, and read/unread status
8
+ - **Search emails** using Gmail's powerful search syntax
9
+ - **Get email content** including full body, headers, and attachment information
10
+ - **Send emails** with support for CC, BCC, and reply-to fields
11
+ - **Reply to emails** with automatic threading and optional quoted text
12
+ - **Email management** - mark as read/unread, add/remove labels, archive, and trash
13
+ - **Test connection** to verify Gmail API connectivity
14
+
15
+ ## Prerequisites
16
+
17
+ 1. **Google Cloud Project** with Gmail API enabled
18
+ 2. **OAuth 2.0 credentials** (client ID and client secret)
19
+ 3. **Ruby environment** with required gems
20
+
21
+ ## Setup
22
+
23
+ ### 1. Enable Gmail API
24
+
25
+ 1. Go to the [Google Cloud Console](https://console.cloud.google.com)
26
+ 2. Create a new project or select an existing one
27
+ 3. Enable the Gmail API:
28
+ - Navigate to "APIs & Services" > "Library"
29
+ - Search for "Gmail API" and click "Enable"
30
+
31
+ ### 2. Create OAuth 2.0 Credentials
32
+
33
+ 1. Go to "APIs & Services" > "Credentials"
34
+ 2. Click "Create Credentials" > "OAuth 2.0 Client IDs"
35
+ 3. Set application type to "Desktop application"
36
+ 4. Download the JSON file containing your credentials
37
+
38
+ ### 3. Install and Configure MCPEasy
39
+
40
+ ```bash
41
+ # Install the gem
42
+ gem install mcpeasy
43
+
44
+ # Set up configuration directories
45
+ mcpz setup
46
+
47
+ # Save your Google credentials
48
+ mcpz set_google_credentials path/to/your/credentials.json
49
+
50
+ # Authenticate with Gmail (will open browser for OAuth)
51
+ mcpz gmail auth
52
+ ```
53
+
54
+ ### 4. Verify Setup
55
+
56
+ ```bash
57
+ # Test the connection
58
+ mcpz gmail test
59
+ ```
60
+
61
+ ## CLI Usage
62
+
63
+ ### Authentication
64
+ ```bash
65
+ # Authenticate with Gmail API
66
+ mcpz gmail auth
67
+ ```
68
+
69
+ ### Listing Emails
70
+ ```bash
71
+ # List recent emails
72
+ mcpz gmail list
73
+
74
+ # Filter by date range
75
+ mcpz gmail list --start_date 2024-01-01 --end_date 2024-01-31
76
+
77
+ # Filter by sender
78
+ mcpz gmail list --sender "someone@example.com"
79
+
80
+ # Filter by subject
81
+ mcpz gmail list --subject "Important"
82
+
83
+ # Filter by labels
84
+ mcpz gmail list --labels "inbox,important"
85
+
86
+ # Filter by read status
87
+ mcpz gmail list --read_status unread
88
+
89
+ # Limit results
90
+ mcpz gmail list --max_results 10
91
+ ```
92
+
93
+ ### Searching Emails
94
+ ```bash
95
+ # Basic search
96
+ mcpz gmail search "quarterly report"
97
+
98
+ # Advanced Gmail search syntax
99
+ mcpz gmail search "from:boss@company.com subject:urgent"
100
+ mcpz gmail search "has:attachment filename:pdf"
101
+ mcpz gmail search "is:unread after:2024/01/01"
102
+ ```
103
+
104
+ ### Reading Emails
105
+ ```bash
106
+ # Read a specific email by ID
107
+ mcpz gmail read 18c8b5d4e8f9a2b6
108
+ ```
109
+
110
+ ### Sending Emails
111
+ ```bash
112
+ # Send a basic email
113
+ mcpz gmail send \
114
+ --to "recipient@example.com" \
115
+ --subject "Hello from MCPEasy" \
116
+ --body "This is a test email sent via Gmail API."
117
+
118
+ # Send with CC and BCC
119
+ mcpz gmail send \
120
+ --to "recipient@example.com" \
121
+ --cc "cc@example.com" \
122
+ --bcc "bcc@example.com" \
123
+ --subject "Team Update" \
124
+ --body "Weekly team update..." \
125
+ --reply_to "noreply@example.com"
126
+ ```
127
+
128
+ ### Replying to Emails
129
+ ```bash
130
+ # Reply to an email
131
+ mcpz gmail reply 18c8b5d4e8f9a2b6 \
132
+ --body "Thank you for your message."
133
+
134
+ # Reply without including quoted original message
135
+ mcpz gmail reply 18c8b5d4e8f9a2b6 \
136
+ --body "Thank you for your message." \
137
+ --include_quoted false
138
+ ```
139
+
140
+ ### Email Management
141
+ ```bash
142
+ # Mark as read/unread
143
+ mcpz gmail mark_read 18c8b5d4e8f9a2b6
144
+ mcpz gmail mark_unread 18c8b5d4e8f9a2b6
145
+
146
+ # Add/remove labels
147
+ mcpz gmail add_label 18c8b5d4e8f9a2b6 "important"
148
+ mcpz gmail remove_label 18c8b5d4e8f9a2b6 "important"
149
+
150
+ # Archive email (remove from inbox)
151
+ mcpz gmail archive 18c8b5d4e8f9a2b6
152
+
153
+ # Move to trash
154
+ mcpz gmail trash 18c8b5d4e8f9a2b6
155
+ ```
156
+
157
+ ## MCP Server Usage
158
+
159
+ ### Running the Server
160
+
161
+ ```bash
162
+ # Start the Gmail MCP server
163
+ mcpz gmail mcp
164
+ ```
165
+
166
+ ### MCP Configuration
167
+
168
+ Add to your `.mcp.json` configuration:
169
+
170
+ ```json
171
+ {
172
+ "mcpServers": {
173
+ "gmail": {
174
+ "command": "mcpz",
175
+ "args": ["gmail", "mcp"]
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### Available MCP Tools
182
+
183
+ - `test_connection` - Test Gmail API connectivity
184
+ - `list_emails` - List emails with filtering options
185
+ - `search_emails` - Search emails using Gmail syntax
186
+ - `get_email_content` - Get full email content including attachments
187
+ - `send_email` - Send new emails
188
+ - `reply_to_email` - Reply to existing emails
189
+ - `mark_as_read` / `mark_as_unread` - Change read status
190
+ - `add_label` / `remove_label` - Manage email labels
191
+ - `archive_email` - Archive emails
192
+ - `trash_email` - Move emails to trash
193
+
194
+ ### Available MCP Prompts
195
+
196
+ - `check_email` - Check inbox for new messages
197
+ - `compose_email` - Compose and send emails
198
+ - `email_search` - Search through emails
199
+ - `email_management` - Manage emails (read/unread, archive, etc.)
200
+
201
+ ## Gmail Search Syntax
202
+
203
+ The Gmail MCP server supports Gmail's advanced search operators:
204
+
205
+ - `from:sender@example.com` - From specific sender
206
+ - `to:recipient@example.com` - To specific recipient
207
+ - `subject:keyword` - Subject contains keyword
208
+ - `has:attachment` - Has attachments
209
+ - `filename:pdf` - Attachment filename contains "pdf"
210
+ - `is:unread` / `is:read` - Read status
211
+ - `is:important` / `is:starred` - Importance/starred
212
+ - `label:labelname` - Has specific label
213
+ - `after:2024/01/01` / `before:2024/12/31` - Date ranges
214
+ - `newer_than:7d` / `older_than:1m` - Relative dates
215
+
216
+ ## API Scopes
217
+
218
+ This MCP server requires the following Gmail API scopes:
219
+
220
+ - `https://www.googleapis.com/auth/gmail.readonly` - Read access to Gmail
221
+ - `https://www.googleapis.com/auth/gmail.send` - Send emails
222
+ - `https://www.googleapis.com/auth/gmail.modify` - Modify email labels and status
223
+
224
+ ## Security Notes
225
+
226
+ - **OAuth tokens are stored locally** in `~/.config/mcpeasy/`
227
+ - **Tokens are automatically refreshed** when they expire
228
+ - **Only your authenticated user** can access emails through this server
229
+ - **No emails are stored** by the MCP server - all data comes directly from Gmail
230
+
231
+ ## Troubleshooting
232
+
233
+ ### Authentication Issues
234
+
235
+ ```bash
236
+ # Re-authenticate if you see authentication errors
237
+ mcpz gmail auth
238
+
239
+ # Check configuration status
240
+ mcpz config
241
+ ```
242
+
243
+ ### API Quota Errors
244
+
245
+ Gmail API has usage quotas. If you hit rate limits:
246
+ - Reduce the number of requests
247
+ - Add delays between operations
248
+ - Check your Google Cloud Console quota usage
249
+
250
+ ### Common Error Messages
251
+
252
+ - **"Gmail authentication required"** - Run `mcpz gmail auth`
253
+ - **"Google API credentials not configured"** - Run `mcpz set_google_credentials <path>`
254
+ - **"Gmail API Error: Insufficient Permission"** - Re-run authentication to grant necessary scopes
255
+
256
+ ## Development
257
+
258
+ The Gmail MCP server follows the same patterns as other MCPEasy services:
259
+
260
+ - `service.rb` - Core Gmail API functionality
261
+ - `cli.rb` - Thor-based CLI commands
262
+ - `mcp.rb` - MCP server implementation
263
+ - `README.md` - Documentation
264
+
265
+ For development setup:
266
+
267
+ ```bash
268
+ # Clone the repository
269
+ git clone https://github.com/your-repo/mcpeasy.git
270
+ cd mcpeasy
271
+
272
+ # Install dependencies
273
+ bundle install
274
+
275
+ # Build and install locally
276
+ gem build mcpeasy.gemspec
277
+ gem install mcpeasy-*.gem
278
+ ```