bundler_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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6e512b7990ccd597566b67a7fa65d6ee92347c670d394cec2fa913c243542353
4
+ data.tar.gz: 3b1a3d045f42f34a05264d22497cd8e0d588deb58a332227bb68f714bba81ed8
5
+ SHA512:
6
+ metadata.gz: 3417bfa815a0b37472355fda7f4ebe8d6d575efabb73c2bf5be107c367dcfb289997d5495f61f037f2d2cd9140698610226e2d1d44716e4098cd375df98f660e
7
+ data.tar.gz: 7fcd767de09ad6691f3fe14c2be1de4b3fa50420dfa34d0af5a9908d310cefa58d1f79a81cdb6a8e5377f80ec11d44434ef7ca47c6aa49dc4239089685a099bc
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## [0.1.0] - 2025-05-18
6
+
7
+ - Initial release
8
+ - Add `list_project_gems` tool
9
+ - Add `get_gem_details` tool
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Mike Subelsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # BundlerMCP
2
+
3
+ A Model Context Protocol (MCP) server enabling AI agents to query information about gems in a Ruby project's Gemfile, including source code and metadata. Built with [fast-mcp](https://github.com/yjacquin/fast-mcp).
4
+
5
+ [![CI](https://github.com/subelsky/bundler_mcp/actions/workflows/main.yml/badge.svg)](https://github.com/subelsky/bundler_mcp/actions/workflows/main.yml)
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```bash
12
+ bundle add bundler_mcp --group=development
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ 1) Generate the binstub:
18
+
19
+ ```bash
20
+ bundle binstubs bundler_mcp
21
+ ```
22
+
23
+ 2) Configure your client to execute the binstub. Here are examples that work for Claude and Cursor:
24
+
25
+ ### Basic Example (mcp.json)
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "bundler-mcp": {
31
+ "command": "/Users/mike/my_project/bin/bundler_mcp"
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ### Example with logging and explicit Gemfile
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "bundler-mcp": {
43
+ "command": "/Users/mike/my_project/bin/bundler_mcp",
44
+
45
+ "env": {
46
+ "BUNDLER_MCP_LOG_FILE": "/Users/mike/my_project/log/mcp.log",
47
+ "BUNDLE_GEMFILE": "/Users/mike/my_project/subdir/Gemfile"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Available Tools
55
+
56
+ The server provides two tools for AI agents:
57
+
58
+ #### list_project_gems
59
+
60
+ Lists all bundled Ruby gems with their:
61
+
62
+ - Versions
63
+ - Descriptions
64
+ - Installation paths
65
+ - Top-level documentation locations (e.g. `README` and `CHANGELOG`)
66
+
67
+ ![list_project_gems tool](/docs/list_project_gems.png)
68
+
69
+ #### get_gem_details
70
+
71
+ Retrieves detailed information about a specific gem, including:
72
+
73
+ - Version
74
+ - Description
75
+ - Installation path
76
+ - Top-level documentation locations
77
+ - Source code file locations
78
+
79
+ ![get_gem_details tool](/docs/get_gem_details.png)
80
+
81
+ ## Environment Variables
82
+
83
+ - `BUNDLE_GEMFILE`: Used by Bundler to locate your Gemfile. If you use the binstub method described in the [Usage](#usage) section, this is usually not needed.
84
+ - `BUNDLER_MCP_LOG_FILE`: Path to log file. Useful for troubleshooting (defaults to no logging)
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bin/setup` to install dependencies and `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
89
+
90
+ ### Testing with the MCP Inspector
91
+
92
+ You can test the server directly using the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector):
93
+
94
+ ```bash
95
+ # Basic usage
96
+ npx @modelcontextprotocol/inspector ./bin/bundler_mcp
97
+
98
+ # With logging enabled
99
+ BUNDLER_MCP_LOG_FILE=/tmp/log/mcp.log npx @modelcontextprotocol/inspector ./bin/bundler_mcp
100
+
101
+ # With custom Gemfile
102
+ BUNDLE_GEMFILE=./other/Gemfile npx @modelcontextprotocol/inspector ./bin/bundler_mcp
103
+ ```
104
+
105
+ ### Release Process
106
+
107
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version:
108
+
109
+ 1. Update the version number in `version.rb`
110
+ 2. Run `bundle exec rake release`
111
+
112
+ This will:
113
+
114
+ - Create a git tag for the version
115
+ - Push git commits and the created tag
116
+ - Push the `.gem` file to [rubygems.org](https://rubygems.org)
117
+
118
+ ## Contributing
119
+
120
+ Bug reports and pull requests are welcome on GitHub at https://github.com/subelsky/bundler_mcp.
121
+
122
+ ## License
123
+
124
+ Open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
125
+
126
+ ## Author
127
+
128
+ [Mike Subelsky](https://subelsky.com)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
Binary file
Binary file
data/exe/bundler_mcp ADDED
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "logger"
5
+ require_relative "../lib/bundler_mcp/server"
6
+
7
+ # Set this up by running `bundle binstubs bundler_mcp`;
8
+ # your client will then able to run `bin/bundler_mcp`
9
+ # in the correct Bundler environment
10
+
11
+ logfile_path = ENV.fetch("BUNDLER_MCP_LOGFILE", nil)
12
+ logger = Logger.new(logfile_path || File::NULL)
13
+
14
+ BundlerMCP::Server.run(logger:)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "bundler_mcp"
5
+
6
+ module BundlerMCP
7
+ # Responsible for checking the environment to make sure Bundler can find gems
8
+ class EnvironmentChecker
9
+ # Check for a Gemfile and raise an error if not found
10
+ # @return [Pathname] The path to the Gemfile
11
+ # @raise [GemfileNotFound]
12
+ # If Bundler cannot find a Gemfile; can be avoided by setting BUNDLE_GEMFILE
13
+ def self.check!
14
+ raise GemfileNotFound unless Bundler.default_gemfile.exist?
15
+
16
+ Bundler.default_gemfile
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module BundlerMCP
6
+ # Represents a Ruby gem and its associated data
7
+ # @see https://docs.ruby-lang.org/en/2.5.0/Gem/Specification.html
8
+ class GemResource
9
+ extend Forwardable
10
+
11
+ def_delegators :gem, :name, :description, :full_gem_path
12
+
13
+ # @param gem [Gem::Specification]
14
+ def initialize(gem)
15
+ @gem = gem
16
+
17
+ @version = gem.version.to_s
18
+ @base_path = Pathname(@gem.full_gem_path)
19
+ @doc_paths = @base_path.glob("{README,CHANGELOG}*").map!(&:to_s)
20
+ @lib_path = @base_path.join("lib").to_s
21
+ end
22
+
23
+ # @return [Hash] A hash containing the gem's details
24
+ def to_h(include_source_files: false)
25
+ base_hash = { name:,
26
+ version:,
27
+ description:,
28
+ full_gem_path:,
29
+ lib_path:,
30
+ top_level_documentation_paths: doc_paths }
31
+
32
+ base_hash[:source_files] = @base_path.glob("**/*.{rb,c,rake}").map!(&:to_s) if include_source_files
33
+
34
+ base_hash
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :gem, :version, :base_path, :doc_paths, :lib_path
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_resource"
4
+ require "singleton"
5
+
6
+ module BundlerMCP
7
+ # Represents a collection of GemResource objects defining all currently bundled gems
8
+ # @see GemResource
9
+ class ResourceCollection
10
+ include Singleton
11
+ include Enumerable
12
+
13
+ def initialize
14
+ @resources = []
15
+
16
+ Gem.loaded_specs.each_value do |spec|
17
+ # Returns most gems as Bundler::StubSpecification, which does not expose
18
+ # many gem details, so we convert to Gem::Specification
19
+ spec = Gem::Specification.find_by_name(spec.name)
20
+ resources << GemResource.new(spec)
21
+ end
22
+ end
23
+
24
+ # Iterate over all GemResource objects in the collection
25
+ # @yield [GemResource] each GemResource object in the collection
26
+ def each(&)
27
+ resources.each(&)
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :resources
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "fast_mcp"
5
+ require "json"
6
+ require "pathname"
7
+ require "logger"
8
+ require_relative "version"
9
+ require_relative "resource_collection"
10
+ require_relative "tool_collection"
11
+ require_relative "environment_checker"
12
+
13
+ module BundlerMCP
14
+ # Main server class for BundlerMCP
15
+ # @see https://github.com/yjacquin/fast-mcp/blob/main/examples/server_with_stdio_transport.rb
16
+ class Server
17
+ # Convenience method to start the server
18
+ # @return [void]
19
+ def self.run(**args)
20
+ new(**args).run
21
+ end
22
+
23
+ # Initialize the server
24
+ # @return [void]
25
+ def initialize(logger: Logger.new(File::NULL))
26
+ @logger = logger
27
+
28
+ @server = FastMcp::Server.new(
29
+ name: "bundler-gem-documentation",
30
+ version: VERSION
31
+ )
32
+ end
33
+
34
+ # Start the MCP server
35
+ # @return [void]
36
+ def run
37
+ gemfile_path = EnvironmentChecker.check!
38
+ logger.info "Starting BundlerMCP server with Gemfile: #{gemfile_path}"
39
+
40
+ ToolCollection.each do |tool|
41
+ server.register_tool(tool)
42
+ end
43
+
44
+ server.start
45
+ rescue StandardError => e
46
+ logger.error e.message
47
+ raise e
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :logger, :server
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Pathname(__dir__).glob("tools/*.rb").each do |tool|
4
+ require tool
5
+ end
6
+
7
+ module BundlerMCP
8
+ # Contains all tools that can be used by the caller
9
+ class ToolCollection
10
+ # @yield [Tool] each MCP tool
11
+ # @yieldparam tool [Tool] a tool in the collection
12
+ def self.each(&)
13
+ TOOLS.each(&)
14
+ end
15
+
16
+ TOOLS = BundlerMCP::Tools.constants.map { |c| BundlerMCP::Tools.const_get(c) }
17
+ private_constant :TOOLS
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fast_mcp"
4
+
5
+ module BundlerMCP
6
+ module Tools
7
+ # Retrieve details about a specific bundled Ruby gem
8
+ # @see ResourceCollection
9
+ class GetGemDetails < FastMcp::Tool
10
+ description <<~DESC
11
+ Returns detailed information about one **Ruby Gem** that is installed in the current project.
12
+
13
+ ‣ Use this tool when you need to know the version, summary, or source code location for a gem that is installed in the current project.
14
+ ‣ Do **not** use it for gems that are *not* part of this project.
15
+
16
+ The data comes directly from Gemfile.lock and the local installation, so it is always up-to-date and requires **no Internet access**.
17
+ DESC
18
+
19
+ arguments do
20
+ required(:name).filled(:string).description("The name of the gem to fetch")
21
+ end
22
+
23
+ # @return [String] Tool name exposed by FastMCP to clients
24
+ def self.name = "get_gem_details"
25
+
26
+ # @param collection [ResourceCollection] contains installed gems
27
+ def initialize(collection = ResourceCollection.instance)
28
+ @resource_collection = collection
29
+ super()
30
+ end
31
+
32
+ # @param name [String] The name of the gem to fetch
33
+ # @return [Hash] Contains the gem's details, or an error message if the gem is not found
34
+ def call(name:)
35
+ name = name.to_s.strip
36
+ gem_resource = resource_collection.find { |r| r.name == name }
37
+
38
+ data = if gem_resource
39
+ gem_resource.to_h(include_source_files: true)
40
+ else
41
+ { error: "We could not find '#{name}' among the project's bundled gems" }
42
+ end
43
+
44
+ JSON.generate(data)
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :resource_collection
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mcp/tool"
4
+
5
+ module BundlerMCP
6
+ module Tools
7
+ # Informs the client of all bundled Ruby gems with their versions, descriptions, installation paths,
8
+ # documentation, and (optionally) source code locations
9
+ # @see https://github.com/yjacquin/fast-mcp/blob/main/docs/tools.md
10
+ class ListProjectGems < FastMcp::Tool
11
+ description <<~DESC
12
+ Lists **all** Ruby Gems declared in this project's Gemfile/Gemfile.lock, returning for each gem:
13
+ name, version, short description, install path, and top-level docs path.
14
+
15
+ ‣ Use this tool whenever you need to know about the project's gem dependencies and how they work.
16
+ ‣ This tool reads local files only, so the data is authoritative for the current workspace
17
+ and never relies on the Internet.
18
+ DESC
19
+
20
+ # @return [String] Tool name exposed by FastMCP to clients
21
+ def self.name
22
+ "list_project_gems"
23
+ end
24
+
25
+ # @param collection [ResourceCollection] contains installed gems
26
+ def initialize(collection = ResourceCollection.instance)
27
+ @resource_collection = collection
28
+ super()
29
+ end
30
+
31
+ # Invoke the tool to list all installed gems
32
+ # @return [Array<Hash>] An array of hashes containing gem details
33
+ def call
34
+ data = resource_collection.map(&:to_h)
35
+ JSON.generate(data)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :resource_collection
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundlerMCP
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bundler_mcp/version"
4
+
5
+ # Namespace for BundlerMCP code
6
+ module BundlerMCP
7
+ # Base error class for BundlerMCP
8
+ Error = Class.new(StandardError)
9
+
10
+ # Raised when Bundler cannot find a Gemfile
11
+ class GemfileNotFound < Error
12
+ def initialize(msg = DEFAULT_MESSAGE)
13
+ super
14
+ end
15
+
16
+ DEFAULT_MESSAGE = "Bundler cannot find a Gemfile; try setting BUNDLE_GEMFILE"
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundler_mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Subelsky
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.6'
26
+ - !ruby/object:Gem::Dependency
27
+ name: fast-mcp
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.4'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
40
+ description: |
41
+ A Model Context Protocol (MCP) server that enables AI agents to query information
42
+ about gems in a Ruby project's Gemfile, including source code and metadata.
43
+ email:
44
+ - 12020+subelsky@users.noreply.github.com
45
+ executables:
46
+ - bundler_mcp
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".ruby-version"
51
+ - CHANGELOG.md
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - docs/get_gem_details.png
56
+ - docs/list_project_gems.png
57
+ - exe/bundler_mcp
58
+ - lib/bundler_mcp.rb
59
+ - lib/bundler_mcp/environment_checker.rb
60
+ - lib/bundler_mcp/gem_resource.rb
61
+ - lib/bundler_mcp/resource_collection.rb
62
+ - lib/bundler_mcp/server.rb
63
+ - lib/bundler_mcp/tool_collection.rb
64
+ - lib/bundler_mcp/tools/get_gem_details.rb
65
+ - lib/bundler_mcp/tools/list_project_gems.rb
66
+ - lib/bundler_mcp/version.rb
67
+ homepage: https://github.com/subelsky/bundler_mcp
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ allowed_push_host: https://rubygems.org
72
+ homepage_uri: https://github.com/subelsky/bundler_mcp
73
+ source_code_uri: https://github.com/subelsky/bundler_mcp
74
+ changelog_uri: https://github.com/subelsky/bundler_mcp/blob/main/CHANGELOG.md
75
+ rubygems_mfa_required: 'true'
76
+ keywords: mcp gems bundler
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '3.2'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.6.8
92
+ specification_version: 4
93
+ summary: MCP server for searching Ruby bundled gem documentation and metadata
94
+ test_files: []