@evilstar9527/tool-bridge 0.1.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/README.md +288 -0
- package/dist/sdk/admin.js +157 -0
- package/dist/sdk/chunks/client-BWz3o-l-.js +36 -0
- package/dist/sdk/chunks/errors-DJj3RaDk.js +54 -0
- package/dist/sdk/host.js +98 -0
- package/dist/sdk/index.js +6 -0
- package/dist/sdk/tb.js +0 -0
- package/dist/sdk/transport.js +14 -0
- package/dist/sdk/tunnel-agent.js +43 -0
- package/dist/sdk/worker.js +2940 -0
- package/dist/types/sdk/admin/index.d.ts +237 -0
- package/dist/types/sdk/client.d.ts +17 -0
- package/dist/types/sdk/host/index.d.ts +65 -0
- package/dist/types/sdk/index.d.ts +5 -0
- package/dist/types/sdk/transport.d.ts +8 -0
- package/dist/types/sdk/tunnel-agent/index.d.ts +29 -0
- package/dist/types/worker/index.d.ts +19 -0
- package/dist/types/worker/tb/adapters/builtin.d.ts +4 -0
- package/dist/types/worker/tb/adapters/directory.d.ts +6 -0
- package/dist/types/worker/tb/adapters/http.d.ts +2 -0
- package/dist/types/worker/tb/adapters/index.d.ts +2 -0
- package/dist/types/worker/tb/adapters/mcp.d.ts +2 -0
- package/dist/types/worker/tb/adapters/mount.d.ts +2 -0
- package/dist/types/worker/tb/adapters/remote.d.ts +2 -0
- package/dist/types/worker/tb/audit.d.ts +56 -0
- package/dist/types/worker/tb/crawl.d.ts +11 -0
- package/dist/types/worker/tb/device.d.ts +80 -0
- package/dist/types/worker/tb/dynamic-servers.d.ts +12 -0
- package/dist/types/worker/tb/entities.d.ts +74 -0
- package/dist/types/worker/tb/errors.d.ts +34 -0
- package/dist/types/worker/tb/help.d.ts +2 -0
- package/dist/types/worker/tb/host-api.d.ts +12 -0
- package/dist/types/worker/tb/materialize.d.ts +4 -0
- package/dist/types/worker/tb/mcp-client.d.ts +17 -0
- package/dist/types/worker/tb/provider-api.d.ts +11 -0
- package/dist/types/worker/tb/registry.d.ts +11 -0
- package/dist/types/worker/tb/remote-client.d.ts +3 -0
- package/dist/types/worker/tb/resolve.d.ts +8 -0
- package/dist/types/worker/tb/storage-r2.d.ts +2 -0
- package/dist/types/worker/tb/tenant.d.ts +28 -0
- package/dist/types/worker/tb/testing/fake-kv.d.ts +20 -0
- package/dist/types/worker/tb/types.d.ts +155 -0
- package/dist/types/worker/tb/util.d.ts +18 -0
- package/dist/types/worker/tb/virtualize.d.ts +7 -0
- package/docs/sdk.md +149 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Tool Bridge
|
|
2
|
+
|
|
3
|
+
Tool Bridge 是一个可部署到 Cloudflare Workers 的 MCP Streamable HTTP bridge。它把远端 MCP server 暴露的 `tools/list` 和 `tools/call` 转换成普通 HTTP call,并提供一个用于发现和调用 tool 的 Web UI。
|
|
4
|
+
|
|
5
|
+
本项目不是 HTBP protocol 本身;HTBP 的 RFC 和设计文档放在 `TokenRollAI/HTBP`。
|
|
6
|
+
|
|
7
|
+
## 能力
|
|
8
|
+
|
|
9
|
+
- 连接 MCP Streamable HTTP server
|
|
10
|
+
- 将 MCP `tools/list` 暴露为 HTTP JSON API
|
|
11
|
+
- 将 MCP `tools/call` 暴露为普通 `POST` call
|
|
12
|
+
- 为 configured MCP server 生成 `~help` 和 `~skill`
|
|
13
|
+
- **把 MCP / HTTP / 远端 TB 实例组织成一棵自描述、可递归的 TB Server 树**
|
|
14
|
+
- 提供 React + TanStack UI(含树视图)
|
|
15
|
+
- 可部署到 Cloudflare Workers
|
|
16
|
+
- 支持 bridge 自身的 Bearer token 或 OAuth JWT verification
|
|
17
|
+
- 支持向上游 MCP server 透传 Bearer token
|
|
18
|
+
- 统一错误契约(`UpstreamError → 502` 等,code 级 retryable 语义)
|
|
19
|
+
- Provider / Publication / Placement 控制面实体 + `/api/providers/**` 管理面(`tbp_` 自助 key)
|
|
20
|
+
- 单包多入口 SDK(如 `@evilstar9527/tool-bridge/host`、`/admin`、`/tunnel-agent`):见 `docs/sdk.md`(含全部 curl 等价表)
|
|
21
|
+
- 最小审计事件流(describe/call 全路径,含拒绝决策;`/api/audit/events`)
|
|
22
|
+
|
|
23
|
+
当前不支持 stdio transport,也不支持 MCP SSE fallback。
|
|
24
|
+
|
|
25
|
+
## TB Server 树(递归 `~help`)
|
|
26
|
+
|
|
27
|
+
Tool Bridge 把配置的资源组织成一棵树,每个节点都响应 `GET {path}/~help`,从根 Domain 出发即可逐级下钻:
|
|
28
|
+
|
|
29
|
+
```txt
|
|
30
|
+
GET /htbp/~help # Domain:列出 namespace / 子节点(相对路径)
|
|
31
|
+
GET /htbp/docs/~help # mid-path:列出下一层资源
|
|
32
|
+
GET /htbp/docs/context7/~help # leaf:MCP server 整体作为 end-path,内嵌全部工具
|
|
33
|
+
POST /htbp/docs/context7 # 调用:body {"tool":"<name>","arguments":{...}}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`~help` 默认返回 JSON payload(`description` + 下一层资源列表 + end-path 的 schema);资源列表中的
|
|
37
|
+
`path` **始终是相对路径**,使任意子树都可被挂载到任意 domain/path。带 `Accept: text/plain` 时返回
|
|
38
|
+
等价的 HTBP text DSL,用于兼容纯文本 Agent。
|
|
39
|
+
|
|
40
|
+
**MCP server 作为整体叶子**:树递归到 MCP 节点即停,不再把每个工具拆成下一层路径。MCP 节点的
|
|
41
|
+
`~help` 是一个 end-path,其 `endpoint.tools` 内嵌该 server 暴露的所有工具(含 `inputSchema`);调用统一
|
|
42
|
+
`POST /htbp/.../{mcp}`,在 body 里用 `tool` 字段选择具体工具。
|
|
43
|
+
|
|
44
|
+
节点类型(配置在 `MCP_SERVERS_JSON`):
|
|
45
|
+
|
|
46
|
+
- `directory`:纯中间节点,`~help` 列出子节点。
|
|
47
|
+
- `mcp`:MCP Streamable HTTP leaf(整体叶子,工具内嵌)。
|
|
48
|
+
- `http`:Custom HTTP handler,声明 `endpoints[]`(method/url/inputSchema)。
|
|
49
|
+
- `remote`:联邦到另一个 TB 实例(`helpUrl`),由 crawler 跟进其 JSON `~help`。
|
|
50
|
+
- `mount`:把对象存储(Cloudflare R2 bucket)的前缀树挂成 TB 子树。前缀=目录、对象=只读叶子,按层
|
|
51
|
+
懒加载(访问某层 `~help` 时才 list 该层)。
|
|
52
|
+
- `builtin`:宿主实现的 whole-leaf(形如 MCP 叶子),声明一组静态工具,工具实现由**宿主 Worker** 注入。
|
|
53
|
+
adapter 只负责树/help/路由,实际 handler 通过 `AdapterContext.builtinHandlers` 传入——这样 tool-bridge
|
|
54
|
+
保持通用,宿主(如 Watt)注入自己的 `websearch` 等实现。
|
|
55
|
+
|
|
56
|
+
### Builtin(宿主注入 handler)
|
|
57
|
+
|
|
58
|
+
`builtin` 节点声明工具及其 `handler` 名,handler 的具体实现由宿主在处理请求时通过
|
|
59
|
+
`AdapterContext.builtinHandlers`(一个 `{ [handler名]: (input, ctx) => result }` 注册表)注入:
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
{
|
|
63
|
+
"type": "builtin",
|
|
64
|
+
"id": "host",
|
|
65
|
+
"title": "Host Tools",
|
|
66
|
+
"builtin": {
|
|
67
|
+
"tools": [
|
|
68
|
+
{ "name": "echo", "handler": "echo", "effect": "read" },
|
|
69
|
+
{ "name": "websearch", "handler": "websearch", "effect": "external", "scope": "net.search", "confirm": true }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
内置 `echoHandler`(`adapters/builtin.ts`)是一个即用参考实现,宿主可直接注册它或自己的 handler。工具的
|
|
76
|
+
`effect` / `scope` / `confirm` 语义字段会随 `~help`(JSON 与 text DSL)一并输出。
|
|
77
|
+
|
|
78
|
+
### 工具调用语义(effect / scope / confirm)
|
|
79
|
+
|
|
80
|
+
`mcp` 内嵌工具、`http` 端点、`builtin` 工具都可声明可选的调用语义,供 agent / UI 判断是否需要确认:
|
|
81
|
+
|
|
82
|
+
- `effect`:`read` | `write` | `destructive` | `external`(缺省视为 `external`,与历史行为一致)。
|
|
83
|
+
- `scope`:该调用所需的权限/能力范围(自由文本)。
|
|
84
|
+
- `confirm`:提示客户端调用前应确认。
|
|
85
|
+
|
|
86
|
+
这些字段进入 JSON `~help` 的 `endpoint`(whole-leaf 的每个 `tools[]` 条目或单发 endpoint),text DSL 里
|
|
87
|
+
则渲染为 `effect` 行以及可选的 `scope` / `confirm` 行。未声明时行为不变(`effect external`、无 scope、无
|
|
88
|
+
confirm),完全向后兼容。
|
|
89
|
+
|
|
90
|
+
### Mount(FS / S3 as TB)
|
|
91
|
+
|
|
92
|
+
`mount` 节点把一个 R2 bucket(或其下某个 `prefix`)映射成 TB 子树:每个文件夹是 directory,每个文件是
|
|
93
|
+
只读 end-path 叶子,`GET` 调用返回文件内容。
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
{ "type": "mount", "id": "files", "title": "Files", "bucket": "TB_FILES", "prefix": "docs" }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`bucket` 是 Worker 上的 R2 binding 名称(在 `wrangler.jsonc` 的 `r2_buckets` 里配置)。存储后端通过
|
|
100
|
+
`StorageProvider` 接口接入,目前实现了 R2;S3 兼容 API 等可作为后续 provider 接入而不改 adapter。
|
|
101
|
+
|
|
102
|
+
### Tools Management(工具虚拟化)
|
|
103
|
+
|
|
104
|
+
`mcp` 节点支持对其工具做 namespace 前缀、重命名、隐藏与描述覆盖。对外只暴露虚拟名,调用时由 bridge
|
|
105
|
+
反向映射回上游真实名;隐藏的工具不出现在 `~help` 中且调用被拒绝。
|
|
106
|
+
|
|
107
|
+
```jsonc
|
|
108
|
+
{
|
|
109
|
+
"type": "mcp",
|
|
110
|
+
"id": "context7",
|
|
111
|
+
"endpoint": "https://mcp.context7.com/mcp",
|
|
112
|
+
"namespace": "c7", // 暴露为 c7__<tool>,避免跨 server 重名
|
|
113
|
+
"toolOverrides": {
|
|
114
|
+
"query-docs": { "rename": "docs", "description": "Query the docs" },
|
|
115
|
+
"resolve-library-id": { "hide": true }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
上例中 `query-docs` 对外是 `c7__docs`,`resolve-library-id` 被隐藏;调用 `POST /htbp/.../context7`
|
|
121
|
+
时 `tool` 只能填 `c7__docs`(填上游真实名 `query-docs` 会被拒绝)。
|
|
122
|
+
|
|
123
|
+
服务端 crawler:
|
|
124
|
+
|
|
125
|
+
```txt
|
|
126
|
+
GET /api/tree # 从根递归 crawl 整棵树(含工具 schema)
|
|
127
|
+
POST /api/crawl # 自定义起点 / 深度,body: {start?, maxDepth?, maxNodes?}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
crawl 带环检测、深度(≤8)与节点数(≤200)上限;远端节点强制 https、可选
|
|
131
|
+
`HTBP_REMOTE_ALLOWLIST` 白名单、大小与超时上限;单个节点失败不会中断整棵 crawl。
|
|
132
|
+
|
|
133
|
+
旧的扁平 `MCP_SERVERS_JSON` 与 `/mcp/{server}/...`、`/api/servers` 路由保持完全兼容:扁平配置会被
|
|
134
|
+
自动包装成根 directory 的 `mcp` 子节点。
|
|
135
|
+
|
|
136
|
+
## 本地运行
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install
|
|
140
|
+
npm run dev
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
默认本地地址:
|
|
144
|
+
|
|
145
|
+
```txt
|
|
146
|
+
http://127.0.0.1:8787
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
线上部署地址:
|
|
150
|
+
|
|
151
|
+
```txt
|
|
152
|
+
https://tool-bridge.fantacy.live
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`npm run dev` 会先构建 UI,再启动完整 Worker。不要只用 Vite UI 来验证 bridge,因为 API 需要 Worker 路由。
|
|
156
|
+
|
|
157
|
+
## 默认 MCP Server
|
|
158
|
+
|
|
159
|
+
本地默认配置包含一个公开的 Context7 MCP server:
|
|
160
|
+
|
|
161
|
+
```txt
|
|
162
|
+
https://mcp.context7.com/mcp
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
启动后可以直接在 `Configured` tab 看到 `Context7`,也可以在 `Ad-hoc` tab 直接点 `Discover`。
|
|
166
|
+
|
|
167
|
+
## HTTP API
|
|
168
|
+
|
|
169
|
+
```txt
|
|
170
|
+
GET /api/auth/config
|
|
171
|
+
GET /api/servers
|
|
172
|
+
GET /api/servers/{server}/tools
|
|
173
|
+
POST /api/servers/{server}/tools/{tool}/call
|
|
174
|
+
|
|
175
|
+
POST /api/bridge/tools
|
|
176
|
+
POST /api/bridge/call
|
|
177
|
+
|
|
178
|
+
GET /mcp/{server}/~help
|
|
179
|
+
GET /mcp/{server}/~skill
|
|
180
|
+
POST /mcp/{server}/tools/{tool}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
示例:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
curl http://127.0.0.1:8787/api/servers
|
|
187
|
+
|
|
188
|
+
curl -X POST http://127.0.0.1:8787/api/bridge/tools \
|
|
189
|
+
-H 'Content-Type: application/json' \
|
|
190
|
+
--data '{"server":{"name":"context7","endpoint":"https://mcp.context7.com/mcp"}}'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
调用 tool:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
curl -X POST http://127.0.0.1:8787/api/bridge/call \
|
|
197
|
+
-H 'Content-Type: application/json' \
|
|
198
|
+
--data '{
|
|
199
|
+
"server": {"name":"context7","endpoint":"https://mcp.context7.com/mcp"},
|
|
200
|
+
"tool": "resolve-library-id",
|
|
201
|
+
"arguments": {"query":"React query library","libraryName":"TanStack Query"}
|
|
202
|
+
}'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 配置 MCP Server
|
|
206
|
+
|
|
207
|
+
通过 `MCP_SERVERS_JSON` 配置 server。非 secret 可以放在 `wrangler.jsonc` 的 `vars` 中;secret 应通过 `.dev.vars` 或 `wrangler secret put` 注入。
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"context7": {
|
|
212
|
+
"name": "Context7",
|
|
213
|
+
"endpoint": "https://mcp.context7.com/mcp",
|
|
214
|
+
"description": "Public documentation MCP server",
|
|
215
|
+
"headers": {
|
|
216
|
+
"Authorization": "Bearer ${CONTEXT7_TOKEN}"
|
|
217
|
+
},
|
|
218
|
+
"allowedTools": ["resolve-library-id", "query-docs"]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
上游 header 支持两种 env 引用:
|
|
224
|
+
|
|
225
|
+
```txt
|
|
226
|
+
Bearer ${TOKEN_NAME}
|
|
227
|
+
Bearer $env:TOKEN_NAME
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Bridge Auth
|
|
231
|
+
|
|
232
|
+
如果不配置 `AUTH_BEARER_TOKEN` 或 `OAUTH_ISSUER`,bridge 处于 unauthenticated mode。
|
|
233
|
+
|
|
234
|
+
静态 Bearer gate:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
wrangler secret put AUTH_BEARER_TOKEN
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
OAuth JWT verification:
|
|
241
|
+
|
|
242
|
+
```txt
|
|
243
|
+
OAUTH_ISSUER=https://issuer.example.com
|
|
244
|
+
OAUTH_REQUIRED_AUDIENCE=tool-bridge
|
|
245
|
+
OAUTH_JWKS_URI=https://issuer.example.com/.well-known/jwks.json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`OAUTH_JWKS_URI` 可省略;Worker 会读取 issuer 的 OpenID configuration 来发现 `jwks_uri`。
|
|
249
|
+
|
|
250
|
+
## 多租户(Tenant Isolation)
|
|
251
|
+
|
|
252
|
+
配置 `TENANTS` KV namespace 即开启多租户:**bearer token 此时被当作 Secret Key**,按其
|
|
253
|
+
`sha256` 哈希在 KV 中查到所属租户,加载该租户专属的 TB 树。租户之间互不可见、互不可调用——
|
|
254
|
+
A 的请求只能解析/调用/crawl A 树内的节点,访问 B 独有节点返回 404。
|
|
255
|
+
|
|
256
|
+
KV schema:
|
|
257
|
+
|
|
258
|
+
```txt
|
|
259
|
+
apikey:{sha256hex(secretKey)} -> {"tenantId":"acme","label":"...","createdAt":"..."}
|
|
260
|
+
tenant:{tenantId} -> 该租户的树配置 JSON(同 MCP_SERVERS_JSON 形态)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
原始 Secret Key 不落盘(只存哈希),按哈希查找,天然常量时间。
|
|
264
|
+
|
|
265
|
+
种数据并启用:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
wrangler kv key put --binding=TENANTS "tenant:acme" '<tree-json>'
|
|
269
|
+
# hash 用 sha256(secretKey) 计算后填入
|
|
270
|
+
wrangler kv key put --binding=TENANTS "apikey:<sha256hex>" '{"tenantId":"acme"}'
|
|
271
|
+
curl -H "Authorization: Bearer <secretKey>" https://tool-bridge.fantacy.live/htbp/~help
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
未配置 `TENANTS` 时,bridge 退回单租户的全局 `MCP_SERVERS_JSON` 树(向后兼容,行为不变)。
|
|
275
|
+
本轮仅做隔离 + Secret Key;OAuth 用户、User Group/per-node 权限、admin 为后续。
|
|
276
|
+
|
|
277
|
+
## 部署
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npm run deploy
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
部署前建议先跑:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm run check
|
|
287
|
+
npx wrangler deploy --dry-run
|
|
288
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { i as e, n as t, r as n, t as r } from "./chunks/client-BWz3o-l-.js";
|
|
2
|
+
import { https as i, serviceBinding as a } from "./transport.js";
|
|
3
|
+
//#region src/sdk/admin/index.ts
|
|
4
|
+
function o(r) {
|
|
5
|
+
let { transport: i, credential: a } = r, o = (t, n) => e(i, a, t, n);
|
|
6
|
+
return {
|
|
7
|
+
auth: { config: () => e(i, void 0, "/api/auth/config") },
|
|
8
|
+
providers: {
|
|
9
|
+
list: () => o("/api/providers").then((e) => e.providers),
|
|
10
|
+
get: (e) => o(`/api/providers/${encodeURIComponent(e)}`).then((e) => e.provider),
|
|
11
|
+
create: (e) => o("/api/providers", {
|
|
12
|
+
method: "POST",
|
|
13
|
+
body: e
|
|
14
|
+
}).then((e) => e.provider),
|
|
15
|
+
update: (e, t) => o(`/api/providers/${encodeURIComponent(e)}`, {
|
|
16
|
+
method: "PUT",
|
|
17
|
+
body: t
|
|
18
|
+
}).then((e) => e.provider),
|
|
19
|
+
delete: (e) => o(`/api/providers/${encodeURIComponent(e)}`, { method: "DELETE" }),
|
|
20
|
+
createKey: (e, t = {}) => o(`/api/providers/${encodeURIComponent(e)}/keys`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
body: t
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
publications: {
|
|
26
|
+
list: (e) => o(`/api/providers/${encodeURIComponent(e)}/pubs`).then((e) => e.publications),
|
|
27
|
+
get: (e, t) => o(`/api/providers/${encodeURIComponent(e)}/pubs/${encodeURIComponent(t)}`).then((e) => e.publication),
|
|
28
|
+
create: (e, t) => o(`/api/providers/${encodeURIComponent(e)}/pubs`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: t
|
|
31
|
+
}).then((e) => e.publication),
|
|
32
|
+
update: (e, t, n) => o(`/api/providers/${encodeURIComponent(e)}/pubs/${encodeURIComponent(t)}`, {
|
|
33
|
+
method: "PUT",
|
|
34
|
+
body: n
|
|
35
|
+
}).then((e) => e.publication),
|
|
36
|
+
delete: (e, t) => o(`/api/providers/${encodeURIComponent(e)}/pubs/${encodeURIComponent(t)}`, { method: "DELETE" }),
|
|
37
|
+
publish: (e, t) => o(`/api/providers/${encodeURIComponent(e)}/pubs/${encodeURIComponent(t)}/publish`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: {}
|
|
40
|
+
}).then((e) => e.publication)
|
|
41
|
+
},
|
|
42
|
+
placements: {
|
|
43
|
+
list: (e) => o(`/api/placements${e ? `?tenant=${encodeURIComponent(e)}` : ""}`).then((e) => e.placements),
|
|
44
|
+
put: (e) => o("/api/placements", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: e
|
|
47
|
+
}),
|
|
48
|
+
dryRun: (e) => o("/api/placements", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: {
|
|
51
|
+
...e,
|
|
52
|
+
dryRun: !0
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
delete: (e, t, n = {}) => o(`/api/placements/${encodeURIComponent(e)}?${new URLSearchParams({
|
|
56
|
+
...t ? { tenant: t } : {},
|
|
57
|
+
...n.dryRun ? { dryRun: "true" } : {}
|
|
58
|
+
}).toString()}`, { method: "DELETE" })
|
|
59
|
+
},
|
|
60
|
+
hosts: {
|
|
61
|
+
create: (e) => o("/api/hosts", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: e
|
|
64
|
+
}).then((e) => e.host),
|
|
65
|
+
get: (e) => o(`/api/hosts/${encodeURIComponent(e)}`).then((e) => e.host),
|
|
66
|
+
createKey: (e, t = {}) => o(`/api/hosts/${encodeURIComponent(e)}/keys`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: t
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
endpoints: {
|
|
72
|
+
list: () => o("/api/endpoints").then((e) => e.endpoints),
|
|
73
|
+
create: (e) => o("/api/endpoints", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: e
|
|
76
|
+
}).then((e) => e.endpoint),
|
|
77
|
+
get: (e) => o(`/api/endpoints/${encodeURIComponent(e)}`).then((e) => e.endpoint),
|
|
78
|
+
update: (e, t) => o(`/api/endpoints/${encodeURIComponent(e)}`, {
|
|
79
|
+
method: "PUT",
|
|
80
|
+
body: t
|
|
81
|
+
}).then((e) => e.endpoint),
|
|
82
|
+
revoke: (e) => o(`/api/endpoints/${encodeURIComponent(e)}`, { method: "DELETE" }).then((e) => e.endpoint)
|
|
83
|
+
},
|
|
84
|
+
commandPolicies: {
|
|
85
|
+
list: () => o("/api/command-policies").then((e) => e.policies),
|
|
86
|
+
create: (e) => o("/api/command-policies", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body: e
|
|
89
|
+
}).then((e) => e.policy),
|
|
90
|
+
get: (e) => o(`/api/command-policies/${encodeURIComponent(e)}`).then((e) => e.policy),
|
|
91
|
+
update: (e, t) => o(`/api/command-policies/${encodeURIComponent(e)}`, {
|
|
92
|
+
method: "PUT",
|
|
93
|
+
body: t
|
|
94
|
+
}).then((e) => e.policy),
|
|
95
|
+
delete: (e) => o(`/api/command-policies/${encodeURIComponent(e)}`, { method: "DELETE" })
|
|
96
|
+
},
|
|
97
|
+
audit: { events: (e = {}) => {
|
|
98
|
+
let t = new URLSearchParams();
|
|
99
|
+
return e.tenant && t.set("tenant", e.tenant), e.limit !== void 0 && t.set("limit", String(e.limit)), o(`/api/audit/events${t.size > 0 ? `?${t.toString()}` : ""}`);
|
|
100
|
+
} },
|
|
101
|
+
servers: {
|
|
102
|
+
list: () => o("/api/servers").then((e) => ({
|
|
103
|
+
servers: e.servers,
|
|
104
|
+
dynamicEnabled: e.dynamicEnabled === !0
|
|
105
|
+
})),
|
|
106
|
+
create: (e) => o("/api/servers", {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: e
|
|
109
|
+
}),
|
|
110
|
+
delete: (e) => o(`/api/servers/${encodeURIComponent(e)}`, { method: "DELETE" }),
|
|
111
|
+
get: (e) => o(`/api/servers/${encodeURIComponent(e)}`),
|
|
112
|
+
tools: (e) => o(`/api/servers/${encodeURIComponent(e)}/tools`),
|
|
113
|
+
help: async (e) => {
|
|
114
|
+
let r = await n(i, a, `/api/servers/${encodeURIComponent(e)}/~help`, { accept: "text/plain" });
|
|
115
|
+
if (!r.ok) throw await t(r);
|
|
116
|
+
return r.text();
|
|
117
|
+
},
|
|
118
|
+
skill: async (e) => {
|
|
119
|
+
let r = await n(i, a, `/api/servers/${encodeURIComponent(e)}/~skill`, { accept: "text/markdown" });
|
|
120
|
+
if (!r.ok) throw await t(r);
|
|
121
|
+
return r.text();
|
|
122
|
+
},
|
|
123
|
+
call: (e, t, n = {}) => o(`/api/servers/${encodeURIComponent(e)}/tools/${encodeURIComponent(t)}`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: { arguments: n }
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
bridge: {
|
|
129
|
+
tools: (e) => o("/api/bridge/tools", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: { server: e }
|
|
132
|
+
}),
|
|
133
|
+
call: (e, t, n = {}) => o("/api/bridge/call", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
body: {
|
|
136
|
+
server: e,
|
|
137
|
+
tool: t,
|
|
138
|
+
arguments: n
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
tree: {
|
|
143
|
+
get: () => o("/api/tree").then((e) => e.tree),
|
|
144
|
+
crawl: (e = {}) => o("/api/crawl", {
|
|
145
|
+
method: "POST",
|
|
146
|
+
body: e
|
|
147
|
+
}).then((e) => e.tree),
|
|
148
|
+
help: (e = "") => o(`/htbp/${e.replace(/^\/+/, "").replace(/\/+$/, "")}/~help`.replace("/htbp//", "/htbp/")),
|
|
149
|
+
call: (e, t = {}) => o(`/htbp/${e.replace(/^\/+/, "")}`, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
body: t
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { r as TBApiError, o as createToolBridgeAdmin, i as https, a as serviceBinding };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { l as e } from "./errors-DJj3RaDk.js";
|
|
2
|
+
//#region src/sdk/client.ts
|
|
3
|
+
var t = class extends Error {
|
|
4
|
+
code;
|
|
5
|
+
status;
|
|
6
|
+
details;
|
|
7
|
+
retryable;
|
|
8
|
+
constructor(t, n, r, i) {
|
|
9
|
+
super(r), this.code = t, this.status = n, this.details = i, this.retryable = e(t);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
async function n(e, t, n, a = {}) {
|
|
13
|
+
let o = await r(e, t, n, a);
|
|
14
|
+
if (!o.ok) throw await i(o);
|
|
15
|
+
return await o.json();
|
|
16
|
+
}
|
|
17
|
+
async function r(e, t, n, r = {}) {
|
|
18
|
+
let i = new Headers(r.headers);
|
|
19
|
+
t && !i.has("Authorization") && i.set("Authorization", `Bearer ${t}`), r.accept && !i.has("Accept") && i.set("Accept", r.accept);
|
|
20
|
+
let a = r.body !== void 0;
|
|
21
|
+
return a && !i.has("Content-Type") && i.set("Content-Type", "application/json"), e.fetch(n, {
|
|
22
|
+
method: r.method ?? (a ? "POST" : "GET"),
|
|
23
|
+
headers: i,
|
|
24
|
+
body: a ? JSON.stringify(r.body) : void 0
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function i(e) {
|
|
28
|
+
let n = "internal_error", r = `HTTP ${e.status}`, i;
|
|
29
|
+
try {
|
|
30
|
+
let t = await e.json();
|
|
31
|
+
t?.error && typeof t.error.code == "string" && (n = t.error.code, r = typeof t.error.message == "string" ? t.error.message : r, i = t.error.details);
|
|
32
|
+
} catch {}
|
|
33
|
+
return new t(n, e.status, r, i);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { n as i, i as n, r, t };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/worker/tb/errors.ts
|
|
2
|
+
var e = /* @__PURE__ */ new Set(["UpstreamError", "EndpointUnavailable"]);
|
|
3
|
+
function t(t) {
|
|
4
|
+
return e.has(t);
|
|
5
|
+
}
|
|
6
|
+
var n = class extends Error {
|
|
7
|
+
code;
|
|
8
|
+
status;
|
|
9
|
+
details;
|
|
10
|
+
constructor(e, t, n, r) {
|
|
11
|
+
super(n), this.code = e, this.status = t, this.details = r;
|
|
12
|
+
}
|
|
13
|
+
get retryable() {
|
|
14
|
+
return t(this.code);
|
|
15
|
+
}
|
|
16
|
+
}, r = class extends n {
|
|
17
|
+
constructor(e, t) {
|
|
18
|
+
super("not_found", 404, e, t);
|
|
19
|
+
}
|
|
20
|
+
}, i = class extends n {
|
|
21
|
+
constructor(e, t) {
|
|
22
|
+
super("UpstreamError", 502, e, t);
|
|
23
|
+
}
|
|
24
|
+
}, a = class extends n {
|
|
25
|
+
constructor(e, t) {
|
|
26
|
+
super("EndpointUnavailable", 503, e, t);
|
|
27
|
+
}
|
|
28
|
+
}, o = class extends n {
|
|
29
|
+
constructor(e, t) {
|
|
30
|
+
super("Forbidden", 403, e, t);
|
|
31
|
+
}
|
|
32
|
+
}, s = class extends n {
|
|
33
|
+
constructor(e, t) {
|
|
34
|
+
super("bad_request", 400, e, t);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function c(e, t, n) {
|
|
38
|
+
return { error: {
|
|
39
|
+
code: e,
|
|
40
|
+
message: t,
|
|
41
|
+
details: n
|
|
42
|
+
} };
|
|
43
|
+
}
|
|
44
|
+
function l(e, t, n, r) {
|
|
45
|
+
return new Response(JSON.stringify(c(t, n, r), null, 2), {
|
|
46
|
+
status: e,
|
|
47
|
+
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function u(e) {
|
|
51
|
+
return e instanceof n ? l(e.status, e.code, e.message, e.details) : l(500, "internal_error", e instanceof Error ? e.message : String(e));
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { n as a, u as c, r as i, t as l, a as n, i as o, o as r, l as s, s as t };
|
package/dist/sdk/host.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { i as e, n as t, r as n, t as r } from "./chunks/client-BWz3o-l-.js";
|
|
2
|
+
import { https as i, serviceBinding as a } from "./transport.js";
|
|
3
|
+
//#region src/sdk/host/index.ts
|
|
4
|
+
function o(i) {
|
|
5
|
+
let { transport: a, credential: o, hostId: c } = i, l = {}, u = (e = {}) => {
|
|
6
|
+
let t = {};
|
|
7
|
+
return e.as && (t["X-TB-On-Behalf-Of"] = e.as), e.traceId && (t["X-TB-Trace-Id"] = e.traceId), e.reason && (t["X-TB-Reason"] = e.reason), t;
|
|
8
|
+
}, d = (e) => `/htbp/${e.replace(/^\/+/, "").replace(/\/+$/, "")}`;
|
|
9
|
+
return {
|
|
10
|
+
builtins: {
|
|
11
|
+
register(e, t) {
|
|
12
|
+
l[e] = t;
|
|
13
|
+
},
|
|
14
|
+
registry() {
|
|
15
|
+
return l;
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
mounts: { async sync(t, n = {}) {
|
|
19
|
+
if (!c) throw new r("bad_request", 400, "mounts.sync requires a hostId in createToolBridgeHost options.");
|
|
20
|
+
return e(a, o, `/api/hosts/${encodeURIComponent(c)}/mounts:sync`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
body: {
|
|
23
|
+
mounts: t,
|
|
24
|
+
prune: n.prune
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
} },
|
|
28
|
+
tree: {
|
|
29
|
+
async help(e, r = {}) {
|
|
30
|
+
let i = await n(a, o, `${d(e)}/~help`, {
|
|
31
|
+
headers: u(r),
|
|
32
|
+
accept: r.accept === "text" ? "text/plain" : "application/json"
|
|
33
|
+
});
|
|
34
|
+
if (!i.ok) throw await t(i);
|
|
35
|
+
return r.accept === "text" ? i.text() : await i.json();
|
|
36
|
+
},
|
|
37
|
+
async call(t, n, r = {}) {
|
|
38
|
+
return e(a, o, d(t), {
|
|
39
|
+
method: "POST",
|
|
40
|
+
body: n ?? {},
|
|
41
|
+
headers: u(r)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
adapters: {
|
|
46
|
+
wattError() {
|
|
47
|
+
return (e) => e instanceof r ? {
|
|
48
|
+
code: s(e),
|
|
49
|
+
message: e.message,
|
|
50
|
+
retryable: e.retryable,
|
|
51
|
+
cause: {
|
|
52
|
+
code: e.code,
|
|
53
|
+
status: e.status,
|
|
54
|
+
details: e.details
|
|
55
|
+
}
|
|
56
|
+
} : {
|
|
57
|
+
code: "internal",
|
|
58
|
+
message: e instanceof Error ? e.message : String(e),
|
|
59
|
+
retryable: !1
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
effectMap(e) {
|
|
63
|
+
let t = (t) => {
|
|
64
|
+
let n = t ?? "external";
|
|
65
|
+
return e[n] ?? n;
|
|
66
|
+
};
|
|
67
|
+
return (e) => {
|
|
68
|
+
let n = { ...e };
|
|
69
|
+
return e.endpoint && (n.endpoint = {
|
|
70
|
+
...e.endpoint,
|
|
71
|
+
effect: t(e.endpoint.effect),
|
|
72
|
+
tools: e.endpoint.tools?.map((e) => ({
|
|
73
|
+
...e,
|
|
74
|
+
effect: t(e.effect)
|
|
75
|
+
}))
|
|
76
|
+
}), n;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function s(e) {
|
|
83
|
+
switch (e.status) {
|
|
84
|
+
case 400: return "invalid_argument";
|
|
85
|
+
case 401: return "unauthenticated";
|
|
86
|
+
case 403: return "permission_denied";
|
|
87
|
+
case 404: return "not_found";
|
|
88
|
+
case 409: return "confirmation_required";
|
|
89
|
+
case 502:
|
|
90
|
+
case 503: return "unavailable";
|
|
91
|
+
default: return "internal";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function c(e) {
|
|
95
|
+
return e;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { r as TBApiError, o as createToolBridgeHost, i as https, c as s2sKey, a as serviceBinding };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { t as e } from "./chunks/client-BWz3o-l-.js";
|
|
2
|
+
import { https as t, serviceBinding as n } from "./transport.js";
|
|
3
|
+
import { createToolBridgeAdmin as r } from "./admin.js";
|
|
4
|
+
import { createToolBridgeHost as i, s2sKey as a } from "./host.js";
|
|
5
|
+
import { createTunnelAgent as o } from "./tunnel-agent.js";
|
|
6
|
+
export { e as TBApiError, r as createToolBridgeAdmin, i as createToolBridgeHost, o as createTunnelAgent, t as https, a as s2sKey, n as serviceBinding };
|
package/dist/sdk/tb.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/sdk/transport.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
let n = e.replace(/\/+$/, ""), r = t ?? ((e, t) => fetch(e, t));
|
|
4
|
+
return { fetch(e, t) {
|
|
5
|
+
return r(`${n}${e.startsWith("/") ? e : `/${e}`}`, t);
|
|
6
|
+
} };
|
|
7
|
+
}
|
|
8
|
+
function t(e) {
|
|
9
|
+
return { fetch(t, n) {
|
|
10
|
+
return e.fetch(new Request(`https://tool-bridge.internal${t}`, n));
|
|
11
|
+
} };
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { e as https, t as serviceBinding };
|