Module: Legion::MCP::StructuralIndex

Extended by:
Logging::Helper
Defined in:
lib/legion/mcp/structural_index.rb

Constant Summary collapse

CACHE_PATH =
File.expand_path('~/.legionio/cache/structural_index.json')

Class Method Summary collapse

Class Method Details

.apply_type_filter(result, type) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/legion/mcp/structural_index.rb', line 122

def apply_type_filter(result, type)
  case type
  when 'tools'
    result.delete(:extensions)
  when 'extensions'
    result.delete(:tools)
  when 'runners'
    result[:extensions]&.each { |e| e.delete(:actors) }
    result.delete(:tools)
  when 'actors'
    result[:extensions]&.each { |e| e.delete(:runners) }
    result.delete(:tools)
  end
end

.buildObject



14
15
16
17
18
19
20
# File 'lib/legion/mcp/structural_index.rb', line 14

def build
  {
    extensions:   scan_extensions,
    tools:        scan_tools,
    generated_at: Time.now.iso8601
  }
end

.build_actor_entry(actor_mod) ⇒ Object



72
73
74
75
76
77
# File 'lib/legion/mcp/structural_index.rb', line 72

def build_actor_entry(actor_mod)
  {
    name: actor_mod.respond_to?(:name) ? actor_mod.name : actor_mod.to_s,
    type: actor_mod.respond_to?(:actor_type) ? actor_mod.actor_type : 'unknown'
  }
end

.build_extension_entry(ext) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/legion/mcp/structural_index.rb', line 40

def build_extension_entry(ext)
  runners = if ext.respond_to?(:runner_modules)
              ext.runner_modules.filter_map { |rm| build_runner_entry(rm) }
            else
              []
            end

  actors = if ext.respond_to?(:actor_modules)
             ext.actor_modules.filter_map { |am| build_actor_entry(am) }
           else
             []
           end

  name = ext.respond_to?(:extension_name) ? ext.extension_name : ext.class.name

  {
    name:    name,
    runners: runners,
    actors:  actors
  }
end

.build_runner_entry(runner_mod) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/legion/mcp/structural_index.rb', line 62

def build_runner_entry(runner_mod)
  settings = runner_mod.respond_to?(:settings) ? runner_mod.settings : {}
  functions = settings.is_a?(Hash) ? (settings[:functions] || {}) : {}

  {
    name:      runner_mod.respond_to?(:name) ? runner_mod.name : runner_mod.to_s,
    functions: functions.keys.map(&:to_s)
  }
end

.cachedObject



89
90
91
92
93
94
95
96
97
# File 'lib/legion/mcp/structural_index.rb', line 89

def cached
  return nil unless File.exist?(CACHE_PATH)

  data = File.read(CACHE_PATH)
  Legion::JSON.load(data)
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'legion.mcp.structural_index.cached')
  nil
end

.filter(index, extension: nil, type: nil) ⇒ Object



115
116
117
118
119
120
# File 'lib/legion/mcp/structural_index.rb', line 115

def filter(index, extension: nil, type: nil)
  result = index.dup
  result[:extensions] = result[:extensions]&.select { |e| e[:name]&.include?(extension) } || [] if extension
  apply_type_filter(result, type) if type
  result
end

.invalidate_cacheObject



107
108
109
# File 'lib/legion/mcp/structural_index.rb', line 107

def invalidate_cache
  FileUtils.rm_f(CACHE_PATH)
end

.load_or_buildObject



111
112
113
# File 'lib/legion/mcp/structural_index.rb', line 111

def load_or_build
  cached || save_cache(build)
end

.save_cache(index = nil) ⇒ Object



99
100
101
102
103
104
105
# File 'lib/legion/mcp/structural_index.rb', line 99

def save_cache(index = nil)
  index ||= build
  dir = File.dirname(CACHE_PATH)
  FileUtils.mkdir_p(dir) unless File.directory?(dir)
  File.write(CACHE_PATH, Legion::JSON.dump(index))
  index
end

.scan_extensionsObject



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/legion/mcp/structural_index.rb', line 22

def scan_extensions
  return [] unless defined?(Legion::Extensions)

  extensions = if Legion::Extensions.respond_to?(:extensions)
                 Legion::Extensions.extensions || []
               else
                 Legion::Extensions.instance_variable_get(:@extensions) || []
               end

  extensions.filter_map do |ext|
    build_extension_entry(ext)
  rescue StandardError => e
    handle_exception(e, level: :debug, operation: 'legion.mcp.structural_index.scan_extensions')
    log.debug("StructuralIndex: skipping #{ext}: #{e.message}")
    nil
  end
end

.scan_toolsObject



79
80
81
82
83
84
85
86
87
# File 'lib/legion/mcp/structural_index.rb', line 79

def scan_tools
  Server.tool_registry.map do |tc|
    {
      name:        tc.tool_name,
      description: tc.description,
      catalog:     tc.respond_to?(:catalog_entry) && tc.catalog_entry ? true : false
    }
  end
end