@claude-flow/cli 3.54.1 → 3.56.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.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/.claude/settings.json +9 -9
- package/bin/cli.js +1 -1
- package/bin/mcp-server.js +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/autopilot.js +2 -2
- package/dist/src/commands/daemon.js +15 -3
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/hive-mind.js +7 -0
- package/dist/src/commands/mcp.js +35 -1
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/policy.js +10 -2
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.js +96 -4
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -4
- package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +95 -61
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +71 -13
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/production/retry.js +1 -1
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/bounded-worker-pool.js +3 -0
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/config-file-manager.d.ts +3 -0
- package/dist/src/services/config-file-manager.js +24 -7
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/policy-ledger-anchor.d.ts +42 -0
- package/dist/src/services/policy-ledger-anchor.js +163 -0
- package/dist/src/services/policy-runtime.d.ts +11 -5
- package/dist/src/services/policy-runtime.js +70 -15
- package/dist/src/services/worker-daemon.d.ts +5 -2
- package/dist/src/services/worker-daemon.js +26 -6
- package/dist/src/services/worker-queue.js +11 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +60 -47
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +11 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
- package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -1,39 +1,53 @@
|
|
|
1
1
|
import { output } from '../output.js';
|
|
2
|
-
import { backupMemoryDb,
|
|
2
|
+
import { backupMemoryDb, backupProjectMemoryDbs } from '../services/memory-backup.js';
|
|
3
3
|
export const backupCommand = {
|
|
4
4
|
name: 'backup',
|
|
5
|
-
description: 'Snapshot
|
|
5
|
+
description: 'Snapshot active project memory stores — WAL-safe, rotated, optional GCS offsite',
|
|
6
6
|
options: [
|
|
7
|
-
{ name: 'db', description: 'Source DB (default:
|
|
7
|
+
{ name: 'db', description: 'Source DB (default: configured CLI and AgentDB stores)', type: 'string' },
|
|
8
8
|
{ name: 'dir', description: 'Destination dir (default: .swarm/backups)', type: 'string' },
|
|
9
9
|
{ name: 'keep', description: 'Rotation — keep the newest N snapshots (default 7)', type: 'number', default: 7 },
|
|
10
10
|
{ name: 'gcs', description: 'Also upload the snapshot to a gs://bucket/prefix (offsite)', type: 'string' },
|
|
11
11
|
{ name: 'verbose', short: 'v', description: 'Verbose logging', type: 'boolean' },
|
|
12
12
|
],
|
|
13
13
|
examples: [
|
|
14
|
-
{ command: 'claude-flow memory backup', description: 'Snapshot
|
|
14
|
+
{ command: 'claude-flow memory backup', description: 'Snapshot active project stores, keep last 7' },
|
|
15
15
|
{ command: 'claude-flow memory backup --keep 30', description: 'Keep a month of nightly snapshots' },
|
|
16
16
|
{ command: 'claude-flow memory backup --gcs gs://my-bucket/ruflo-backups', description: 'Also upload offsite to GCS' },
|
|
17
17
|
],
|
|
18
18
|
action: async (ctx) => {
|
|
19
|
-
const
|
|
20
|
-
dbPath: ctx.flags.db || defaultMemoryDbPath(ctx.cwd),
|
|
19
|
+
const options = {
|
|
21
20
|
destDir: ctx.flags.dir,
|
|
22
21
|
keep: typeof ctx.flags.keep === 'number' ? ctx.flags.keep : 7,
|
|
23
22
|
gcs: ctx.flags.gcs,
|
|
24
23
|
verbose: ctx.flags.verbose === true,
|
|
25
|
-
}
|
|
24
|
+
};
|
|
25
|
+
const explicitDb = ctx.flags.db;
|
|
26
|
+
let r;
|
|
27
|
+
let receipts;
|
|
28
|
+
if (explicitDb) {
|
|
29
|
+
r = await backupMemoryDb({ ...options, dbPath: explicitDb });
|
|
30
|
+
receipts = [r];
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const project = await backupProjectMemoryDbs(ctx.cwd, options);
|
|
34
|
+
r = project;
|
|
35
|
+
receipts = project.backups;
|
|
36
|
+
}
|
|
26
37
|
if (!r.backedUp) {
|
|
27
38
|
// no-db is a benign "nothing to back up yet"; anything else is a real skip.
|
|
28
39
|
if (r.skipped === 'no-db') {
|
|
29
|
-
output.printWarning('No memory DB found to back up
|
|
40
|
+
output.printWarning('No memory DB found to back up. Nothing to do.');
|
|
30
41
|
return { success: true, data: r };
|
|
31
42
|
}
|
|
32
43
|
output.printError(`Backup skipped: ${r.skipped}`);
|
|
33
44
|
return { success: false, exitCode: 1, data: r };
|
|
34
45
|
}
|
|
35
46
|
output.writeln();
|
|
36
|
-
|
|
47
|
+
for (const receipt of receipts) {
|
|
48
|
+
if (receipt.backedUp)
|
|
49
|
+
output.writeln(output.success(`Backed up → ${receipt.path}`));
|
|
50
|
+
}
|
|
37
51
|
output.printList([
|
|
38
52
|
`Size: ${Math.round((r.sizeBytes ?? 0) / 1024)} KB`,
|
|
39
53
|
`Rotated: ${r.rotatedAway?.length ?? 0} old snapshot(s) removed`,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* V3 CLI Memory Command
|
|
3
3
|
* Memory operations for AgentDB integration
|
|
4
4
|
*/
|
|
5
|
+
import { validateAppendConditions } from '../memory/append-conditions.js';
|
|
5
6
|
import { output } from '../output.js';
|
|
6
7
|
import { select, confirm, input } from '../prompt.js';
|
|
7
8
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
@@ -110,6 +111,10 @@ const storeCommand = {
|
|
|
110
111
|
type: 'boolean',
|
|
111
112
|
default: false
|
|
112
113
|
},
|
|
114
|
+
{ name: 'embedding', type: 'boolean', description: 'Generate an embedding (default true; --no-embedding stores structured data only)' },
|
|
115
|
+
{ name: 'append-conditions', type: 'string', description: 'JSON predicates checked atomically with a native append-only write' },
|
|
116
|
+
{ name: 'require-native', type: 'boolean', description: 'Refuse whole-image and in-memory fallback memory writers' },
|
|
117
|
+
{ name: 'append-only', type: 'boolean', description: 'Immutable insert; never update or resurrect an existing key' },
|
|
113
118
|
{
|
|
114
119
|
name: 'upsert',
|
|
115
120
|
short: 'u',
|
|
@@ -233,7 +238,7 @@ const storeCommand = {
|
|
|
233
238
|
size: Buffer.byteLength(value, 'utf8')
|
|
234
239
|
};
|
|
235
240
|
output.printInfo(`Storing in ${namespace}/${key}...`);
|
|
236
|
-
// Use
|
|
241
|
+
// Use the canonical provider; embedding and native immutable append controls are explicit.
|
|
237
242
|
try {
|
|
238
243
|
const { storeEntry, resolveDbPath: _rdbStore } = await import('../memory/memory-initializer.js');
|
|
239
244
|
const dbPath = _rdbStore(ctx.flags.path);
|
|
@@ -244,7 +249,10 @@ const storeCommand = {
|
|
|
244
249
|
key,
|
|
245
250
|
value,
|
|
246
251
|
namespace,
|
|
247
|
-
generateEmbeddingFlag:
|
|
252
|
+
generateEmbeddingFlag: ctx.flags.embedding !== false,
|
|
253
|
+
requireNative: ctx.flags.requireNative === true,
|
|
254
|
+
appendOnly: ctx.flags.appendOnly === true,
|
|
255
|
+
...(typeof ctx.flags.appendConditions === 'string' ? { appendConditions: validateAppendConditions(JSON.parse(ctx.flags.appendConditions)) } : {}),
|
|
248
256
|
tags,
|
|
249
257
|
ttl,
|
|
250
258
|
upsert,
|
|
@@ -86,7 +86,15 @@ const listCommand = {
|
|
|
86
86
|
output.printError(result.error || 'Unknown error');
|
|
87
87
|
return { success: false, exitCode: 1 };
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
if (result.demo) {
|
|
90
|
+
// The signed registry was unavailable or failed verification (see ruvnet/ruflo#3211):
|
|
91
|
+
// say so instead of reporting a successful discovery over the built-in list.
|
|
92
|
+
spinner.stop(output.warning(`Signed plugin registry unavailable or unverified; showing the built-in list ` +
|
|
93
|
+
`(${result.registry.totalPlugins} plugins)`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
spinner.succeed(`Registry discovered: ${result.registry.totalPlugins} plugins available`);
|
|
97
|
+
}
|
|
90
98
|
output.writeln();
|
|
91
99
|
// Build search options
|
|
92
100
|
const searchOptions = {
|
|
@@ -165,6 +173,10 @@ const listCommand = {
|
|
|
165
173
|
output.writeln(output.dim('(ratings: cached — cloud unavailable)'));
|
|
166
174
|
}
|
|
167
175
|
output.writeln(output.dim(`Source: ${result.source}${result.fromCache ? ' (cached)' : ''}`));
|
|
176
|
+
if (result.demo) {
|
|
177
|
+
output.writeln(output.warning('Not from the signed registry: download counts, ratings and trust labels are the ' +
|
|
178
|
+
"CLI's built-in defaults, not verified registry data."));
|
|
179
|
+
}
|
|
168
180
|
if (result.cid) {
|
|
169
181
|
output.writeln(output.dim(`Registry CID: ${result.cid.slice(0, 30)}...`));
|
|
170
182
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anchorPosture } from '../services/policy-ledger-anchor.js';
|
|
1
2
|
import { output } from '../output.js';
|
|
2
3
|
import { autoMigratePolicyStateIfNeeded, evaluatePolicyRequest, loadPolicyState, revokePolicyApproval, setPolicyBudget, setPolicyMode, upsertPolicyRule, verifyPolicyLedger, } from '../services/policy-runtime.js';
|
|
3
4
|
function argJson(value, label) {
|
|
@@ -19,12 +20,16 @@ function requireInteractiveAdministrator() {
|
|
|
19
20
|
throw new Error('policy administration requires an interactive local terminal');
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
// #3602: say what verify can and cannot prove.
|
|
24
|
+
const LEDGER_TRUST_SCOPE = 'detects edits confined to state.json or to the project directory; '
|
|
25
|
+
+ 'does not defend against an attacker who can rewrite both the project directory and ~/.config/ruflo (ADR-475)';
|
|
22
26
|
export const policyCommand = {
|
|
23
27
|
name: 'policy',
|
|
24
28
|
description: 'Agentic policy engine — evaluate actions, manage rules/approvals, and verify the decision ledger (ADR-324)',
|
|
25
29
|
options: [
|
|
26
30
|
{ name: 'mode', type: 'string', description: 'Policy mode: legacy | observe | enforce' },
|
|
27
31
|
{ name: 'project-root', type: 'string', description: 'Project root containing .claude-flow/policy' },
|
|
32
|
+
{ name: 'establish-anchor', type: 'boolean', description: 'verify: explicitly anchor a ledger that has receipts but no anchor (interactive, logged with user and time)' },
|
|
28
33
|
],
|
|
29
34
|
async action(context) {
|
|
30
35
|
const args = context.args ?? [];
|
|
@@ -87,8 +92,11 @@ export const policyCommand = {
|
|
|
87
92
|
return print({ receipts: state.receipts });
|
|
88
93
|
}
|
|
89
94
|
if (operation === 'verify') {
|
|
90
|
-
const
|
|
91
|
-
|
|
95
|
+
const establishAnchor = flags['establish-anchor'] === true || flags.establishAnchor === true;
|
|
96
|
+
if (establishAnchor)
|
|
97
|
+
requireInteractiveAdministrator();
|
|
98
|
+
const ledger = await verifyPolicyLedger(root, { establishAnchor });
|
|
99
|
+
const result = print({ ...ledger, anchors: anchorPosture(root), scope: LEDGER_TRUST_SCOPE });
|
|
92
100
|
return ledger.valid ? result : { ...result, success: false, exitCode: 1 };
|
|
93
101
|
}
|
|
94
102
|
throw new Error(`unknown policy operation: ${operation}`);
|
|
@@ -339,6 +339,18 @@ const statusCommand = {
|
|
|
339
339
|
includeLogs: ctx.flags.logs,
|
|
340
340
|
includeMetrics: true
|
|
341
341
|
});
|
|
342
|
+
// The MCP tool answers an unknown id with status "not_found" and no other fields.
|
|
343
|
+
// Rendering that as a record printed "Task: undefined", "Invalid Date" and
|
|
344
|
+
// "undefined%" and exited 0, so scripts and agents took it as success.
|
|
345
|
+
if ((result.status) === 'not_found') {
|
|
346
|
+
if (ctx.flags.format === 'json') {
|
|
347
|
+
output.printJson(result);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
output.printError(`Task not found: ${taskId}`);
|
|
351
|
+
}
|
|
352
|
+
return { success: false, exitCode: 1 };
|
|
353
|
+
}
|
|
342
354
|
if (ctx.flags.format === 'json') {
|
|
343
355
|
output.printJson(result);
|
|
344
356
|
return { success: true, data: result };
|
|
@@ -51,9 +51,9 @@ export function generateSettings(options) {
|
|
|
51
51
|
pr: '🤖 Generated with [RuFlo](https://github.com/ruvnet/ruflo)',
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
54
|
+
// #3527: deliberately no settings.model. Project settings outrank the user's
|
|
55
|
+
// own settings, so a pin here would silently override the model the user chose.
|
|
56
|
+
// Ruflo's own routing preferences live in claudeFlow.modelPreferences below.
|
|
57
57
|
// Add Agent Teams configuration (experimental feature)
|
|
58
58
|
settings.env = {
|
|
59
59
|
// Enable Claude Code Agent Teams for multi-agent coordination
|
package/dist/src/mcp-server.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface MCPServerOptions {
|
|
|
30
30
|
daemonize?: boolean;
|
|
31
31
|
timeout?: number;
|
|
32
32
|
requestTimeoutMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Bearer token every HTTP request must present (`Authorization: Bearer <token>`).
|
|
35
|
+
* http transport only. Prefer RUFLO_MCP_HTTP_TOKEN / a token file over this
|
|
36
|
+
* field's CLI flag so the secret stays out of argv. Never logged or emitted.
|
|
37
|
+
*/
|
|
38
|
+
authToken?: string;
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
41
|
* MCP Server status
|
|
@@ -49,6 +55,33 @@ export interface MCPServerStatus {
|
|
|
49
55
|
metrics?: Record<string, number>;
|
|
50
56
|
};
|
|
51
57
|
}
|
|
58
|
+
/** Exported for tests — see LOOPBACK_HOSTS. */
|
|
59
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Explicit, documented opt-out of the non-loopback authorization gate in
|
|
62
|
+
* startHttpServer(). Exported for tests.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isUnauthenticatedHttpAllowed(env?: NodeJS.ProcessEnv): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The full startHttpServer() authorization gate, as a pure function:
|
|
67
|
+
* true means "refuse to start" (see the thrown error for why).
|
|
68
|
+
*
|
|
69
|
+
* A non-loopback bind is allowed when requests are authenticated (a bearer
|
|
70
|
+
* token is configured on the http transport) or the operator explicitly opts
|
|
71
|
+
* out with RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1. Loopback binds are never
|
|
72
|
+
* refused, with or without a token. Exported for tests.
|
|
73
|
+
*/
|
|
74
|
+
export declare function shouldRefuseUnauthenticatedHttp(host: string, env?: NodeJS.ProcessEnv, authenticated?: boolean): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the HTTP bearer token. Precedence: explicit value (CLI flag), token
|
|
77
|
+
* file, then RUFLO_MCP_HTTP_TOKEN. An unset/empty env var means "no token";
|
|
78
|
+
* a token that is set but malformed throws, so a typo can never silently
|
|
79
|
+
* start the server unauthenticated. Exported for tests.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveMcpHttpAuthToken(input?: {
|
|
82
|
+
token?: string;
|
|
83
|
+
tokenFile?: string;
|
|
84
|
+
}, env?: NodeJS.ProcessEnv, readFile?: (p: string) => string): string | undefined;
|
|
52
85
|
export declare function parseMcpToolSelection(value: string | undefined): string[] | 'all';
|
|
53
86
|
/**
|
|
54
87
|
* Apply the existing `--tools` contract to advertised schemas. A selector can
|
package/dist/src/mcp-server.js
CHANGED
|
@@ -104,8 +104,72 @@ function processStartToken(pid) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
107
|
+
* Hosts treated as same-machine-only for MCP HTTP tool-call authorization
|
|
108
|
+
* purposes (see startHttpServer's non-loopback authorization gate below).
|
|
109
|
+
* Deliberately exact-string matching only (no DNS resolution) so anything
|
|
110
|
+
* not spelled exactly one of these three fails closed rather than open.
|
|
108
111
|
*/
|
|
112
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
|
|
113
|
+
/** Exported for tests — see LOOPBACK_HOSTS. */
|
|
114
|
+
export function isLoopbackHost(host) {
|
|
115
|
+
return LOOPBACK_HOSTS.has(host);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Explicit, documented opt-out of the non-loopback authorization gate in
|
|
119
|
+
* startHttpServer(). Exported for tests.
|
|
120
|
+
*/
|
|
121
|
+
export function isUnauthenticatedHttpAllowed(env = process.env) {
|
|
122
|
+
const v = env.RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP;
|
|
123
|
+
return v === '1' || v === 'true';
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The full startHttpServer() authorization gate, as a pure function:
|
|
127
|
+
* true means "refuse to start" (see the thrown error for why).
|
|
128
|
+
*
|
|
129
|
+
* A non-loopback bind is allowed when requests are authenticated (a bearer
|
|
130
|
+
* token is configured on the http transport) or the operator explicitly opts
|
|
131
|
+
* out with RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1. Loopback binds are never
|
|
132
|
+
* refused, with or without a token. Exported for tests.
|
|
133
|
+
*/
|
|
134
|
+
export function shouldRefuseUnauthenticatedHttp(host, env = process.env, authenticated = false) {
|
|
135
|
+
return !isLoopbackHost(host) && !authenticated && !isUnauthenticatedHttpAllowed(env);
|
|
136
|
+
}
|
|
137
|
+
/** Printable ASCII without space (RFC 6750 token-ish), 16-512 chars. */
|
|
138
|
+
const AUTH_TOKEN_PATTERN = /^[\x21-\x7e]{16,512}$/;
|
|
139
|
+
/**
|
|
140
|
+
* Resolve the HTTP bearer token. Precedence: explicit value (CLI flag), token
|
|
141
|
+
* file, then RUFLO_MCP_HTTP_TOKEN. An unset/empty env var means "no token";
|
|
142
|
+
* a token that is set but malformed throws, so a typo can never silently
|
|
143
|
+
* start the server unauthenticated. Exported for tests.
|
|
144
|
+
*/
|
|
145
|
+
export function resolveMcpHttpAuthToken(input = {}, env = process.env, readFile = (p) => fs.readFileSync(p, 'utf8')) {
|
|
146
|
+
let raw;
|
|
147
|
+
let source = '';
|
|
148
|
+
if (input.token !== undefined) {
|
|
149
|
+
raw = input.token;
|
|
150
|
+
source = '--auth-token';
|
|
151
|
+
}
|
|
152
|
+
else if (input.tokenFile !== undefined) {
|
|
153
|
+
source = '--auth-token-file';
|
|
154
|
+
try {
|
|
155
|
+
raw = readFile(input.tokenFile).replace(/\r?\n$/, '');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
throw new Error('Could not read the MCP HTTP auth token file given to --auth-token-file');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (env.RUFLO_MCP_HTTP_TOKEN !== undefined && env.RUFLO_MCP_HTTP_TOKEN !== '') {
|
|
162
|
+
raw = env.RUFLO_MCP_HTTP_TOKEN;
|
|
163
|
+
source = 'RUFLO_MCP_HTTP_TOKEN';
|
|
164
|
+
}
|
|
165
|
+
if (raw === undefined)
|
|
166
|
+
return undefined;
|
|
167
|
+
if (!AUTH_TOKEN_PATTERN.test(raw)) {
|
|
168
|
+
// The value is deliberately not echoed.
|
|
169
|
+
throw new Error(`Invalid MCP HTTP auth token from ${source}: it must be 16-512 printable ASCII characters with no spaces`);
|
|
170
|
+
}
|
|
171
|
+
return raw;
|
|
172
|
+
}
|
|
109
173
|
const DEFAULT_OPTIONS = {
|
|
110
174
|
transport: 'stdio',
|
|
111
175
|
host: 'localhost',
|
|
@@ -239,7 +303,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
239
303
|
}
|
|
240
304
|
const startTime = performance.now();
|
|
241
305
|
this.startTime = new Date();
|
|
242
|
-
this.emit('starting', { options: this.options });
|
|
306
|
+
this.emit('starting', { options: { ...this.options, authToken: this.options.authToken ? '[redacted]' : undefined } });
|
|
243
307
|
try {
|
|
244
308
|
if (this.options.transport === 'stdio') {
|
|
245
309
|
// For stdio transport, spawn the server process
|
|
@@ -671,7 +735,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
671
735
|
}
|
|
672
736
|
}
|
|
673
737
|
try {
|
|
674
|
-
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
738
|
+
const result = await callMCPTool(toolName, toolParams, { sessionId, transport: 'stdio' });
|
|
675
739
|
const isError = hasToolError(result);
|
|
676
740
|
trackRequest(toolName, !isError);
|
|
677
741
|
return {
|
|
@@ -725,6 +789,31 @@ export class MCPServerManager extends EventEmitter {
|
|
|
725
789
|
* Start HTTP server in-process
|
|
726
790
|
*/
|
|
727
791
|
async startHttpServer() {
|
|
792
|
+
// Authorization gate first, before any import or bind: a refused start must
|
|
793
|
+
// not depend on (or touch) anything else.
|
|
794
|
+
const authToken = this.options.authToken || undefined;
|
|
795
|
+
if (authToken && this.options.transport !== 'http') {
|
|
796
|
+
// The standalone websocket transport has no working token check, so a
|
|
797
|
+
// token there would be a false promise of protection.
|
|
798
|
+
throw new Error('An MCP HTTP auth token is only supported with --transport http');
|
|
799
|
+
}
|
|
800
|
+
if (shouldRefuseUnauthenticatedHttp(this.options.host, process.env, !!authToken)) {
|
|
801
|
+
// No ToolAuthorizer is wired below: @claude-flow/mcp's tool-call
|
|
802
|
+
// authorization is fully opt-in (requireToolAuthorization/toolAuthorizer),
|
|
803
|
+
// so without this check every registered tool (memory_*, hooks_*,
|
|
804
|
+
// agentdb_*, hive-mind_*, ...) would be callable by any client that can
|
|
805
|
+
// reach this host:port, unauthenticated — the same shape as
|
|
806
|
+
// CVE-2026-81735 (CVSS 10/10, UI-TARS-desktop mcp-http-server: optional
|
|
807
|
+
// auth middleware never wired by the integrating CLI + a non-loopback
|
|
808
|
+
// bind). Loopback stays unaffected; this only gates an explicit
|
|
809
|
+
// non-default `--host`. A configured bearer token (below) lifts the gate.
|
|
810
|
+
throw new Error(`Refusing to start the MCP HTTP server on non-loopback host "${this.options.host}": ` +
|
|
811
|
+
'this server is unauthenticated unless a token is set, so every registered MCP tool ' +
|
|
812
|
+
'would be reachable over the network. Set RUFLO_MCP_HTTP_TOKEN (or --auth-token-file) ' +
|
|
813
|
+
'to require a bearer token, bind to a loopback host (127.0.0.1, ::1, or localhost), or set ' +
|
|
814
|
+
'RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1 to acknowledge the risk and proceed ' +
|
|
815
|
+
'(e.g. when a trusted reverse proxy or network boundary already enforces auth).');
|
|
816
|
+
}
|
|
728
817
|
// Dynamically import the MCP server package
|
|
729
818
|
// FIX for issue #942: Use proper package import instead of broken relative path
|
|
730
819
|
const { createMCPServer } = await import('@claude-flow/mcp');
|
|
@@ -747,7 +836,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
747
836
|
cacheTTL: tool.cacheTTL,
|
|
748
837
|
handler: async (input, context) => {
|
|
749
838
|
try {
|
|
750
|
-
const result = await callMCPTool(tool.name, input || {}, { sessionId: context?.sessionId || fallbackSessionId });
|
|
839
|
+
const result = await callMCPTool(tool.name, input || {}, { sessionId: context?.sessionId || fallbackSessionId, transport: this.options.transport === 'websocket' ? 'websocket' : 'http' });
|
|
751
840
|
trackRequest(tool.name, !hasToolError(result));
|
|
752
841
|
return result;
|
|
753
842
|
}
|
|
@@ -770,6 +859,9 @@ export class MCPServerManager extends EventEmitter {
|
|
|
770
859
|
enableMetrics: true,
|
|
771
860
|
enableCaching: true,
|
|
772
861
|
requestTimeout: this.options.requestTimeoutMs,
|
|
862
|
+
auth: authToken
|
|
863
|
+
? { enabled: true, method: 'token', tokens: [authToken] }
|
|
864
|
+
: undefined,
|
|
773
865
|
}, logger);
|
|
774
866
|
const registration = mcpServer.registerTools(cliTools);
|
|
775
867
|
if (registration.failed.length > 0) {
|