rubygems_mcp 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +1 -28
- data/lib/rubygems_mcp/client.rb +121 -38
- data/lib/rubygems_mcp/server.rb +63 -24
- data/lib/rubygems_mcp/version.rb +1 -1
- data/sig/rubygems_mcp.rbs +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a457f8426c2139f86ee038c95ceaca65c6b58ae60505f3f7c20e01f6d8df0b01
|
|
4
|
+
data.tar.gz: d8076f2259213d6a4737bbf4c3e1ec9f479354fc24a15bec3c3cdf7bd343bcbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52e7b9d695199beeabd3becceaf587c30e2d5b8b862a6a5648c3d290ef9a5a365c3709f2d43547dac81d3366beef9d23a3d12d32ad7bbec4208bf90c12b89704
|
|
7
|
+
data.tar.gz: 5730b35728600a1b2f1526cd2d18d87167b62054910c4b3c026481743bd1bbaa36c4f05aa0722b51fc62439b40322d81aca96a57dc7687abff8a00beba4e67d8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (2025-11-21)
|
|
4
|
+
|
|
5
|
+
- Enhance Ruby version changelog retrieval and increase maximum response size
|
|
6
|
+
- Rename rubygems key to rubygems-dev in mcp.json configuration for development environment setup
|
|
7
|
+
|
|
8
|
+
## 0.1.1 (2025-11-21)
|
|
9
|
+
|
|
10
|
+
- Update fast-mcp dependency version constraints to allow versions >= 0.1 and < 2.0
|
|
11
|
+
- Fix Resource class references to use FastMcp::Resource instead of MCP::Resource
|
|
12
|
+
- Update resource methods from `default_content` to `content` to match fast-mcp 1.0.0+ API
|
|
13
|
+
- Fix test expectations to match fast-mcp API (tool names without `::` separators, resources as Array)
|
|
14
|
+
- Add explicit `tool_name` definitions to all tools for user-friendly snake_case names (e.g., `get_latest_versions` instead of long class names)
|
|
15
|
+
- Fix array argument definitions: Change from `filled(:array)` to `array(:string)` to fix JSON schema conversion errors with dry-schema 1.14.1+ (fixes "Could not find an equivalent conversion for type :array" error)
|
|
16
|
+
|
|
3
17
|
## 0.1.0 (2025-01-15)
|
|
4
18
|
|
|
5
19
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# rubygems_mcp
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/rubygems_mcp) [](https://github.com/amkisko/rubygems_mcp.rb/actions/workflows/test.yml)
|
|
3
|
+
[](https://badge.fury.io/rb/rubygems_mcp) [](https://github.com/amkisko/rubygems_mcp.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/rubygems_mcp.rb)
|
|
4
4
|
|
|
5
5
|
Ruby gem providing RubyGems and Ruby version information via MCP (Model Context Protocol) server tools. Integrates with MCP-compatible clients like Cursor IDE, Claude Desktop, and other MCP-enabled tools.
|
|
6
6
|
|
|
@@ -26,19 +26,6 @@ gem install rubygems_mcp
|
|
|
26
26
|
|
|
27
27
|
For Cursor IDE, create or update `.cursor/mcp.json` in your project:
|
|
28
28
|
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"mcpServers": {
|
|
32
|
-
"rubygems": {
|
|
33
|
-
"command": "bundle",
|
|
34
|
-
"args": ["exec", "rubygems_mcp"]
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Or if installed globally:
|
|
41
|
-
|
|
42
29
|
```json
|
|
43
30
|
{
|
|
44
31
|
"mcpServers": {
|
|
@@ -56,20 +43,6 @@ For Claude Desktop, edit the MCP configuration file:
|
|
|
56
43
|
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
57
44
|
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
58
45
|
|
|
59
|
-
```json
|
|
60
|
-
{
|
|
61
|
-
"mcpServers": {
|
|
62
|
-
"rubygems": {
|
|
63
|
-
"command": "bundle",
|
|
64
|
-
"args": ["exec", "rubygems_mcp"],
|
|
65
|
-
"cwd": "/path/to/your/project"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Or if installed globally:
|
|
72
|
-
|
|
73
46
|
```json
|
|
74
47
|
{
|
|
75
48
|
"mcpServers": {
|
data/lib/rubygems_mcp/client.rb
CHANGED
|
@@ -14,8 +14,8 @@ module RubygemsMcp
|
|
|
14
14
|
# all_versions = client.get_gem_versions("rails")
|
|
15
15
|
# ruby_version = client.get_latest_ruby_version
|
|
16
16
|
class Client
|
|
17
|
-
# Maximum response size (
|
|
18
|
-
MAX_RESPONSE_SIZE = 1024 * 1024 #
|
|
17
|
+
# Maximum response size (5MB) to protect against crawler protection pages
|
|
18
|
+
MAX_RESPONSE_SIZE = 5 * 1024 * 1024 # 5MB
|
|
19
19
|
|
|
20
20
|
# Custom exception for corrupted data
|
|
21
21
|
class CorruptedDataError < StandardError
|
|
@@ -349,18 +349,36 @@ module RubygemsMcp
|
|
|
349
349
|
end
|
|
350
350
|
end
|
|
351
351
|
|
|
352
|
-
# Get changelog
|
|
352
|
+
# Get full changelog content for a Ruby version from release notes
|
|
353
353
|
#
|
|
354
|
-
# @param version [String] Ruby version (e.g., "3.4.7")
|
|
355
|
-
# @return [Hash] Hash with :version, :release_notes_url, and :
|
|
354
|
+
# @param version [String] Ruby version (e.g., "3.4.7" or "4.0.0-preview2")
|
|
355
|
+
# @return [Hash] Hash with :version, :release_notes_url, and :content (full content)
|
|
356
356
|
def get_ruby_version_changelog(version)
|
|
357
357
|
# First get the release notes URL for this version
|
|
358
358
|
versions = get_ruby_versions
|
|
359
|
-
|
|
360
|
-
|
|
359
|
+
|
|
360
|
+
# Normalize the input version for comparison (handles formats like "4.0.0-preview2" -> "4.0.0.pre.preview2")
|
|
361
|
+
normalized_input = begin
|
|
362
|
+
Gem::Version.new(version).to_s
|
|
363
|
+
rescue ArgumentError
|
|
364
|
+
version
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Try exact match first, then normalized match
|
|
368
|
+
version_data = versions.find do |v|
|
|
369
|
+
v[:version] == version ||
|
|
370
|
+
v[:version] == normalized_input ||
|
|
371
|
+
begin
|
|
372
|
+
Gem::Version.new(v[:version]).to_s == normalized_input
|
|
373
|
+
rescue ArgumentError
|
|
374
|
+
false
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
return {version: version, release_notes_url: nil, content: nil, error: "Version not found"} unless version_data
|
|
361
379
|
|
|
362
380
|
release_notes_url = version_data[:release_notes_url]
|
|
363
|
-
return {version: version, release_notes_url: nil,
|
|
381
|
+
return {version: version, release_notes_url: nil, content: nil, error: "No release notes available"} unless release_notes_url
|
|
364
382
|
|
|
365
383
|
cache_key = "ruby_changelog:#{version}"
|
|
366
384
|
|
|
@@ -371,29 +389,32 @@ module RubygemsMcp
|
|
|
371
389
|
|
|
372
390
|
uri = URI(release_notes_url)
|
|
373
391
|
response = make_request(uri, parse_html: true)
|
|
374
|
-
return {version: version, release_notes_url: release_notes_url,
|
|
392
|
+
return {version: version, release_notes_url: release_notes_url, content: nil, error: "Failed to fetch release notes"} unless response
|
|
375
393
|
|
|
376
|
-
# Extract the main content -
|
|
377
|
-
|
|
378
|
-
content = response.css("div.content, div.entry-content, article, main").first || response.css("body").first
|
|
394
|
+
# Extract the main content - Ruby release notes use div#content
|
|
395
|
+
content = response.css("div#content").first || response.css("div.content, div.entry-content, article, main").first
|
|
379
396
|
|
|
380
397
|
if content
|
|
381
|
-
#
|
|
398
|
+
# Remove navigation and metadata elements
|
|
399
|
+
content.css("p.post-info, .post-info, nav, .navigation, header, footer, .sidebar").remove
|
|
400
|
+
|
|
401
|
+
# Get the full text content, preserving structure
|
|
382
402
|
text = content.text.strip
|
|
383
|
-
# Split into paragraphs and take first 3-5 meaningful ones
|
|
384
|
-
paragraphs = text.split(/\n\n+/).reject { |p| p.strip.length < 50 }
|
|
385
|
-
summary = paragraphs.first(5).join("\n\n").strip
|
|
386
403
|
|
|
387
|
-
#
|
|
388
|
-
|
|
404
|
+
# Clean up excessive whitespace but preserve paragraph structure
|
|
405
|
+
text = text.gsub(/\n{3,}/, "\n\n")
|
|
406
|
+
text = text.gsub(/[ \t]+/, " ")
|
|
407
|
+
|
|
408
|
+
# Remove empty lines at start/end
|
|
409
|
+
text = text.strip
|
|
389
410
|
else
|
|
390
|
-
|
|
411
|
+
text = nil
|
|
391
412
|
end
|
|
392
413
|
|
|
393
414
|
result = {
|
|
394
415
|
version: version,
|
|
395
416
|
release_notes_url: release_notes_url,
|
|
396
|
-
|
|
417
|
+
content: text
|
|
397
418
|
}
|
|
398
419
|
|
|
399
420
|
# Cache for 24 hours
|
|
@@ -562,9 +583,10 @@ module RubygemsMcp
|
|
|
562
583
|
|
|
563
584
|
# Extract the main content - try GitHub release page first, then generic selectors
|
|
564
585
|
content = if changelog_uri.include?("github.com") && changelog_uri.include?("/releases/")
|
|
565
|
-
# GitHub release page - look for release notes in markdown-body
|
|
566
|
-
response.css(".markdown-body
|
|
567
|
-
response.css("
|
|
586
|
+
# GitHub release page - look for release notes in markdown-body
|
|
587
|
+
response.css(".markdown-body").first ||
|
|
588
|
+
response.css("[data-testid='release-body'], .release-body").first ||
|
|
589
|
+
response.css("div.repository-content article").first
|
|
568
590
|
else
|
|
569
591
|
# Generic changelog page
|
|
570
592
|
response.css("div.content, div.entry-content, article, main, .markdown-body").first
|
|
@@ -573,29 +595,90 @@ module RubygemsMcp
|
|
|
573
595
|
content ||= response.css("body").first
|
|
574
596
|
|
|
575
597
|
summary = if content
|
|
598
|
+
# Remove UI elements, navigation, and error messages
|
|
599
|
+
content.css("nav, header, footer, .navigation, .sidebar, .blankslate, details, summary, .Box-footer, .Counter, [data-view-component], script, style").remove
|
|
600
|
+
|
|
601
|
+
# Remove elements with common UI classes
|
|
602
|
+
content.css("[class*='blankslate'], [class*='Box-footer'], [class*='Counter'], [class*='details-toggle']").remove
|
|
603
|
+
|
|
604
|
+
# Get text content
|
|
576
605
|
text = content.text.strip
|
|
577
|
-
|
|
606
|
+
|
|
607
|
+
# Remove common GitHub UI text patterns
|
|
578
608
|
text = text.gsub(/Notifications.*?signed in.*?reload/im, "")
|
|
579
609
|
text = text.gsub(/You must be signed in.*?reload/im, "")
|
|
580
610
|
text = text.gsub(/There was an error.*?reload/im, "")
|
|
611
|
+
text = text.gsub(/Please reload this page.*?/im, "")
|
|
612
|
+
text = text.gsub(/Loading.*?/im, "")
|
|
613
|
+
text = text.gsub(/Uh oh!.*?/im, "")
|
|
614
|
+
text = text.gsub(/Assets.*?\d+.*?/im, "")
|
|
615
|
+
|
|
616
|
+
# Remove commit hashes and issue references that are just links without context
|
|
617
|
+
text = text.gsub(/\b[a-f0-9]{7,40}\b/, "") # Remove commit hashes
|
|
618
|
+
text = text.gsub(/#\d+\s*$/, "") # Remove trailing issue numbers without context
|
|
619
|
+
|
|
620
|
+
# Clean up whitespace
|
|
621
|
+
text = text.gsub(/\n{3,}/, "\n\n")
|
|
622
|
+
text = text.gsub(/[ \t]{2,}/, " ")
|
|
623
|
+
|
|
624
|
+
# Split into lines and filter out irrelevant content
|
|
625
|
+
lines = text.split(/\n+/)
|
|
626
|
+
|
|
627
|
+
# Filter out lines that are likely UI elements or irrelevant
|
|
628
|
+
filtered_lines = []
|
|
629
|
+
prev_line_was_meaningful = false
|
|
630
|
+
|
|
631
|
+
lines.each_with_index do |line, idx|
|
|
632
|
+
stripped = line.strip
|
|
633
|
+
next if stripped.empty?
|
|
634
|
+
|
|
635
|
+
# Skip UI elements
|
|
636
|
+
next if stripped.match?(/^(Notifications|You must|There was|Please reload|Loading|Uh oh|Assets|\d+\s*$)/i)
|
|
637
|
+
next if stripped.match?(/^\/\s*$/)
|
|
638
|
+
next if stripped.match?(/^[a-f0-9]{7,40}$/) # Standalone commit hashes
|
|
639
|
+
next if stripped.match?(/^\s*#\d+\s*$/) # Standalone issue numbers
|
|
640
|
+
|
|
641
|
+
# Skip author names that appear alone (pattern: First Last or First Middle Last)
|
|
642
|
+
# Author names typically appear after a change description ends with punctuation
|
|
643
|
+
if stripped.match?(/^[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,2}$/) && stripped.length < 50 && !stripped.match?(/^[A-Z][a-z]+ [A-Z]\./) # Not initials like "J. Smith"
|
|
644
|
+
# Check if previous line ends with punctuation (end of sentence = author attribution follows)
|
|
645
|
+
if idx > 0 && filtered_lines.any?
|
|
646
|
+
prev = filtered_lines.last.to_s.strip
|
|
647
|
+
# If previous line ends with punctuation, this standalone name is likely an author
|
|
648
|
+
if prev.match?(/[.!]$/)
|
|
649
|
+
next
|
|
650
|
+
end
|
|
651
|
+
elsif idx == 0
|
|
652
|
+
# First line that's just a name, skip it
|
|
653
|
+
next
|
|
654
|
+
end
|
|
655
|
+
end
|
|
581
656
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
text.split(/\n+/)
|
|
657
|
+
# Keep meaningful lines
|
|
658
|
+
if stripped.length >= 10
|
|
659
|
+
filtered_lines << line
|
|
660
|
+
prev_line_was_meaningful = true
|
|
661
|
+
end
|
|
588
662
|
end
|
|
589
663
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
664
|
+
# Remove trailing "No changes." and similar repetitive endings
|
|
665
|
+
while filtered_lines.last&.strip&.match?(/^(No changes\.?|Guides)$/i)
|
|
666
|
+
filtered_lines.pop
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Join back and clean up
|
|
670
|
+
summary_text = filtered_lines.join("\n").strip
|
|
671
|
+
|
|
672
|
+
# Remove excessive blank lines
|
|
673
|
+
summary_text = summary_text.gsub(/\n{3,}/, "\n\n")
|
|
674
|
+
|
|
675
|
+
# Limit length but keep it reasonable for changelogs
|
|
676
|
+
if summary_text.length > 10000
|
|
677
|
+
# Try to cut at a reasonable point (end of a section)
|
|
678
|
+
cut_point = summary_text[0..10000].rindex(/\n\n/)
|
|
679
|
+
summary_text = summary_text[0..(cut_point || 10000)].strip + "\n\n..."
|
|
680
|
+
end
|
|
596
681
|
|
|
597
|
-
# Limit summary length
|
|
598
|
-
summary_text = summary_text[0..3000] + "..." if summary_text.length > 3000
|
|
599
682
|
summary_text.empty? ? nil : summary_text
|
|
600
683
|
end
|
|
601
684
|
|
data/lib/rubygems_mcp/server.rb
CHANGED
|
@@ -16,6 +16,21 @@ FastMcp = MCP unless defined?(FastMcp)
|
|
|
16
16
|
module MCP
|
|
17
17
|
module Transports
|
|
18
18
|
class StdioTransport
|
|
19
|
+
if method_defined?(:send_error)
|
|
20
|
+
alias_method :original_send_error, :send_error
|
|
21
|
+
|
|
22
|
+
def send_error(code, message, id = nil)
|
|
23
|
+
# Use placeholder id if nil to satisfy strict MCP client validation
|
|
24
|
+
# JSON-RPC 2.0 allows null for notifications, but MCP clients require valid id
|
|
25
|
+
id = "error_#{SecureRandom.hex(8)}" if id.nil?
|
|
26
|
+
original_send_error(code, message, id)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Server
|
|
33
|
+
if method_defined?(:send_error)
|
|
19
34
|
alias_method :original_send_error, :send_error
|
|
20
35
|
|
|
21
36
|
def send_error(code, message, id = nil)
|
|
@@ -26,17 +41,6 @@ module MCP
|
|
|
26
41
|
end
|
|
27
42
|
end
|
|
28
43
|
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
44
|
end
|
|
41
45
|
|
|
42
46
|
module RubygemsMcp
|
|
@@ -151,11 +155,12 @@ module RubygemsMcp
|
|
|
151
155
|
|
|
152
156
|
# Get latest versions for a list of gems with release dates
|
|
153
157
|
class GetLatestVersionsTool < BaseTool
|
|
158
|
+
tool_name "get_latest_versions"
|
|
154
159
|
description "Get latest versions for a list of gems with release dates and licenses. Supports GraphQL-like field selection."
|
|
155
160
|
|
|
156
161
|
arguments do
|
|
157
|
-
required(:gem_names).
|
|
158
|
-
optional(:fields).
|
|
162
|
+
required(:gem_names).array(:string, min_size?: 1).description("Array of gem names (e.g., ['rails', 'nokogiri', 'rack'])")
|
|
163
|
+
optional(:fields).array(:string).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
164
|
end
|
|
160
165
|
|
|
161
166
|
def call(gem_names:, fields: nil)
|
|
@@ -165,6 +170,7 @@ module RubygemsMcp
|
|
|
165
170
|
|
|
166
171
|
# Get all versions for a single gem
|
|
167
172
|
class GetGemVersionsTool < BaseTool
|
|
173
|
+
tool_name "get_gem_versions"
|
|
168
174
|
description "Get all versions for a single gem with release dates and licenses, sorted by version descending. Supports GraphQL-like field selection."
|
|
169
175
|
|
|
170
176
|
arguments do
|
|
@@ -172,7 +178,7 @@ module RubygemsMcp
|
|
|
172
178
|
optional(:limit).filled(:integer).description("Maximum number of versions to return (for pagination)")
|
|
173
179
|
optional(:offset).filled(:integer).description("Number of versions to skip (for pagination)")
|
|
174
180
|
optional(:sort).filled(:string).description("Sort order: version_desc, version_asc, date_desc, or date_asc (default: version_desc)")
|
|
175
|
-
optional(:fields).
|
|
181
|
+
optional(:fields).array(:string).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
182
|
end
|
|
177
183
|
|
|
178
184
|
def call(gem_name:, limit: nil, offset: 0, sort: "version_desc", fields: nil)
|
|
@@ -189,6 +195,7 @@ module RubygemsMcp
|
|
|
189
195
|
|
|
190
196
|
# Get latest Ruby version with release date
|
|
191
197
|
class GetLatestRubyVersionTool < BaseTool
|
|
198
|
+
tool_name "get_latest_ruby_version"
|
|
192
199
|
description "Get latest Ruby version with release date"
|
|
193
200
|
|
|
194
201
|
arguments do
|
|
@@ -202,6 +209,7 @@ module RubygemsMcp
|
|
|
202
209
|
|
|
203
210
|
# Get all Ruby versions with release dates
|
|
204
211
|
class GetRubyVersionsTool < BaseTool
|
|
212
|
+
tool_name "get_ruby_versions"
|
|
205
213
|
description "Get all Ruby versions with release dates, download URLs, and release notes URLs, sorted by version descending"
|
|
206
214
|
|
|
207
215
|
arguments do
|
|
@@ -224,6 +232,7 @@ module RubygemsMcp
|
|
|
224
232
|
|
|
225
233
|
# Get changelog summary for a Ruby version
|
|
226
234
|
class GetRubyVersionChangelogTool < BaseTool
|
|
235
|
+
tool_name "get_ruby_version_changelog"
|
|
227
236
|
description "Get changelog summary for a specific Ruby version by fetching and parsing the release notes"
|
|
228
237
|
|
|
229
238
|
arguments do
|
|
@@ -231,17 +240,41 @@ module RubygemsMcp
|
|
|
231
240
|
end
|
|
232
241
|
|
|
233
242
|
def call(version:)
|
|
234
|
-
get_client.get_ruby_version_changelog(version)
|
|
243
|
+
result = get_client.get_ruby_version_changelog(version)
|
|
244
|
+
# Convert content to MCP-compliant format
|
|
245
|
+
# MCP expects content to be an array of content items with type and text fields
|
|
246
|
+
if result.is_a?(Hash)
|
|
247
|
+
if result[:content].is_a?(String) && !result[:content].empty?
|
|
248
|
+
result[:content] = [{type: "text", text: result[:content]}]
|
|
249
|
+
elsif result[:content].is_a?(String) && result[:content].empty?
|
|
250
|
+
result[:content] = []
|
|
251
|
+
elsif result[:content].nil?
|
|
252
|
+
result[:content] = []
|
|
253
|
+
elsif result[:content].is_a?(Array)
|
|
254
|
+
# Ensure array elements have the correct format
|
|
255
|
+
result[:content] = result[:content].map do |item|
|
|
256
|
+
if item.is_a?(String)
|
|
257
|
+
{type: "text", text: item}
|
|
258
|
+
elsif item.is_a?(Hash) && !item.key?(:type)
|
|
259
|
+
item.merge(type: "text")
|
|
260
|
+
else
|
|
261
|
+
item
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
result
|
|
235
267
|
end
|
|
236
268
|
end
|
|
237
269
|
|
|
238
270
|
# Get gem information (summary, homepage, etc.)
|
|
239
271
|
class GetGemInfoTool < BaseTool
|
|
272
|
+
tool_name "get_gem_info"
|
|
240
273
|
description "Get detailed information about a gem (summary, homepage, source code, documentation, licenses, authors, dependencies, downloads). Supports GraphQL-like field selection."
|
|
241
274
|
|
|
242
275
|
arguments do
|
|
243
276
|
required(:gem_name).filled(:string).description("Gem name (e.g., 'rails')")
|
|
244
|
-
optional(:fields).
|
|
277
|
+
optional(:fields).array(:string).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
278
|
end
|
|
246
279
|
|
|
247
280
|
def call(gem_name:, fields: nil)
|
|
@@ -251,6 +284,7 @@ module RubygemsMcp
|
|
|
251
284
|
|
|
252
285
|
# Get reverse dependencies (gems that depend on this gem)
|
|
253
286
|
class GetGemReverseDependenciesTool < BaseTool
|
|
287
|
+
tool_name "get_gem_reverse_dependencies"
|
|
254
288
|
description "Get reverse dependencies - list of gems that depend on the specified gem"
|
|
255
289
|
|
|
256
290
|
arguments do
|
|
@@ -264,6 +298,7 @@ module RubygemsMcp
|
|
|
264
298
|
|
|
265
299
|
# Get download statistics for a gem version
|
|
266
300
|
class GetGemVersionDownloadsTool < BaseTool
|
|
301
|
+
tool_name "get_gem_version_downloads"
|
|
267
302
|
description "Get download statistics for a specific gem version"
|
|
268
303
|
|
|
269
304
|
arguments do
|
|
@@ -278,6 +313,7 @@ module RubygemsMcp
|
|
|
278
313
|
|
|
279
314
|
# Get latest gems (most recently added)
|
|
280
315
|
class GetLatestGemsTool < BaseTool
|
|
316
|
+
tool_name "get_latest_gems"
|
|
281
317
|
description "Get latest gems - most recently added gems to RubyGems.org"
|
|
282
318
|
|
|
283
319
|
arguments do
|
|
@@ -291,6 +327,7 @@ module RubygemsMcp
|
|
|
291
327
|
|
|
292
328
|
# Get recently updated gems
|
|
293
329
|
class GetRecentlyUpdatedGemsTool < BaseTool
|
|
330
|
+
tool_name "get_recently_updated_gems"
|
|
294
331
|
description "Get recently updated gems - most recently updated gem versions"
|
|
295
332
|
|
|
296
333
|
arguments do
|
|
@@ -304,6 +341,7 @@ module RubygemsMcp
|
|
|
304
341
|
|
|
305
342
|
# Get changelog summary for a gem
|
|
306
343
|
class GetGemChangelogTool < BaseTool
|
|
344
|
+
tool_name "get_gem_changelog"
|
|
307
345
|
description "Get changelog summary for a gem by fetching and parsing the changelog from its changelog_uri"
|
|
308
346
|
|
|
309
347
|
arguments do
|
|
@@ -318,6 +356,7 @@ module RubygemsMcp
|
|
|
318
356
|
|
|
319
357
|
# Search for gems by name
|
|
320
358
|
class SearchGemsTool < BaseTool
|
|
359
|
+
tool_name "search_gems"
|
|
321
360
|
description "Search for gems by name on RubyGems"
|
|
322
361
|
|
|
323
362
|
arguments do
|
|
@@ -330,13 +369,13 @@ module RubygemsMcp
|
|
|
330
369
|
end
|
|
331
370
|
|
|
332
371
|
# Resource: Popular Ruby gems list
|
|
333
|
-
class PopularGemsResource <
|
|
372
|
+
class PopularGemsResource < FastMcp::Resource
|
|
334
373
|
uri "rubygems://popular"
|
|
335
374
|
resource_name "Popular Ruby Gems"
|
|
336
375
|
description "A curated list of popular Ruby gems with their latest versions"
|
|
337
376
|
mime_type "application/json"
|
|
338
377
|
|
|
339
|
-
def
|
|
378
|
+
def content
|
|
340
379
|
client = Client.new
|
|
341
380
|
popular_gems = %w[
|
|
342
381
|
rails nokogiri bundler rake rspec devise puma sidekiq
|
|
@@ -366,13 +405,13 @@ module RubygemsMcp
|
|
|
366
405
|
end
|
|
367
406
|
|
|
368
407
|
# Resource: Ruby version compatibility information
|
|
369
|
-
class RubyVersionCompatibilityResource <
|
|
408
|
+
class RubyVersionCompatibilityResource < FastMcp::Resource
|
|
370
409
|
uri "rubygems://ruby/compatibility"
|
|
371
410
|
resource_name "Ruby Version Compatibility"
|
|
372
411
|
description "Information about Ruby version compatibility and release dates"
|
|
373
412
|
mime_type "application/json"
|
|
374
413
|
|
|
375
|
-
def
|
|
414
|
+
def content
|
|
376
415
|
client = Client.new
|
|
377
416
|
ruby_versions = client.get_ruby_versions(limit: 20, sort: :version_desc)
|
|
378
417
|
latest = client.get_latest_ruby_version
|
|
@@ -402,13 +441,13 @@ module RubygemsMcp
|
|
|
402
441
|
end
|
|
403
442
|
|
|
404
443
|
# Resource: Ruby maintenance status for all versions
|
|
405
|
-
class RubyMaintenanceStatusResource <
|
|
444
|
+
class RubyMaintenanceStatusResource < FastMcp::Resource
|
|
406
445
|
uri "rubygems://ruby/maintenance"
|
|
407
446
|
resource_name "Ruby Maintenance Status"
|
|
408
447
|
description "Detailed maintenance status for all Ruby versions including EOL dates and maintenance phases"
|
|
409
448
|
mime_type "application/json"
|
|
410
449
|
|
|
411
|
-
def
|
|
450
|
+
def content
|
|
412
451
|
client = Client.new
|
|
413
452
|
maintenance_status = client.get_ruby_maintenance_status
|
|
414
453
|
|
|
@@ -428,13 +467,13 @@ module RubygemsMcp
|
|
|
428
467
|
end
|
|
429
468
|
|
|
430
469
|
# Resource: Latest Ruby version
|
|
431
|
-
class LatestRubyVersionResource <
|
|
470
|
+
class LatestRubyVersionResource < FastMcp::Resource
|
|
432
471
|
uri "rubygems://ruby/latest"
|
|
433
472
|
resource_name "Latest Ruby Version"
|
|
434
473
|
description "The latest stable Ruby version with release date"
|
|
435
474
|
mime_type "application/json"
|
|
436
475
|
|
|
437
|
-
def
|
|
476
|
+
def content
|
|
438
477
|
client = Client.new
|
|
439
478
|
latest = client.get_latest_ruby_version
|
|
440
479
|
JSON.pretty_generate(latest)
|
data/lib/rubygems_mcp/version.rb
CHANGED
data/sig/rubygems_mcp.rbs
CHANGED
|
@@ -59,7 +59,7 @@ module RubygemsMcp
|
|
|
59
59
|
) -> Array[Hash[Symbol, (String | nil)]]
|
|
60
60
|
|
|
61
61
|
# Get changelog for a Ruby version
|
|
62
|
-
def get_ruby_version_changelog: (String version) -> Hash[Symbol, (String | nil)]
|
|
62
|
+
def get_ruby_version_changelog: (String version) -> Hash[Symbol, (String | nil)] # Returns :version, :release_notes_url, :content
|
|
63
63
|
|
|
64
64
|
# Get reverse dependencies (gems that depend on this gem)
|
|
65
65
|
def get_gem_reverse_dependencies: (String gem_name) -> Array[String]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubygems_mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -13,16 +13,22 @@ dependencies:
|
|
|
13
13
|
name: fast-mcp
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '0.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
|
-
- - "
|
|
26
|
+
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '0.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: nokogiri
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|