Class: HotwireClub::MCP::Database::Adapter

Inherits:
Object
  • Object
show all
Defined in:
lib/hotwire_club/mcp/database/adapter.rb

Overview

Database Adapter providing MCP-facing API

This adapter wraps the ROM repositories and provides a clean interface for searching chunks, listing documents, categories, and tags.

Instance Method Summary collapse

Constructor Details

#initialize(*args, **kwargs) ⇒ Adapter

Initialize the adapter with a ROM container or database path

Supports both positional and keyword arguments for backward compatibility:

  • Adapter.new(container) - positional container
  • Adapter.new(container: container) - keyword container
  • Adapter.new(db_path: "path") - keyword db_path

Parameters:

  • args (Array)

    Positional arguments (container as first arg)

  • kwargs (Hash)

    Keyword arguments (container: or db_path:)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 23

def initialize(*args, **kwargs)
  # Support both positional and keyword arguments for backward compatibility
  if args.first.respond_to?(:relations)
    # Positional container argument (backward compatible)
    @container = args.first
  elsif kwargs[:container]
    # Keyword container argument
    @container = kwargs[:container]
  elsif kwargs[:db_path]
    # Keyword db_path argument
    @container = HotwireClub::MCP::Database.container(kwargs[:db_path])
  else
    raise ArgumentError, "Either container or db_path: must be provided"
  end
  @chunks_repo = ChunksRepo.new(@container)
  @docs_repo = DocsRepo.new(@container)
  @tags_repo = TagsRepo.new(@container)
end

Instance Method Details

#get_chunk(chunk_id:) ⇒ Hash?

Get a single chunk by ID

Parameters:

  • chunk_id (String)

    Chunk ID

Returns:

  • (Hash, nil)

    Chunk hash or nil if not found



73
74
75
76
77
78
79
80
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 73

def get_chunk(chunk_id:)
  return nil if chunk_id.nil? || chunk_id.to_s.empty?

  chunk = @chunks_repo.by_id(chunk_id)
  return nil unless chunk

  format_chunk_result(chunk)
end

#list_categoriesArray<String>

List all unique categories from documents

Returns:

  • (Array<String>)

    Array of category names



85
86
87
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 85

def list_categories
  @docs_repo.categories
end

#list_docs(category: nil, tags: nil, limit: 20, offset: 0) ⇒ Array

List documents with optional filters

Parameters:

  • category (String, nil) (defaults to: nil)

    Filter by category

  • tags (Array<String>, nil) (defaults to: nil)

    Filter by tags (documents must have all tags)

  • limit (Integer) (defaults to: 20)

    Maximum number of results (default: 20)

  • offset (Integer) (defaults to: 0)

    Number of results to skip (default: 0)

Returns:

  • (Array)

    Array of documents



103
104
105
106
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 103

def list_docs(category: nil, tags: nil, limit: 20, offset: 0)
  tags = Array(tags).compact.reject(&:empty?)
  @docs_repo.list(category: category, tags: tags, limit: limit, offset: offset)
end

#list_tagsArray<String>

List all tags

Returns:

  • (Array<String>)

    Array of tag names



92
93
94
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 92

def list_tags
  @container.relations[:tags].select(:name).map { |row| row[:name] || row.name }
end

Find related documents based on category and tag overlap

Parameters:

  • doc_id (String, nil) (defaults to: nil)

    Document ID to find related docs for

  • chunk_id (String, nil) (defaults to: nil)

    Chunk ID to find related docs for (will use chunk's doc_id)

  • limit (Integer) (defaults to: 5)

    Maximum number of results (default: 5)

Returns:

  • (Array)

    Array of related documents



114
115
116
117
118
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 114

def related_docs(doc_id: nil, chunk_id: nil, limit: 5)
  # Prioritize doc_id over chunk_id if both are provided
  effective_chunk_id = doc_id ? nil : chunk_id
  @docs_repo.related_docs(doc_id: doc_id, chunk_id: effective_chunk_id, limit: limit)
end

#search(query:, category: nil, tags: nil, limit: 8) ⇒ Array<Hash>

Search chunks using full-text search with optional filters

Parameters:

  • query (String)

    Search query

  • category (String, nil) (defaults to: nil)

    Filter by category

  • tags (Array<String>, nil) (defaults to: nil)

    Filter by tags (chunks must have all tags)

  • limit (Integer) (defaults to: 8)

    Maximum number of results (default: 8)

Returns:

  • (Array<Hash>)

    Array of result hashes with keys:

    • chunk_id [String]
    • doc_id [String]
    • title [String, nil]
    • category [String, nil]
    • tags [Array]
    • position [Integer]
    • score [Numeric] FTS5 relevance score
    • snippet [String] First 400 characters of text
    • date [Date, Time, String, nil] From doc (may be nil)


58
59
60
61
62
63
64
65
66
67
# File 'lib/hotwire_club/mcp/database/adapter.rb', line 58

def search(query:, category: nil, tags: nil, limit: 8)
  query = query.to_s.strip
  return [] if query.empty?

  tags = Array(tags).compact.reject(&:empty?)
  escaped_query = escape_fts5_query(query)
  dataset = build_search_dataset(escaped_query, category, tags, limit)

  dataset.to_a.map { |row| format_search_result(row) }
end