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
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
|
|
5
|
-
module McpOnRuby
|
|
6
|
-
module Generators
|
|
7
|
-
# Generator for creating MCP resources
|
|
8
|
-
class ResourceGenerator < Rails::Generators::NamedBase
|
|
9
|
-
source_root File.expand_path('templates', __dir__)
|
|
10
|
-
|
|
11
|
-
desc "Generate an MCP resource class"
|
|
12
|
-
|
|
13
|
-
argument :name, type: :string, desc: "Name of the resource"
|
|
14
|
-
|
|
15
|
-
class_option :uri, type: :string, desc: "URI pattern for the resource"
|
|
16
|
-
class_option :description, type: :string, desc: "Description of the resource"
|
|
17
|
-
class_option :mime_type, type: :string, default: 'application/json', desc: "MIME type of the resource"
|
|
18
|
-
class_option :template, type: :boolean, default: false, desc: "Create a templated resource with parameters"
|
|
19
|
-
|
|
20
|
-
def create_resource_file
|
|
21
|
-
template 'resource.rb', File.join('app/resources', "#{file_name}_resource.rb")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def create_spec_file
|
|
25
|
-
return unless File.exist?(Rails.root.join('spec'))
|
|
26
|
-
|
|
27
|
-
template 'resource_spec.rb', File.join('spec/resources', "#{file_name}_resource_spec.rb")
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def resource_name
|
|
33
|
-
name.underscore
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def resource_class_name
|
|
37
|
-
"#{name.camelize}Resource"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def resource_uri
|
|
41
|
-
options[:uri] || (options[:template] ? "#{resource_name}/{id}" : resource_name)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def resource_description
|
|
45
|
-
options[:description] || "#{name.humanize} resource"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def resource_mime_type
|
|
49
|
-
options[:mime_type]
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def is_template?
|
|
53
|
-
options[:template] || resource_uri.include?('{')
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def template_params
|
|
57
|
-
return [] unless is_template?
|
|
58
|
-
|
|
59
|
-
resource_uri.scan(/\{([^}]+)\}/).flatten
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
MCP on Ruby has been installed successfully!
|
|
2
|
-
|
|
3
|
-
Next steps:
|
|
4
|
-
|
|
5
|
-
1. Configure your MCP server in config/initializers/mcp_on_ruby.rb
|
|
6
|
-
2. Create tools in app/tools/ using: rails generate mcp_on_ruby:tool ToolName
|
|
7
|
-
3. Create resources in app/resources/ using: rails generate mcp_on_ruby:resource ResourceName
|
|
8
|
-
4. Start your Rails server and access MCP at http://localhost:3000/mcp
|
|
9
|
-
|
|
10
|
-
Example tool generation:
|
|
11
|
-
rails generate mcp_on_ruby:tool UserManager --description "Manage users"
|
|
12
|
-
|
|
13
|
-
Example resource generation:
|
|
14
|
-
rails generate mcp_on_ruby:resource UserData --uri "users/{id}" --template
|
|
15
|
-
|
|
16
|
-
Your MCP server will automatically discover and register all tools and resources
|
|
17
|
-
in app/tools/ and app/resources/ that inherit from ApplicationTool and ApplicationResource.
|
|
18
|
-
|
|
19
|
-
For manual setup, you can configure tools and resources in the initializer:
|
|
20
|
-
|
|
21
|
-
McpOnRuby.mount_in_rails(Rails.application) do |server|
|
|
22
|
-
server.tool 'custom_tool', 'Description' do |args|
|
|
23
|
-
{ result: 'Custom logic here' }
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
server.resource 'custom_resource' do
|
|
27
|
-
{ data: 'Custom resource data' }
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
Visit the documentation for more advanced usage patterns!
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Base class for all MCP resources in this application
|
|
4
|
-
class ApplicationResource < McpOnRuby::Resource
|
|
5
|
-
# Common functionality for all resources can be added here
|
|
6
|
-
|
|
7
|
-
# Example: Add common authorization logic
|
|
8
|
-
# def authorize(context)
|
|
9
|
-
# # Check if user is authenticated
|
|
10
|
-
# context[:authenticated] == true
|
|
11
|
-
# end
|
|
12
|
-
|
|
13
|
-
# Example: Add caching for all resource reads
|
|
14
|
-
# def read(params = {}, context = {})
|
|
15
|
-
# cache_key = "mcp:resource:#{uri}:#{params.to_json}"
|
|
16
|
-
# Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
|
|
17
|
-
# super
|
|
18
|
-
# end
|
|
19
|
-
# end
|
|
20
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Base class for all MCP tools in this application
|
|
4
|
-
class ApplicationTool < McpOnRuby::Tool
|
|
5
|
-
# Common functionality for all tools can be added here
|
|
6
|
-
|
|
7
|
-
# Example: Add common authorization logic
|
|
8
|
-
# def authorize(context)
|
|
9
|
-
# # Check if user is authenticated
|
|
10
|
-
# context[:authenticated] == true
|
|
11
|
-
# end
|
|
12
|
-
|
|
13
|
-
# Example: Add logging for all tool executions
|
|
14
|
-
# def call(arguments = {}, context = {})
|
|
15
|
-
# Rails.logger.info("Tool #{name} called with: #{arguments.inspect}")
|
|
16
|
-
# super
|
|
17
|
-
# end
|
|
18
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# MCP on Ruby configuration
|
|
2
|
-
McpOnRuby.configure do |config|
|
|
3
|
-
# Logging level
|
|
4
|
-
config.log_level = Rails.logger.level
|
|
5
|
-
|
|
6
|
-
# MCP endpoint path
|
|
7
|
-
config.path = '/mcp'
|
|
8
|
-
|
|
9
|
-
# Authentication (set to true for production)
|
|
10
|
-
config.authentication_required = false
|
|
11
|
-
config.authentication_token = ENV['MCP_AUTH_TOKEN']
|
|
12
|
-
|
|
13
|
-
# Security settings
|
|
14
|
-
config.allowed_origins = [] # Empty means allow all origins
|
|
15
|
-
config.localhost_only = Rails.env.development?
|
|
16
|
-
config.dns_rebinding_protection = true
|
|
17
|
-
|
|
18
|
-
# Rate limiting (requests per minute per IP)
|
|
19
|
-
config.rate_limit_per_minute = 60
|
|
20
|
-
|
|
21
|
-
# Features
|
|
22
|
-
config.enable_sse = true
|
|
23
|
-
config.cors_enabled = true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Enable MCP server in Rails
|
|
27
|
-
Rails.application.configure do
|
|
28
|
-
config.mcp.enabled = true
|
|
29
|
-
config.mcp.auto_register_tools = true
|
|
30
|
-
config.mcp.auto_register_resources = true
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Mount MCP server (alternative to route-based mounting)
|
|
34
|
-
# Rails.application.config.after_initialize do
|
|
35
|
-
# McpOnRuby.mount_in_rails(Rails.application) do |server|
|
|
36
|
-
# # Manual tool/resource registration if needed
|
|
37
|
-
# # server.tool 'custom_tool', 'Description' do |args|
|
|
38
|
-
# # { result: 'Custom logic here' }
|
|
39
|
-
# # end
|
|
40
|
-
# end
|
|
41
|
-
# end
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# <%= resource_description %>
|
|
4
|
-
class <%= resource_class_name %> < ApplicationResource
|
|
5
|
-
def initialize
|
|
6
|
-
super(
|
|
7
|
-
uri: '<%= resource_uri %>',
|
|
8
|
-
name: '<%= name.humanize %>',
|
|
9
|
-
description: '<%= resource_description %>',
|
|
10
|
-
mime_type: '<%= resource_mime_type %>',
|
|
11
|
-
metadata: {
|
|
12
|
-
category: 'custom',
|
|
13
|
-
version: '1.0.0'
|
|
14
|
-
},
|
|
15
|
-
tags: ['<%= resource_name %>']
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
protected
|
|
20
|
-
|
|
21
|
-
def fetch_content(params, context)
|
|
22
|
-
<% if is_template? -%>
|
|
23
|
-
# This is a templated resource with parameters: <%= template_params.join(', ') %>
|
|
24
|
-
# Access parameters via params hash:
|
|
25
|
-
<% template_params.each do |param| -%>
|
|
26
|
-
# <%= param %> = params['<%= param %>']
|
|
27
|
-
<% end -%>
|
|
28
|
-
<% end -%>
|
|
29
|
-
|
|
30
|
-
# Implement your resource content fetching logic here
|
|
31
|
-
# Return data that will be serialized according to mime_type
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
resource: '<%= resource_name %>',
|
|
35
|
-
<% if is_template? -%>
|
|
36
|
-
parameters: params,
|
|
37
|
-
<% end -%>
|
|
38
|
-
data: {
|
|
39
|
-
# Your resource data here
|
|
40
|
-
},
|
|
41
|
-
generated_at: Time.current.iso8601
|
|
42
|
-
}
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Optional: Add authorization logic
|
|
46
|
-
# def authorize(context)
|
|
47
|
-
# # Return true/false based on context (user, permissions, etc.)
|
|
48
|
-
# true
|
|
49
|
-
# end
|
|
50
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe <%= resource_class_name %>, type: :model do
|
|
6
|
-
subject(:resource) { described_class.new }
|
|
7
|
-
|
|
8
|
-
describe '#read' do
|
|
9
|
-
<% if is_template? -%>
|
|
10
|
-
let(:params) { { <%= template_params.map { |p| "'#{p}' => 'test_#{p}'" }.join(', ') %> } }
|
|
11
|
-
<% else -%>
|
|
12
|
-
let(:params) { {} }
|
|
13
|
-
<% end -%>
|
|
14
|
-
let(:context) { { remote_ip: '127.0.0.1' } }
|
|
15
|
-
|
|
16
|
-
it 'returns resource content' do
|
|
17
|
-
result = resource.read(params, context)
|
|
18
|
-
|
|
19
|
-
expect(result).to be_a(Hash)
|
|
20
|
-
expect(result).to have_key(:contents)
|
|
21
|
-
expect(result[:contents]).to be_an(Array)
|
|
22
|
-
|
|
23
|
-
content = result[:contents].first
|
|
24
|
-
expect(content).to include(:uri, :mimeType, :text)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
<% if is_template? -%>
|
|
28
|
-
it 'uses template parameters' do
|
|
29
|
-
result = resource.read(params, context)
|
|
30
|
-
content = JSON.parse(result[:contents].first[:text])
|
|
31
|
-
|
|
32
|
-
expect(content['parameters']).to eq(params.stringify_keys)
|
|
33
|
-
end
|
|
34
|
-
<% end -%>
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
describe '#authorize' do
|
|
38
|
-
let(:context) { { authenticated: true } }
|
|
39
|
-
|
|
40
|
-
it 'returns true for authorized context' do
|
|
41
|
-
expect(resource.authorized?(context)).to be true
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
describe '#to_schema' do
|
|
46
|
-
it 'returns valid schema' do
|
|
47
|
-
schema = resource.to_schema
|
|
48
|
-
|
|
49
|
-
expect(schema).to include(:uri, :mimeType)
|
|
50
|
-
expect(schema[:uri]).to eq('<%= resource_uri %>')
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
<% if is_template? -%>
|
|
55
|
-
describe '#template?' do
|
|
56
|
-
it 'returns true' do
|
|
57
|
-
expect(resource.template?).to be true
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
describe '#template_params' do
|
|
62
|
-
it 'returns parameter names' do
|
|
63
|
-
expect(resource.template_params).to match_array(<%= template_params.inspect %>)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
<% end -%>
|
|
67
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Sample resource demonstrating MCP resource creation
|
|
4
|
-
class SampleResource < ApplicationResource
|
|
5
|
-
def initialize
|
|
6
|
-
super(
|
|
7
|
-
uri: 'sample_data',
|
|
8
|
-
name: 'Sample Data',
|
|
9
|
-
description: 'A sample resource that provides application statistics',
|
|
10
|
-
mime_type: 'application/json',
|
|
11
|
-
metadata: {
|
|
12
|
-
category: 'sample',
|
|
13
|
-
version: '1.0.0'
|
|
14
|
-
},
|
|
15
|
-
tags: ['sample', 'stats']
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
protected
|
|
20
|
-
|
|
21
|
-
def fetch_content(params, context)
|
|
22
|
-
# Example: Fetch data from Rails models
|
|
23
|
-
{
|
|
24
|
-
application: {
|
|
25
|
-
name: Rails.application.class.module_parent_name,
|
|
26
|
-
environment: Rails.env,
|
|
27
|
-
version: '1.0.0'
|
|
28
|
-
},
|
|
29
|
-
statistics: {
|
|
30
|
-
# users_count: User.count,
|
|
31
|
-
# posts_count: Post.count,
|
|
32
|
-
uptime: uptime_info
|
|
33
|
-
},
|
|
34
|
-
timestamp: Time.current.iso8601,
|
|
35
|
-
request_info: {
|
|
36
|
-
remote_ip: context[:remote_ip],
|
|
37
|
-
user_agent: context[:user_agent]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Optional: Add authorization logic
|
|
43
|
-
# def authorize(context)
|
|
44
|
-
# # Example: Only allow authenticated users to read this resource
|
|
45
|
-
# context[:authenticated] == true
|
|
46
|
-
# end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def uptime_info
|
|
51
|
-
load_avg = `uptime`.strip rescue 'unavailable'
|
|
52
|
-
{
|
|
53
|
-
server_uptime: load_avg,
|
|
54
|
-
rails_uptime: Time.current - Rails.application.config.time_zone.parse('2024-01-01')
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Sample tool demonstrating MCP tool creation
|
|
4
|
-
class SampleTool < ApplicationTool
|
|
5
|
-
def initialize
|
|
6
|
-
super(
|
|
7
|
-
name: 'sample_tool',
|
|
8
|
-
description: 'A sample tool that demonstrates basic functionality',
|
|
9
|
-
input_schema: {
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: {
|
|
12
|
-
message: {
|
|
13
|
-
type: 'string',
|
|
14
|
-
description: 'Message to process'
|
|
15
|
-
},
|
|
16
|
-
count: {
|
|
17
|
-
type: 'integer',
|
|
18
|
-
description: 'Number of times to repeat',
|
|
19
|
-
minimum: 1,
|
|
20
|
-
maximum: 10,
|
|
21
|
-
default: 1
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
required: ['message']
|
|
25
|
-
},
|
|
26
|
-
metadata: {
|
|
27
|
-
category: 'sample',
|
|
28
|
-
version: '1.0.0'
|
|
29
|
-
},
|
|
30
|
-
tags: ['sample', 'demo']
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
protected
|
|
35
|
-
|
|
36
|
-
def execute(arguments, context)
|
|
37
|
-
message = arguments['message']
|
|
38
|
-
count = arguments['count'] || 1
|
|
39
|
-
|
|
40
|
-
# Example: Access Rails models or services
|
|
41
|
-
# user_count = User.count
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
success: true,
|
|
45
|
-
result: message * count,
|
|
46
|
-
processed_at: Time.current.iso8601,
|
|
47
|
-
context_info: {
|
|
48
|
-
remote_ip: context[:remote_ip],
|
|
49
|
-
user_agent: context[:user_agent]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Optional: Add authorization logic
|
|
55
|
-
# def authorize(context)
|
|
56
|
-
# # Example: Only allow authenticated users
|
|
57
|
-
# context[:authenticated] == true
|
|
58
|
-
# end
|
|
59
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# <%= tool_description %>
|
|
4
|
-
class <%= tool_class_name %> < ApplicationTool
|
|
5
|
-
def initialize
|
|
6
|
-
super(
|
|
7
|
-
name: '<%= tool_name %>',
|
|
8
|
-
description: '<%= tool_description %>',
|
|
9
|
-
<%= input_schema_code %>,
|
|
10
|
-
metadata: {
|
|
11
|
-
category: 'custom',
|
|
12
|
-
version: '1.0.0'
|
|
13
|
-
},
|
|
14
|
-
tags: ['<%= tool_name %>']
|
|
15
|
-
)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
protected
|
|
19
|
-
|
|
20
|
-
def execute(arguments, context)
|
|
21
|
-
# Implement your tool logic here
|
|
22
|
-
# Arguments are validated according to input_schema
|
|
23
|
-
# Context contains request information (IP, headers, etc.)
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
success: true,
|
|
27
|
-
result: "Tool <%= tool_name %> executed successfully",
|
|
28
|
-
arguments: arguments,
|
|
29
|
-
processed_at: Time.current.iso8601
|
|
30
|
-
}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Optional: Add authorization logic
|
|
34
|
-
# def authorize(context)
|
|
35
|
-
# # Return true/false based on context (user, permissions, etc.)
|
|
36
|
-
# true
|
|
37
|
-
# end
|
|
38
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe <%= tool_class_name %>, type: :model do
|
|
6
|
-
subject(:tool) { described_class.new }
|
|
7
|
-
|
|
8
|
-
describe '#execute' do
|
|
9
|
-
let(:arguments) { {} }
|
|
10
|
-
let(:context) { { remote_ip: '127.0.0.1' } }
|
|
11
|
-
|
|
12
|
-
it 'executes successfully' do
|
|
13
|
-
result = tool.call(arguments, context)
|
|
14
|
-
|
|
15
|
-
expect(result).to be_a(Hash)
|
|
16
|
-
expect(result[:success]).to be true
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Add more specific tests based on your tool's functionality
|
|
20
|
-
# context 'with valid arguments' do
|
|
21
|
-
# let(:arguments) { { param: 'value' } }
|
|
22
|
-
#
|
|
23
|
-
# it 'returns expected result' do
|
|
24
|
-
# result = tool.call(arguments, context)
|
|
25
|
-
#
|
|
26
|
-
# expect(result[:result]).to eq('expected_value')
|
|
27
|
-
# end
|
|
28
|
-
# end
|
|
29
|
-
|
|
30
|
-
# context 'with invalid arguments' do
|
|
31
|
-
# let(:arguments) { { invalid: 'param' } }
|
|
32
|
-
#
|
|
33
|
-
# it 'raises validation error' do
|
|
34
|
-
# expect { tool.call(arguments, context) }.to raise_error(McpOnRuby::ValidationError)
|
|
35
|
-
# end
|
|
36
|
-
# end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
describe '#authorize' do
|
|
40
|
-
let(:context) { { authenticated: true } }
|
|
41
|
-
|
|
42
|
-
it 'returns true for authorized context' do
|
|
43
|
-
expect(tool.authorized?(context)).to be true
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
describe '#to_schema' do
|
|
48
|
-
it 'returns valid schema' do
|
|
49
|
-
schema = tool.to_schema
|
|
50
|
-
|
|
51
|
-
expect(schema).to include(:name, :description, :inputSchema)
|
|
52
|
-
expect(schema[:name]).to eq('<%= tool_name %>')
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
|
|
5
|
-
module McpOnRuby
|
|
6
|
-
module Generators
|
|
7
|
-
# Generator for creating MCP tools
|
|
8
|
-
class ToolGenerator < Rails::Generators::NamedBase
|
|
9
|
-
source_root File.expand_path('templates', __dir__)
|
|
10
|
-
|
|
11
|
-
desc "Generate an MCP tool class"
|
|
12
|
-
|
|
13
|
-
argument :name, type: :string, desc: "Name of the tool"
|
|
14
|
-
|
|
15
|
-
class_option :description, type: :string, desc: "Description of the tool"
|
|
16
|
-
class_option :input_schema, type: :hash, default: {}, desc: "JSON Schema for input validation"
|
|
17
|
-
|
|
18
|
-
def create_tool_file
|
|
19
|
-
template 'tool.rb', File.join('app/tools', "#{file_name}_tool.rb")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def create_spec_file
|
|
23
|
-
return unless File.exist?(Rails.root.join('spec'))
|
|
24
|
-
|
|
25
|
-
template 'tool_spec.rb', File.join('spec/tools', "#{file_name}_tool_spec.rb")
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def tool_name
|
|
31
|
-
name.underscore
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def tool_class_name
|
|
35
|
-
"#{name.camelize}Tool"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def tool_description
|
|
39
|
-
options[:description] || "#{name.humanize} tool"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def input_schema_code
|
|
43
|
-
if options[:input_schema].any?
|
|
44
|
-
"input_schema #{options[:input_schema].inspect}"
|
|
45
|
-
else
|
|
46
|
-
"# input_schema({\n # type: 'object',\n # properties: {\n # param: { type: 'string' }\n # },\n # required: ['param']\n # })"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
data/lib/mcp_on_ruby/railtie.rb
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
if defined?(Rails)
|
|
4
|
-
module McpOnRuby
|
|
5
|
-
# Rails integration via Railtie
|
|
6
|
-
class Railtie < Rails::Railtie
|
|
7
|
-
# Add MCP-specific directories to Rails autoload paths
|
|
8
|
-
initializer "mcp_on_ruby.setup_autoload_paths" do |app|
|
|
9
|
-
# Add app/tools and app/resources to autoload paths
|
|
10
|
-
%w[tools resources].each do |dir|
|
|
11
|
-
path = app.root.join("app", dir)
|
|
12
|
-
if path.exist? && !app.config.autoload_paths.include?(path.to_s)
|
|
13
|
-
app.config.autoload_paths = app.config.autoload_paths.dup if app.config.autoload_paths.frozen?
|
|
14
|
-
app.config.autoload_paths << path.to_s
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Set up Rails-friendly aliases
|
|
20
|
-
initializer "mcp_on_ruby.setup_aliases" do
|
|
21
|
-
# Create Rails-style base classes
|
|
22
|
-
Object.const_set('ApplicationTool', Class.new(McpOnRuby::Tool)) unless defined?(ApplicationTool)
|
|
23
|
-
Object.const_set('ApplicationResource', Class.new(McpOnRuby::Resource)) unless defined?(ApplicationResource)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Configure MCP server if needed
|
|
27
|
-
initializer "mcp_on_ruby.configure" do |app|
|
|
28
|
-
# Set up default configuration
|
|
29
|
-
McpOnRuby.configure do |config|
|
|
30
|
-
config.log_level = Rails.logger.level
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Add MCP configuration to Rails application
|
|
34
|
-
app.config.mcp = ActiveSupport::OrderedOptions.new
|
|
35
|
-
app.config.mcp.enabled = false
|
|
36
|
-
app.config.mcp.path = '/mcp'
|
|
37
|
-
app.config.mcp.authentication_required = false
|
|
38
|
-
app.config.mcp.authentication_token = nil
|
|
39
|
-
app.config.mcp.rate_limit_per_minute = 60
|
|
40
|
-
app.config.mcp.auto_register_tools = true
|
|
41
|
-
app.config.mcp.auto_register_resources = true
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Setup MCP middleware
|
|
45
|
-
initializer "mcp_on_ruby.setup_middleware", after: :load_config_initializers do |app|
|
|
46
|
-
next unless app.config.mcp.enabled
|
|
47
|
-
|
|
48
|
-
# Eager load MCP classes in development
|
|
49
|
-
if Rails.env.development?
|
|
50
|
-
tools_path = app.root.join('app/tools')
|
|
51
|
-
resources_path = app.root.join('app/resources')
|
|
52
|
-
|
|
53
|
-
if tools_path.exist?
|
|
54
|
-
Dir[tools_path.join('**/*.rb')].each { |file| require_dependency file }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
if resources_path.exist?
|
|
58
|
-
Dir[resources_path.join('**/*.rb')].each { |file| require_dependency file }
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Create MCP server instance
|
|
63
|
-
server = McpOnRuby.server do |s|
|
|
64
|
-
# Auto-register tools if enabled
|
|
65
|
-
if app.config.mcp.auto_register_tools && defined?(ApplicationTool)
|
|
66
|
-
ApplicationTool.descendants.each do |tool_class|
|
|
67
|
-
instance = tool_class.new
|
|
68
|
-
s.register_tool(instance)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Auto-register resources if enabled
|
|
73
|
-
if app.config.mcp.auto_register_resources && defined?(ApplicationResource)
|
|
74
|
-
ApplicationResource.descendants.each do |resource_class|
|
|
75
|
-
instance = resource_class.new
|
|
76
|
-
s.register_resource(instance)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Mount the server middleware (before the stack is frozen)
|
|
82
|
-
app.middleware.use(
|
|
83
|
-
McpOnRuby::Transport::RackMiddleware,
|
|
84
|
-
server: server,
|
|
85
|
-
path: app.config.mcp.path,
|
|
86
|
-
authentication_required: app.config.mcp.authentication_required,
|
|
87
|
-
authentication_token: app.config.mcp.authentication_token,
|
|
88
|
-
rate_limit_per_minute: app.config.mcp.rate_limit_per_minute
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# Store server reference for manual access
|
|
92
|
-
app.config.mcp_server = server
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Add rake tasks (uncomment when tasks file is created)
|
|
96
|
-
# rake_tasks do
|
|
97
|
-
# load File.expand_path('tasks/mcp.rake', __dir__)
|
|
98
|
-
# end
|
|
99
|
-
|
|
100
|
-
# Add generators
|
|
101
|
-
generators do
|
|
102
|
-
require_relative 'generators/install_generator'
|
|
103
|
-
require_relative 'generators/tool_generator'
|
|
104
|
-
require_relative 'generators/resource_generator'
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|