open_gemdocs 0.2.4 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd7cbbce2ee27525094486dde7d4b75c04075ca23f9c70e94fd5967b4e61075b
4
- data.tar.gz: 660ee427703e51eda2215c6f04e83b6ed919097cde4c48a736c8b7b366f40e94
3
+ metadata.gz: 9bbe6e98bfab5caa95c6324956c9a45c44460d78851aeec0187d2a2718a0009a
4
+ data.tar.gz: 5f63e28eb06fc99aa6302784266315f9db0c7564a97319a12db88ad6b1faf917
5
5
  SHA512:
6
- metadata.gz: fcc5a0e61f086ef4381b272b0cb9d47a49ae3046a9f33516279cd481ceae0bec09ea70b988cf981759d64961ca9d8917866b26d06a8a2e1422667078db86da9c
7
- data.tar.gz: 6dea5832d8b8ffcbc941d77100a84c5873dcc351441780883c45950e1cc64c26d4a75cdb565b1d500433d600d352589792616edd7ebd83390f470145d759c406
6
+ metadata.gz: f8ac2030c49a4a20960eaee3d183ffb00794678db396897f14c969088355c734fa57d9ea62613946553a7400dfcbd344613eddeb74734acf0bdad35964f0e474
7
+ data.tar.gz: bc42203b67b70b9839844febce219fa5d7b9732c609120eeac8f39d32b86de423794ace83d1c208a069eff4df9ca14f5958d57ce3a36a611e1c6d61cabb7a66b
checksums.yaml.gz.sig CHANGED
Binary file
data/.rubocop.yml CHANGED
@@ -1,8 +1,45 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.1
3
+ NewCops: enable
4
+ SuggestExtensions: false
3
5
 
4
6
  Style/StringLiterals:
5
7
  EnforcedStyle: double_quotes
6
8
 
7
9
  Style/StringLiteralsInInterpolation:
8
10
  EnforcedStyle: double_quotes
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Metrics/MethodLength:
16
+ Max: 30
17
+ Exclude:
18
+ - 'lib/open_gemdocs/mcp/tools.rb'
19
+
20
+ Metrics/ClassLength:
21
+ Max: 350
22
+
23
+ Metrics/BlockLength:
24
+ Exclude:
25
+ - 'spec/**/*'
26
+ - '*.gemspec'
27
+
28
+ Metrics/AbcSize:
29
+ Max: 35
30
+
31
+ Metrics/CyclomaticComplexity:
32
+ Max: 15
33
+
34
+ Metrics/PerceivedComplexity:
35
+ Max: 15
36
+
37
+ Layout/LineLength:
38
+ Max: 200
39
+
40
+ Naming/AccessorMethodName:
41
+ Exclude:
42
+ - 'lib/open_gemdocs/mcp/tools.rb'
43
+
44
+ Style/MultilineBlockChain:
45
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-04-27
4
+ ### Added
5
+ - Added a MCP server that can serve local yard documentation for all installed gems. This allows AI agents to access gem documentation without needing to run a local server manually.
6
+
3
7
  ## [0.2.1] - 2025-04-26
4
8
  ### Changed
5
9
  - Fixed a bug where the `--local` option was not working correctly when a specific gem was specified. The command now correctly serves the documentation for the specified gem from the local server.
data/CLAUDE.md ADDED
@@ -0,0 +1,136 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Repository Overview
6
+
7
+ Open_gemdocs is a Ruby gem that provides a command-line tool for opening Ruby gem documentation. It supports both local documentation (via Yard server) and online documentation (via gemdocs.org).
8
+
9
+ ## Common Commands
10
+
11
+ ### Development Setup
12
+ ```bash
13
+ # Install dependencies
14
+ bundle install
15
+
16
+ # Setup development environment
17
+ bin/setup
18
+
19
+ # Open IRB console with gem loaded
20
+ bin/console
21
+ ```
22
+
23
+ ### Testing
24
+ ```bash
25
+ # Run all tests
26
+ bundle exec rspec
27
+
28
+ # Run specific test file
29
+ bundle exec rspec spec/open_gemdocs_spec.rb
30
+
31
+ # Run tests via Rake
32
+ bundle exec rake spec
33
+ ```
34
+
35
+ ### Linting & Code Quality
36
+ ```bash
37
+ # Run RuboCop for code linting
38
+ bundle exec rubocop
39
+
40
+ # Auto-fix RuboCop violations
41
+ bundle exec rubocop -a
42
+
43
+ # Run all checks (tests + linting)
44
+ bundle exec rake
45
+ ```
46
+
47
+ ### Building & Installation
48
+ ```bash
49
+ # Build the gem
50
+ gem build open_gemdocs.gemspec
51
+
52
+ # Install locally for testing
53
+ gem install ./open_gemdocs-*.gem
54
+
55
+ # Build via Rake
56
+ bundle exec rake build
57
+
58
+ # Release new version (requires gem push permissions)
59
+ bundle exec rake release
60
+ ```
61
+
62
+ ### Local Testing of CLI
63
+ ```bash
64
+ # Test the main executable
65
+ bundle exec exe/open-gem-docs rails
66
+
67
+ # Test with local documentation
68
+ bundle exec exe/open-gem-docs --local rails
69
+
70
+ # Test the local-only wrapper
71
+ bundle exec exe/open-local-docs rails
72
+ ```
73
+
74
+ ### MCP Server
75
+ ```bash
76
+ # Start MCP server on default port (6789)
77
+ bundle exec exe/open-gem-docs-mcp
78
+
79
+ # Start MCP server on custom port
80
+ bundle exec exe/open-gem-docs-mcp --port 8080
81
+
82
+ # Start MCP server in stdio mode (for Claude Desktop integration)
83
+ bundle exec exe/open-gem-docs-mcp-stdio
84
+
85
+ # View MCP server help and available tools
86
+ bundle exec exe/open-gem-docs-mcp --help
87
+ ```
88
+
89
+ ## Architecture
90
+
91
+ The gem follows a simple, focused architecture with core documentation components and MCP server integration:
92
+
93
+ ### Core Module Structure
94
+ - **`lib/open_gemdocs.rb`**: Main module that orchestrates between Browser and Yard classes based on user options
95
+ - **`lib/open_gemdocs/browser.rb`**: Handles opening online documentation from gemdocs.org, includes Gemfile.lock parsing for version detection
96
+ - **`lib/open_gemdocs/yard.rb`**: Manages local Yard server lifecycle (starting, stopping, checking status) and opens local documentation
97
+
98
+ ### MCP (Model Context Protocol) Integration
99
+ The gem includes MCP server capabilities for AI assistant integration:
100
+
101
+ - **`lib/open_gemdocs/mcp/server.rb`**: MCP server implementation supporting both TCP and stdio modes
102
+ - **`lib/open_gemdocs/mcp/handlers.rb`**: JSON-RPC request handling for MCP protocol
103
+ - **`lib/open_gemdocs/mcp/tools.rb`**: MCP tool implementations providing programmatic access to gem documentation
104
+
105
+ Available MCP tools:
106
+ - `search_gems`: Search for installed Ruby gems by name
107
+ - `get_gem_info`: Get detailed metadata about a specific gem
108
+ - `start_yard_server`: Start the Yard documentation server
109
+ - `stop_yard_server`: Stop the Yard documentation server
110
+ - `get_yard_server_status`: Check if Yard server is running and where
111
+ - `get_gem_documentation_url`: Get the local documentation URL for a gem
112
+ - `fetch_gem_docs`: Fetch documentation content from Yard server
113
+
114
+ ### Key Implementation Details
115
+
116
+ 1. **Version Detection**: The Browser class automatically detects gem versions from Gemfile.lock when no version is specified
117
+ 2. **Server Management**: The Yard class manages a background Yard server process, checking if it's running and starting it if needed
118
+ 3. **Platform Dependency**: Uses macOS `open` command - platform-specific implementation would be needed for Linux/Windows support
119
+ 4. **Error Handling**: Custom `OpenGemdocs::Error` class for consistent error reporting
120
+
121
+ ### CLI Entry Points
122
+ - **`exe/open-gem-docs`**: Full-featured CLI with option parsing (--local, --version, etc.)
123
+ - **`exe/open-local-docs`**: Convenience wrapper that always uses local documentation
124
+ - **`exe/open-gem-docs-mcp`**: MCP server for TCP connections (default port 6789)
125
+ - **`exe/open-gem-docs-mcp-stdio`**: MCP server for stdio mode (Claude Desktop integration)
126
+
127
+ ## Important Considerations
128
+
129
+ - The gem is signed with a certificate in `certs/mrinterweb.pem` - ensure this is present when building releases
130
+ - Requires Ruby >= 3.1.0
131
+ - The test suite needs expansion - currently only has placeholder tests
132
+ - When modifying CLI behavior, update all relevant executables if needed
133
+ - The Yard server runs on port 8808 by default
134
+ - MCP server runs on port 6789 by default (configurable via --port)
135
+ - MCP stdio mode is designed for Claude Desktop integration via the MCP protocol
136
+ - The MCP server automatically manages the Yard server lifecycle as needed
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # OpenGemdocs
2
2
 
3
- This is ruby gem that provides a simple command line tool that helps open gem documentation. There are two documentation sources this gem supports.
3
+ This gem makes accessing ruby gem documentation easy for users with a CLI tool and AI agents with a MCP server.
4
+
5
+ There are two documentation sources this gem supports.
4
6
 
5
7
  1. local gems served with the yard gem via `yard server --gems` or `yard server --gemfile` accessible at http://localhost:8808.
6
8
  2. [https://gemdocs.org](https://gemdocs.org) - a good ruby gem documentation host
@@ -24,7 +26,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
24
26
  gem install open_gemdocs
25
27
  ```
26
28
 
27
- ## Usage
29
+ ## CLI Usage
28
30
 
29
31
  Currently, this only works on Macs because of the `open` command. It opens the documentation for a gem in your default web browser.
30
32
 
@@ -69,6 +71,55 @@ Example using the `open-local-docs` command:
69
71
  open-local-docs rspec
70
72
  ```
71
73
 
74
+ ## MCP Server
75
+
76
+ The gem includes an MCP (Model Context Protocol) server that allows AI assistants to programmatically access Ruby gem documentation. The MCP server manages a local Yard documentation server and provides tools for searching and retrieving gem documentation.
77
+
78
+ ### Starting the MCP Server
79
+
80
+ ```bash
81
+ open-gem-docs-mcp
82
+ ```
83
+
84
+ By default, the server runs on port 6789. You can specify a different port:
85
+
86
+ ```bash
87
+ open-gem-docs-mcp --port 8080
88
+ ```
89
+
90
+ ### Available MCP Tools
91
+
92
+ The MCP server provides the following tools:
93
+
94
+ - **search_gems** - Search for installed Ruby gems by name
95
+ - **get_gem_info** - Get detailed information about a specific gem
96
+ - **start_yard_server** - Start the Yard documentation server
97
+ - **stop_yard_server** - Stop the Yard documentation server
98
+ - **get_yard_server_status** - Check if the Yard server is running
99
+ - **get_gem_documentation_url** - Get the local documentation URL for a gem
100
+ - **fetch_gem_docs** - Fetch documentation content from the Yard server
101
+
102
+ ### Using with Claude Code
103
+
104
+ ```
105
+ claude mcp add open-gem-docs -- open-gem-docs-mcp-stdio
106
+ ```
107
+
108
+ ### Using with Claude Desktop
109
+
110
+ To use the MCP server with Claude Desktop, add the following to your Claude Desktop configuration:
111
+
112
+ ```json
113
+ {
114
+ "mcpServers": {
115
+ "open_gemdocs": {
116
+ "command": "open-gem-docs-mcp",
117
+ "args": ["--port", "6789"]
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
72
123
  ## Development
73
124
 
74
125
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1 @@
1
+ 32e9cdcb7c3c463dbff38e38fb53e0a51eb2cf8e80250420e40129d32911b9fce015ef3cc0266f1773f0cf39ff644a16c662068860c43d4f0d210688df4dd8f8
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ system(%q{
4
+ bundle list | awk '/ \* / {gsub(/[()]/, "", $3); print $2, $3}' | while read gem version; do
5
+ echo "Processing docs for: $gem $version"
6
+ yard gems "$gem" "$version"
7
+ done
8
+ })
data/exe/open-gem-docs CHANGED
@@ -1,34 +1,34 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'uri'
5
- require 'net/http'
6
- require 'json'
7
- require 'optparse'
8
- require_relative File.join('..', 'lib', 'open_gemdocs')
4
+ require "uri"
5
+ require "net/http"
6
+ require "json"
7
+ require "optparse"
8
+ require_relative File.join("..", "lib", "open_gemdocs")
9
9
 
10
10
  options = {}
11
11
 
12
12
  OptionParser.new do |opts|
13
- opts.banner = 'Usage: open-gem-docs [options] <gem_name>'
13
+ opts.banner = "Usage: open-gem-docs [options] <gem_name>"
14
14
 
15
- opts.on('--local', 'Use local documentation') do
15
+ opts.on("--local", "Use local documentation") do
16
16
  options[:local] = true
17
17
  end
18
- opts.on('-v', '--version VERSION', 'Specify the version') do |version|
18
+ opts.on("-v", "--version VERSION", "Specify the version") do |version|
19
19
  options[:version] = version
20
20
  end
21
21
 
22
- opts.on('--latest', 'Use the latest version') do
22
+ opts.on("--latest", "Use the latest version") do
23
23
  options[:latest] = true
24
24
  end
25
25
 
26
- opts.on('-h', '--help', 'Display this help message') do
26
+ opts.on("-h", "--help", "Display this help message") do
27
27
  puts opts
28
28
  exit
29
29
  end
30
30
 
31
- opts.on('-s', '--stop', 'stops the yard server') do
31
+ opts.on("-s", "--stop", "stops the yard server") do
32
32
  OpenGemdocs::Yard.stop_server
33
33
  exit
34
34
  end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+ require_relative "../lib/open_gemdocs/version"
6
+ require_relative "../lib/open_gemdocs/mcp/server"
7
+ require_relative "../lib/open_gemdocs/mcp/handlers"
8
+ require_relative "../lib/open_gemdocs/mcp/tools"
9
+
10
+ options = { port: 6789 }
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: open-gem-docs-mcp [options]"
14
+
15
+ opts.on("-p", "--port PORT", Integer, "Port to run MCP server on (default: 6789)") do |p|
16
+ options[:port] = p
17
+ end
18
+
19
+ opts.on("-v", "--version", "Show version") do
20
+ puts "open_gemdocs MCP server v#{OpenGemdocs::VERSION}"
21
+ exit
22
+ end
23
+
24
+ opts.on("-h", "--help", "Show this help message") do
25
+ puts opts
26
+ puts
27
+ puts "This MCP server provides tools for accessing Ruby gem documentation."
28
+ puts "It integrates with the Yard documentation server to provide local docs."
29
+ puts
30
+ puts "Available MCP tools:"
31
+ puts " - search_gems: Search for installed Ruby gems"
32
+ puts " - get_gem_info: Get detailed information about a gem"
33
+ puts " - start_yard_server: Start the Yard documentation server"
34
+ puts " - stop_yard_server: Stop the Yard documentation server"
35
+ puts " - get_yard_server_status: Check Yard server status"
36
+ puts " - get_gem_documentation_url: Get the documentation URL for a gem"
37
+ puts " - fetch_gem_docs: Fetch documentation content from Yard server"
38
+ exit
39
+ end
40
+ end.parse!
41
+
42
+ begin
43
+ server = OpenGemdocs::MCP::Server.new(port: options[:port])
44
+ server.start
45
+ rescue Interrupt
46
+ puts "\nShutting down..."
47
+ exit 0
48
+ rescue StandardError => e
49
+ warn "Error: #{e.message}"
50
+ warn e.backtrace if ENV["DEBUG"]
51
+ exit 1
52
+ end
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require_relative "../lib/open_gemdocs/version"
6
+ require_relative "../lib/open_gemdocs/mcp/handlers"
7
+ require_relative "../lib/open_gemdocs/mcp/tools"
8
+
9
+ # MCP stdio server for Claude Desktop
10
+ class MCPStdioServer
11
+ def initialize
12
+ @handlers = OpenGemdocs::MCP::Handlers.new
13
+ STDERR.puts "open_gemdocs MCP server v#{OpenGemdocs::VERSION} (stdio mode)"
14
+ end
15
+
16
+ def run
17
+ STDERR.puts "MCP server started in stdio mode"
18
+
19
+ loop do
20
+ begin
21
+ # Read line from stdin
22
+ line = STDIN.gets
23
+ break if line.nil? # EOF
24
+
25
+ next if line.strip.empty?
26
+
27
+ # Parse JSON-RPC request
28
+ request = JSON.parse(line.strip)
29
+
30
+ # Handle the request
31
+ response = @handlers.handle(request)
32
+
33
+ # Write response to stdout if there is one
34
+ if response
35
+ STDOUT.puts JSON.generate(response)
36
+ STDOUT.flush
37
+ end
38
+ rescue JSON::ParserError => e
39
+ error_response = {
40
+ jsonrpc: "2.0",
41
+ error: {
42
+ code: -32700,
43
+ message: "Parse error",
44
+ data: e.message
45
+ }
46
+ }
47
+ STDOUT.puts JSON.generate(error_response)
48
+ STDOUT.flush
49
+ rescue StandardError => e
50
+ STDERR.puts "Error: #{e.message}"
51
+ STDERR.puts e.backtrace if ENV["DEBUG"]
52
+
53
+ error_response = {
54
+ jsonrpc: "2.0",
55
+ error: {
56
+ code: -32603,
57
+ message: "Internal error",
58
+ data: e.message
59
+ }
60
+ }
61
+ STDOUT.puts JSON.generate(error_response)
62
+ STDOUT.flush
63
+ end
64
+ end
65
+
66
+ STDERR.puts "MCP server shutting down"
67
+ rescue Interrupt
68
+ STDERR.puts "\nShutting down MCP server..."
69
+ OpenGemdocs::Yard.stop_server
70
+ exit 0
71
+ end
72
+ end
73
+
74
+ # Run the server
75
+ server = MCPStdioServer.new
76
+ server.run
@@ -6,7 +6,7 @@ module OpenGemdocs
6
6
 
7
7
  def initialize(gem_name:, version: nil, use_latest: false)
8
8
  @gem_name = gem_name
9
- raise ArgumentError, 'Gem name is required' if gem_name.nil? || gem_name.empty?
9
+ raise ArgumentError, "Gem name is required" if gem_name.nil? || gem_name.empty?
10
10
 
11
11
  @version = version
12
12
  @use_latest = use_latest
@@ -15,7 +15,7 @@ module OpenGemdocs
15
15
  def resolve_version
16
16
  return version if version
17
17
 
18
- if !use_latest && File.exist?('Gemfile.lock')
18
+ if !use_latest && File.exist?("Gemfile.lock")
19
19
  @version = check_bundle_version
20
20
  if @version
21
21
  puts "Using version from Gemfile.lock: #{version}"
@@ -25,27 +25,27 @@ module OpenGemdocs
25
25
 
26
26
  @use_latest = true
27
27
 
28
- puts 'No version specified, using latest version'
28
+ puts "No version specified, using latest version"
29
29
  end
30
30
 
31
31
  def open_browser
32
32
  resolve_version
33
- raise Error, 'No version URL found' unless version_url
33
+ raise Error, "No version URL found" unless version_url
34
34
 
35
- version_str = version ? "@v#{version}" : ''
36
- latest_str = use_latest ? ' (latest)' : ''
35
+ version_str = version ? "@v#{version}" : ""
36
+ latest_str = use_latest ? " (latest)" : ""
37
37
  puts "Fetching gem documentation for #{gem_name}#{version_str}#{latest_str}..."
38
38
  # open is a macOS command to open a URL in the default browser
39
- raise Error, 'Could not resolve URL' if version_url.nil?
39
+ raise Error, "Could not resolve URL" if version_url.nil?
40
40
 
41
- `open "#{version_url.sub('production.', '')}"`
41
+ `open "#{version_url.sub("production.", "")}"`
42
42
  end
43
43
 
44
44
  def version_url
45
45
  if use_latest
46
- version_data.last['url']
46
+ version_data.last["url"]
47
47
  else
48
- version_data.detect { |row| row['version'] == version }&.fetch('url')
48
+ version_data.detect { |row| row["version"] == version }&.fetch("url")
49
49
  end
50
50
  end
51
51
 
@@ -57,11 +57,9 @@ module OpenGemdocs
57
57
  @version_data ||= begin
58
58
  uri = URI("https://gemdocs.org/gems/#{ARGV[0]}/versions.json")
59
59
  res = Net::HTTP.get_response(uri)
60
- unless res.is_a?(Net::HTTPSuccess)
61
- raise Error, "HTTP request failed to uri: #{uri} -- #{res.code} #{res.message}"
62
- end
60
+ raise Error, "HTTP request failed to uri: #{uri} -- #{res.code} #{res.message}" unless res.is_a?(Net::HTTPSuccess)
63
61
 
64
- JSON.parse(res.body)['versions']
62
+ JSON.parse(res.body)["versions"]
65
63
  end
66
64
  end
67
65
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tools"
4
+
5
+ module OpenGemdocs
6
+ module MCP
7
+ class Handlers
8
+ def initialize
9
+ @tools = Tools.new
10
+ end
11
+
12
+ def handle(request)
13
+ method = request["method"]
14
+ id = request["id"]
15
+
16
+ case method
17
+ when "initialize"
18
+ handle_initialize(id)
19
+ when "initialized"
20
+ # Client notification, no response needed
21
+ nil
22
+ when "tools/list"
23
+ handle_tools_list(id)
24
+ when "tools/call"
25
+ handle_tool_call(id, request["params"])
26
+ when "ping"
27
+ { "jsonrpc" => "2.0", "id" => id, "result" => {} }
28
+ else
29
+ error_response(id, -32_601, "Method not found: #{method}")
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def handle_initialize(id)
36
+ {
37
+ "jsonrpc" => "2.0",
38
+ "id" => id,
39
+ "result" => {
40
+ "protocolVersion" => "2024-11-05",
41
+ "capabilities" => {
42
+ "tools" => {},
43
+ "resources" => {}
44
+ },
45
+ "serverInfo" => {
46
+ "name" => "open_gemdocs_mcp",
47
+ "version" => OpenGemdocs::VERSION
48
+ }
49
+ }
50
+ }
51
+ end
52
+
53
+ def handle_tools_list(id)
54
+ {
55
+ "jsonrpc" => "2.0",
56
+ "id" => id,
57
+ "result" => {
58
+ "tools" => @tools.list
59
+ }
60
+ }
61
+ end
62
+
63
+ def handle_tool_call(id, params)
64
+ tool_name = params["name"]
65
+ arguments = params["arguments"] || {}
66
+
67
+ begin
68
+ result = @tools.call(tool_name, arguments)
69
+
70
+ {
71
+ "jsonrpc" => "2.0",
72
+ "id" => id,
73
+ "result" => result
74
+ }
75
+ rescue StandardError => e
76
+ error_response(id, -32_603, "Tool execution error: #{e.message}")
77
+ end
78
+ end
79
+
80
+ def error_response(id, code, message)
81
+ {
82
+ "jsonrpc" => "2.0",
83
+ "id" => id,
84
+ "error" => {
85
+ "code" => code,
86
+ "message" => message
87
+ }
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end