@mediagato/modelreins-channel 4.10.0 → 4.11.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.
- package/LICENSE +21 -0
- package/modelreins-channel.mjs +109 -5
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steve Kingsley / MEDiAGATO LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/modelreins-channel.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* @module modelreins-channel
|
|
4
|
-
* @version 4.
|
|
4
|
+
* @version 4.6.0
|
|
5
5
|
*
|
|
6
6
|
* ModelReins MCP Channel
|
|
7
7
|
* ======================
|
|
@@ -273,7 +273,7 @@ const STUCK_JOB_MS = 30 * 60 * 1000; // 30 min — generous Claude completion w
|
|
|
273
273
|
// without user interaction.
|
|
274
274
|
|
|
275
275
|
const mcp = new Server(
|
|
276
|
-
{ name: 'modelreins', version: '4.
|
|
276
|
+
{ name: 'modelreins', version: '4.6.0' },
|
|
277
277
|
{
|
|
278
278
|
capabilities: {
|
|
279
279
|
// Enable the Claude-specific channel notification method.
|
|
@@ -463,6 +463,40 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
463
463
|
},
|
|
464
464
|
},
|
|
465
465
|
|
|
466
|
+
// ── modelreins_plan ───────────────────────────────────────────────────
|
|
467
|
+
{
|
|
468
|
+
name: 'modelreins_plan',
|
|
469
|
+
description: [
|
|
470
|
+
'Turn a free-form goal into a multi-step plan and, by default, run it across the fleet.',
|
|
471
|
+
'',
|
|
472
|
+
'This is the Director: it plans a sequence of skills (dispatch_inference, web_search,',
|
|
473
|
+
'worker_list, worker_load_model, etc — see GET /director/skills on the API) from your',
|
|
474
|
+
'request, then executes each step in order and returns the results. Reach for this instead',
|
|
475
|
+
'of modelreins_dispatch when a goal needs more than one action, or you want the fleet',
|
|
476
|
+
'itself to decide the steps rather than picking a single worker by hand.',
|
|
477
|
+
'',
|
|
478
|
+
'Set auto_execute: false to preview the plan without running it, so you can inspect the',
|
|
479
|
+
'proposed steps before committing the fleet to them.',
|
|
480
|
+
'',
|
|
481
|
+
'Caveat: the web_search skill is currently a stub (returns no results) pending the',
|
|
482
|
+
'Neutron-as-worker integration — a plan that includes it will run but find nothing yet.',
|
|
483
|
+
].join('\n'),
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: 'object',
|
|
486
|
+
properties: {
|
|
487
|
+
request: {
|
|
488
|
+
type: 'string',
|
|
489
|
+
description: 'The goal, in plain language. The planner only sees this text — be as specific as a good dispatch prompt.',
|
|
490
|
+
},
|
|
491
|
+
auto_execute: {
|
|
492
|
+
type: 'boolean',
|
|
493
|
+
description: 'Execute the plan immediately after planning it. Default true. Set false to preview only.',
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
required: ['request'],
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
|
|
466
500
|
],
|
|
467
501
|
}));
|
|
468
502
|
|
|
@@ -531,11 +565,74 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
531
565
|
};
|
|
532
566
|
}
|
|
533
567
|
|
|
568
|
+
// ── modelreins_plan ────────────────────────────────────────────────────────
|
|
569
|
+
|
|
570
|
+
if (name === 'modelreins_plan') {
|
|
571
|
+
const { request, auto_execute } = args;
|
|
572
|
+
const shouldExecute = auto_execute !== false;
|
|
573
|
+
|
|
574
|
+
const planRes = await api('POST', '/director/plan', {
|
|
575
|
+
request,
|
|
576
|
+
auto_execute: shouldExecute,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
if (!planRes.data?.success) {
|
|
580
|
+
return {
|
|
581
|
+
content: [{
|
|
582
|
+
type: 'text',
|
|
583
|
+
text: `Planning failed (HTTP ${planRes.status}): ${JSON.stringify(planRes.data)}`,
|
|
584
|
+
}],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const { plan_id, plan } = planRes.data;
|
|
589
|
+
const lines = [`## Director Plan #${plan_id}`, ''];
|
|
590
|
+
lines.push(`Rationale: ${plan.rationale}`);
|
|
591
|
+
lines.push(`Risk tier: ${plan.risk_tier}`);
|
|
592
|
+
lines.push(`Steps (${plan.step_count}):`);
|
|
593
|
+
plan.steps.forEach((s, i) => {
|
|
594
|
+
lines.push(` ${i + 1}. ${s.tool_name} ${JSON.stringify(s.arguments)}`);
|
|
595
|
+
if (s.rationale) lines.push(` -> ${s.rationale}`);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
if (!shouldExecute) {
|
|
599
|
+
lines.push('', 'Not executed (auto_execute=false). Call modelreins_plan again with auto_execute:true to run it.');
|
|
600
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const execRes = await api('POST', '/director/execute', { plan_id });
|
|
604
|
+
|
|
605
|
+
// api() always returns a truthy data object (even on network failure),
|
|
606
|
+
// so failure must be detected via the HTTP status / success flag.
|
|
607
|
+
if (execRes.status < 200 || execRes.status >= 300 || execRes.data.success === false) {
|
|
608
|
+
lines.push('', `## Execution FAILED (HTTP ${execRes.status})`);
|
|
609
|
+
lines.push(`Error: ${execRes.data.error || execRes.data.detail || JSON.stringify(execRes.data)}`);
|
|
610
|
+
lines.push(`Plan #${plan_id} was created but not executed. Call modelreins_plan again (or POST /director/execute with this plan_id) to run it.`);
|
|
611
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const { status: finalStatus, results } = execRes.data;
|
|
615
|
+
lines.push('', `## Execution — ${finalStatus || 'unknown'}`);
|
|
616
|
+
for (const r of (results || [])) {
|
|
617
|
+
const mark = r.status === 'done' ? 'done' : r.status === 'failed' ? 'FAILED' : (r.status || 'unknown');
|
|
618
|
+
lines.push(` [${r.step_index + 1}] ${r.tool_name.padEnd(20)} ${mark}`);
|
|
619
|
+
if (r.status === 'done' && r.result !== undefined) {
|
|
620
|
+
const preview = typeof r.result === 'string' ? r.result : JSON.stringify(r.result);
|
|
621
|
+
lines.push(` ${preview.slice(0, 400)}`);
|
|
622
|
+
}
|
|
623
|
+
if (r.status === 'failed' && r.error) {
|
|
624
|
+
lines.push(` error: ${r.error}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
629
|
+
}
|
|
630
|
+
|
|
534
631
|
// ── modelreins_status ─────────────────────────────────────────────────────
|
|
535
632
|
|
|
536
633
|
if (name === 'modelreins_status') {
|
|
537
634
|
const [workers, jobs, schedules] = await Promise.all([
|
|
538
|
-
api('GET', '/workers'),
|
|
635
|
+
api('GET', '/fleet/workers'),
|
|
539
636
|
api('GET', '/jobs?limit=5'),
|
|
540
637
|
api('GET', '/schedules'),
|
|
541
638
|
]);
|
|
@@ -544,8 +641,15 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
544
641
|
|
|
545
642
|
// Workers
|
|
546
643
|
const wList = workers.data?.workers || [];
|
|
547
|
-
|
|
548
|
-
|
|
644
|
+
// An empty list and a failed request are NOT the same finding -- collapsing
|
|
645
|
+
// them into "No workers registered" is what let 12 live workers read as
|
|
646
|
+
// zero for as long as that line existed (see 71be396). Say which happened.
|
|
647
|
+
const wFailed = workers.status !== 200;
|
|
648
|
+
lines.push(`### Workers (${wFailed ? 'unreadable' : `${wList.length} known`})`);
|
|
649
|
+
if (wFailed) {
|
|
650
|
+
lines.push(` COULD NOT READ /fleet/workers — HTTP ${workers.status}${workers.data?.error ? ` (${workers.data.error})` : ''}.`);
|
|
651
|
+
lines.push(' This is a failed check, not an empty fleet.');
|
|
652
|
+
} else if (wList.length === 0) {
|
|
549
653
|
lines.push(' No workers registered.');
|
|
550
654
|
}
|
|
551
655
|
for (const w of wList) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediagato/modelreins-channel",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.1",
|
|
4
4
|
"description": "ModelReins MCP Channel — turn any Claude Code session into a ModelReins worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "modelreins-channel.mjs",
|
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
"channel",
|
|
18
18
|
"ai-orchestration"
|
|
19
19
|
],
|
|
20
|
-
"license": "
|
|
20
|
+
"license": "MIT",
|
|
21
21
|
"homepage": "https://modelreins.com"
|
|
22
22
|
}
|