@@ -65,6 +65,43 @@ def output_schema(schema = nil)
6565 @output_schema
6666 end
6767
68+ ##
69+ # Defines a filter for selecting results through input parameters. Only one of filter_proc and filter_class are allowed at
70+ # the same time. If none is provided, a default where-based filter is created, using name as the filtered attribute name.
71+ #
72+ # Filters defined here can later be applied by the tool implementation using #apply_filters.
73+ #
74+ # @param name [Symbol] The name of the input parameter used for filtering.
75+ # @param filter_class [Queries::Filters::Base] A shared filter implementation to be used to perform filtering.
76+ # @param operator [String] When using a filter_class, this is the operator that will be used for filtering. Default: "="
77+ # @param filter_proc [Proc] A callback procedure used for filtering that must accept two arguments:
78+ # The base scope that the filter applies to and the value that's used as a filter input.
79+ # @example
80+ # filter :id
81+ #
82+ # @example
83+ # filter :name, filter_class: Queries::Projects::Filters::NameFilter, operator: "~"
84+ #
85+ # @example
86+ # filter :status, filter_proc: ->(scope, value) { scope.where(status_name: value) }
87+ def filter ( name , filter_class : nil , filter_proc : nil , operator : "=" )
88+ if filter_class && filter_proc
89+ raise ArgumentError , "filter_proc and filter_class are mutually exclusive, please only specify one"
90+ end
91+
92+ if filter_class
93+ filter_proc = -> ( scope , value ) { filter_class . create! ( operator :, values : Array ( value ) ) . apply_to ( scope ) }
94+ elsif !filter_proc
95+ filter_proc = -> ( scope , value ) { scope . where ( name . to_sym => value ) }
96+ end
97+
98+ filters [ name . to_sym ] = filter_proc
99+ end
100+
101+ def filters
102+ @filters ||= { }
103+ end
104+
68105 def tool
69106 config = McpConfiguration . find_by ( identifier : qualified_name )
70107 return nil if config . nil?
@@ -98,9 +135,26 @@ def handle_request(**)
98135 MCP ::Tool ::Response . new ( [ { type : "text" , text : result . to_json } ] , structured_content : result )
99136 end
100137
138+ private
139+
101140 # Intended to be implemented by subclasses. It should return a structured result (e.g. a Hash or Array).
102141 def call ( **)
103142 raise NotImplemented , "#{ self . class } needs to implement #call method"
104143 end
144+
145+ # Usable by tool implementations. Takes a scope and filters it according to the passed params.
146+ # Filtering happens based on the filters defined for the tool, see .filter.
147+ def apply_filters ( scope , params )
148+ params . each do |name , value |
149+ filter_proc = filter_proc_for ( name )
150+ scope = filter_proc . call ( scope , value )
151+ end
152+
153+ scope
154+ end
155+
156+ def filter_proc_for ( name )
157+ self . class . filters [ name ] || raise ( ArgumentError , "Don't know how to handle filter argument called #{ name } " )
158+ end
105159 end
106160end
0 commit comments