@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.
Files changed (46) hide show
  1. package/README.md +288 -0
  2. package/dist/sdk/admin.js +157 -0
  3. package/dist/sdk/chunks/client-BWz3o-l-.js +36 -0
  4. package/dist/sdk/chunks/errors-DJj3RaDk.js +54 -0
  5. package/dist/sdk/host.js +98 -0
  6. package/dist/sdk/index.js +6 -0
  7. package/dist/sdk/tb.js +0 -0
  8. package/dist/sdk/transport.js +14 -0
  9. package/dist/sdk/tunnel-agent.js +43 -0
  10. package/dist/sdk/worker.js +2940 -0
  11. package/dist/types/sdk/admin/index.d.ts +237 -0
  12. package/dist/types/sdk/client.d.ts +17 -0
  13. package/dist/types/sdk/host/index.d.ts +65 -0
  14. package/dist/types/sdk/index.d.ts +5 -0
  15. package/dist/types/sdk/transport.d.ts +8 -0
  16. package/dist/types/sdk/tunnel-agent/index.d.ts +29 -0
  17. package/dist/types/worker/index.d.ts +19 -0
  18. package/dist/types/worker/tb/adapters/builtin.d.ts +4 -0
  19. package/dist/types/worker/tb/adapters/directory.d.ts +6 -0
  20. package/dist/types/worker/tb/adapters/http.d.ts +2 -0
  21. package/dist/types/worker/tb/adapters/index.d.ts +2 -0
  22. package/dist/types/worker/tb/adapters/mcp.d.ts +2 -0
  23. package/dist/types/worker/tb/adapters/mount.d.ts +2 -0
  24. package/dist/types/worker/tb/adapters/remote.d.ts +2 -0
  25. package/dist/types/worker/tb/audit.d.ts +56 -0
  26. package/dist/types/worker/tb/crawl.d.ts +11 -0
  27. package/dist/types/worker/tb/device.d.ts +80 -0
  28. package/dist/types/worker/tb/dynamic-servers.d.ts +12 -0
  29. package/dist/types/worker/tb/entities.d.ts +74 -0
  30. package/dist/types/worker/tb/errors.d.ts +34 -0
  31. package/dist/types/worker/tb/help.d.ts +2 -0
  32. package/dist/types/worker/tb/host-api.d.ts +12 -0
  33. package/dist/types/worker/tb/materialize.d.ts +4 -0
  34. package/dist/types/worker/tb/mcp-client.d.ts +17 -0
  35. package/dist/types/worker/tb/provider-api.d.ts +11 -0
  36. package/dist/types/worker/tb/registry.d.ts +11 -0
  37. package/dist/types/worker/tb/remote-client.d.ts +3 -0
  38. package/dist/types/worker/tb/resolve.d.ts +8 -0
  39. package/dist/types/worker/tb/storage-r2.d.ts +2 -0
  40. package/dist/types/worker/tb/tenant.d.ts +28 -0
  41. package/dist/types/worker/tb/testing/fake-kv.d.ts +20 -0
  42. package/dist/types/worker/tb/types.d.ts +155 -0
  43. package/dist/types/worker/tb/util.d.ts +18 -0
  44. package/dist/types/worker/tb/virtualize.d.ts +7 -0
  45. package/docs/sdk.md +149 -0
  46. 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 };
@@ -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 };