legion-mcp 0.7.0 → 0.7.1
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/CHANGELOG.md +13 -0
- data/legion-mcp.gemspec +1 -1
- data/lib/legion/mcp/actors/self_generate_cycle.rb +4 -10
- data/lib/legion/mcp/auth.rb +5 -1
- data/lib/legion/mcp/catalog_bridge.rb +7 -1
- data/lib/legion/mcp/catalog_dispatcher.rb +60 -9
- data/lib/legion/mcp/client/connection.rb +62 -3
- data/lib/legion/mcp/client/pool.rb +9 -2
- data/lib/legion/mcp/client.rb +10 -2
- data/lib/legion/mcp/cold_start.rb +7 -2
- data/lib/legion/mcp/context_guard.rb +4 -1
- data/lib/legion/mcp/dynamic_injector.rb +4 -1
- data/lib/legion/mcp/embedding_index.rb +6 -2
- data/lib/legion/mcp/function_discovery.rb +5 -1
- data/lib/legion/mcp/logging_support.rb +159 -0
- data/lib/legion/mcp/observer.rb +10 -4
- data/lib/legion/mcp/override_broadcast.rb +14 -4
- data/lib/legion/mcp/pattern_compiler.rb +4 -1
- data/lib/legion/mcp/pattern_exchange.rb +5 -1
- data/lib/legion/mcp/pattern_gossip.rb +8 -2
- data/lib/legion/mcp/pattern_store.rb +125 -18
- data/lib/legion/mcp/resources/extension_info.rb +7 -2
- data/lib/legion/mcp/resources/runner_catalog.rb +7 -2
- data/lib/legion/mcp/self_generate.rb +7 -1
- data/lib/legion/mcp/server.rb +53 -5
- data/lib/legion/mcp/state_tracker.rb +12 -4
- data/lib/legion/mcp/structural_index.rb +6 -2
- data/lib/legion/mcp/tier_router.rb +135 -23
- data/lib/legion/mcp/tools/absorb.rb +5 -1
- data/lib/legion/mcp/tools/ask_peer.rb +5 -1
- data/lib/legion/mcp/tools/broadcast_peers.rb +5 -1
- data/lib/legion/mcp/tools/create_chain.rb +7 -2
- data/lib/legion/mcp/tools/create_relationship.rb +7 -2
- data/lib/legion/mcp/tools/create_schedule.rb +7 -2
- data/lib/legion/mcp/tools/dataset_list.rb +7 -2
- data/lib/legion/mcp/tools/dataset_show.rb +7 -2
- data/lib/legion/mcp/tools/delete_chain.rb +7 -2
- data/lib/legion/mcp/tools/delete_relationship.rb +7 -2
- data/lib/legion/mcp/tools/delete_schedule.rb +7 -2
- data/lib/legion/mcp/tools/delete_task.rb +7 -2
- data/lib/legion/mcp/tools/describe_runner.rb +7 -2
- data/lib/legion/mcp/tools/disable_extension.rb +7 -2
- data/lib/legion/mcp/tools/discover_tools.rb +5 -1
- data/lib/legion/mcp/tools/do_action.rb +141 -25
- data/lib/legion/mcp/tools/enable_extension.rb +7 -2
- data/lib/legion/mcp/tools/eval_list.rb +7 -2
- data/lib/legion/mcp/tools/eval_results.rb +9 -3
- data/lib/legion/mcp/tools/eval_run.rb +7 -2
- data/lib/legion/mcp/tools/experiment_results.rb +9 -3
- data/lib/legion/mcp/tools/get_config.rb +5 -1
- data/lib/legion/mcp/tools/get_extension.rb +7 -2
- data/lib/legion/mcp/tools/get_status.rb +11 -4
- data/lib/legion/mcp/tools/get_task.rb +7 -2
- data/lib/legion/mcp/tools/get_task_logs.rb +7 -2
- data/lib/legion/mcp/tools/knowledge_context.rb +6 -2
- data/lib/legion/mcp/tools/knowledge_health.rb +7 -2
- data/lib/legion/mcp/tools/list_chains.rb +7 -2
- data/lib/legion/mcp/tools/list_extensions.rb +7 -2
- data/lib/legion/mcp/tools/list_peers.rb +5 -1
- data/lib/legion/mcp/tools/list_relationships.rb +7 -2
- data/lib/legion/mcp/tools/list_schedules.rb +7 -2
- data/lib/legion/mcp/tools/list_tasks.rb +7 -2
- data/lib/legion/mcp/tools/list_workers.rb +7 -2
- data/lib/legion/mcp/tools/mesh_status.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_approve.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_build_queue.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_cognitive_profile.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_health.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_propose.rb +5 -1
- data/lib/legion/mcp/tools/mind_growth_status.rb +5 -1
- data/lib/legion/mcp/tools/notify_peer.rb +5 -1
- data/lib/legion/mcp/tools/plan_action.rb +7 -2
- data/lib/legion/mcp/tools/prompt_list.rb +7 -2
- data/lib/legion/mcp/tools/prompt_run.rb +7 -2
- data/lib/legion/mcp/tools/prompt_show.rb +7 -2
- data/lib/legion/mcp/tools/query_knowledge.rb +5 -1
- data/lib/legion/mcp/tools/rbac_assignments.rb +5 -1
- data/lib/legion/mcp/tools/rbac_check.rb +5 -1
- data/lib/legion/mcp/tools/rbac_grants.rb +5 -1
- data/lib/legion/mcp/tools/routing_stats.rb +7 -2
- data/lib/legion/mcp/tools/run_task.rb +7 -2
- data/lib/legion/mcp/tools/search_sessions.rb +7 -2
- data/lib/legion/mcp/tools/show_worker.rb +7 -2
- data/lib/legion/mcp/tools/state_diff.rb +5 -1
- data/lib/legion/mcp/tools/structural_index.rb +5 -1
- data/lib/legion/mcp/tools/team_summary.rb +7 -2
- data/lib/legion/mcp/tools/tool_audit.rb +5 -1
- data/lib/legion/mcp/tools/update_chain.rb +7 -2
- data/lib/legion/mcp/tools/update_relationship.rb +7 -2
- data/lib/legion/mcp/tools/update_schedule.rb +7 -2
- data/lib/legion/mcp/tools/worker_costs.rb +7 -2
- data/lib/legion/mcp/tools/worker_lifecycle.rb +9 -3
- data/lib/legion/mcp/version.rb +1 -1
- data/lib/legion/mcp.rb +13 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30a63f2232747773e34419533d0b0ef00b6c10c396885a976b322702281f5062
|
|
4
|
+
data.tar.gz: f7191a38ce628ffc0c61a46010ce8c7f6ac4188c213c5462de449c9a118fe0c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1bf54172fb099fb382a8d4921990b47ad005d545163656de251c2e025bc28dbcebc789752009298dc8cab87a539651b0ec56a440e1209d27cb619c66ea95e8dd
|
|
7
|
+
data.tar.gz: 246f2c244d3e4429017e5ccdf339d2b0a00033a8d49df8f652ab8ca52c7ed9b0e1f1d6eb1cf765971d7ab3cebd7f410ebb8733e42f840bad931b2e4397c55933
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.1] - 2026-04-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `LoggingSupport` helper for structured MCP event logging with request, parameter, and result summarization
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Uplifted non-Sinatra `lib/**/*.rb` logging paths to use `Legion::Logging::Helper` instead of direct `Legion::Logging.*` calls
|
|
12
|
+
- Added `handle_exception` coverage across MCP tool, resource, router, client, observer, and pattern-store rescue paths
|
|
13
|
+
- Added `info`-level tracing for MCP tool/resource entrypoints and key client, routing, and pattern lifecycle actions
|
|
14
|
+
- Removed the custom fallback logger from `Actor::SelfGenerateCycle` in favor of the shared logging helper
|
|
15
|
+
- Added explicit `require 'legion/logging'` at the main MCP entrypoint
|
|
16
|
+
- Raised the minimum `legion-logging` dependency to `>= 1.4.3` for helper support
|
|
17
|
+
|
|
5
18
|
## [0.7.0] - 2026-03-31
|
|
6
19
|
|
|
7
20
|
### Added
|
data/legion-mcp.gemspec
CHANGED
|
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.add_dependency 'concurrent-ruby', '>= 1.2'
|
|
30
30
|
spec.add_dependency 'legion-data', '>= 1.4.19'
|
|
31
31
|
spec.add_dependency 'legion-json', '>= 1.2.0'
|
|
32
|
-
spec.add_dependency 'legion-logging', '>= 1.
|
|
32
|
+
spec.add_dependency 'legion-logging', '>= 1.4.3'
|
|
33
33
|
spec.add_dependency 'legion-settings', '>= 1.3.12'
|
|
34
34
|
spec.add_dependency 'mcp', '~> 0.8'
|
|
35
35
|
end
|
|
@@ -6,6 +6,8 @@ module Legion
|
|
|
6
6
|
module MCP
|
|
7
7
|
module Actor
|
|
8
8
|
class SelfGenerateCycle < Legion::Extensions::Actors::Every
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
9
11
|
def runner_class = self.class
|
|
10
12
|
def runner_function = 'action'
|
|
11
13
|
def check_subtask? = false
|
|
@@ -18,6 +20,7 @@ module Legion
|
|
|
18
20
|
300
|
|
19
21
|
end
|
|
20
22
|
rescue StandardError => e
|
|
23
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.actors.self_generate_cycle.time')
|
|
21
24
|
log.warn(e.message)
|
|
22
25
|
300
|
|
23
26
|
end
|
|
@@ -25,6 +28,7 @@ module Legion
|
|
|
25
28
|
def enabled?
|
|
26
29
|
SelfGenerate.enabled?
|
|
27
30
|
rescue StandardError => e
|
|
31
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.actors.self_generate_cycle.enabled?')
|
|
28
32
|
log.warn(e.message)
|
|
29
33
|
false
|
|
30
34
|
end
|
|
@@ -32,16 +36,6 @@ module Legion
|
|
|
32
36
|
def action(_payload = nil)
|
|
33
37
|
SelfGenerate.run_cycle
|
|
34
38
|
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def log
|
|
39
|
-
return Legion::Logging if defined?(Legion::Logging)
|
|
40
|
-
|
|
41
|
-
@log ||= Object.new.tap do |nl|
|
|
42
|
-
%i[debug info warn error fatal].each { |m| nl.define_singleton_method(m) { |*| nil } }
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
39
|
end
|
|
46
40
|
end
|
|
47
41
|
end
|
data/lib/legion/mcp/auth.rb
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module MCP
|
|
5
5
|
module Auth
|
|
6
|
+
extend Legion::Logging::Helper
|
|
7
|
+
|
|
6
8
|
module_function
|
|
7
9
|
|
|
8
10
|
def authenticate(token)
|
|
11
|
+
log.info('Starting legion.mcp.auth.authenticate')
|
|
9
12
|
return { authenticated: false, error: 'missing_token' } unless token
|
|
10
13
|
|
|
11
14
|
if jwt_token?(token)
|
|
@@ -34,7 +37,8 @@ module Legion
|
|
|
34
37
|
{ authenticated: true, identity: { user_id: claims[:sub], risk_tier: claims[:risk_tier]&.to_sym,
|
|
35
38
|
tenant_id: claims[:tenant_id], worker_id: claims[:worker_id] } }
|
|
36
39
|
rescue StandardError => e
|
|
37
|
-
|
|
40
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.auth.verify_jwt')
|
|
41
|
+
log.warn("Auth#verify_jwt failed: #{e.message}")
|
|
38
42
|
{ authenticated: false, error: e.message }
|
|
39
43
|
end
|
|
40
44
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module MCP
|
|
5
5
|
module CatalogBridge
|
|
6
|
+
include Legion::Logging::Helper
|
|
7
|
+
|
|
6
8
|
def hydrate_override_confidence
|
|
7
9
|
return unless defined?(Legion::LLM::OverrideConfidence)
|
|
8
10
|
return unless Legion::LLM::OverrideConfidence.respond_to?(:hydrate_from_l2)
|
|
@@ -19,6 +21,7 @@ module Legion
|
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def dispatch_catalog_tool(tool_name, arguments)
|
|
24
|
+
log.info('Starting legion.mcp.catalog_bridge.dispatch_catalog_tool')
|
|
22
25
|
return nil unless defined?(Legion::Extensions::Catalog::Registry)
|
|
23
26
|
|
|
24
27
|
cap = Legion::Extensions::Catalog::Registry.find_by_mcp_name(tool_name)
|
|
@@ -31,13 +34,16 @@ module Legion
|
|
|
31
34
|
result = runner.send(fn, **(arguments || {}).transform_keys(&:to_sym))
|
|
32
35
|
{ status: :success, result: result, source: :catalog }
|
|
33
36
|
rescue NameError => e
|
|
34
|
-
|
|
37
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.catalog_bridge.dispatch_catalog_tool')
|
|
38
|
+
log.warn("Catalog dispatch failed: #{e.message}")
|
|
35
39
|
nil
|
|
36
40
|
rescue StandardError => e
|
|
41
|
+
handle_exception(e, level: :error, operation: 'legion.mcp.catalog_bridge.dispatch_catalog_tool')
|
|
37
42
|
{ status: :error, error: e.message, source: :catalog }
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def register_catalog_tools
|
|
46
|
+
log.info('Starting legion.mcp.catalog_bridge.register_catalog_tools')
|
|
41
47
|
CatalogDispatcher.generate_tools_from_catalog.each { |tc| Server.register_tool(tc) }
|
|
42
48
|
end
|
|
43
49
|
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'logging_support'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module MCP
|
|
5
7
|
module CatalogDispatcher
|
|
8
|
+
extend Legion::Logging::Helper
|
|
9
|
+
|
|
6
10
|
module_function
|
|
7
11
|
|
|
8
|
-
def dispatch(runner_class:, function:, params:, source: :mcp)
|
|
9
|
-
|
|
12
|
+
def dispatch(runner_class:, function:, params:, source: :mcp) # rubocop:disable Metrics/MethodLength
|
|
13
|
+
LoggingSupport.info(
|
|
14
|
+
'catalog.dispatch.start',
|
|
15
|
+
runner_class: runner_class,
|
|
16
|
+
function: function,
|
|
17
|
+
source: source,
|
|
18
|
+
params: LoggingSupport.summarize_params(params)
|
|
19
|
+
)
|
|
20
|
+
unless defined?(Legion::Ingress)
|
|
21
|
+
LoggingSupport.warn(
|
|
22
|
+
'catalog.dispatch.skipped',
|
|
23
|
+
runner_class: runner_class,
|
|
24
|
+
function: function,
|
|
25
|
+
reason: 'ingress unavailable'
|
|
26
|
+
)
|
|
27
|
+
return nil
|
|
28
|
+
end
|
|
10
29
|
|
|
11
|
-
Legion::Ingress.run(
|
|
30
|
+
result = Legion::Ingress.run(
|
|
12
31
|
payload: params,
|
|
13
32
|
runner_class: runner_class,
|
|
14
33
|
function: function.to_sym,
|
|
@@ -16,6 +35,14 @@ module Legion
|
|
|
16
35
|
check_subtask: true,
|
|
17
36
|
generate_task: true
|
|
18
37
|
)
|
|
38
|
+
LoggingSupport.info(
|
|
39
|
+
'catalog.dispatch.complete',
|
|
40
|
+
runner_class: runner_class,
|
|
41
|
+
function: function,
|
|
42
|
+
source: source,
|
|
43
|
+
result: LoggingSupport.summarize_result(result)
|
|
44
|
+
)
|
|
45
|
+
result
|
|
19
46
|
end
|
|
20
47
|
|
|
21
48
|
def build_tool_class(entry)
|
|
@@ -35,13 +62,21 @@ module Legion
|
|
|
35
62
|
define_singleton_method(:mcp_tier) { tier }
|
|
36
63
|
define_singleton_method(:catalog_entry) { true }
|
|
37
64
|
end
|
|
65
|
+
klass.extend(Legion::Logging::Helper)
|
|
38
66
|
|
|
39
|
-
wire_dispatch(klass, runner_class_str, function_name)
|
|
67
|
+
wire_dispatch(klass, runner_class_str, function_name, tool_name_val)
|
|
40
68
|
klass
|
|
41
69
|
end
|
|
42
70
|
|
|
43
|
-
def wire_dispatch(klass, runner_class_str, function_name)
|
|
44
|
-
klass.define_singleton_method(:call) do |**params|
|
|
71
|
+
def wire_dispatch(klass, runner_class_str, function_name, tool_name_val) # rubocop:disable Metrics/MethodLength
|
|
72
|
+
klass.define_singleton_method(:call) do |**params| # rubocop:disable Metrics/BlockLength
|
|
73
|
+
LoggingSupport.info(
|
|
74
|
+
'catalog.tool_call.start',
|
|
75
|
+
tool_name: tool_name_val,
|
|
76
|
+
runner_class: runner_class_str,
|
|
77
|
+
function: function_name,
|
|
78
|
+
params: LoggingSupport.summarize_params(params)
|
|
79
|
+
)
|
|
45
80
|
result = CatalogDispatcher.dispatch(
|
|
46
81
|
runner_class: runner_class_str,
|
|
47
82
|
function: function_name,
|
|
@@ -53,10 +88,25 @@ module Legion
|
|
|
53
88
|
::MCP::Tool::Response.new([{ type: 'text', text: text }], error: true)
|
|
54
89
|
else
|
|
55
90
|
text = defined?(Legion::JSON) ? Legion::JSON.dump(result) : result.to_s
|
|
56
|
-
::MCP::Tool::Response.new([{ type: 'text', text: text }])
|
|
91
|
+
response = ::MCP::Tool::Response.new([{ type: 'text', text: text }])
|
|
92
|
+
LoggingSupport.info(
|
|
93
|
+
'catalog.tool_call.complete',
|
|
94
|
+
tool_name: tool_name_val,
|
|
95
|
+
runner_class: runner_class_str,
|
|
96
|
+
function: function_name,
|
|
97
|
+
result: LoggingSupport.summarize_result(response)
|
|
98
|
+
)
|
|
99
|
+
response
|
|
57
100
|
end
|
|
58
101
|
rescue StandardError => e
|
|
59
|
-
|
|
102
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.catalog_dispatcher.call')
|
|
103
|
+
LoggingSupport.warn(
|
|
104
|
+
'catalog.tool_call.failed',
|
|
105
|
+
tool_name: tool_name_val,
|
|
106
|
+
runner_class: runner_class_str,
|
|
107
|
+
function: function_name,
|
|
108
|
+
error: e.message
|
|
109
|
+
)
|
|
60
110
|
text = Legion::JSON.dump({ error: e.message })
|
|
61
111
|
::MCP::Tool::Response.new([{ type: 'text', text: text }], error: true)
|
|
62
112
|
end
|
|
@@ -77,7 +127,8 @@ module Legion
|
|
|
77
127
|
tier: cap.respond_to?(:tier) ? cap.tier : nil
|
|
78
128
|
)
|
|
79
129
|
rescue StandardError => e
|
|
80
|
-
|
|
130
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.catalog_dispatcher.generate_tools_from_catalog')
|
|
131
|
+
log.debug("CatalogDispatcher: skipping #{cap}: #{e.message}")
|
|
81
132
|
nil
|
|
82
133
|
end
|
|
83
134
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../logging_support'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module MCP
|
|
5
7
|
module Client
|
|
6
|
-
class Connection
|
|
8
|
+
class Connection # rubocop:disable Metrics/ClassLength
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
attr_reader :name, :transport_type, :config
|
|
8
12
|
|
|
9
13
|
TOOL_CACHE_TTL = 300 # seconds
|
|
@@ -26,6 +30,12 @@ module Legion
|
|
|
26
30
|
@mutex.synchronize do
|
|
27
31
|
return if @connected
|
|
28
32
|
|
|
33
|
+
LoggingSupport.info(
|
|
34
|
+
'client.connect.start',
|
|
35
|
+
connection: @name,
|
|
36
|
+
transport: @transport_type,
|
|
37
|
+
config: @config.slice(:url, :command)
|
|
38
|
+
)
|
|
29
39
|
case @transport_type
|
|
30
40
|
when :stdio
|
|
31
41
|
connect_stdio
|
|
@@ -35,9 +45,15 @@ module Legion
|
|
|
35
45
|
raise ArgumentError, "Unknown transport: #{@transport_type}"
|
|
36
46
|
end
|
|
37
47
|
@connected = true
|
|
48
|
+
LoggingSupport.info(
|
|
49
|
+
'client.connect.complete',
|
|
50
|
+
connection: @name,
|
|
51
|
+
transport: @transport_type
|
|
52
|
+
)
|
|
38
53
|
end
|
|
39
|
-
rescue StandardError
|
|
54
|
+
rescue StandardError => e
|
|
40
55
|
@connected = false
|
|
56
|
+
handle_exception(e, level: :error, operation: 'legion.mcp.client.connection.connect')
|
|
41
57
|
raise
|
|
42
58
|
end
|
|
43
59
|
|
|
@@ -53,18 +69,61 @@ module Legion
|
|
|
53
69
|
@mutex.synchronize do
|
|
54
70
|
if !force_refresh && @tools_cache && @tools_cached_at &&
|
|
55
71
|
(Time.now - @tools_cached_at) < TOOL_CACHE_TTL
|
|
72
|
+
LoggingSupport.info(
|
|
73
|
+
'client.tools.cache_hit',
|
|
74
|
+
connection: @name,
|
|
75
|
+
transport: @transport_type,
|
|
76
|
+
count: @tools_cache.size
|
|
77
|
+
)
|
|
56
78
|
return @tools_cache
|
|
57
79
|
end
|
|
58
80
|
|
|
81
|
+
LoggingSupport.info(
|
|
82
|
+
'client.tools.fetch.start',
|
|
83
|
+
connection: @name,
|
|
84
|
+
transport: @transport_type,
|
|
85
|
+
force_refresh: force_refresh
|
|
86
|
+
)
|
|
59
87
|
@tools_cache = fetch_tools
|
|
60
88
|
@tools_cached_at = Time.now
|
|
89
|
+
LoggingSupport.info(
|
|
90
|
+
'client.tools.fetch.complete',
|
|
91
|
+
connection: @name,
|
|
92
|
+
transport: @transport_type,
|
|
93
|
+
count: @tools_cache.size
|
|
94
|
+
)
|
|
61
95
|
@tools_cache
|
|
62
96
|
end
|
|
63
97
|
end
|
|
64
98
|
|
|
65
99
|
def call_tool(name:, arguments: {})
|
|
66
100
|
connect unless connected?
|
|
67
|
-
|
|
101
|
+
LoggingSupport.info(
|
|
102
|
+
'client.tool_call.start',
|
|
103
|
+
connection: @name,
|
|
104
|
+
transport: @transport_type,
|
|
105
|
+
tool_name: name,
|
|
106
|
+
arguments: LoggingSupport.summarize_params(arguments)
|
|
107
|
+
)
|
|
108
|
+
result = execute_tool_call(name: name, arguments: arguments)
|
|
109
|
+
LoggingSupport.info(
|
|
110
|
+
'client.tool_call.complete',
|
|
111
|
+
connection: @name,
|
|
112
|
+
transport: @transport_type,
|
|
113
|
+
tool_name: name,
|
|
114
|
+
result: LoggingSupport.summarize_result(result)
|
|
115
|
+
)
|
|
116
|
+
result
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.client.connection.call_tool')
|
|
119
|
+
LoggingSupport.warn(
|
|
120
|
+
'client.tool_call.failed',
|
|
121
|
+
connection: @name,
|
|
122
|
+
transport: @transport_type,
|
|
123
|
+
tool_name: name,
|
|
124
|
+
error: e.message
|
|
125
|
+
)
|
|
126
|
+
raise
|
|
68
127
|
end
|
|
69
128
|
|
|
70
129
|
private
|
|
@@ -7,6 +7,8 @@ module Legion
|
|
|
7
7
|
@connections = {}
|
|
8
8
|
@mutex = Mutex.new
|
|
9
9
|
|
|
10
|
+
extend Legion::Logging::Helper
|
|
11
|
+
|
|
10
12
|
module_function
|
|
11
13
|
|
|
12
14
|
def connection_for(server_name)
|
|
@@ -30,7 +32,8 @@ module Legion
|
|
|
30
32
|
tool.merge(source: { type: :mcp, server: name })
|
|
31
33
|
end
|
|
32
34
|
rescue StandardError => e
|
|
33
|
-
|
|
35
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.client.pool.all_tools')
|
|
36
|
+
log.warn("MCP tool discovery failed for #{name}: #{e.message}")
|
|
34
37
|
ServerRegistry.mark_unhealthy(name)
|
|
35
38
|
[]
|
|
36
39
|
end
|
|
@@ -38,7 +41,11 @@ module Legion
|
|
|
38
41
|
|
|
39
42
|
def reset!
|
|
40
43
|
@mutex.synchronize do
|
|
41
|
-
@connections.each_value
|
|
44
|
+
@connections.each_value do |connection|
|
|
45
|
+
connection.disconnect
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.client.pool.reset!')
|
|
48
|
+
end
|
|
42
49
|
@connections.clear
|
|
43
50
|
end
|
|
44
51
|
end
|
data/lib/legion/mcp/client.rb
CHANGED
|
@@ -3,14 +3,22 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module MCP
|
|
5
5
|
module Client
|
|
6
|
+
extend Legion::Logging::Helper
|
|
7
|
+
|
|
6
8
|
module_function
|
|
7
9
|
|
|
8
10
|
def boot
|
|
9
|
-
|
|
11
|
+
log.info('Starting legion.mcp.client.boot')
|
|
12
|
+
servers = begin
|
|
13
|
+
Legion::Settings.dig(:mcp, :servers)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.client.boot')
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
10
18
|
return unless servers.is_a?(Hash) && servers.any?
|
|
11
19
|
|
|
12
20
|
ServerRegistry.load_from_settings(servers)
|
|
13
|
-
|
|
21
|
+
log.info("MCP Client: #{servers.length} servers registered")
|
|
14
22
|
end
|
|
15
23
|
|
|
16
24
|
def shutdown
|
|
@@ -6,9 +6,12 @@ require_relative 'pattern_exchange'
|
|
|
6
6
|
module Legion
|
|
7
7
|
module MCP
|
|
8
8
|
module ColdStart
|
|
9
|
+
extend Legion::Logging::Helper
|
|
10
|
+
|
|
9
11
|
module_function
|
|
10
12
|
|
|
11
13
|
def load_community_patterns(path: nil)
|
|
14
|
+
log.info('Starting legion.mcp.cold_start.load_community_patterns')
|
|
12
15
|
return { skipped: true, reason: 'store not empty' } unless PatternStore.empty?
|
|
13
16
|
|
|
14
17
|
path ||= configured_path
|
|
@@ -16,7 +19,8 @@ module Legion
|
|
|
16
19
|
|
|
17
20
|
PatternExchange.import_from_file(path, trust_level: :community)
|
|
18
21
|
rescue StandardError => e
|
|
19
|
-
|
|
22
|
+
handle_exception(e, level: :error, operation: 'legion.mcp.cold_start.load_community_patterns')
|
|
23
|
+
log.error("ColdStart#load_community_patterns failed: #{e.message}")
|
|
20
24
|
{ error: e.message, imported: 0 }
|
|
21
25
|
end
|
|
22
26
|
|
|
@@ -25,7 +29,8 @@ module Legion
|
|
|
25
29
|
|
|
26
30
|
Legion::Settings.dig(:mcp, :cold_start, :patterns_path)
|
|
27
31
|
rescue StandardError => e
|
|
28
|
-
|
|
32
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.cold_start.configured_path')
|
|
33
|
+
log.warn("ColdStart#configured_path failed: #{e.message}")
|
|
29
34
|
nil
|
|
30
35
|
end
|
|
31
36
|
end
|
|
@@ -8,6 +8,8 @@ module Legion
|
|
|
8
8
|
DEFAULT_RAPID_FIRE_WINDOW_SECS = 600
|
|
9
9
|
DEFAULT_ANOMALY_MISS_THRESHOLD = 2
|
|
10
10
|
|
|
11
|
+
extend Legion::Logging::Helper
|
|
12
|
+
|
|
11
13
|
module_function
|
|
12
14
|
|
|
13
15
|
def check(pattern, _params, _context)
|
|
@@ -76,7 +78,8 @@ module Legion
|
|
|
76
78
|
|
|
77
79
|
Legion::Settings.dig(:mcp, :tier0, :guards, key)
|
|
78
80
|
rescue StandardError => e
|
|
79
|
-
|
|
81
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.context_guard.setting')
|
|
82
|
+
log.warn("ContextGuard#setting failed for key #{key}: #{e.message}")
|
|
80
83
|
nil
|
|
81
84
|
end
|
|
82
85
|
|
|
@@ -5,6 +5,8 @@ module Legion
|
|
|
5
5
|
module DynamicInjector
|
|
6
6
|
MAX_INJECTED = 10
|
|
7
7
|
|
|
8
|
+
extend Legion::Logging::Helper
|
|
9
|
+
|
|
8
10
|
module_function
|
|
9
11
|
|
|
10
12
|
def enabled?
|
|
@@ -52,7 +54,8 @@ module Legion
|
|
|
52
54
|
|
|
53
55
|
server.notify_tools_list_changed
|
|
54
56
|
rescue StandardError => e
|
|
55
|
-
|
|
57
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.dynamic_injector.notify_if_changed')
|
|
58
|
+
log.debug("DynamicInjector: notify failed: #{e.message}")
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
def inject_for_context(server, intent_string, previous_names: [])
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module MCP
|
|
5
5
|
module EmbeddingIndex
|
|
6
|
+
extend Legion::Logging::Helper
|
|
7
|
+
|
|
6
8
|
module_function
|
|
7
9
|
|
|
8
10
|
def build_from_tool_data(tool_data, embedder: default_embedder)
|
|
@@ -98,7 +100,8 @@ module Legion
|
|
|
98
100
|
|
|
99
101
|
result
|
|
100
102
|
rescue StandardError => e
|
|
101
|
-
|
|
103
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.embedding_index.safe_embed')
|
|
104
|
+
log.debug("EmbeddingIndex#safe_embed failed: #{e.message}")
|
|
102
105
|
nil
|
|
103
106
|
end
|
|
104
107
|
|
|
@@ -107,7 +110,8 @@ module Legion
|
|
|
107
110
|
|
|
108
111
|
->(text) { Legion::LLM.embed(text)[:vector] }
|
|
109
112
|
rescue StandardError => e
|
|
110
|
-
|
|
113
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.embedding_index.default_embedder')
|
|
114
|
+
log.debug("EmbeddingIndex#default_embedder failed: #{e.message}")
|
|
111
115
|
nil
|
|
112
116
|
end
|
|
113
117
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module MCP
|
|
5
5
|
module FunctionDiscovery
|
|
6
|
+
extend Legion::Logging::Helper
|
|
7
|
+
|
|
6
8
|
module_function
|
|
7
9
|
|
|
8
10
|
def discover_and_register
|
|
@@ -19,7 +21,8 @@ module Legion
|
|
|
19
21
|
|
|
20
22
|
ext.runner_modules.each { |runner_mod| build_tools_from_runner(runner_mod) }
|
|
21
23
|
rescue StandardError => e
|
|
22
|
-
|
|
24
|
+
handle_exception(e, level: :debug, operation: 'legion.mcp.function_discovery.discover_and_register')
|
|
25
|
+
log.debug("FunctionDiscovery: skipping #{ext}: #{e.message}")
|
|
23
26
|
end
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -141,6 +144,7 @@ module Legion
|
|
|
141
144
|
begin
|
|
142
145
|
runner_ref.public_send(func_ref, **params)
|
|
143
146
|
rescue StandardError => e
|
|
147
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.function_discovery.call')
|
|
144
148
|
error = true
|
|
145
149
|
{ error: e.message }
|
|
146
150
|
end
|