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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +10 -1
- data/CLAUDE.md +19 -7
- data/lib/legion/mcp/catalog_dispatcher.rb +5 -23
- data/lib/legion/mcp/deferred_registry.rb +12 -1
- data/lib/legion/mcp/embedding_index.rb +38 -2
- data/lib/legion/mcp/function_discovery.rb +14 -1
- data/lib/legion/mcp/server.rb +41 -131
- data/lib/legion/mcp/tool_adapter.rb +42 -0
- data/lib/legion/mcp/tools_loader.rb +71 -0
- data/lib/legion/mcp/version.rb +1 -1
- data/lib/legion/mcp.rb +1 -0
- metadata +3 -2
- data/lib/legion/mcp/catalog_bridge.rb +0 -66
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f74d80bf3fc37b0b0130f77f38d7b1611c3efa6ab193085022d3aa1462e3ad4
|
|
4
|
+
data.tar.gz: a9ca6d64d2f3c089a787258e4ac88855d9f5d149c22eef1863bfa429157b70ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd0bf2a74ac997e5ce7db2bee56a6e9d821f6244ba211c41be054700bfeb7037c557079b6616403a4f188193133d2b2087f496a0ea7be43eec96694e6feed9b9
|
|
7
|
+
data.tar.gz: af2d74fa1eeb888753a815af65e9a4527fc00d44dfdf4941abc9955f1da539606f3284413518b0b618bcd50c71894ef5f37750154cbf3f35ae224390a89b4204
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# legion-mcp Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
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.
|
|
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,
|
|
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 #
|
|
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
|
-
├──
|
|
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,
|
|
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` |
|
|
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/
|
|
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::
|
|
117
|
-
return [] unless Legion::
|
|
117
|
+
return [] unless defined?(Legion::Tools::Registry)
|
|
118
|
+
return [] unless Legion::Tools::Registry.respond_to?(:all_tools)
|
|
118
119
|
|
|
119
|
-
Legion::
|
|
120
|
-
|
|
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) ? (
|
|
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.
|
|
14
|
-
|
|
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
|
|
data/lib/legion/mcp/server.rb
CHANGED
|
@@ -3,164 +3,61 @@
|
|
|
3
3
|
require_relative 'observer'
|
|
4
4
|
require_relative 'logging_support'
|
|
5
5
|
require_relative 'usage_filter'
|
|
6
|
-
require_relative '
|
|
7
|
-
require_relative '
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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(
|
|
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/
|
|
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'
|
data/lib/legion/mcp/version.rb
CHANGED
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.
|
|
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
|