cuboid 0.3.6 → 0.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/README.md +195 -0
- data/cuboid.gemspec +4 -0
- data/lib/cuboid/application.rb +84 -3
- data/lib/cuboid/mcp/auth.rb +99 -0
- data/lib/cuboid/mcp/core_tools.rb +318 -0
- data/lib/cuboid/mcp/live.rb +166 -0
- data/lib/cuboid/mcp/server.rb +426 -0
- data/lib/cuboid/option_groups/paths.rb +40 -0
- data/lib/cuboid/processes/executables/base.rb +37 -0
- data/lib/cuboid/processes/executables/mcp.rb +20 -0
- data/lib/cuboid/processes/instances.rb +9 -1
- data/lib/cuboid/processes/manager.rb +22 -1
- data/lib/cuboid/rest/server/instance_helpers.rb +21 -70
- data/lib/cuboid/rest/server/routes/instances.rb +1 -3
- data/lib/cuboid/rest/server.rb +1 -1
- data/lib/cuboid/rpc/server/agent.rb +6 -1
- data/lib/cuboid/rpc/server/instance.rb +32 -0
- data/lib/cuboid/server/instance_helpers.rb +131 -0
- data/lib/version +1 -1
- data/spec/cuboid/mcp/auth_spec.rb +179 -0
- data/spec/cuboid/mcp/server_spec.rb +346 -0
- data/spec/cuboid/rest/server_spec.rb +3 -4
- data/spec/support/shared/option_group.rb +11 -1
- metadata +26 -2
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require "#{Cuboid::Options.paths.lib}/mcp/server"
|
|
3
|
+
|
|
4
|
+
describe Cuboid::MCP::Server do
|
|
5
|
+
include Rack::Test::Methods
|
|
6
|
+
|
|
7
|
+
# Each test gets a fresh anonymous Application subclass so service /
|
|
8
|
+
# validator registrations don't leak between examples.
|
|
9
|
+
let(:fake_application) { Class.new(Cuboid::Application) }
|
|
10
|
+
|
|
11
|
+
before(:each) do
|
|
12
|
+
@prev_application = Cuboid::Application.application
|
|
13
|
+
Cuboid::Application.application = fake_application
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
after(:each) do
|
|
17
|
+
Cuboid::Application.application = @prev_application
|
|
18
|
+
# Reset the shared instances map so per-example fixtures don't
|
|
19
|
+
# bleed into the next example.
|
|
20
|
+
Cuboid::Server::InstanceHelpers.instances.clear
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Rack::Test's `app` hook — built per-test from current options/state.
|
|
24
|
+
# Default to `stateless: true` so tests can hit individual JSON-RPC
|
|
25
|
+
# methods without an explicit `initialize` handshake first.
|
|
26
|
+
def app
|
|
27
|
+
described_class.rack_app({ stateless: true }.merge(@app_options || {}))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def jsonrpc(method, params = {}, id: 1)
|
|
31
|
+
{
|
|
32
|
+
jsonrpc: '2.0',
|
|
33
|
+
id: id,
|
|
34
|
+
method: method,
|
|
35
|
+
params: params
|
|
36
|
+
}.to_json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def post_jsonrpc(path, method, params = {}, headers: {})
|
|
40
|
+
post path,
|
|
41
|
+
jsonrpc(method, params),
|
|
42
|
+
{
|
|
43
|
+
'CONTENT_TYPE' => 'application/json',
|
|
44
|
+
'HTTP_ACCEPT' => 'application/json, text/event-stream'
|
|
45
|
+
}.merge(headers)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
INITIALIZE_PARAMS = {
|
|
49
|
+
protocolVersion: '2025-06-18',
|
|
50
|
+
capabilities: {},
|
|
51
|
+
clientInfo: { name: 'spec', version: '0' }
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# Stub for an MCP-tool handler module/class — what an application
|
|
55
|
+
# gem supplies via `mcp_service_for`.
|
|
56
|
+
def build_handler_with_tools(*tools)
|
|
57
|
+
h = Module.new
|
|
58
|
+
h.define_singleton_method(:tools) { tools }
|
|
59
|
+
h
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Plant an entry in the shared instance map so the dispatcher can
|
|
63
|
+
# resolve `:instance` to something. The value is whatever an RPC
|
|
64
|
+
# client would normally be — for tests we just use a plain
|
|
65
|
+
# double / OpenStruct that the tools may interact with.
|
|
66
|
+
def stub_instance(id, instance_obj = Object.new)
|
|
67
|
+
Cuboid::Server::InstanceHelpers.instances[id] = instance_obj
|
|
68
|
+
instance_obj
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'when the path is unrecognised' do
|
|
72
|
+
before do
|
|
73
|
+
fake_application.mcp_service_for(:my_service, build_handler_with_tools)
|
|
74
|
+
stub_instance('inst-1')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it '404s an unrelated path' do
|
|
78
|
+
post_jsonrpc '/random/path', 'initialize', INITIALIZE_PARAMS
|
|
79
|
+
last_response.status.should == 404
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'core tools at /mcp (framework-level)' do
|
|
84
|
+
# Stub double for kill_instance: needs `.shutdown` and `.close`.
|
|
85
|
+
let(:killable) do
|
|
86
|
+
obj = Object.new
|
|
87
|
+
obj.define_singleton_method(:shutdown) { nil }
|
|
88
|
+
obj.define_singleton_method(:close) { nil }
|
|
89
|
+
obj
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'serves an initialize handshake' do
|
|
93
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS
|
|
94
|
+
|
|
95
|
+
last_response.status.should == 200
|
|
96
|
+
JSON.parse(last_response.body)['result']['protocolVersion']
|
|
97
|
+
.should == '2025-06-18'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'lists the framework tools (list/spawn/kill instance)' do
|
|
101
|
+
post_jsonrpc '/mcp', 'tools/list'
|
|
102
|
+
|
|
103
|
+
names = JSON.parse(last_response.body)['result']['tools'].map { |t| t['name'] }
|
|
104
|
+
names.sort.should == %w[kill_instance list_instances spawn_instance]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'list_instances returns currently-registered ids' do
|
|
108
|
+
stub_instance('inst-a')
|
|
109
|
+
stub_instance('inst-b')
|
|
110
|
+
|
|
111
|
+
post_jsonrpc '/mcp', 'tools/call', { name: 'list_instances', arguments: {} }
|
|
112
|
+
|
|
113
|
+
structured = JSON.parse(last_response.body)['result']['structuredContent']
|
|
114
|
+
structured['instances'].keys.sort.should == %w[inst-a inst-b]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'kill_instance removes the instance from the shared map' do
|
|
118
|
+
stub_instance('inst-x', killable)
|
|
119
|
+
|
|
120
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
121
|
+
{ name: 'kill_instance', arguments: { instance_id: 'inst-x' } }
|
|
122
|
+
|
|
123
|
+
structured = JSON.parse(last_response.body)['result']['structuredContent']
|
|
124
|
+
structured['killed'].should == 'inst-x'
|
|
125
|
+
Cuboid::Server::InstanceHelpers.instances.key?('inst-x').should == false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'kill_instance returns an error response when the id is unknown' do
|
|
129
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
130
|
+
{ name: 'kill_instance', arguments: { instance_id: 'nope' } }
|
|
131
|
+
|
|
132
|
+
body = JSON.parse(last_response.body)
|
|
133
|
+
content = body['result']['content'].first
|
|
134
|
+
body['result']['isError'].should == true
|
|
135
|
+
content['text'].should include('unknown instance')
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
context 'with a service registered' do
|
|
140
|
+
let(:tool_class) do
|
|
141
|
+
klass = Class.new(MCP::Tool)
|
|
142
|
+
klass.instance_eval do
|
|
143
|
+
tool_name 'echo'
|
|
144
|
+
description 'Returns "<instance_id>: <message>".'
|
|
145
|
+
input_schema(
|
|
146
|
+
properties: { message: { type: 'string' } },
|
|
147
|
+
required: ['message']
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def self.call(message:, server_context:)
|
|
151
|
+
text = "#{server_context[:instance_id]}: #{message}"
|
|
152
|
+
MCP::Tool::Response.new([{ type: 'text', text: text }])
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
klass
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
let(:handler) { build_handler_with_tools(tool_class) }
|
|
159
|
+
|
|
160
|
+
before do
|
|
161
|
+
fake_application.mcp_service_for(:my_service, handler)
|
|
162
|
+
stub_instance('inst-1')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'lists the wrapped tool under its <service>_<tool> name with instance_id required' do
|
|
166
|
+
post_jsonrpc '/mcp', 'tools/list'
|
|
167
|
+
|
|
168
|
+
tools = JSON.parse(last_response.body)['result']['tools']
|
|
169
|
+
wrapped = tools.find { |t| t['name'] == 'my_service_echo' }
|
|
170
|
+
wrapped.should_not be_nil
|
|
171
|
+
wrapped['inputSchema']['required'].should include('instance_id')
|
|
172
|
+
wrapped['inputSchema']['properties'].keys.map(&:to_s)
|
|
173
|
+
.should include('instance_id', 'message')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'resolves the instance_id arg into server_context for the wrapped tool' do
|
|
177
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
178
|
+
{ name: 'my_service_echo',
|
|
179
|
+
arguments: { instance_id: 'inst-1', message: 'hi' } }
|
|
180
|
+
|
|
181
|
+
content = JSON.parse(last_response.body)['result']['content']
|
|
182
|
+
content.first['text'].should == 'inst-1: hi'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'returns an MCP tool error (not a routing 404) when instance_id is unknown' do
|
|
186
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
187
|
+
{ name: 'my_service_echo',
|
|
188
|
+
arguments: { instance_id: 'missing', message: 'hi' } }
|
|
189
|
+
|
|
190
|
+
body = JSON.parse(last_response.body)
|
|
191
|
+
body['result']['isError'].should == true
|
|
192
|
+
body['result']['content'].first['text'].should include('unknown instance')
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'isolates state across instance_ids passed as arguments' do
|
|
196
|
+
stub_instance('inst-2')
|
|
197
|
+
|
|
198
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
199
|
+
{ name: 'my_service_echo',
|
|
200
|
+
arguments: { instance_id: 'inst-1', message: 'hi' } }
|
|
201
|
+
JSON.parse(last_response.body)['result']['content']
|
|
202
|
+
.first['text'].should == 'inst-1: hi'
|
|
203
|
+
|
|
204
|
+
post_jsonrpc '/mcp', 'tools/call',
|
|
205
|
+
{ name: 'my_service_echo',
|
|
206
|
+
arguments: { instance_id: 'inst-2', message: 'hi' } }
|
|
207
|
+
JSON.parse(last_response.body)['result']['content']
|
|
208
|
+
.first['text'].should == 'inst-2: hi'
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context 'when an auth validator is registered' do
|
|
213
|
+
before do
|
|
214
|
+
fake_application.mcp_service_for(:my_service, build_handler_with_tools)
|
|
215
|
+
stub_instance('inst-1')
|
|
216
|
+
fake_application.mcp_authenticate_with do |token|
|
|
217
|
+
token == 'good' ? :ok : nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it '401s a request with no Authorization header' do
|
|
222
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS
|
|
223
|
+
last_response.status.should == 401
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'allows requests with a valid bearer token' do
|
|
227
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS,
|
|
228
|
+
headers: { 'HTTP_AUTHORIZATION' => 'Bearer good' }
|
|
229
|
+
last_response.status.should == 200
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
context 'with custom name / version' do
|
|
234
|
+
before do
|
|
235
|
+
fake_application.mcp_service_for(:my_service, build_handler_with_tools)
|
|
236
|
+
stub_instance('inst-1')
|
|
237
|
+
@app_options = { name: 'custom-mcp', version: '7.7.7' }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'advertises them in serverInfo' do
|
|
241
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS
|
|
242
|
+
|
|
243
|
+
info = JSON.parse(last_response.body)['result']['serverInfo']
|
|
244
|
+
info['name'].should == 'custom-mcp'
|
|
245
|
+
info['version'].should == '7.7.7'
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context 'when the application class lives under a branded top-level namespace' do
|
|
250
|
+
# Synthesize a real-looking namespace: a `shortname` method
|
|
251
|
+
# (the brand the user wants advertised) and a `version`
|
|
252
|
+
# method (preferred over the VERSION constant when both are
|
|
253
|
+
# present). The dispatcher should pick the branded methods
|
|
254
|
+
# over the raw module name.
|
|
255
|
+
before do
|
|
256
|
+
stub_const('BrandFake', Module.new)
|
|
257
|
+
BrandFake.define_singleton_method(:shortname) { :foo }
|
|
258
|
+
BrandFake.define_singleton_method(:version) { '9.9.9' }
|
|
259
|
+
BrandFake.const_set(
|
|
260
|
+
:Application,
|
|
261
|
+
Class.new(Cuboid::Application) { def self.name; 'BrandFake::Application'; end }
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
Cuboid::Application.application = BrandFake::Application
|
|
265
|
+
BrandFake::Application.mcp_service_for(:my_service, build_handler_with_tools)
|
|
266
|
+
stub_instance('inst-1')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'advertises the brand shortname + version at /mcp' do
|
|
270
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS
|
|
271
|
+
|
|
272
|
+
info = JSON.parse(last_response.body)['result']['serverInfo']
|
|
273
|
+
info['name'].should == 'foo'
|
|
274
|
+
info['version'].should == '9.9.9'
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context 'live route (`/mcp/live/<token>`)' do
|
|
280
|
+
# The route is loopback-only; Rack::Test sets REMOTE_ADDR to
|
|
281
|
+
# '127.0.0.1' by default which lets the loopback gate pass.
|
|
282
|
+
|
|
283
|
+
before(:each) do
|
|
284
|
+
# Reset Live registry between examples so tokens don't bleed.
|
|
285
|
+
Cuboid::MCP::Live.instance_variable_set(:@by_token, {})
|
|
286
|
+
Cuboid::MCP::Live.instance_variable_set(:@by_instance_id, {})
|
|
287
|
+
Cuboid::MCP::Live.transport = nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it '410s when the token isn\'t registered' do
|
|
291
|
+
post '/mcp/live/no-such-token', '{}',
|
|
292
|
+
{ 'CONTENT_TYPE' => 'application/json' }
|
|
293
|
+
|
|
294
|
+
last_response.status.should == 410
|
|
295
|
+
JSON.parse(last_response.body)['error']
|
|
296
|
+
.should include('live token unknown')
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'rejects pushes from non-loopback with 404' do
|
|
300
|
+
post '/mcp/live/whatever', '{}',
|
|
301
|
+
{ 'CONTENT_TYPE' => 'application/json',
|
|
302
|
+
'REMOTE_ADDR' => '203.0.113.5' }
|
|
303
|
+
|
|
304
|
+
last_response.status.should == 404
|
|
305
|
+
JSON.parse(last_response.body)['error']['message']
|
|
306
|
+
.should include('loopback')
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it '400s on an undecodable body for the declared content type' do
|
|
310
|
+
# Register a token so we get past the 410 path.
|
|
311
|
+
Cuboid::MCP::Live.instance_variable_get(:@by_token)['tok'] = {
|
|
312
|
+
session_id: 'sess-1', instance_id: 'inst-x'
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
post '/mcp/live/tok', "\xff\xff\xff not json".b,
|
|
316
|
+
{ 'CONTENT_TYPE' => 'application/json' }
|
|
317
|
+
|
|
318
|
+
last_response.status.should == 400
|
|
319
|
+
JSON.parse(last_response.body)['error']
|
|
320
|
+
.should include('could not decode')
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
context 'when the namespace exposes only a VERSION constant (no branded methods)' do
|
|
325
|
+
before do
|
|
326
|
+
stub_const('PlainFake', Module.new)
|
|
327
|
+
PlainFake.const_set(:VERSION, '2.0.0')
|
|
328
|
+
PlainFake.const_set(
|
|
329
|
+
:Application,
|
|
330
|
+
Class.new(Cuboid::Application) { def self.name; 'PlainFake::Application'; end }
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
Cuboid::Application.application = PlainFake::Application
|
|
334
|
+
PlainFake::Application.mcp_service_for(:my_service, build_handler_with_tools)
|
|
335
|
+
stub_instance('inst-1')
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'falls back to the namespace name + VERSION' do
|
|
339
|
+
post_jsonrpc '/mcp', 'initialize', INITIALIZE_PARAMS
|
|
340
|
+
|
|
341
|
+
info = JSON.parse(last_response.body)['result']['serverInfo']
|
|
342
|
+
info['name'].should == 'PlainFake'
|
|
343
|
+
info['version'].should == '2.0.0'
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
@@ -479,7 +479,7 @@ describe Cuboid::Rest::Server do
|
|
|
479
479
|
end
|
|
480
480
|
end
|
|
481
481
|
|
|
482
|
-
context 'when the instance is from the Scheduler'
|
|
482
|
+
context 'when the instance is from the Scheduler'do
|
|
483
483
|
before do
|
|
484
484
|
put '/scheduler/url', scheduler.url
|
|
485
485
|
end
|
|
@@ -581,7 +581,6 @@ describe Cuboid::Rest::Server do
|
|
|
581
581
|
delete url
|
|
582
582
|
|
|
583
583
|
get "/instances/#{id}"
|
|
584
|
-
ap response
|
|
585
584
|
expect(response_code).to eq 404
|
|
586
585
|
end
|
|
587
586
|
|
|
@@ -606,9 +605,9 @@ describe Cuboid::Rest::Server do
|
|
|
606
605
|
delete url
|
|
607
606
|
expect(response_code).to eq 200
|
|
608
607
|
|
|
609
|
-
sleep
|
|
608
|
+
sleep 1 while !scheduler.running.empty?
|
|
610
609
|
|
|
611
|
-
expect(scheduler.
|
|
610
|
+
expect(scheduler.completed).to include @id
|
|
612
611
|
end
|
|
613
612
|
|
|
614
613
|
context 'when the instance completes' do
|
|
@@ -7,9 +7,19 @@ shared_examples_for 'option_group' do
|
|
|
7
7
|
it 'converts self to a serializable hash' do
|
|
8
8
|
expect(data).to be_kind_of Hash
|
|
9
9
|
|
|
10
|
+
# MessagePack — the on-wire serializer behind
|
|
11
|
+
# `Cuboid::RPC::Serializer` — has no Symbol type, so any
|
|
12
|
+
# Symbol value in `data` round-trips as a String. Compare
|
|
13
|
+
# against the stringified-Symbol form rather than `data`
|
|
14
|
+
# itself; the round-trip equality the spec is asserting
|
|
15
|
+
# holds modulo that one well-known coercion.
|
|
16
|
+
expected = data.each_with_object({}) do |(k, v), h|
|
|
17
|
+
h[k] = v.is_a?(Symbol) ? v.to_s : v
|
|
18
|
+
end
|
|
19
|
+
|
|
10
20
|
expect(Cuboid::RPC::Serializer.load(
|
|
11
21
|
Cuboid::RPC::Serializer.dump( data )
|
|
12
|
-
)).to eq(
|
|
22
|
+
)).to eq(expected)
|
|
13
23
|
end
|
|
14
24
|
end
|
|
15
25
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cuboid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.4'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tasos Laskos
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: awesome_print
|
|
@@ -178,6 +178,20 @@ dependencies:
|
|
|
178
178
|
- - ">="
|
|
179
179
|
- !ruby/object:Gem::Version
|
|
180
180
|
version: '4.0'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: mcp
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0.15'
|
|
188
|
+
type: :runtime
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - ">="
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '0.15'
|
|
181
195
|
- !ruby/object:Gem::Dependency
|
|
182
196
|
name: toq
|
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -248,6 +262,10 @@ files:
|
|
|
248
262
|
- lib/cuboid/data.rb
|
|
249
263
|
- lib/cuboid/data/application.rb
|
|
250
264
|
- lib/cuboid/error.rb
|
|
265
|
+
- lib/cuboid/mcp/auth.rb
|
|
266
|
+
- lib/cuboid/mcp/core_tools.rb
|
|
267
|
+
- lib/cuboid/mcp/live.rb
|
|
268
|
+
- lib/cuboid/mcp/server.rb
|
|
251
269
|
- lib/cuboid/option_group.rb
|
|
252
270
|
- lib/cuboid/option_groups.rb
|
|
253
271
|
- lib/cuboid/option_groups/agent.rb
|
|
@@ -265,6 +283,7 @@ files:
|
|
|
265
283
|
- lib/cuboid/processes/executables/agent.rb
|
|
266
284
|
- lib/cuboid/processes/executables/base.rb
|
|
267
285
|
- lib/cuboid/processes/executables/instance.rb
|
|
286
|
+
- lib/cuboid/processes/executables/mcp.rb
|
|
268
287
|
- lib/cuboid/processes/executables/rest_service.rb
|
|
269
288
|
- lib/cuboid/processes/executables/scheduler.rb
|
|
270
289
|
- lib/cuboid/processes/helpers.rb
|
|
@@ -304,6 +323,7 @@ files:
|
|
|
304
323
|
- lib/cuboid/ruby/array.rb
|
|
305
324
|
- lib/cuboid/ruby/hash.rb
|
|
306
325
|
- lib/cuboid/ruby/object.rb
|
|
326
|
+
- lib/cuboid/server/instance_helpers.rb
|
|
307
327
|
- lib/cuboid/snapshot.rb
|
|
308
328
|
- lib/cuboid/state.rb
|
|
309
329
|
- lib/cuboid/state/application.rb
|
|
@@ -362,6 +382,8 @@ files:
|
|
|
362
382
|
- spec/cuboid/data/application_spec.rb
|
|
363
383
|
- spec/cuboid/data_spec.rb
|
|
364
384
|
- spec/cuboid/error_spec.rb
|
|
385
|
+
- spec/cuboid/mcp/auth_spec.rb
|
|
386
|
+
- spec/cuboid/mcp/server_spec.rb
|
|
365
387
|
- spec/cuboid/option_groups/agent_spec.rb
|
|
366
388
|
- spec/cuboid/option_groups/datastore_spec.rb
|
|
367
389
|
- spec/cuboid/option_groups/output_spec.rb
|
|
@@ -486,6 +508,8 @@ test_files:
|
|
|
486
508
|
- spec/cuboid/data/application_spec.rb
|
|
487
509
|
- spec/cuboid/data_spec.rb
|
|
488
510
|
- spec/cuboid/error_spec.rb
|
|
511
|
+
- spec/cuboid/mcp/auth_spec.rb
|
|
512
|
+
- spec/cuboid/mcp/server_spec.rb
|
|
489
513
|
- spec/cuboid/option_groups/agent_spec.rb
|
|
490
514
|
- spec/cuboid/option_groups/datastore_spec.rb
|
|
491
515
|
- spec/cuboid/option_groups/output_spec.rb
|