toolfunnel 0.5.0 → 0.6.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 (69) hide show
  1. package/README.md +231 -167
  2. package/bin/toolfunnel.js +372 -147
  3. package/docs/KNOWN_BUGS.md +99 -33
  4. package/docs/MANUAL.pdf +0 -0
  5. package/docs/design.md +70 -0
  6. package/docs/packaging.md +115 -115
  7. package/hooks/hooks.manifest.json +27 -27
  8. package/hooks/scripts/example-audit.js +21 -21
  9. package/hooks/scripts/example-deny-dangerous.js +31 -31
  10. package/mcp/expose.example.json +30 -30
  11. package/mcp/servers/mock-modern/server.js +147 -0
  12. package/mcp/servers/mock-upstream/server.js +192 -12
  13. package/package.json +1 -1
  14. package/src/auth/config.js +174 -174
  15. package/src/auth/install.js +181 -181
  16. package/src/auth/resource-server.js +344 -344
  17. package/src/core/config-home.js +134 -134
  18. package/src/core/events.js +105 -105
  19. package/src/core/hook-engine.js +275 -275
  20. package/src/core/hook-loader.js +791 -791
  21. package/src/core/hook-runner.js +346 -346
  22. package/src/core/logger.js +171 -171
  23. package/src/core/matcher.js +51 -51
  24. package/src/core/metrics.js +75 -75
  25. package/src/core/requires.js +138 -138
  26. package/src/core/server-config.js +98 -77
  27. package/src/core/tool-registry.js +180 -180
  28. package/src/extend/add-hook.md +152 -142
  29. package/src/extend/add-mcp.md +122 -121
  30. package/src/extend/configure.md +87 -0
  31. package/src/extend/create-tool.md +168 -152
  32. package/src/extend/howto.js +102 -98
  33. package/src/extend/package.md +187 -187
  34. package/src/extend/wrap.md +99 -0
  35. package/src/mcp/aggregator.js +1246 -807
  36. package/src/mcp/expose-store.js +460 -430
  37. package/src/mcp/gated-run.js +201 -194
  38. package/src/mcp/http-transport.js +1311 -1012
  39. package/src/mcp/mcp-client.js +907 -530
  40. package/src/mcp/modern.js +372 -0
  41. package/src/mcp/protocol.js +468 -466
  42. package/src/mcp/server.js +2688 -1405
  43. package/src/packages/packager.js +715 -715
  44. package/src/tools/drift.js +138 -138
  45. package/src/tools/registry.js +513 -513
  46. package/src/tools/tool-state.js +170 -104
  47. package/src/ui/public/app.js +1174 -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 +1348 -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 +112 -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 +227 -216
@@ -1,249 +1,286 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>ToolFunnel</title>
7
- <link rel="stylesheet" href="/styles.css">
8
- </head>
9
- <body>
10
- <div class="app">
11
- <header class="topbar">
12
- <div class="brand">
13
- <span class="logo" aria-hidden="true">
14
- <img class="logo-img" src="/logo.jpg" alt="">
15
- </span>
16
- <div class="brand-text">
17
- <h1><span class="brand-tool">Tool</span>Funnel</h1>
18
- <p class="tagline">MCP gateway &middot; management console</p>
19
- </div>
20
- </div>
21
- <div class="stats" id="stats">
22
- <div class="stat"><span class="stat-num" id="stat-tools">&middot;</span><span class="stat-label">tools</span></div>
23
- <div class="stat"><span class="stat-num" id="stat-upstreams">&middot;</span><span class="stat-label">mcps</span></div>
24
- <div class="stat"><span class="stat-num" id="stat-hooks">&middot;</span><span class="stat-label">hooks</span></div>
25
- </div>
26
- </header>
27
-
28
- <nav class="tabs">
29
- <button class="tab is-active" data-tab="tools">Tools</button>
30
- <button class="tab" data-tab="mcps">MCPs</button>
31
- <button class="tab" data-tab="hooks">Hooks</button>
32
- <button class="tab" data-tab="logs">Logs</button>
33
- <button class="tab" data-tab="auth">Auth</button>
34
- </nav>
35
-
36
- <main>
37
- <!-- ───────────────────────── Tools view ───────────────────────── -->
38
- <section class="view is-active" id="view-tools">
39
- <!-- Top-level surface: which tools are injected into the AI's context EVERY turn. The 4
40
- meta-tools default ON; promote local/upstream tools with the per-row "Hot" toggle. -->
41
- <div class="surface-panel" id="surface-panel">
42
- <div class="surface-head">
43
- <span class="surface-title">Top-level surface</span>
44
- <span class="surface-sub">tools injected every turn &middot; the rest stay lean, discovered on demand</span>
45
- <span class="surface-count" id="surface-count"></span>
46
- </div>
47
- <div class="surface-meta" id="surface-meta"></div>
48
- <div class="surface-warnings" id="surface-warnings" hidden></div>
49
- </div>
50
-
51
- <div class="toolbar">
52
- <div class="searchbar">
53
- <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
54
- <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
55
- </svg>
56
- <input id="search-tools" type="search" placeholder="Search tools by name, summary, category, id, or mode&hellip;" autocomplete="off" spellcheck="false">
57
- <span class="result-count" id="count-tools"></span>
58
- </div>
59
- <label class="show-hidden" title="Show tools you've hidden from this manager list"><input type="checkbox" id="show-hidden"> show hidden</label>
60
- <button class="btn btn-add" id="addbtn-tools" type="button" aria-expanded="false">+ Add tool</button>
61
- </div>
62
-
63
- <form class="addform" id="addform-tools" hidden autocomplete="off">
64
- <div class="field-grid">
65
- <label class="field"><span>id <em>*</em></span><input id="t-id" type="text" placeholder="my_tool" spellcheck="false"></label>
66
- <label class="field"><span>name <em>*</em></span><input id="t-name" type="text" placeholder="My Tool"></label>
67
- <label class="field"><span>category</span><input id="t-category" type="text" placeholder="utilities"></label>
68
- <label class="field">
69
- <span>mode</span>
70
- <select id="t-mode">
71
- <option value="gateway">gateway — run here</option>
72
- <option value="reference">reference — AI runs it</option>
73
- </select>
74
- </label>
75
- <label class="field field-wide"><span>summary</span><input id="t-summary" type="text" placeholder="One-line description shown in the list."></label>
76
- <label class="field" id="t-invoke-type-field">
77
- <span>invoke type</span>
78
- <select id="t-invoke-type">
79
- <option value="script">script</option>
80
- <option value="shell">shell</option>
81
- <option value="none">none</option>
82
- </select>
83
- </label>
84
- <label class="field" id="t-path-field"><span>script path</span><input id="t-path" type="text" placeholder="scripts/my-tool.js" spellcheck="false"></label>
85
- <label class="field field-wide" id="t-command-field" hidden><span>shell command</span><input id="t-command" type="text" placeholder="echo hello" spellcheck="false"></label>
86
- <label class="field field-wide"><span>instructions</span><textarea id="t-instructions" rows="2" placeholder="How the connected AI should call this tool."></textarea></label>
87
- <label class="field field-wide" id="t-script-field"><span>script body <em class="hint">(optional — authored under tools/scripts/)</em></span><textarea id="t-scripttext" rows="5" spellcheck="false" placeholder="'use strict';&#10;// reads JSON args from process.env.TOOLFUNNEL_TOOL_ARGS"></textarea></label>
88
- </div>
89
- <div class="addform-actions">
90
- <button class="btn btn-primary" id="t-submit" type="submit">Add tool</button>
91
- <button class="btn btn-ghost" id="t-cancel" type="button">Cancel</button>
92
- </div>
93
- </form>
94
-
95
- <div id="tool-list" class="card-list"></div>
96
- <div id="tools-empty" class="empty" hidden>No tools in the register.</div>
97
- </section>
98
-
99
- <!-- ───────────────────────── MCPs view ───────────────────────── -->
100
- <section class="view" id="view-mcps">
101
- <div class="toolbar">
102
- <div class="searchbar">
103
- <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
104
- <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
105
- </svg>
106
- <input id="search-mcps" type="search" placeholder="Search upstream MCPs by id, command, or description&hellip;" autocomplete="off" spellcheck="false">
107
- <span class="result-count" id="count-mcps"></span>
108
- </div>
109
- <button class="btn btn-add" id="addbtn-mcps" type="button" aria-expanded="false">+ Add MCP</button>
110
- </div>
111
-
112
- <form class="addform" id="addform-mcps" hidden autocomplete="off">
113
- <div class="field-grid">
114
- <label class="field"><span>id <em>*</em></span><input id="m-id" type="text" placeholder="my-upstream" spellcheck="false"></label>
115
- <label class="field">
116
- <span>transport</span>
117
- <select id="m-transport"><option value="stdio">stdio</option></select>
118
- </label>
119
- <label class="field field-wide"><span>command <em>*</em></span><input id="m-command" type="text" placeholder="node" spellcheck="false"></label>
120
- <label class="field field-wide"><span>args <em class="hint">(space-separated)</em></span><input id="m-args" type="text" placeholder="path/to/server.js --flag" spellcheck="false"></label>
121
- <label class="field field-wide"><span>description</span><input id="m-description" type="text" placeholder="What this upstream provides."></label>
122
- </div>
123
- <div class="subsection">
124
- <div class="subsection-head">
125
- <span class="subsection-title">Exposed tools <em class="hint">(optional — curated-direct selections)</em></span>
126
- <button class="btn btn-ghost btn-sm" id="m-add-expose" type="button">+ expose</button>
127
- </div>
128
- <div id="m-expose-rows" class="expose-rows"></div>
129
- </div>
130
- <div class="addform-actions">
131
- <button class="btn btn-primary" id="m-submit" type="submit">Add MCP</button>
132
- <button class="btn btn-ghost" id="m-cancel" type="button">Cancel</button>
133
- </div>
134
- </form>
135
-
136
- <div id="mcp-list" class="card-list"></div>
137
- <div id="mcps-empty" class="empty" hidden>
138
- <p>No upstream MCPs configured.</p>
139
- <p class="muted">Add one above to surface its tools through the gateway.</p>
140
- </div>
141
- </section>
142
-
143
- <!-- ───────────────────────── Hooks view ───────────────────────── -->
144
- <section class="view" id="view-hooks">
145
- <div class="toolbar">
146
- <div class="searchbar">
147
- <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
148
- <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
149
- </svg>
150
- <input id="search-hooks" type="search" placeholder="Search hooks by id, event, matcher, or description&hellip;" autocomplete="off" spellcheck="false">
151
- <span class="result-count" id="count-hooks"></span>
152
- </div>
153
- <button class="btn btn-add" id="addbtn-hooks" type="button" aria-expanded="false">+ Add hook</button>
154
- </div>
155
-
156
- <form class="addform" id="addform-hooks" hidden autocomplete="off">
157
- <div class="field-grid">
158
- <label class="field"><span>id <em>*</em></span><input id="h-id" type="text" placeholder="pre-tool-use/my-gate" spellcheck="false"></label>
159
- <label class="field">
160
- <span>event <em>*</em></span>
161
- <select id="h-event"></select>
162
- </label>
163
- <label class="field"><span>matcher</span><input id="h-matcher" type="text" placeholder="Bash|Write (blank = all)" spellcheck="false"></label>
164
- <label class="field field-wide"><span>command <em>*</em></span><input id="h-command" type="text" placeholder='node "${HOOKS_DIR}/scripts/my-gate.js"' spellcheck="false"></label>
165
- <label class="field field-wide"><span>script body <em class="hint">(optional — authored under hooks/scripts/)</em></span><textarea id="h-scripttext" rows="5" spellcheck="false" placeholder="#!/usr/bin/env node&#10;// reads the event payload as JSON on stdin"></textarea></label>
166
- </div>
167
- <div class="addform-actions">
168
- <button class="btn btn-primary" id="h-submit" type="submit">Add hook</button>
169
- <button class="btn btn-ghost" id="h-cancel" type="button">Cancel</button>
170
- </div>
171
- </form>
172
-
173
- <div id="hook-list" class="card-list"></div>
174
- <div id="hooks-empty" class="empty" hidden>
175
- <p>No hooks configured.</p>
176
- <p class="muted">Add a lifecycle hook above, or toggle per-tool Pre/Post gates on the Tools tab.</p>
177
- </div>
178
- </section>
179
-
180
- <!-- ───────────────────────── Logs view ───────────────────────── -->
181
- <section class="view" id="view-logs">
182
- <div class="toolbar logs-toolbar">
183
- <div class="log-config">
184
- <label class="switch"><input type="checkbox" id="log-enabled"><span class="slider"></span></label>
185
- <span class="log-status" id="log-status">&middot;</span>
186
- <span class="log-path mono" id="log-path"></span>
187
- </div>
188
- <span class="result-count log-count" id="count-logs"></span>
189
- <button class="btn btn-ghost" id="logs-refresh" type="button" title="Reload recent entries">Refresh</button>
190
- </div>
191
-
192
- <div id="log-list" class="card-list log-list"></div>
193
- <div id="logs-empty" class="empty" hidden>
194
- <p>No entries yet.</p>
195
- <p class="muted">Gate decisions and tool runs will appear here once activity is logged.</p>
196
- </div>
197
- </section>
198
-
199
- <!-- ───────────────────────── Auth view ───────────────────────── -->
200
- <section class="view" id="view-auth">
201
- <div class="auth-intro">
202
- <p>
203
- ToolFunnel ships <strong>zero runtime dependencies</strong>. OAuth&nbsp;2.1 is
204
- <strong>opt-in</strong>: enabling it adds exactly one audited, itself-zero-dependency
205
- library&nbsp;&mdash; <code id="auth-josepin">jose</code>&nbsp;&mdash; the same one the
206
- official MCP SDK uses. It is installed only when you choose to, here or via
207
- <code>toolfunnel install-oauth</code>. With auth on, the HTTP host can validate bearer
208
- tokens (resource-server role) and safely bind off&nbsp;localhost.
209
- </p>
210
- </div>
211
-
212
- <!-- Dependency status + on-demand install -->
213
- <div class="auth-card" id="auth-dep-card">
214
- <div class="auth-row">
215
- <span class="auth-dep-state" id="auth-dep-state">&middot;</span>
216
- <button class="btn btn-primary" id="auth-install-btn" type="button" hidden>Install OAuth dependency</button>
217
- </div>
218
- <pre class="auth-install-log mono" id="auth-install-log" hidden></pre>
219
- </div>
220
-
221
- <!-- Config form -->
222
- <form class="auth-card auth-form" id="auth-form" autocomplete="off">
223
- <div class="auth-enable-row">
224
- <label class="switch"><input type="checkbox" id="auth-enabled"><span class="slider"></span></label>
225
- <span class="auth-enable-label">Enable OAuth&nbsp;2.1 resource-server validation</span>
226
- <span class="auth-ready-badge" id="auth-ready-badge" hidden></span>
227
- </div>
228
- <div class="field-grid">
229
- <label class="field field-wide"><span>issuer <em>*</em> <em class="hint">(the authorization server)</em></span><input id="auth-issuer" type="text" placeholder="https://auth.example.com" spellcheck="false"></label>
230
- <label class="field field-wide"><span>audience <em>*</em> <em class="hint">(this gateway's resource URI — binds tokens, RFC&nbsp;8707)</em></span><input id="auth-audience" type="text" placeholder="https://gateway.example.com" spellcheck="false"></label>
231
- <label class="field field-wide"><span>jwksUri <em class="hint">(optional — derived from the issuer via discovery if blank)</em></span><input id="auth-jwksuri" type="text" placeholder="https://auth.example.com/.well-known/jwks.json" spellcheck="false"></label>
232
- <label class="field"><span>algorithms <em class="hint">(comma-separated, pinned)</em></span><input id="auth-algorithms" type="text" placeholder="RS256, ES256" spellcheck="false"></label>
233
- <label class="field"><span>required scopes <em class="hint">(comma-separated, optional)</em></span><input id="auth-scopes" type="text" placeholder="tools:run" spellcheck="false"></label>
234
- <label class="field"><span>clock tolerance (s)</span><input id="auth-clock" type="number" min="0" placeholder="30"></label>
235
- </div>
236
- <div class="addform-actions">
237
- <button class="btn btn-primary" id="auth-save" type="submit">Save auth config</button>
238
- <span class="auth-config-note muted" id="auth-config-note"></span>
239
- </div>
240
- </form>
241
- </section>
242
- </main>
243
- </div>
244
-
245
- <div id="toast" class="toast" role="status" aria-live="polite"></div>
246
-
247
- <script src="/app.js"></script>
248
- </body>
249
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ToolFunnel</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="app">
11
+ <header class="topbar">
12
+ <div class="brand">
13
+ <span class="logo" aria-hidden="true">
14
+ <img class="logo-img" src="/logo.jpg" alt="">
15
+ </span>
16
+ <div class="brand-text">
17
+ <h1><span class="brand-tool">Tool</span>Funnel</h1>
18
+ <p class="tagline">MCP gateway &middot; management console</p>
19
+ </div>
20
+ </div>
21
+ <div class="stats" id="stats">
22
+ <div class="stat"><span class="stat-num" id="stat-tools">&middot;</span><span class="stat-label">tools</span></div>
23
+ <div class="stat"><span class="stat-num" id="stat-upstreams">&middot;</span><span class="stat-label">mcps</span></div>
24
+ <div class="stat"><span class="stat-num" id="stat-hooks">&middot;</span><span class="stat-label">hooks</span></div>
25
+ </div>
26
+ </header>
27
+
28
+ <nav class="tabs">
29
+ <button class="tab is-active" data-tab="tools">Tools</button>
30
+ <button class="tab" data-tab="mcps">MCPs</button>
31
+ <button class="tab" data-tab="hooks">Hooks</button>
32
+ <button class="tab" data-tab="logs">Logs</button>
33
+ <button class="tab" data-tab="auth">Auth</button>
34
+ <button class="tab" data-tab="settings">Settings</button>
35
+ </nav>
36
+
37
+ <main>
38
+ <!-- ───────────────────────── Tools view ───────────────────────── -->
39
+ <section class="view is-active" id="view-tools">
40
+ <!-- Top-level surface: which tools are injected into the AI's context EVERY turn. The 4
41
+ meta-tools default ON; promote local/upstream tools with the per-row "Hot" toggle. -->
42
+ <div class="surface-panel" id="surface-panel">
43
+ <div class="surface-head">
44
+ <span class="surface-title">Top-level surface</span>
45
+ <span class="surface-sub">tools injected every turn &middot; the rest stay lean, discovered on demand</span>
46
+ <span class="surface-count" id="surface-count"></span>
47
+ </div>
48
+ <div class="surface-meta" id="surface-meta"></div>
49
+ <div class="surface-warnings" id="surface-warnings" hidden></div>
50
+ </div>
51
+
52
+ <div class="toolbar">
53
+ <div class="searchbar">
54
+ <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
55
+ <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
56
+ </svg>
57
+ <input id="search-tools" type="search" placeholder="Search tools by name, summary, category, id, or mode&hellip;" autocomplete="off" spellcheck="false">
58
+ <span class="result-count" id="count-tools"></span>
59
+ </div>
60
+ <label class="show-hidden" title="Show tools you've hidden from this manager list"><input type="checkbox" id="show-hidden"> show hidden</label>
61
+ <button class="btn btn-add" id="addbtn-tools" type="button" aria-expanded="false">+ Add tool</button>
62
+ </div>
63
+
64
+ <form class="addform" id="addform-tools" hidden autocomplete="off">
65
+ <div class="field-grid">
66
+ <label class="field"><span>id <em>*</em></span><input id="t-id" type="text" placeholder="my_tool" spellcheck="false"></label>
67
+ <label class="field"><span>name <em>*</em></span><input id="t-name" type="text" placeholder="My Tool"></label>
68
+ <label class="field"><span>category</span><input id="t-category" type="text" placeholder="utilities"></label>
69
+ <label class="field">
70
+ <span>mode</span>
71
+ <select id="t-mode">
72
+ <option value="gateway">gateway - run here</option>
73
+ <option value="reference">reference - AI runs it</option>
74
+ </select>
75
+ </label>
76
+ <label class="field field-wide"><span>summary</span><input id="t-summary" type="text" placeholder="One-line description shown in the list."></label>
77
+ <label class="field" id="t-invoke-type-field">
78
+ <span>invoke type</span>
79
+ <select id="t-invoke-type">
80
+ <option value="script">script</option>
81
+ <option value="shell">shell</option>
82
+ <option value="none">none</option>
83
+ </select>
84
+ </label>
85
+ <label class="field" id="t-path-field"><span>script path</span><input id="t-path" type="text" placeholder="scripts/my-tool.js" spellcheck="false"></label>
86
+ <label class="field field-wide" id="t-command-field" hidden><span>shell command</span><input id="t-command" type="text" placeholder="echo hello" spellcheck="false"></label>
87
+ <label class="field field-wide"><span>instructions</span><textarea id="t-instructions" rows="2" placeholder="How the connected AI should call this tool."></textarea></label>
88
+ <label class="field field-wide" id="t-script-field"><span>script body <em class="hint">(optional - authored under tools/scripts/)</em></span><textarea id="t-scripttext" rows="5" spellcheck="false" placeholder="'use strict';&#10;// reads JSON args from process.env.TOOLFUNNEL_TOOL_ARGS"></textarea></label>
89
+ </div>
90
+ <div class="addform-actions">
91
+ <button class="btn btn-primary" id="t-submit" type="submit">Add tool</button>
92
+ <button class="btn btn-ghost" id="t-cancel" type="button">Cancel</button>
93
+ </div>
94
+ </form>
95
+
96
+ <div id="tool-list" class="card-list"></div>
97
+ <div id="tools-empty" class="empty" hidden>No tools in the register.</div>
98
+ </section>
99
+
100
+ <!-- ───────────────────────── MCPs view ───────────────────────── -->
101
+ <section class="view" id="view-mcps">
102
+ <div class="toolbar">
103
+ <div class="searchbar">
104
+ <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
105
+ <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
106
+ </svg>
107
+ <input id="search-mcps" type="search" placeholder="Search upstream MCPs by id, command, or description&hellip;" autocomplete="off" spellcheck="false">
108
+ <span class="result-count" id="count-mcps"></span>
109
+ </div>
110
+ <button class="btn btn-add" id="addbtn-mcps" type="button" aria-expanded="false">+ Add MCP</button>
111
+ </div>
112
+
113
+ <form class="addform" id="addform-mcps" hidden autocomplete="off">
114
+ <div class="field-grid">
115
+ <label class="field"><span>id <em>*</em></span><input id="m-id" type="text" placeholder="my-upstream" spellcheck="false"></label>
116
+ <label class="field">
117
+ <span>transport</span>
118
+ <select id="m-transport"><option value="stdio">stdio</option></select>
119
+ </label>
120
+ <label class="field field-wide"><span>command <em>*</em></span><input id="m-command" type="text" placeholder="node" spellcheck="false"></label>
121
+ <label class="field field-wide"><span>args <em class="hint">(space-separated)</em></span><input id="m-args" type="text" placeholder="path/to/server.js --flag" spellcheck="false"></label>
122
+ <label class="field field-wide"><span>description</span><input id="m-description" type="text" placeholder="What this upstream provides."></label>
123
+ </div>
124
+ <div class="subsection">
125
+ <div class="subsection-head">
126
+ <span class="subsection-title">Exposed tools <em class="hint">(optional - curated-direct selections)</em></span>
127
+ <button class="btn btn-ghost btn-sm" id="m-add-expose" type="button">+ expose</button>
128
+ </div>
129
+ <div id="m-expose-rows" class="expose-rows"></div>
130
+ </div>
131
+ <div class="addform-actions">
132
+ <button class="btn btn-primary" id="m-submit" type="submit">Add MCP</button>
133
+ <button class="btn btn-ghost" id="m-cancel" type="button">Cancel</button>
134
+ </div>
135
+ </form>
136
+
137
+ <div id="mcp-list" class="card-list"></div>
138
+ <div id="mcps-empty" class="empty" hidden>
139
+ <p>No upstream MCPs configured.</p>
140
+ <p class="muted">Add one above to surface its tools through the gateway.</p>
141
+ </div>
142
+ </section>
143
+
144
+ <!-- ───────────────────────── Hooks view ───────────────────────── -->
145
+ <section class="view" id="view-hooks">
146
+ <div class="toolbar">
147
+ <div class="searchbar">
148
+ <svg class="search-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
149
+ <circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path>
150
+ </svg>
151
+ <input id="search-hooks" type="search" placeholder="Search hooks by id, event, matcher, or description&hellip;" autocomplete="off" spellcheck="false">
152
+ <span class="result-count" id="count-hooks"></span>
153
+ </div>
154
+ <button class="btn btn-add" id="addbtn-hooks" type="button" aria-expanded="false">+ Add hook</button>
155
+ </div>
156
+
157
+ <form class="addform" id="addform-hooks" hidden autocomplete="off">
158
+ <div class="field-grid">
159
+ <label class="field"><span>id <em>*</em></span><input id="h-id" type="text" placeholder="pre-tool-use/my-gate" spellcheck="false"></label>
160
+ <label class="field">
161
+ <span>event <em>*</em></span>
162
+ <select id="h-event"></select>
163
+ </label>
164
+ <label class="field"><span>matcher</span><input id="h-matcher" type="text" placeholder="Bash|Write (blank = all)" spellcheck="false"></label>
165
+ <label class="field field-wide"><span>command <em>*</em></span><input id="h-command" type="text" placeholder='node "${HOOKS_DIR}/scripts/my-gate.js"' spellcheck="false"></label>
166
+ <label class="field field-wide"><span>script body <em class="hint">(optional - authored under hooks/scripts/)</em></span><textarea id="h-scripttext" rows="5" spellcheck="false" placeholder="#!/usr/bin/env node&#10;// reads the event payload as JSON on stdin"></textarea></label>
167
+ </div>
168
+ <div class="addform-actions">
169
+ <button class="btn btn-primary" id="h-submit" type="submit">Add hook</button>
170
+ <button class="btn btn-ghost" id="h-cancel" type="button">Cancel</button>
171
+ </div>
172
+ </form>
173
+
174
+ <div id="hook-list" class="card-list"></div>
175
+ <div id="hooks-empty" class="empty" hidden>
176
+ <p>No hooks configured.</p>
177
+ <p class="muted">Add a lifecycle hook above, or toggle per-tool Pre/Post gates on the Tools tab.</p>
178
+ </div>
179
+ </section>
180
+
181
+ <!-- ───────────────────────── Logs view ───────────────────────── -->
182
+ <section class="view" id="view-logs">
183
+ <div class="toolbar logs-toolbar">
184
+ <div class="log-config">
185
+ <label class="switch"><input type="checkbox" id="log-enabled"><span class="slider"></span></label>
186
+ <span class="log-status" id="log-status">&middot;</span>
187
+ <span class="log-path mono" id="log-path"></span>
188
+ </div>
189
+ <span class="result-count log-count" id="count-logs"></span>
190
+ <button class="btn btn-ghost" id="logs-refresh" type="button" title="Reload recent entries">Refresh</button>
191
+ </div>
192
+
193
+ <div id="log-list" class="card-list log-list"></div>
194
+ <div id="logs-empty" class="empty" hidden>
195
+ <p>No entries yet.</p>
196
+ <p class="muted">Gate decisions and tool runs will appear here once activity is logged.</p>
197
+ </div>
198
+ </section>
199
+
200
+ <!-- ───────────────────────── Auth view ───────────────────────── -->
201
+ <section class="view" id="view-auth">
202
+ <div class="auth-intro">
203
+ <p>
204
+ ToolFunnel ships <strong>zero runtime dependencies</strong>. OAuth&nbsp;2.1 is
205
+ <strong>opt-in</strong>: enabling it adds exactly one audited, itself-zero-dependency
206
+ library&nbsp;&mdash; <code id="auth-josepin">jose</code>&nbsp;&mdash; the same one the
207
+ official MCP SDK uses. It is installed only when you choose to, here or via
208
+ <code>toolfunnel install-oauth</code>. With auth on, the HTTP host can validate bearer
209
+ tokens (resource-server role) and safely bind off&nbsp;localhost.
210
+ </p>
211
+ </div>
212
+
213
+ <!-- Dependency status + on-demand install -->
214
+ <div class="auth-card" id="auth-dep-card">
215
+ <div class="auth-row">
216
+ <span class="auth-dep-state" id="auth-dep-state">&middot;</span>
217
+ <button class="btn btn-primary" id="auth-install-btn" type="button" hidden>Install OAuth dependency</button>
218
+ </div>
219
+ <pre class="auth-install-log mono" id="auth-install-log" hidden></pre>
220
+ </div>
221
+
222
+ <!-- Config form -->
223
+ <form class="auth-card auth-form" id="auth-form" autocomplete="off">
224
+ <div class="auth-enable-row">
225
+ <label class="switch"><input type="checkbox" id="auth-enabled"><span class="slider"></span></label>
226
+ <span class="auth-enable-label">Enable OAuth&nbsp;2.1 resource-server validation</span>
227
+ <span class="auth-ready-badge" id="auth-ready-badge" hidden></span>
228
+ </div>
229
+ <div class="field-grid">
230
+ <label class="field field-wide"><span>issuer <em>*</em> <em class="hint">(the authorization server)</em></span><input id="auth-issuer" type="text" placeholder="https://auth.example.com" spellcheck="false"></label>
231
+ <label class="field field-wide"><span>audience <em>*</em> <em class="hint">(this gateway's resource URI - binds tokens, RFC&nbsp;8707)</em></span><input id="auth-audience" type="text" placeholder="https://gateway.example.com" spellcheck="false"></label>
232
+ <label class="field field-wide"><span>jwksUri <em class="hint">(optional - derived from the issuer via discovery if blank)</em></span><input id="auth-jwksuri" type="text" placeholder="https://auth.example.com/.well-known/jwks.json" spellcheck="false"></label>
233
+ <label class="field"><span>algorithms <em class="hint">(comma-separated, pinned)</em></span><input id="auth-algorithms" type="text" placeholder="RS256, ES256" spellcheck="false"></label>
234
+ <label class="field"><span>required scopes <em class="hint">(comma-separated, optional)</em></span><input id="auth-scopes" type="text" placeholder="tools:run" spellcheck="false"></label>
235
+ <label class="field"><span>clock tolerance (s)</span><input id="auth-clock" type="number" min="0" placeholder="30"></label>
236
+ </div>
237
+ <div class="addform-actions">
238
+ <button class="btn btn-primary" id="auth-save" type="submit">Save auth config</button>
239
+ <span class="auth-config-note muted" id="auth-config-note"></span>
240
+ </div>
241
+ </form>
242
+ </section>
243
+
244
+ <!-- ───────────────────────── Settings view ───────────────────────── -->
245
+ <section class="view" id="view-settings">
246
+ <div class="auth-intro">
247
+ <p>
248
+ <strong>Identity &amp; ports</strong> &mdash; stored in <code>toolfunnel.json</code> at the
249
+ config home. The <em>server</em> identity is what connected clients see in the
250
+ <code>initialize</code> handshake; the <em>client</em> identity is what upstream MCP servers
251
+ see from the gateway. Under a <strong>wrap</strong> on stdio, the wrapped upstream is shown
252
+ the real downstream client's identity automatically &mdash; these settings cover funnel
253
+ mode, HTTP, and the boot connection. Leave any field blank to use the default.
254
+ </p>
255
+ </div>
256
+
257
+ <form class="auth-card auth-form" id="identity-form" autocomplete="off">
258
+ <div class="field-grid">
259
+ <label class="field"><span>serverName <em class="hint">(shown to clients &mdash; default "toolfunnel")</em></span><input id="id-servername" type="text" placeholder="toolfunnel" spellcheck="false"></label>
260
+ <label class="field"><span>serverVersion <em class="hint">(default: package version)</em></span><input id="id-serverversion" type="text" placeholder="1.0.0" spellcheck="false"></label>
261
+ <label class="field"><span>clientName <em class="hint">(shown to upstreams &mdash; default "toolfunnel")</em></span><input id="id-clientname" type="text" placeholder="my-client" spellcheck="false"></label>
262
+ <label class="field"><span>clientVersion</span><input id="id-clientversion" type="text" placeholder="1.0.0" spellcheck="false"></label>
263
+ <label class="field"><span>httpPort <em class="hint">(default 9998)</em></span><input id="id-httpport" type="number" min="1" max="65535" placeholder="9998"></label>
264
+ <label class="field"><span>uiPort <em class="hint">(default 9777)</em></span><input id="id-uiport" type="number" min="1" max="65535" placeholder="9777"></label>
265
+ <label class="field"><span>Serve legacy protocol <em class="hint">(default on - untick for modern-only: legacy clients are refused)</em></span><input id="id-servelegacy" type="checkbox" checked></label>
266
+ </div>
267
+ <div class="addform-actions">
268
+ <button class="btn btn-primary" id="identity-save" type="submit">Save settings</button>
269
+ <span class="auth-config-note muted" id="identity-note"></span>
270
+ </div>
271
+ </form>
272
+
273
+ <div class="auth-card">
274
+ <div class="subsection-head"><span class="subsection-title">Effective values (as the gateway resolves them)</span></div>
275
+ <pre class="mono" id="identity-effective">&middot;</pre>
276
+ <p class="muted">Changes take effect the next time the gateway starts. CLI flags (e.g. <code>--port</code>) still win over these.</p>
277
+ </div>
278
+ </section>
279
+ </main>
280
+ </div>
281
+
282
+ <div id="toast" class="toast" role="status" aria-live="polite"></div>
283
+
284
+ <script src="/app.js"></script>
285
+ </body>
286
+ </html>