Class: Collavre::McpService
- Inherits:
-
Object
- Object
- Collavre::McpService
- Defined in:
- app/services/collavre/mcp_service.rb
Class Method Summary collapse
-
.available_tools(user) ⇒ Object
Fetch and filter available tools for the given user.
- .delete_tool(tool_name) ⇒ Object
- .filter_tools(tools, user) ⇒ Object
- .load_active_tools ⇒ Object
-
.register_tool_from_source(source_code) ⇒ Object
— Registration Logic (from MetaToolService) —.
Instance Method Summary collapse
-
#update_from_creative(input_creative) ⇒ Object
— Creative Parsing Logic (from MetaToolWriteService) —.
Class Method Details
.available_tools(user) ⇒ Object
Fetch and filter available tools for the given user. Returns an array of tool hashes with :name, :description, :params keys.
116 117 118 119 120 121 122 123 124 125 126 |
# File 'app/services/collavre/mcp_service.rb', line 116 def self.available_tools(user) return [] unless defined?(RailsMcpEngine) RailsMcpEngine::Engine.build_tools! result = ::Tools::MetaToolService.new.call(action: "list", tool_name: nil, query: nil, arguments: nil) tool_list = Array(result[:tools]) filter_tools(tool_list, user) rescue StandardError => e Rails.logger.error("Failed to load available tools: #{e.}") [] end |
.delete_tool(tool_name) ⇒ Object
128 129 130 131 132 133 134 135 136 |
# File 'app/services/collavre/mcp_service.rb', line 128 def self.delete_tool(tool_name) result = ::Tools::MetaToolWriteService.new.delete_tool(tool_name) if result[:error] Rails.logger.error("Failed to delete tool #{tool_name}: #{result[:error]}") end rescue StandardError => e Rails.logger.error("Failed to delete tool #{tool_name}: #{e.}") end |
.filter_tools(tools, user) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'app/services/collavre/mcp_service.rb', line 63 def self.filter_tools(tools, user) return [] if tools.blank? # Identify dynamic tools (user-defined) vs system tools. # Tools can be objects (FastMcp::Tool) or Hashes (from MetaToolService) registered_names = tools.map do |tool| if tool.respond_to?(:tool_name) tool.tool_name elsif tool.is_a?(Hash) tool[:name] || tool["name"] end end # Check strict loading? No, simple where is fine. dynamic_tools = McpTool.where(name: registered_names).includes(:creative) dynamic_tool_names = dynamic_tools.pluck(:name).to_set # Build set of tool names the user has permission to run # User needs write permission on the creative to run its tools accessible_tool_names = if user dynamic_tools.select do |mcp_tool| mcp_tool.creative&.(user, :write) end.map(&:name).to_set else Set.new end tools.select do |tool| name = if tool.respond_to?(:tool_name) tool.tool_name elsif tool.is_a?(Hash) tool[:name] || tool["name"] else nil end if dynamic_tool_names.include?(name) # It is a dynamic tool; user must have write permission on its creative. accessible_tool_names.include?(name) else # It is a system tool (not in McpTool database); allow it. true end end end |
.load_active_tools ⇒ Object
108 109 110 111 112 |
# File 'app/services/collavre/mcp_service.rb', line 108 def self.load_active_tools McpTool.active.find_each do |tool| register_tool_from_source(tool.source_code) end end |
.register_tool_from_source(source_code) ⇒ Object
— Registration Logic (from MetaToolService) —
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'app/services/collavre/mcp_service.rb', line 8 def self.register_tool_from_source(source_code) # Extract tool name for logging context tool_name_match = source_code.match(/tool_name\s+["'](.+?)["']/) tool_name = tool_name_match ? tool_name_match[1] : "unknown_tool" before_call = proc do |tool_instance, method_name, args| # Store args for after_call access if needed, or just log start # Using thread local to pass data to after_call if we want to correlate exact timing or args Thread.current[:mcp_tool_args_stack] ||= [] Thread.current[:mcp_tool_args_stack].push(args) end after_call = proc do |tool_instance, method_name, result| # Retrieve args args = Thread.current[:mcp_tool_args_stack]&.pop || {} # Create activity log # We need a user to attribute this to. # If executed in a background job (Task), Current.user is set. # If executed via API, Current.user is set. user = Current.user creative = tool_instance&.try(:creative_context) rescue nil # Assuming some way to get context if needed, or nil ActivityLog.create!( activity: "tool_execution", user: user, creative: creative, # Optional: if we can link it back to a creative log: { tool_name: tool_name, method: method_name, args: args, result: result } ) rescue StandardError => e Rails.logger.error("Failed to log tool activity: #{e.}") end result = ::Tools::MetaToolWriteService.new.register_tool_from_source( source: source_code, before_call: before_call, after_call: after_call ) Rails.logger.info("Registered tool: #{result}") if result[:error] error_msg = "Failed to register tool: #{result[:error]}" Rails.logger.error(error_msg) raise error_msg end rescue StandardError => e Rails.logger.error("Failed to register tool from source: #{e.}") raise e end |
Instance Method Details
#update_from_creative(input_creative) ⇒ Object
— Creative Parsing Logic (from MetaToolWriteService) —
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'app/services/collavre/mcp_service.rb', line 140 def update_from_creative(input_creative) creative = input_creative.effective_origin return unless creative.description.present? # Parse HTML to find code blocks doc = Nokogiri::HTML.fragment(creative.description) # Track found tools to identify removals found_tool_names = [] # Find all code blocks. # Lexical uses <pre class="lexical-code-block">. # Standard markdown often uses <code>. doc.css("pre.lexical-code-block, code").each do |node| # Create a copy to manipulate working_node = node.dup # Replace <br> tags with newlines working_node.search("br").each { |br| br.replace("\n") } code = working_node.text # Check if it looks like a tool definition if code.include?("extend ToolMeta") tool_name = process_tool_definition(creative, code) found_tool_names << tool_name if tool_name end end # Remove tools that are no longer in the description # Ensure we look at the effective origin's tools creative.mcp_tools.where.not(name: found_tool_names).destroy_all end |