toolfunnel 0.5.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.
Files changed (72) hide show
  1. package/README.md +235 -167
  2. package/SECURITY.md +84 -0
  3. package/bin/toolfunnel.js +451 -147
  4. package/docs/KNOWN_BUGS.md +80 -33
  5. package/docs/MANUAL.pdf +0 -0
  6. package/docs/design.md +84 -0
  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 -0
  13. package/mcp/servers/mock-upstream/server.js +222 -12
  14. package/package.json +67 -65
  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 -77
  30. package/src/extend/add-hook.md +152 -142
  31. package/src/extend/add-mcp.md +119 -121
  32. package/src/extend/configure.md +88 -0
  33. package/src/extend/create-tool.md +168 -152
  34. package/src/extend/howto.js +102 -98
  35. package/src/extend/package.md +190 -187
  36. package/src/extend/wrap.md +99 -0
  37. package/src/mcp/aggregator.js +1674 -807
  38. package/src/mcp/expose-store.js +577 -430
  39. package/src/mcp/gated-run.js +210 -194
  40. package/src/mcp/http-transport.js +1563 -1012
  41. package/src/mcp/mcp-client.js +1070 -530
  42. package/src/mcp/modern.js +406 -0
  43. package/src/mcp/protocol.js +468 -466
  44. package/src/mcp/server.js +3112 -1405
  45. package/src/tools/registry.js +661 -513
  46. package/src/tools/tool-state.js +170 -104
  47. package/src/ui/public/app.js +1187 -1055
  48. package/src/ui/public/index.html +286 -249
  49. package/src/ui/public/styles.css +535 -535
  50. package/src/ui/server.js +1616 -1190
  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 -102
  64. package/tools/scripts/tf-pack.js +237 -237
  65. package/tools/scripts/tf-tool-add.js +134 -129
  66. package/tools/scripts/tf-tool-set.js +142 -142
  67. package/tools/scripts/tf-wrap.js +103 -0
  68. package/tools/scripts/uuid.js +102 -102
  69. package/tools/tools.register.json +14 -3
  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 };