rails_mcp_engine 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53d474e90f159408325d95bf24d519e93a5f82b2b00258dc756300dd6ae04365
4
- data.tar.gz: f38095e894a2ae6b3aba6744aadfaf529d8aa9029f7f1b0f0c4992516e2beb93
3
+ metadata.gz: f6c501b19758aada1c867684284621f255130d3d031aa39d4df44da9f3a79f93
4
+ data.tar.gz: e07a3da73bf2414648584e8a1eaa05b45e903d64151ec458dfd554c9ae15c0bd
5
5
  SHA512:
6
- metadata.gz: 67040a2d30ec66489b8dd99f7d995f7f93e36f353e935384d8205b51d8d4103438f3ea83f8ead1397ef8afd0f54b823f76f513f3d5fe4613a0316de380d8b5a4
7
- data.tar.gz: 75e1d06bb502e402beb4f8aa85c5ede2ab741017526cd7ed413130ab6c12edeb20a77ce3fdb7cccaf859103de82d24389d029a041271b90bf79fa8938835217b
6
+ metadata.gz: 6a4df6b3b995f865c86ed8d648d1629207ad9a603033f4b74ecbfff58c9913550b90588ae71952fbec505b3b4699e1c6247c4b310e7f777a60c3167c32aadb5b
7
+ data.tar.gz: 76372d4ad6346c20b65306dc277e7daba2921089fe808e721258b5b6ecb3bb72ba8d1caf16e57f7be728ad0e77a5131ad0b6cbe22d1adbf86dd2ec64a66ec345
data/README.md CHANGED
@@ -99,6 +99,23 @@ Tools::MetaToolService.new.register_tool(
99
99
 
100
100
  These hooks are executed around the tool's entrypoint method for both RubyLLM and FastMCP wrappers.
101
101
 
102
+ ## Using Tools in Host Application
103
+
104
+ You can easily fetch the generated RubyLLM tool classes for use in your host application (e.g., when calling an LLM API):
105
+
106
+ ```ruby
107
+ # Fetch specific tool classes by name
108
+ tool_classes = Tools::MetaToolService.ruby_llm_tools(['book_meeting', 'calculator'])
109
+
110
+ # Use them with RubyLLM
111
+ response = RubyLLM.chat(
112
+ messages: messages,
113
+ tools: tool_classes,
114
+ model: 'gpt-4o'
115
+ )
116
+ ```
117
+
118
+
102
119
  ## Development
103
120
 
104
121
  After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rails test` to run the tests.
@@ -50,7 +50,7 @@ module RailsMcpEngine
50
50
  result = if schema.nil?
51
51
  { error: "Tool not found: #{tool_name}" }
52
52
  else
53
- delete_tool_from_registry(schema[:service_class])
53
+ delete_tool_from_registry(tool_name)
54
54
  end
55
55
 
56
56
  flash[:register_result] = result
@@ -139,8 +139,9 @@ module RailsMcpEngine
139
139
  # The engine.rb defines ApplicationTool.
140
140
 
141
141
  # Re-using the logic from ManualController but adapting for Engine.
142
- ::Tools::MetaToolService.new.register_tool(
142
+ ::Tools::MetaToolWriteService.new.register_tool(
143
143
  class_name,
144
+ source: source,
144
145
  before_call: ->(args) { Rails.logger.info(" [MCP] Request #{class_name}: #{args.inspect}") },
145
146
  after_call: ->(result) { Rails.logger.info(" [MCP] Response #{class_name}: #{result.inspect}") }
146
147
  )
@@ -168,14 +169,8 @@ module RailsMcpEngine
168
169
  { error: e.message }
169
170
  end
170
171
 
171
- def delete_tool_from_registry(service_class)
172
- ToolMeta.registry.delete(service_class)
173
-
174
- # Also remove the RubyLLM tool class constant
175
- tool_constant = ToolSchema::RubyLlmFactory.tool_class_name(service_class)
176
- ::Tools.send(:remove_const, tool_constant) if ::Tools.const_defined?(tool_constant, false)
177
-
178
- { success: 'Tool deleted successfully' }
172
+ def delete_tool_from_registry(tool_name)
173
+ ::Tools::MetaToolWriteService.new.delete_tool(tool_name)
179
174
  rescue StandardError => e
180
175
  { error: e.message }
181
176
  end
@@ -44,28 +44,19 @@ module Tools
44
44
  end
45
45
  end
46
46
 
47
- sig { params(class_name: T.nilable(String), before_call: T.nilable(Proc), after_call: T.nilable(Proc)).returns(T::Hash[Symbol, T.untyped]) }
48
- def register_tool(class_name, before_call: nil, after_call: nil)
49
- return { error: 'class_name is required for register' } if class_name.nil? || class_name.empty?
50
47
 
51
- service_class = constantize(class_name)
52
- return { error: "Could not find #{class_name}" } if service_class.nil?
53
- return { error: "#{class_name} must extend ToolMeta" } unless service_class.respond_to?(:tool_metadata)
54
48
 
55
- ToolMeta.registry << service_class unless ToolMeta.registry.include?(service_class)
49
+ sig { params(tool_names: T::Array[String]).returns(T::Array[T.class_of(Object)]) }
50
+ def self.ruby_llm_tools(tool_names)
51
+ ToolMeta.registry.filter_map do |service_class|
52
+ schema = ToolSchema::Builder.build(service_class)
53
+ next unless tool_names.include?(schema[:name])
56
54
 
57
- schema = ToolSchema::Builder.build(service_class)
58
- ToolSchema::RubyLlmFactory.build(service_class, schema, before_call: before_call, after_call: after_call)
59
- ToolSchema::FastMcpFactory.build(service_class, schema, before_call: before_call, after_call: after_call)
60
-
61
- { status: 'registered', tool: summary_payload(schema) }
62
- rescue ToolMeta::MissingSignatureError => e
63
- { error: e.message }
64
- rescue NameError => e
65
- { error: "Could not find #{class_name}: #{e.message}" }
55
+ tool_class_name = ToolSchema::RubyLlmFactory.tool_class_name(service_class)
56
+ Tools.const_get(tool_class_name) if Tools.const_defined?(tool_class_name)
57
+ end
66
58
  end
67
59
 
68
- private
69
60
 
70
61
  sig { params(query: T.nilable(String)).returns(T::Hash[Symbol, T.untyped]) }
71
62
  def search_tools(query)
@@ -0,0 +1,72 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require 'tool_meta'
6
+ require 'tool_schema/builder'
7
+ require 'tool_schema/ruby_llm_factory'
8
+ require 'tool_schema/fast_mcp_factory'
9
+
10
+ module Tools
11
+ class MetaToolWriteService
12
+ extend T::Sig
13
+
14
+ sig { params(class_name: T.nilable(String), source: T.nilable(String), before_call: T.nilable(Proc), after_call: T.nilable(Proc)).returns(T::Hash[Symbol, T.untyped]) }
15
+ def register_tool(class_name, source: nil, before_call: nil, after_call: nil)
16
+ return { error: 'class_name is required for register' } if class_name.nil? || class_name.empty?
17
+
18
+ # If source is provided, evaluate it first
19
+ if source
20
+ begin
21
+ Object.class_eval(source)
22
+ rescue StandardError => e
23
+ return { error: "Failed to evaluate source: #{e.message}" }
24
+ end
25
+ end
26
+
27
+ begin
28
+ service_class = meta_service.constantize(class_name)
29
+ rescue NameError
30
+ return { error: "Could not find #{class_name}" }
31
+ end
32
+
33
+ return { error: "#{class_name} must extend ToolMeta" } unless service_class.respond_to?(:tool_metadata)
34
+
35
+ ToolMeta.registry << service_class unless ToolMeta.registry.include?(service_class)
36
+
37
+ schema = ToolSchema::Builder.build(service_class)
38
+ ToolSchema::RubyLlmFactory.build(service_class, schema, before_call: before_call, after_call: after_call)
39
+ ToolSchema::FastMcpFactory.build(service_class, schema, before_call: before_call, after_call: after_call)
40
+
41
+ { status: 'registered', tool: meta_service.summary_payload(schema) }
42
+ rescue ToolMeta::MissingSignatureError => e
43
+ { error: e.message }
44
+ rescue NameError => e
45
+ { error: "Could not find #{class_name}: #{e.message}" }
46
+ end
47
+
48
+ sig { params(tool_name: String).returns(T::Hash[Symbol, T.untyped]) }
49
+ def delete_tool(tool_name)
50
+ schema = meta_service.find_schema(tool_name)
51
+ return { error: "Tool not found: #{tool_name}" } unless schema
52
+
53
+ service_class = schema[:service_class]
54
+ ToolMeta.registry.delete(service_class)
55
+
56
+ tool_constant = ToolSchema::RubyLlmFactory.tool_class_name(service_class)
57
+ Tools.send(:remove_const, tool_constant) if Tools.const_defined?(tool_constant, false)
58
+ fast_mcp_constant = ToolSchema::FastMcpFactory.tool_class_name(service_class)
59
+ Mcp.send(:remove_const, fast_mcp_constant) if Mcp.const_defined?(fast_mcp_constant, false)
60
+
61
+ { success: 'Tool deleted successfully' }
62
+ rescue StandardError => e
63
+ { error: e.message }
64
+ end
65
+
66
+ private
67
+
68
+ def meta_service
69
+ @meta_service ||= Tools::MetaToolService.new
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsMcpEngine
2
- VERSION = '0.1.0'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_mcp_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soonoh Jung
@@ -92,6 +92,7 @@ files:
92
92
  - app/lib/tool_schema/ruby_llm_factory.rb
93
93
  - app/lib/tool_schema/sorbet_type_mapper.rb
94
94
  - app/services/tools/meta_tool_service.rb
95
+ - app/services/tools/meta_tool_write_service.rb
95
96
  - app/views/rails_mcp_engine/chat/show.html.erb
96
97
  - app/views/rails_mcp_engine/playground/show.html.erb
97
98
  - config/routes.rb