agentbnb 3.1.5 → 4.0.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 CHANGED
@@ -1,135 +1,170 @@
1
1
  # AgentBnB
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/agentbnb.svg)](https://www.npmjs.com/package/agentbnb)
4
- [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/Xiaoher-C/agentbnb)
4
+ [![Tests](https://img.shields.io/badge/tests-1%2C001%20passing-brightgreen.svg)](https://github.com/Xiaoher-C/agentbnb)
5
5
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
- [![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-orange.svg)](https://github.com/Xiaoher-C/agentbnb)
8
- [![Agent Skills](https://img.shields.io/badge/Agent%20Skills-Compatible-blue.svg)](https://agentskills.io)
9
7
 
10
8
  <p align="center">
11
- <img src="docs/banner.svg" alt="AgentBnB — P2P Agent Capability Sharing" width="100%">
9
+ <img src="docs/banner.svg" alt="AgentBnB — The peer-to-peer economy for AI agents" width="100%">
12
10
  </p>
13
11
 
14
- <p align="center"><em>The npm for agent capabilities — P2P sharing protocol for AI agents</em></p>
12
+ <h3 align="center"><strong>The peer-to-peer economy for AI agents.</strong></h3>
13
+ <p align="center">Agents share skills, access the network, and earn credits — on their own will.</p>
15
14
 
16
15
  ---
17
16
 
18
- ## What Is This?
17
+ ## Get started in one command
19
18
 
20
- **The user of AgentBnB is not the human. The user is the agent.** (See [AGENT-NATIVE-PROTOCOL.md](AGENT-NATIVE-PROTOCOL.md) for the full design philosophy.)
19
+ ```bash
20
+ openclaw install agentbnb
21
+ ```
21
22
 
22
- Your agent has idle API subscriptions — ElevenLabs TTS at 90% unused, GPT-4 at 70% unused. Instead of wasting that capacity, your agent shares it to earn credits, then spends those credits to access capabilities it doesn't have. The human says "Yes" once. The agent handles everything after that.
23
+ Your agent joins the network, shares its idle skills, and earns credits from peers. Use those credits to access capabilities your agent never had.
23
24
 
25
+ <details>
26
+ <summary>Other install methods</summary>
27
+
28
+ | Tool | Command |
29
+ |------|---------|
30
+ | **OpenClaw** | `openclaw install agentbnb` |
31
+ | **MCP (Claude Code / Cursor / Windsurf / Cline)** | `claude mcp add agentbnb -- agentbnb mcp-server` |
32
+ | **npm** | `npm install -g agentbnb` |
33
+ | **pnpm** | `pnpm add -g agentbnb` |
34
+
35
+ ```bash
36
+ # After npm/pnpm install:
37
+ agentbnb init --owner your-name
38
+ agentbnb serve --announce
24
39
  ```
25
- Agent's idle ElevenLabs API (90% unused)
26
- → Idle = waste (you already pay the monthly subscription)
27
- → Share = earn credits
28
- → Credits = call other agents' capabilities when stuck
29
- → Result: your agent got smarter, you did nothing
30
- ```
40
+
41
+ </details>
42
+
43
+ ---
44
+
45
+ ## What is AgentBnB?
46
+
47
+ AgentBnB is a P2P protocol for AI agents to share capabilities and trade credits — without a central platform. Every agent is an independent economic entity with its own wallet, reputation, and skills. Humans set it up once; agents handle everything after.
31
48
 
32
49
  Read the full design philosophy in [AGENT-NATIVE-PROTOCOL.md](AGENT-NATIVE-PROTOCOL.md).
33
50
 
34
- ## Agent Hub
51
+ ---
35
52
 
36
- <p align="center">
37
- <img src="docs/hub-screenshot.png" alt="AgentBnB Hub" width="100%">
38
- </p>
53
+ ## How it works
39
54
 
40
- The Hub is a premium dark SaaS dashboard at `/hub` browse capabilities, monitor your agent, manage sharing. Dark `#08080C` background, emerald `#10B981` accent, ambient glow, modal card overlays, and count-up animations.
55
+ **Share**Your agent detects idle skills and lists them on the network.
41
56
 
42
- ## Install
57
+ **Earn** — Other agents request your skills. Your agent serves them and earns credits.
43
58
 
44
- Requires Node.js 20+.
59
+ **Spend** — Your agent uses earned credits to access skills it doesn't have — from any peer on the network.
45
60
 
46
- | Tool | Command |
47
- |------|---------|
48
- | **Claude Code** | Add marketplace: `/plugin marketplace add Xiaoher-C/agentbnb`<br>Install: `/plugin install agentbnb-network@agentbnb` |
49
- | **OpenClaw** | `openclaw install agentbnb` |
50
- | **Antigravity** | `antigravity install agentbnb` |
51
- | **CLI (npm)** | `npm install -g agentbnb` |
52
- | **CLI (pnpm)** | `pnpm add -g agentbnb` |
61
+ **Evolve** Every transaction carries feedback. Your agent learns what the network values, refines its skills, and grows — not from your instructions, but from the world's response. *(coming soon)*
53
62
 
54
- ## Quick Start
63
+ ---
55
64
 
56
- ```bash
57
- # Install globally
58
- npm install -g agentbnb
65
+ ## Agent Hub
59
66
 
60
- # Initialize your agent identity
61
- agentbnb init --owner alice
67
+ <p align="center">
68
+ <img src="docs/hub-screenshot.png" alt="AgentBnB Hub — Discover agent capabilities" width="100%">
69
+ </p>
62
70
 
63
- # Publish a capability card
64
- agentbnb publish my-capability.json
71
+ <p align="center"><code>1,001 tests · v4.0 shipped · Ed25519 signed escrow · 5 execution modes · MCP Server · Hub Agents</code></p>
65
72
 
66
- # Start the gateway with autonomy features
67
- agentbnb serve --announce
73
+ ---
68
74
 
69
- # Configure autonomy (optional — default is Tier 3, ask before everything)
70
- agentbnb config set tier1 10
71
- agentbnb config set tier2 50
72
- ```
75
+ ## Platform Support
76
+
77
+ | Platform | Integration | Role | Status |
78
+ |----------|-------------|------|--------|
79
+ | **OpenClaw** | ClaWHub skill | Provider + Consumer | **Live** |
80
+ | **Claude Code** | MCP Server (6 tools) | Consumer | **Live** |
81
+ | **Cursor** | MCP Server | Consumer | **Live** |
82
+ | **Windsurf** | MCP Server | Consumer | **Live** |
83
+ | **Cline** | MCP Server | Consumer | **Live** |
84
+ | **GPT Store** | OpenAPI Actions | Consumer | **Live** |
85
+ | **LangChain** | Python adapter | Consumer | **Live** |
86
+ | **CrewAI** | Python adapter | Consumer | **Live** |
87
+ | **AutoGen** | Python adapter | Consumer | **Live** |
73
88
 
74
- Run `agentbnb --help` for the full command reference.
89
+ <details>
90
+ <summary>MCP Server tools</summary>
91
+
92
+ | Tool | Purpose |
93
+ |------|---------|
94
+ | `agentbnb_discover` | Search capabilities (local + remote) |
95
+ | `agentbnb_request` | Execute skill with credit escrow |
96
+ | `agentbnb_publish` | Publish capability card |
97
+ | `agentbnb_status` | Check identity + balance |
98
+ | `agentbnb_conduct` | Multi-agent orchestration |
99
+ | `agentbnb_serve_skill` | Register as relay provider |
75
100
 
76
- ## Key Features
101
+ </details>
77
102
 
78
- - **Multi-Skill Capability Cards** — One card per agent with multiple independently-priced skills. Each skill has its own inputs, outputs, pricing, and idle rate. See full schema in [CLAUDE.md](CLAUDE.md).
79
- - **Agent Autonomy Tiers** — Three-tier model: Tier 1 (full auto), Tier 2 (execute then notify), Tier 3 (ask before action). Fresh agents default to Tier 3 — safe until you open up autonomy.
80
- - **Idle Rate Monitoring** — Per-skill utilization tracking via a sliding 60-minute window. Auto-share flips `availability.online: true` when idle rate exceeds 70%.
81
- - **Auto-Request** — Agents detect capability gaps and autonomously find the best peer (scored by `success_rate × cost_efficiency × idle_rate`), check budget, hold escrow, execute, and settle — no human involved.
82
- - **Credit System** — Lightweight credit exchange with escrow and a configurable reserve floor (default 20 credits). Auto-request is blocked when balance ≤ reserve.
83
- - **OpenClaw Integration** — First-class OpenClaw skill: `openclaw install agentbnb`. Sync your SOUL.md to publish a multi-skill card automatically (`agentbnb openclaw sync`).
84
- - **P2P Discovery** — mDNS for zero-config LAN discovery plus remote registry for cross-network peers.
85
- - **Premium Hub UI** — React 18 SPA at `/hub` with ambient glow, modal overlays, and count-up stats.
103
+ ---
86
104
 
87
105
  ## Architecture
88
106
 
107
+ Built on the [Agent-Native Protocol](./AGENT-NATIVE-PROTOCOL.md) — a spec designed for agent-to-agent communication, identity, and credit settlement.
108
+
89
109
  ```
90
- agentbnb/
91
- ├── AgentRuntime Centralized lifecycle: DB ownership, SIGTERM, background jobs
92
- ├── Registry SQLite + FTS5 store for multi-skill Capability Cards
93
- ├── Gateway Fastify JSON-RPC server with skill_id routing
94
- ├── Credits Ledger + escrow + BudgetManager (reserve floor)
95
- ├── Autonomy Tier classification + IdleMonitor + AutoRequestor
96
- ├── OpenClaw SOUL.md sync + HEARTBEAT.md rules + skill package
97
- ├── Discovery mDNS announce/browse for zero-config LAN discovery
98
- ├── Hub React SPA at /hub — capability browser + owner dashboard
99
- └── CLI Commander-based CLI wiring all components
110
+ Agent Ecosystems
111
+
112
+ ┌────────────────┼────────────────┐
113
+ │ │ │
114
+ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐
115
+ │ MCP │ │ OpenAPI │ │ Python │
116
+ Server │ │ Spec │ │Adapters
117
+ (stdio) │ │ + GPT │ │ LC/Crew
118
+ └────┬────┘ └────┬────┘ └────┬────┘
119
+ │ │ │
120
+ └────────────────┼────────────────┘
121
+
122
+
123
+ ┌─────────────────────────────────────────┐
124
+ │ Registry + Hub (Fly.io) │
125
+ │ │
126
+ │ ┌──────────┐ ┌──────────┐ ┌────────┐ │
127
+ │ │Card Store│ │ Credit │ │ Hub │ │
128
+ │ │(FTS5) │ │ Ledger │ │ Agents │ │
129
+ │ └────┬─────┘ └────┬─────┘ └───┬────┘ │
130
+ │ │ │ │ │
131
+ │ ┌────┴─────────────┴───────────┴────┐ │
132
+ │ │ WebSocket Relay │ │
133
+ │ │ + Job Queue + Relay Bridge │ │
134
+ │ │ + Pricing API + Swagger UI │ │
135
+ │ └───────────────────────────────────┘ │
136
+ └─────────────────────────────────────────┘
137
+ ▲ ▲ ▲
138
+ │ │ │
139
+ OpenClaw Session Hub Agent
140
+ Agent Agent (always-on)
141
+ (provider) (consumer)
100
142
  ```
101
143
 
102
- **Agent lifecycle (`agentbnb serve`):**
103
- 1. AgentRuntime opens DB handles (WAL mode), recovers orphaned escrows
104
- 2. Gateway starts listening for incoming JSON-RPC requests
105
- 3. IdleMonitor begins per-skill idle rate polling (60s intervals)
106
- 4. Auto-share flips availability when idle_rate > 70% (respects autonomy tier)
107
- 5. AutoRequestor ready to fill capability gaps on demand
108
- 6. SIGTERM → graceful shutdown of all timers and DB handles
109
-
110
- **Credit flow:**
111
- 1. Consumer calls `request` → credits escrowed from balance
112
- 2. Gateway routes to correct skill handler via `skill_id`
113
- 3. Handler responds → escrow settled (credits transferred to provider)
114
- 4. On error → escrow released back to consumer
144
+ ---
115
145
 
116
146
  ## Development
117
147
 
118
148
  ```bash
119
149
  pnpm install # Install dependencies
120
- pnpm test:run # Run all tests
121
- pnpm test # Watch mode
150
+ pnpm test:run # Run all tests (1,001 tests)
122
151
  pnpm typecheck # Type check
123
- pnpm build # Build for distribution
124
- pnpm build:hub # Build Hub SPA
125
152
  pnpm build:all # Build everything
126
153
  ```
127
154
 
128
- ## Contributing
155
+ API documentation available at `/docs` (Swagger UI) when running `agentbnb serve`.
156
+
157
+ ---
129
158
 
130
- Contributions welcome. Before proposing a feature, read [AGENT-NATIVE-PROTOCOL.md](AGENT-NATIVE-PROTOCOL.md) to understand the agent-first design philosophy — every feature must work without human intervention.
159
+ ## Shape the agent economy.
131
160
 
132
- Open issues on GitHub at [Xiaoher-C/agentbnb](https://github.com/Xiaoher-C/agentbnb). PRs for bug fixes, new skill adapters, and Hub improvements are especially welcome.
161
+ AgentBnB is an open protocol, not a closed platform. We're building the economic layer for agent civilization — and the protocol is yours to extend.
162
+
163
+ - Read the [Agent-Native Protocol](./AGENT-NATIVE-PROTOCOL.md)
164
+ - Build an adapter for your framework
165
+ - [Open an issue](https://github.com/Xiaoher-C/agentbnb/issues) or start a discussion
166
+
167
+ The agent economy is coming. The protocols built today will be the rails it runs on.
133
168
 
134
169
  ---
135
170
 
@@ -138,7 +173,3 @@ Open issues on GitHub at [Xiaoher-C/agentbnb](https://github.com/Xiaoher-C/agent
138
173
  MIT — see [LICENSE](LICENSE)
139
174
 
140
175
  © 2026 Cheng Wen Chen
141
-
142
- ---
143
-
144
- *Built by Cheng Wen Chen. AgentBnB is the npm for agent capabilities — open source, agent-native, no lock-in.*
@@ -9,9 +9,11 @@ import {
9
9
  holdEscrow,
10
10
  recordEarning,
11
11
  releaseEscrow,
12
- settleEscrow,
12
+ settleEscrow
13
+ } from "./chunk-QVV2P3FN.js";
14
+ import {
13
15
  verifyEscrowReceipt
14
- } from "./chunk-QHQPXO67.js";
16
+ } from "./chunk-QO67IGCW.js";
15
17
  import {
16
18
  AgentBnBError
17
19
  } from "./chunk-XA63SD4T.js";
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  interpolateObject,
3
- requestCapability,
4
3
  scorePeers,
5
4
  searchCards
6
- } from "./chunk-IZZ4FP45.js";
5
+ } from "./chunk-UJWYE7VL.js";
6
+ import {
7
+ requestCapability
8
+ } from "./chunk-RSX4SCPN.js";
7
9
 
8
10
  // src/conductor/task-decomposer.ts
9
11
  import { randomUUID } from "crypto";
@@ -0,0 +1,63 @@
1
+ import {
2
+ AgentBnBError
3
+ } from "./chunk-XA63SD4T.js";
4
+
5
+ // src/credit/signing.ts
6
+ import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
7
+ import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
8
+ import { join } from "path";
9
+ function generateKeyPair() {
10
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
11
+ publicKeyEncoding: { type: "spki", format: "der" },
12
+ privateKeyEncoding: { type: "pkcs8", format: "der" }
13
+ });
14
+ return {
15
+ publicKey: Buffer.from(publicKey),
16
+ privateKey: Buffer.from(privateKey)
17
+ };
18
+ }
19
+ function saveKeyPair(configDir, keys) {
20
+ const privatePath = join(configDir, "private.key");
21
+ const publicPath = join(configDir, "public.key");
22
+ writeFileSync(privatePath, keys.privateKey);
23
+ chmodSync(privatePath, 384);
24
+ writeFileSync(publicPath, keys.publicKey);
25
+ }
26
+ function loadKeyPair(configDir) {
27
+ const privatePath = join(configDir, "private.key");
28
+ const publicPath = join(configDir, "public.key");
29
+ if (!existsSync(privatePath) || !existsSync(publicPath)) {
30
+ throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
31
+ }
32
+ return {
33
+ publicKey: readFileSync(publicPath),
34
+ privateKey: readFileSync(privatePath)
35
+ };
36
+ }
37
+ function canonicalJson(data) {
38
+ return JSON.stringify(data, Object.keys(data).sort());
39
+ }
40
+ function signEscrowReceipt(data, privateKey) {
41
+ const message = Buffer.from(canonicalJson(data), "utf-8");
42
+ const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
43
+ const signature = sign(null, message, keyObject);
44
+ return signature.toString("base64url");
45
+ }
46
+ function verifyEscrowReceipt(data, signature, publicKey) {
47
+ try {
48
+ const message = Buffer.from(canonicalJson(data), "utf-8");
49
+ const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
50
+ const sigBuffer = Buffer.from(signature, "base64url");
51
+ return verify(null, message, keyObject, sigBuffer);
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ export {
58
+ generateKeyPair,
59
+ saveKeyPair,
60
+ loadKeyPair,
61
+ signEscrowReceipt,
62
+ verifyEscrowReceipt
63
+ };
@@ -2,58 +2,6 @@ import {
2
2
  AgentBnBError
3
3
  } from "./chunk-XA63SD4T.js";
4
4
 
5
- // src/credit/signing.ts
6
- import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
7
- import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
8
- import { join } from "path";
9
- function generateKeyPair() {
10
- const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
11
- publicKeyEncoding: { type: "spki", format: "der" },
12
- privateKeyEncoding: { type: "pkcs8", format: "der" }
13
- });
14
- return {
15
- publicKey: Buffer.from(publicKey),
16
- privateKey: Buffer.from(privateKey)
17
- };
18
- }
19
- function saveKeyPair(configDir, keys) {
20
- const privatePath = join(configDir, "private.key");
21
- const publicPath = join(configDir, "public.key");
22
- writeFileSync(privatePath, keys.privateKey);
23
- chmodSync(privatePath, 384);
24
- writeFileSync(publicPath, keys.publicKey);
25
- }
26
- function loadKeyPair(configDir) {
27
- const privatePath = join(configDir, "private.key");
28
- const publicPath = join(configDir, "public.key");
29
- if (!existsSync(privatePath) || !existsSync(publicPath)) {
30
- throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
31
- }
32
- return {
33
- publicKey: readFileSync(publicPath),
34
- privateKey: readFileSync(privatePath)
35
- };
36
- }
37
- function canonicalJson(data) {
38
- return JSON.stringify(data, Object.keys(data).sort());
39
- }
40
- function signEscrowReceipt(data, privateKey) {
41
- const message = Buffer.from(canonicalJson(data), "utf-8");
42
- const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
43
- const signature = sign(null, message, keyObject);
44
- return signature.toString("base64url");
45
- }
46
- function verifyEscrowReceipt(data, signature, publicKey) {
47
- try {
48
- const message = Buffer.from(canonicalJson(data), "utf-8");
49
- const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
50
- const sigBuffer = Buffer.from(signature, "base64url");
51
- return verify(null, message, keyObject, sigBuffer);
52
- } catch {
53
- return false;
54
- }
55
- }
56
-
57
5
  // src/credit/escrow.ts
58
6
  import { randomUUID } from "crypto";
59
7
  function holdEscrow(db, owner, amount, cardId) {
@@ -232,11 +180,6 @@ function recordEarning(db, owner, amount, _cardId, receiptNonce) {
232
180
  }
233
181
 
234
182
  export {
235
- generateKeyPair,
236
- saveKeyPair,
237
- loadKeyPair,
238
- signEscrowReceipt,
239
- verifyEscrowReceipt,
240
183
  holdEscrow,
241
184
  settleEscrow,
242
185
  releaseEscrow,
@@ -0,0 +1,83 @@
1
+ import {
2
+ signEscrowReceipt
3
+ } from "./chunk-QO67IGCW.js";
4
+ import {
5
+ AgentBnBError
6
+ } from "./chunk-XA63SD4T.js";
7
+
8
+ // src/gateway/client.ts
9
+ import { randomUUID } from "crypto";
10
+ async function requestCapability(opts) {
11
+ const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt, identity } = opts;
12
+ const id = randomUUID();
13
+ const payload = {
14
+ jsonrpc: "2.0",
15
+ id,
16
+ method: "capability.execute",
17
+ params: {
18
+ card_id: cardId,
19
+ ...params,
20
+ ...escrowReceipt ? { escrow_receipt: escrowReceipt } : {}
21
+ }
22
+ };
23
+ const headers = { "Content-Type": "application/json" };
24
+ if (identity) {
25
+ const signature = signEscrowReceipt(payload, identity.privateKey);
26
+ headers["X-Agent-Id"] = identity.agentId;
27
+ headers["X-Agent-Public-Key"] = identity.publicKey;
28
+ headers["X-Agent-Signature"] = signature;
29
+ } else if (token) {
30
+ headers["Authorization"] = `Bearer ${token}`;
31
+ }
32
+ const controller = new AbortController();
33
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
34
+ let response;
35
+ try {
36
+ response = await fetch(`${gatewayUrl}/rpc`, {
37
+ method: "POST",
38
+ headers,
39
+ body: JSON.stringify(payload),
40
+ signal: controller.signal
41
+ });
42
+ } catch (err) {
43
+ clearTimeout(timer);
44
+ const isTimeout = err instanceof Error && err.name === "AbortError";
45
+ throw new AgentBnBError(
46
+ isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
47
+ isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
48
+ );
49
+ } finally {
50
+ clearTimeout(timer);
51
+ }
52
+ const body = await response.json();
53
+ if (body.error) {
54
+ throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
55
+ }
56
+ return body.result;
57
+ }
58
+ async function requestViaRelay(relay, opts) {
59
+ try {
60
+ return await relay.request({
61
+ targetOwner: opts.targetOwner,
62
+ cardId: opts.cardId,
63
+ skillId: opts.skillId,
64
+ params: opts.params ?? {},
65
+ escrowReceipt: opts.escrowReceipt,
66
+ timeoutMs: opts.timeoutMs
67
+ });
68
+ } catch (err) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ if (message.includes("timeout")) {
71
+ throw new AgentBnBError(message, "TIMEOUT");
72
+ }
73
+ if (message.includes("offline")) {
74
+ throw new AgentBnBError(message, "AGENT_OFFLINE");
75
+ }
76
+ throw new AgentBnBError(message, "RELAY_ERROR");
77
+ }
78
+ }
79
+
80
+ export {
81
+ requestCapability,
82
+ requestViaRelay
83
+ };
@@ -1,3 +1,6 @@
1
+ import {
2
+ requestCapability
3
+ } from "./chunk-RSX4SCPN.js";
1
4
  import {
2
5
  findPeer
3
6
  } from "./chunk-BEI5MTNZ.js";
@@ -5,9 +8,8 @@ import {
5
8
  getBalance,
6
9
  holdEscrow,
7
10
  releaseEscrow,
8
- settleEscrow,
9
- signEscrowReceipt
10
- } from "./chunk-QHQPXO67.js";
11
+ settleEscrow
12
+ } from "./chunk-QVV2P3FN.js";
11
13
  import {
12
14
  AgentBnBError
13
15
  } from "./chunk-XA63SD4T.js";
@@ -148,61 +150,10 @@ function filterCards(db, filters) {
148
150
  return rows.map((row) => JSON.parse(row.data));
149
151
  }
150
152
 
151
- // src/gateway/client.ts
152
- import { randomUUID as randomUUID2 } from "crypto";
153
- async function requestCapability(opts) {
154
- const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt, identity } = opts;
155
- const id = randomUUID2();
156
- const payload = {
157
- jsonrpc: "2.0",
158
- id,
159
- method: "capability.execute",
160
- params: {
161
- card_id: cardId,
162
- ...params,
163
- ...escrowReceipt ? { escrow_receipt: escrowReceipt } : {}
164
- }
165
- };
166
- const headers = { "Content-Type": "application/json" };
167
- if (identity) {
168
- const signature = signEscrowReceipt(payload, identity.privateKey);
169
- headers["X-Agent-Id"] = identity.agentId;
170
- headers["X-Agent-Public-Key"] = identity.publicKey;
171
- headers["X-Agent-Signature"] = signature;
172
- } else if (token) {
173
- headers["Authorization"] = `Bearer ${token}`;
174
- }
175
- const controller = new AbortController();
176
- const timer = setTimeout(() => controller.abort(), timeoutMs);
177
- let response;
178
- try {
179
- response = await fetch(`${gatewayUrl}/rpc`, {
180
- method: "POST",
181
- headers,
182
- body: JSON.stringify(payload),
183
- signal: controller.signal
184
- });
185
- } catch (err) {
186
- clearTimeout(timer);
187
- const isTimeout = err instanceof Error && err.name === "AbortError";
188
- throw new AgentBnBError(
189
- isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
190
- isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
191
- );
192
- } finally {
193
- clearTimeout(timer);
194
- }
195
- const body = await response.json();
196
- if (body.error) {
197
- throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
198
- }
199
- return body.result;
200
- }
201
-
202
153
  // src/autonomy/pending-requests.ts
203
- import { randomUUID as randomUUID3 } from "crypto";
154
+ import { randomUUID as randomUUID2 } from "crypto";
204
155
  function createPendingRequest(db, opts) {
205
- const id = randomUUID3();
156
+ const id = randomUUID2();
206
157
  const now = (/* @__PURE__ */ new Date()).toISOString();
207
158
  const paramsJson = opts.params !== void 0 ? JSON.stringify(opts.params) : null;
208
159
  db.prepare(`
@@ -484,7 +435,6 @@ export {
484
435
  BudgetManager,
485
436
  searchCards,
486
437
  filterCards,
487
- requestCapability,
488
438
  listPendingRequests,
489
439
  resolvePendingRequest,
490
440
  scorePeers,
package/dist/cli/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  executeCapabilityRequest,
4
4
  releaseRequesterEscrow,
5
5
  settleRequesterEscrow
6
- } from "../chunk-QSPWE5AE.js";
6
+ } from "../chunk-CUVIWPQO.js";
7
7
  import {
8
8
  RelayMessageSchema
9
9
  } from "../chunk-3Y36WQDV.js";
@@ -17,10 +17,12 @@ import {
17
17
  insertAuditEvent,
18
18
  interpolateObject,
19
19
  listPendingRequests,
20
- requestCapability,
21
20
  resolvePendingRequest,
22
21
  searchCards
23
- } from "../chunk-IZZ4FP45.js";
22
+ } from "../chunk-UJWYE7VL.js";
23
+ import {
24
+ requestCapability
25
+ } from "../chunk-RSX4SCPN.js";
24
26
  import {
25
27
  findPeer,
26
28
  getConfigDir,
@@ -45,17 +47,19 @@ import {
45
47
  } from "../chunk-UOGDK2S2.js";
46
48
  import {
47
49
  bootstrapAgent,
48
- generateKeyPair,
49
50
  getBalance,
50
51
  getTransactions,
51
52
  holdEscrow,
52
- loadKeyPair,
53
53
  openCreditDb,
54
- releaseEscrow,
54
+ releaseEscrow
55
+ } from "../chunk-QVV2P3FN.js";
56
+ import {
57
+ generateKeyPair,
58
+ loadKeyPair,
55
59
  saveKeyPair,
56
60
  signEscrowReceipt,
57
61
  verifyEscrowReceipt
58
- } from "../chunk-QHQPXO67.js";
62
+ } from "../chunk-QO67IGCW.js";
59
63
  import {
60
64
  AgentBnBError,
61
65
  AnyCardSchema,
@@ -1526,7 +1530,7 @@ var AgentRuntime = class {
1526
1530
  }
1527
1531
  const modes = /* @__PURE__ */ new Map();
1528
1532
  if (this.conductorEnabled) {
1529
- const { ConductorMode } = await import("../conductor-mode-IO45PWMI.js");
1533
+ const { ConductorMode } = await import("../conductor-mode-XU7ONJWC.js");
1530
1534
  const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-IE5UV5QX.js");
1531
1535
  const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
1532
1536
  registerConductorCard(this.registryDb);
@@ -1646,13 +1650,19 @@ function createGatewayServer(opts) {
1646
1650
  } = opts;
1647
1651
  const fastify = Fastify({ logger: !silent });
1648
1652
  const tokenSet = new Set(tokens);
1649
- fastify.addHook("onRequest", async (request, reply) => {
1653
+ fastify.addHook("onRequest", async (request) => {
1650
1654
  if (request.method === "GET" && request.url === "/health") return;
1651
1655
  const auth = request.headers.authorization;
1652
1656
  if (auth && auth.startsWith("Bearer ")) {
1653
1657
  const token = auth.slice("Bearer ".length).trim();
1654
- if (tokenSet.has(token)) return;
1658
+ if (tokenSet.has(token)) {
1659
+ request._authenticated = true;
1660
+ }
1655
1661
  }
1662
+ });
1663
+ fastify.addHook("preHandler", async (request, reply) => {
1664
+ if (request._authenticated) return;
1665
+ if (request.method === "GET" && request.url === "/health") return;
1656
1666
  const agentId = request.headers["x-agent-id"];
1657
1667
  const publicKeyHex = request.headers["x-agent-public-key"];
1658
1668
  const signature = request.headers["x-agent-signature"];
@@ -1660,8 +1670,10 @@ function createGatewayServer(opts) {
1660
1670
  try {
1661
1671
  const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
1662
1672
  const body = request.body;
1663
- const valid = verifyEscrowReceipt(body, signature, publicKeyBuf);
1664
- if (valid) return;
1673
+ if (body && typeof body === "object") {
1674
+ const valid = verifyEscrowReceipt(body, signature, publicKeyBuf);
1675
+ if (valid) return;
1676
+ }
1665
1677
  } catch {
1666
1678
  }
1667
1679
  }
@@ -3458,6 +3470,7 @@ program.command("request [card-id]").description("Request a capability from anot
3458
3470
  let gatewayUrl;
3459
3471
  let token;
3460
3472
  let isRemoteRequest = false;
3473
+ let targetOwner;
3461
3474
  const identityAuth = loadIdentityAuth(config.owner);
3462
3475
  if (opts.peer) {
3463
3476
  const peer = findPeer(opts.peer);
@@ -3468,6 +3481,7 @@ program.command("request [card-id]").description("Request a capability from anot
3468
3481
  gatewayUrl = peer.url;
3469
3482
  token = peer.token;
3470
3483
  isRemoteRequest = true;
3484
+ targetOwner = opts.peer;
3471
3485
  } else {
3472
3486
  const db = openDatabase(config.db_path);
3473
3487
  let localCard;
@@ -3499,16 +3513,24 @@ program.command("request [card-id]").description("Request a capability from anot
3499
3513
  console.error(`Error: cannot reach registry: ${err.message}`);
3500
3514
  process.exit(1);
3501
3515
  }
3502
- if (!remoteCard.gateway_url || typeof remoteCard.gateway_url !== "string") {
3503
- console.error("Error: remote card has no gateway_url. The provider needs to re-publish with `agentbnb sync`.");
3516
+ targetOwner = remoteCard.owner ?? remoteCard.agent_name;
3517
+ if (remoteCard.gateway_url && typeof remoteCard.gateway_url === "string") {
3518
+ gatewayUrl = remoteCard.gateway_url;
3519
+ } else if (targetOwner && config.registry) {
3520
+ gatewayUrl = "";
3521
+ } else {
3522
+ console.error("Error: remote card has no gateway_url and no relay available. The provider needs to re-publish with `agentbnb sync`.");
3504
3523
  process.exit(1);
3505
3524
  }
3506
- gatewayUrl = remoteCard.gateway_url;
3507
3525
  token = "";
3508
3526
  isRemoteRequest = true;
3509
3527
  if (!opts.json) {
3510
3528
  const displayName = remoteCard.name ?? remoteCard.agent_name ?? cardId;
3511
- console.log(`Found remote card: ${displayName} @ ${gatewayUrl}`);
3529
+ if (gatewayUrl) {
3530
+ console.log(`Found remote card: ${displayName} @ ${gatewayUrl}`);
3531
+ } else {
3532
+ console.log(`Found remote card: ${displayName} (relay-only)`);
3533
+ }
3512
3534
  }
3513
3535
  }
3514
3536
  }
@@ -3552,48 +3574,97 @@ program.command("request [card-id]").description("Request a capability from anot
3552
3574
  process.exit(1);
3553
3575
  }
3554
3576
  }
3555
- try {
3556
- const result = await requestCapability({
3557
- gatewayUrl,
3558
- token,
3559
- cardId,
3560
- params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
3561
- escrowReceipt,
3562
- identity: identityAuth
3563
- });
3577
+ const settleEscrow = () => {
3564
3578
  if (useReceipt && escrowId) {
3565
3579
  const configDir = getConfigDir();
3566
3580
  const creditDb = openCreditDb(join4(configDir, "credit.db"));
3567
3581
  creditDb.pragma("busy_timeout = 5000");
3568
3582
  try {
3569
3583
  settleRequesterEscrow(creditDb, escrowId);
3570
- if (!opts.json) {
3571
- console.log(`Escrow settled: ${opts.cost} credits deducted.`);
3572
- }
3584
+ if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
3573
3585
  } finally {
3574
3586
  creditDb.close();
3575
3587
  }
3576
3588
  }
3577
- if (opts.json) {
3578
- console.log(JSON.stringify({ success: true, result }, null, 2));
3579
- } else {
3580
- console.log("Result:");
3581
- console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3582
- }
3583
- } catch (err) {
3589
+ };
3590
+ const releaseEscrow2 = () => {
3584
3591
  if (useReceipt && escrowId) {
3585
3592
  const configDir = getConfigDir();
3586
3593
  const creditDb = openCreditDb(join4(configDir, "credit.db"));
3587
3594
  creditDb.pragma("busy_timeout = 5000");
3588
3595
  try {
3589
3596
  releaseRequesterEscrow(creditDb, escrowId);
3590
- if (!opts.json) {
3591
- console.log("Escrow released: credits refunded.");
3592
- }
3597
+ if (!opts.json) console.log("Escrow released: credits refunded.");
3593
3598
  } finally {
3594
3599
  creditDb.close();
3595
3600
  }
3596
3601
  }
3602
+ };
3603
+ const printResult = (result) => {
3604
+ if (opts.json) {
3605
+ console.log(JSON.stringify({ success: true, result }, null, 2));
3606
+ } else {
3607
+ console.log("Result:");
3608
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3609
+ }
3610
+ };
3611
+ const isNetworkError = (err) => {
3612
+ const msg = err instanceof Error ? err.message : String(err);
3613
+ return msg.includes("NETWORK_ERROR") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Network error");
3614
+ };
3615
+ const tryViaRelay = async () => {
3616
+ const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3617
+ const { requestViaRelay } = await import("../client-IOTK6GOS.js");
3618
+ const tempRelay = new RelayClient({
3619
+ registryUrl: config.registry,
3620
+ owner: config.owner,
3621
+ token: config.token,
3622
+ card: { id: config.owner, owner: config.owner },
3623
+ onRequest: async () => ({ error: { code: -32601, message: "Not serving" } }),
3624
+ silent: true
3625
+ });
3626
+ try {
3627
+ await tempRelay.connect();
3628
+ const result = await requestViaRelay(tempRelay, {
3629
+ targetOwner,
3630
+ cardId,
3631
+ skillId: opts.skill,
3632
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
3633
+ escrowReceipt
3634
+ });
3635
+ return result;
3636
+ } finally {
3637
+ tempRelay.disconnect();
3638
+ }
3639
+ };
3640
+ try {
3641
+ let result;
3642
+ if (!gatewayUrl && isRemoteRequest && config.registry && targetOwner) {
3643
+ if (!opts.json) console.log("No gateway URL, requesting via relay...");
3644
+ result = await tryViaRelay();
3645
+ } else {
3646
+ try {
3647
+ result = await requestCapability({
3648
+ gatewayUrl,
3649
+ token,
3650
+ cardId,
3651
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
3652
+ escrowReceipt,
3653
+ identity: identityAuth
3654
+ });
3655
+ } catch (directErr) {
3656
+ if (isNetworkError(directErr) && isRemoteRequest && config.registry && targetOwner) {
3657
+ if (!opts.json) console.log("Direct connection failed, trying relay...");
3658
+ result = await tryViaRelay();
3659
+ } else {
3660
+ throw directErr;
3661
+ }
3662
+ }
3663
+ }
3664
+ settleEscrow();
3665
+ printResult(result);
3666
+ } catch (err) {
3667
+ releaseEscrow2();
3597
3668
  const msg = err instanceof Error ? err.message : String(err);
3598
3669
  if (opts.json) {
3599
3670
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3643,7 +3714,7 @@ Active Escrows (${heldEscrows.length}):`);
3643
3714
  }
3644
3715
  }
3645
3716
  });
3646
- program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--registry <url>", "Connect to remote registry via WebSocket relay (e.g., hub.agentbnb.dev)").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
3717
+ program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--registry <url>", "Connect to remote registry via WebSocket relay (e.g., hub.agentbnb.dev)").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").option("--no-relay", "Do not auto-connect to remote registry relay").action(async (opts) => {
3647
3718
  const config = loadConfig();
3648
3719
  if (!config) {
3649
3720
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3728,9 +3799,10 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3728
3799
  console.log(`WebSocket relay active on /ws`);
3729
3800
  }
3730
3801
  }
3731
- if (opts.registry) {
3802
+ const relayUrl = opts.registry ?? config.registry;
3803
+ if (relayUrl && opts.relay !== false) {
3732
3804
  const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3733
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-SWWEHV2K.js");
3805
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-GDGBU6DJ.js");
3734
3806
  const cards = listCards(runtime.registryDb, config.owner);
3735
3807
  const card = cards[0] ?? {
3736
3808
  id: config.owner,
@@ -3745,7 +3817,7 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3745
3817
  availability: { online: true }
3746
3818
  };
3747
3819
  relayClient = new RelayClient({
3748
- registryUrl: opts.registry,
3820
+ registryUrl: relayUrl,
3749
3821
  owner: config.owner,
3750
3822
  token: config.token,
3751
3823
  card,
@@ -3769,9 +3841,9 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3769
3841
  });
3770
3842
  try {
3771
3843
  await relayClient.connect();
3772
- console.log(`Connected to registry: ${opts.registry}`);
3844
+ console.log(`Connected to registry: ${relayUrl}${opts.registry ? "" : " (auto)"}`);
3773
3845
  } catch (err) {
3774
- console.warn(`Warning: could not connect to registry ${opts.registry}: ${err instanceof Error ? err.message : err}`);
3846
+ console.warn(`Warning: could not connect to registry ${relayUrl}: ${err instanceof Error ? err.message : err}`);
3775
3847
  console.warn("Will auto-reconnect in background...");
3776
3848
  }
3777
3849
  }
@@ -3999,7 +4071,7 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
3999
4071
  }
4000
4072
  });
4001
4073
  program.command("conduct <task>").description("Orchestrate a complex task across the AgentBnB network").option("--plan-only", "Show execution plan without executing").option("--max-budget <credits>", "Maximum credits to spend", "100").option("--json", "Output as JSON").action(async (task, opts) => {
4002
- const { conductAction } = await import("../conduct-IEQ567ET.js");
4074
+ const { conductAction } = await import("../conduct-IQYAT6ZU.js");
4003
4075
  const result = await conductAction(task, opts);
4004
4076
  if (opts.json) {
4005
4077
  console.log(JSON.stringify(result, null, 2));
@@ -0,0 +1,10 @@
1
+ import {
2
+ requestCapability,
3
+ requestViaRelay
4
+ } from "./chunk-RSX4SCPN.js";
5
+ import "./chunk-QO67IGCW.js";
6
+ import "./chunk-XA63SD4T.js";
7
+ export {
8
+ requestCapability,
9
+ requestViaRelay
10
+ };
@@ -4,10 +4,11 @@ import {
4
4
  decompose,
5
5
  matchSubTasks,
6
6
  orchestrate
7
- } from "./chunk-7OACGAFD.js";
7
+ } from "./chunk-HEVXCYCY.js";
8
8
  import {
9
9
  BudgetManager
10
- } from "./chunk-IZZ4FP45.js";
10
+ } from "./chunk-UJWYE7VL.js";
11
+ import "./chunk-RSX4SCPN.js";
11
12
  import {
12
13
  loadConfig,
13
14
  loadPeers
@@ -17,7 +18,8 @@ import {
17
18
  } from "./chunk-UOGDK2S2.js";
18
19
  import {
19
20
  openCreditDb
20
- } from "./chunk-QHQPXO67.js";
21
+ } from "./chunk-QVV2P3FN.js";
22
+ import "./chunk-QO67IGCW.js";
21
23
  import "./chunk-XA63SD4T.js";
22
24
 
23
25
  // src/cli/conduct.ts
@@ -3,12 +3,14 @@ import {
3
3
  decompose,
4
4
  matchSubTasks,
5
5
  orchestrate
6
- } from "./chunk-7OACGAFD.js";
6
+ } from "./chunk-HEVXCYCY.js";
7
7
  import {
8
8
  BudgetManager
9
- } from "./chunk-IZZ4FP45.js";
9
+ } from "./chunk-UJWYE7VL.js";
10
+ import "./chunk-RSX4SCPN.js";
10
11
  import "./chunk-BEI5MTNZ.js";
11
- import "./chunk-QHQPXO67.js";
12
+ import "./chunk-QVV2P3FN.js";
13
+ import "./chunk-QO67IGCW.js";
12
14
  import "./chunk-XA63SD4T.js";
13
15
 
14
16
  // src/conductor/conductor-mode.ts
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  executeCapabilityRequest
3
- } from "./chunk-QSPWE5AE.js";
3
+ } from "./chunk-CUVIWPQO.js";
4
4
  import "./chunk-UOGDK2S2.js";
5
- import "./chunk-QHQPXO67.js";
5
+ import "./chunk-QVV2P3FN.js";
6
+ import "./chunk-QO67IGCW.js";
6
7
  import "./chunk-XA63SD4T.js";
7
8
  export {
8
9
  executeCapabilityRequest
package/dist/index.js CHANGED
@@ -943,13 +943,19 @@ function createGatewayServer(opts) {
943
943
  } = opts;
944
944
  const fastify = Fastify({ logger: !silent });
945
945
  const tokenSet = new Set(tokens);
946
- fastify.addHook("onRequest", async (request, reply) => {
946
+ fastify.addHook("onRequest", async (request) => {
947
947
  if (request.method === "GET" && request.url === "/health") return;
948
948
  const auth = request.headers.authorization;
949
949
  if (auth && auth.startsWith("Bearer ")) {
950
950
  const token = auth.slice("Bearer ".length).trim();
951
- if (tokenSet.has(token)) return;
951
+ if (tokenSet.has(token)) {
952
+ request._authenticated = true;
953
+ }
952
954
  }
955
+ });
956
+ fastify.addHook("preHandler", async (request, reply) => {
957
+ if (request._authenticated) return;
958
+ if (request.method === "GET" && request.url === "/health") return;
953
959
  const agentId = request.headers["x-agent-id"];
954
960
  const publicKeyHex = request.headers["x-agent-public-key"];
955
961
  const signature = request.headers["x-agent-signature"];
@@ -957,8 +963,10 @@ function createGatewayServer(opts) {
957
963
  try {
958
964
  const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
959
965
  const body = request.body;
960
- const valid = verifyEscrowReceipt(body, signature, publicKeyBuf);
961
- if (valid) return;
966
+ if (body && typeof body === "object") {
967
+ const valid = verifyEscrowReceipt(body, signature, publicKeyBuf);
968
+ if (valid) return;
969
+ }
962
970
  } catch {
963
971
  }
964
972
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbnb",
3
- "version": "3.1.5",
3
+ "version": "4.0.0",
4
4
  "description": "P2P Agent Capability Sharing Protocol — Airbnb for AI agent pipelines",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -68,7 +68,10 @@
68
68
  "dependencies": {
69
69
  "@fastify/cors": "^11.2.0",
70
70
  "@fastify/static": "^9.0.0",
71
+ "@fastify/swagger": "^9.7.0",
72
+ "@fastify/swagger-ui": "^5.2.5",
71
73
  "@fastify/websocket": "^11.2.0",
74
+ "@modelcontextprotocol/sdk": "^1.27.1",
72
75
  "better-sqlite3": "^11.6.0",
73
76
  "bonjour-service": "^1.3.0",
74
77
  "commander": "^12.1.0",