agorai 0.5.0 → 0.6.1
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 +27 -9
- package/dist/bridge/auth.d.ts.map +1 -1
- package/dist/bridge/auth.js +4 -1
- package/dist/bridge/auth.js.map +1 -1
- package/dist/bridge/server.d.ts +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/bridge/server.js +163 -31
- package/dist/bridge/server.js.map +1 -1
- package/dist/bridge/tools.d.ts +55 -7
- package/dist/bridge/tools.d.ts.map +1 -1
- package/dist/bridge/tools.js +31 -12
- package/dist/bridge/tools.js.map +1 -1
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +16 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/store/interfaces.d.ts +14 -6
- package/dist/store/interfaces.d.ts.map +1 -1
- package/dist/store/sqlite.d.ts +17 -7
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +178 -31
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/types.d.ts +45 -10
- package/dist/store/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,12 +75,16 @@ Your PC / VPS
|
|
|
75
75
|
│ │
|
|
76
76
|
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
|
|
77
77
|
│ │ Projects │ │ Convos │ │ Shared Memory │ │
|
|
78
|
-
│ │
|
|
78
|
+
│ │ + Tasks │ │ + Whisper │ │ + Agent Memory │ │
|
|
79
79
|
│ └──────────┘ └───────────┘ └──────────────────┘ │
|
|
80
80
|
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
|
|
81
81
|
│ │ Auth │ │ Rate │ │ 4-level │ │
|
|
82
82
|
│ │ (salted) │ │ limiting │ │ visibility │ │
|
|
83
83
|
│ └──────────┘ └───────────┘ └──────────────────┘ │
|
|
84
|
+
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
|
|
85
|
+
│ │ Capabil. │ │ Skills │ │ 35 MCP tools │ │
|
|
86
|
+
│ │ catalog │ │ system │ │ + SSE push │ │
|
|
87
|
+
│ └──────────┘ └───────────┘ └──────────────────┘ │
|
|
84
88
|
│ SQLite │
|
|
85
89
|
└────────────────────┬─────────────────────────────┘
|
|
86
90
|
│ HTTP (MCP protocol)
|
|
@@ -94,7 +98,7 @@ Your PC / VPS
|
|
|
94
98
|
|
|
95
99
|
Two npm packages:
|
|
96
100
|
|
|
97
|
-
- **`agorai`** — The bridge server. Hosts projects, conversations, shared memory, auth, and
|
|
101
|
+
- **`agorai`** — The bridge server. Hosts projects, conversations, shared memory, auth, and 35 MCP tools over HTTP. SQLite storage, zero external services. Can also run internal agents in the same process via `--with-agent`.
|
|
98
102
|
- **`agorai-connect`** — Connects any agent to the bridge. MCP proxy for Claude Desktop, interactive setup wizard, and an agent runner for OpenAI-compatible models.
|
|
99
103
|
|
|
100
104
|
## Key features
|
|
@@ -122,6 +126,16 @@ npx agorai debate "Redis vs Memcached for session storage?"
|
|
|
122
126
|
|
|
123
127
|
**Task claiming** — Create tasks with required capabilities, claim them atomically (no race conditions), complete with results. Stale claims auto-release when agents go offline. Pull model — agents discover and claim work, not push.
|
|
124
128
|
|
|
129
|
+
**Directed messages (whisper)** — Send private messages to specific agents with `recipients`. Only listed agents and the sender can see the message. Store-enforced — non-recipients never know the message exists. Additive to visibility: both filters apply.
|
|
130
|
+
|
|
131
|
+
**Capability discovery** — Agents register capabilities on connect. `discover_capabilities` lets agents find each other by skill (`code-review`, `analysis`, `code-execution`). The foundation for intelligent task routing.
|
|
132
|
+
|
|
133
|
+
**Skills system** — Progressive disclosure skills replace instructions. Skills have title, summary, instructions hint, full content, and supporting files. Agents receive only metadata (tier 1) on subscribe — load full content (tier 2) and files (tier 3) on demand. Target skills to specific agents by name or by type/capability selector. Tags for filtering. ~80-90% context savings.
|
|
134
|
+
|
|
135
|
+
**Agent memory** — Private per-agent scratchpad with 3 scopes: global, per-project, and per-conversation. Each agent manages its own memory — invisible to other agents. Conversation memory auto-cleans on unsubscribe.
|
|
136
|
+
|
|
137
|
+
**Message tags** — Tag messages with metadata (`review`, `urgent`, `decision`) and filter by tags or sender in `get_messages`. Structured message types (`proposal`, `decision`) enable formal conversation protocols.
|
|
138
|
+
|
|
125
139
|
**Structured metadata** — Every message carries trusted `bridgeMetadata` (visibility, capping info, confidentiality instructions) and private `agentMetadata` (only visible to the sender). Agents can't forge bridge data.
|
|
126
140
|
|
|
127
141
|
**Security** — Salted HMAC-SHA-256 API key hashing, per-agent rate limiting, input size limits on all fields, visibility-capped writes. Everything localhost by default.
|
|
@@ -142,15 +156,16 @@ docker run -v ./agorai.config.json:/app/agorai.config.json -p 3100:3100 agorai/b
|
|
|
142
156
|
|
|
143
157
|
| Version | Focus |
|
|
144
158
|
|---------|-------|
|
|
145
|
-
| **v0.2** | **Bridge — shared workspace, visibility, auth,
|
|
159
|
+
| **v0.2** | **Bridge — shared workspace, visibility, auth, MCP tools** |
|
|
146
160
|
| v0.2.x | Security hardening, Docker, npm publish, session recovery, internal agents |
|
|
147
161
|
| **v0.3** | **SSE push notifications — real-time message delivery, 3-layer EventBus→Dispatcher→Client** |
|
|
148
|
-
| **v0.4** | **Metadata overhaul — bridgeMetadata/agentMetadata, confidentiality modes,
|
|
149
|
-
| v0.
|
|
150
|
-
| v0.
|
|
151
|
-
| v0.
|
|
152
|
-
| v0.
|
|
153
|
-
| v0.
|
|
162
|
+
| **v0.4** | **Metadata overhaul — bridgeMetadata/agentMetadata, confidentiality modes, access requests** |
|
|
163
|
+
| **v0.5** | **Discover, Decide, Deliver — 32 tools: capability catalog, task claiming, whispers, message tags, agent memory, instruction matrix, structured protocol** |
|
|
164
|
+
| **v0.6** | **Skills system — progressive disclosure (3-tier), agent targeting, skill files, replaces instruction matrix. 35 tools** |
|
|
165
|
+
| v0.7 | Task dependencies, explicit project access control, full-text search, conversation templates |
|
|
166
|
+
| v0.8 | Orchestrator agent, Sentinel AI, debate engine via bridge |
|
|
167
|
+
| v0.9 | Web dashboard, human participants, A2A protocol support |
|
|
168
|
+
| v1.0+ | Enterprise — OAuth/JWT, RBAC, audit trail, SaaS |
|
|
154
169
|
|
|
155
170
|
## Positioning
|
|
156
171
|
|
|
@@ -162,6 +177,9 @@ Agorai is **not** another agent framework. It's infrastructure — the collabora
|
|
|
162
177
|
| Protocol | MCP (open standard) | Custom | Custom | Custom |
|
|
163
178
|
| Models | Any (BYOM) | OpenAI-focused | OpenAI-focused | LangChain |
|
|
164
179
|
| Visibility | 4-level, store-enforced | None | None | None |
|
|
180
|
+
| Task claiming | Atomic, capability-based | Role assignment | None | DAG nodes |
|
|
181
|
+
| Agent memory | Private per-agent, 3 scopes | Shared only | Shared only | None |
|
|
182
|
+
| Directed messages | Whisper (recipients) | None | None | None |
|
|
165
183
|
| Debate/consensus | Built-in | None | Basic | None |
|
|
166
184
|
| Local-first | Yes | Cloud-centric | Cloud-centric | Cloud-centric |
|
|
167
185
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAC,CAAS;gBAEV,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAC,CAAS;gBAEV,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CA8BvD"}
|
package/dist/bridge/auth.js
CHANGED
|
@@ -38,7 +38,10 @@ export class ApiKeyAuthProvider {
|
|
|
38
38
|
log.warn("No bridge.salt configured — API key hashes are unsalted. Set bridge.salt in agorai.config.json for better security.");
|
|
39
39
|
}
|
|
40
40
|
for (const entry of apiKeys) {
|
|
41
|
-
const
|
|
41
|
+
const resolvedKey = entry.keyEnv ? process.env[entry.keyEnv] : entry.key;
|
|
42
|
+
if (!resolvedKey)
|
|
43
|
+
continue; // skip entries with missing env var
|
|
44
|
+
const hash = hashApiKey(resolvedKey, salt);
|
|
42
45
|
this.keyMap.set(hash, entry);
|
|
43
46
|
}
|
|
44
47
|
}
|
package/dist/bridge/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAcjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAa;IACnD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,CAA4B;IAClC,KAAK,CAAS;IACd,IAAI,CAAU;IAEtB,YAAY,OAAuB,EAAE,KAAa,EAAE,IAAa;QAC/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QAExB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAC;QAClI,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAcjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAa;IACnD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,CAA4B;IAClC,KAAK,CAAS;IACd,IAAI,CAAU;IAEtB,YAAY,OAAuB,EAAE,KAAa,EAAE,IAAa;QAC/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QAExB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAC;QAClI,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;YACzE,IAAI,CAAC,WAAW;gBAAE,SAAS,CAAC,oCAAoC;YAChE,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAExC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,yCAAyC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;YAC3C,IAAI,EAAE,SAAS,CAAC,KAAK;YACrB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,cAAc,EAAE,SAAS,CAAC,cAAc;YACxC,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAE/C,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,SAAS,EAAE,KAAK,CAAC,IAAI;YACrB,cAAc,EAAE,KAAK,CAAC,cAAc;SACrC,CAAC;IACJ,CAAC;CACF"}
|
package/dist/bridge/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bridge HTTP server — Streamable HTTP transport for the MCP bridge.
|
|
3
3
|
*
|
|
4
|
-
* Exposes
|
|
4
|
+
* Exposes 35 bridge tools + debate tools over HTTP.
|
|
5
5
|
* Auth is handled via API key in Authorization header.
|
|
6
6
|
* Each request is authenticated before being passed to the MCP handler.
|
|
7
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA2G3C,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AA+6BD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAqLD"}
|
package/dist/bridge/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bridge HTTP server — Streamable HTTP transport for the MCP bridge.
|
|
3
3
|
*
|
|
4
|
-
* Exposes
|
|
4
|
+
* Exposes 35 bridge tools + debate tools over HTTP.
|
|
5
5
|
* Auth is handled via API key in Authorization header.
|
|
6
6
|
* Each request is authenticated before being passed to the MCP handler.
|
|
7
7
|
*/
|
|
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
import { resolve, dirname } from "node:path";
|
|
17
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const PKG_VERSION = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8")).version;
|
|
19
|
-
import { RegisterAgentSchema, ListBridgeAgentsSchema, DiscoverCapabilitiesSchema, CreateProjectSchema, ListProjectsSchema, SetMemorySchema, GetMemorySchema, DeleteMemorySchema, CreateConversationSchema, ListConversationsSchema, SubscribeSchema, UnsubscribeSchema, SendMessageSchema, GetMessagesSchema, GetStatusSchema, MarkReadSchema, ListSubscribersSchema, ListAccessRequestsSchema, RespondToAccessRequestSchema, GetMyAccessRequestsSchema, CreateTaskSchema, ListTasksSchema, ClaimTaskSchema, CompleteTaskSchema, ReleaseTaskSchema, UpdateTaskSchema,
|
|
19
|
+
import { RegisterAgentSchema, ListBridgeAgentsSchema, DiscoverCapabilitiesSchema, CreateProjectSchema, ListProjectsSchema, SetMemorySchema, GetMemorySchema, DeleteMemorySchema, CreateConversationSchema, ListConversationsSchema, SubscribeSchema, UnsubscribeSchema, SendMessageSchema, GetMessagesSchema, GetStatusSchema, MarkReadSchema, ListSubscribersSchema, ListAccessRequestsSchema, RespondToAccessRequestSchema, GetMyAccessRequestsSchema, CreateTaskSchema, ListTasksSchema, ClaimTaskSchema, CompleteTaskSchema, ReleaseTaskSchema, UpdateTaskSchema, SetSkillSchema, ListSkillsSchema, GetSkillSchema, DeleteSkillSchema, SetSkillFileSchema, GetSkillFileSchema, SetAgentMemorySchema, GetAgentMemorySchema, DeleteAgentMemorySchema, } from "./tools.js";
|
|
20
20
|
const log = createLogger("bridge");
|
|
21
21
|
/** Per-session context: maps transport sessionId → agent auth. */
|
|
22
22
|
const sessionAuth = new Map();
|
|
@@ -98,6 +98,13 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
98
98
|
"If you try to subscribe to a conversation you don't have access to, an access request is created automatically.",
|
|
99
99
|
"Subscribers of that conversation can approve or deny your request via list_access_requests + respond_to_access_request.",
|
|
100
100
|
"Check your request status with get_my_access_requests.",
|
|
101
|
+
"",
|
|
102
|
+
"IMPORTANT — Skills system (progressive disclosure):",
|
|
103
|
+
"Skills provide behavioral instructions and context. They use 3-tier progressive disclosure to save context:",
|
|
104
|
+
"- Tier 1 (metadata): When you subscribe, you receive skill metadata (title, summary, instructions, tags) — NOT the full content.",
|
|
105
|
+
"- Tier 2 (content): Call get_skill(skill_id) to load the full content of a skill you need.",
|
|
106
|
+
"- Tier 3 (files): Call get_skill_file(skill_id, filename) to load supporting files attached to a skill.",
|
|
107
|
+
"Only load tier 2/3 when you actually need the detail. The summary and instructions fields give you enough to decide.",
|
|
101
108
|
].join("\n"),
|
|
102
109
|
});
|
|
103
110
|
// --- Agent tools ---
|
|
@@ -214,9 +221,20 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
214
221
|
defaultVisibility: args.default_visibility,
|
|
215
222
|
createdBy: agentId,
|
|
216
223
|
});
|
|
217
|
-
// Auto-subscribe the creator
|
|
224
|
+
// Auto-subscribe the creator and include skills metadata
|
|
218
225
|
await store.subscribe(conv.id, agentId);
|
|
219
|
-
|
|
226
|
+
const agent = await store.getAgent(agentId);
|
|
227
|
+
const matchingSkills = agent
|
|
228
|
+
? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, conv.id)
|
|
229
|
+
: [];
|
|
230
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
231
|
+
...conv,
|
|
232
|
+
skills: matchingSkills.map((s) => ({
|
|
233
|
+
id: s.id, title: s.title, summary: s.summary,
|
|
234
|
+
instructions: s.instructions, tags: s.tags,
|
|
235
|
+
scope: s.scope, agents: s.agents, files: s.files,
|
|
236
|
+
})),
|
|
237
|
+
}, null, 2) }] };
|
|
220
238
|
});
|
|
221
239
|
server.tool("list_conversations", "List conversations in a project", ListConversationsSchema.shape, async (args) => {
|
|
222
240
|
let conversations = await store.listConversations(args.project_id, agentId);
|
|
@@ -226,10 +244,23 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
226
244
|
return { content: [{ type: "text", text: JSON.stringify(conversations, null, 2) }] };
|
|
227
245
|
});
|
|
228
246
|
server.tool("subscribe", "Subscribe to a conversation. If you don't have access, an access request is created automatically — existing subscribers can approve it.", SubscribeSchema.shape, async (args) => {
|
|
229
|
-
//
|
|
247
|
+
// If already subscribed, return current skills metadata (not an error)
|
|
230
248
|
const alreadySubscribed = await store.isSubscribed(args.conversation_id, agentId);
|
|
231
249
|
if (alreadySubscribed) {
|
|
232
|
-
|
|
250
|
+
const agent = await store.getAgent(agentId);
|
|
251
|
+
const matchingSkills = agent
|
|
252
|
+
? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, args.conversation_id)
|
|
253
|
+
: [];
|
|
254
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
255
|
+
subscribed: true,
|
|
256
|
+
already_subscribed: true,
|
|
257
|
+
conversation_id: args.conversation_id,
|
|
258
|
+
skills: matchingSkills.map((s) => ({
|
|
259
|
+
id: s.id, title: s.title, summary: s.summary,
|
|
260
|
+
instructions: s.instructions, tags: s.tags,
|
|
261
|
+
scope: s.scope, agents: s.agents, files: s.files,
|
|
262
|
+
})),
|
|
263
|
+
}) }] };
|
|
233
264
|
}
|
|
234
265
|
// Verify conversation exists and caller can access its project
|
|
235
266
|
const conv = await store.getConversation(args.conversation_id);
|
|
@@ -241,18 +272,23 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
241
272
|
await store.subscribe(args.conversation_id, agentId, {
|
|
242
273
|
historyAccess: args.history_access,
|
|
243
274
|
});
|
|
244
|
-
// Include matching
|
|
275
|
+
// Include matching skill metadata in subscribe response (progressive disclosure: tier 1 only)
|
|
245
276
|
const agent = await store.getAgent(agentId);
|
|
246
|
-
const
|
|
247
|
-
? await store.
|
|
277
|
+
const matchingSkills = agent
|
|
278
|
+
? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, args.conversation_id)
|
|
248
279
|
: [];
|
|
249
280
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
250
281
|
subscribed: true,
|
|
251
282
|
conversation_id: args.conversation_id,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
283
|
+
skills: matchingSkills.map((s) => ({
|
|
284
|
+
id: s.id,
|
|
285
|
+
title: s.title,
|
|
286
|
+
summary: s.summary,
|
|
287
|
+
instructions: s.instructions,
|
|
288
|
+
tags: s.tags,
|
|
289
|
+
scope: s.scope,
|
|
290
|
+
agents: s.agents,
|
|
291
|
+
files: s.files,
|
|
256
292
|
})),
|
|
257
293
|
}) }] };
|
|
258
294
|
}
|
|
@@ -411,48 +447,71 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
411
447
|
}
|
|
412
448
|
return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
|
|
413
449
|
});
|
|
414
|
-
// ---
|
|
415
|
-
server.tool("
|
|
450
|
+
// --- Skill tools ---
|
|
451
|
+
server.tool("set_skill", "Create or update a skill in a scope. Creator can always create/edit. Listed agents (in agents[]) can edit existing skills. Use selector and agents[] to target specific agents.", SetSkillSchema.shape, async (args) => {
|
|
416
452
|
let scope = "bridge";
|
|
417
453
|
let scopeId;
|
|
418
454
|
if (args.conversation_id) {
|
|
419
455
|
scope = "conversation";
|
|
420
456
|
scopeId = args.conversation_id;
|
|
421
|
-
// Verify caller created the conversation
|
|
422
457
|
const conv = await store.getConversation(args.conversation_id);
|
|
423
|
-
if (!conv
|
|
424
|
-
return
|
|
458
|
+
if (!conv)
|
|
459
|
+
return ACCESS_DENIED;
|
|
460
|
+
// Creator can always set. Listed agents can edit existing.
|
|
461
|
+
if (conv.createdBy !== agentId) {
|
|
462
|
+
// Check if caller is a listed agent on an existing skill with this title
|
|
463
|
+
const existing = (await store.listSkills("conversation", args.conversation_id)).find((s) => s.title === args.title);
|
|
464
|
+
if (!existing) {
|
|
465
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the conversation creator can create new skills" }) }] };
|
|
466
|
+
}
|
|
467
|
+
const agent = await store.getAgent(agentId);
|
|
468
|
+
const agentName = agent?.name ?? "";
|
|
469
|
+
if (!existing.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
|
|
470
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can edit this skill" }) }] };
|
|
471
|
+
}
|
|
425
472
|
}
|
|
426
473
|
}
|
|
427
474
|
else if (args.project_id) {
|
|
428
475
|
scope = "project";
|
|
429
476
|
scopeId = args.project_id;
|
|
430
|
-
// Verify caller created the project
|
|
431
477
|
const project = await store.getProject(args.project_id, agentId);
|
|
432
|
-
if (!project
|
|
433
|
-
return
|
|
478
|
+
if (!project)
|
|
479
|
+
return ACCESS_DENIED;
|
|
480
|
+
if (project.createdBy !== agentId) {
|
|
481
|
+
const existing = (await store.listSkills("project", args.project_id)).find((s) => s.title === args.title);
|
|
482
|
+
if (!existing) {
|
|
483
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the project creator can create new skills" }) }] };
|
|
484
|
+
}
|
|
485
|
+
const agent = await store.getAgent(agentId);
|
|
486
|
+
const agentName = agent?.name ?? "";
|
|
487
|
+
if (!existing.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
|
|
488
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can edit this skill" }) }] };
|
|
489
|
+
}
|
|
434
490
|
}
|
|
435
491
|
}
|
|
436
492
|
else {
|
|
437
|
-
|
|
438
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: "Bridge-level instructions cannot be set via MCP. Provide project_id or conversation_id." }) }] };
|
|
493
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Bridge-level skills cannot be set via MCP. Provide project_id or conversation_id." }) }] };
|
|
439
494
|
}
|
|
440
|
-
const
|
|
495
|
+
const skill = await store.setSkill({
|
|
441
496
|
scope,
|
|
442
497
|
scopeId,
|
|
498
|
+
title: args.title,
|
|
499
|
+
summary: args.summary,
|
|
500
|
+
instructions: args.instructions,
|
|
443
501
|
selector: args.selector,
|
|
502
|
+
agents: args.agents,
|
|
503
|
+
tags: args.tags,
|
|
444
504
|
content: args.content,
|
|
445
505
|
createdBy: agentId,
|
|
446
506
|
});
|
|
447
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
507
|
+
return { content: [{ type: "text", text: JSON.stringify(skill, null, 2) }] };
|
|
448
508
|
});
|
|
449
|
-
server.tool("
|
|
509
|
+
server.tool("list_skills", "List skills for a scope (returns metadata only — no content). No params = bridge-level. With project_id = project-level. With conversation_id = conversation-level.", ListSkillsSchema.shape, async (args) => {
|
|
450
510
|
let scope = "bridge";
|
|
451
511
|
let scopeId;
|
|
452
512
|
if (args.conversation_id) {
|
|
453
513
|
scope = "conversation";
|
|
454
514
|
scopeId = args.conversation_id;
|
|
455
|
-
// Verify subscribed
|
|
456
515
|
const subscribed = await store.isSubscribed(args.conversation_id, agentId);
|
|
457
516
|
if (!subscribed)
|
|
458
517
|
return ACCESS_DENIED;
|
|
@@ -464,13 +523,86 @@ function createBridgeMcpServer(store, agentId) {
|
|
|
464
523
|
if (!project)
|
|
465
524
|
return ACCESS_DENIED;
|
|
466
525
|
}
|
|
467
|
-
const
|
|
468
|
-
|
|
526
|
+
const skills = await store.listSkills(scope, scopeId, { tags: args.tags });
|
|
527
|
+
// Filter by agent visibility
|
|
528
|
+
const agent = await store.getAgent(agentId);
|
|
529
|
+
const agentName = agent?.name ?? "";
|
|
530
|
+
const visible = skills.filter((s) => {
|
|
531
|
+
if (s.agents.length === 0)
|
|
532
|
+
return true;
|
|
533
|
+
return s.agents.some((a) => a.toLowerCase() === agentName.toLowerCase());
|
|
534
|
+
});
|
|
535
|
+
// Return metadata only (progressive disclosure tier 1)
|
|
536
|
+
const metadata = visible.map(({ content: _, ...rest }) => rest);
|
|
537
|
+
return { content: [{ type: "text", text: JSON.stringify(metadata, null, 2) }] };
|
|
469
538
|
});
|
|
470
|
-
server.tool("
|
|
471
|
-
const
|
|
539
|
+
server.tool("get_skill", "Get a skill's full content by ID (progressive disclosure tier 2). Returns content + file list.", GetSkillSchema.shape, async (args) => {
|
|
540
|
+
const skill = await store.getSkill(args.skill_id);
|
|
541
|
+
if (!skill)
|
|
542
|
+
return ACCESS_DENIED;
|
|
543
|
+
// Check agent visibility (agents[] + selector)
|
|
544
|
+
const agent = await store.getAgent(agentId);
|
|
545
|
+
if (agent && skill.agents.length > 0) {
|
|
546
|
+
if (!skill.agents.some((a) => a.toLowerCase() === agent.name.toLowerCase())) {
|
|
547
|
+
return ACCESS_DENIED;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (agent && skill.selector) {
|
|
551
|
+
if (skill.selector.type && skill.selector.type.toLowerCase() !== agent.type.toLowerCase())
|
|
552
|
+
return ACCESS_DENIED;
|
|
553
|
+
if (skill.selector.capability && !agent.capabilities.some((c) => c.toLowerCase() === skill.selector.capability.toLowerCase()))
|
|
554
|
+
return ACCESS_DENIED;
|
|
555
|
+
}
|
|
556
|
+
return { content: [{ type: "text", text: JSON.stringify(skill, null, 2) }] };
|
|
557
|
+
});
|
|
558
|
+
server.tool("delete_skill", "Delete a skill by ID. Creator or listed agents can delete.", DeleteSkillSchema.shape, async (args) => {
|
|
559
|
+
const skill = await store.getSkill(args.skill_id);
|
|
560
|
+
if (!skill)
|
|
561
|
+
return ACCESS_DENIED;
|
|
562
|
+
// Authorization: creator OR listed agent
|
|
563
|
+
if (skill.createdBy !== agentId) {
|
|
564
|
+
const agent = await store.getAgent(agentId);
|
|
565
|
+
const agentName = agent?.name ?? "";
|
|
566
|
+
if (!skill.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
|
|
567
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can delete this skill" }) }] };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const deleted = await store.deleteSkill(args.skill_id);
|
|
472
571
|
return { content: [{ type: "text", text: JSON.stringify({ deleted }) }] };
|
|
473
572
|
});
|
|
573
|
+
// --- Skill File tools ---
|
|
574
|
+
server.tool("set_skill_file", "Add or update a file attached to a skill. Creator or listed agents can manage files.", SetSkillFileSchema.shape, async (args) => {
|
|
575
|
+
const skill = await store.getSkill(args.skill_id);
|
|
576
|
+
if (!skill)
|
|
577
|
+
return ACCESS_DENIED;
|
|
578
|
+
// Authorization: creator OR listed agent
|
|
579
|
+
if (skill.createdBy !== agentId) {
|
|
580
|
+
const agent = await store.getAgent(agentId);
|
|
581
|
+
const agentName = agent?.name ?? "";
|
|
582
|
+
if (!skill.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
|
|
583
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can manage skill files" }) }] };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const file = await store.setSkillFile(args.skill_id, args.filename, args.content);
|
|
587
|
+
return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
|
|
588
|
+
});
|
|
589
|
+
server.tool("get_skill_file", "Get a file attached to a skill (progressive disclosure tier 3).", GetSkillFileSchema.shape, async (args) => {
|
|
590
|
+
// Check parent skill visibility first
|
|
591
|
+
const skill = await store.getSkill(args.skill_id);
|
|
592
|
+
if (!skill)
|
|
593
|
+
return ACCESS_DENIED;
|
|
594
|
+
const agent = await store.getAgent(agentId);
|
|
595
|
+
if (agent && skill.agents.length > 0) {
|
|
596
|
+
if (!skill.agents.some((a) => a.toLowerCase() === agent.name.toLowerCase())) {
|
|
597
|
+
return ACCESS_DENIED;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const file = await store.getSkillFile(args.skill_id, args.filename);
|
|
601
|
+
if (!file) {
|
|
602
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "File not found" }) }] };
|
|
603
|
+
}
|
|
604
|
+
return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
|
|
605
|
+
});
|
|
474
606
|
// --- Agent Memory tools ---
|
|
475
607
|
server.tool("set_agent_memory", "Save private memory for yourself. No scope = global. With project_id = per-project. With conversation_id = per-conversation. Content overwrites previous.", SetAgentMemorySchema.shape, async (args) => {
|
|
476
608
|
let scope = "global";
|