toolfunnel 0.7.0 → 0.8.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 (73) hide show
  1. package/README.md +238 -235
  2. package/SECURITY.md +84 -84
  3. package/bin/toolfunnel.js +530 -451
  4. package/docs/KNOWN_BUGS.md +100 -80
  5. package/docs/MANUAL.pdf +0 -0
  6. package/docs/design.md +84 -84
  7. package/docs/packaging.md +116 -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 -39
  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 +379 -380
  14. package/package.json +67 -67
  15. package/src/auth/config.js +172 -174
  16. package/src/auth/install.js +181 -181
  17. package/src/auth/resource-server.js +358 -353
  18. package/src/core/atomic-write.js +97 -0
  19. package/src/core/child-reaper.js +61 -61
  20. package/src/core/config-home.js +226 -134
  21. package/src/core/events.js +105 -105
  22. package/src/core/hook-engine.js +304 -329
  23. package/src/core/hook-loader.js +766 -824
  24. package/src/core/hook-runner.js +615 -566
  25. package/src/core/logger.js +295 -297
  26. package/src/core/matcher.js +100 -100
  27. package/src/core/metrics.js +90 -75
  28. package/src/core/output-cap.js +31 -0
  29. package/src/core/rename-migration.js +178 -163
  30. package/src/core/requires.js +138 -138
  31. package/src/core/server-config.js +98 -98
  32. package/src/extend/add-hook.md +155 -152
  33. package/src/extend/add-mcp.md +119 -119
  34. package/src/extend/configure.md +88 -88
  35. package/src/extend/create-tool.md +168 -168
  36. package/src/extend/howto.js +102 -102
  37. package/src/extend/package.md +190 -190
  38. package/src/extend/wrap.md +99 -99
  39. package/src/mcp/aggregator.js +2248 -1674
  40. package/src/mcp/expose-store.js +755 -577
  41. package/src/mcp/gated-run.js +210 -210
  42. package/src/mcp/http-transport.js +1657 -1563
  43. package/src/mcp/mcp-client.js +1174 -1070
  44. package/src/mcp/modern.js +409 -406
  45. package/src/mcp/protocol.js +473 -468
  46. package/src/mcp/server.js +3317 -3112
  47. package/src/mcp/tool-approvals.js +396 -0
  48. package/src/tools/registry.js +607 -661
  49. package/src/tools/tool-state.js +170 -170
  50. package/src/ui/public/app.js +1246 -1187
  51. package/src/ui/public/index.html +286 -286
  52. package/src/ui/public/styles.css +535 -535
  53. package/src/ui/server.js +1742 -1616
  54. package/tools/scripts/base64.js +139 -139
  55. package/tools/scripts/danger.js +61 -61
  56. package/tools/scripts/echo.js +39 -39
  57. package/tools/scripts/hash.js +101 -101
  58. package/tools/scripts/json.js +109 -109
  59. package/tools/scripts/text-stats.js +131 -131
  60. package/tools/scripts/tf-accept-change.js +67 -0
  61. package/tools/scripts/tf-env.js +43 -43
  62. package/tools/scripts/tf-hook-add.js +151 -151
  63. package/tools/scripts/tf-hook-set.js +120 -120
  64. package/tools/scripts/tf-list.js +192 -192
  65. package/tools/scripts/tf-log.js +86 -86
  66. package/tools/scripts/tf-mcp-add.js +130 -117
  67. package/tools/scripts/tf-mcp-set.js +121 -121
  68. package/tools/scripts/tf-pack.js +237 -237
  69. package/tools/scripts/tf-tool-add.js +134 -134
  70. package/tools/scripts/tf-tool-set.js +142 -142
  71. package/tools/scripts/tf-wrap.js +103 -103
  72. package/tools/scripts/uuid.js +102 -102
  73. package/tools/tools.register.json +16 -5
@@ -1,99 +1,99 @@
1
- # How to wrap an MCP server (transparent passthrough)
2
-
3
- This is the instruction served by `toolfunnel_howto({ topic: "wrap" })`. It explains the
4
- **passthrough wrap**: making ONE attached upstream MCP's tools the gateway's ENTIRE surface, under
5
- their original names, with the upstream's own identity presented to the client - so the client
6
- cannot tell ToolFunnel is in the middle. Use it to keep a legacy server working with modern
7
- clients (or vice versa - all four era combinations work), or to put a policy gate in front of a
8
- server you didn't write.
9
-
10
- **What the client sees:** the wrapped server's own `serverInfo`, capabilities, instructions,
11
- tools, results, errors and notifications - verbatim. **What the wrapped server sees** (stdio,
12
- legacy clients): the real downstream client's identity, mirrored automatically. Mid-call prompts
13
- (elicitation), resource subscriptions, progress tokens and cancellations are all bridged across
14
- protocol eras. Wrapped tool calls wait up to 120 s by default, and a tool that reports progress
15
- keeps its call alive indefinitely; for silent tools that run longer, set `"timeoutMs"` on the
16
- upstream entry in `mcp/expose.json`.
17
-
18
- **What changes for YOU (the agent):** while a wrap is active, ALL ToolFunnel tools - the four
19
- meta-tools included - are hidden and uncallable. That includes `tf_wrap` itself. Read §3 before
20
- setting a wrap in-band.
21
-
22
- ---
23
-
24
- ## 1. Setting a wrap - three equivalent paths
25
-
26
- The wrap is one persisted field (`"passthrough"` in `tools/tools.state.json`); all three paths
27
- write the same state and survive restarts.
28
-
29
- **CLI (recommended for humans):**
30
-
31
- ```
32
- toolfunnel wrap <upstreamId> # wrap - probes the upstream's protocol era and reports it
33
- toolfunnel wrap <upstreamId> --as <name> # also present that name in the handshake (serverInfo)
34
- toolfunnel wrap # status - current wrap + attached upstream ids
35
- toolfunnel wrap --off # clear - restore the normal funnel surface
36
- ```
37
-
38
- Global flags may sit anywhere: `toolfunnel --config-dir <dir> wrap <upstreamId>` and
39
- `toolfunnel wrap <upstreamId> --config-dir <dir>` are equivalent.
40
-
41
- The wrap persists as one field in `tools/tools.state.json` in the config home -
42
- `{ "passthrough": "<upstreamId>" }` when set. Editing that file by hand is a valid last-resort
43
- undo: clearing the `passthrough` key (e.g. writing `{}` if nothing else is in the file) is the
44
- file-edit equivalent of `wrap --off`; a running host picks the change up live.
45
-
46
- **UI:** the MCPs tab → the Wrap button on the upstream's row (Unwrap on the same button while
47
- active). A banner shows the active wrap.
48
-
49
- **In-band (`tf_wrap`, via `toolfunnel_run_tool`):**
50
-
51
- ```
52
- { } → status: { ok, wrapping, upstreams }
53
- { "off": true } → clear
54
- { "upstream": "<id>", "confirm": true } → set (confirm REQUIRED - see §3)
55
- ```
56
-
57
- The upstream must already be attached and enabled (`toolfunnel_howto({ topic: "add-mcp" })` covers
58
- attaching one).
59
-
60
- ## 2. Verifying a wrap
61
-
62
- - `toolfunnel wrap` (or `tf_wrap {}` before wrapping) shows the current state.
63
- - After wrapping, a client's `initialize`/`server/discover` handshake presents the WRAPPED
64
- server's identity, and `tools/list` shows its tools under their original names.
65
- - The CLI wrap command prints an era report: whether the upstream speaks the modern
66
- (2026-07-28) protocol, the legacy family, or both.
67
-
68
- ## 3. The lockout - why `confirm: true` is required in-band
69
-
70
- Setting a wrap HIDES every ToolFunnel tool, including `tf_wrap`. An agent that sets a wrap
71
- in-band therefore loses the in-band path to undo it. Recovery is always available OUT-of-band:
72
-
73
- - `toolfunnel wrap --off` on the CLI, or
74
- - the UI's Unwrap button.
75
-
76
- `tf_wrap` refuses to set a wrap without `confirm: true` precisely so this is a deliberate,
77
- informed action. Do not set a wrap unless the operator asked for it or you can reach one of the
78
- two recovery paths.
79
-
80
- ## 4. Security - what wrapping changes
81
-
82
- Wrapping SUSPENDS the path-isolation guard for the wrapped upstream only (a wrap is an explicit
83
- "this server is my whole surface" declaration - e.g. a filesystem server serving a documents
84
- folder MUST reach outside the gateway root). The CLI and UI both print a security notice naming
85
- the outside paths when this applies. Two things stay true regardless:
86
-
87
- - **Every call still passes the PreToolUse gate.** Wrapping never bypasses policy.
88
- - **Funnel-mode upstreams keep the guard** - the suspension is wrap-scoped, nothing else.
89
-
90
- To restrict a wrapped server after wrapping: add a PreToolUse hook that blocks by tool/args
91
- (`toolfunnel_howto({ topic: "add-hook" })`), or disable individual tools (keyed by the surfaced
92
- name). The manual's "Wrapping & security" section covers the patterns.
93
-
94
- ## 5. Related settings
95
-
96
- - `legacyPin: true` on an upstream (in `mcp/expose.json`, or the UI's "Legacy pin" toggle) pins
97
- that upstream to the legacy protocol forever - the era probe is skipped. Opt-in, warns loudly.
98
- - The gateway's own presented identities (funnel mode / HTTP) are configured in
99
- `toolfunnel.json` - see `toolfunnel_howto({ topic: "configure" })`.
1
+ # How to wrap an MCP server (transparent passthrough)
2
+
3
+ This is the instruction served by `toolfunnel_howto({ topic: "wrap" })`. It explains the
4
+ **passthrough wrap**: making ONE attached upstream MCP's tools the gateway's ENTIRE surface, under
5
+ their original names, with the upstream's own identity presented to the client - so the client
6
+ cannot tell ToolFunnel is in the middle. Use it to keep a legacy server working with modern
7
+ clients (or vice versa - all four era combinations work), or to put a policy gate in front of a
8
+ server you didn't write.
9
+
10
+ **What the client sees:** the wrapped server's own `serverInfo`, capabilities, instructions,
11
+ tools, results, errors and notifications - verbatim. **What the wrapped server sees** (stdio,
12
+ legacy clients): the real downstream client's identity, mirrored automatically. Mid-call prompts
13
+ (elicitation), resource subscriptions, progress tokens and cancellations are all bridged across
14
+ protocol eras. Wrapped tool calls wait up to 120 s by default, and a tool that reports progress
15
+ keeps its call alive indefinitely; for silent tools that run longer, set `"timeoutMs"` on the
16
+ upstream entry in `mcp/expose.json`.
17
+
18
+ **What changes for YOU (the agent):** while a wrap is active, ALL ToolFunnel tools - the four
19
+ meta-tools included - are hidden and uncallable. That includes `tf_wrap` itself. Read §3 before
20
+ setting a wrap in-band.
21
+
22
+ ---
23
+
24
+ ## 1. Setting a wrap - three equivalent paths
25
+
26
+ The wrap is one persisted field (`"passthrough"` in `tools/tools.state.json`); all three paths
27
+ write the same state and survive restarts.
28
+
29
+ **CLI (recommended for humans):**
30
+
31
+ ```
32
+ toolfunnel wrap <upstreamId> # wrap - probes the upstream's protocol era and reports it
33
+ toolfunnel wrap <upstreamId> --as <name> # also present that name in the handshake (serverInfo)
34
+ toolfunnel wrap # status - current wrap + attached upstream ids
35
+ toolfunnel wrap --off # clear - restore the normal funnel surface
36
+ ```
37
+
38
+ Global flags may sit anywhere: `toolfunnel --config-dir <dir> wrap <upstreamId>` and
39
+ `toolfunnel wrap <upstreamId> --config-dir <dir>` are equivalent.
40
+
41
+ The wrap persists as one field in `tools/tools.state.json` in the config home -
42
+ `{ "passthrough": "<upstreamId>" }` when set. Editing that file by hand is a valid last-resort
43
+ undo: clearing the `passthrough` key (e.g. writing `{}` if nothing else is in the file) is the
44
+ file-edit equivalent of `wrap --off`; a running host picks the change up live.
45
+
46
+ **UI:** the MCPs tab → the Wrap button on the upstream's row (Unwrap on the same button while
47
+ active). A banner shows the active wrap.
48
+
49
+ **In-band (`tf_wrap`, via `toolfunnel_run_tool`):**
50
+
51
+ ```
52
+ { } → status: { ok, wrapping, upstreams }
53
+ { "off": true } → clear
54
+ { "upstream": "<id>", "confirm": true } → set (confirm REQUIRED - see §3)
55
+ ```
56
+
57
+ The upstream must already be attached and enabled (`toolfunnel_howto({ topic: "add-mcp" })` covers
58
+ attaching one).
59
+
60
+ ## 2. Verifying a wrap
61
+
62
+ - `toolfunnel wrap` (or `tf_wrap {}` before wrapping) shows the current state.
63
+ - After wrapping, a client's `initialize`/`server/discover` handshake presents the WRAPPED
64
+ server's identity, and `tools/list` shows its tools under their original names.
65
+ - The CLI wrap command prints an era report: whether the upstream speaks the modern
66
+ (2026-07-28) protocol, the legacy family, or both.
67
+
68
+ ## 3. The lockout - why `confirm: true` is required in-band
69
+
70
+ Setting a wrap HIDES every ToolFunnel tool, including `tf_wrap`. An agent that sets a wrap
71
+ in-band therefore loses the in-band path to undo it. Recovery is always available OUT-of-band:
72
+
73
+ - `toolfunnel wrap --off` on the CLI, or
74
+ - the UI's Unwrap button.
75
+
76
+ `tf_wrap` refuses to set a wrap without `confirm: true` precisely so this is a deliberate,
77
+ informed action. Do not set a wrap unless the operator asked for it or you can reach one of the
78
+ two recovery paths.
79
+
80
+ ## 4. Security - what wrapping changes
81
+
82
+ Wrapping SUSPENDS the path-isolation guard for the wrapped upstream only (a wrap is an explicit
83
+ "this server is my whole surface" declaration - e.g. a filesystem server serving a documents
84
+ folder MUST reach outside the gateway root). The CLI and UI both print a security notice naming
85
+ the outside paths when this applies. Two things stay true regardless:
86
+
87
+ - **Every call still passes the PreToolUse gate.** Wrapping never bypasses policy.
88
+ - **Funnel-mode upstreams keep the guard** - the suspension is wrap-scoped, nothing else.
89
+
90
+ To restrict a wrapped server after wrapping: add a PreToolUse hook that blocks by tool/args
91
+ (`toolfunnel_howto({ topic: "add-hook" })`), or disable individual tools (keyed by the surfaced
92
+ name). The manual's "Wrapping & security" section covers the patterns.
93
+
94
+ ## 5. Related settings
95
+
96
+ - `legacyPin: true` on an upstream (in `mcp/expose.json`, or the UI's "Legacy pin" toggle) pins
97
+ that upstream to the legacy protocol forever - the era probe is skipped. Opt-in, warns loudly.
98
+ - The gateway's own presented identities (funnel mode / HTTP) are configured in
99
+ `toolfunnel.json` - see `toolfunnel_howto({ topic: "configure" })`.