mcp_on_ruby 1.0.0 → 2.0.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -4
  3. data/LICENSE.txt +1 -1
  4. data/README.md +350 -163
  5. data/config/routes.rb +5 -0
  6. data/lib/generators/mcp_on_ruby/expose/expose_generator.rb +108 -0
  7. data/lib/generators/mcp_on_ruby/install/install_generator.rb +37 -0
  8. data/lib/generators/mcp_on_ruby/install/templates/initializer.rb +22 -0
  9. data/lib/mcp_on_ruby/call_context.rb +29 -0
  10. data/lib/mcp_on_ruby/configuration.rb +63 -59
  11. data/lib/mcp_on_ruby/controller.rb +60 -0
  12. data/lib/mcp_on_ruby/declaration.rb +24 -0
  13. data/lib/mcp_on_ruby/dispatcher.rb +97 -0
  14. data/lib/mcp_on_ruby/doctor.rb +67 -0
  15. data/lib/mcp_on_ruby/endpoint.rb +43 -0
  16. data/lib/mcp_on_ruby/engine.rb +34 -0
  17. data/lib/mcp_on_ruby/registry.rb +146 -0
  18. data/lib/mcp_on_ruby/result.rb +83 -0
  19. data/lib/mcp_on_ruby/rspec.rb +8 -0
  20. data/lib/mcp_on_ruby/schema.rb +149 -0
  21. data/lib/mcp_on_ruby/server_factory.rb +70 -0
  22. data/lib/mcp_on_ruby/stdio.rb +42 -0
  23. data/lib/mcp_on_ruby/test_helper.rb +86 -0
  24. data/lib/mcp_on_ruby/tool_spec.rb +87 -0
  25. data/lib/mcp_on_ruby/tool_table.rb +31 -0
  26. data/lib/mcp_on_ruby/version.rb +2 -3
  27. data/lib/mcp_on_ruby/warden_hook.rb +21 -0
  28. data/lib/mcp_on_ruby.rb +44 -122
  29. data/lib/tasks/mcp_on_ruby.rake +20 -0
  30. metadata +65 -136
  31. data/CODE_OF_CONDUCT.md +0 -56
  32. data/CONTRIBUTING.md +0 -86
  33. data/bin/console +0 -11
  34. data/bin/setup +0 -6
  35. data/docs/advanced-usage.md +0 -132
  36. data/docs/api-reference.md +0 -35
  37. data/docs/testing.md +0 -55
  38. data/examples/claude/README.md +0 -171
  39. data/examples/claude/claude-bridge.js +0 -122
  40. data/lib/mcp_on_ruby/errors.rb +0 -137
  41. data/lib/mcp_on_ruby/generators/install_generator.rb +0 -46
  42. data/lib/mcp_on_ruby/generators/resource_generator.rb +0 -63
  43. data/lib/mcp_on_ruby/generators/templates/README +0 -31
  44. data/lib/mcp_on_ruby/generators/templates/application_resource.rb +0 -20
  45. data/lib/mcp_on_ruby/generators/templates/application_tool.rb +0 -18
  46. data/lib/mcp_on_ruby/generators/templates/initializer.rb +0 -41
  47. data/lib/mcp_on_ruby/generators/templates/resource.rb +0 -50
  48. data/lib/mcp_on_ruby/generators/templates/resource_spec.rb +0 -67
  49. data/lib/mcp_on_ruby/generators/templates/sample_resource.rb +0 -57
  50. data/lib/mcp_on_ruby/generators/templates/sample_tool.rb +0 -59
  51. data/lib/mcp_on_ruby/generators/templates/tool.rb +0 -38
  52. data/lib/mcp_on_ruby/generators/templates/tool_spec.rb +0 -55
  53. data/lib/mcp_on_ruby/generators/tool_generator.rb +0 -51
  54. data/lib/mcp_on_ruby/railtie.rb +0 -108
  55. data/lib/mcp_on_ruby/resource.rb +0 -161
  56. data/lib/mcp_on_ruby/server.rb +0 -378
  57. data/lib/mcp_on_ruby/tool.rb +0 -134
  58. data/lib/mcp_on_ruby/transport.rb +0 -330
data/CONTRIBUTING.md DELETED
@@ -1,86 +0,0 @@
1
- # Contributing to MCP on Ruby
2
-
3
- We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
4
-
5
- - Reporting a bug
6
- - Discussing the current state of the code
7
- - Submitting a fix
8
- - Proposing new features
9
- - Becoming a maintainer
10
-
11
- ## Development Process
12
-
13
- We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
14
-
15
- ## Pull Requests
16
-
17
- 1. Fork the repo and create your branch from `main`.
18
- 2. If you've added code that should be tested, add tests.
19
- 3. If you've changed APIs, update the documentation.
20
- 4. Ensure the test suite passes.
21
- 5. Make sure your code lints.
22
- 6. Issue that pull request!
23
-
24
- ## Development Setup
25
-
26
- ```bash
27
- # Clone your fork
28
- git clone https://github.com/nagstler/mcp_on_ruby.git
29
- cd mcp_on_ruby
30
-
31
- # Install dependencies
32
- bundle install
33
-
34
- # Run the generators to test Rails integration
35
- mkdir test_app && cd test_app
36
- rails new . --skip-git
37
- echo "gem 'mcp_on_ruby', path: '..' " >> Gemfile
38
- bundle install
39
- rails generate mcp_on_ruby:install
40
-
41
- # Test the gem
42
- cd .. && ruby -e "require './lib/mcp_on_ruby'; puts 'Gem loads successfully'"
43
- ```
44
-
45
- ## Code Style
46
-
47
- - Follow standard Ruby conventions
48
- - Use meaningful variable and method names
49
- - Add documentation for public APIs
50
- - Keep methods focused and small
51
-
52
- ## Testing
53
-
54
- Currently, we use manual testing and examples. In the future, we plan to add:
55
- - RSpec test suite
56
- - Integration tests with Rails
57
- - CI/CD pipeline
58
-
59
- ## Bug Reports
60
-
61
- We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/nagstler/mcp_on_ruby/issues).
62
-
63
- **Great Bug Reports** tend to have:
64
-
65
- - A quick summary and/or background
66
- - Steps to reproduce
67
- - Be specific!
68
- - Give sample code if you can
69
- - What you expected would happen
70
- - What actually happens
71
- - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
72
-
73
- ## Feature Requests
74
-
75
- We use GitHub issues to track feature requests. Propose a feature by [opening a new issue](https://github.com/nagstler/mcp_on_ruby/issues).
76
-
77
- **Great Feature Requests** include:
78
-
79
- - Clear use case description
80
- - Why this feature would be useful
81
- - Proposed API or implementation approach
82
- - Examples of how it would be used
83
-
84
- ## License
85
-
86
- By contributing, you agree that your contributions will be licensed under the same MIT License that covers the project.
data/bin/console DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "mcp_on_ruby"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- require "irb"
11
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
@@ -1,132 +0,0 @@
1
- # Advanced Usage
2
-
3
- ## Custom Authorization
4
-
5
- ```ruby
6
- class ApplicationTool < McpOnRuby::Tool
7
- def authorize(context)
8
- token = context[:auth_token]
9
- user = authenticate_token(token)
10
- user&.has_permission?(:mcp_access)
11
- end
12
-
13
- private
14
-
15
- def authenticate_token(token)
16
- # Your authentication logic
17
- JWT.decode(token, Rails.application.secret_key_base).first
18
- rescue JWT::DecodeError
19
- nil
20
- end
21
- end
22
- ```
23
-
24
- ## Resource Caching
25
-
26
- ```ruby
27
- class ApplicationResource < McpOnRuby::Resource
28
- def read(params = {}, context = {})
29
- cache_key = "mcp:#{uri}:#{params.hash}"
30
- Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
31
- super
32
- end
33
- end
34
- end
35
- ```
36
-
37
- ## Manual Server Configuration
38
-
39
- ```ruby
40
- # For advanced scenarios where auto-registration isn't sufficient
41
- McpOnRuby.mount_in_rails(Rails.application) do |server|
42
- # Register tools manually
43
- server.register_tool(CustomTool.new)
44
-
45
- # Define tools with DSL
46
- server.tool 'database_query', 'Execute read-only database queries' do |args|
47
- query = args['query']
48
- raise 'Only SELECT allowed' unless query.strip.upcase.start_with?('SELECT')
49
-
50
- result = ActiveRecord::Base.connection.execute(query)
51
- { rows: result.to_a }
52
- end
53
-
54
- # Define resources with DSL
55
- server.resource 'health' do
56
- {
57
- status: 'healthy',
58
- database: database_healthy?,
59
- redis: redis_healthy?,
60
- timestamp: Time.current
61
- }
62
- end
63
- end
64
- ```
65
-
66
- ## Production Configuration
67
-
68
- ### Security Setup
69
-
70
- ```ruby
71
- # config/initializers/mcp_on_ruby.rb
72
- McpOnRuby.configure do |config|
73
- # Authentication
74
- config.authentication_required = true
75
- config.authentication_token = ENV['MCP_AUTH_TOKEN']
76
-
77
- # Security
78
- config.dns_rebinding_protection = true
79
- config.allowed_origins = [
80
- ENV['ALLOWED_ORIGIN'],
81
- /\A#{Regexp.escape(ENV['DOMAIN'])}\z/
82
- ]
83
- config.localhost_only = false
84
-
85
- # Rate limiting
86
- config.rate_limit_per_minute = 100
87
-
88
- # Features
89
- config.cors_enabled = true
90
- end
91
- ```
92
-
93
- ### Monitoring & Logging
94
-
95
- ```ruby
96
- class ApplicationTool < McpOnRuby::Tool
97
- def call(arguments = {}, context = {})
98
- start_time = Time.current
99
- result = super
100
- duration = Time.current - start_time
101
-
102
- Rails.logger.info("MCP Tool executed", {
103
- tool: name,
104
- duration: duration,
105
- success: !result.key?(:error),
106
- user_ip: context[:remote_ip]
107
- })
108
-
109
- result
110
- end
111
- end
112
- ```
113
-
114
- ### Error Monitoring
115
-
116
- ```ruby
117
- # config/initializers/mcp_on_ruby.rb
118
- class CustomTool < ApplicationTool
119
- def execute(arguments, context)
120
- # Your tool logic
121
- rescue => error
122
- # Report to error monitoring service
123
- Bugsnag.notify(error, {
124
- tool: name,
125
- arguments: arguments,
126
- context: context
127
- })
128
-
129
- raise
130
- end
131
- end
132
- ```
@@ -1,35 +0,0 @@
1
- # API Reference
2
-
3
- ## Server Methods
4
-
5
- ```ruby
6
- server = McpOnRuby.server do |s|
7
- s.tool(name, description, input_schema, **options, &block)
8
- s.resource(uri, **options, &block)
9
- s.register_tool(tool_instance)
10
- s.register_resource(resource_instance)
11
- end
12
-
13
- # Handle requests
14
- server.handle_request(json_string, context)
15
- ```
16
-
17
- ## Tool Class
18
-
19
- ```ruby
20
- class MyTool < McpOnRuby::Tool
21
- def initialize(name:, description: '', input_schema: {}, **options)
22
- def execute(arguments, context) # Override this
23
- def authorize(context) # Optional override
24
- end
25
- ```
26
-
27
- ## Resource Class
28
-
29
- ```ruby
30
- class MyResource < McpOnRuby::Resource
31
- def initialize(uri:, name: nil, description: '', mime_type: 'application/json', **options)
32
- def fetch_content(params, context) # Override this
33
- def authorize(context) # Optional override
34
- end
35
- ```
data/docs/testing.md DELETED
@@ -1,55 +0,0 @@
1
- # Testing Guide
2
-
3
- ## RSpec Integration
4
-
5
- ```ruby
6
- # spec/tools/user_manager_tool_spec.rb
7
- require 'rails_helper'
8
-
9
- RSpec.describe UserManagerTool do
10
- subject(:tool) { described_class.new }
11
-
12
- describe '#execute' do
13
- context 'creating a user' do
14
- let(:arguments) do
15
- {
16
- 'action' => 'create',
17
- 'attributes' => { 'name' => 'John Doe', 'email' => 'john@example.com' }
18
- }
19
- end
20
-
21
- it 'creates user successfully' do
22
- result = tool.call(arguments, { authenticated: true })
23
-
24
- expect(result[:success]).to be true
25
- expect(result[:user]['name']).to eq 'John Doe'
26
- end
27
- end
28
- end
29
- end
30
- ```
31
-
32
- ## Integration Testing
33
-
34
- ```ruby
35
- # spec/integration/mcp_server_spec.rb
36
- require 'rails_helper'
37
-
38
- RSpec.describe 'MCP Server Integration' do
39
- let(:server) { Rails.application.config.mcp_server }
40
-
41
- it 'handles tool calls' do
42
- request = {
43
- jsonrpc: '2.0',
44
- method: 'tools/call',
45
- params: { name: 'user_manager', arguments: { action: 'create' } },
46
- id: 1
47
- }
48
-
49
- response = server.handle_request(request.to_json)
50
- parsed = JSON.parse(response)
51
-
52
- expect(parsed['result']).to be_present
53
- end
54
- end
55
- ```
@@ -1,171 +0,0 @@
1
- # Claude Desktop Integration with MCP on Ruby
2
-
3
- This example demonstrates how to connect Claude Desktop to your Rails application using MCP on Ruby. We'll walk through the complete setup process that bridges the protocol difference between Claude Desktop (stdio) and Rails (HTTP).
4
-
5
- ## The Challenge
6
-
7
- Claude Desktop communicates with MCP servers using **stdio** (standard input/output), but MCP on Ruby runs as an **HTTP server** within your Rails application. We need a bridge to convert between these two protocols.
8
-
9
- ## Complete Setup Guide
10
-
11
- ### Step 1: Prepare Your Rails Application
12
-
13
- 1. **Add MCP on Ruby to your Rails app:**
14
- ```bash
15
- # In your Rails app directory
16
- bundle add mcp_on_ruby
17
- rails generate mcp_on_ruby:install
18
- ```
19
-
20
- 2. **Configure MCP settings:**
21
- ```ruby
22
- # config/initializers/mcp_on_ruby.rb
23
- McpOnRuby.configure do |config|
24
- config.authentication_required = true
25
- config.authentication_token = 'my-secure-token'
26
- config.rate_limit_per_minute = 60
27
- end
28
-
29
- Rails.application.configure do
30
- config.mcp.enabled = true
31
- config.mcp.auto_register_tools = true
32
- end
33
- ```
34
-
35
- 3. **Create a sample tool for testing:**
36
- ```bash
37
- rails generate mcp_on_ruby:tool UserManager --description "Manage application users"
38
- ```
39
-
40
- This creates `app/tools/user_manager_tool.rb`. You can customize it or use the default implementation to test the connection.
41
-
42
- ### Step 2: Set Up the Bridge Script
43
-
44
- 1. **Copy the bridge script:**
45
- Copy `claude-bridge.js` from this directory to your preferred location (e.g., `~/mcp-bridge/claude-bridge.js`)
46
-
47
- 2. **Make it executable:**
48
- ```bash
49
- chmod +x ~/mcp-bridge/claude-bridge.js
50
- ```
51
-
52
- 3. **Update the bridge script configuration:**
53
- Edit the top of `claude-bridge.js` to match your setup:
54
- ```javascript
55
- const RAILS_PORT = 3001; // Your Rails server port
56
- const AUTH_TOKEN = 'my-secure-token'; // Must match your Rails config
57
- ```
58
-
59
- ### Step 3: Configure Claude Desktop
60
-
61
- 1. **Locate Claude Desktop config file:**
62
- - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
63
- - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
64
-
65
- 2. **Add your MCP server configuration:**
66
- ```json
67
- {
68
- "mcpServers": {
69
- "my-rails-app": {
70
- "command": "node",
71
- "args": ["/Users/yourusername/mcp-bridge/claude-bridge.js"]
72
- }
73
- }
74
- }
75
- ```
76
-
77
- Replace `/Users/yourusername/mcp-bridge/claude-bridge.js` with the actual path to your bridge script.
78
-
79
- ### Step 4: Start Everything
80
-
81
- 1. **Start your Rails server:**
82
- ```bash
83
- cd your-rails-app
84
- rails server -p 3001
85
- ```
86
-
87
- 2. **Restart Claude Desktop** to load the new MCP server configuration
88
-
89
- ### Step 5: Test the Integration
90
-
91
- 1. Open Claude Desktop
92
- 2. Look for your MCP tools in the available tools (they should appear automatically)
93
- 3. Try asking Claude to use your tools:
94
- - "Use the user manager tool to help me understand what it does"
95
- - "What tools are available from my Rails application?"
96
- - Test any specific functionality you've implemented in your tools
97
-
98
- ## How the Bridge Works
99
-
100
- ```
101
- ┌─────────────────┐ stdio ┌─────────────────┐ HTTP ┌─────────────────┐
102
- │ Claude │ ←──────────→ │ Bridge │ ←─────────→ │ Rails MCP │
103
- │ Desktop │ │ Script │ │ Server │
104
- └─────────────────┘ └─────────────────┘ └─────────────────┘
105
- ```
106
-
107
- The bridge script (`claude-bridge.js`):
108
- 1. Receives JSON-RPC messages from Claude Desktop via stdin
109
- 2. Adds authentication headers (Bearer token from your configuration)
110
- 3. Forwards requests to Rails at `http://localhost:3001/mcp`
111
- 4. Returns responses to Claude Desktop via stdout
112
- 5. Handles errors and ensures proper JSON-RPC formatting
113
-
114
- ## Directory Structure
115
-
116
- ```
117
- your-rails-app/
118
- ├── app/
119
- │ └── tools/
120
- │ └── user_manager_tool.rb # Your generated tool
121
- ├── config/
122
- │ └── initializers/
123
- │ └── mcp_on_ruby.rb # MCP configuration
124
- └── config/routes.rb # MCP route added automatically
125
-
126
- ~/mcp-bridge/ # Or your preferred location
127
- └── claude-bridge.js # Bridge script
128
- ```
129
-
130
- ## Troubleshooting
131
-
132
- **Claude Desktop shows "Connection failed":**
133
- - Ensure Rails server is running on port 3001
134
- - Check that the bridge script path in Claude config is correct
135
-
136
- **"Unexpected end of JSON input":**
137
- - Verify authentication token matches exactly in both Rails config (`my-secure-token`) and bridge script (`AUTH_TOKEN`)
138
- - Check Rails server logs for authentication errors
139
-
140
- **Tools not appearing in Claude:**
141
- - Restart Claude Desktop after configuration changes
142
- - Verify tools are being auto-registered (check Rails logs)
143
-
144
- **Bridge script not executing:**
145
- - Ensure Node.js is installed and accessible
146
- - Make sure the script has execute permissions
147
-
148
- ## Customizing the Setup
149
-
150
- To modify ports or authentication:
151
-
152
- 1. **Change Rails port:**
153
- ```bash
154
- rails server -p 3002 # Use different port
155
- ```
156
-
157
- 2. **Update bridge script:**
158
- Edit `RAILS_PORT` in `claude-bridge.js`
159
-
160
- 3. **Change authentication token:**
161
- Update both Rails config (`config.authentication_token`) and `AUTH_TOKEN` in bridge script
162
-
163
- ## What You Get
164
-
165
- Once connected, Claude Desktop can:
166
- - Discover and use any tools you create in `app/tools/`
167
- - Access resources you define in `app/resources/`
168
- - Interact with your Rails application's data and functionality
169
- - Provide a natural language interface to your application's capabilities
170
-
171
- This setup provides a secure, production-ready connection between Claude Desktop and your Rails application via MCP on Ruby.
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const http = require('http');
4
- const { stdin, stdout, stderr } = process;
5
-
6
- // MCP server configuration
7
- const RAILS_HOST = 'localhost';
8
- const RAILS_PORT = 3001;
9
- const RAILS_PATH = '/mcp';
10
- const AUTH_TOKEN = 'test-db-inspector-token';
11
-
12
- // Set up stdio
13
- stdin.setEncoding('utf8');
14
- process.on('SIGTERM', () => process.exit(0));
15
- process.on('SIGINT', () => process.exit(0));
16
-
17
- let buffer = '';
18
-
19
- stdin.on('data', (chunk) => {
20
- buffer += chunk;
21
-
22
- // Process complete JSON-RPC messages
23
- const lines = buffer.split('\n');
24
- buffer = lines.pop() || ''; // Keep incomplete line
25
-
26
- for (const line of lines) {
27
- if (line.trim()) {
28
- handleRequest(line.trim());
29
- }
30
- }
31
- });
32
-
33
- stdin.on('end', () => {
34
- if (buffer.trim()) {
35
- handleRequest(buffer.trim());
36
- }
37
- });
38
-
39
- async function handleRequest(requestLine) {
40
- let requestId = 1; // Default ID
41
-
42
- try {
43
- // Parse and validate JSON-RPC request
44
- const request = JSON.parse(requestLine);
45
- requestId = request.id || 1; // Use request ID or default to 1
46
-
47
- // Forward to Rails MCP server
48
- const response = await forwardToRails(requestLine);
49
-
50
- // Parse response to ensure it has correct ID
51
- try {
52
- const parsedResponse = JSON.parse(response);
53
- if (parsedResponse.id === null || parsedResponse.id === undefined) {
54
- parsedResponse.id = requestId;
55
- }
56
- stdout.write(JSON.stringify(parsedResponse) + '\n');
57
- } catch {
58
- // If response is not valid JSON, create proper response
59
- const successResponse = {
60
- jsonrpc: "2.0",
61
- id: requestId,
62
- result: { message: response }
63
- };
64
- stdout.write(JSON.stringify(successResponse) + '\n');
65
- }
66
-
67
- } catch (error) {
68
- // Send JSON-RPC error response with proper ID
69
- const errorResponse = {
70
- jsonrpc: "2.0",
71
- id: requestId,
72
- error: {
73
- code: -32700,
74
- message: "Parse error",
75
- data: error.message
76
- }
77
- };
78
- stdout.write(JSON.stringify(errorResponse) + '\n');
79
- }
80
- }
81
-
82
- function forwardToRails(requestData) {
83
- return new Promise((resolve, reject) => {
84
- const options = {
85
- hostname: RAILS_HOST,
86
- port: RAILS_PORT,
87
- path: RAILS_PATH,
88
- method: 'POST',
89
- headers: {
90
- 'Content-Type': 'application/json',
91
- 'Authorization': `Bearer ${AUTH_TOKEN}`,
92
- 'Content-Length': Buffer.byteLength(requestData)
93
- },
94
- timeout: 30000
95
- };
96
-
97
- const req = http.request(options, (res) => {
98
- let responseData = '';
99
-
100
- res.on('data', (chunk) => {
101
- responseData += chunk;
102
- });
103
-
104
- res.on('end', () => {
105
- resolve(responseData);
106
- });
107
- });
108
-
109
- req.on('error', (error) => {
110
- // Return error as string to be wrapped in proper JSON-RPC format
111
- resolve(`Connection failed: ${error.message}`);
112
- });
113
-
114
- req.on('timeout', () => {
115
- req.destroy();
116
- resolve('Request timeout');
117
- });
118
-
119
- req.write(requestData);
120
- req.end();
121
- });
122
- }