legion-mcp 0.7.4 → 0.8.0
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 +10 -0
- data/lib/legion/mcp/server.rb +5 -1
- data/lib/legion/mcp/tools/skills.rb +230 -0
- data/lib/legion/mcp/tools_loader.rb +1 -0
- data/lib/legion/mcp/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a3e25d857f68078fd0cdee66c754af9f90d463b51e781c77fc98101a4f6417e
|
|
4
|
+
data.tar.gz: 210832fec6116565553d64f5903f9c6c2599b4f98d3aaa30a43d78ab6488322b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33a52a4ad2246bc5332cdd9d2fa63c8ba285945b43c56c79b5fb22088315a51f53828ff3ccd3507ad39cc8613459dfa272aaccda73362a364c91acf21d5200bf
|
|
7
|
+
data.tar.gz: 4ebbba0174ae96289f20001c0acdaa2d7b475ae671f783cf0297533ddee50bc54ed889f313aff1442c3a840ef2b56637e990f063d412db369048924897518969
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# legion-mcp Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0] - 2026-04-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `Tools::SkillList` (`legion.skill.list`) — MCP tool to list all skills registered in the Legion daemon with name, namespace, description, trigger words, and trigger type
|
|
7
|
+
- `Tools::SkillDescribe` (`legion.skill.describe`) — MCP tool to get full detail for a named skill (`namespace:name` or bare `name`)
|
|
8
|
+
- `Tools::SkillInvoke` (`legion.skill.invoke`) — MCP tool to invoke a skill by name with optional `conversation_id` context
|
|
9
|
+
- `Tools::SkillCancel` (`legion.skill.cancel`) — MCP tool to cancel an active skill for a given conversation
|
|
10
|
+
- All four skill tools registered in `MCP_SPECIFIC_TOOLS` (now 10 total)
|
|
11
|
+
- `require_relative 'tools/skills'` added to `tools_loader.rb`
|
|
12
|
+
|
|
3
13
|
## [0.7.4] - 2026-04-06
|
|
4
14
|
|
|
5
15
|
### Changed
|
data/lib/legion/mcp/server.rb
CHANGED
|
@@ -31,7 +31,11 @@ module Legion
|
|
|
31
31
|
Tools::StructuralIndexTool,
|
|
32
32
|
Tools::ToolAudit,
|
|
33
33
|
Tools::StateDiff,
|
|
34
|
-
Tools::SearchSessions
|
|
34
|
+
Tools::SearchSessions,
|
|
35
|
+
Tools::SkillList,
|
|
36
|
+
Tools::SkillDescribe,
|
|
37
|
+
Tools::SkillInvoke,
|
|
38
|
+
Tools::SkillCancel
|
|
35
39
|
].freeze
|
|
36
40
|
|
|
37
41
|
@tool_registry = Concurrent::Array.new(MCP_SPECIFIC_TOOLS)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module MCP
|
|
7
|
+
module Tools
|
|
8
|
+
class SkillList < ::MCP::Tool
|
|
9
|
+
tool_name 'legion.skill.list'
|
|
10
|
+
description 'List all skills available in this Legion instance.'
|
|
11
|
+
|
|
12
|
+
input_schema(properties: {})
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
include Legion::Logging::Helper
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
log.info('Starting legion.mcp.tools.skill_list.call')
|
|
19
|
+
return error_response('Skills not available: legion-llm not loaded') unless defined?(Legion::LLM::Skills::Registry)
|
|
20
|
+
|
|
21
|
+
skills = Legion::LLM::Skills::Registry.all.map do |s|
|
|
22
|
+
{
|
|
23
|
+
name: s.skill_name,
|
|
24
|
+
namespace: s.namespace,
|
|
25
|
+
description: s.description,
|
|
26
|
+
trigger_words: s.trigger_words,
|
|
27
|
+
trigger: s.trigger
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
text_response({ skills: skills, count: skills.size })
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.tools.skill_list.call')
|
|
33
|
+
log.warn("SkillList#call failed: #{e.message}")
|
|
34
|
+
error_response("Failed to list skills: #{e.message}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def text_response(data)
|
|
40
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def error_response(msg)
|
|
44
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class SkillDescribe < ::MCP::Tool
|
|
50
|
+
tool_name 'legion.skill.describe'
|
|
51
|
+
description 'Describe a specific skill by its namespace:name key or bare name.'
|
|
52
|
+
|
|
53
|
+
input_schema(
|
|
54
|
+
properties: {
|
|
55
|
+
name: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Skill key in namespace:name format (e.g. "superpowers:brainstorming") or bare skill name'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
required: ['name']
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
include Legion::Logging::Helper
|
|
65
|
+
|
|
66
|
+
def call(name:)
|
|
67
|
+
log.info('Starting legion.mcp.tools.skill_describe.call')
|
|
68
|
+
return error_response('Skills not available: legion-llm not loaded') unless defined?(Legion::LLM::Skills::Registry)
|
|
69
|
+
|
|
70
|
+
skill = find_skill(name)
|
|
71
|
+
return error_response("Skill '#{name}' not found") if skill.nil?
|
|
72
|
+
|
|
73
|
+
text_response({
|
|
74
|
+
name: skill.skill_name,
|
|
75
|
+
namespace: skill.namespace,
|
|
76
|
+
description: skill.description,
|
|
77
|
+
trigger_words: skill.trigger_words,
|
|
78
|
+
trigger: skill.trigger,
|
|
79
|
+
follows_skill: skill.follows_skill,
|
|
80
|
+
steps: skill.steps
|
|
81
|
+
})
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.tools.skill_describe.call')
|
|
84
|
+
log.warn("SkillDescribe#call failed: #{e.message}")
|
|
85
|
+
error_response("Failed to describe skill: #{e.message}")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def find_skill(name)
|
|
91
|
+
skill = Legion::LLM::Skills::Registry.find(name)
|
|
92
|
+
return skill unless skill.nil?
|
|
93
|
+
return nil if name.include?(':')
|
|
94
|
+
|
|
95
|
+
Legion::LLM::Skills::Registry.all.find { |s| s.skill_name == name }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def text_response(data)
|
|
99
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def error_response(msg)
|
|
103
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class SkillInvoke < ::MCP::Tool
|
|
109
|
+
tool_name 'legion.skill.invoke'
|
|
110
|
+
description 'Invoke a skill for a conversation. The skill will run its configured steps.'
|
|
111
|
+
|
|
112
|
+
input_schema(
|
|
113
|
+
properties: {
|
|
114
|
+
name: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Skill key in namespace:name format (e.g. "superpowers:brainstorming")'
|
|
117
|
+
},
|
|
118
|
+
conversation_id: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'Conversation ID to associate the skill run with (optional — generated if omitted)'
|
|
121
|
+
},
|
|
122
|
+
initial_message: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'Optional initial message to seed the skill run (defaults to "start skill")'
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
required: ['name']
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
class << self
|
|
131
|
+
include Legion::Logging::Helper
|
|
132
|
+
|
|
133
|
+
def call(name:, conversation_id: nil, initial_message: nil)
|
|
134
|
+
log.info('Starting legion.mcp.tools.skill_invoke.call')
|
|
135
|
+
return error_response('Skills not available: legion-llm not loaded') unless defined?(Legion::LLM::Skills::Registry)
|
|
136
|
+
|
|
137
|
+
skill = Legion::LLM::Skills::Registry.find(name)
|
|
138
|
+
return error_response("Skill '#{name}' not found") if skill.nil?
|
|
139
|
+
|
|
140
|
+
conv_id = conversation_id || "conv_#{::SecureRandom.hex(8)}"
|
|
141
|
+
invoke_skill(name, conv_id, initial_message)
|
|
142
|
+
rescue StandardError => e
|
|
143
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.tools.skill_invoke.call')
|
|
144
|
+
log.warn("SkillInvoke#call failed: #{e.message}")
|
|
145
|
+
error_response("Failed to invoke skill: #{e.message}")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def invoke_skill(name, conv_id, initial_message)
|
|
151
|
+
return error_response('ConversationStore not available — cannot invoke skill') unless defined?(Legion::LLM::ConversationStore)
|
|
152
|
+
|
|
153
|
+
unless defined?(Legion::LLM::Pipeline::Executor) && defined?(Legion::LLM::Pipeline::Request)
|
|
154
|
+
Legion::LLM::ConversationStore.set_skill_state(conv_id, skill_key: name, resume_at: 0)
|
|
155
|
+
return text_response({ invoked: false, skill: name, conversation_id: conv_id,
|
|
156
|
+
note: 'skill state queued — pipeline executor not available' })
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
Legion::LLM::ConversationStore.set_skill_state(conv_id, skill_key: name, resume_at: 0)
|
|
160
|
+
req = Legion::LLM::Pipeline::Request.build(
|
|
161
|
+
messages: [{ role: :user, content: initial_message || 'start skill' }],
|
|
162
|
+
conversation_id: conv_id,
|
|
163
|
+
metadata: { skill_invoke: true },
|
|
164
|
+
stream: false
|
|
165
|
+
)
|
|
166
|
+
result = Legion::LLM::Pipeline::Executor.new(req).call
|
|
167
|
+
text_response({ invoked: true, skill: name, conversation_id: conv_id,
|
|
168
|
+
content: result.message[:content] })
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
Legion::LLM::ConversationStore.clear_skill_state(conv_id) if defined?(Legion::LLM::ConversationStore)
|
|
171
|
+
raise e
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def text_response(data)
|
|
175
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def error_response(msg)
|
|
179
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class SkillCancel < ::MCP::Tool
|
|
185
|
+
tool_name 'legion.skill.cancel'
|
|
186
|
+
description 'Cancel an active skill run for a conversation.'
|
|
187
|
+
|
|
188
|
+
input_schema(
|
|
189
|
+
properties: {
|
|
190
|
+
conversation_id: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'Conversation ID whose active skill should be cancelled'
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
required: ['conversation_id']
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
class << self
|
|
199
|
+
include Legion::Logging::Helper
|
|
200
|
+
|
|
201
|
+
def call(conversation_id:)
|
|
202
|
+
log.info('Starting legion.mcp.tools.skill_cancel.call')
|
|
203
|
+
return error_response('ConversationStore not available') unless defined?(Legion::LLM::ConversationStore)
|
|
204
|
+
|
|
205
|
+
result = Legion::LLM::ConversationStore.cancel_skill!(conversation_id)
|
|
206
|
+
if result
|
|
207
|
+
text_response({ cancelled: true, skill_key: result[:skill_key] })
|
|
208
|
+
else
|
|
209
|
+
text_response({ cancelled: false, reason: 'not_running' })
|
|
210
|
+
end
|
|
211
|
+
rescue StandardError => e
|
|
212
|
+
handle_exception(e, level: :warn, operation: 'legion.mcp.tools.skill_cancel.call')
|
|
213
|
+
log.warn("SkillCancel#call failed: #{e.message}")
|
|
214
|
+
error_response("Failed to cancel skill: #{e.message}")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
private
|
|
218
|
+
|
|
219
|
+
def text_response(data)
|
|
220
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump(data) }])
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def error_response(msg)
|
|
224
|
+
::MCP::Tool::Response.new([{ type: 'text', text: Legion::JSON.dump({ error: msg }) }], error: true)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
data/lib/legion/mcp/version.rb
CHANGED
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
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -209,6 +209,7 @@ files:
|
|
|
209
209
|
- lib/legion/mcp/tools/run_task.rb
|
|
210
210
|
- lib/legion/mcp/tools/search_sessions.rb
|
|
211
211
|
- lib/legion/mcp/tools/show_worker.rb
|
|
212
|
+
- lib/legion/mcp/tools/skills.rb
|
|
212
213
|
- lib/legion/mcp/tools/state_diff.rb
|
|
213
214
|
- lib/legion/mcp/tools/structural_index.rb
|
|
214
215
|
- lib/legion/mcp/tools/team_summary.rb
|