legion-mcp 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd343295ee4f59ac0614a3058aabfac15045eb01dd0b5e9dc5506f420059d577
4
- data.tar.gz: 0cb3173967bcdb22b7c2d230f173c26d4884dc05b2acbbb5d000041c2509e4aa
3
+ metadata.gz: 1f74d80bf3fc37b0b0130f77f38d7b1611c3efa6ab193085022d3aa1462e3ad4
4
+ data.tar.gz: a9ca6d64d2f3c089a787258e4ac88855d9f5d149c22eef1863bfa429157b70ed
5
5
  SHA512:
6
- metadata.gz: 56c5321aa074674433bf222fd3e513217b983e11712dad1d1fff24bab90e0de9acf0896c3d54f38a67f585ef666ffb0d5e538de787e28e04f9f8e00f59ba3787
7
- data.tar.gz: 4950bfc2b5a43bb7ef7dabb34d3b93258611b9d85a551910793dac1229f463b464ea0c44cfc690bb7a729b44493fdb4b1fe0fe9801f0f0e4cc9cbe657b2dfc2d
6
+ metadata.gz: cd0bf2a74ac997e5ce7db2bee56a6e9d821f6244ba211c41be054700bfeb7037c557079b6616403a4f188193133d2b2087f496a0ea7be43eec96694e6feed9b9
7
+ data.tar.gz: af2d74fa1eeb888753a815af65e9a4527fc00d44dfdf4941abc9955f1da539606f3284413518b0b618bcd50c71894ef5f37750154cbf3f35ae224390a89b4204
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ pkg/
4
4
  *.gem
5
5
  .bundle/
6
6
  vendor/
7
+ .worktrees
data/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # legion-mcp Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.7.4] - 2026-04-06
4
+
5
+ ### Changed
6
+ - `STATIC_TOOLS` renamed to `MCP_SPECIFIC_TOOLS` (6 MCP-only tools)
7
+ - `Catalog::Registry` calls replaced with `Tools::Registry` in catalog_dispatcher
8
+
9
+ ### Removed
10
+ - `CatalogBridge` module (replaced by `Tools::Registry`)
11
+ - `dynamic_tool_list`, `dispatch_catalog_tool`, `register_catalog_listener` from server.rb
12
+ - Stale spec files for catalog integration
4
13
 
5
14
  ## [0.7.2] - 2026-04-03
6
15
 
data/CLAUDE.md CHANGED
@@ -7,7 +7,7 @@
7
7
  Standalone gem providing the Model Context Protocol (MCP) server for LegionIO. Extracted from LegionIO to enable independent versioning and reuse. Includes semantic tool matching, observation pipeline, context compilation, tiered inference (Tier 0/1/2), and tool governance.
8
8
 
9
9
  **GitHub**: https://github.com/LegionIO/legion-mcp
10
- **Version**: 0.6.2
10
+ **Version**: 0.7.4
11
11
  **License**: Apache-2.0
12
12
  **Ruby**: >= 3.4
13
13
 
@@ -15,20 +15,30 @@ Standalone gem providing the Model Context Protocol (MCP) server for LegionIO. E
15
15
 
16
16
  ```
17
17
  Legion::MCP
18
- ├── Server # MCP::Server builder, TOOL_CLASSES registration, governance-aware build
18
+ ├── Server # MCP::Server builder, governance-aware build; tool list sourced from Legion::Tools::Registry via DeferredRegistry
19
19
  ├── Auth # JWT + API key authentication
20
20
  ├── ToolGovernance # Risk-tier tool filtering + invocation audit
21
21
  ├── ContextCompiler # Keyword + semantic tool matching, blended scoring (60% semantic + 40% keyword)
22
- ├── EmbeddingIndex # In-memory vector cache for semantic tool matching
22
+ ├── EmbeddingIndex # Semantic tool matching; delegates embedding persistence to Tools::EmbeddingCache (L0-L4)
23
23
  ├── Observer # Instrumentation pipeline: counters, ring buffer, pattern promotion
24
24
  ├── UsageFilter # Frequency/recency/keyword scoring for dynamic tool filtering
25
25
  ├── PatternStore # 4-layer degrading storage (L0 memory, L1 cache, L2 local SQLite)
26
26
  ├── TierRouter # Confidence-gated tier selection (Tier 0/1/2)
27
27
  ├── ContextGuard # Staleness, rapid-fire, anomaly detection guards
28
- ├── Tools/ # 59 MCP::Tool subclasses (legion.* namespace)
28
+ ├── ToolAdapter # Adapts Legion::Tools::Base subclasses to MCP SDK format (McpToolAdapter kept as alias)
29
+ ├── DeferredRegistry # Reads deferred tools from Legion::Tools::Registry at request time
30
+ ├── Tools/ # MCP_SPECIFIC_TOOLS only (6 registered); remaining tool files exist but are not registered in Server.tool_registry — extension tools discovered via Legion::Tools::Discovery
29
31
  └── Resources/ # RunnerCatalog, ExtensionInfo
30
32
  ```
31
33
 
34
+ ### Tool Registry Migration Notes
35
+
36
+ - **Before**: legion-mcp owned 57+ individual `Tools/*.rb` files registered in `TOOL_CLASSES`.
37
+ - **After**: Tools discovered dynamically via `Legion::Tools::Discovery` from extension `runner_modules` at boot. `Legion::Tools::Registry` classifies each as `:always` or `:deferred`. `DeferredRegistry` resolves the deferred set at request time.
38
+ - `MCP_SPECIFIC_TOOLS` (6 tools) covers MCP-only concerns not owned by any extension.
39
+ - `CatalogBridge` removed — bridged old `Extensions::Capability` / `Catalog::Registry` which no longer exist.
40
+ - `EmbeddingIndex` uses `Legion::Tools::EmbeddingCache` (5-tier L0–L4) instead of its own in-memory store.
41
+
32
42
  ## Dependencies
33
43
 
34
44
  | Gem | Required | Purpose |
@@ -65,17 +75,19 @@ All optional dependencies use `defined?()` guards:
65
75
  |------|---------|
66
76
  | `lib/legion/mcp.rb` | Entry point: `Legion::MCP.server` singleton factory |
67
77
  | `lib/legion/mcp/version.rb` | `Legion::MCP::VERSION` constant |
68
- | `lib/legion/mcp/server.rb` | MCP::Server builder, TOOL_CLASSES array, governance-aware build |
78
+ | `lib/legion/mcp/server.rb` | MCP::Server builder, governance-aware build; reads tools from Tools::Registry |
69
79
  | `lib/legion/mcp/auth.rb` | JWT + API key authentication |
70
80
  | `lib/legion/mcp/tool_governance.rb` | Risk-tier tool filtering + invocation audit |
71
81
  | `lib/legion/mcp/context_compiler.rb` | Keyword + semantic tool matching (60/40 blend) |
72
- | `lib/legion/mcp/embedding_index.rb` | In-memory vector cache for semantic matching |
82
+ | `lib/legion/mcp/embedding_index.rb` | Semantic tool matching; delegates persistence to Legion::Tools::EmbeddingCache |
73
83
  | `lib/legion/mcp/observer.rb` | Instrumentation: counters, ring buffer, pattern promotion |
74
84
  | `lib/legion/mcp/usage_filter.rb` | Frequency/recency/keyword scoring for dynamic tool filtering |
75
85
  | `lib/legion/mcp/pattern_store.rb` | 4-layer degrading storage (L0/L1/L2) with thread-safe access |
76
86
  | `lib/legion/mcp/tier_router.rb` | Confidence-gated tier selection, tool chain execution |
77
87
  | `lib/legion/mcp/context_guard.rb` | Staleness, rapid-fire, anomaly detection |
78
- | `lib/legion/mcp/tools/` | 59 MCP::Tool subclasses (legion.* namespace) |
88
+ | `lib/legion/mcp/tool_adapter.rb` | MCP::ToolAdapter wraps Legion::Tools::Base for MCP SDK (McpToolAdapter kept as alias) |
89
+ | `lib/legion/mcp/deferred_registry.rb` | DeferredRegistry — reads deferred tools from Legion::Tools::Registry at request time |
90
+ | `lib/legion/mcp/tools/` | All tool implementations; only MCP_SPECIFIC_TOOLS (6 tools) registered in Server.tool_registry — extension tools sourced via Legion::Tools::Discovery |
79
91
  | `lib/legion/mcp/tools/do_action.rb` | Natural language intent routing with Tier 0 fast path |
80
92
  | `lib/legion/mcp/tools/discover_tools.rb` | Dynamic tool discovery with context |
81
93
  | `lib/legion/mcp/tools/run_task.rb` | Execute runner function via dot notation |
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'logging_support'
4
+ require_relative 'tool_adapter'
4
5
 
5
6
  module Legion
6
7
  module MCP
@@ -113,35 +114,16 @@ module Legion
113
114
  end
114
115
 
115
116
  def generate_tools_from_catalog
116
- return [] unless defined?(Legion::Extensions::Catalog::Registry)
117
- return [] unless Legion::Extensions::Catalog::Registry.respond_to?(:for_mcp)
117
+ return [] unless defined?(Legion::Tools::Registry)
118
+ return [] unless Legion::Tools::Registry.respond_to?(:all_tools)
118
119
 
119
- Legion::Extensions::Catalog::Registry.for_mcp.filter_map do |cap|
120
- raw_name = cap.respond_to?(:mcp_name) ? cap.mcp_name : "legion.catalog.#{cap.function}"
121
- build_tool_class(
122
- runner_class: resolve_runner_class(cap),
123
- function: cap.function,
124
- tool_name: sanitize_tool_name(raw_name),
125
- description: cap.respond_to?(:description) ? cap.description : "Auto-generated: #{cap.function}",
126
- input_schema: cap.respond_to?(:input_schema) ? cap.input_schema : { properties: {} },
127
- category: cap.respond_to?(:category) ? cap.category : nil,
128
- tier: cap.respond_to?(:tier) ? cap.tier : nil
129
- )
120
+ Legion::Tools::Registry.all_tools.filter_map do |tool_class|
121
+ ToolAdapter.from_legion_tool(tool_class) if defined?(ToolAdapter) && ToolAdapter.respond_to?(:from_legion_tool)
130
122
  rescue StandardError => e
131
123
  handle_exception(e, level: :debug, operation: 'legion.mcp.catalog_dispatcher.generate_tools_from_catalog')
132
- log.debug("CatalogDispatcher: skipping #{cap}: #{e.message}")
133
124
  nil
134
125
  end
135
126
  end
136
-
137
- def resolve_runner_class(cap)
138
- segments = cap.extension.delete_prefix('lex-').split('-')
139
- (%w[Legion Extensions] + segments.map(&:capitalize) + ['Runners', cap.runner]).join('::')
140
- end
141
-
142
- def sanitize_tool_name(name)
143
- name.gsub(/[^A-Za-z0-9_.-]/, '')
144
- end
145
127
  end
146
128
  end
147
129
  end
@@ -23,14 +23,25 @@ module Legion
23
23
 
24
24
  module_function
25
25
 
26
+ def reset_cache!
27
+ @always_loaded_cache = nil
28
+ end
29
+
26
30
  def enabled?
27
31
  setting = Legion::Settings.dig(:mcp, :deferred_loading, :enabled)
28
32
  setting.nil? || setting
29
33
  end
30
34
 
31
35
  def always_loaded_tools
36
+ return @always_loaded_cache if @always_loaded_cache
37
+
38
+ base = ALWAYS_LOADED.dup
39
+ if defined?(Legion::Tools::Registry) && Legion::Tools::Registry.respond_to?(:tools)
40
+ registry_always = Legion::Tools::Registry.tools(:always)
41
+ base |= registry_always.map(&:tool_name) if registry_always.is_a?(Array)
42
+ end
32
43
  custom = Legion::Settings.dig(:mcp, :deferred_loading, :always_loaded)
33
- custom.is_a?(Array) ? (ALWAYS_LOADED | custom) : ALWAYS_LOADED
44
+ @always_loaded_cache = custom.is_a?(Array) ? (base | custom) : base
34
45
  end
35
46
 
36
47
  def deferred?(tool_class)
@@ -10,9 +10,25 @@ module Legion
10
10
  def build_from_tool_data(tool_data, embedder: default_embedder)
11
11
  @embedder = embedder
12
12
  mutex.synchronize do
13
- tool_data.each do |tool|
14
- composite = build_composite(tool[:name], tool[:description], tool[:params])
13
+ composites = tool_data.to_h do |tool|
14
+ [tool[:name], build_composite(tool[:name], tool[:description], tool[:params])]
15
+ end
16
+
17
+ cached_vectors = bulk_cache_lookup(composites.values)
18
+
19
+ uncached_names = composites.keys.reject { |name| cached_vectors.key?(composites[name]) }
20
+ newly_embedded = {}
21
+ uncached_names.each do |name|
22
+ composite = composites[name]
15
23
  vector = safe_embed(composite, embedder)
24
+ newly_embedded[composite] = vector if vector
25
+ end
26
+
27
+ bulk_cache_store(newly_embedded) unless newly_embedded.empty?
28
+
29
+ tool_data.each do |tool|
30
+ composite = composites[tool[:name]]
31
+ vector = cached_vectors[composite] || newly_embedded[composite]
16
32
  next unless vector
17
33
 
18
34
  index[tool[:name]] = {
@@ -86,6 +102,26 @@ module Legion
86
102
  @mutex ||= Mutex.new
87
103
  end
88
104
 
105
+ def bulk_cache_lookup(composite_texts)
106
+ return {} unless defined?(Legion::Tools::EmbeddingCache) &&
107
+ Legion::Tools::EmbeddingCache.respond_to?(:bulk_lookup)
108
+
109
+ Legion::Tools::EmbeddingCache.bulk_lookup(composite_texts)
110
+ rescue StandardError => e
111
+ handle_exception(e, level: :debug, operation: 'legion.mcp.embedding_index.bulk_cache_lookup')
112
+ {}
113
+ end
114
+
115
+ def bulk_cache_store(composite_to_vector)
116
+ return unless defined?(Legion::Tools::EmbeddingCache) &&
117
+ Legion::Tools::EmbeddingCache.respond_to?(:bulk_store)
118
+
119
+ Legion::Tools::EmbeddingCache.bulk_store(composite_to_vector)
120
+ rescue StandardError => e
121
+ handle_exception(e, level: :debug, operation: 'legion.mcp.embedding_index.bulk_cache_store')
122
+ nil
123
+ end
124
+
89
125
  def build_composite(name, description, params)
90
126
  parts = [name, '--', description]
91
127
  parts << "Params: #{params.join(', ')}" unless params.empty?
@@ -7,7 +7,16 @@ module Legion
7
7
 
8
8
  module_function
9
9
 
10
- def discover_and_register
10
+ def discover_and_register # rubocop:disable Metrics/PerceivedComplexity
11
+ return if @discovery_fired
12
+
13
+ @discovery_fired = true
14
+
15
+ if defined?(Legion::Tools::Discovery) && Legion::Tools::Discovery.respond_to?(:discover_and_register)
16
+ Legion::Tools::Discovery.discover_and_register
17
+ return
18
+ end
19
+
11
20
  return unless defined?(Legion::Extensions)
12
21
 
13
22
  extensions =
@@ -26,6 +35,10 @@ module Legion
26
35
  end
27
36
  end
28
37
 
38
+ def reset_discovery!
39
+ @discovery_fired = false
40
+ end
41
+
29
42
  def build_tools_from_runner(runner_module)
30
43
  return unless runner_module.respond_to?(:settings) && runner_module.settings.is_a?(Hash)
31
44
 
@@ -3,164 +3,61 @@
3
3
  require_relative 'observer'
4
4
  require_relative 'logging_support'
5
5
  require_relative 'usage_filter'
6
- require_relative 'tools/run_task'
7
- require_relative 'tools/describe_runner'
8
- require_relative 'tools/list_tasks'
9
- require_relative 'tools/get_task'
10
- require_relative 'tools/delete_task'
11
- require_relative 'tools/get_task_logs'
12
- require_relative 'tools/list_chains'
13
- require_relative 'tools/create_chain'
14
- require_relative 'tools/update_chain'
15
- require_relative 'tools/delete_chain'
16
- require_relative 'tools/list_relationships'
17
- require_relative 'tools/create_relationship'
18
- require_relative 'tools/update_relationship'
19
- require_relative 'tools/delete_relationship'
20
- require_relative 'tools/list_extensions'
21
- require_relative 'tools/get_extension'
22
- require_relative 'tools/enable_extension'
23
- require_relative 'tools/disable_extension'
24
- require_relative 'tools/list_schedules'
25
- require_relative 'tools/create_schedule'
26
- require_relative 'tools/update_schedule'
27
- require_relative 'tools/delete_schedule'
28
- require_relative 'tools/get_status'
29
- require_relative 'tools/get_config'
30
- require_relative 'tools/list_workers'
31
- require_relative 'tools/show_worker'
32
- require_relative 'tools/worker_lifecycle'
33
- require_relative 'tools/worker_costs'
34
- require_relative 'tools/team_summary'
35
- require_relative 'tools/routing_stats'
36
- require_relative 'tools/rbac_check'
37
- require_relative 'tools/rbac_assignments'
38
- require_relative 'tools/rbac_grants'
39
- require_relative 'tools/prompt_list'
40
- require_relative 'tools/prompt_show'
41
- require_relative 'tools/prompt_run'
42
- require_relative 'tools/dataset_list'
43
- require_relative 'tools/dataset_show'
44
- require_relative 'tools/experiment_results'
45
- require_relative 'tools/eval_list'
46
- require_relative 'tools/eval_run'
47
- require_relative 'tools/eval_results'
6
+ require_relative 'tools_loader'
7
+ require_relative 'tool_adapter'
48
8
  require_relative 'context_compiler'
49
9
  require_relative 'embedding_index'
50
10
  require_relative 'cold_start'
51
11
  require_relative 'gap_detector'
52
12
  require_relative 'function_discovery'
53
13
  require_relative 'self_generate'
54
- require_relative 'tools/do_action'
55
- require_relative 'tools/plan_action'
56
- require_relative 'tools/discover_tools'
57
- require_relative 'tools/ask_peer'
58
- require_relative 'tools/list_peers'
59
- require_relative 'tools/notify_peer'
60
- require_relative 'tools/broadcast_peers'
61
- require_relative 'tools/mesh_status'
62
- require_relative 'tools/mind_growth_status'
63
- require_relative 'tools/mind_growth_propose'
64
- require_relative 'tools/mind_growth_approve'
65
- require_relative 'tools/mind_growth_build_queue'
66
- require_relative 'tools/mind_growth_cognitive_profile'
67
- require_relative 'tools/mind_growth_health'
68
- require_relative 'tools/query_knowledge'
69
- require_relative 'tools/knowledge_health'
70
- require_relative 'tools/knowledge_context'
71
- require_relative 'tools/absorb'
72
- require_relative 'tools/structural_index'
73
- require_relative 'tools/tool_audit'
74
- require_relative 'tools/state_diff'
75
- require_relative 'tools/search_sessions'
76
14
  require_relative 'structural_index'
77
15
  require_relative 'state_tracker'
78
16
  require_relative 'tool_quality'
79
17
  require_relative 'deferred_registry'
80
18
  require_relative 'catalog_dispatcher'
81
19
  require_relative 'dynamic_injector'
82
- require_relative 'catalog_bridge'
83
20
  require_relative 'resources/runner_catalog'
84
21
  require_relative 'resources/extension_info'
85
22
 
86
23
  module Legion
87
24
  module MCP
88
25
  module Server
89
- STATIC_TOOLS = [
90
- Tools::RunTask,
91
- Tools::DescribeRunner,
92
- Tools::ListTasks,
93
- Tools::GetTask,
94
- Tools::DeleteTask,
95
- Tools::GetTaskLogs,
96
- Tools::ListChains,
97
- Tools::CreateChain,
98
- Tools::UpdateChain,
99
- Tools::DeleteChain,
100
- Tools::ListRelationships,
101
- Tools::CreateRelationship,
102
- Tools::UpdateRelationship,
103
- Tools::DeleteRelationship,
104
- Tools::ListExtensions,
105
- Tools::GetExtension,
106
- Tools::EnableExtension,
107
- Tools::DisableExtension,
108
- Tools::ListSchedules,
109
- Tools::CreateSchedule,
110
- Tools::UpdateSchedule,
111
- Tools::DeleteSchedule,
112
- Tools::GetStatus,
113
- Tools::GetConfig,
114
- Tools::ListWorkers,
115
- Tools::ShowWorker,
116
- Tools::WorkerLifecycle,
117
- Tools::WorkerCosts,
118
- Tools::TeamSummary,
119
- Tools::RoutingStats,
120
- Tools::RbacCheck,
121
- Tools::RbacAssignments,
122
- Tools::RbacGrants,
123
- Tools::PromptList,
124
- Tools::PromptShow,
125
- Tools::PromptRun,
126
- Tools::DatasetList,
127
- Tools::DatasetShow,
128
- Tools::ExperimentResults,
129
- Tools::EvalList,
130
- Tools::EvalRun,
131
- Tools::EvalResults,
132
- Tools::DoAction,
26
+ # MCP-specific tools not owned by any extension.
27
+ # All extension-owned tools are discovered via Legion::Tools::Registry.
28
+ MCP_SPECIFIC_TOOLS = [
133
29
  Tools::PlanAction,
134
30
  Tools::DiscoverTools,
135
- Tools::AskPeer,
136
- Tools::ListPeers,
137
- Tools::NotifyPeer,
138
- Tools::BroadcastPeers,
139
- Tools::MeshStatus,
140
- Tools::MindGrowthStatus,
141
- Tools::MindGrowthPropose,
142
- Tools::MindGrowthApprove,
143
- Tools::MindGrowthBuildQueue,
144
- Tools::MindGrowthCognitiveProfile,
145
- Tools::MindGrowthHealth,
146
- Tools::QueryKnowledge,
147
- Tools::KnowledgeHealth,
148
- Tools::KnowledgeContext,
149
- Tools::Absorb,
150
31
  Tools::StructuralIndexTool,
151
32
  Tools::ToolAudit,
152
33
  Tools::StateDiff,
153
34
  Tools::SearchSessions
154
35
  ].freeze
155
36
 
156
- @tool_registry = Concurrent::Array.new(STATIC_TOOLS)
37
+ @tool_registry = Concurrent::Array.new(MCP_SPECIFIC_TOOLS)
157
38
  @tool_registry_lock = Mutex.new
158
39
 
159
40
  class << self # rubocop:disable Metrics/ClassLength
160
- include CatalogBridge
161
-
162
41
  attr_reader :tool_registry
163
42
 
43
+ def rebuild_tool_registry
44
+ @tool_registry_lock.synchronize do
45
+ @tool_registry = Concurrent::Array.new(MCP_SPECIFIC_TOOLS)
46
+
47
+ if defined?(Legion::Tools::Registry) && Legion::Tools::Registry.respond_to?(:all_tools)
48
+ Legion::Tools::Registry.all_tools.each do |legion_tool_class|
49
+ next if @tool_registry.any? { |tc| tc.tool_name == legion_tool_class.tool_name }
50
+
51
+ adapted = ToolAdapter.from_legion_tool(legion_tool_class)
52
+ @tool_registry << adapted
53
+ end
54
+ end
55
+
56
+ DeferredRegistry.reset_cache! if defined?(DeferredRegistry) && DeferredRegistry.respond_to?(:reset_cache!)
57
+ reset_caches!
58
+ end
59
+ end
60
+
164
61
  def register_tool(tool_class)
165
62
  @tool_registry_lock.synchronize do
166
63
  return if tool_registry.any? { |tc| tc.tool_name == tool_class.tool_name }
@@ -192,7 +89,10 @@ module Legion
192
89
  EmbeddingIndex.reset! if defined?(EmbeddingIndex) && EmbeddingIndex.respond_to?(:reset!)
193
90
  end
194
91
 
195
- def build(identity: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
92
+ def build(identity: nil) # rubocop:disable Metrics/MethodLength
93
+ run_function_discovery
94
+ rebuild_tool_registry
95
+
196
96
  LoggingSupport.info(
197
97
  'server.build.start',
198
98
  identity: LoggingSupport.summarize_identity(identity),
@@ -224,14 +124,11 @@ module Legion
224
124
 
225
125
  PatternStore.hydrate_from_l2 if defined?(PatternStore)
226
126
  ColdStart.load_community_patterns if defined?(ColdStart)
227
- FunctionDiscovery.discover_and_register if defined?(Legion::Extensions)
228
- register_catalog_tools
229
127
  populate_embedding_index
230
128
 
231
129
  Resources::RunnerCatalog.register(server)
232
130
  Resources::ExtensionInfo.register_read_handler(server)
233
131
 
234
- register_catalog_listener
235
132
  hydrate_override_confidence
236
133
 
237
134
  LoggingSupport.info(
@@ -309,6 +206,19 @@ module Legion
309
206
 
310
207
  private
311
208
 
209
+ def run_function_discovery
210
+ FunctionDiscovery.reset_discovery!
211
+ FunctionDiscovery.discover_and_register
212
+ end
213
+
214
+ def hydrate_override_confidence
215
+ return unless defined?(Legion::LLM::OverrideConfidence)
216
+ return unless Legion::LLM::OverrideConfidence.respond_to?(:hydrate_from_l2)
217
+
218
+ Legion::LLM::OverrideConfidence.hydrate_from_l2
219
+ Legion::LLM::OverrideConfidence.hydrate_from_apollo if Legion::LLM::OverrideConfidence.respond_to?(:hydrate_from_apollo)
220
+ end
221
+
312
222
  def install_deferred_tools_list_handler(server)
313
223
  handlers = server.instance_variable_get(:@handlers)
314
224
  return unless handlers
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ class ToolAdapter < ::MCP::Tool
6
+ class << self
7
+ MCP_NAME_PATTERN = /[^a-zA-Z0-9_-]/
8
+
9
+ def sanitize_tool_name(name)
10
+ name.to_s.gsub(MCP_NAME_PATTERN, '_').slice(0, 64)
11
+ end
12
+
13
+ def from_legion_tool(tool_class)
14
+ safe_name = sanitize_tool_name(tool_class.tool_name)
15
+ Class.new(::MCP::Tool) do
16
+ tool_name safe_name
17
+ description tool_class.description
18
+ input_schema(tool_class.input_schema || { properties: {} })
19
+
20
+ define_singleton_method(:legion_tool_class) { tool_class }
21
+
22
+ define_singleton_method(:call) do |**params|
23
+ result = tool_class.call(**params)
24
+ if result.is_a?(Hash) && result[:content]
25
+ content = result[:content].map do |c|
26
+ { type: c[:type] || 'text', text: c[:text] || '' }
27
+ end
28
+ ::MCP::Tool::Response.new(content, error: result[:error] || false)
29
+ else
30
+ text = result.is_a?(String) ? result : Legion::JSON.dump(result)
31
+ error = result.is_a?(Hash) ? !!result[:error] : false
32
+ ::MCP::Tool::Response.new([{ type: 'text', text: text }], error: error)
33
+ end
34
+ rescue StandardError => e
35
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: e.message }) }], error: true)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Requires all built-in MCP tool files so they are defined as Ruby constants.
4
+ # server.rb no longer owns these requires — Legion::Tools::Registry provides
5
+ # runtime registration, but the classes must still be defined for specs and
6
+ # for any code that references Legion::MCP::Tools::* directly.
7
+
8
+ require_relative 'tools/run_task'
9
+ require_relative 'tools/describe_runner'
10
+ require_relative 'tools/list_tasks'
11
+ require_relative 'tools/get_task'
12
+ require_relative 'tools/delete_task'
13
+ require_relative 'tools/get_task_logs'
14
+ require_relative 'tools/list_chains'
15
+ require_relative 'tools/create_chain'
16
+ require_relative 'tools/update_chain'
17
+ require_relative 'tools/delete_chain'
18
+ require_relative 'tools/list_relationships'
19
+ require_relative 'tools/create_relationship'
20
+ require_relative 'tools/update_relationship'
21
+ require_relative 'tools/delete_relationship'
22
+ require_relative 'tools/list_extensions'
23
+ require_relative 'tools/get_extension'
24
+ require_relative 'tools/enable_extension'
25
+ require_relative 'tools/disable_extension'
26
+ require_relative 'tools/list_schedules'
27
+ require_relative 'tools/create_schedule'
28
+ require_relative 'tools/update_schedule'
29
+ require_relative 'tools/delete_schedule'
30
+ require_relative 'tools/get_status'
31
+ require_relative 'tools/get_config'
32
+ require_relative 'tools/list_workers'
33
+ require_relative 'tools/show_worker'
34
+ require_relative 'tools/worker_lifecycle'
35
+ require_relative 'tools/worker_costs'
36
+ require_relative 'tools/team_summary'
37
+ require_relative 'tools/routing_stats'
38
+ require_relative 'tools/rbac_check'
39
+ require_relative 'tools/rbac_assignments'
40
+ require_relative 'tools/rbac_grants'
41
+ require_relative 'tools/prompt_list'
42
+ require_relative 'tools/prompt_show'
43
+ require_relative 'tools/prompt_run'
44
+ require_relative 'tools/dataset_list'
45
+ require_relative 'tools/dataset_show'
46
+ require_relative 'tools/experiment_results'
47
+ require_relative 'tools/eval_list'
48
+ require_relative 'tools/eval_run'
49
+ require_relative 'tools/eval_results'
50
+ require_relative 'tools/do_action'
51
+ require_relative 'tools/plan_action'
52
+ require_relative 'tools/discover_tools'
53
+ require_relative 'tools/ask_peer'
54
+ require_relative 'tools/list_peers'
55
+ require_relative 'tools/notify_peer'
56
+ require_relative 'tools/broadcast_peers'
57
+ require_relative 'tools/mesh_status'
58
+ require_relative 'tools/mind_growth_status'
59
+ require_relative 'tools/mind_growth_propose'
60
+ require_relative 'tools/mind_growth_approve'
61
+ require_relative 'tools/mind_growth_build_queue'
62
+ require_relative 'tools/mind_growth_cognitive_profile'
63
+ require_relative 'tools/mind_growth_health'
64
+ require_relative 'tools/query_knowledge'
65
+ require_relative 'tools/knowledge_health'
66
+ require_relative 'tools/knowledge_context'
67
+ require_relative 'tools/absorb'
68
+ require_relative 'tools/structural_index'
69
+ require_relative 'tools/tool_audit'
70
+ require_relative 'tools/state_diff'
71
+ require_relative 'tools/search_sessions'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module MCP
5
- VERSION = '0.7.2'
5
+ VERSION = '0.7.4'
6
6
  end
7
7
  end
data/lib/legion/mcp.rb CHANGED
@@ -9,6 +9,7 @@ require_relative 'mcp/version'
9
9
  require_relative 'mcp/settings'
10
10
  require_relative 'mcp/auth'
11
11
  require_relative 'mcp/tool_governance'
12
+ require_relative 'mcp/tools_loader'
12
13
  require_relative 'mcp/server'
13
14
  require_relative 'mcp/override_broadcast'
14
15
  require_relative 'mcp/client'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -122,7 +122,6 @@ files:
122
122
  - lib/legion/mcp.rb
123
123
  - lib/legion/mcp/actors/self_generate_cycle.rb
124
124
  - lib/legion/mcp/auth.rb
125
- - lib/legion/mcp/catalog_bridge.rb
126
125
  - lib/legion/mcp/catalog_dispatcher.rb
127
126
  - lib/legion/mcp/client.rb
128
127
  - lib/legion/mcp/client/connection.rb
@@ -152,6 +151,7 @@ files:
152
151
  - lib/legion/mcp/state_tracker.rb
153
152
  - lib/legion/mcp/structural_index.rb
154
153
  - lib/legion/mcp/tier_router.rb
154
+ - lib/legion/mcp/tool_adapter.rb
155
155
  - lib/legion/mcp/tool_governance.rb
156
156
  - lib/legion/mcp/tool_quality.rb
157
157
  - lib/legion/mcp/tools/absorb.rb
@@ -218,6 +218,7 @@ files:
218
218
  - lib/legion/mcp/tools/update_schedule.rb
219
219
  - lib/legion/mcp/tools/worker_costs.rb
220
220
  - lib/legion/mcp/tools/worker_lifecycle.rb
221
+ - lib/legion/mcp/tools_loader.rb
221
222
  - lib/legion/mcp/usage_filter.rb
222
223
  - lib/legion/mcp/version.rb
223
224
  homepage: https://github.com/LegionIO/legion-mcp
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module MCP
5
- module CatalogBridge
6
- include Legion::Logging::Helper
7
-
8
- def hydrate_override_confidence
9
- return unless defined?(Legion::LLM::OverrideConfidence)
10
- return unless Legion::LLM::OverrideConfidence.respond_to?(:hydrate_from_l2)
11
-
12
- Legion::LLM::OverrideConfidence.hydrate_from_l2
13
- Legion::LLM::OverrideConfidence.hydrate_from_apollo if Legion::LLM::OverrideConfidence.respond_to?(:hydrate_from_apollo)
14
- end
15
-
16
- def register_catalog_listener
17
- return unless defined?(Legion::Extensions::Catalog::Registry)
18
- return unless Legion::Extensions::Catalog::Registry.respond_to?(:on_change)
19
-
20
- Legion::Extensions::Catalog::Registry.on_change { Legion::MCP.reset! }
21
- end
22
-
23
- def dispatch_catalog_tool(tool_name, arguments)
24
- log.info('Starting legion.mcp.catalog_bridge.dispatch_catalog_tool')
25
- return nil unless defined?(Legion::Extensions::Catalog::Registry)
26
-
27
- cap = Legion::Extensions::Catalog::Registry.find_by_mcp_name(tool_name)
28
- return nil unless cap
29
-
30
- segments = cap.extension.delete_prefix('lex-').split('-')
31
- runner_path = (%w[Legion Extensions] + segments.map(&:capitalize) + ['Runners', cap.runner]).join('::')
32
- runner = Kernel.const_get(runner_path)
33
- fn = cap.function.to_sym
34
- result = runner.send(fn, **(arguments || {}).transform_keys(&:to_sym))
35
- { status: :success, result: result, source: :catalog }
36
- rescue NameError => e
37
- handle_exception(e, level: :warn, operation: 'legion.mcp.catalog_bridge.dispatch_catalog_tool')
38
- log.warn("Catalog dispatch failed: #{e.message}")
39
- nil
40
- rescue StandardError => e
41
- handle_exception(e, level: :error, operation: 'legion.mcp.catalog_bridge.dispatch_catalog_tool')
42
- { status: :error, error: e.message, source: :catalog }
43
- end
44
-
45
- def register_catalog_tools
46
- log.info('Starting legion.mcp.catalog_bridge.register_catalog_tools')
47
- CatalogDispatcher.generate_tools_from_catalog.each { |tc| Server.register_tool(tc) }
48
- end
49
-
50
- def dynamic_tool_list
51
- static = Server.tool_registry.map do |klass|
52
- { name: klass.tool_name, description: klass.description,
53
- input_schema: klass.input_schema, source: :builtin, klass: klass }
54
- end
55
-
56
- dynamic = if defined?(Legion::Extensions::Catalog::Registry)
57
- Legion::Extensions::Catalog::Registry.for_mcp.map(&:to_mcp_tool)
58
- else
59
- []
60
- end
61
-
62
- static + dynamic
63
- end
64
- end
65
- end
66
- end