legion-mcp 0.4.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc7341c88afa89639cebd05ccffc1dfb00792062373fe295743f525c9c0380c9
4
- data.tar.gz: d2b832be2e2942286489edad666215e13f8de19b888a32551bdedef4e70b1f7f
3
+ metadata.gz: 37f03ddf4778cc238c0e69b039b9cd6c55acc2ada6207ef32c777c844d60d5e1
4
+ data.tar.gz: ffe2b6b6da0930d795ca3965b86f5edabdf8f6b698232b110bd3ae6187ce0c20
5
5
  SHA512:
6
- metadata.gz: 89874ef7825b398392ed7966573379a9c9edc55500cb874bb79897b3b1f65631ff1c594c90420948468d00d9cbe9e8a83838d482d491d7260cea76450b977358
7
- data.tar.gz: 58b8e23f9a5a37a87f2d50150cdc8872deaa654524c1ce92a507ac7b157aa9b687a6b99a591d3841f94e37a567dc7f30a4922ca55e5e1646072cf84331947354
6
+ metadata.gz: a4c148c867a384494dd92847f6ec4f25b6010d73ce3f076e6d01c0f5ae47c8a7c6d8cfa909da58f067334612f7b7420e87e388c0910fad2edf604ea45a670206
7
+ data.tar.gz: eb8d5f6536df790298e4613a92f61f8a4e136d16d83ede2a99c92a9507ddfd8f7301fe52a12678f891b0227e1568660b77db008b09bd9e1a98f55644e64a56b2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # legion-mcp Changelog
2
2
 
3
+ ## [0.4.1] - 2026-03-19
4
+
5
+ ### Added
6
+ - `legion.prompt_list` — list all stored prompt templates via lex-prompt Client
7
+ - `legion.prompt_show` — fetch a prompt by name, version, or tag via lex-prompt Client
8
+ - `legion.prompt_run` — render a prompt template with ERB variable substitution via lex-prompt Client
9
+ - `legion.dataset_list` — list all stored datasets via lex-dataset Client
10
+ - `legion.dataset_show` — fetch a dataset with all rows, optionally version-pinned, via lex-dataset Client
11
+ - `legion.experiment_results` — retrieve per-row results and summary for a named experiment from lex-dataset
12
+ - `legion.eval_list` — list available evaluator templates via lex-eval Client
13
+ - `legion.eval_run` — run a single input/output pair through a named evaluator via lex-eval Client
14
+ - `legion.eval_results` — retrieve stored experiment results via lex-dataset experiment store
15
+ - All 9 tools registered in `TOOL_CLASSES`; total tool count raised from 36 to 45
16
+ - Specs for all 9 new tools
17
+
3
18
  ## [0.4.0] - 2026-03-20
4
19
 
5
20
  ### Added
@@ -35,6 +35,15 @@ require_relative 'tools/routing_stats'
35
35
  require_relative 'tools/rbac_check'
36
36
  require_relative 'tools/rbac_assignments'
37
37
  require_relative 'tools/rbac_grants'
38
+ require_relative 'tools/prompt_list'
39
+ require_relative 'tools/prompt_show'
40
+ require_relative 'tools/prompt_run'
41
+ require_relative 'tools/dataset_list'
42
+ require_relative 'tools/dataset_show'
43
+ require_relative 'tools/experiment_results'
44
+ require_relative 'tools/eval_list'
45
+ require_relative 'tools/eval_run'
46
+ require_relative 'tools/eval_results'
38
47
  require_relative 'context_compiler'
39
48
  require_relative 'embedding_index'
40
49
  require_relative 'cold_start'
@@ -81,6 +90,15 @@ module Legion
81
90
  Tools::RbacCheck,
82
91
  Tools::RbacAssignments,
83
92
  Tools::RbacGrants,
93
+ Tools::PromptList,
94
+ Tools::PromptShow,
95
+ Tools::PromptRun,
96
+ Tools::DatasetList,
97
+ Tools::DatasetShow,
98
+ Tools::ExperimentResults,
99
+ Tools::EvalList,
100
+ Tools::EvalRun,
101
+ Tools::EvalResults,
84
102
  Tools::DoAction,
85
103
  Tools::PlanAction,
86
104
  Tools::DiscoverTools
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class DatasetList < ::MCP::Tool
7
+ tool_name 'legion.dataset_list'
8
+ description 'List all stored datasets with their latest version and row counts.'
9
+
10
+ input_schema(properties: {})
11
+
12
+ class << self
13
+ def call
14
+ return error_response('lex-dataset is not loaded') unless extension_loaded?('dataset')
15
+
16
+ require 'legion/extensions/dataset/client'
17
+ client = Legion::Extensions::Dataset::Client.new(db: db)
18
+ result = client.list_datasets
19
+ text_response(result)
20
+ rescue StandardError => e
21
+ error_response("Failed to list datasets: #{e.message}")
22
+ end
23
+
24
+ private
25
+
26
+ def extension_loaded?(name)
27
+ require "legion/extensions/#{name}"
28
+ true
29
+ rescue LoadError
30
+ false
31
+ end
32
+
33
+ def db
34
+ Legion::Data.db
35
+ end
36
+
37
+ def text_response(data)
38
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
39
+ end
40
+
41
+ def error_response(msg)
42
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class DatasetShow < ::MCP::Tool
7
+ tool_name 'legion.dataset_show'
8
+ description 'Retrieve a dataset by name including all rows, optionally pinned to a specific version.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ name: { type: 'string', description: 'Name of the dataset' },
13
+ version: { type: 'integer', description: 'Specific version to fetch (default: latest)' }
14
+ },
15
+ required: ['name']
16
+ )
17
+
18
+ class << self
19
+ def call(name:, version: nil)
20
+ return error_response('lex-dataset is not loaded') unless extension_loaded?('dataset')
21
+
22
+ require 'legion/extensions/dataset/client'
23
+ client = Legion::Extensions::Dataset::Client.new(db: db)
24
+ result = client.get_dataset(name: name, version: version)
25
+ text_response(result)
26
+ rescue StandardError => e
27
+ error_response("Failed to fetch dataset: #{e.message}")
28
+ end
29
+
30
+ private
31
+
32
+ def extension_loaded?(name)
33
+ require "legion/extensions/#{name}"
34
+ true
35
+ rescue LoadError
36
+ false
37
+ end
38
+
39
+ def db
40
+ Legion::Data.db
41
+ end
42
+
43
+ def text_response(data)
44
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
45
+ end
46
+
47
+ def error_response(msg)
48
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class EvalList < ::MCP::Tool
7
+ tool_name 'legion.eval_list'
8
+ description 'List all available evaluator templates (LLM-as-judge and code-based).'
9
+
10
+ input_schema(properties: {})
11
+
12
+ class << self
13
+ def call
14
+ return error_response('lex-eval is not loaded') unless extension_loaded?('eval')
15
+
16
+ require 'legion/extensions/eval/client'
17
+ client = Legion::Extensions::Eval::Client.new(db: db)
18
+ result = client.list_evaluators
19
+ text_response(result)
20
+ rescue StandardError => e
21
+ error_response("Failed to list evaluators: #{e.message}")
22
+ end
23
+
24
+ private
25
+
26
+ def extension_loaded?(name)
27
+ require "legion/extensions/#{name}"
28
+ true
29
+ rescue LoadError
30
+ false
31
+ end
32
+
33
+ def db
34
+ Legion::Data.db
35
+ end
36
+
37
+ def text_response(data)
38
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
39
+ end
40
+
41
+ def error_response(msg)
42
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class EvalResults < ::MCP::Tool
7
+ tool_name 'legion.eval_results'
8
+ description 'Retrieve stored results for a named experiment from the dataset experiment store.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ experiment_name: { type: 'string', description: 'Name of the experiment to retrieve results for' }
13
+ },
14
+ required: ['experiment_name']
15
+ )
16
+
17
+ class << self
18
+ def call(experiment_name:)
19
+ return error_response('lex-dataset is not loaded') unless extension_loaded?('dataset')
20
+
21
+ require 'legion/extensions/dataset/client'
22
+ client = Legion::Extensions::Dataset::Client.new(db: db)
23
+ result = fetch_experiment(client, experiment_name)
24
+ text_response(result)
25
+ rescue StandardError => e
26
+ error_response("Failed to fetch eval results: #{e.message}")
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_experiment(client, name)
32
+ db_handle = client.instance_variable_get(:@db)
33
+ return { error: 'database_unavailable' } unless db_handle
34
+
35
+ exp = db_handle[:experiments].where(name: name).first
36
+ return { error: 'not_found' } unless exp
37
+
38
+ rows = db_handle[:experiment_results]
39
+ .where(experiment_id: exp[:id])
40
+ .order(:row_index)
41
+ .all
42
+ .map { |r| { row_index: r[:row_index], passed: r[:passed], latency_ms: r[:latency_ms] } }
43
+
44
+ summary = begin
45
+ ::JSON.parse(exp[:summary], symbolize_names: true)
46
+ rescue StandardError
47
+ {}
48
+ end
49
+
50
+ { experiment_id: exp[:id], name: exp[:name], status: exp[:status],
51
+ created_at: exp[:created_at], completed_at: exp[:completed_at],
52
+ summary: summary, rows: rows }
53
+ end
54
+
55
+ def extension_loaded?(name)
56
+ require "legion/extensions/#{name}"
57
+ true
58
+ rescue LoadError
59
+ false
60
+ end
61
+
62
+ def db
63
+ Legion::Data.db
64
+ end
65
+
66
+ def text_response(data)
67
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
68
+ end
69
+
70
+ def error_response(msg)
71
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class EvalRun < ::MCP::Tool
7
+ tool_name 'legion.eval_run'
8
+ description 'Run an evaluator against a single input/output pair and return pass/fail with score.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ evaluator_name: { type: 'string', description: 'Name of the evaluator template to use' },
13
+ input: { type: 'string', description: 'The original input/prompt given to the model' },
14
+ output: { type: 'string', description: 'The model output to evaluate' },
15
+ expected: { type: 'string', description: 'Optional expected/reference output for comparison' }
16
+ },
17
+ required: %w[evaluator_name input output]
18
+ )
19
+
20
+ class << self
21
+ def call(evaluator_name:, input:, output:, expected: nil)
22
+ return error_response('lex-eval is not loaded') unless extension_loaded?('eval')
23
+
24
+ require 'legion/extensions/eval/client'
25
+ client = Legion::Extensions::Eval::Client.new(db: db)
26
+ inputs = [{ input: input, output: output, expected: expected }.compact]
27
+ result = client.run_evaluation(evaluator_name: evaluator_name, inputs: inputs)
28
+ text_response(result)
29
+ rescue StandardError => e
30
+ error_response("Failed to run evaluation: #{e.message}")
31
+ end
32
+
33
+ private
34
+
35
+ def extension_loaded?(name)
36
+ require "legion/extensions/#{name}"
37
+ true
38
+ rescue LoadError
39
+ false
40
+ end
41
+
42
+ def db
43
+ Legion::Data.db
44
+ end
45
+
46
+ def text_response(data)
47
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
48
+ end
49
+
50
+ def error_response(msg)
51
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class ExperimentResults < ::MCP::Tool
7
+ tool_name 'legion.experiment_results'
8
+ description 'Retrieve stored results for a named experiment, including per-row pass/fail and summary stats.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ name: { type: 'string', description: 'Name of the experiment to retrieve results for' }
13
+ },
14
+ required: ['name']
15
+ )
16
+
17
+ class << self
18
+ def call(name:)
19
+ return error_response('lex-dataset is not loaded') unless extension_loaded?('dataset')
20
+
21
+ require 'legion/extensions/dataset/client'
22
+ client = Legion::Extensions::Dataset::Client.new(db: db)
23
+ result = fetch_experiment(client, name)
24
+ text_response(result)
25
+ rescue StandardError => e
26
+ error_response("Failed to fetch experiment results: #{e.message}")
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_experiment(client, name)
32
+ db_handle = client.instance_variable_get(:@db)
33
+ return { error: 'database_unavailable' } unless db_handle
34
+
35
+ exp = db_handle[:experiments].where(name: name).first
36
+ return { error: 'not_found' } unless exp
37
+
38
+ rows = db_handle[:experiment_results]
39
+ .where(experiment_id: exp[:id])
40
+ .order(:row_index)
41
+ .all
42
+ .map { |r| { row_index: r[:row_index], passed: r[:passed], latency_ms: r[:latency_ms] } }
43
+
44
+ summary = begin
45
+ ::JSON.parse(exp[:summary], symbolize_names: true)
46
+ rescue StandardError
47
+ {}
48
+ end
49
+
50
+ { experiment_id: exp[:id], name: exp[:name], status: exp[:status],
51
+ created_at: exp[:created_at], completed_at: exp[:completed_at],
52
+ summary: summary, rows: rows }
53
+ end
54
+
55
+ def extension_loaded?(name)
56
+ require "legion/extensions/#{name}"
57
+ true
58
+ rescue LoadError
59
+ false
60
+ end
61
+
62
+ def db
63
+ Legion::Data.db
64
+ end
65
+
66
+ def text_response(data)
67
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
68
+ end
69
+
70
+ def error_response(msg)
71
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class PromptList < ::MCP::Tool
7
+ tool_name 'legion.prompt_list'
8
+ description 'List all stored LLM prompt templates with their latest version and metadata.'
9
+
10
+ input_schema(properties: {})
11
+
12
+ class << self
13
+ def call
14
+ return error_response('lex-prompt is not loaded') unless extension_loaded?('prompt')
15
+
16
+ require 'legion/extensions/prompt/client'
17
+ client = Legion::Extensions::Prompt::Client.new(db: db)
18
+ result = client.list_prompts
19
+ text_response(result)
20
+ rescue StandardError => e
21
+ error_response("Failed to list prompts: #{e.message}")
22
+ end
23
+
24
+ private
25
+
26
+ def extension_loaded?(name)
27
+ require "legion/extensions/#{name}"
28
+ true
29
+ rescue LoadError
30
+ false
31
+ end
32
+
33
+ def db
34
+ Legion::Data.db
35
+ end
36
+
37
+ def text_response(data)
38
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
39
+ end
40
+
41
+ def error_response(msg)
42
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class PromptRun < ::MCP::Tool
7
+ tool_name 'legion.prompt_run'
8
+ description 'Render a prompt template with variable substitution and return the final text.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ name: { type: 'string', description: 'Name of the prompt template to render' },
13
+ variables: {
14
+ type: 'object',
15
+ description: 'Key/value pairs to substitute into the ERB template',
16
+ additionalProperties: true
17
+ },
18
+ version: { type: 'integer', description: 'Specific version to render (default: latest)' }
19
+ },
20
+ required: ['name']
21
+ )
22
+
23
+ class << self
24
+ def call(name:, variables: {}, version: nil)
25
+ return error_response('lex-prompt is not loaded') unless extension_loaded?('prompt')
26
+
27
+ require 'legion/extensions/prompt/client'
28
+ client = Legion::Extensions::Prompt::Client.new(db: db)
29
+ result = client.render_prompt(name: name, variables: variables, version: version)
30
+ text_response(result)
31
+ rescue StandardError => e
32
+ error_response("Failed to render prompt: #{e.message}")
33
+ end
34
+
35
+ private
36
+
37
+ def extension_loaded?(name)
38
+ require "legion/extensions/#{name}"
39
+ true
40
+ rescue LoadError
41
+ false
42
+ end
43
+
44
+ def db
45
+ Legion::Data.db
46
+ end
47
+
48
+ def text_response(data)
49
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
50
+ end
51
+
52
+ def error_response(msg)
53
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module MCP
5
+ module Tools
6
+ class PromptShow < ::MCP::Tool
7
+ tool_name 'legion.prompt_show'
8
+ description 'Retrieve a prompt template by name, optionally pinned to a specific version or tag.'
9
+
10
+ input_schema(
11
+ properties: {
12
+ name: { type: 'string', description: 'Name of the prompt template' },
13
+ version: { type: 'integer', description: 'Specific version number to fetch (default: latest)' },
14
+ tag: { type: 'string', description: 'Named tag to resolve (e.g. "stable", "production")' }
15
+ },
16
+ required: ['name']
17
+ )
18
+
19
+ class << self
20
+ def call(name:, version: nil, tag: nil)
21
+ return error_response('lex-prompt is not loaded') unless extension_loaded?('prompt')
22
+
23
+ require 'legion/extensions/prompt/client'
24
+ client = Legion::Extensions::Prompt::Client.new(db: db)
25
+ result = client.get_prompt(name: name, version: version, tag: tag)
26
+ text_response(result)
27
+ rescue StandardError => e
28
+ error_response("Failed to fetch prompt: #{e.message}")
29
+ end
30
+
31
+ private
32
+
33
+ def extension_loaded?(name)
34
+ require "legion/extensions/#{name}"
35
+ true
36
+ rescue LoadError
37
+ false
38
+ end
39
+
40
+ def db
41
+ Legion::Data.db
42
+ end
43
+
44
+ def text_response(data)
45
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
46
+ end
47
+
48
+ def error_response(msg)
49
+ ::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module MCP
5
- VERSION = '0.4.0'
5
+ VERSION = '0.4.1'
6
6
  end
7
7
  end
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.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -137,6 +137,8 @@ files:
137
137
  - lib/legion/mcp/tools/create_chain.rb
138
138
  - lib/legion/mcp/tools/create_relationship.rb
139
139
  - lib/legion/mcp/tools/create_schedule.rb
140
+ - lib/legion/mcp/tools/dataset_list.rb
141
+ - lib/legion/mcp/tools/dataset_show.rb
140
142
  - lib/legion/mcp/tools/delete_chain.rb
141
143
  - lib/legion/mcp/tools/delete_relationship.rb
142
144
  - lib/legion/mcp/tools/delete_schedule.rb
@@ -146,6 +148,10 @@ files:
146
148
  - lib/legion/mcp/tools/discover_tools.rb
147
149
  - lib/legion/mcp/tools/do_action.rb
148
150
  - lib/legion/mcp/tools/enable_extension.rb
151
+ - lib/legion/mcp/tools/eval_list.rb
152
+ - lib/legion/mcp/tools/eval_results.rb
153
+ - lib/legion/mcp/tools/eval_run.rb
154
+ - lib/legion/mcp/tools/experiment_results.rb
149
155
  - lib/legion/mcp/tools/get_config.rb
150
156
  - lib/legion/mcp/tools/get_extension.rb
151
157
  - lib/legion/mcp/tools/get_status.rb
@@ -158,6 +164,9 @@ files:
158
164
  - lib/legion/mcp/tools/list_tasks.rb
159
165
  - lib/legion/mcp/tools/list_workers.rb
160
166
  - lib/legion/mcp/tools/plan_action.rb
167
+ - lib/legion/mcp/tools/prompt_list.rb
168
+ - lib/legion/mcp/tools/prompt_run.rb
169
+ - lib/legion/mcp/tools/prompt_show.rb
161
170
  - lib/legion/mcp/tools/rbac_assignments.rb
162
171
  - lib/legion/mcp/tools/rbac_check.rb
163
172
  - lib/legion/mcp/tools/rbac_grants.rb