toolfunnel 0.6.0 → 0.7.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/README.md +11 -7
- package/SECURITY.md +84 -0
- package/bin/toolfunnel.js +97 -18
- package/docs/KNOWN_BUGS.md +71 -90
- package/docs/MANUAL.pdf +0 -0
- package/docs/design.md +24 -10
- package/hooks/hooks.manifest.json +1 -1
- package/hooks/scripts/example-deny-dangerous.js +12 -4
- package/mcp/servers/mock-upstream/server.js +36 -6
- package/package.json +67 -65
- package/src/auth/config.js +2 -2
- package/src/auth/resource-server.js +12 -3
- package/src/core/child-reaper.js +61 -0
- package/src/core/events.js +7 -7
- package/src/core/hook-engine.js +85 -31
- package/src/core/hook-loader.js +46 -13
- package/src/core/hook-runner.js +258 -38
- package/src/core/logger.js +138 -12
- package/src/core/matcher.js +59 -10
- package/src/core/rename-migration.js +163 -0
- package/src/core/requires.js +1 -1
- package/src/extend/add-hook.md +2 -2
- package/src/extend/add-mcp.md +3 -6
- package/src/extend/configure.md +2 -1
- package/src/extend/create-tool.md +1 -1
- package/src/extend/howto.js +3 -3
- package/src/extend/package.md +11 -8
- package/src/mcp/aggregator.js +480 -52
- package/src/mcp/expose-store.js +130 -13
- package/src/mcp/gated-run.js +26 -17
- package/src/mcp/http-transport.js +274 -22
- package/src/mcp/mcp-client.js +194 -31
- package/src/mcp/modern.js +44 -10
- package/src/mcp/server.js +513 -89
- package/src/tools/registry.js +172 -24
- package/src/ui/public/app.js +17 -4
- package/src/ui/server.js +297 -29
- package/tools/scripts/tf-hook-set.js +11 -3
- package/tools/tools.register.json +227 -227
- package/src/core/tool-registry.js +0 -180
- package/src/packages/packager.js +0 -715
- package/src/tools/drift.js +0 -138
package/src/tools/drift.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* drift.js - tool register drift detector.
|
|
5
|
-
*
|
|
6
|
-
* The register can go stale when entries are not maintained alongside the files
|
|
7
|
-
* they describe (a config file left unmaintained over time, tools missing). A
|
|
8
|
-
* behavioural "update it immediately" rule is fragile. This is the STRUCTURAL
|
|
9
|
-
* guard (fix-the-tool, not the discipline): scan the real scripts directory
|
|
10
|
-
* against the register and surface drift so it can never silently rot again.
|
|
11
|
-
*
|
|
12
|
-
* Mirrors the host's auto-detect (the folder is the source of truth for what
|
|
13
|
-
* EXISTS; the register carries config). Read-only - it never edits anything,
|
|
14
|
-
* inside or outside the gateway (honours the isolation rule: MAY READ the live
|
|
15
|
-
* scripts directory, MUST NOT write it).
|
|
16
|
-
*
|
|
17
|
-
* detectDrift({ registerPath, toolsDir }) -> {
|
|
18
|
-
* ok, registerPath, toolsDir,
|
|
19
|
-
* registeredCount, // distinct .js basenames referenced by the register
|
|
20
|
-
* onDiskCount, // tool files found under toolsDir
|
|
21
|
-
* missingFromRegister: [str], // on disk under toolsDir but NOT referenced by any entry
|
|
22
|
-
* deadEntries: [{id, path}], // entries whose referenced script no longer exists
|
|
23
|
-
* note,
|
|
24
|
-
* }
|
|
25
|
-
*
|
|
26
|
-
* Matching is by FILENAME (basename), case-insensitive: an entry "covers" a file
|
|
27
|
-
* if the file's basename appears anywhere in the entry's `path` or invoke.command
|
|
28
|
-
* (so grouped family entries that list their variants in `path` cover them).
|
|
29
|
-
*
|
|
30
|
-
* CommonJS, Node built-ins only.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const fs = require('node:fs');
|
|
34
|
-
const path = require('node:path');
|
|
35
|
-
|
|
36
|
-
// dirs/files that are not tools - never count as drift
|
|
37
|
-
const SKIP_DIRS = new Set(['.archive', 'node_modules', 'scripts', 'docs', 'setup']);
|
|
38
|
-
const TOOL_EXTS = new Set(['.js']);
|
|
39
|
-
// obvious non-tool scripts (probes/tests) - surfaced separately, not as "missing"
|
|
40
|
-
const NONTOOL_RE = /^(test_|_)/i;
|
|
41
|
-
|
|
42
|
-
/** Every .js basename (lowercased) referenced by the register's path + invoke fields. */
|
|
43
|
-
function registeredBasenames(data) {
|
|
44
|
-
const set = new Set();
|
|
45
|
-
const tools = Array.isArray(data && data.tools) ? data.tools : [];
|
|
46
|
-
const grab = (s) => {
|
|
47
|
-
if (typeof s !== 'string') return;
|
|
48
|
-
// filename run ending in .js - NO spaces in the class (so "wraps helper.js"
|
|
49
|
-
// yields "helper.js", not the whole phrase). path/dir prefixes are excluded too.
|
|
50
|
-
const m = s.match(/[A-Za-z0-9_.\-]+\.js/g);
|
|
51
|
-
if (m) for (const f of m) set.add(path.basename(f).toLowerCase());
|
|
52
|
-
};
|
|
53
|
-
for (const t of tools) {
|
|
54
|
-
grab(t.path);
|
|
55
|
-
if (t.invoke && typeof t.invoke.command === 'string') grab(t.invoke.command);
|
|
56
|
-
// a family entry may explicitly name the member files it covers
|
|
57
|
-
if (Array.isArray(t.covers)) for (const f of t.covers) {
|
|
58
|
-
if (typeof f === 'string') set.add(path.basename(f).toLowerCase());
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return set;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Recursively collect .js files under dir, skipping SKIP_DIRS and dotdirs. */
|
|
65
|
-
function scanTools(dir) {
|
|
66
|
-
const out = [];
|
|
67
|
-
let entries;
|
|
68
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_e) { return out; }
|
|
69
|
-
for (const e of entries) {
|
|
70
|
-
if (e.isDirectory()) {
|
|
71
|
-
if (SKIP_DIRS.has(e.name) || e.name.startsWith('.')) continue;
|
|
72
|
-
out.push(...scanTools(path.join(dir, e.name)));
|
|
73
|
-
} else if (e.isFile() && TOOL_EXTS.has(path.extname(e.name).toLowerCase())) {
|
|
74
|
-
out.push(path.join(dir, e.name));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function detectDrift({ registerPath, toolsDir } = {}) {
|
|
81
|
-
if (!registerPath || !toolsDir) {
|
|
82
|
-
return { ok: false, error: 'detectDrift: registerPath and toolsDir are required' };
|
|
83
|
-
}
|
|
84
|
-
let data;
|
|
85
|
-
try {
|
|
86
|
-
data = JSON.parse(fs.readFileSync(registerPath, 'utf8'));
|
|
87
|
-
} catch (err) {
|
|
88
|
-
return { ok: false, error: `cannot read/parse register: ${err.message}` };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const registered = registeredBasenames(data);
|
|
92
|
-
const onDiskFiles = scanTools(toolsDir);
|
|
93
|
-
const onDiskBasenames = new Set(onDiskFiles.map((f) => path.basename(f).toLowerCase()));
|
|
94
|
-
|
|
95
|
-
// on disk but not referenced by any register entry (skip obvious probes/tests)
|
|
96
|
-
const missingFromRegister = [];
|
|
97
|
-
const nonToolScripts = [];
|
|
98
|
-
for (const f of onDiskFiles) {
|
|
99
|
-
const base = path.basename(f);
|
|
100
|
-
const lower = base.toLowerCase();
|
|
101
|
-
if (registered.has(lower)) continue;
|
|
102
|
-
if (NONTOOL_RE.test(base)) { nonToolScripts.push(base); continue; }
|
|
103
|
-
missingFromRegister.push(base);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// entries whose script-invoke path references a file no longer present under scriptsRoot
|
|
107
|
-
const deadEntries = [];
|
|
108
|
-
const tools = Array.isArray(data.tools) ? data.tools : [];
|
|
109
|
-
for (const t of tools) {
|
|
110
|
-
const p = typeof t.path === 'string' ? t.path : '';
|
|
111
|
-
const cmd = t.invoke && typeof t.invoke.command === 'string' ? t.invoke.command : '';
|
|
112
|
-
const m = p.match(/[A-Za-z0-9_\-]+\.js/) || cmd.match(/[A-Za-z0-9_\-]+\.js/);
|
|
113
|
-
if (m && !onDiskBasenames.has(m[0].toLowerCase())) {
|
|
114
|
-
deadEntries.push({ id: t.id, path: p });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
missingFromRegister.sort();
|
|
119
|
-
nonToolScripts.sort();
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
ok: true,
|
|
123
|
-
registerPath,
|
|
124
|
-
toolsDir,
|
|
125
|
-
registeredCount: registered.size,
|
|
126
|
-
onDiskCount: onDiskFiles.length,
|
|
127
|
-
missingFromRegister,
|
|
128
|
-
deadEntries,
|
|
129
|
-
nonToolScripts,
|
|
130
|
-
note:
|
|
131
|
-
'Scope: scans toolsDir (the scripts root) only. Scripts that live outside it ' +
|
|
132
|
-
'may be referenced by the register but are not drift-checked here. Matching is ' +
|
|
133
|
-
'by filename. nonToolScripts (test_*/_*) are listed separately, not counted as ' +
|
|
134
|
-
'missing.',
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
module.exports = { detectDrift, registeredBasenames, scanTools };
|