ruvector 0.2.31 → 0.2.32
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/README.md +32 -0
- package/bin/cli.js +131 -0
- package/bin/mcp-policy.js +95 -0
- package/bin/mcp-server.js +21 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -91,6 +91,38 @@ claude mcp add ruvector -- npx ruvector mcp start
|
|
|
91
91
|
- `midstream_status`, `midstream_attractor`, `midstream_scheduler` — Streaming analysis
|
|
92
92
|
- `midstream_benchmark`, `midstream_search`, `midstream_health` — Latency benchmarks + health
|
|
93
93
|
|
|
94
|
+
**MCP tool-access policy (default-deny, ADR-256):** restrict the exposed/callable
|
|
95
|
+
tool surface with environment variables — useful for least-privilege deployments.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Only expose specific tools (everything else is denied)
|
|
99
|
+
RUVECTOR_MCP_ALLOW="hooks_route,hooks_recall" npx ruvector mcp start
|
|
100
|
+
|
|
101
|
+
# Block specific tools (deny wins over allow)
|
|
102
|
+
RUVECTOR_MCP_DENY="hooks_force_learn" npx ruvector mcp start
|
|
103
|
+
|
|
104
|
+
# Apply a curated read-only profile (safe, non-mutating subset)
|
|
105
|
+
RUVECTOR_MCP_PROFILE=readonly npx ruvector mcp start
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Precedence is **DENY > ALLOW/PROFILE > allow-all**. With no policy set, all tools
|
|
109
|
+
are available (backward compatible). Inspect the active posture with
|
|
110
|
+
`npx ruvector harness status --json` (see `mcp.accessControl`).
|
|
111
|
+
|
|
112
|
+
### Harness Router (ADR-256)
|
|
113
|
+
|
|
114
|
+
`ruvector harness` surfaces the unified routing/agentic primitives ruvector ships —
|
|
115
|
+
cost-optimal model routing (Tiny Dancer), semantic routing, hooks routing, the MCP
|
|
116
|
+
server, witness-signed provenance, and SONA memory — in one place:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx ruvector harness status # human-readable surface + availability
|
|
120
|
+
npx ruvector harness status --json # structured, for tooling/CI
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Memory + learning loops use a stable namespace (default `ruvector`), overridable per
|
|
124
|
+
deployment with `RUVECTOR_MEMORY_NAMESPACE` and reported under `memory.namespace`.
|
|
125
|
+
|
|
94
126
|
### Brain AGI Commands
|
|
95
127
|
|
|
96
128
|
Access all 8 AGI subsystems deployed at π.ruv.io:
|
package/bin/cli.js
CHANGED
|
@@ -10101,6 +10101,137 @@ const optimizeCmd = program.command('optimize')
|
|
|
10101
10101
|
console.log('');
|
|
10102
10102
|
});
|
|
10103
10103
|
|
|
10104
|
+
// =============================================================================
|
|
10105
|
+
// Harness Commands - unified "harness router" surface (ADR-256)
|
|
10106
|
+
// Borrows metaharness concepts using primitives ruvector already ships:
|
|
10107
|
+
// cost router (tiny-dancer) + semantic router + hooks routing + MCP + witness
|
|
10108
|
+
// Read-only status surface; degrades gracefully when optional deps are absent.
|
|
10109
|
+
// =============================================================================
|
|
10110
|
+
|
|
10111
|
+
function buildHarnessSurface() {
|
|
10112
|
+
const primitives = {};
|
|
10113
|
+
|
|
10114
|
+
// Cost-optimal model router — Tiny Dancer FastGRNN (ADR-252)
|
|
10115
|
+
try {
|
|
10116
|
+
const td = require('@ruvector/tiny-dancer');
|
|
10117
|
+
primitives.costRouter = {
|
|
10118
|
+
name: '@ruvector/tiny-dancer',
|
|
10119
|
+
role: 'cost-optimal model routing (cheap vs strong)',
|
|
10120
|
+
available: true,
|
|
10121
|
+
version: typeof td.version === 'function' ? td.version() : null,
|
|
10122
|
+
usage: 'npx ruvector tiny-dancer score <model> --query <embedding>',
|
|
10123
|
+
};
|
|
10124
|
+
} catch {
|
|
10125
|
+
primitives.costRouter = {
|
|
10126
|
+
name: '@ruvector/tiny-dancer',
|
|
10127
|
+
role: 'cost-optimal model routing (cheap vs strong)',
|
|
10128
|
+
available: false,
|
|
10129
|
+
install: 'npm install @ruvector/tiny-dancer',
|
|
10130
|
+
};
|
|
10131
|
+
}
|
|
10132
|
+
|
|
10133
|
+
// Semantic intent router — @ruvector/router / ruvector-router-core
|
|
10134
|
+
let semanticAvailable = false;
|
|
10135
|
+
try { require.resolve('@ruvector/router'); semanticAvailable = true; } catch { semanticAvailable = false; }
|
|
10136
|
+
primitives.semanticRouter = {
|
|
10137
|
+
name: '@ruvector/router',
|
|
10138
|
+
role: 'semantic intent routing',
|
|
10139
|
+
available: semanticAvailable,
|
|
10140
|
+
...(semanticAvailable ? { usage: 'npx ruvector router --route "<text>"' } : { install: 'npm install @ruvector/router' }),
|
|
10141
|
+
};
|
|
10142
|
+
|
|
10143
|
+
// Multi-tier intelligence routing — bundled (ADR-026)
|
|
10144
|
+
primitives.hooksRouting = {
|
|
10145
|
+
name: 'hooks route',
|
|
10146
|
+
role: '3-tier task→agent/model routing (ADR-026)',
|
|
10147
|
+
available: true,
|
|
10148
|
+
usage: 'npx ruvector hooks route "<task>"',
|
|
10149
|
+
};
|
|
10150
|
+
|
|
10151
|
+
// Agentic tool surface — bundled MCP server (with ADR-256 default-deny policy)
|
|
10152
|
+
const mcpPath = path.join(__dirname, 'mcp-server.js');
|
|
10153
|
+
let mcpPolicy = { configured: false };
|
|
10154
|
+
try {
|
|
10155
|
+
const { buildToolPolicy } = require('./mcp-policy.js');
|
|
10156
|
+
const p = buildToolPolicy(process.env);
|
|
10157
|
+
mcpPolicy = {
|
|
10158
|
+
configured: p.configured,
|
|
10159
|
+
profile: p.profile || null,
|
|
10160
|
+
allow: p.allowSet ? p.allowSet.size : 0,
|
|
10161
|
+
deny: p.deny.size,
|
|
10162
|
+
};
|
|
10163
|
+
} catch { /* policy module optional */ }
|
|
10164
|
+
primitives.mcp = {
|
|
10165
|
+
name: 'mcp-server',
|
|
10166
|
+
role: 'agentic tool surface (Model Context Protocol)',
|
|
10167
|
+
available: fs.existsSync(mcpPath),
|
|
10168
|
+
usage: 'npx ruvector mcp start',
|
|
10169
|
+
policy: mcpPolicy,
|
|
10170
|
+
accessControl: mcpPolicy.configured ? 'default-deny (configured)' : 'allow-all (set RUVECTOR_MCP_ALLOW/PROFILE)',
|
|
10171
|
+
};
|
|
10172
|
+
|
|
10173
|
+
// Signed provenance — witness chain (ADR-103 / ADR-134)
|
|
10174
|
+
primitives.witness = {
|
|
10175
|
+
name: 'witness-chain',
|
|
10176
|
+
role: 'signed provenance / release signing (ADR-103, ADR-134)',
|
|
10177
|
+
available: true,
|
|
10178
|
+
};
|
|
10179
|
+
|
|
10180
|
+
// Memory + learning loops — SONA / ReasoningBank (stable namespace, ADR-256 step 3)
|
|
10181
|
+
primitives.memory = {
|
|
10182
|
+
name: 'sona+reasoningbank',
|
|
10183
|
+
role: 'persistent memory + self-learning loops',
|
|
10184
|
+
available: true,
|
|
10185
|
+
namespace: (process.env.RUVECTOR_MEMORY_NAMESPACE || 'ruvector').trim() || 'ruvector',
|
|
10186
|
+
};
|
|
10187
|
+
|
|
10188
|
+
const values = Object.values(primitives);
|
|
10189
|
+
return {
|
|
10190
|
+
adr: 'ADR-256',
|
|
10191
|
+
decision: 'borrow metaharness concepts using primitives ruvector already ships',
|
|
10192
|
+
primitives,
|
|
10193
|
+
summary: {
|
|
10194
|
+
available: values.filter((p) => p.available).length,
|
|
10195
|
+
total: values.length,
|
|
10196
|
+
},
|
|
10197
|
+
};
|
|
10198
|
+
}
|
|
10199
|
+
|
|
10200
|
+
const harnessCmd = program
|
|
10201
|
+
.command('harness')
|
|
10202
|
+
.description('Unified "harness router" surface — cost router + semantic router + hooks routing + MCP + witness (ADR-256)');
|
|
10203
|
+
|
|
10204
|
+
function printHarnessStatus(opts) {
|
|
10205
|
+
const surface = buildHarnessSurface();
|
|
10206
|
+
if (opts && opts.json) {
|
|
10207
|
+
console.log(JSON.stringify(surface, null, 2));
|
|
10208
|
+
return;
|
|
10209
|
+
}
|
|
10210
|
+
console.log(chalk.cyan('\n═══════════════════════════════════════════════════════════════'));
|
|
10211
|
+
console.log(chalk.cyan(' RuVector Harness Router (ADR-256)'));
|
|
10212
|
+
console.log(chalk.cyan('═══════════════════════════════════════════════════════════════\n'));
|
|
10213
|
+
console.log(chalk.gray(' ' + surface.decision + '\n'));
|
|
10214
|
+
for (const p of Object.values(surface.primitives)) {
|
|
10215
|
+
const badge = p.available ? chalk.green('● available') : chalk.yellow('○ optional ');
|
|
10216
|
+
console.log(` ${badge} ${chalk.white(p.name)}${p.version ? chalk.dim(' v' + p.version) : ''}`);
|
|
10217
|
+
console.log(` ${chalk.dim(p.role)}`);
|
|
10218
|
+
if (p.available && p.usage) console.log(` ${chalk.dim(p.usage)}`);
|
|
10219
|
+
if (!p.available && p.install) console.log(` ${chalk.dim('install: ' + p.install)}`);
|
|
10220
|
+
}
|
|
10221
|
+
console.log('');
|
|
10222
|
+
console.log(chalk.cyan(` ${surface.summary.available}/${surface.summary.total} primitives available\n`));
|
|
10223
|
+
}
|
|
10224
|
+
|
|
10225
|
+
harnessCmd
|
|
10226
|
+
.command('status')
|
|
10227
|
+
.alias('info')
|
|
10228
|
+
.description('Show the unified harness routing surface and primitive availability')
|
|
10229
|
+
.option('--json', 'Output as JSON')
|
|
10230
|
+
.action((opts) => printHarnessStatus(opts));
|
|
10231
|
+
|
|
10232
|
+
// Bare `ruvector harness` defaults to status
|
|
10233
|
+
harnessCmd.action(() => printHarnessStatus({}));
|
|
10234
|
+
|
|
10104
10235
|
program.parse();
|
|
10105
10236
|
|
|
10106
10237
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP tool-access policy (ADR-256 — default-deny posture).
|
|
5
|
+
*
|
|
6
|
+
* Borrows metaharness's "default-deny allowlist" security concept using a small,
|
|
7
|
+
* pure, testable module. Operators restrict the exposed/callable MCP tool surface
|
|
8
|
+
* via environment variables:
|
|
9
|
+
*
|
|
10
|
+
* RUVECTOR_MCP_ALLOW="hooks_route,hooks_recall" → only these are exposed/callable
|
|
11
|
+
* RUVECTOR_MCP_DENY="hooks_security_scan" → these are blocked (wins over allow)
|
|
12
|
+
* RUVECTOR_MCP_PROFILE=readonly → a curated safe subset
|
|
13
|
+
*
|
|
14
|
+
* Precedence: DENY > ALLOW/PROFILE > (default allow-all).
|
|
15
|
+
* With no policy configured, all registered tools are allowed (backward compatible);
|
|
16
|
+
* `policy.configured` is false so the server can nudge toward an allowlist.
|
|
17
|
+
*
|
|
18
|
+
* This module is dependency-free and side-effect-free so it can be unit-tested
|
|
19
|
+
* without spawning an MCP stdio server.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Curated read-only / low-risk tool subsets. */
|
|
23
|
+
const MCP_PROFILES = {
|
|
24
|
+
readonly: [
|
|
25
|
+
'ruvector',
|
|
26
|
+
'hooks_stats',
|
|
27
|
+
'hooks_recall',
|
|
28
|
+
'hooks_route',
|
|
29
|
+
'hooks_route_enhanced',
|
|
30
|
+
'hooks_suggest_context',
|
|
31
|
+
'hooks_capabilities',
|
|
32
|
+
'hooks_export',
|
|
33
|
+
'hooks_doctor',
|
|
34
|
+
'hooks_attention_info',
|
|
35
|
+
'hooks_gnn_info',
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Split a comma/space separated env list into a clean array. */
|
|
40
|
+
function parseList(value) {
|
|
41
|
+
if (!value || typeof value !== 'string') return [];
|
|
42
|
+
return value
|
|
43
|
+
.split(/[,\s]+/)
|
|
44
|
+
.map((s) => s.trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build a tool-access policy from an env-like object (defaults to process.env).
|
|
50
|
+
* Returns { allowSet: Set|null, deny: Set, profile: string|null, configured: bool }.
|
|
51
|
+
*/
|
|
52
|
+
function buildToolPolicy(env) {
|
|
53
|
+
const e = env || process.env;
|
|
54
|
+
const allow = parseList(e.RUVECTOR_MCP_ALLOW);
|
|
55
|
+
const deny = new Set(parseList(e.RUVECTOR_MCP_DENY));
|
|
56
|
+
const profileName = e.RUVECTOR_MCP_PROFILE && e.RUVECTOR_MCP_PROFILE.trim();
|
|
57
|
+
|
|
58
|
+
let allowSet = null;
|
|
59
|
+
if (profileName && MCP_PROFILES[profileName]) {
|
|
60
|
+
allowSet = new Set(MCP_PROFILES[profileName]);
|
|
61
|
+
}
|
|
62
|
+
if (allow.length) {
|
|
63
|
+
allowSet = new Set([...(allowSet || []), ...allow]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
allowSet,
|
|
68
|
+
deny,
|
|
69
|
+
profile: profileName && MCP_PROFILES[profileName] ? profileName : null,
|
|
70
|
+
// An unknown profile name with no allow/deny is treated as "not configured".
|
|
71
|
+
configured: Boolean(allowSet || deny.size),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Is a tool name permitted under the given policy? DENY always wins. */
|
|
76
|
+
function isToolAllowed(name, policy) {
|
|
77
|
+
if (!policy) return true;
|
|
78
|
+
if (policy.deny.has(name)) return false;
|
|
79
|
+
if (policy.allowSet) return policy.allowSet.has(name);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Filter a TOOLS array (objects with a `.name`) down to the allowed subset. */
|
|
84
|
+
function filterAllowedTools(tools, policy) {
|
|
85
|
+
if (!Array.isArray(tools)) return [];
|
|
86
|
+
return tools.filter((t) => t && isToolAllowed(t.name, policy));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
MCP_PROFILES,
|
|
91
|
+
parseList,
|
|
92
|
+
buildToolPolicy,
|
|
93
|
+
isToolAllowed,
|
|
94
|
+
filterAllowedTools,
|
|
95
|
+
};
|
package/bin/mcp-server.js
CHANGED
|
@@ -26,6 +26,10 @@ const path = require('path');
|
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const { execSync, execFileSync } = require('child_process');
|
|
28
28
|
|
|
29
|
+
// ADR-256: default-deny MCP tool-access policy (RUVECTOR_MCP_ALLOW/DENY/PROFILE)
|
|
30
|
+
const { buildToolPolicy, isToolAllowed, filterAllowedTools } = require('./mcp-policy.js');
|
|
31
|
+
const MCP_TOOL_POLICY = buildToolPolicy(process.env);
|
|
32
|
+
|
|
29
33
|
// ── Security Helpers ────────────────────────────────────────────────────────
|
|
30
34
|
|
|
31
35
|
/**
|
|
@@ -1692,15 +1696,30 @@ const TOOLS = [
|
|
|
1692
1696
|
}
|
|
1693
1697
|
];
|
|
1694
1698
|
|
|
1695
|
-
// List tools handler
|
|
1699
|
+
// List tools handler — only expose tools permitted by the access policy (ADR-256)
|
|
1696
1700
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1697
|
-
return { tools: TOOLS };
|
|
1701
|
+
return { tools: filterAllowedTools(TOOLS, MCP_TOOL_POLICY) };
|
|
1698
1702
|
});
|
|
1699
1703
|
|
|
1700
1704
|
// Call tool handler
|
|
1701
1705
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1702
1706
|
const { name, arguments: args } = request.params;
|
|
1703
1707
|
|
|
1708
|
+
// ADR-256 default-deny gate: refuse tools excluded by RUVECTOR_MCP_ALLOW/DENY/PROFILE
|
|
1709
|
+
if (!isToolAllowed(name, MCP_TOOL_POLICY)) {
|
|
1710
|
+
return {
|
|
1711
|
+
content: [{
|
|
1712
|
+
type: 'text',
|
|
1713
|
+
text: JSON.stringify({
|
|
1714
|
+
success: false,
|
|
1715
|
+
error: `Tool '${name}' is denied by the MCP access policy (ADR-256). ` +
|
|
1716
|
+
`Adjust RUVECTOR_MCP_ALLOW / RUVECTOR_MCP_DENY / RUVECTOR_MCP_PROFILE to permit it.`,
|
|
1717
|
+
}, null, 2),
|
|
1718
|
+
}],
|
|
1719
|
+
isError: true,
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1704
1723
|
try {
|
|
1705
1724
|
switch (name) {
|
|
1706
1725
|
case 'hooks_stats': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"description": "Self-learning vector database for Node.js \u2014 hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"ruvector": "./bin/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsc &&
|
|
11
|
+
"build": "tsc && node -e \"require('fs').cpSync('src/core/onnx','dist/core/onnx',{recursive:true})\"",
|
|
12
12
|
"verify-dist": "node scripts/verify-dist.js",
|
|
13
13
|
"prepack": "npm run build && npm run verify-dist",
|
|
14
14
|
"prepublishOnly": "npm run build && npm run verify-dist",
|
|
15
|
-
"test": "node test/integration.js && node test/cli-commands.js && node test/db-workflow.js && node test/sigterm-cleanup.js"
|
|
15
|
+
"test": "node test/integration.js && node test/cli-commands.js && node test/db-workflow.js && node test/sigterm-cleanup.js && node test/mcp-policy.js && node test/startup-budget.js"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"vector",
|