toolfunnel 0.3.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.
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/bin/toolfunnel.js +112 -0
- package/docs/admin-ui.md +404 -0
- package/docs/configuration.md +402 -0
- package/docs/hooks-and-gating.md +428 -0
- package/docs/logging.md +223 -0
- package/docs/management.md +429 -0
- package/docs/oauth.md +103 -0
- package/docs/setup.md +352 -0
- package/docs/tools-and-upstreams.md +469 -0
- package/hooks/hooks.manifest.json +27 -0
- package/hooks/scripts/.gitkeep +3 -0
- package/hooks/scripts/example-audit.js +21 -0
- package/hooks/scripts/example-deny-dangerous.js +31 -0
- package/mcp/expose.example.json +30 -0
- package/mcp/servers/mock-upstream/server.js +170 -0
- package/package.json +63 -0
- package/src/auth/config.js +172 -0
- package/src/auth/install.js +181 -0
- package/src/auth/resource-server.js +319 -0
- package/src/core/events.js +105 -0
- package/src/core/hook-engine.js +275 -0
- package/src/core/hook-loader.js +791 -0
- package/src/core/hook-runner.js +346 -0
- package/src/core/logger.js +169 -0
- package/src/core/matcher.js +51 -0
- package/src/core/metrics.js +75 -0
- package/src/core/tool-registry.js +180 -0
- package/src/extend/add-hook.md +142 -0
- package/src/extend/add-mcp.md +121 -0
- package/src/extend/create-tool.md +132 -0
- package/src/extend/howto.js +98 -0
- package/src/extend/package.md +154 -0
- package/src/mcp/aggregator.js +802 -0
- package/src/mcp/expose-store.js +430 -0
- package/src/mcp/gated-run.js +194 -0
- package/src/mcp/http-transport.js +962 -0
- package/src/mcp/mcp-client.js +524 -0
- package/src/mcp/protocol.js +466 -0
- package/src/mcp/server.js +1240 -0
- package/src/packages/packager.js +713 -0
- package/src/tools/drift.js +138 -0
- package/src/tools/registry.js +468 -0
- package/src/tools/tool-state.js +104 -0
- package/src/ui/public/app.js +1055 -0
- package/src/ui/public/index.html +249 -0
- package/src/ui/public/logo.jpg +0 -0
- package/src/ui/public/styles.css +535 -0
- package/src/ui/server.js +1144 -0
- package/tools/scripts/base64.js +139 -0
- package/tools/scripts/danger.js +61 -0
- package/tools/scripts/echo.js +39 -0
- package/tools/scripts/hash.js +101 -0
- package/tools/scripts/json.js +109 -0
- package/tools/scripts/text-stats.js +131 -0
- package/tools/scripts/tf-hook-add.js +152 -0
- package/tools/scripts/tf-hook-set.js +113 -0
- package/tools/scripts/tf-list.js +195 -0
- package/tools/scripts/tf-log.js +83 -0
- package/tools/scripts/tf-mcp-add.js +119 -0
- package/tools/scripts/tf-mcp-set.js +104 -0
- package/tools/scripts/tf-tool-add.js +129 -0
- package/tools/scripts/tf-tool-set.js +148 -0
- package/tools/scripts/uuid.js +102 -0
- package/tools/tools.register.json +171 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# How to package and share units (self-extension)
|
|
2
|
+
|
|
3
|
+
This is the instruction served by `toolfunnel_howto({ topic: "package" })`. It explains how to bundle
|
|
4
|
+
tools, hooks, and MCP configs into a **shareable package** and how import/export work. Packaging is a
|
|
5
|
+
**byproduct** of the gateway design: because every unit is already structured and
|
|
6
|
+
self-documented, a package is just a folder that lists its units and carries their files.
|
|
7
|
+
|
|
8
|
+
> Phase note: packages are Phase 2 (`packages/` directory). The structure below is what the importer
|
|
9
|
+
> and exporter target; defining it now means the units authored today are already package-ready.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. What a unit is (and what travels with it)
|
|
14
|
+
|
|
15
|
+
Each unit is self-contained — this is the whole reason packaging is cheap:
|
|
16
|
+
|
|
17
|
+
| Unit | Its metadata | Its files |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **tool** | one `tools.register.json` entry (`id/name/summary/category/instructions/invoke`) | the script under `tools/scripts/` (for `invoke.type === "script"`) |
|
|
20
|
+
| **hook** | one `hooks.manifest.json` entry (`id/event/matcher/command/script/timeout/description`) + its `hooks.state.json` enabled value | the script under `hooks/scripts/` |
|
|
21
|
+
| **mcp** | one `expose.json` `upstreams[]` entry + its `expose[]` selections | any vendored server files it spawns (must live inside the package) |
|
|
22
|
+
|
|
23
|
+
A package gathers one or more of these, plus the files they reference.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Package layout
|
|
28
|
+
|
|
29
|
+
A package is a folder under `packages/` containing a manifest and the units' files in a mirrored
|
|
30
|
+
tree so import is a structured copy, not guesswork:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
packages/
|
|
34
|
+
notify-pack/
|
|
35
|
+
package.json # the package manifest (below)
|
|
36
|
+
tools/
|
|
37
|
+
scripts/
|
|
38
|
+
msg-digest.sh
|
|
39
|
+
hooks/
|
|
40
|
+
scripts/
|
|
41
|
+
rate-limit.sh
|
|
42
|
+
mcp/
|
|
43
|
+
vendor/ # any vendored upstream server files
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `package.json` — the package manifest
|
|
47
|
+
|
|
48
|
+
A `package.json`-style manifest listing the package's identity and its units (the metadata blocks are
|
|
49
|
+
embedded so import is self-sufficient — it does not need the source register/manifest to reconstruct
|
|
50
|
+
entries):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"name": "notify-pack",
|
|
55
|
+
"version": "1.0.0",
|
|
56
|
+
"description": "Message digest tool + rate-limit gate + upstream MCP exposure.",
|
|
57
|
+
"author": "unknown",
|
|
58
|
+
"units": {
|
|
59
|
+
"tools": [
|
|
60
|
+
{
|
|
61
|
+
"entry": {
|
|
62
|
+
"id": "msg-digest",
|
|
63
|
+
"name": "Message Digest",
|
|
64
|
+
"summary": "Summarises unread chats.",
|
|
65
|
+
"category": "messaging",
|
|
66
|
+
"instructions": "Pass { since? }. Returns a per-chat unread digest.",
|
|
67
|
+
"invoke": { "type": "script", "path": "scripts/msg-digest.sh" }
|
|
68
|
+
},
|
|
69
|
+
"files": ["tools/scripts/msg-digest.sh"]
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"entry": {
|
|
75
|
+
"id": "pre-tool-use/rate-limit",
|
|
76
|
+
"event": "PreToolUse",
|
|
77
|
+
"matcher": "msg_send_message",
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "bash \"${HOOKS_DIR}/scripts/rate-limit.sh\"",
|
|
80
|
+
"script": "scripts/rate-limit.sh",
|
|
81
|
+
"timeout": 5,
|
|
82
|
+
"description": "Denies sends past a per-minute cap."
|
|
83
|
+
},
|
|
84
|
+
"enabled": true,
|
|
85
|
+
"files": ["hooks/scripts/rate-limit.sh"]
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"mcp": [
|
|
89
|
+
{
|
|
90
|
+
"upstream": {
|
|
91
|
+
"id": "notify",
|
|
92
|
+
"transport": "stdio",
|
|
93
|
+
"command": "node",
|
|
94
|
+
"args": ["${PACKAGE_DIR}/mcp/vendor/notify-mcp/server.js"],
|
|
95
|
+
"enabled": true,
|
|
96
|
+
"description": "Messaging bridge MCP."
|
|
97
|
+
},
|
|
98
|
+
"expose": [
|
|
99
|
+
{ "upstream": "notify", "tool": "list_chats", "as": "msg_list_chats", "category": "messaging", "enabled": true },
|
|
100
|
+
{ "upstream": "notify", "tool": "send_message", "as": "msg_send_message", "category": "messaging", "enabled": true }
|
|
101
|
+
],
|
|
102
|
+
"files": ["mcp/vendor/notify-mcp/server.js"]
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Field | Meaning |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `name` / `version` / `description` / `author` | Package identity. `name` is the folder name and the import key. |
|
|
112
|
+
| `units.tools[]` | Each has the full register `entry` plus `files` (paths inside the package). |
|
|
113
|
+
| `units.hooks[]` | Each has the full manifest `entry`, its seed `enabled` (for `hooks.state.json`), and `files`. |
|
|
114
|
+
| `units.mcp[]` | Each has an `upstream` block, its `expose[]` selections, and any vendored `files`. |
|
|
115
|
+
|
|
116
|
+
`${PACKAGE_DIR}` expands to the installed package's absolute path (mirrors `${HOOKS_DIR}` /
|
|
117
|
+
`${TOOLS_DIR}`), so a package's MCP/tool commands resolve wherever it lands.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 3. Export — bundling a unit (or several)
|
|
122
|
+
|
|
123
|
+
To export, gather a unit's metadata and its files into a package folder:
|
|
124
|
+
|
|
125
|
+
1. Create `packages/<name>/` with a `package.json` manifest.
|
|
126
|
+
2. For each tool: copy its `tools.register.json` entry into `units.tools[].entry`, copy its
|
|
127
|
+
`tools/scripts/<file>` into the package's `tools/scripts/`, and list it in `files`.
|
|
128
|
+
3. For each hook: copy its `hooks.manifest.json` entry into `units.hooks[].entry`, copy its enabled
|
|
129
|
+
value from `hooks.state.json` into `enabled`, copy the script into `hooks/scripts/`, list it.
|
|
130
|
+
4. For each MCP: copy the `upstreams[]` block + matching `expose[]` selections, and vendor any server
|
|
131
|
+
files into `mcp/vendor/`.
|
|
132
|
+
|
|
133
|
+
The package is then a single folder, self-describing and copy-portable. (Everything stays inside
|
|
134
|
+
`packages/` — the isolation rule still holds.)
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 4. Import — installing a package
|
|
139
|
+
|
|
140
|
+
Import is the reverse: **drop a package folder in `packages/` + register its units.**
|
|
141
|
+
|
|
142
|
+
1. Read `packages/<name>/package.json`.
|
|
143
|
+
2. **Tools:** copy each `units.tools[].files` into `tools/scripts/` and add each `entry` to
|
|
144
|
+
`tools.register.json` (atomic write via `registry.js`). Reject id collisions or prompt to rename.
|
|
145
|
+
3. **Hooks:** copy each `units.hooks[].files` into `hooks/scripts/` and add each `entry` to
|
|
146
|
+
`hooks.manifest.json`; write its `enabled` into `hooks.state.json`.
|
|
147
|
+
4. **MCP:** copy vendored `files` into the package's `mcp/vendor/`, add each `upstream` to
|
|
148
|
+
`expose.json` `upstreams[]` and its `expose[]` selections (path-rewritten to `${PACKAGE_DIR}`).
|
|
149
|
+
|
|
150
|
+
After import: tools are live immediately (the register is read fresh on every `toolfunnel_list_tools`).
|
|
151
|
+
Hooks take effect once the loader re-scans (next startup, or a reload). Curated-direct MCP tools need
|
|
152
|
+
a reconnect to reach the CLI surface (register-routed tools do not). Verify with `toolfunnel_list_tools`
|
|
153
|
+
and a `run`/run-once test that the gate still fires on the imported units — the PreToolUse gate
|
|
154
|
+
applies to imported tools exactly as it does to local ones.
|