mcp_on_ruby 0.3.0 → 1.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -28
  3. data/CODE_OF_CONDUCT.md +30 -58
  4. data/CONTRIBUTING.md +61 -67
  5. data/LICENSE.txt +2 -2
  6. data/README.md +159 -509
  7. data/bin/console +11 -0
  8. data/bin/setup +6 -0
  9. data/docs/advanced-usage.md +132 -0
  10. data/docs/api-reference.md +35 -0
  11. data/docs/testing.md +55 -0
  12. data/examples/claude/README.md +171 -0
  13. data/examples/claude/claude-bridge.js +122 -0
  14. data/lib/mcp_on_ruby/configuration.rb +74 -0
  15. data/lib/mcp_on_ruby/errors.rb +137 -0
  16. data/lib/mcp_on_ruby/generators/install_generator.rb +46 -0
  17. data/lib/mcp_on_ruby/generators/resource_generator.rb +63 -0
  18. data/lib/mcp_on_ruby/generators/templates/README +31 -0
  19. data/lib/mcp_on_ruby/generators/templates/application_resource.rb +20 -0
  20. data/lib/mcp_on_ruby/generators/templates/application_tool.rb +18 -0
  21. data/lib/mcp_on_ruby/generators/templates/initializer.rb +41 -0
  22. data/lib/mcp_on_ruby/generators/templates/resource.rb +50 -0
  23. data/lib/mcp_on_ruby/generators/templates/resource_spec.rb +67 -0
  24. data/lib/mcp_on_ruby/generators/templates/sample_resource.rb +57 -0
  25. data/lib/mcp_on_ruby/generators/templates/sample_tool.rb +59 -0
  26. data/lib/mcp_on_ruby/generators/templates/tool.rb +38 -0
  27. data/lib/mcp_on_ruby/generators/templates/tool_spec.rb +55 -0
  28. data/lib/mcp_on_ruby/generators/tool_generator.rb +51 -0
  29. data/lib/mcp_on_ruby/railtie.rb +108 -0
  30. data/lib/mcp_on_ruby/resource.rb +161 -0
  31. data/lib/mcp_on_ruby/server.rb +378 -0
  32. data/lib/mcp_on_ruby/tool.rb +134 -0
  33. data/lib/mcp_on_ruby/transport.rb +330 -0
  34. data/lib/mcp_on_ruby/version.rb +6 -0
  35. data/lib/mcp_on_ruby.rb +142 -0
  36. metadata +62 -173
  37. data/lib/ruby_mcp/client.rb +0 -43
  38. data/lib/ruby_mcp/configuration.rb +0 -90
  39. data/lib/ruby_mcp/errors.rb +0 -17
  40. data/lib/ruby_mcp/models/context.rb +0 -52
  41. data/lib/ruby_mcp/models/engine.rb +0 -31
  42. data/lib/ruby_mcp/models/message.rb +0 -60
  43. data/lib/ruby_mcp/providers/anthropic.rb +0 -269
  44. data/lib/ruby_mcp/providers/base.rb +0 -57
  45. data/lib/ruby_mcp/providers/openai.rb +0 -265
  46. data/lib/ruby_mcp/schemas.rb +0 -56
  47. data/lib/ruby_mcp/server/app.rb +0 -84
  48. data/lib/ruby_mcp/server/base_controller.rb +0 -49
  49. data/lib/ruby_mcp/server/content_controller.rb +0 -68
  50. data/lib/ruby_mcp/server/contexts_controller.rb +0 -67
  51. data/lib/ruby_mcp/server/controller.rb +0 -29
  52. data/lib/ruby_mcp/server/engines_controller.rb +0 -34
  53. data/lib/ruby_mcp/server/generate_controller.rb +0 -140
  54. data/lib/ruby_mcp/server/messages_controller.rb +0 -30
  55. data/lib/ruby_mcp/server/router.rb +0 -84
  56. data/lib/ruby_mcp/storage/active_record.rb +0 -414
  57. data/lib/ruby_mcp/storage/base.rb +0 -43
  58. data/lib/ruby_mcp/storage/error.rb +0 -8
  59. data/lib/ruby_mcp/storage/memory.rb +0 -69
  60. data/lib/ruby_mcp/storage/redis.rb +0 -197
  61. data/lib/ruby_mcp/storage_factory.rb +0 -43
  62. data/lib/ruby_mcp/validator.rb +0 -45
  63. data/lib/ruby_mcp/version.rb +0 -6
  64. data/lib/ruby_mcp.rb +0 -71
@@ -0,0 +1,137 @@
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
@@ -0,0 +1,46 @@
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
@@ -0,0 +1,63 @@
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
@@ -0,0 +1,31 @@
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!
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,50 @@
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
@@ -0,0 +1,67 @@
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
@@ -0,0 +1,57 @@
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
@@ -0,0 +1,59 @@
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
@@ -0,0 +1,38 @@
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
@@ -0,0 +1,55 @@
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