rubygems_mcp 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/CHANGELOG.md +33 -0
- data/LICENSE.md +22 -0
- data/README.md +369 -0
- data/bin/rubygems_mcp +18 -0
- data/lib/rubygems_mcp/client.rb +912 -0
- data/lib/rubygems_mcp/server.rb +444 -0
- data/lib/rubygems_mcp/version.rb +3 -0
- data/lib/rubygems_mcp.rb +31 -0
- data/sig/rubygems_mcp.rbs +199 -0
- metadata +266 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "fast_mcp"
|
|
5
|
+
require "rubygems_mcp"
|
|
6
|
+
require "logger"
|
|
7
|
+
require "stringio"
|
|
8
|
+
require "securerandom"
|
|
9
|
+
|
|
10
|
+
# Alias MCP to FastMcp for compatibility
|
|
11
|
+
FastMcp = MCP unless defined?(FastMcp)
|
|
12
|
+
|
|
13
|
+
# Monkey-patch fast-mcp to ensure error responses always have a valid id
|
|
14
|
+
# JSON-RPC 2.0 allows id: null for notifications, but MCP clients (Cursor/Inspector)
|
|
15
|
+
# use strict Zod validation that requires id to be a string or number
|
|
16
|
+
module MCP
|
|
17
|
+
module Transports
|
|
18
|
+
class StdioTransport
|
|
19
|
+
alias_method :original_send_error, :send_error
|
|
20
|
+
|
|
21
|
+
def send_error(code, message, id = nil)
|
|
22
|
+
# Use placeholder id if nil to satisfy strict MCP client validation
|
|
23
|
+
# JSON-RPC 2.0 allows null for notifications, but MCP clients require valid id
|
|
24
|
+
id = "error_#{SecureRandom.hex(8)}" if id.nil?
|
|
25
|
+
original_send_error(code, message, id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Server
|
|
31
|
+
alias_method :original_send_error, :send_error
|
|
32
|
+
|
|
33
|
+
def send_error(code, message, id = nil)
|
|
34
|
+
# Use placeholder id if nil to satisfy strict MCP client validation
|
|
35
|
+
# JSON-RPC 2.0 allows null for notifications, but MCP clients require valid id
|
|
36
|
+
id = "error_#{SecureRandom.hex(8)}" if id.nil?
|
|
37
|
+
original_send_error(code, message, id)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module RubygemsMcp
|
|
43
|
+
# MCP Server for RubyGems integration
|
|
44
|
+
#
|
|
45
|
+
# This server provides MCP tools for interacting with RubyGems and Ruby version information
|
|
46
|
+
# Usage: bundle exec rubygems_mcp
|
|
47
|
+
class Server
|
|
48
|
+
# Simple null logger that suppresses all output
|
|
49
|
+
# Must implement the same interface as MCP::Logger
|
|
50
|
+
class NullLogger
|
|
51
|
+
attr_accessor :transport, :client_initialized
|
|
52
|
+
|
|
53
|
+
def initialize
|
|
54
|
+
@transport = nil
|
|
55
|
+
@client_initialized = false
|
|
56
|
+
@level = nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_writer :level
|
|
60
|
+
|
|
61
|
+
attr_reader :level
|
|
62
|
+
|
|
63
|
+
def debug(*)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def info(*)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def warn(*)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def error(*)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fatal(*)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def unknown(*)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def client_initialized?
|
|
82
|
+
@client_initialized
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set_client_initialized(value = true)
|
|
86
|
+
@client_initialized = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def stdio_transport?
|
|
90
|
+
@transport == :stdio
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def rack_transport?
|
|
94
|
+
@transport == :rack
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.start
|
|
99
|
+
# Create server with null logger to prevent any output
|
|
100
|
+
server = FastMcp::Server.new(
|
|
101
|
+
name: "rubygems",
|
|
102
|
+
version: RubygemsMcp::VERSION,
|
|
103
|
+
logger: NullLogger.new
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Register all tools
|
|
107
|
+
register_tools(server)
|
|
108
|
+
|
|
109
|
+
# Register all resources
|
|
110
|
+
register_resources(server)
|
|
111
|
+
|
|
112
|
+
# Start the server (blocks and speaks MCP over STDIN/STDOUT)
|
|
113
|
+
server.start
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.register_tools(server)
|
|
117
|
+
server.register_tool(GetLatestVersionsTool)
|
|
118
|
+
server.register_tool(GetGemVersionsTool)
|
|
119
|
+
server.register_tool(GetLatestRubyVersionTool)
|
|
120
|
+
server.register_tool(GetRubyVersionsTool)
|
|
121
|
+
server.register_tool(GetRubyVersionChangelogTool)
|
|
122
|
+
server.register_tool(GetGemInfoTool)
|
|
123
|
+
server.register_tool(GetGemReverseDependenciesTool)
|
|
124
|
+
server.register_tool(GetGemVersionDownloadsTool)
|
|
125
|
+
server.register_tool(GetLatestGemsTool)
|
|
126
|
+
server.register_tool(GetRecentlyUpdatedGemsTool)
|
|
127
|
+
server.register_tool(GetGemChangelogTool)
|
|
128
|
+
server.register_tool(SearchGemsTool)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.register_resources(server)
|
|
132
|
+
server.register_resource(PopularGemsResource)
|
|
133
|
+
server.register_resource(RubyVersionCompatibilityResource)
|
|
134
|
+
server.register_resource(RubyMaintenanceStatusResource)
|
|
135
|
+
server.register_resource(LatestRubyVersionResource)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Base tool class with common error handling
|
|
139
|
+
#
|
|
140
|
+
# Exceptions raised in tool #call methods are automatically caught by fast-mcp
|
|
141
|
+
# and converted to MCP error results with the request ID preserved.
|
|
142
|
+
# fast-mcp uses send_error_result(message, id) which sends a result with
|
|
143
|
+
# isError: true, not a JSON-RPC error response.
|
|
144
|
+
class BaseTool < FastMcp::Tool
|
|
145
|
+
protected
|
|
146
|
+
|
|
147
|
+
def get_client
|
|
148
|
+
Client.new
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get latest versions for a list of gems with release dates
|
|
153
|
+
class GetLatestVersionsTool < BaseTool
|
|
154
|
+
description "Get latest versions for a list of gems with release dates and licenses. Supports GraphQL-like field selection."
|
|
155
|
+
|
|
156
|
+
arguments do
|
|
157
|
+
required(:gem_names).filled(:array, min_size?: 1).description("Array of gem names (e.g., ['rails', 'nokogiri', 'rack'])")
|
|
158
|
+
optional(:fields).filled(:array).description("GraphQL-like field selection. Available: name, version, release_date, license, built_at, prerelease, platform, ruby_version, rubygems_version, downloads_count, sha, spec_sha, requirements, metadata")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def call(gem_names:, fields: nil)
|
|
162
|
+
get_client.get_latest_versions(gem_names, fields: fields)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Get all versions for a single gem
|
|
167
|
+
class GetGemVersionsTool < BaseTool
|
|
168
|
+
description "Get all versions for a single gem with release dates and licenses, sorted by version descending. Supports GraphQL-like field selection."
|
|
169
|
+
|
|
170
|
+
arguments do
|
|
171
|
+
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
172
|
+
optional(:limit).filled(:integer).description("Maximum number of versions to return (for pagination)")
|
|
173
|
+
optional(:offset).filled(:integer).description("Number of versions to skip (for pagination)")
|
|
174
|
+
optional(:sort).filled(:string).description("Sort order: version_desc, version_asc, date_desc, or date_asc (default: version_desc)")
|
|
175
|
+
optional(:fields).filled(:array).description("GraphQL-like field selection. Available: version, release_date, license, built_at, prerelease, platform, ruby_version, rubygems_version, downloads_count, sha, spec_sha, requirements, metadata")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def call(gem_name:, limit: nil, offset: 0, sort: "version_desc", fields: nil)
|
|
179
|
+
valid_sorts = %w[version_desc version_asc date_desc date_asc]
|
|
180
|
+
sort_value = sort.to_s
|
|
181
|
+
sort_sym = if valid_sorts.include?(sort_value)
|
|
182
|
+
sort_value.to_sym
|
|
183
|
+
else
|
|
184
|
+
:version_desc
|
|
185
|
+
end
|
|
186
|
+
get_client.get_gem_versions(gem_name, limit: limit, offset: offset, sort: sort_sym, fields: fields)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Get latest Ruby version with release date
|
|
191
|
+
class GetLatestRubyVersionTool < BaseTool
|
|
192
|
+
description "Get latest Ruby version with release date"
|
|
193
|
+
|
|
194
|
+
arguments do
|
|
195
|
+
# No arguments required
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def call
|
|
199
|
+
get_client.get_latest_ruby_version
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get all Ruby versions with release dates
|
|
204
|
+
class GetRubyVersionsTool < BaseTool
|
|
205
|
+
description "Get all Ruby versions with release dates, download URLs, and release notes URLs, sorted by version descending"
|
|
206
|
+
|
|
207
|
+
arguments do
|
|
208
|
+
optional(:limit).filled(:integer).description("Maximum number of versions to return (for pagination)")
|
|
209
|
+
optional(:offset).filled(:integer).description("Number of versions to skip (for pagination)")
|
|
210
|
+
optional(:sort).filled(:string).description("Sort order: version_desc, version_asc, date_desc, or date_asc (default: version_desc)")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def call(limit: nil, offset: 0, sort: "version_desc")
|
|
214
|
+
valid_sorts = %w[version_desc version_asc date_desc date_asc]
|
|
215
|
+
sort_value = sort.to_s
|
|
216
|
+
sort_sym = if valid_sorts.include?(sort_value)
|
|
217
|
+
sort_value.to_sym
|
|
218
|
+
else
|
|
219
|
+
:version_desc
|
|
220
|
+
end
|
|
221
|
+
get_client.get_ruby_versions(limit: limit, offset: offset, sort: sort_sym)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Get changelog summary for a Ruby version
|
|
226
|
+
class GetRubyVersionChangelogTool < BaseTool
|
|
227
|
+
description "Get changelog summary for a specific Ruby version by fetching and parsing the release notes"
|
|
228
|
+
|
|
229
|
+
arguments do
|
|
230
|
+
required(:version).filled(:string).description("Ruby version (e.g., '3.4.7')")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def call(version:)
|
|
234
|
+
get_client.get_ruby_version_changelog(version)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get gem information (summary, homepage, etc.)
|
|
239
|
+
class GetGemInfoTool < BaseTool
|
|
240
|
+
description "Get detailed information about a gem (summary, homepage, source code, documentation, licenses, authors, dependencies, downloads). Supports GraphQL-like field selection."
|
|
241
|
+
|
|
242
|
+
arguments do
|
|
243
|
+
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
244
|
+
optional(:fields).filled(:array).description("GraphQL-like field selection. Available: name, version, summary, description, homepage, source_code, documentation, licenses, authors, info, downloads, version_downloads, yanked, dependencies, changelog_uri, funding_uri, platform, sha, spec_sha, metadata")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def call(gem_name:, fields: nil)
|
|
248
|
+
get_client.get_gem_info(gem_name, fields: fields)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Get reverse dependencies (gems that depend on this gem)
|
|
253
|
+
class GetGemReverseDependenciesTool < BaseTool
|
|
254
|
+
description "Get reverse dependencies - list of gems that depend on the specified gem"
|
|
255
|
+
|
|
256
|
+
arguments do
|
|
257
|
+
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def call(gem_name:)
|
|
261
|
+
get_client.get_gem_reverse_dependencies(gem_name)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Get download statistics for a gem version
|
|
266
|
+
class GetGemVersionDownloadsTool < BaseTool
|
|
267
|
+
description "Get download statistics for a specific gem version"
|
|
268
|
+
|
|
269
|
+
arguments do
|
|
270
|
+
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
271
|
+
required(:version).filled(:string).description("Gem version (e.g., '7.1.0')")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def call(gem_name:, version:)
|
|
275
|
+
get_client.get_gem_version_downloads(gem_name, version)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Get latest gems (most recently added)
|
|
280
|
+
class GetLatestGemsTool < BaseTool
|
|
281
|
+
description "Get latest gems - most recently added gems to RubyGems.org"
|
|
282
|
+
|
|
283
|
+
arguments do
|
|
284
|
+
optional(:limit).filled(:integer).description("Maximum number of gems to return (default: 30, max: 50)")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def call(limit: 30)
|
|
288
|
+
get_client.get_latest_gems(limit: limit)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Get recently updated gems
|
|
293
|
+
class GetRecentlyUpdatedGemsTool < BaseTool
|
|
294
|
+
description "Get recently updated gems - most recently updated gem versions"
|
|
295
|
+
|
|
296
|
+
arguments do
|
|
297
|
+
optional(:limit).filled(:integer).description("Maximum number of gems to return (default: 30, max: 50)")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def call(limit: 30)
|
|
301
|
+
get_client.get_recently_updated_gems(limit: limit)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Get changelog summary for a gem
|
|
306
|
+
class GetGemChangelogTool < BaseTool
|
|
307
|
+
description "Get changelog summary for a gem by fetching and parsing the changelog from its changelog_uri"
|
|
308
|
+
|
|
309
|
+
arguments do
|
|
310
|
+
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
311
|
+
optional(:version).filled(:string).description("Gem version (optional, uses latest if not provided)")
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def call(gem_name:, version: nil)
|
|
315
|
+
get_client.get_gem_changelog(gem_name, version: version)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Search for gems by name
|
|
320
|
+
class SearchGemsTool < BaseTool
|
|
321
|
+
description "Search for gems by name on RubyGems"
|
|
322
|
+
|
|
323
|
+
arguments do
|
|
324
|
+
required(:query).filled(:string).description("Search query (e.g., 'rails')")
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def call(query:)
|
|
328
|
+
get_client.search_gems(query)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Resource: Popular Ruby gems list
|
|
333
|
+
class PopularGemsResource < MCP::Resource
|
|
334
|
+
uri "rubygems://popular"
|
|
335
|
+
resource_name "Popular Ruby Gems"
|
|
336
|
+
description "A curated list of popular Ruby gems with their latest versions"
|
|
337
|
+
mime_type "application/json"
|
|
338
|
+
|
|
339
|
+
def default_content
|
|
340
|
+
client = Client.new
|
|
341
|
+
popular_gems = %w[
|
|
342
|
+
rails nokogiri bundler rake rspec devise puma sidekiq
|
|
343
|
+
pg mysql2 redis json webrick sinatra haml sass
|
|
344
|
+
jekyll octokit faraday httparty rest-client
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
gems_data = popular_gems.map do |gem_name|
|
|
348
|
+
versions = client.get_gem_versions(gem_name, limit: 1, fields: ["name", "version", "release_date"])
|
|
349
|
+
latest = versions.first
|
|
350
|
+
if latest
|
|
351
|
+
result = latest.dup
|
|
352
|
+
result[:name] = gem_name
|
|
353
|
+
result
|
|
354
|
+
else
|
|
355
|
+
{name: gem_name, version: nil, release_date: nil}
|
|
356
|
+
end
|
|
357
|
+
rescue Client::ResponseSizeExceededError, Client::CorruptedDataError => e
|
|
358
|
+
# Skip gems that exceed size limit or have corrupted data
|
|
359
|
+
{name: gem_name, version: nil, release_date: nil, error: e.message}
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Filter out gems that weren't found (nil versions)
|
|
363
|
+
gems_data = gems_data.reject { |g| g[:version].nil? }
|
|
364
|
+
JSON.pretty_generate(gems_data)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Resource: Ruby version compatibility information
|
|
369
|
+
class RubyVersionCompatibilityResource < MCP::Resource
|
|
370
|
+
uri "rubygems://ruby/compatibility"
|
|
371
|
+
resource_name "Ruby Version Compatibility"
|
|
372
|
+
description "Information about Ruby version compatibility and release dates"
|
|
373
|
+
mime_type "application/json"
|
|
374
|
+
|
|
375
|
+
def default_content
|
|
376
|
+
client = Client.new
|
|
377
|
+
ruby_versions = client.get_ruby_versions(limit: 20, sort: :version_desc)
|
|
378
|
+
latest = client.get_latest_ruby_version
|
|
379
|
+
maintenance_status = client.get_ruby_maintenance_status
|
|
380
|
+
|
|
381
|
+
# Create a map of version to maintenance status for quick lookup
|
|
382
|
+
maintenance_status.each_with_object({}) do |status, map|
|
|
383
|
+
map[status[:version]] = status
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
data = {
|
|
387
|
+
latest: latest,
|
|
388
|
+
recent_versions: ruby_versions,
|
|
389
|
+
maintenance_status: maintenance_status.first(10), # Most recent 10 versions
|
|
390
|
+
compatibility_notes: {
|
|
391
|
+
"3.4.x" => "Latest stable series. Normal maintenance. Supports all modern gems.",
|
|
392
|
+
"3.3.x" => "Stable series. Normal maintenance until 2027. Well-supported by most gems.",
|
|
393
|
+
"3.2.x" => "Security maintenance only. EOL expected 2026-03-31.",
|
|
394
|
+
"3.1.x" => "End of life (EOL: 2025-03-26). No longer supported.",
|
|
395
|
+
"3.0.x" => "End of life (EOL: 2024-04-23). No longer supported.",
|
|
396
|
+
"2.7.x" => "End of life. No longer supported."
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
JSON.pretty_generate(data)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Resource: Ruby maintenance status for all versions
|
|
405
|
+
class RubyMaintenanceStatusResource < MCP::Resource
|
|
406
|
+
uri "rubygems://ruby/maintenance"
|
|
407
|
+
resource_name "Ruby Maintenance Status"
|
|
408
|
+
description "Detailed maintenance status for all Ruby versions including EOL dates and maintenance phases"
|
|
409
|
+
mime_type "application/json"
|
|
410
|
+
|
|
411
|
+
def default_content
|
|
412
|
+
client = Client.new
|
|
413
|
+
maintenance_status = client.get_ruby_maintenance_status
|
|
414
|
+
|
|
415
|
+
data = {
|
|
416
|
+
updated_at: Time.now.iso8601,
|
|
417
|
+
versions: maintenance_status,
|
|
418
|
+
summary: {
|
|
419
|
+
preview: maintenance_status.count { |v| v[:status] == "preview" },
|
|
420
|
+
normal_maintenance: maintenance_status.count { |v| v[:status] == "normal maintenance" },
|
|
421
|
+
security_maintenance: maintenance_status.count { |v| v[:status] == "security maintenance" },
|
|
422
|
+
eol: maintenance_status.count { |v| v[:status] == "eol" }
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
JSON.pretty_generate(data)
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Resource: Latest Ruby version
|
|
431
|
+
class LatestRubyVersionResource < MCP::Resource
|
|
432
|
+
uri "rubygems://ruby/latest"
|
|
433
|
+
resource_name "Latest Ruby Version"
|
|
434
|
+
description "The latest stable Ruby version with release date"
|
|
435
|
+
mime_type "application/json"
|
|
436
|
+
|
|
437
|
+
def default_content
|
|
438
|
+
client = Client.new
|
|
439
|
+
latest = client.get_latest_ruby_version
|
|
440
|
+
JSON.pretty_generate(latest)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
data/lib/rubygems_mcp.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "json"
|
|
4
|
+
require "date"
|
|
5
|
+
|
|
6
|
+
require_relative "rubygems_mcp/version"
|
|
7
|
+
require_relative "rubygems_mcp/client"
|
|
8
|
+
# Server is loaded on-demand when running the executable
|
|
9
|
+
# require_relative "rubygems_mcp/server"
|
|
10
|
+
|
|
11
|
+
module RubygemsMcp
|
|
12
|
+
# Main module for RubyGems MCP integration
|
|
13
|
+
#
|
|
14
|
+
# This gem provides:
|
|
15
|
+
# - RubygemsMcp::Client - API client for RubyGems and Ruby version information
|
|
16
|
+
#
|
|
17
|
+
# @example Basic usage
|
|
18
|
+
# require "rubygems_mcp"
|
|
19
|
+
#
|
|
20
|
+
# # Create client
|
|
21
|
+
# client = RubygemsMcp::Client.new
|
|
22
|
+
#
|
|
23
|
+
# # Get latest versions for multiple gems
|
|
24
|
+
# versions = client.get_latest_versions(["rails", "nokogiri", "rack"])
|
|
25
|
+
#
|
|
26
|
+
# # Get all versions for a gem
|
|
27
|
+
# versions = client.get_gem_versions("rails")
|
|
28
|
+
#
|
|
29
|
+
# # Get latest Ruby version
|
|
30
|
+
# ruby_version = client.get_latest_ruby_version
|
|
31
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
module RubygemsMcp
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Client
|
|
5
|
+
MAX_RESPONSE_SIZE: Integer
|
|
6
|
+
|
|
7
|
+
class ResponseSizeExceededError < StandardError
|
|
8
|
+
def initialize: (Integer size, Integer max_size) -> void
|
|
9
|
+
attr_reader size: Integer
|
|
10
|
+
attr_reader max_size: Integer
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class CorruptedDataError < StandardError
|
|
14
|
+
def initialize: (String message, ?original_error: Exception?, ?response_size: Integer?) -> void
|
|
15
|
+
attr_reader original_error: Exception?
|
|
16
|
+
attr_reader response_size: Integer?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Cache
|
|
20
|
+
def initialize: () -> void
|
|
21
|
+
def get: (String key) -> untyped?
|
|
22
|
+
def set: (String key, untyped value, Integer ttl_seconds) -> void
|
|
23
|
+
def clear: () -> void
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.cache: () -> Cache
|
|
27
|
+
|
|
28
|
+
def initialize: (?cache_enabled: bool) -> void
|
|
29
|
+
def build_http_client: (URI uri) -> Net::HTTP
|
|
30
|
+
def validate_and_parse_json: (String body, URI uri) -> (Hash[untyped, untyped] | Array[untyped])
|
|
31
|
+
def validate_and_parse_html: (String body, URI uri) -> Nokogiri::HTML::Document
|
|
32
|
+
|
|
33
|
+
# Get latest versions for multiple gems
|
|
34
|
+
def get_latest_versions: (
|
|
35
|
+
Array[String] gem_names,
|
|
36
|
+
?fields: Array[String]?
|
|
37
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
38
|
+
|
|
39
|
+
# Get all versions for a gem with pagination, sorting, and field selection
|
|
40
|
+
def get_gem_versions: (
|
|
41
|
+
String gem_name,
|
|
42
|
+
?limit: Integer?,
|
|
43
|
+
?offset: Integer,
|
|
44
|
+
?sort: (:version_desc | :version_asc | :date_desc | :date_asc),
|
|
45
|
+
?fields: Array[String]?
|
|
46
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
47
|
+
|
|
48
|
+
# Get latest Ruby version
|
|
49
|
+
def get_latest_ruby_version: () -> Hash[Symbol, (String | nil)]
|
|
50
|
+
|
|
51
|
+
# Get Ruby maintenance status
|
|
52
|
+
def get_ruby_maintenance_status: () -> Array[Hash[Symbol, (String | nil)]]
|
|
53
|
+
|
|
54
|
+
# Get all Ruby versions with download and release notes URLs
|
|
55
|
+
def get_ruby_versions: (
|
|
56
|
+
?limit: Integer?,
|
|
57
|
+
?offset: Integer,
|
|
58
|
+
?sort: (:version_desc | :version_asc | :date_desc | :date_asc)
|
|
59
|
+
) -> Array[Hash[Symbol, (String | nil)]]
|
|
60
|
+
|
|
61
|
+
# Get changelog for a Ruby version
|
|
62
|
+
def get_ruby_version_changelog: (String version) -> Hash[Symbol, (String | nil)]
|
|
63
|
+
|
|
64
|
+
# Get reverse dependencies (gems that depend on this gem)
|
|
65
|
+
def get_gem_reverse_dependencies: (String gem_name) -> Array[String]
|
|
66
|
+
|
|
67
|
+
# Get download statistics for a gem version
|
|
68
|
+
def get_gem_version_downloads: (
|
|
69
|
+
String gem_name,
|
|
70
|
+
String version
|
|
71
|
+
) -> Hash[Symbol, (String | Integer | nil)]
|
|
72
|
+
|
|
73
|
+
# Get latest gems (most recently added)
|
|
74
|
+
def get_latest_gems: (?limit: Integer) -> Array[Hash[Symbol, untyped]]
|
|
75
|
+
|
|
76
|
+
# Get recently updated gems
|
|
77
|
+
def get_recently_updated_gems: (?limit: Integer) -> Array[Hash[Symbol, untyped]]
|
|
78
|
+
|
|
79
|
+
# Get changelog for a gem
|
|
80
|
+
def get_gem_changelog: (
|
|
81
|
+
String gem_name,
|
|
82
|
+
?version: String?
|
|
83
|
+
) -> Hash[Symbol, (String | nil)]
|
|
84
|
+
|
|
85
|
+
# Get gem information with field selection
|
|
86
|
+
def get_gem_info: (
|
|
87
|
+
String gem_name,
|
|
88
|
+
?fields: Array[String]?
|
|
89
|
+
) -> Hash[Symbol, untyped]
|
|
90
|
+
|
|
91
|
+
# Search for gems with pagination
|
|
92
|
+
def search_gems: (
|
|
93
|
+
String query,
|
|
94
|
+
?limit: Integer?,
|
|
95
|
+
?offset: Integer
|
|
96
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class Server
|
|
100
|
+
class NullLogger
|
|
101
|
+
def initialize: () -> void
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class BaseTool
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class GetLatestVersionsTool < BaseTool
|
|
108
|
+
def call: (Array[String] gem_names, ?Array[String]? fields) -> Array[Hash[Symbol, untyped]]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class GetGemVersionsTool < BaseTool
|
|
112
|
+
def call: (
|
|
113
|
+
String gem_name,
|
|
114
|
+
?Integer? limit,
|
|
115
|
+
?Integer offset,
|
|
116
|
+
?String sort,
|
|
117
|
+
?Array[String]? fields
|
|
118
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class GetLatestRubyVersionTool < BaseTool
|
|
122
|
+
def call: () -> Hash[Symbol, (String | nil)]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class GetRubyVersionsTool < BaseTool
|
|
126
|
+
def call: (
|
|
127
|
+
?Integer? limit,
|
|
128
|
+
?Integer offset,
|
|
129
|
+
?String sort
|
|
130
|
+
) -> Array[Hash[Symbol, (String | nil)]]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class GetRubyVersionChangelogTool < BaseTool
|
|
134
|
+
def call: (String version) -> Hash[Symbol, (String | nil)]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class GetGemInfoTool < BaseTool
|
|
138
|
+
def call: (String gem_name, ?Array[String]? fields) -> Hash[Symbol, untyped]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class GetGemReverseDependenciesTool < BaseTool
|
|
142
|
+
def call: (String gem_name) -> Array[String]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class GetGemVersionDownloadsTool < BaseTool
|
|
146
|
+
def call: (String gem_name, String version) -> Hash[Symbol, (String | Integer | nil)]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class GetLatestGemsTool < BaseTool
|
|
150
|
+
def call: (?Integer limit) -> Array[Hash[Symbol, untyped]]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class GetRecentlyUpdatedGemsTool < BaseTool
|
|
154
|
+
def call: (?Integer limit) -> Array[Hash[Symbol, untyped]]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class GetGemChangelogTool < BaseTool
|
|
158
|
+
def call: (String gem_name, ?String? version) -> Hash[Symbol, (String | nil)]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class SearchGemsTool < BaseTool
|
|
162
|
+
def call: (String query) -> Array[Hash[Symbol, untyped]]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class PopularGemsResource
|
|
166
|
+
def self.uri: () -> String
|
|
167
|
+
def self.resource_name: () -> String
|
|
168
|
+
def uri: () -> String
|
|
169
|
+
def content: () -> String
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class RubyVersionCompatibilityResource
|
|
173
|
+
def self.uri: () -> String
|
|
174
|
+
def self.resource_name: () -> String
|
|
175
|
+
def uri: () -> String
|
|
176
|
+
def content: () -> String
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
class RubyMaintenanceStatusResource
|
|
180
|
+
def self.uri: () -> String
|
|
181
|
+
def self.resource_name: () -> String
|
|
182
|
+
def uri: () -> String
|
|
183
|
+
def content: () -> String
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class LatestRubyVersionResource
|
|
187
|
+
def self.uri: () -> String
|
|
188
|
+
def self.resource_name: () -> String
|
|
189
|
+
def uri: () -> String
|
|
190
|
+
def content: () -> String
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def self.start: () -> void
|
|
194
|
+
def self.register_tools: (untyped server) -> void
|
|
195
|
+
def self.register_resources: (untyped server) -> void
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
|