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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2e3e700975a36670beecb72ac35ea09692525da7af72e60b0aada577a06c15f
|
|
4
|
+
data.tar.gz: 4a7bc04fd73423db8a1704705c3d9855aeaf997ff677244029e935cd04d50f01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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),
|
|
15
|
-
def register_tool(class_name,
|
|
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
|
-
|
|
19
|
-
if
|
|
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
|