mcp_on_ruby 1.0.0 → 2.0.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 +4 -4
- data/CHANGELOG.md +39 -3
- data/README.md +359 -163
- data/config/routes.rb +5 -0
- data/lib/generators/mcp_on_ruby/expose/expose_generator.rb +108 -0
- data/lib/generators/mcp_on_ruby/install/install_generator.rb +37 -0
- data/lib/generators/mcp_on_ruby/install/templates/initializer.rb +22 -0
- data/lib/mcp_on_ruby/call_context.rb +29 -0
- data/lib/mcp_on_ruby/configuration.rb +63 -59
- data/lib/mcp_on_ruby/controller.rb +60 -0
- data/lib/mcp_on_ruby/declaration.rb +24 -0
- data/lib/mcp_on_ruby/dispatcher.rb +97 -0
- data/lib/mcp_on_ruby/doctor.rb +67 -0
- data/lib/mcp_on_ruby/endpoint.rb +43 -0
- data/lib/mcp_on_ruby/engine.rb +29 -0
- data/lib/mcp_on_ruby/registry.rb +146 -0
- data/lib/mcp_on_ruby/result.rb +83 -0
- data/lib/mcp_on_ruby/rspec.rb +8 -0
- data/lib/mcp_on_ruby/schema.rb +149 -0
- data/lib/mcp_on_ruby/server_factory.rb +66 -0
- data/lib/mcp_on_ruby/stdio.rb +42 -0
- data/lib/mcp_on_ruby/test_helper.rb +86 -0
- data/lib/mcp_on_ruby/tool_spec.rb +87 -0
- data/lib/mcp_on_ruby/tool_table.rb +31 -0
- data/lib/mcp_on_ruby/version.rb +2 -3
- data/lib/mcp_on_ruby/warden_hook.rb +21 -0
- data/lib/mcp_on_ruby.rb +44 -122
- data/lib/tasks/mcp_on_ruby.rake +20 -0
- metadata +65 -136
- data/CODE_OF_CONDUCT.md +0 -56
- data/CONTRIBUTING.md +0 -86
- data/bin/console +0 -11
- data/bin/setup +0 -6
- data/docs/advanced-usage.md +0 -132
- data/docs/api-reference.md +0 -35
- data/docs/testing.md +0 -55
- data/examples/claude/README.md +0 -171
- data/examples/claude/claude-bridge.js +0 -122
- data/lib/mcp_on_ruby/errors.rb +0 -137
- data/lib/mcp_on_ruby/generators/install_generator.rb +0 -46
- data/lib/mcp_on_ruby/generators/resource_generator.rb +0 -63
- data/lib/mcp_on_ruby/generators/templates/README +0 -31
- data/lib/mcp_on_ruby/generators/templates/application_resource.rb +0 -20
- data/lib/mcp_on_ruby/generators/templates/application_tool.rb +0 -18
- data/lib/mcp_on_ruby/generators/templates/initializer.rb +0 -41
- data/lib/mcp_on_ruby/generators/templates/resource.rb +0 -50
- data/lib/mcp_on_ruby/generators/templates/resource_spec.rb +0 -67
- data/lib/mcp_on_ruby/generators/templates/sample_resource.rb +0 -57
- data/lib/mcp_on_ruby/generators/templates/sample_tool.rb +0 -59
- data/lib/mcp_on_ruby/generators/templates/tool.rb +0 -38
- data/lib/mcp_on_ruby/generators/templates/tool_spec.rb +0 -55
- data/lib/mcp_on_ruby/generators/tool_generator.rb +0 -51
- data/lib/mcp_on_ruby/railtie.rb +0 -108
- data/lib/mcp_on_ruby/resource.rb +0 -161
- data/lib/mcp_on_ruby/server.rb +0 -378
- data/lib/mcp_on_ruby/tool.rb +0 -134
- data/lib/mcp_on_ruby/transport.rb +0 -330
data/docs/advanced-usage.md
DELETED
|
@@ -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
|
-
```
|
data/docs/api-reference.md
DELETED
|
@@ -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
|
-
```
|
data/examples/claude/README.md
DELETED
|
@@ -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
|
-
}
|
data/lib/mcp_on_ruby/errors.rb
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module McpOnRuby
|
|
4
|
-
# Base error class for MCP-related errors
|
|
5
|
-
class Error < StandardError
|
|
6
|
-
attr_reader :code, :data
|
|
7
|
-
|
|
8
|
-
def initialize(message, code: nil, data: nil)
|
|
9
|
-
super(message)
|
|
10
|
-
@code = code
|
|
11
|
-
@data = data
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Convert error to JSON-RPC error format
|
|
15
|
-
def to_json_rpc
|
|
16
|
-
{
|
|
17
|
-
code: @code || default_error_code,
|
|
18
|
-
message: message,
|
|
19
|
-
data: @data
|
|
20
|
-
}.compact
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def default_error_code
|
|
26
|
-
-32603 # Internal error
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# JSON-RPC parsing error
|
|
31
|
-
class ParseError < Error
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def default_error_code
|
|
35
|
-
-32700
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Invalid JSON-RPC request
|
|
40
|
-
class InvalidRequestError < Error
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def default_error_code
|
|
44
|
-
-32600
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Method not found
|
|
49
|
-
class MethodNotFoundError < Error
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
def default_error_code
|
|
53
|
-
-32601
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Invalid method parameters
|
|
58
|
-
class InvalidParamsError < Error
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
def default_error_code
|
|
62
|
-
-32602
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Tool or resource not found
|
|
67
|
-
class NotFoundError < Error
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
def default_error_code
|
|
71
|
-
-32603
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Authorization/authentication failed
|
|
76
|
-
class AuthorizationError < Error
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
def default_error_code
|
|
80
|
-
-32600
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Validation error for tool arguments or resource parameters
|
|
85
|
-
class ValidationError < Error
|
|
86
|
-
private
|
|
87
|
-
|
|
88
|
-
def default_error_code
|
|
89
|
-
-32602
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Tool execution error
|
|
94
|
-
class ToolExecutionError < Error
|
|
95
|
-
private
|
|
96
|
-
|
|
97
|
-
def default_error_code
|
|
98
|
-
-32603
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Resource read error
|
|
103
|
-
class ResourceReadError < Error
|
|
104
|
-
private
|
|
105
|
-
|
|
106
|
-
def default_error_code
|
|
107
|
-
-32603
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Rate limiting error
|
|
112
|
-
class RateLimitError < Error
|
|
113
|
-
private
|
|
114
|
-
|
|
115
|
-
def default_error_code
|
|
116
|
-
-32603
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Configuration error
|
|
121
|
-
class ConfigurationError < Error
|
|
122
|
-
private
|
|
123
|
-
|
|
124
|
-
def default_error_code
|
|
125
|
-
-32603
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Transport error
|
|
130
|
-
class TransportError < Error
|
|
131
|
-
private
|
|
132
|
-
|
|
133
|
-
def default_error_code
|
|
134
|
-
-32603
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
|
|
5
|
-
module McpOnRuby
|
|
6
|
-
module Generators
|
|
7
|
-
# Generator for installing MCP server in Rails application
|
|
8
|
-
class InstallGenerator < Rails::Generators::Base
|
|
9
|
-
source_root File.expand_path('templates', __dir__)
|
|
10
|
-
|
|
11
|
-
desc "Install MCP server in Rails application"
|
|
12
|
-
|
|
13
|
-
def create_initializer
|
|
14
|
-
template 'initializer.rb', 'config/initializers/mcp_on_ruby.rb'
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def create_application_classes
|
|
18
|
-
template 'application_tool.rb', 'app/tools/application_tool.rb'
|
|
19
|
-
template 'application_resource.rb', 'app/resources/application_resource.rb'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def create_example_tool
|
|
23
|
-
template 'sample_tool.rb', 'app/tools/sample_tool.rb'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def create_example_resource
|
|
27
|
-
template 'sample_resource.rb', 'app/resources/sample_resource.rb'
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def add_route
|
|
31
|
-
say "MCP server will be available at /mcp via middleware", :green
|
|
32
|
-
say "No route configuration needed - handled by Railtie", :blue
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def show_readme
|
|
36
|
-
readme 'README' if behavior == :invoke
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def readme(path)
|
|
42
|
-
say IO.read(File.join(self.class.source_root, path)), :green if File.exist?(File.join(self.class.source_root, path))
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|