Class: Tools::FindImagesTool

Inherits:
ApplicationTool show all
Defined in:
app/mcp/tools/find_images_tool.rb

Overview

MCP Tool for searching and retrieving images from the WarmlyYours image library.

Provides rich image data including:

  • CRM links (both relative and absolute image library URLs)
  • Public ImageKit URLs (for sharing or embedding)
  • Ready-to-use HTML tags with responsive srcset (for blogs/website)
  • Thumbnail URLs and markdown for inline chat previews

Supports keyword, AI (semantic/vision), and hybrid search modes,
mirroring the CRM /images index filter capabilities.

Every linkage on an image is filterable:

  • Party (customer / contact): party_id
  • Opportunity: opportunity_id
  • Item (direct link): item_id or item_sku
  • Product line (+ descendants): product_line_url
  • Product category: product_category_id
  • Tags: tags
  • Asset metadata: category, series, source, locale

Examples:

Search for bathroom images

{ query: "bathroom floor heating installation", limit: 5 }

Find images by tag with AI search

{ query: "snow melting driveway", tags: ["for-product-page"], search_mode: "ai" }

Find images for a customer by ID, CN number, or CRM URL

{ party_id: "4140751" }
{ party_id: "CN4140751" }
{ party_id: "https://crm.warmlyyours.com/en-US/customers/4140751#digital_assets" }

Find images linked to a specific item SKU

{ item_sku: "TZB120-1.5R" }

Find all bathroom-category images for a product line

{ product_line_url: "floor-heating", category: "bathroom" }

Class Method Summary collapse

Class Method Details

.call(ids: nil, query: nil, search_mode: 'hybrid', party_id: nil, opportunity_id: nil, item_id: nil, item_sku: nil, product_line_url: nil, product_line: nil, product_category_id: nil, tags: nil, category: nil, series: nil, source: nil, locale: nil, active_only: true, min_width: nil, limit: 10, thumbnail_width: 300, server_context: nil) ⇒ Object

rubocop:disable Metrics/ParameterLists



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'app/mcp/tools/find_images_tool.rb', line 166

def call(ids: nil, query: nil, search_mode: 'hybrid', # rubocop:disable Metrics/ParameterLists
         party_id: nil, opportunity_id: nil,
         item_id: nil, item_sku: nil,
         product_line_url: nil, product_line: nil,
         product_category_id: nil,
         tags: nil, category: nil, series: nil, source: nil, locale: nil,
         active_only: true, min_width: nil,
         limit: 10, thumbnail_width: 300, server_context: nil)
  limit = [[limit.to_i, 1].max, 20].min
  thumbnail_width = [[thumbnail_width.to_i, 50].max, 800].min

  # Direct ID lookup short-circuits the search pipeline. This is the
  # specific case Julia's chat hit ("find_images(ids: [10228, 3046])"
  # → unknown keyword crash → fallback to query: "10228" returning
  # irrelevant results). Stage 5 of the Sunny blog editor fix plan.
  if ids.is_a?(Array) && ids.any?
    # Cap direct ID lookups using the same upper bound as the query path
    # (see `limit` clamp above) so a huge `ids:` array can't trigger heavy
    # DB work or oversized tool payloads.
    requested_ids = ids.map(&:to_i).select(&:positive?).uniq
    numeric_ids   = requested_ids.first(limit)
    images        = Image.where(id: numeric_ids)
                         .includes(:product_lines, taggings: :tag)
                         .index_by(&:id)
    ordered = numeric_ids.filter_map { |id| images[id] }

    return json_response(
      ids: numeric_ids,
      requested_id_count: requested_ids.size,
      returned_id_count: numeric_ids.size,
      truncated: numeric_ids.size < requested_ids.size,
      total_results: ordered.size,
      images: ordered.map { |img| format_image(img, thumbnail_width) }
    )
  end

  resolved_party_id       = parse_numeric_id(party_id, resources: %w[customers contacts])
  resolved_opportunity_id = parse_numeric_id(opportunity_id, resources: %w[opportunities])
  resolved_item_ids       = resolve_item_ids(item_id: item_id.presence&.to_i, item_sku: item_sku)

  images = search_images(
    query: query.to_s.strip,
    search_mode: search_mode || 'hybrid',
    party_id: resolved_party_id,
    opportunity_id: resolved_opportunity_id,
    item_ids: resolved_item_ids,
    product_line_url: product_line_url,
    product_line: product_line,
    product_category_id: product_category_id,
    tags: tags,
    category: category,
    series: series,
    source: source,
    locale: locale,
    active_only: active_only != false,
    min_width: min_width,
    limit: limit
  )

  json_response(
    query: query,
    search_mode: search_mode,
    filters_applied: active_filters(
      party_id: resolved_party_id,
      opportunity_id: resolved_opportunity_id,
      item_ids: resolved_item_ids,
      product_line_url: product_line_url,
      product_line: product_line,
      product_category_id: product_category_id,
      tags: tags, category: category, series: series, source: source, locale: locale
    ),
    total_results: images.size,
    images: images.map { |img| format_image(img, thumbnail_width) }
  )
end