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
@@ -3,436 +3,438 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "json"
6
- require_relative "gmeet_tool"
7
-
8
- class MCPServer
9
- def initialize
10
- # Defer GmeetTool initialization until actually needed
11
- @gmeet_tool = nil
12
- @tools = {
13
- "test_connection" => {
14
- name: "test_connection",
15
- description: "Test the Google Calendar API connection",
16
- inputSchema: {
17
- type: "object",
18
- properties: {},
19
- required: []
20
- }
21
- },
22
- "list_meetings" => {
23
- name: "list_meetings",
24
- description: "List Google Meet meetings with optional date filtering",
25
- inputSchema: {
26
- type: "object",
27
- properties: {
28
- start_date: {
29
- type: "string",
30
- description: "Start date in YYYY-MM-DD format (default: today)"
31
- },
32
- end_date: {
33
- type: "string",
34
- description: "End date in YYYY-MM-DD format (default: 7 days from start)"
35
- },
36
- max_results: {
37
- type: "number",
38
- description: "Maximum number of meetings to return (default: 20)"
39
- },
40
- calendar_id: {
41
- type: "string",
42
- description: "Calendar ID to list meetings from (default: primary calendar)"
43
- }
44
- },
45
- required: []
46
- }
47
- },
48
- "upcoming_meetings" => {
49
- name: "upcoming_meetings",
50
- description: "List upcoming Google Meet meetings in the next 24 hours",
51
- inputSchema: {
52
- type: "object",
53
- properties: {
54
- max_results: {
55
- type: "number",
56
- description: "Maximum number of meetings to return (default: 10)"
57
- },
58
- calendar_id: {
59
- type: "string",
60
- description: "Calendar ID to list meetings from (default: primary calendar)"
61
- }
62
- },
63
- required: []
64
- }
65
- },
66
- "search_meetings" => {
67
- name: "search_meetings",
68
- description: "Search for Google Meet meetings by text content",
69
- inputSchema: {
70
- type: "object",
71
- properties: {
72
- query: {
73
- type: "string",
74
- description: "Search query to find meetings"
6
+ require_relative "service"
7
+
8
+ module Gmeet
9
+ class MCPServer
10
+ def initialize
11
+ # Defer Service initialization until actually needed
12
+ @gmeet_tool = nil
13
+ @tools = {
14
+ "test_connection" => {
15
+ name: "test_connection",
16
+ description: "Test the Google Calendar API connection",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {},
20
+ required: []
21
+ }
22
+ },
23
+ "list_meetings" => {
24
+ name: "list_meetings",
25
+ description: "List Google Meet meetings with optional date filtering",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ start_date: {
30
+ type: "string",
31
+ description: "Start date in YYYY-MM-DD format (default: today)"
32
+ },
33
+ end_date: {
34
+ type: "string",
35
+ description: "End date in YYYY-MM-DD format (default: 7 days from start)"
36
+ },
37
+ max_results: {
38
+ type: "number",
39
+ description: "Maximum number of meetings to return (default: 20)"
40
+ },
41
+ calendar_id: {
42
+ type: "string",
43
+ description: "Calendar ID to list meetings from (default: primary calendar)"
44
+ }
75
45
  },
76
- start_date: {
77
- type: "string",
78
- description: "Start date in YYYY-MM-DD format (default: today)"
46
+ required: []
47
+ }
48
+ },
49
+ "upcoming_meetings" => {
50
+ name: "upcoming_meetings",
51
+ description: "List upcoming Google Meet meetings in the next 24 hours",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ max_results: {
56
+ type: "number",
57
+ description: "Maximum number of meetings to return (default: 10)"
58
+ },
59
+ calendar_id: {
60
+ type: "string",
61
+ description: "Calendar ID to list meetings from (default: primary calendar)"
62
+ }
79
63
  },
80
- end_date: {
81
- type: "string",
82
- description: "End date in YYYY-MM-DD format (default: 30 days from start)"
64
+ required: []
65
+ }
66
+ },
67
+ "search_meetings" => {
68
+ name: "search_meetings",
69
+ description: "Search for Google Meet meetings by text content",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ query: {
74
+ type: "string",
75
+ description: "Search query to find meetings"
76
+ },
77
+ start_date: {
78
+ type: "string",
79
+ description: "Start date in YYYY-MM-DD format (default: today)"
80
+ },
81
+ end_date: {
82
+ type: "string",
83
+ description: "End date in YYYY-MM-DD format (default: 30 days from start)"
84
+ },
85
+ max_results: {
86
+ type: "number",
87
+ description: "Maximum number of meetings to return (default: 10)"
88
+ }
83
89
  },
84
- max_results: {
85
- type: "number",
86
- description: "Maximum number of meetings to return (default: 10)"
87
- }
88
- },
89
- required: ["query"]
90
- }
91
- },
92
- "get_meeting_url" => {
93
- name: "get_meeting_url",
94
- description: "Get the Google Meet URL for a specific event",
95
- inputSchema: {
96
- type: "object",
97
- properties: {
98
- event_id: {
99
- type: "string",
100
- description: "Calendar event ID"
90
+ required: ["query"]
91
+ }
92
+ },
93
+ "get_meeting_url" => {
94
+ name: "get_meeting_url",
95
+ description: "Get the Google Meet URL for a specific event",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ event_id: {
100
+ type: "string",
101
+ description: "Calendar event ID"
102
+ },
103
+ calendar_id: {
104
+ type: "string",
105
+ description: "Calendar ID (default: primary calendar)"
106
+ }
101
107
  },
102
- calendar_id: {
103
- type: "string",
104
- description: "Calendar ID (default: primary calendar)"
105
- }
106
- },
107
- required: ["event_id"]
108
+ required: ["event_id"]
109
+ }
108
110
  }
109
111
  }
110
- }
111
- end
112
-
113
- def run
114
- # Disable stdout buffering for immediate response
115
- $stdout.sync = true
116
-
117
- # Log startup to file instead of stdout to avoid protocol interference
118
- Mcpeasy::Config.ensure_config_dirs
119
- File.write(Mcpeasy::Config.log_file_path("gmeet", "startup"), "#{Time.now}: Google Meet MCP Server starting on stdio\n", mode: "a")
120
- while (line = $stdin.gets)
121
- handle_request(line.strip)
122
112
  end
123
- rescue Interrupt
124
- # Silent shutdown
125
- rescue => e
126
- # Log to a file instead of stderr to avoid protocol interference
127
- File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
128
- end
129
113
 
130
- private
114
+ def run
115
+ # Disable stdout buffering for immediate response
116
+ $stdout.sync = true
131
117
 
132
- def handle_request(line)
133
- return if line.empty?
134
-
135
- begin
136
- request = JSON.parse(line)
137
- response = process_request(request)
138
- if response
139
- puts JSON.generate(response)
140
- $stdout.flush
118
+ # Log startup to file instead of stdout to avoid protocol interference
119
+ Mcpeasy::Config.ensure_config_dirs
120
+ File.write(Mcpeasy::Config.log_file_path("gmeet", "startup"), "#{Time.now}: Google Meet MCP Server starting on stdio\n", mode: "a")
121
+ while (line = $stdin.gets)
122
+ handle_request(line.strip)
141
123
  end
142
- rescue JSON::ParserError => e
143
- error_response = {
144
- jsonrpc: "2.0",
145
- id: nil,
146
- error: {
147
- code: -32700,
148
- message: "Parse error",
149
- data: e.message
150
- }
151
- }
152
- puts JSON.generate(error_response)
153
- $stdout.flush
124
+ rescue Interrupt
125
+ # Silent shutdown
154
126
  rescue => e
155
- File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
156
- error_response = {
157
- jsonrpc: "2.0",
158
- id: request&.dig("id"),
159
- error: {
160
- code: -32603,
161
- message: "Internal error",
162
- data: e.message
163
- }
164
- }
165
- puts JSON.generate(error_response)
166
- $stdout.flush
127
+ # Log to a file instead of stderr to avoid protocol interference
128
+ File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
167
129
  end
168
- end
169
130
 
170
- def process_request(request)
171
- id = request["id"]
172
- method = request["method"]
173
- params = request["params"] || {}
174
-
175
- case method
176
- when "notifications/initialized"
177
- # Client acknowledgment - no response needed
178
- nil
179
- when "initialize"
180
- initialize_response(id, params)
181
- when "tools/list"
182
- tools_list_response(id, params)
183
- when "tools/call"
184
- tools_call_response(id, params)
185
- else
186
- {
187
- jsonrpc: "2.0",
188
- id: id,
189
- error: {
190
- code: -32601,
191
- message: "Method not found",
192
- data: "Unknown method: #{method}"
131
+ private
132
+
133
+ def handle_request(line)
134
+ return if line.empty?
135
+
136
+ begin
137
+ request = JSON.parse(line)
138
+ response = process_request(request)
139
+ if response
140
+ puts JSON.generate(response)
141
+ $stdout.flush
142
+ end
143
+ rescue JSON::ParserError => e
144
+ error_response = {
145
+ jsonrpc: "2.0",
146
+ id: nil,
147
+ error: {
148
+ code: -32700,
149
+ message: "Parse error",
150
+ data: e.message
151
+ }
193
152
  }
194
- }
195
- end
196
- end
197
-
198
- def initialize_response(id, params)
199
- {
200
- jsonrpc: "2.0",
201
- id: id,
202
- result: {
203
- protocolVersion: "2024-11-05",
204
- capabilities: {
205
- tools: {}
206
- },
207
- serverInfo: {
208
- name: "gmeet-mcp-server",
209
- version: "1.0.0"
153
+ puts JSON.generate(error_response)
154
+ $stdout.flush
155
+ rescue => e
156
+ File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
157
+ error_response = {
158
+ jsonrpc: "2.0",
159
+ id: request&.dig("id"),
160
+ error: {
161
+ code: -32603,
162
+ message: "Internal error",
163
+ data: e.message
164
+ }
210
165
  }
211
- }
212
- }
213
- end
214
-
215
- def tools_list_response(id, params)
216
- {
217
- jsonrpc: "2.0",
218
- id: id,
219
- result: {
220
- tools: @tools.values
221
- }
222
- }
223
- end
224
-
225
- def tools_call_response(id, params)
226
- tool_name = params["name"]
227
- arguments = params["arguments"] || {}
166
+ puts JSON.generate(error_response)
167
+ $stdout.flush
168
+ end
169
+ end
228
170
 
229
- unless @tools.key?(tool_name)
230
- return {
231
- jsonrpc: "2.0",
232
- id: id,
233
- error: {
234
- code: -32602,
235
- message: "Unknown tool",
236
- data: "Tool '#{tool_name}' not found"
171
+ def process_request(request)
172
+ id = request["id"]
173
+ method = request["method"]
174
+ params = request["params"] || {}
175
+
176
+ case method
177
+ when "notifications/initialized"
178
+ # Client acknowledgment - no response needed
179
+ nil
180
+ when "initialize"
181
+ initialize_response(id, params)
182
+ when "tools/list"
183
+ tools_list_response(id, params)
184
+ when "tools/call"
185
+ tools_call_response(id, params)
186
+ else
187
+ {
188
+ jsonrpc: "2.0",
189
+ id: id,
190
+ error: {
191
+ code: -32601,
192
+ message: "Method not found",
193
+ data: "Unknown method: #{method}"
194
+ }
237
195
  }
238
- }
196
+ end
239
197
  end
240
198
 
241
- begin
242
- result = call_tool(tool_name, arguments)
199
+ def initialize_response(id, params)
243
200
  {
244
201
  jsonrpc: "2.0",
245
202
  id: id,
246
203
  result: {
247
- content: [
248
- {
249
- type: "text",
250
- text: result
251
- }
252
- ],
253
- isError: false
204
+ protocolVersion: "2024-11-05",
205
+ capabilities: {
206
+ tools: {}
207
+ },
208
+ serverInfo: {
209
+ name: "gmeet-mcp-server",
210
+ version: "1.0.0"
211
+ }
254
212
  }
255
213
  }
256
- rescue => e
257
- File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
214
+ end
215
+
216
+ def tools_list_response(id, params)
258
217
  {
259
218
  jsonrpc: "2.0",
260
219
  id: id,
261
220
  result: {
262
- content: [
263
- {
264
- type: "text",
265
- text: "❌ Error: #{e.message}"
266
- }
267
- ],
268
- isError: true
221
+ tools: @tools.values
269
222
  }
270
223
  }
271
224
  end
272
- end
273
225
 
274
- def call_tool(tool_name, arguments)
275
- # Initialize GmeetTool only when needed
276
- @gmeet_tool ||= GmeetTool.new
277
-
278
- case tool_name
279
- when "test_connection"
280
- test_connection
281
- when "list_meetings"
282
- list_meetings(arguments)
283
- when "upcoming_meetings"
284
- upcoming_meetings(arguments)
285
- when "search_meetings"
286
- search_meetings(arguments)
287
- when "get_meeting_url"
288
- get_meeting_url(arguments)
289
- else
290
- raise "Unknown tool: #{tool_name}"
291
- end
292
- end
226
+ def tools_call_response(id, params)
227
+ tool_name = params["name"]
228
+ arguments = params["arguments"] || {}
229
+
230
+ unless @tools.key?(tool_name)
231
+ return {
232
+ jsonrpc: "2.0",
233
+ id: id,
234
+ error: {
235
+ code: -32602,
236
+ message: "Unknown tool",
237
+ data: "Tool '#{tool_name}' not found"
238
+ }
239
+ }
240
+ end
293
241
 
294
- def test_connection
295
- response = @gmeet_tool.test_connection
296
- if response[:ok]
297
- "✅ Successfully connected to Google Calendar.\n" \
298
- " User: #{response[:user]} (#{response[:email]})"
299
- else
300
- raise "Connection test failed"
242
+ begin
243
+ result = call_tool(tool_name, arguments)
244
+ {
245
+ jsonrpc: "2.0",
246
+ id: id,
247
+ result: {
248
+ content: [
249
+ {
250
+ type: "text",
251
+ text: result
252
+ }
253
+ ],
254
+ isError: false
255
+ }
256
+ }
257
+ rescue => e
258
+ File.write(Mcpeasy::Config.log_file_path("gmeet", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
259
+ {
260
+ jsonrpc: "2.0",
261
+ id: id,
262
+ result: {
263
+ content: [
264
+ {
265
+ type: "text",
266
+ text: "❌ Error: #{e.message}"
267
+ }
268
+ ],
269
+ isError: true
270
+ }
271
+ }
272
+ end
301
273
  end
302
- end
303
274
 
304
- def list_meetings(arguments)
305
- start_date = arguments["start_date"]
306
- end_date = arguments["end_date"]
307
- max_results = arguments["max_results"]&.to_i || 20
308
- calendar_id = arguments["calendar_id"] || "primary"
309
-
310
- result = @gmeet_tool.list_meetings(
311
- start_date: start_date,
312
- end_date: end_date,
313
- max_results: max_results,
314
- calendar_id: calendar_id
315
- )
316
- meetings = result[:meetings]
317
-
318
- if meetings.empty?
319
- "🎥 No Google Meet meetings found for the specified date range"
320
- else
321
- output = "🎥 Found #{result[:count]} Google Meet meeting(s):\n\n"
322
- meetings.each_with_index do |meeting, index|
323
- output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
324
- output << " - Start: #{format_datetime(meeting[:start])}\n"
325
- output << " - End: #{format_datetime(meeting[:end])}\n"
326
- output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
327
- output << " - Location: #{meeting[:location]}\n" if meeting[:location]
328
- output << " - Attendees: #{meeting[:attendees].join(", ")}\n" if meeting[:attendees]&.any?
329
- output << " - **Meet Link: #{meeting[:meet_link]}**\n"
330
- output << " - Calendar Link: #{meeting[:html_link]}\n\n"
275
+ def call_tool(tool_name, arguments)
276
+ # Initialize Service only when needed
277
+ @gmeet_tool ||= Service.new
278
+
279
+ case tool_name
280
+ when "test_connection"
281
+ test_connection
282
+ when "list_meetings"
283
+ list_meetings(arguments)
284
+ when "upcoming_meetings"
285
+ upcoming_meetings(arguments)
286
+ when "search_meetings"
287
+ search_meetings(arguments)
288
+ when "get_meeting_url"
289
+ get_meeting_url(arguments)
290
+ else
291
+ raise "Unknown tool: #{tool_name}"
331
292
  end
332
- output
333
293
  end
334
- end
335
294
 
336
- def upcoming_meetings(arguments)
337
- max_results = arguments["max_results"]&.to_i || 10
338
- calendar_id = arguments["calendar_id"] || "primary"
339
-
340
- result = @gmeet_tool.upcoming_meetings(
341
- max_results: max_results,
342
- calendar_id: calendar_id
343
- )
344
- meetings = result[:meetings]
345
-
346
- if meetings.empty?
347
- "🎥 No upcoming Google Meet meetings found in the next 24 hours"
348
- else
349
- output = "🎥 Found #{result[:count]} upcoming Google Meet meeting(s):\n\n"
350
- meetings.each_with_index do |meeting, index|
351
- output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
352
- output << " - Start: #{format_datetime(meeting[:start])} (#{meeting[:time_until_start]})\n"
353
- output << " - End: #{format_datetime(meeting[:end])}\n"
354
- output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
355
- output << " - Location: #{meeting[:location]}\n" if meeting[:location]
356
- output << " - Attendees: #{meeting[:attendees].join(", ")}\n" if meeting[:attendees]&.any?
357
- output << " - **Meet Link: #{meeting[:meet_link]}**\n"
358
- output << " - Calendar Link: #{meeting[:html_link]}\n\n"
295
+ def test_connection
296
+ response = @gmeet_tool.test_connection
297
+ if response[:ok]
298
+ "✅ Successfully connected to Google Calendar.\n" \
299
+ " User: #{response[:user]} (#{response[:email]})"
300
+ else
301
+ raise "Connection test failed"
359
302
  end
360
- output
361
303
  end
362
- end
363
304
 
364
- def search_meetings(arguments)
365
- unless arguments["query"]
366
- raise "Missing required argument: query"
305
+ def list_meetings(arguments)
306
+ start_date = arguments["start_date"]
307
+ end_date = arguments["end_date"]
308
+ max_results = arguments["max_results"]&.to_i || 20
309
+ calendar_id = arguments["calendar_id"] || "primary"
310
+
311
+ result = @gmeet_tool.list_meetings(
312
+ start_date: start_date,
313
+ end_date: end_date,
314
+ max_results: max_results,
315
+ calendar_id: calendar_id
316
+ )
317
+ meetings = result[:meetings]
318
+
319
+ if meetings.empty?
320
+ "🎥 No Google Meet meetings found for the specified date range"
321
+ else
322
+ output = "🎥 Found #{result[:count]} Google Meet meeting(s):\n\n"
323
+ meetings.each_with_index do |meeting, index|
324
+ output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
325
+ output << " - Start: #{format_datetime(meeting[:start])}\n"
326
+ output << " - End: #{format_datetime(meeting[:end])}\n"
327
+ output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
328
+ output << " - Location: #{meeting[:location]}\n" if meeting[:location]
329
+ output << " - Attendees: #{meeting[:attendees].join(", ")}\n" if meeting[:attendees]&.any?
330
+ output << " - **Meet Link: #{meeting[:meet_link]}**\n"
331
+ output << " - Calendar Link: #{meeting[:html_link]}\n\n"
332
+ end
333
+ output
334
+ end
367
335
  end
368
336
 
369
- query = arguments["query"].to_s
370
- start_date = arguments["start_date"]
371
- end_date = arguments["end_date"]
372
- max_results = arguments["max_results"]&.to_i || 10
373
-
374
- result = @gmeet_tool.search_meetings(
375
- query,
376
- start_date: start_date,
377
- end_date: end_date,
378
- max_results: max_results
379
- )
380
- meetings = result[:meetings]
381
-
382
- if meetings.empty?
383
- "🔍 No Google Meet meetings found matching '#{query}'"
384
- else
385
- output = "🔍 Found #{result[:count]} Google Meet meeting(s) matching '#{query}':\n\n"
386
- meetings.each_with_index do |meeting, index|
387
- output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
388
- output << " - Start: #{format_datetime(meeting[:start])}\n"
389
- output << " - End: #{format_datetime(meeting[:end])}\n"
390
- output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
391
- output << " - Location: #{meeting[:location]}\n" if meeting[:location]
392
- output << " - **Meet Link: #{meeting[:meet_link]}**\n"
393
- output << " - Calendar Link: #{meeting[:html_link]}\n\n"
337
+ def upcoming_meetings(arguments)
338
+ max_results = arguments["max_results"]&.to_i || 10
339
+ calendar_id = arguments["calendar_id"] || "primary"
340
+
341
+ result = @gmeet_tool.upcoming_meetings(
342
+ max_results: max_results,
343
+ calendar_id: calendar_id
344
+ )
345
+ meetings = result[:meetings]
346
+
347
+ if meetings.empty?
348
+ "🎥 No upcoming Google Meet meetings found in the next 24 hours"
349
+ else
350
+ output = "🎥 Found #{result[:count]} upcoming Google Meet meeting(s):\n\n"
351
+ meetings.each_with_index do |meeting, index|
352
+ output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
353
+ output << " - Start: #{format_datetime(meeting[:start])} (#{meeting[:time_until_start]})\n"
354
+ output << " - End: #{format_datetime(meeting[:end])}\n"
355
+ output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
356
+ output << " - Location: #{meeting[:location]}\n" if meeting[:location]
357
+ output << " - Attendees: #{meeting[:attendees].join(", ")}\n" if meeting[:attendees]&.any?
358
+ output << " - **Meet Link: #{meeting[:meet_link]}**\n"
359
+ output << " - Calendar Link: #{meeting[:html_link]}\n\n"
360
+ end
361
+ output
394
362
  end
395
- output
396
363
  end
397
- end
398
364
 
399
- def get_meeting_url(arguments)
400
- unless arguments["event_id"]
401
- raise "Missing required argument: event_id"
365
+ def search_meetings(arguments)
366
+ unless arguments["query"]
367
+ raise "Missing required argument: query"
368
+ end
369
+
370
+ query = arguments["query"].to_s
371
+ start_date = arguments["start_date"]
372
+ end_date = arguments["end_date"]
373
+ max_results = arguments["max_results"]&.to_i || 10
374
+
375
+ result = @gmeet_tool.search_meetings(
376
+ query,
377
+ start_date: start_date,
378
+ end_date: end_date,
379
+ max_results: max_results
380
+ )
381
+ meetings = result[:meetings]
382
+
383
+ if meetings.empty?
384
+ "🔍 No Google Meet meetings found matching '#{query}'"
385
+ else
386
+ output = "🔍 Found #{result[:count]} Google Meet meeting(s) matching '#{query}':\n\n"
387
+ meetings.each_with_index do |meeting, index|
388
+ output << "#{index + 1}. **#{meeting[:summary] || "No title"}**\n"
389
+ output << " - Start: #{format_datetime(meeting[:start])}\n"
390
+ output << " - End: #{format_datetime(meeting[:end])}\n"
391
+ output << " - Description: #{meeting[:description] || "No description"}\n" if meeting[:description]
392
+ output << " - Location: #{meeting[:location]}\n" if meeting[:location]
393
+ output << " - **Meet Link: #{meeting[:meet_link]}**\n"
394
+ output << " - Calendar Link: #{meeting[:html_link]}\n\n"
395
+ end
396
+ output
397
+ end
402
398
  end
403
399
 
404
- event_id = arguments["event_id"].to_s
405
- calendar_id = arguments["calendar_id"] || "primary"
400
+ def get_meeting_url(arguments)
401
+ unless arguments["event_id"]
402
+ raise "Missing required argument: event_id"
403
+ end
404
+
405
+ event_id = arguments["event_id"].to_s
406
+ calendar_id = arguments["calendar_id"] || "primary"
406
407
 
407
- result = @gmeet_tool.get_meeting_url(event_id, calendar_id: calendar_id)
408
+ result = @gmeet_tool.get_meeting_url(event_id, calendar_id: calendar_id)
408
409
 
409
- output = "🎥 **#{result[:summary] || "Meeting"}**\n"
410
- output << " - Start: #{format_datetime(result[:start])}\n"
411
- output << " - End: #{format_datetime(result[:end])}\n"
412
- output << " - **Meet Link: #{result[:meet_link]}**\n"
413
- output << " - Event ID: #{result[:event_id]}\n"
410
+ output = "🎥 **#{result[:summary] || "Meeting"}**\n"
411
+ output << " - Start: #{format_datetime(result[:start])}\n"
412
+ output << " - End: #{format_datetime(result[:end])}\n"
413
+ output << " - **Meet Link: #{result[:meet_link]}**\n"
414
+ output << " - Event ID: #{result[:event_id]}\n"
414
415
 
415
- output
416
- end
416
+ output
417
+ end
417
418
 
418
- private
419
+ private
419
420
 
420
- def format_datetime(datetime_info)
421
- return "Unknown" unless datetime_info
421
+ def format_datetime(datetime_info)
422
+ return "Unknown" unless datetime_info
422
423
 
423
- if datetime_info[:date]
424
- # All-day event
425
- datetime_info[:date]
426
- elsif datetime_info[:date_time]
427
- # Specific time event
428
- time = Time.parse(datetime_info[:date_time])
429
- time.strftime("%Y-%m-%d %H:%M")
430
- else
431
- "Unknown"
424
+ if datetime_info[:date]
425
+ # All-day event
426
+ datetime_info[:date]
427
+ elsif datetime_info[:date_time]
428
+ # Specific time event
429
+ time = Time.parse(datetime_info[:date_time])
430
+ time.strftime("%Y-%m-%d %H:%M")
431
+ else
432
+ "Unknown"
433
+ end
432
434
  end
433
435
  end
434
436
  end
435
437
 
436
438
  if __FILE__ == $0
437
- MCPServer.new.run
439
+ Gmeet::MCPServer.new.run
438
440
  end