toolfunnel 0.3.0 → 0.5.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 (40) hide show
  1. package/README.md +167 -324
  2. package/bin/toolfunnel.js +39 -4
  3. package/docs/KNOWN_BUGS.md +33 -0
  4. package/docs/MANUAL.pdf +0 -0
  5. package/docs/packaging.md +115 -0
  6. package/package.json +3 -1
  7. package/src/auth/config.js +4 -2
  8. package/src/auth/resource-server.js +28 -3
  9. package/src/core/config-home.js +134 -0
  10. package/src/core/logger.js +4 -2
  11. package/src/core/requires.js +138 -0
  12. package/src/core/server-config.js +77 -0
  13. package/src/extend/create-tool.md +24 -4
  14. package/src/extend/package.md +33 -0
  15. package/src/mcp/aggregator.js +12 -7
  16. package/src/mcp/http-transport.js +71 -21
  17. package/src/mcp/mcp-client.js +9 -3
  18. package/src/mcp/server.js +181 -16
  19. package/src/packages/packager.js +5 -3
  20. package/src/tools/registry.js +47 -2
  21. package/src/ui/server.js +49 -3
  22. package/tools/scripts/tf-env.js +43 -0
  23. package/tools/scripts/tf-hook-add.js +4 -5
  24. package/tools/scripts/tf-hook-set.js +4 -5
  25. package/tools/scripts/tf-list.js +9 -12
  26. package/tools/scripts/tf-log.js +4 -1
  27. package/tools/scripts/tf-mcp-add.js +4 -6
  28. package/tools/scripts/tf-mcp-set.js +4 -6
  29. package/tools/scripts/tf-pack.js +237 -0
  30. package/tools/scripts/tf-tool-add.js +16 -16
  31. package/tools/scripts/tf-tool-set.js +5 -11
  32. package/tools/tools.register.json +46 -1
  33. package/docs/admin-ui.md +0 -404
  34. package/docs/configuration.md +0 -402
  35. package/docs/hooks-and-gating.md +0 -428
  36. package/docs/logging.md +0 -223
  37. package/docs/management.md +0 -429
  38. package/docs/oauth.md +0 -103
  39. package/docs/setup.md +0 -352
  40. package/docs/tools-and-upstreams.md +0 -469
package/docs/admin-ui.md DELETED
@@ -1,404 +0,0 @@
1
- # Admin UI — the management console
2
-
3
- ToolFunnel ships an **optional** local web console for viewing, searching, and configuring
4
- everything the gateway exposes — first-party tools, upstream MCP connections, lifecycle hooks,
5
- and the activity log — without hand-editing JSON. It is a convenience layer, not a dependency:
6
- the gateway runs perfectly with the UI never started.
7
-
8
- The console is built from vanilla HTML/CSS/JS served by a zero-dependency `node:http` server
9
- (`src/ui/server.js`). There is nothing to `npm install`, no framework, no external fonts or
10
- CDN — it works fully offline. The theme is dark and the whole app is a single page with five
11
- tabs.
12
-
13
- The most important thing to understand up front: **the UI is a front end over the same config
14
- files the CLI and the management functions write.** It does not keep its own state. Every read
15
- goes through the gateway's own stores and every write is atomic (temp file + rename) into the
16
- same JSON the running server reads:
17
-
18
- | Tab | Reads / writes | Same store the CLI uses |
19
- |------------|-----------------------------------------|---------------------------------------|
20
- | Tools | `tools/tools.register.json`, `tools/tools.state.json` | `tf_tool_add` / `tf_tool_set` |
21
- | MCPs | `mcp/expose.json` | `tf_mcp_add` / `tf_mcp_set` |
22
- | Hooks | `hooks/hooks.manifest.json`, `hooks/hooks.state.json` | `tf_hook_add` / `tf_hook_set` |
23
- | Logs | `logs/log.config.json` + the JSONL log | `tf_log` |
24
- | Auth | `auth/auth.config.json` | `toolfunnel install-oauth` (install) |
25
-
26
- Because the running MCP server re-reads the register, state, and manifest on every call, a change
27
- you make in the UI is **live immediately — no restart**. A UI edit is byte-identical to the same
28
- edit made through `tf_tool_add`, `tf_mcp_add`, `tf_hook_add`, or `tf_log`. The reverse is also
29
- true: anything you change via the CLI/management functions shows the next time the UI reads
30
- (reload the page, switch tabs, or hit a tab's refresh). Each request reads fresh from disk.
31
-
32
- ---
33
-
34
- ## 1. Launching
35
-
36
- Start the console from the repo root:
37
-
38
- ```bash
39
- node bin/toolfunnel.js --ui
40
- ```
41
-
42
- It binds **`127.0.0.1:9777`** and prints the URL to stderr:
43
-
44
- ```
45
- [toolfunnel] config UI listening on http://127.0.0.1:9777
46
- ```
47
-
48
- Open that URL in a browser on the same machine.
49
-
50
- **Options:**
51
-
52
- ```bash
53
- node bin/toolfunnel.js --ui --port 9000 # bind a specific port (0 = OS-assigned ephemeral)
54
- node bin/toolfunnel.js --ui --host 127.0.0.1 # bind a specific loopback host
55
- ```
56
-
57
- **Loopback only — by design.** The server binds `127.0.0.1` by default and, as defence in depth,
58
- rejects any request whose `Host` header is not a recognised loopback name (`127.0.0.1`,
59
- `localhost`, `::1`) — a DNS-rebinding guard. The console is meant for the trusted human operator
60
- on the same machine, not remote access. (`--host` exists for advanced local setups, but the
61
- loopback boundary is the intended one; leave it at the default unless you have a reason.)
62
-
63
- **It runs in-process, with full authority.** Because it is loopback-only and operator-driven, the
64
- UI talks to the stores directly rather than through the gated MCP path. That means the console can
65
- do everything the (gated) management functions can — add/remove tools, switch execution modes,
66
- wire hooks — so treat access to the port as administrative access to the gateway's configuration.
67
-
68
- **Stopping it:** `Ctrl+C` (or `SIGTERM`) shuts it down cleanly. If the port is already in use the
69
- process prints `failed to start config UI: …` and exits — rerun with a different `--port`.
70
-
71
- **The header.** Across the top sits the brand and three live counts — **tools**, **mcps**,
72
- **hooks** — that refresh after every successful change so you can see the effect of an edit at a
73
- glance. Below that are the five tabs: **Tools** (open by default), **MCPs**, **Hooks**, **Logs**,
74
- **Auth**. The Tools list loads on boot; the other tabs load the first time you open them.
75
-
76
- Every action gives feedback: a short-lived **toast** at the bottom of the screen (green on
77
- success, red on error) reports what happened, and destructive actions (Remove) ask for an inline
78
- confirm first.
79
-
80
- ---
81
-
82
- ## 2. Tools tab
83
-
84
- This is where you manage the first-party tool register.
85
-
86
- ### Search
87
-
88
- The search box filters the list live (case-insensitive substring) across **name, summary,
89
- category, id, and mode**. When a filter is active the count beside the box shows `shown/total`.
90
- Clear the box to see everything again.
91
-
92
- ### Each tool row
93
-
94
- A row shows the tool's **name**, its **category** chip, its **id**, and a one-line **summary**,
95
- followed by a strip of controls:
96
-
97
- - **Mode toggle** (small switch, labelled with the current mode). Off = **reference**, on =
98
- **gateway**. This is the per-tool execution mode (see below).
99
- - **Pre / Post** (two small switches) — wire a `PreToolUse` / `PostToolUse` gate to this tool.
100
- - **Enabled** (the larger switch) — turn the tool on or off in the surface. A disabled tool's row
101
- dims.
102
- - **Details / edit** — expand an inline panel to view *and edit* everything about the tool (see
103
- below).
104
- - **Remove** — delete the tool from the register (with a confirm).
105
-
106
- #### Details / edit — view and change one tool
107
-
108
- Click **Details / edit** on any row to expand a lazy inline panel (loaded on first open from
109
- `GET /api/tools/detail?id=<id>`). It shows the tool's **full** entry — name, summary, category,
110
- instructions, the resolved invoke, and, for a **script** invoke, the **script body** read from
111
- `tools/scripts/`. The fields are editable: change the name, summary, category, instructions,
112
- execution mode, or the script source, then **Save** to write the change via
113
- `POST /api/tools/update`. The update **shallow-merges** your patch into the entry, re-validates it,
114
- and persists `tools/tools.register.json` atomically; a script edit is authored back under
115
- `tools/scripts/` (path-guarded) before the entry is saved. Because the running server reads fresh,
116
- the edit is live with no restart. An invalid edit (e.g. switching to `gateway` with no invoke)
117
- comes back as a red error toast and nothing is written.
118
-
119
- #### The execution-mode toggle (reference vs gateway)
120
-
121
- Every tool runs in one of two modes, and the switch flips between them live:
122
-
123
- - **reference** — the gateway only *describes* the tool. When the AI asks to run it, ToolFunnel
124
- hands back the instructions and the AI executes it in its own environment.
125
- - **gateway** — ToolFunnel *executes* the tool server-side itself, through the gated run path
126
- (which fires the Pre/Post hooks).
127
-
128
- Flipping the switch writes the new mode immediately. One thing to know: switching a **reference**
129
- tool that has *no invoke defined* over to **gateway** will fail — there is nothing for the gateway
130
- to run — and you'll get a red error toast explaining it. Give the tool a script or shell invoke
131
- first (re-add it, or edit the register / use the management tools), then switch.
132
-
133
- #### The Pre / Post hook toggles
134
-
135
- These wire a per-tool gate without touching the Hooks tab. Turning **Pre** on appends a
136
- `PreToolUse` hook to `hooks/hooks.manifest.json` whose matcher matches *this tool's gate name
137
- exactly*; **Post** does the same for `PostToolUse`. When you enable one, the toast reports the
138
- **script path** you now need to create — for example:
139
-
140
- ```
141
- hooks/scripts/<tool-id>-pretooluse.js
142
- ```
143
-
144
- The gate is wired, but **until that script file exists it has nothing to run.** Author the script
145
- at the reported path to define the behaviour (a `PreToolUse` script can block the call; a
146
- `PostToolUse` script observes the result). Turning the toggle off removes the matching gate entry
147
- again. These gates are what fire on every gateway-run and forwarded call.
148
-
149
- (These same gates also appear on the Hooks tab, shown read-only — see below.)
150
-
151
- ### Adding a tool
152
-
153
- Click **+ Add tool** to open the form. Fields:
154
-
155
- | Field | Notes |
156
- |----------------|-----------------------------------------------------------------------|
157
- | **id** * | Required, unique. The register key (e.g. `my_tool`). |
158
- | **name** * | Required. Display name. |
159
- | **category** | Optional grouping label (shown as a chip). |
160
- | **mode** | `gateway — run here` (default) or `reference — AI runs it`. |
161
- | **summary** | One-line description shown in the list. |
162
- | **invoke type**| `script` / `shell` / `none` — *gateway mode only*. |
163
- | **script path**| e.g. `scripts/my-tool.js` — *gateway + script only*. |
164
- | **shell command** | e.g. `echo hello` — *gateway + shell only*. |
165
- | **instructions** | How the connected AI should call this tool. |
166
- | **script body**| Optional inline source, authored under `tools/scripts/` — *gateway + script only*. |
167
-
168
- The form is mode-aware. Choose **reference** and the invoke fields disappear — a reference tool
169
- needs no invoke, just its metadata and instructions. Choose **gateway** and the invoke type
170
- selector appears: pick **script** to reveal the *script path* and *script body* fields, **shell**
171
- to reveal the *shell command* field, or **none** for a gateway entry with no invoke yet.
172
-
173
- A practical script tool, end to end:
174
-
175
- 1. mode = **gateway**, invoke type = **script**
176
- 2. **script path** = `scripts/my-tool.js`
177
- 3. paste the source into **script body** (it is written to `tools/scripts/my-tool.js` for you,
178
- before the tool is registered — the path is guarded to stay inside `tools/scripts/`)
179
- 4. fill **id**, **name**, a **summary**, and **instructions**
180
- 5. **Add tool**
181
-
182
- `id` and `name` are required (and a script invoke requires a path, a shell invoke a command) — the
183
- form blocks submission and toasts if one is missing. A duplicate `id` is rejected by the register
184
- with a clear error. New tools land in the register and appear in the list straight away.
185
-
186
- ### Enable / disable and remove
187
-
188
- The **Enabled** switch flips the tool's entry in `tools/tools.state.json` — a disabled tool stays
189
- in the register but drops out of the live surface. **Remove** deletes it from the register and
190
- clears its state-overlay key (so a future re-add of the same id starts clean); the inline confirm
191
- guards against a misclick.
192
-
193
- ---
194
-
195
- ## 3. MCPs tab
196
-
197
- This tab manages upstream MCP servers and the tools curated from them, stored in `mcp/expose.json`.
198
-
199
- ### Search
200
-
201
- Filters the upstream list by **id, command, args, and description**, with the same `shown/total`
202
- counter.
203
-
204
- ### Each upstream row
205
-
206
- A row shows the upstream **id** (monospace), a **transport** badge (`stdio`), an
207
- **enabled/disabled** badge, the resolved **command line** (command + args), an optional
208
- **description**, and an **Exposed tools** block. Each exposed entry reads `tool → as` with an
209
- optional category chip and an on/off badge; an upstream with none shows *"No exposed tools."*
210
- Controls: an **Enabled** switch and a **Remove** button.
211
-
212
- ### Adding an upstream
213
-
214
- Click **+ Add MCP**. Fields:
215
-
216
- | Field | Notes |
217
- |---------------|----------------------------------------------------------------|
218
- | **id** * | Required, unique upstream id. |
219
- | **transport** | `stdio` (the supported transport). |
220
- | **command** * | Required executable, e.g. `node`. |
221
- | **args** | Space-separated, e.g. `path/to/server.js --flag`. |
222
- | **description** | What this upstream provides. |
223
-
224
- Below the basics is the **Exposed tools** subsection — the curated-direct selections. Click
225
- **+ expose** to add a row, and fill in:
226
-
227
- - **tool** — the upstream tool's name (required for the row to count)
228
- - **as** — optional rename for how it surfaces through the gateway (defaults to `<id>_<tool>`)
229
- - **category** — optional grouping label
230
-
231
- Add as many rows as you need (the **×** removes a row). On submit, ToolFunnel registers the
232
- upstream first, then attaches each expose selection to it. Leave the expose rows empty to add the
233
- upstream now and curate its tools later.
234
-
235
- `id` and `command` are required; a duplicate or otherwise invalid upstream is rejected with an
236
- error toast.
237
-
238
- ### Enable / disable and remove
239
-
240
- The **Enabled** switch turns the whole upstream on or off. **Remove** deletes the upstream **and
241
- cascades** — its exposed-tool entries go with it — behind a confirm. (Fine-grained editing of an
242
- individual expose entry after creation isn't surfaced in the UI; use the management tools / CLI
243
- for that. The badges on each expose row reflect its state read-only.)
244
-
245
- ---
246
-
247
- ## 4. Hooks tab
248
-
249
- This tab manages lifecycle hooks in `hooks/hooks.manifest.json` (the enabled state is overlaid
250
- from `hooks/hooks.state.json`).
251
-
252
- ### Search
253
-
254
- Filters by **id, event, matcher, and description**.
255
-
256
- ### Each hook row
257
-
258
- A row shows the hook **id** (monospace), its **event** chip, the **matcher** (or `* (all)` when
259
- blank), and an optional description. There are two kinds of row:
260
-
261
- - **Named hooks** (have an id) carry an **Enabled** switch and a **Remove** button.
262
- - **Per-tool gates** (the Pre/Post gates created on the Tools tab) have no id, so they show as
263
- `(tool gate)` with an **auto** badge and an enabled/disabled badge, plus a *"managed on Tools
264
- tab"* note. They are **read-only here** — toggle them from the tool's Pre/Post switches.
265
-
266
- ### The two shipped example hooks
267
-
268
- A fresh install ships **two example hooks, both disabled**, so this tab is never empty and you have
269
- working policy to copy:
270
-
271
- - `pre-tool-use/example-deny-dangerous` — a `PreToolUse` gate that **blocks** any call whose
272
- arguments contain an obviously-destructive shell pattern (`rm -rf`, `mkfs`, drive `format`, fork
273
- bomb). Script: `hooks/scripts/example-deny-dangerous.js`.
274
- - `post-tool-use/example-audit` — a `PostToolUse` observer that runs *after* a tool executes
275
- (advisory — it cannot un-run it) and receives the result on stdin; the stub just exits 0. Script:
276
- `hooks/scripts/example-audit.js`.
277
-
278
- They ship **disabled**, so the default install is still allow-all — nothing gates until you opt in.
279
- **To enable one:** flip its **Enabled** switch here (or set `"enabled": true` in
280
- `hooks/hooks.manifest.json`). Edit the script at the path above to encode your own policy. They are
281
- deliberately illustrative, not a security baseline.
282
-
283
- ### Adding a hook
284
-
285
- Click **+ Add hook**. Fields:
286
-
287
- | Field | Notes |
288
- |--------------|-------------------------------------------------------------------------|
289
- | **id** * | Required, unique (e.g. `pre-tool-use/my-gate`). |
290
- | **event** * | Dropdown of the six lifecycle events (see below). |
291
- | **matcher** | Regex to match tool/gate names; **blank = matches all**. |
292
- | **command** *| Required, e.g. `node "${HOOKS_DIR}/scripts/my-gate.js"`. |
293
- | **script body** | Optional inline source, authored under `hooks/scripts/`. |
294
-
295
- The **event** dropdown offers all six events, defaulting to `PreToolUse`:
296
-
297
- `SessionStart` · `UserPromptSubmit` · `PreToolUse` · `PostToolUse` · `Stop` · `PreCompact`
298
-
299
- Use the **matcher** to scope which tools a `PreToolUse`/`PostToolUse` gate fires for (e.g.
300
- `Bash|Write`); leave it blank to fire for all. The **command** is what runs when the hook fires —
301
- note the portable `${HOOKS_DIR}` token, which the loader expands to the real hooks directory at
302
- load time, so the manifest stays machine-independent. If you paste a **script body** and the
303
- command points at a `scripts/<file>`, that file is written under `hooks/scripts/` for you.
304
-
305
- **Important:** a newly added hook is created **disabled** — the toast says so ("disabled — enable
306
- it to fire"). Flip its **Enabled** switch when you're ready for it to run. This lets you stage a
307
- hook (and write its script) before it goes live.
308
-
309
- ### Enable / disable and remove
310
-
311
- The **Enabled** switch toggles the hook through the state overlay. **Remove** deletes the hook
312
- entry from the manifest — note its **script file is left in place** on disk (so you can re-add or
313
- reuse it). Both are guarded by the usual confirm on remove.
314
-
315
- ---
316
-
317
- ## 5. Logs tab
318
-
319
- ToolFunnel can record an append-only JSONL activity log of tool runs and gate decisions. It is
320
- **off by default**; this tab is the simplest place to turn it on and watch it.
321
-
322
- ### The on/off toggle
323
-
324
- At the top of the tab is a switch bound to the logger config (`logs/log.config.json`):
325
-
326
- - **Off** (default) — nothing is recorded. The status reads *"logging is off."*
327
- - **On** — flip the switch and recording starts immediately; the status reads *"logging is on"*
328
- and the resolved log file **path** is shown beside it.
329
-
330
- This is the same setting controlled by `tf_log {action: enable|disable|status}` and the
331
- `logs/log.config.json` file — flipping the switch here is identical to those.
332
-
333
- ### Viewing entries
334
-
335
- The list shows the **most recent ~100 records, newest first**. Hit **Refresh** to reload both the
336
- config and the latest entries (the log doesn't stream — refresh to pull new activity). Each entry
337
- has a **type badge** that tells you which kind of record it is:
338
-
339
- - **gate** — a Pre/Post hook decision. The status badge reads **allow** or **deny** (deny shown in
340
- red), and the secondary line shows the **reason** when one was given.
341
- - **tool** — a tool execution. The status badge reads **ok**, **fail**, or **blocked** (a blocked
342
- run is one a `PreToolUse` gate stopped; fail and blocked show in red), and the secondary line
343
- shows the execution **mode** and **duration in ms**.
344
- - **config** — a configuration change made through this UI (e.g. a tool toggled, an upstream added,
345
- OAuth enabled). The badge names the change `event`, and the secondary line shows the changed
346
- fields. These let you audit *who changed the gateway's posture and when* — see
347
- [`docs/logging.md`](logging.md).
348
- - **auth** — an OAuth denial at the HTTP transport gate (a rejected token). The status badge reads
349
- **deny**, and the secondary line shows the `error` and HTTP `status` (401/403).
350
-
351
- All kinds also show the subject (tool/gate name, config event, or auth path) and a **timestamp**.
352
- When the log is off and empty the tab prompts you to flip the switch; when it's on but nothing has
353
- been recorded yet it says so.
354
-
355
- ---
356
-
357
- ## 6. Auth tab
358
-
359
- This tab configures the **optional** OAuth 2.1 resource-server validation, stored in
360
- `auth/auth.config.json`. ToolFunnel is loopback-only and unauthenticated by default; this tab is
361
- where you turn on bearer-token validation for a networked or multi-user deployment. The full
362
- reference — fields, security rationale, what is and isn't implemented — lives in
363
- [`docs/oauth.md`](oauth.md); this is just the UI walk-through.
364
-
365
- ### Installing the one dependency
366
-
367
- OAuth is the **only** feature that adds a dependency (`jose`, itself zero-dependency). The tab
368
- reports whether it is present. If it isn't, an **Install OAuth dependency** button appears — click
369
- it and the gateway installs the pinned `jose` on demand (`POST /api/oauth/install`). If the
370
- auto-install can't complete (an unusual Node layout, no network), the toast surfaces a copyable
371
- `npm install jose@<pin>` command to run yourself. Everyone who never enables OAuth pulls nothing.
372
-
373
- ### The config form
374
-
375
- - **Enable OAuth 2.1 resource-server validation** — the master switch. Enabling it while the config
376
- is incoherent, or while `jose` is absent, is refused with an explanatory toast (fail-fast — the
377
- same guard the HTTP host applies at start).
378
- - **issuer** — the authorization server tokens must come from (`iss`). Required when enabled.
379
- - **audience** — *this gateway's* resource URI (`aud`). Required when enabled — without it tokens
380
- are unbound (the confused-deputy hole).
381
- - **jwksUri** — explicit JWKS endpoint; blank = discover it from the issuer.
382
- - **algorithms**, **requiredScopes**, **clockToleranceSec** — the pinned algorithm allowlist,
383
- optional required scopes, and clock-skew tolerance.
384
-
385
- Saving writes `auth/auth.config.json` (`POST /api/auth/config`, atomic, read fresh per request — the
386
- change is live, no restart). The config is **off by default**; a missing file reads as disabled.
387
-
388
- ---
389
-
390
- ## Summary
391
-
392
- - The console is **optional** and **loopback-only**: `node bin/toolfunnel.js --ui` →
393
- `http://127.0.0.1:9777`.
394
- - Five tabs — **Tools / MCPs / Hooks / Logs / Auth** — each with live search (where it applies), a
395
- collapsible add form, and per-row controls.
396
- - Every edit writes the **same config files** the CLI and management functions use, atomically,
397
- and the running gateway picks it up **with no restart**.
398
- - Per-tool **execution mode** (reference ↔ gateway) and **Pre/Post gates** are toggled on the Tools
399
- tab; those gates also appear (read-only) on the Hooks tab. **Details / edit** opens an inline
400
- panel to view and change a tool's full entry (including its script body).
401
- - The **activity log** is off by default — turn it on from the Logs tab to watch gate decisions,
402
- tool runs, **config changes**, and **auth denials**.
403
- - The **Auth** tab configures the optional OAuth 2.1 resource server (and installs its single
404
- dependency on demand). See [`docs/oauth.md`](oauth.md).