toolfunnel 0.6.0 → 0.7.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.
Files changed (72) hide show
  1. package/README.md +228 -231
  2. package/SECURITY.md +84 -0
  3. package/bin/toolfunnel.js +451 -372
  4. package/docs/KNOWN_BUGS.md +80 -99
  5. package/docs/MANUAL.pdf +0 -0
  6. package/docs/design.md +84 -70
  7. package/docs/packaging.md +115 -115
  8. package/hooks/hooks.manifest.json +27 -27
  9. package/hooks/scripts/example-audit.js +21 -21
  10. package/hooks/scripts/example-deny-dangerous.js +39 -31
  11. package/mcp/expose.example.json +30 -30
  12. package/mcp/servers/mock-modern/server.js +147 -147
  13. package/mcp/servers/mock-upstream/server.js +380 -350
  14. package/package.json +3 -1
  15. package/src/auth/config.js +174 -174
  16. package/src/auth/install.js +181 -181
  17. package/src/auth/resource-server.js +353 -344
  18. package/src/core/child-reaper.js +61 -0
  19. package/src/core/config-home.js +134 -134
  20. package/src/core/events.js +105 -105
  21. package/src/core/hook-engine.js +329 -275
  22. package/src/core/hook-loader.js +824 -791
  23. package/src/core/hook-runner.js +566 -346
  24. package/src/core/logger.js +297 -171
  25. package/src/core/matcher.js +100 -51
  26. package/src/core/metrics.js +75 -75
  27. package/src/core/rename-migration.js +163 -0
  28. package/src/core/requires.js +138 -138
  29. package/src/core/server-config.js +98 -98
  30. package/src/extend/add-hook.md +152 -152
  31. package/src/extend/add-mcp.md +119 -122
  32. package/src/extend/configure.md +88 -87
  33. package/src/extend/create-tool.md +168 -168
  34. package/src/extend/howto.js +102 -102
  35. package/src/extend/package.md +190 -187
  36. package/src/extend/wrap.md +99 -99
  37. package/src/mcp/aggregator.js +1719 -1246
  38. package/src/mcp/expose-store.js +577 -460
  39. package/src/mcp/gated-run.js +210 -201
  40. package/src/mcp/http-transport.js +1563 -1311
  41. package/src/mcp/mcp-client.js +1070 -907
  42. package/src/mcp/modern.js +406 -372
  43. package/src/mcp/protocol.js +468 -468
  44. package/src/mcp/server.js +3112 -2688
  45. package/src/tools/registry.js +661 -513
  46. package/src/tools/tool-state.js +170 -170
  47. package/src/ui/public/app.js +1187 -1174
  48. package/src/ui/public/index.html +286 -286
  49. package/src/ui/public/styles.css +535 -535
  50. package/src/ui/server.js +1616 -1348
  51. package/tools/scripts/base64.js +139 -139
  52. package/tools/scripts/danger.js +61 -61
  53. package/tools/scripts/echo.js +39 -39
  54. package/tools/scripts/hash.js +101 -101
  55. package/tools/scripts/json.js +109 -109
  56. package/tools/scripts/text-stats.js +131 -131
  57. package/tools/scripts/tf-env.js +43 -43
  58. package/tools/scripts/tf-hook-add.js +151 -151
  59. package/tools/scripts/tf-hook-set.js +120 -112
  60. package/tools/scripts/tf-list.js +192 -192
  61. package/tools/scripts/tf-log.js +86 -86
  62. package/tools/scripts/tf-mcp-add.js +117 -117
  63. package/tools/scripts/tf-mcp-set.js +121 -121
  64. package/tools/scripts/tf-pack.js +237 -237
  65. package/tools/scripts/tf-tool-add.js +134 -134
  66. package/tools/scripts/tf-tool-set.js +142 -142
  67. package/tools/scripts/tf-wrap.js +103 -103
  68. package/tools/scripts/uuid.js +102 -102
  69. package/tools/tools.register.json +227 -227
  70. package/src/core/tool-registry.js +0 -180
  71. package/src/packages/packager.js +0 -715
  72. package/src/tools/drift.js +0 -138
@@ -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 };