mcpeasy 0.1.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 +7 -0
- data/.claude/settings.json +15 -0
- data/.claudeignore +4 -0
- data/.envrc +2 -0
- data/.mcp.json +40 -0
- data/CLAUDE.md +170 -0
- data/README.md +161 -0
- data/bin/mcpz +6 -0
- data/env.template +11 -0
- data/ext/setup.rb +7 -0
- data/lib/mcpeasy/cli.rb +154 -0
- data/lib/mcpeasy/config.rb +102 -0
- data/lib/mcpeasy/setup.rb +22 -0
- data/lib/mcpeasy/version.rb +5 -0
- data/lib/mcpeasy.rb +9 -0
- data/lib/utilities/_google/auth_server.rb +149 -0
- data/lib/utilities/gcal/README.md +237 -0
- data/lib/utilities/gcal/cli.rb +134 -0
- data/lib/utilities/gcal/gcal_tool.rb +308 -0
- data/lib/utilities/gcal/mcp.rb +381 -0
- data/lib/utilities/gdrive/README.md +269 -0
- data/lib/utilities/gdrive/cli.rb +118 -0
- data/lib/utilities/gdrive/gdrive_tool.rb +291 -0
- data/lib/utilities/gdrive/mcp.rb +347 -0
- data/lib/utilities/gmeet/README.md +133 -0
- data/lib/utilities/gmeet/cli.rb +157 -0
- data/lib/utilities/gmeet/gmeet_tool.rb +407 -0
- data/lib/utilities/gmeet/mcp.rb +438 -0
- data/lib/utilities/slack/README.md +211 -0
- data/lib/utilities/slack/cli.rb +74 -0
- data/lib/utilities/slack/mcp.rb +280 -0
- data/lib/utilities/slack/slack_tool.rb +119 -0
- data/logs/.keep +0 -0
- data/mcpeasy.gemspec +47 -0
- metadata +191 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "thor"
|
5
|
+
require_relative "slack_tool"
|
6
|
+
|
7
|
+
class SlackCLI < Thor
|
8
|
+
desc "test", "Test the Slack API connection"
|
9
|
+
def test
|
10
|
+
response = tool.test_connection
|
11
|
+
|
12
|
+
if response["ok"]
|
13
|
+
puts "✅ Successfully connected to Slack"
|
14
|
+
puts " Bot name: #{response["user"]}"
|
15
|
+
puts " Team: #{response["team"]}"
|
16
|
+
else
|
17
|
+
warn "❌ Authentication failed: #{response["error"]}"
|
18
|
+
end
|
19
|
+
rescue RuntimeError => e
|
20
|
+
puts "❌ Failed to connect to Slack: #{e.message}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "list", "List available Slack channels"
|
25
|
+
def list
|
26
|
+
channels = tool.list_channels
|
27
|
+
|
28
|
+
if channels && !channels.empty?
|
29
|
+
puts "📋 Available channels:"
|
30
|
+
channels.each do |channel|
|
31
|
+
puts " ##{channel[:name]} (ID: #{channel[:id]})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue RuntimeError => e
|
35
|
+
warn "❌ Failed to list channels: #{e.message}"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "post", "Post a message to a Slack channel"
|
40
|
+
method_option :channel, required: true, type: :string, aliases: "-c"
|
41
|
+
method_option :message, required: true, type: :string, aliases: "-m"
|
42
|
+
method_option :username, type: :string, aliases: "-u"
|
43
|
+
method_option :timestamp, type: :string, aliases: "-t"
|
44
|
+
def post
|
45
|
+
channel = options[:channel]
|
46
|
+
text = options[:message]
|
47
|
+
username = options[:username]
|
48
|
+
thread_ts = options[:timestamp]
|
49
|
+
|
50
|
+
response = tool.post_message(
|
51
|
+
channel: channel,
|
52
|
+
text: text,
|
53
|
+
username: username,
|
54
|
+
thread_ts: thread_ts
|
55
|
+
)
|
56
|
+
|
57
|
+
if response["ok"]
|
58
|
+
puts "✅ Message posted successfully to ##{channel}"
|
59
|
+
puts " Message timestamp: #{response["ts"]}"
|
60
|
+
else
|
61
|
+
warn "❌ Failed to post message: #{response["error"]}"
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
rescue RuntimeError => e
|
65
|
+
warn "❌ Unexpected error: #{e.message}"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def tool
|
72
|
+
@tool ||= SlackTool.new
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "json"
|
6
|
+
require_relative "slack_tool"
|
7
|
+
|
8
|
+
class MCPServer
|
9
|
+
def initialize
|
10
|
+
# Defer SlackTool initialization until actually needed
|
11
|
+
@slack_tool = nil
|
12
|
+
@tools = {
|
13
|
+
"test_connection" => {
|
14
|
+
name: "test_connection",
|
15
|
+
description: "Test the Slack API connection",
|
16
|
+
inputSchema: {
|
17
|
+
type: "object",
|
18
|
+
properties: {},
|
19
|
+
required: []
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"list_channels" => {
|
23
|
+
name: "list_channels",
|
24
|
+
description: "List available Slack channels",
|
25
|
+
inputSchema: {
|
26
|
+
type: "object",
|
27
|
+
properties: {},
|
28
|
+
required: []
|
29
|
+
}
|
30
|
+
},
|
31
|
+
"post_message" => {
|
32
|
+
name: "post_message",
|
33
|
+
description: "Post a message to a Slack channel",
|
34
|
+
inputSchema: {
|
35
|
+
type: "object",
|
36
|
+
properties: {
|
37
|
+
channel: {
|
38
|
+
type: "string",
|
39
|
+
description: "The Slack channel name (with or without #)"
|
40
|
+
},
|
41
|
+
text: {
|
42
|
+
type: "string",
|
43
|
+
description: "The message text to post"
|
44
|
+
},
|
45
|
+
username: {
|
46
|
+
type: "string",
|
47
|
+
description: "Optional custom username for the message"
|
48
|
+
},
|
49
|
+
thread_ts: {
|
50
|
+
type: "string",
|
51
|
+
description: "Optional timestamp of parent message to reply to"
|
52
|
+
}
|
53
|
+
},
|
54
|
+
required: ["channel", "text"]
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def run
|
61
|
+
# Disable stdout buffering for immediate response
|
62
|
+
$stdout.sync = true
|
63
|
+
|
64
|
+
# Log startup to file instead of stdout to avoid protocol interference
|
65
|
+
Mcpeasy::Config.ensure_config_dirs
|
66
|
+
File.write(Mcpeasy::Config.log_file_path("slack", "startup"), "#{Time.now}: Slack MCP Server starting on stdio\n", mode: "a")
|
67
|
+
while (line = $stdin.gets)
|
68
|
+
handle_request(line.strip)
|
69
|
+
end
|
70
|
+
rescue Interrupt
|
71
|
+
# Silent shutdown
|
72
|
+
rescue => e
|
73
|
+
# Log to a file instead of stderr to avoid protocol interference
|
74
|
+
File.write(Mcpeasy::Config.log_file_path("slack", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def handle_request(line)
|
80
|
+
return if line.empty?
|
81
|
+
|
82
|
+
begin
|
83
|
+
request = JSON.parse(line)
|
84
|
+
response = process_request(request)
|
85
|
+
if response
|
86
|
+
puts JSON.generate(response)
|
87
|
+
$stdout.flush
|
88
|
+
end
|
89
|
+
rescue JSON::ParserError => e
|
90
|
+
error_response = {
|
91
|
+
jsonrpc: "2.0",
|
92
|
+
id: nil,
|
93
|
+
error: {
|
94
|
+
code: -32700,
|
95
|
+
message: "Parse error",
|
96
|
+
data: e.message
|
97
|
+
}
|
98
|
+
}
|
99
|
+
puts JSON.generate(error_response)
|
100
|
+
$stdout.flush
|
101
|
+
rescue => e
|
102
|
+
File.write(Mcpeasy::Config.log_file_path("slack", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
103
|
+
error_response = {
|
104
|
+
jsonrpc: "2.0",
|
105
|
+
id: request&.dig("id"),
|
106
|
+
error: {
|
107
|
+
code: -32603,
|
108
|
+
message: "Internal error",
|
109
|
+
data: e.message
|
110
|
+
}
|
111
|
+
}
|
112
|
+
puts JSON.generate(error_response)
|
113
|
+
$stdout.flush
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_request(request)
|
118
|
+
id = request["id"]
|
119
|
+
method = request["method"]
|
120
|
+
params = request["params"] || {}
|
121
|
+
|
122
|
+
case method
|
123
|
+
when "notifications/initialized"
|
124
|
+
# Client acknowledgment - no response needed
|
125
|
+
nil
|
126
|
+
when "initialize"
|
127
|
+
initialize_response(id, params)
|
128
|
+
when "tools/list"
|
129
|
+
tools_list_response(id, params)
|
130
|
+
when "tools/call"
|
131
|
+
tools_call_response(id, params)
|
132
|
+
else
|
133
|
+
{
|
134
|
+
jsonrpc: "2.0",
|
135
|
+
id: id,
|
136
|
+
error: {
|
137
|
+
code: -32601,
|
138
|
+
message: "Method not found",
|
139
|
+
data: "Unknown method: #{method}"
|
140
|
+
}
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def initialize_response(id, params)
|
146
|
+
{
|
147
|
+
jsonrpc: "2.0",
|
148
|
+
id: id,
|
149
|
+
result: {
|
150
|
+
protocolVersion: "2024-11-05",
|
151
|
+
capabilities: {
|
152
|
+
tools: {}
|
153
|
+
},
|
154
|
+
serverInfo: {
|
155
|
+
name: "slack-mcp-server",
|
156
|
+
version: "1.0.0"
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def tools_list_response(id, params)
|
163
|
+
{
|
164
|
+
jsonrpc: "2.0",
|
165
|
+
id: id,
|
166
|
+
result: {
|
167
|
+
tools: @tools.values
|
168
|
+
}
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def tools_call_response(id, params)
|
173
|
+
tool_name = params["name"]
|
174
|
+
arguments = params["arguments"] || {}
|
175
|
+
|
176
|
+
unless @tools.key?(tool_name)
|
177
|
+
return {
|
178
|
+
jsonrpc: "2.0",
|
179
|
+
id: id,
|
180
|
+
error: {
|
181
|
+
code: -32602,
|
182
|
+
message: "Unknown tool",
|
183
|
+
data: "Tool '#{tool_name}' not found"
|
184
|
+
}
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
begin
|
189
|
+
result = call_tool(tool_name, arguments)
|
190
|
+
{
|
191
|
+
jsonrpc: "2.0",
|
192
|
+
id: id,
|
193
|
+
result: {
|
194
|
+
content: [
|
195
|
+
{
|
196
|
+
type: "text",
|
197
|
+
text: result
|
198
|
+
}
|
199
|
+
],
|
200
|
+
isError: false
|
201
|
+
}
|
202
|
+
}
|
203
|
+
rescue => e
|
204
|
+
File.write(Mcpeasy::Config.log_file_path("slack", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
205
|
+
{
|
206
|
+
jsonrpc: "2.0",
|
207
|
+
id: id,
|
208
|
+
result: {
|
209
|
+
content: [
|
210
|
+
{
|
211
|
+
type: "text",
|
212
|
+
text: "❌ Error: #{e.message}"
|
213
|
+
}
|
214
|
+
],
|
215
|
+
isError: true
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def call_tool(tool_name, arguments)
|
222
|
+
# Initialize SlackTool only when needed
|
223
|
+
@slack_tool ||= SlackTool.new
|
224
|
+
|
225
|
+
case tool_name
|
226
|
+
when "test_connection"
|
227
|
+
test_connection
|
228
|
+
when "list_channels"
|
229
|
+
list_channels
|
230
|
+
when "post_message"
|
231
|
+
post_message(arguments)
|
232
|
+
else
|
233
|
+
raise "Unknown tool: #{tool_name}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_connection
|
238
|
+
response = @slack_tool.test_connection
|
239
|
+
if response["ok"]
|
240
|
+
"✅ Successfully connected to Slack. Bot: #{response["user"]}, Team: #{response["team"]}"
|
241
|
+
else
|
242
|
+
raise "Authentication failed: #{response["error"]}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def list_channels
|
247
|
+
channels = @slack_tool.list_channels
|
248
|
+
output = "📋 #{channels.count} Available channels: "
|
249
|
+
output << channels.map { |c| "##{c[:name]} (ID: #{c[:id]})" }.join(", ")
|
250
|
+
output
|
251
|
+
end
|
252
|
+
|
253
|
+
def post_message(arguments)
|
254
|
+
# Validate required arguments
|
255
|
+
unless arguments["channel"]
|
256
|
+
raise "Missing required argument: channel"
|
257
|
+
end
|
258
|
+
unless arguments["text"]
|
259
|
+
raise "Missing required argument: text"
|
260
|
+
end
|
261
|
+
|
262
|
+
channel = arguments["channel"].to_s.sub(/^#/, "")
|
263
|
+
text = arguments["text"].to_s
|
264
|
+
username = arguments["username"]&.to_s
|
265
|
+
thread_ts = arguments["thread_ts"]&.to_s
|
266
|
+
|
267
|
+
response = @slack_tool.post_message(
|
268
|
+
channel: channel,
|
269
|
+
text: text,
|
270
|
+
username: username&.empty? ? nil : username,
|
271
|
+
thread_ts: thread_ts&.empty? ? nil : thread_ts
|
272
|
+
)
|
273
|
+
|
274
|
+
"✅ Message posted successfully to ##{channel} (Message timestamp: #{response["ts"]})"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
if __FILE__ == $0
|
279
|
+
MCPServer.new.run
|
280
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "slack-ruby-client"
|
5
|
+
require "mcpeasy/config"
|
6
|
+
|
7
|
+
class SlackTool
|
8
|
+
def initialize
|
9
|
+
ensure_env!
|
10
|
+
@client = Slack::Web::Client.new(
|
11
|
+
token: Mcpeasy::Config.slack_bot_token,
|
12
|
+
timeout: 10, # 10 second timeout
|
13
|
+
open_timeout: 5 # 5 second connection timeout
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post_message(channel:, text:, username: nil, thread_ts: nil)
|
18
|
+
# Clean up parameters
|
19
|
+
clean_channel = channel.to_s.sub(/^#/, "").strip
|
20
|
+
clean_text = text.to_s.strip
|
21
|
+
|
22
|
+
# Validate inputs
|
23
|
+
raise "Channel cannot be empty" if clean_channel.empty?
|
24
|
+
raise "Text cannot be empty" if clean_text.empty?
|
25
|
+
|
26
|
+
# Build request parameters
|
27
|
+
params = {
|
28
|
+
channel: clean_channel,
|
29
|
+
text: clean_text
|
30
|
+
}
|
31
|
+
params[:username] = username if username && !username.to_s.strip.empty?
|
32
|
+
params[:thread_ts] = thread_ts if thread_ts && !thread_ts.to_s.strip.empty?
|
33
|
+
|
34
|
+
# Retry logic for reliability
|
35
|
+
max_retries = 3
|
36
|
+
retry_count = 0
|
37
|
+
|
38
|
+
begin
|
39
|
+
response = @client.chat_postMessage(params)
|
40
|
+
|
41
|
+
if response["ok"]
|
42
|
+
response
|
43
|
+
else
|
44
|
+
raise "Failed to post message: #{response["error"]} (#{response.inspect})"
|
45
|
+
end
|
46
|
+
rescue Slack::Web::Api::Errors::TooManyRequestsError => e
|
47
|
+
retry_count += 1
|
48
|
+
if retry_count <= max_retries
|
49
|
+
sleep_time = e.retry_after || 1
|
50
|
+
sleep(sleep_time)
|
51
|
+
retry
|
52
|
+
else
|
53
|
+
raise "Slack API Error: #{e.message}"
|
54
|
+
end
|
55
|
+
rescue Slack::Web::Api::Errors::SlackError => e
|
56
|
+
retry_count += 1
|
57
|
+
if retry_count <= max_retries && retryable_error?(e)
|
58
|
+
sleep(0.5 * retry_count) # Exponential backoff
|
59
|
+
retry
|
60
|
+
else
|
61
|
+
raise "Slack API Error: #{e.message}"
|
62
|
+
end
|
63
|
+
rescue => e
|
64
|
+
Mcpeasy::Config.ensure_config_dirs
|
65
|
+
File.write(Mcpeasy::Config.log_file_path("slack", "error"), "#{Time.now}: SlackTool error: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
66
|
+
raise e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def list_channels
|
71
|
+
response = @client.conversations_list(types: "public_channel,private_channel")
|
72
|
+
|
73
|
+
if response["ok"]
|
74
|
+
response["channels"].to_a.map do |channel|
|
75
|
+
{
|
76
|
+
name: channel["name"],
|
77
|
+
id: channel["id"]
|
78
|
+
}
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise "Failed to list channels: #{response["error"]}"
|
82
|
+
end
|
83
|
+
rescue Slack::Web::Api::Errors::SlackError => e
|
84
|
+
raise "Slack API Error: #{e.message}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_connection
|
88
|
+
response = @client.auth_test
|
89
|
+
|
90
|
+
if response["ok"]
|
91
|
+
response
|
92
|
+
else
|
93
|
+
raise "Authentication failed: #{response["error"]}"
|
94
|
+
end
|
95
|
+
rescue Slack::Web::Api::Errors::SlackError => e
|
96
|
+
raise "Slack API Error: #{e.message}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def tool_definitions
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def retryable_error?(error)
|
105
|
+
# Network-related errors that might be temporary
|
106
|
+
error.is_a?(Slack::Web::Api::Errors::TimeoutError) ||
|
107
|
+
error.is_a?(Slack::Web::Api::Errors::UnavailableError) ||
|
108
|
+
(error.respond_to?(:message) && error.message.include?("timeout"))
|
109
|
+
end
|
110
|
+
|
111
|
+
def ensure_env!
|
112
|
+
unless Mcpeasy::Config.slack_bot_token
|
113
|
+
raise <<~ERROR
|
114
|
+
Slack bot token is not configured!
|
115
|
+
Please run: mcp set slack_bot_token YOUR_TOKEN
|
116
|
+
ERROR
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/logs/.keep
ADDED
File without changes
|
data/mcpeasy.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/mcpeasy/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "mcpeasy"
|
7
|
+
spec.version = Mcpeasy::VERSION
|
8
|
+
spec.authors = ["Joel Helbling"]
|
9
|
+
spec.email = ["joel@joelhelbling.com"]
|
10
|
+
|
11
|
+
spec.summary = "MCP servers made easy"
|
12
|
+
spec.description = "mcpeasy, LM squeezy - Easy-to-use MCP servers for Google Calendar, Google Drive, Google Meet, and Slack"
|
13
|
+
spec.homepage = "https://github.com/joelhelbling/mcpeasy"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/joelhelbling/mcpeasy"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(File.expand_path(f) == __FILE__) ||
|
25
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
spec.bindir = "bin"
|
30
|
+
spec.executables = ["mcpz"]
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Dependencies
|
34
|
+
spec.add_dependency "google-apis-calendar_v3", "~> 0.35"
|
35
|
+
spec.add_dependency "google-apis-drive_v3", "~> 0.45"
|
36
|
+
spec.add_dependency "googleauth", "~> 1.8"
|
37
|
+
spec.add_dependency "slack-ruby-client", "~> 2.1"
|
38
|
+
spec.add_dependency "webrick", "~> 1.8"
|
39
|
+
spec.add_dependency "thor", "~> 1.3"
|
40
|
+
spec.add_dependency "mother", "~> 0.1"
|
41
|
+
|
42
|
+
# Development dependencies
|
43
|
+
spec.add_development_dependency "standard", "~> 1.50"
|
44
|
+
|
45
|
+
# Post-install message
|
46
|
+
spec.post_install_message = "mcpeasy installed! Run 'mcpz setup' to configure."
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mcpeasy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joel Helbling
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: google-apis-calendar_v3
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.35'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.35'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: google-apis-drive_v3
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.45'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.45'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: googleauth
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.8'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.8'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: slack-ruby-client
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.1'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.1'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: webrick
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.8'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.8'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: thor
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.3'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.3'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: mother
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.1'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.1'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: standard
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.50'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1.50'
|
124
|
+
description: mcpeasy, LM squeezy - Easy-to-use MCP servers for Google Calendar, Google
|
125
|
+
Drive, Google Meet, and Slack
|
126
|
+
email:
|
127
|
+
- joel@joelhelbling.com
|
128
|
+
executables:
|
129
|
+
- mcpz
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".claude/settings.json"
|
134
|
+
- ".claudeignore"
|
135
|
+
- ".envrc"
|
136
|
+
- ".mcp.json"
|
137
|
+
- CLAUDE.md
|
138
|
+
- README.md
|
139
|
+
- bin/mcpz
|
140
|
+
- env.template
|
141
|
+
- ext/setup.rb
|
142
|
+
- lib/mcpeasy.rb
|
143
|
+
- lib/mcpeasy/cli.rb
|
144
|
+
- lib/mcpeasy/config.rb
|
145
|
+
- lib/mcpeasy/setup.rb
|
146
|
+
- lib/mcpeasy/version.rb
|
147
|
+
- lib/utilities/_google/auth_server.rb
|
148
|
+
- lib/utilities/gcal/README.md
|
149
|
+
- lib/utilities/gcal/cli.rb
|
150
|
+
- lib/utilities/gcal/gcal_tool.rb
|
151
|
+
- lib/utilities/gcal/mcp.rb
|
152
|
+
- lib/utilities/gdrive/README.md
|
153
|
+
- lib/utilities/gdrive/cli.rb
|
154
|
+
- lib/utilities/gdrive/gdrive_tool.rb
|
155
|
+
- lib/utilities/gdrive/mcp.rb
|
156
|
+
- lib/utilities/gmeet/README.md
|
157
|
+
- lib/utilities/gmeet/cli.rb
|
158
|
+
- lib/utilities/gmeet/gmeet_tool.rb
|
159
|
+
- lib/utilities/gmeet/mcp.rb
|
160
|
+
- lib/utilities/slack/README.md
|
161
|
+
- lib/utilities/slack/cli.rb
|
162
|
+
- lib/utilities/slack/mcp.rb
|
163
|
+
- lib/utilities/slack/slack_tool.rb
|
164
|
+
- logs/.keep
|
165
|
+
- mcpeasy.gemspec
|
166
|
+
homepage: https://github.com/joelhelbling/mcpeasy
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata:
|
170
|
+
allowed_push_host: https://rubygems.org
|
171
|
+
homepage_uri: https://github.com/joelhelbling/mcpeasy
|
172
|
+
source_code_uri: https://github.com/joelhelbling/mcpeasy
|
173
|
+
post_install_message: mcpeasy installed! Run 'mcpz setup' to configure.
|
174
|
+
rdoc_options: []
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: 3.0.0
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
requirements: []
|
188
|
+
rubygems_version: 3.6.7
|
189
|
+
specification_version: 4
|
190
|
+
summary: MCP servers made easy
|
191
|
+
test_files: []
|