rails_mcp_engine 0.3.0 → 0.4.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: f6c501b19758aada1c867684284621f255130d3d031aa39d4df44da9f3a79f93
4
- data.tar.gz: e07a3da73bf2414648584e8a1eaa05b45e903d64151ec458dfd554c9ae15c0bd
3
+ metadata.gz: b2e3e700975a36670beecb72ac35ea09692525da7af72e60b0aada577a06c15f
4
+ data.tar.gz: 4a7bc04fd73423db8a1704705c3d9855aeaf997ff677244029e935cd04d50f01
5
5
  SHA512:
6
- metadata.gz: 6a4df6b3b995f865c86ed8d648d1629207ad9a603033f4b74ecbfff58c9913550b90588ae71952fbec505b3b4699e1c6247c4b310e7f777a60c3167c32aadb5b
7
- data.tar.gz: 76372d4ad6346c20b65306dc277e7daba2921089fe808e721258b5b6ecb3bb72ba8d1caf16e57f7be728ad0e77a5131ad0b6cbe22d1adbf86dd2ec64a66ec345
6
+ metadata.gz: d3be894c46aa48a2a14116d77d6d5427df142ed026f89da13a3042c2ff7e935daa91ddd78f3e1a36702025926bbc9ff6f74e0bf249d4ce74f9861d99c92c55fe
7
+ data.tar.gz: 99c99e5a1e62477b9b0f24d01598e4738a032d4ae9bc2317b56326e05a5e29cc51679a770a33f50e7d01968d3653496bf197d9e1bbd90fb5fbfb578317b8321b
@@ -12,14 +12,11 @@ module RailsMcpEngine
12
12
 
13
13
  def register
14
14
  source = params[:source].to_s
15
- class_name = extract_class_name(source)
16
15
 
17
16
  result = if source.strip.empty?
18
17
  { error: 'Tool source code is required' }
19
- elsif class_name.nil?
20
- { error: 'Could not infer class name from the provided source' }
21
18
  else
22
- register_source(source, class_name)
19
+ register_source(source)
23
20
  end
24
21
 
25
22
  flash[:register_result] = result
@@ -63,70 +60,7 @@ module RailsMcpEngine
63
60
  ToolMeta.registry.map { |service_class| ToolSchema::Builder.build(service_class) }
64
61
  end
65
62
 
66
- def extract_class_name(source)
67
- require 'ripper'
68
- sexp = Ripper.sexp(source)
69
- return nil unless sexp
70
-
71
- # sexp is [:program, statements]
72
- statements = sexp[1]
73
- find_class(statements, [])
74
- end
75
-
76
- def find_class(statements, namespace)
77
- return nil unless statements.is_a?(Array)
78
-
79
- statements.each do |stmt|
80
- next unless stmt.is_a?(Array)
81
-
82
- case stmt.first
83
- when :module
84
- # [:module, const_ref, body]
85
- # body is [:bodystmt, statements, ...]
86
- const_node = stmt[1]
87
- const_name = get_const_name(const_node)
88
-
89
- body_stmt = stmt[2]
90
- inner_statements = body_stmt[1]
91
-
92
- result = find_class(inner_statements, namespace + [const_name])
93
- return result if result
94
- when :class
95
- # [:class, const_ref, superclass, body]
96
- const_node = stmt[1]
97
- const_name = get_const_name(const_node)
98
-
99
- return (namespace + [const_name]).join('::')
100
- end
101
- end
102
- nil
103
- end
104
-
105
- def get_const_name(node)
106
- return nil unless node.is_a?(Array)
107
-
108
- type = node.first
109
- if type == :const_ref
110
- # [:const_ref, [:@const, "Name", ...]]
111
- node[1][1]
112
- elsif type == :const_path_ref
113
- # [:const_path_ref, parent, child]
114
- parent = node[1]
115
- child = node[2] # [:@const, "Name", ...]
116
-
117
- parent_name = if parent.first == :var_ref
118
- parent[1][1]
119
- else
120
- get_const_name(parent)
121
- end
122
-
123
- "#{parent_name}::#{child[1]}"
124
- else
125
- nil
126
- end
127
- end
128
-
129
- def register_source(source, class_name)
63
+ def register_source(source)
130
64
  Object.class_eval(source)
131
65
  # Use the engine's namespace or ensure Tools is available.
132
66
  # Assuming Tools module is defined in the host app or globally.
@@ -139,9 +73,9 @@ module RailsMcpEngine
139
73
  # The engine.rb defines ApplicationTool.
140
74
 
141
75
  # Re-using the logic from ManualController but adapting for Engine.
76
+
142
77
  ::Tools::MetaToolWriteService.new.register_tool(
143
78
  class_name,
144
- source: source,
145
79
  before_call: ->(args) { Rails.logger.info(" [MCP] Request #{class_name}: #{args.inspect}") },
146
80
  after_call: ->(result) { Rails.logger.info(" [MCP] Response #{class_name}: #{result.inspect}") }
147
81
  )
@@ -133,11 +133,6 @@ module Tools
133
133
  "#{schema[:name]}(#{param_list})"
134
134
  end
135
135
 
136
- sig { params(name: String).returns(T.class_of(Object)) }
137
- def constantize(name)
138
- Object.const_get(name)
139
- end
140
-
141
136
  sig { params(value: T.untyped).returns(T.untyped) }
142
137
  def deep_symbolize(value)
143
138
  case value
@@ -11,25 +11,12 @@ module Tools
11
11
  class MetaToolWriteService
12
12
  extend T::Sig
13
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)
14
+ sig { params(class_name: 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, before_call: nil, after_call: nil)
16
16
  return { error: 'class_name is required for register' } if class_name.nil? || class_name.empty?
17
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
-
18
+ service_class = constantize(class_name)
19
+ return { error: "Could not find #{class_name}" } if service_class.nil?
33
20
  return { error: "#{class_name} must extend ToolMeta" } unless service_class.respond_to?(:tool_metadata)
34
21
 
35
22
  ToolMeta.registry << service_class unless ToolMeta.registry.include?(service_class)
@@ -45,6 +32,23 @@ module Tools
45
32
  { error: "Could not find #{class_name}: #{e.message}" }
46
33
  end
47
34
 
35
+ sig { params(source: T.nilable(String), before_call: T.nilable(Proc), after_call: T.nilable(Proc)).returns(T::Hash[Symbol, T.untyped]) }
36
+ def register_tool_from_source(source: nil, before_call: nil, after_call: nil)
37
+ class_name = extract_class_name(source)
38
+ return { error: 'class_name is required for register' } if class_name.nil? || class_name.empty?
39
+
40
+ # If source is provided, evaluate it first
41
+ if source
42
+ begin
43
+ Object.class_eval(source)
44
+ rescue StandardError => e
45
+ return { error: "Failed to evaluate source: #{e.message}" }
46
+ end
47
+ end
48
+
49
+ register_tool(class_name, before_call: before_call, after_call: after_call)
50
+ end
51
+
48
52
  sig { params(tool_name: String).returns(T::Hash[Symbol, T.untyped]) }
49
53
  def delete_tool(tool_name)
50
54
  schema = meta_service.find_schema(tool_name)
@@ -68,5 +72,72 @@ module Tools
68
72
  def meta_service
69
73
  @meta_service ||= Tools::MetaToolService.new
70
74
  end
75
+
76
+ def extract_class_name(source)
77
+ require 'ripper'
78
+ sexp = Ripper.sexp(source)
79
+ return nil unless sexp
80
+
81
+ # sexp is [:program, statements]
82
+ statements = sexp[1]
83
+ find_class(statements, [])
84
+ end
85
+
86
+ def find_class(statements, namespace)
87
+ return nil unless statements.is_a?(Array)
88
+
89
+ statements.each do |stmt|
90
+ next unless stmt.is_a?(Array)
91
+
92
+ case stmt.first
93
+ when :module
94
+ # [:module, const_ref, body]
95
+ # body is [:bodystmt, statements, ...]
96
+ const_node = stmt[1]
97
+ const_name = get_const_name(const_node)
98
+
99
+ body_stmt = stmt[2]
100
+ inner_statements = body_stmt[1]
101
+
102
+ result = find_class(inner_statements, namespace + [const_name])
103
+ return result if result
104
+ when :class
105
+ # [:class, const_ref, superclass, body]
106
+ const_node = stmt[1]
107
+ const_name = get_const_name(const_node)
108
+
109
+ return (namespace + [const_name]).join('::')
110
+ end
111
+ end
112
+ nil
113
+ end
114
+
115
+ def get_const_name(node)
116
+ return nil unless node.is_a?(Array)
117
+
118
+ type = node.first
119
+ if type == :const_ref
120
+ # [:const_ref, [:@const, "Name", ...]]
121
+ node[1][1]
122
+ elsif type == :const_path_ref
123
+ # [:const_path_ref, parent, child]
124
+ parent = node[1]
125
+ child = node[2] # [:@const, "Name", ...]
126
+
127
+ parent_name = if parent.first == :var_ref
128
+ parent[1][1]
129
+ else
130
+ get_const_name(parent)
131
+ end
132
+
133
+ "#{parent_name}::#{child[1]}"
134
+ else
135
+ nil
136
+ end
137
+ end
138
+
139
+ def constantize(name)
140
+ Object.const_get(name)
141
+ end
71
142
  end
72
143
  end
@@ -1,3 +1,3 @@
1
1
  module RailsMcpEngine
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soonoh Jung