Class: Tools::SemanticSearchTool

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

Overview

MCP Tool for performing semantic search across all embedded content.
Uses pgvector embeddings to find content matching natural language queries.

Examples:

Search for showcases about snow melting

{ query: "snow melting under pavers", types: ["showcases"], limit: 5 }

Search across all content

{ query: "how to install floor heating", limit: 10 }

Constant Summary collapse

SENSITIVE_SEARCH_TYPES =
%w[support_notes support_communications].freeze

Class Method Summary collapse

Class Method Details

.call(query:, types: nil, limit: 10, server_context: nil) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'app/mcp/tools/semantic_search_tool.rb', line 53

def call(query:, types: nil, limit: 10, server_context: nil)
  limit = [[limit.to_i, 1].max, 50].min

  normalized = normalize_types(types)
  needs_sensitive = Array(types).any? { |t| SENSITIVE_SEARCH_TYPES.include?(t.to_s.downcase) }

  search_limit = needs_sensitive ? [[limit * 3, 1].max, 50].min : limit

  results = SemanticSearchService.search(
    query,
    types: normalized,
    limit: search_limit,
    exclude_sensitive: !needs_sensitive
  )

  results = filter_support_case_embeddables(results) if needs_sensitive
  results = results.first(limit)

  records = results.map { |r| r[:record] }
  items = records.select { |rec| rec.is_a?(Item) }
  product_lines = records.select { |rec| rec.is_a?(ProductLine) }
  all_pls = (items.filter_map(&:primary_product_line) + product_lines).uniq(&:id)
  if all_pls.any?
    paths = ProductLine.canonical_paths_for(all_pls)
    all_pls.each { |pl| pl.instance_variable_set(:@canonical_path, paths[pl.id]) }
  end

  json_response(
    query: query,
    total_results: results.size,
    results: results.map { |r| format_result(r) }
  )
end