Class: RailsMcpServer::SearchTools

Inherits:
BaseTool
  • Object
show all
Defined in:
lib/rails-mcp-server/tools/search_tools.rb

Constant Summary collapse

TOOL_CATALOG =
{
  "project_info" => {
    category: "project",
    keywords: %w[project info structure directory tree rails version],
    summary: "Get Rails version, directory structure, and project overview",
    parameters: [
      {name: "max_depth", type: "integer", required: false, description: "Directory tree depth (default: 2, max: 5)"},
      {name: "include_files", type: "boolean", required: false, description: "Include files in tree (default: true)"},
      {name: "detail_level", type: "string", required: false, description: "'minimal', 'summary', or 'full' (default: 'full')"}
    ]
  },
  "list_files" => {
    category: "files",
    keywords: %w[files list directory glob find search],
    summary: "List files matching a pattern in a directory",
    parameters: [
      {name: "directory", type: "string", required: false, description: "Directory path relative to project root"},
      {name: "pattern", type: "string", required: false, description: "Glob pattern (default: '*.rb')"}
    ]
  },
  "get_file" => {
    category: "files",
    keywords: %w[file read content source code view],
    summary: "Read the contents of a specific file",
    parameters: [
      {name: "path", type: "string", required: true, description: "File path relative to project root"}
    ]
  },
  "get_routes" => {
    category: "routing",
    keywords: %w[routes endpoints urls api paths http verbs controller actions introspection],
    summary: "List HTTP routes via Rails introspection with filtering by controller, verb, or path",
    parameters: [
      {name: "controller", type: "string", required: false, description: "Filter by controller name"},
      {name: "verb", type: "string", required: false, description: "Filter by HTTP verb (GET, POST, PUT, PATCH, DELETE)"},
      {name: "path_contains", type: "string", required: false, description: "Filter routes containing path segment"},
      {name: "named_only", type: "boolean", required: false, description: "Only return named routes (default: false)"},
      {name: "detail_level", type: "string", required: false, description: "'names', 'summary', or 'full' (default: 'full')"}
    ]
  },
  "analyze_models" => {
    category: "models",
    keywords: %w[model active_record orm association schema database belongs_to has_many validations callbacks scopes prism introspection],
    summary: "Analyze ActiveRecord models using Rails introspection and/or Prism static analysis",
    parameters: [
      {name: "model_name", type: "string", required: false, description: "Single model name (CamelCase)"},
      {name: "model_names", type: "array", required: false, description: "Array of model names for batch analysis"},
      {name: "detail_level", type: "string", required: false, description: "'names', 'associations', or 'full' (default: 'full')"},
      {name: "analysis_type", type: "string", required: false, description: "'introspection' (runtime), 'static' (Prism AST), or 'full' (both). Default: 'introspection'"}
    ]
  },
  "get_schema" => {
    category: "database",
    keywords: %w[schema database table column migration sql foreign_key index],
    summary: "Get database schema - all tables or specific table details",
    parameters: [
      {name: "table_name", type: "string", required: false, description: "Specific table (snake_case, plural)"},
      {name: "table_names", type: "array", required: false, description: "Array of table names for batch retrieval"},
      {name: "detail_level", type: "string", required: false, description: "'tables', 'summary', or 'full' (default: 'full')"}
    ]
  },
  "analyze_controller_views" => {
    category: "controllers",
    keywords: %w[controller view action template partial stimulus erb html callbacks filters before_action strong_params prism introspection],
    summary: "Analyze controllers using Rails introspection and/or Prism static analysis (callbacks, filters, strong params, renders)",
    parameters: [
      {name: "controller_name", type: "string", required: false, description: "Specific controller to analyze"},
      {name: "detail_level", type: "string", required: false, description: "'names', 'summary', or 'full' (default: 'full')"},
      {name: "analysis_type", type: "string", required: false, description: "'introspection' (runtime), 'static' (Prism AST), or 'full' (both). Default: 'introspection'"}
    ]
  },
  "analyze_environment_config" => {
    category: "project",
    keywords: %w[environment config configuration development production test settings],
    summary: "Compare environment configurations and find inconsistencies",
    parameters: []
  },
  "load_guide" => {
    category: "guides",
    keywords: %w[guide documentation rails turbo stimulus kamal docs help custom],
    summary: "Load Rails, Turbo, Stimulus, Kamal, or custom documentation guides",
    parameters: [
      {name: "library", type: "string", required: true, description: "Guide source: 'rails', 'turbo', 'stimulus', 'kamal', or 'custom'"},
      {name: "guide", type: "string", required: false, description: "Specific guide name to load (omit to list available guides)"}
    ]
  }
}.freeze
CATEGORIES =
%w[models database routing controllers files project guides].freeze

Instance Method Summary collapse

Instance Method Details

#call(query: nil, category: nil, detail_level: "summary") ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/rails-mcp-server/tools/search_tools.rb', line 118

def call(query: nil, category: nil, detail_level: "summary")
  detail_level = "summary" unless %w[names summary full].include?(detail_level)

  # Filter tools
  filtered_tools = TOOL_CATALOG.dup

  if category && CATEGORIES.include?(category.downcase)
    filtered_tools = filtered_tools.select { |_, info| info[:category] == category.downcase }
  end

  if query && !query.strip.empty?
    query_terms = query.downcase.split(/\s+/)
    filtered_tools = filtered_tools.select do |name, info|
      searchable = [name, info[:category], info[:summary], *info[:keywords]].join(" ").downcase
      query_terms.all? { |term| searchable.include?(term) }
    end
  end

  if filtered_tools.empty?
    return "No tools found matching your criteria. Available categories: #{CATEGORIES.join(", ")}"
  end

  format_results(filtered_tools, detail_level)
end