borgmcp 0.2.0-beta.9 → 0.4.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 +2 -2
- package/dist/auth.js +5 -5
- package/dist/auth.js.map +1 -1
- package/dist/claude.d.ts +9 -4
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +62 -26
- package/dist/claude.js.map +1 -1
- package/dist/config-utils.d.ts +32 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +159 -0
- package/dist/config-utils.js.map +1 -1
- package/dist/cubes.d.ts +51 -0
- package/dist/cubes.d.ts.map +1 -0
- package/dist/cubes.js +161 -0
- package/dist/cubes.js.map +1 -0
- package/dist/inbox.d.ts +33 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +125 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +701 -25
- package/dist/index.js.map +1 -1
- package/dist/log-audit.d.ts +27 -0
- package/dist/log-audit.d.ts.map +1 -0
- package/dist/log-audit.js +161 -0
- package/dist/log-audit.js.map +1 -0
- package/dist/postinstall.d.ts +1 -1
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +4 -4
- package/dist/postinstall.js.map +1 -1
- package/dist/regen-format.d.ts +59 -0
- package/dist/regen-format.d.ts.map +1 -0
- package/dist/regen-format.js +172 -0
- package/dist/regen-format.js.map +1 -0
- package/dist/regen.d.ts +19 -0
- package/dist/regen.d.ts.map +1 -0
- package/dist/regen.js +36 -0
- package/dist/regen.js.map +1 -0
- package/dist/remote-client.d.ts +186 -2
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +232 -11
- package/dist/remote-client.js.map +1 -1
- package/dist/setup.js +40 -33
- package/dist/setup.js.map +1 -1
- package/dist/sync.js +1 -1
- package/dist/sync.js.map +1 -1
- package/dist/templates.d.ts +33 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +96 -0
- package/dist/templates.js.map +1 -0
- package/package.json +11 -10
package/dist/index.js
CHANGED
|
@@ -6,16 +6,101 @@
|
|
|
6
6
|
* 1. Connects to Claude Code via stdio transport
|
|
7
7
|
* 2. Authenticates via Google OAuth device flow
|
|
8
8
|
* 3. Proxies MCP tools to remote server at api.borgmcp.ai
|
|
9
|
-
* 4. Provides borg:
|
|
9
|
+
* 4. Provides the borg: cube tool surface (assimilate / cube / role /
|
|
10
|
+
* roster / read-log) so Claude can act as a Drone in a hive of
|
|
11
|
+
* collaborating sessions.
|
|
10
12
|
*/
|
|
11
13
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
-
import {
|
|
15
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
+
import { assimilate, getCubeInfo, getRoleInfo, getRoster, readLog, appendLog, regen, listCubes, createCube, updateCube, deleteCube, createRole, updateRole, deleteRole, reassignDrone, getCube, checkSubscriptionStatus, createSubscription, API_URL, } from './remote-client.js';
|
|
17
|
+
import { getTemplate, listTemplateNames } from './templates.js';
|
|
18
|
+
import { getActiveCube, setActiveCube } from './cubes.js';
|
|
19
|
+
import { addSessionStartHook, addUserPromptSubmitHook } from './config-utils.js';
|
|
20
|
+
import { humanAgo, formatRegenMarkdown, getDronePlaybook, getDroneMode } from './regen-format.js';
|
|
21
|
+
import { startInboxPoller } from './inbox.js';
|
|
22
|
+
import open from 'open';
|
|
23
|
+
/**
|
|
24
|
+
* Apply a template's roles to a cube. Roles are merged by name:
|
|
25
|
+
* - existing role with same name → updateRole with the template's fields
|
|
26
|
+
* - no existing role with that name → createRole
|
|
27
|
+
* Returns counts for the caller to report.
|
|
28
|
+
*/
|
|
29
|
+
async function applyTemplateToCube(cubeId, templateRoles) {
|
|
30
|
+
const { roles: existing } = await getCube(cubeId);
|
|
31
|
+
const byName = new Map(existing.map((r) => [r.name, r]));
|
|
32
|
+
let created = 0;
|
|
33
|
+
let updated = 0;
|
|
34
|
+
for (const tr of templateRoles) {
|
|
35
|
+
const match = byName.get(tr.name);
|
|
36
|
+
if (match) {
|
|
37
|
+
await updateRole(match.id, {
|
|
38
|
+
short_description: tr.short_description,
|
|
39
|
+
detailed_description: tr.detailed_description,
|
|
40
|
+
is_default: tr.is_default === true,
|
|
41
|
+
is_coordinator: tr.is_coordinator === true,
|
|
42
|
+
});
|
|
43
|
+
updated += 1;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await createRole(cubeId, {
|
|
47
|
+
name: tr.name,
|
|
48
|
+
short_description: tr.short_description,
|
|
49
|
+
detailed_description: tr.detailed_description,
|
|
50
|
+
is_default: tr.is_default === true,
|
|
51
|
+
is_coordinator: tr.is_coordinator === true,
|
|
52
|
+
});
|
|
53
|
+
created += 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { created, updated };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Throw a friendly error if the client has not been assimilated to a cube.
|
|
60
|
+
*/
|
|
61
|
+
async function requireActiveCube() {
|
|
62
|
+
const active = await getActiveCube();
|
|
63
|
+
if (!active) {
|
|
64
|
+
throw new Error('Not assimilated to a cube. Use borg:assimilate <cube-name> first.');
|
|
65
|
+
}
|
|
66
|
+
return active;
|
|
67
|
+
}
|
|
15
68
|
/**
|
|
16
69
|
* Main entry point - MCP stdio server
|
|
17
70
|
*/
|
|
18
71
|
async function main() {
|
|
72
|
+
// Auto-register the SessionStart hook so existing users get borg-regen
|
|
73
|
+
// auto-orientation on session start without re-running borg setup. Idempotent.
|
|
74
|
+
try {
|
|
75
|
+
addSessionStartHook();
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Silent on failure — never break the MCP server because of hook registration.
|
|
79
|
+
}
|
|
80
|
+
// Auto-register the UserPromptSubmit audit hook so the drone gets a
|
|
81
|
+
// nudge if the previous assistant span used state-changing tools
|
|
82
|
+
// without calling borg:log. Domain-agnostic — knows nothing about git
|
|
83
|
+
// or any specific convention. Idempotent.
|
|
84
|
+
try {
|
|
85
|
+
addUserPromptSubmitHook();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// Silent on failure — same rationale as above.
|
|
89
|
+
}
|
|
90
|
+
// Spawn the long-poll background poller. This gives drones real-time
|
|
91
|
+
// wakeup: when another drone posts to the cube, a line gets appended
|
|
92
|
+
// to the per-cube inbox file (see inboxPathForCube in cubes.ts) and
|
|
93
|
+
// the launcher's Monitor wakes the active /loop iteration immediately.
|
|
94
|
+
// No truncation needed — the launcher's Monitor uses `tail -n 0 -F`,
|
|
95
|
+
// which starts at end-of-file and only emits new appends. Failure
|
|
96
|
+
// here is non-fatal — the launcher's fallback heartbeat still keeps
|
|
97
|
+
// things moving.
|
|
98
|
+
try {
|
|
99
|
+
startInboxPoller();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Silent — never break the MCP server because of inbox setup.
|
|
103
|
+
}
|
|
19
104
|
// Create MCP server
|
|
20
105
|
const server = new Server({
|
|
21
106
|
name: 'borg-mcp-client',
|
|
@@ -23,6 +108,7 @@ async function main() {
|
|
|
23
108
|
}, {
|
|
24
109
|
capabilities: {
|
|
25
110
|
tools: {},
|
|
111
|
+
prompts: {},
|
|
26
112
|
},
|
|
27
113
|
});
|
|
28
114
|
// Register tool listing
|
|
@@ -31,7 +117,25 @@ async function main() {
|
|
|
31
117
|
tools: [
|
|
32
118
|
{
|
|
33
119
|
name: 'subscribe',
|
|
34
|
-
description: 'Create Stripe checkout session ($
|
|
120
|
+
description: 'Create Stripe checkout session ($1/month, 7-day trial)',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {},
|
|
124
|
+
required: [],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'subscription_status',
|
|
129
|
+
description: 'Check subscription status',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {},
|
|
133
|
+
required: [],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'open_dashboard',
|
|
138
|
+
description: 'Open Borg MCP dashboard in browser to manage cubes, roles, and drones',
|
|
35
139
|
inputSchema: {
|
|
36
140
|
type: 'object',
|
|
37
141
|
properties: {},
|
|
@@ -40,30 +144,232 @@ async function main() {
|
|
|
40
144
|
},
|
|
41
145
|
{
|
|
42
146
|
name: 'borg:regen',
|
|
43
|
-
description:
|
|
147
|
+
description: "Refresh your context as a Drone. Returns the active cube's ground rules, " +
|
|
148
|
+
"your role's detailed playbook, the drone roster, and recent activity log entries — " +
|
|
149
|
+
'everything you need to be oriented. Call on session start, and again before each new ' +
|
|
150
|
+
'task to stay in sync with the cube. Returns "not connected" if no active cube; use ' +
|
|
151
|
+
'borg:assimilate first in that case.',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {},
|
|
155
|
+
required: [],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'borg:assimilate',
|
|
160
|
+
description: "Connect this Claude session as a Drone to a Cube. Provide the cube's name. " +
|
|
161
|
+
"Returns the cube's ground rules, your assigned role's detailed instructions, " +
|
|
162
|
+
'and persists a session token locally so subsequent borg: tools work for this cube.',
|
|
44
163
|
inputSchema: {
|
|
45
164
|
type: 'object',
|
|
46
165
|
properties: {
|
|
47
|
-
|
|
48
|
-
type: '
|
|
49
|
-
|
|
50
|
-
type: 'string',
|
|
51
|
-
},
|
|
52
|
-
description: 'Optional: Filter by specific categories (e.g., ["code-style", "commit-rules"])',
|
|
166
|
+
cube_name: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
description: 'The cube to connect to',
|
|
53
169
|
},
|
|
54
170
|
},
|
|
171
|
+
required: ['cube_name'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'borg:cube',
|
|
176
|
+
description: "Read the active Cube's ground rules and the registry of all roles in it " +
|
|
177
|
+
"(each role's name + short description). Use to remind yourself of cube-wide context.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {},
|
|
55
181
|
required: [],
|
|
56
182
|
},
|
|
57
183
|
},
|
|
58
184
|
{
|
|
59
|
-
name: '
|
|
60
|
-
description: '
|
|
185
|
+
name: 'borg:role',
|
|
186
|
+
description: "Read your assigned role's detailed description (your playbook). " +
|
|
187
|
+
'Other drones cannot see this — only you (drones in this role).',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {},
|
|
191
|
+
required: [],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'borg:roster',
|
|
196
|
+
description: "List all currently connected drones in your cube, with each drone's label, role, and last-seen time.",
|
|
61
197
|
inputSchema: {
|
|
62
198
|
type: 'object',
|
|
63
199
|
properties: {},
|
|
64
200
|
required: [],
|
|
65
201
|
},
|
|
66
202
|
},
|
|
203
|
+
{
|
|
204
|
+
name: 'borg:read-log',
|
|
205
|
+
description: "Read recent entries from the cube's shared activity log. Each entry is tagged " +
|
|
206
|
+
"with the drone that wrote it and that drone's role. Optional: since (ISO-8601 " +
|
|
207
|
+
'timestamp, returns entries strictly after) and limit (1–500, default 50).',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
since: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'ISO-8601 timestamp',
|
|
214
|
+
},
|
|
215
|
+
limit: {
|
|
216
|
+
type: 'number',
|
|
217
|
+
description: 'max entries to return (1-500)',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'borg:log',
|
|
224
|
+
description: 'Append a message to the cube\'s shared activity log. All connected drones will see your entry, tagged with your drone label and role name. Use this to coordinate work, post review-ready signals, share findings, or anything else the cube\'s ground rules and your role description direct you to communicate.',
|
|
225
|
+
inputSchema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {
|
|
228
|
+
message: { type: 'string', description: 'The log message (max 10KB).' },
|
|
229
|
+
},
|
|
230
|
+
required: ['message'],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'borg:list-cubes',
|
|
235
|
+
description: 'List every cube owned by this user. Returns id, name, ground_rules, and timestamps for each. Useful before assimilate to see what\'s available, or as a starting point for any management action.',
|
|
236
|
+
inputSchema: { type: 'object', properties: {} },
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'borg:create-cube',
|
|
240
|
+
description: 'Create a new cube. The server seeds a default "Drone" role atomically so the cube is assimilatable immediately. ' +
|
|
241
|
+
'Pass an optional `template` name to apply a richer role set instead (see borg:list-templates / borg:apply-template).',
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: {
|
|
245
|
+
name: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
description: 'Cube name (lowercase letters, digits, hyphens; max 64 chars).',
|
|
248
|
+
pattern: '^[a-z0-9-]+$',
|
|
249
|
+
maxLength: 64,
|
|
250
|
+
},
|
|
251
|
+
ground_rules: { type: 'string', description: 'Markdown text every drone in this cube will see in regen. Anything project-specific.' },
|
|
252
|
+
template: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Optional template name to apply after cube creation (e.g. "software-dev"). Roles are merged by name; the default Drone role gets overwritten by the template if a same-named role is in the template.',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
required: ['name', 'ground_rules'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'borg:update-cube',
|
|
262
|
+
description: 'Update a cube\'s name and/or ground_rules. Pass only what changes.',
|
|
263
|
+
inputSchema: {
|
|
264
|
+
type: 'object',
|
|
265
|
+
properties: {
|
|
266
|
+
cube_id: { type: 'string', description: 'UUID of the cube to update.' },
|
|
267
|
+
name: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
description: 'New name (optional). Lowercase letters, digits, hyphens; max 64 chars.',
|
|
270
|
+
pattern: '^[a-z0-9-]+$',
|
|
271
|
+
maxLength: 64,
|
|
272
|
+
},
|
|
273
|
+
ground_rules: { type: 'string', description: 'New ground rules markdown (optional).' },
|
|
274
|
+
},
|
|
275
|
+
required: ['cube_id'],
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'borg:delete-cube',
|
|
280
|
+
description: 'Delete a cube and all its roles, drones, and log entries. Irreversible — confirm with the user before invoking unless the cube is clearly disposable.',
|
|
281
|
+
inputSchema: {
|
|
282
|
+
type: 'object',
|
|
283
|
+
properties: {
|
|
284
|
+
cube_id: { type: 'string', description: 'UUID of the cube to delete.' },
|
|
285
|
+
},
|
|
286
|
+
required: ['cube_id'],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: 'borg:create-role',
|
|
291
|
+
description: 'Create a role inside a cube. The detailed_description is the role\'s playbook — only drones assigned to this role see it. Setting is_default=true demotes any existing default; a cube has exactly one default role at a time.',
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: 'object',
|
|
294
|
+
properties: {
|
|
295
|
+
cube_id: { type: 'string', description: 'UUID of the cube this role belongs to.' },
|
|
296
|
+
name: { type: 'string', description: 'Role name (e.g. "Builder", "Reviewer").' },
|
|
297
|
+
short_description: { type: 'string', description: 'One-line summary, shown to every drone in the cube.' },
|
|
298
|
+
detailed_description: { type: 'string', description: 'Full playbook for drones in this role — workflow, conventions, log signals to post.' },
|
|
299
|
+
is_default: { type: 'boolean', description: 'If true, new drones assimilating into this cube are assigned this role (when the Coordinator seat is taken or there is no Coordinator role). Demotes the previous default.' },
|
|
300
|
+
is_coordinator: { type: 'boolean', description: 'If true, this role becomes the cube\'s Coordinator (Queen seat, supervised mode). At most one Coordinator role per cube — promoting demotes the previous one.' },
|
|
301
|
+
},
|
|
302
|
+
required: ['cube_id', 'name', 'short_description', 'detailed_description'],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'borg:update-role',
|
|
307
|
+
description: 'Update a role. Pass only the fields that change. Promoting to is_default demotes the previous default in the same cube.',
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: 'object',
|
|
310
|
+
properties: {
|
|
311
|
+
role_id: { type: 'string', description: 'UUID of the role to update.' },
|
|
312
|
+
name: { type: 'string', description: 'New role name (optional).' },
|
|
313
|
+
short_description: { type: 'string', description: 'New short description (optional).' },
|
|
314
|
+
detailed_description: { type: 'string', description: 'New detailed playbook (optional).' },
|
|
315
|
+
is_default: { type: 'boolean', description: 'Set true to make this the cube\'s default role (optional).' },
|
|
316
|
+
is_coordinator: { type: 'boolean', description: 'Set true to promote this role to Coordinator (singleton per cube; demotes the previous one). Setting false on the current Coordinator role demotes it.' },
|
|
317
|
+
},
|
|
318
|
+
required: ['role_id'],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: 'borg:delete-role',
|
|
323
|
+
description: 'Delete a role. Refuses if any drone is still assigned — reassign or evict those drones from the dashboard first.',
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: 'object',
|
|
326
|
+
properties: {
|
|
327
|
+
role_id: { type: 'string', description: 'UUID of the role to delete.' },
|
|
328
|
+
},
|
|
329
|
+
required: ['role_id'],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'borg:reassign-drone',
|
|
334
|
+
description: 'Reassign a drone to a different role in the same cube. Coordinator-shaped: the cube\'s Coordinator drone is the one expected to call this when dispatching new drones to specific work. ' +
|
|
335
|
+
'Server refuses if you try to assign to the Coordinator role when another drone already holds it (evict or reassign that drone first).',
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
properties: {
|
|
339
|
+
drone_id: { type: 'string', description: 'UUID of the drone to reassign.' },
|
|
340
|
+
role_id: { type: 'string', description: 'UUID of the target role. Must belong to the same cube as the drone.' },
|
|
341
|
+
},
|
|
342
|
+
required: ['drone_id', 'role_id'],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'borg:list-drones',
|
|
347
|
+
description: 'List every drone in a cube (owner-scoped). Returns id, label, role_id, last_seen for each — gives the Coordinator a roster they can act on with borg:reassign-drone.',
|
|
348
|
+
inputSchema: {
|
|
349
|
+
type: 'object',
|
|
350
|
+
properties: {
|
|
351
|
+
cube_id: { type: 'string', description: 'UUID of the cube whose drones to list.' },
|
|
352
|
+
},
|
|
353
|
+
required: ['cube_id'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'borg:list-templates',
|
|
358
|
+
description: 'List available cube templates that can be applied via borg:apply-template or passed to borg:create-cube.',
|
|
359
|
+
inputSchema: { type: 'object', properties: {} },
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'borg:apply-template',
|
|
363
|
+
description: 'Apply a named template to an existing cube. Roles are merged by name: existing roles with the same name get their fields overwritten; new ones are created. Use this to retrofit an existing cube with a richer role set (e.g. add Coordinator/Reviewer/UX Expert to a cube that started bare).',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
cube_id: { type: 'string', description: 'UUID of the cube to apply the template to.' },
|
|
368
|
+
template_name: { type: 'string', description: 'Template to apply (see borg:list-templates).' },
|
|
369
|
+
},
|
|
370
|
+
required: ['cube_id', 'template_name'],
|
|
371
|
+
},
|
|
372
|
+
},
|
|
67
373
|
],
|
|
68
374
|
};
|
|
69
375
|
});
|
|
@@ -72,6 +378,22 @@ async function main() {
|
|
|
72
378
|
const { name, arguments: args } = request.params;
|
|
73
379
|
try {
|
|
74
380
|
switch (name) {
|
|
381
|
+
case 'borg:regen': {
|
|
382
|
+
const active = await getActiveCube();
|
|
383
|
+
if (!active) {
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: 'text',
|
|
388
|
+
text: 'Not connected to a cube. Use `borg:assimilate cube_name="<name>"` to join one.',
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const result = await regen(active.sessionToken, active.apiUrl);
|
|
394
|
+
const mode = getDroneMode(result.role?.is_coordinator === true);
|
|
395
|
+
return { content: [{ type: 'text', text: formatRegenMarkdown(result, mode) }] };
|
|
396
|
+
}
|
|
75
397
|
case 'subscribe': {
|
|
76
398
|
const checkoutUrl = await createSubscription();
|
|
77
399
|
return {
|
|
@@ -83,29 +405,334 @@ async function main() {
|
|
|
83
405
|
],
|
|
84
406
|
};
|
|
85
407
|
}
|
|
86
|
-
case '
|
|
87
|
-
const
|
|
88
|
-
const context = await regenerateContext(categories);
|
|
408
|
+
case 'subscription_status': {
|
|
409
|
+
const status = await checkSubscriptionStatus();
|
|
89
410
|
return {
|
|
90
411
|
content: [
|
|
91
412
|
{
|
|
92
413
|
type: 'text',
|
|
93
|
-
text:
|
|
414
|
+
text: JSON.stringify(status, null, 2),
|
|
94
415
|
},
|
|
95
416
|
],
|
|
96
417
|
};
|
|
97
418
|
}
|
|
98
|
-
case '
|
|
99
|
-
const
|
|
419
|
+
case 'open_dashboard': {
|
|
420
|
+
const dashboardUrl = 'https://borgmcp.ai/dashboard';
|
|
421
|
+
await open(dashboardUrl);
|
|
100
422
|
return {
|
|
101
423
|
content: [
|
|
102
424
|
{
|
|
103
425
|
type: 'text',
|
|
104
|
-
text:
|
|
426
|
+
text: `◼ Opened dashboard in browser: ${dashboardUrl}`,
|
|
105
427
|
},
|
|
106
428
|
],
|
|
107
429
|
};
|
|
108
430
|
}
|
|
431
|
+
case 'borg:assimilate': {
|
|
432
|
+
const cubeName = args?.cube_name;
|
|
433
|
+
if (!cubeName)
|
|
434
|
+
throw new Error('cube_name is required');
|
|
435
|
+
// First-call assimilate uses the env-or-prod default; we then
|
|
436
|
+
// persist that same URL so all subsequent drone calls hit the
|
|
437
|
+
// worker that issued the session token.
|
|
438
|
+
const apiUrl = API_URL;
|
|
439
|
+
const result = await assimilate(cubeName, apiUrl);
|
|
440
|
+
await setActiveCube({
|
|
441
|
+
cubeId: result.cube.id,
|
|
442
|
+
droneId: result.drone.id,
|
|
443
|
+
name: result.cube.name,
|
|
444
|
+
sessionToken: result.sessionToken,
|
|
445
|
+
droneLabel: result.drone.label,
|
|
446
|
+
apiUrl,
|
|
447
|
+
});
|
|
448
|
+
const text = [
|
|
449
|
+
`# Assimilated to cube: ${result.cube.name}`,
|
|
450
|
+
``,
|
|
451
|
+
`**Drone label:** ${result.drone.label}`,
|
|
452
|
+
`**Assigned role:** ${result.role.name}`,
|
|
453
|
+
`**Mode:** ${getDroneMode(result.role.is_coordinator === true)}`,
|
|
454
|
+
``,
|
|
455
|
+
`## Ground rules`,
|
|
456
|
+
result.cube.ground_rules || '_(none)_',
|
|
457
|
+
``,
|
|
458
|
+
`## Your role: ${result.role.name}`,
|
|
459
|
+
result.role.detailed_description || '_(no detailed description set)_',
|
|
460
|
+
``,
|
|
461
|
+
getDronePlaybook(getDroneMode(result.role.is_coordinator === true)),
|
|
462
|
+
].join('\n');
|
|
463
|
+
return { content: [{ type: 'text', text }] };
|
|
464
|
+
}
|
|
465
|
+
case 'borg:cube': {
|
|
466
|
+
const active = await requireActiveCube();
|
|
467
|
+
const [{ cube, roles }, { role: ownRole }] = await Promise.all([
|
|
468
|
+
getCubeInfo(active.sessionToken, active.apiUrl),
|
|
469
|
+
getRoleInfo(active.sessionToken, active.apiUrl),
|
|
470
|
+
]);
|
|
471
|
+
// Mode is derived from this drone's own current role —
|
|
472
|
+
// Coordinator → supervised, anything else → autonomous.
|
|
473
|
+
const mode = getDroneMode(ownRole?.is_coordinator === true);
|
|
474
|
+
const lines = [];
|
|
475
|
+
lines.push(`# Cube: ${cube.name}`);
|
|
476
|
+
lines.push(`**Mode:** ${mode}`);
|
|
477
|
+
lines.push('');
|
|
478
|
+
lines.push('## Ground rules');
|
|
479
|
+
lines.push(cube.ground_rules || '_(none)_');
|
|
480
|
+
lines.push('');
|
|
481
|
+
lines.push('## Roles in this cube');
|
|
482
|
+
if (!roles.length) {
|
|
483
|
+
lines.push('_(no roles defined)_');
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
for (const r of roles) {
|
|
487
|
+
const tags = [
|
|
488
|
+
r.is_coordinator ? 'Coordinator' : null,
|
|
489
|
+
r.is_default ? 'default' : null,
|
|
490
|
+
].filter(Boolean).join(', ');
|
|
491
|
+
const marker = tags ? ` (${tags})` : '';
|
|
492
|
+
const desc = r.short_description || '_(no description)_';
|
|
493
|
+
lines.push(`- **${r.name}**${marker} — ${desc}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
lines.push('');
|
|
497
|
+
lines.push(getDronePlaybook(mode));
|
|
498
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
499
|
+
}
|
|
500
|
+
case 'borg:role': {
|
|
501
|
+
const active = await requireActiveCube();
|
|
502
|
+
const { role } = await getRoleInfo(active.sessionToken, active.apiUrl);
|
|
503
|
+
const text = [
|
|
504
|
+
`# Your role: ${role.name}`,
|
|
505
|
+
``,
|
|
506
|
+
role.detailed_description || '_(no detailed description set)_',
|
|
507
|
+
].join('\n');
|
|
508
|
+
return { content: [{ type: 'text', text }] };
|
|
509
|
+
}
|
|
510
|
+
case 'borg:roster': {
|
|
511
|
+
const active = await requireActiveCube();
|
|
512
|
+
const { drones, roles } = await getRoster(active.sessionToken, active.apiUrl);
|
|
513
|
+
const roleById = new Map();
|
|
514
|
+
for (const r of roles)
|
|
515
|
+
roleById.set(r.id, r);
|
|
516
|
+
const lines = [];
|
|
517
|
+
lines.push(`# Drones in cube: ${active.name}`);
|
|
518
|
+
lines.push('');
|
|
519
|
+
if (!drones.length) {
|
|
520
|
+
lines.push('_(no drones connected)_');
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
for (const d of drones) {
|
|
524
|
+
const role = roleById.get(d.role_id);
|
|
525
|
+
const roleName = role?.name ?? 'unknown';
|
|
526
|
+
lines.push(`- **${d.label}** (${roleName}) — last seen ${humanAgo(d.last_seen)}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
530
|
+
}
|
|
531
|
+
case 'borg:read-log': {
|
|
532
|
+
const active = await requireActiveCube();
|
|
533
|
+
const since = typeof args?.since === 'string' ? args.since : undefined;
|
|
534
|
+
const limit = typeof args?.limit === 'number' ? args.limit : undefined;
|
|
535
|
+
const { entries, drones, roles } = await readLog(active.sessionToken, active.apiUrl, {
|
|
536
|
+
since,
|
|
537
|
+
limit,
|
|
538
|
+
});
|
|
539
|
+
const droneById = new Map();
|
|
540
|
+
for (const d of drones)
|
|
541
|
+
droneById.set(d.id, d);
|
|
542
|
+
const roleById = new Map();
|
|
543
|
+
for (const r of roles)
|
|
544
|
+
roleById.set(r.id, r);
|
|
545
|
+
const lines = [];
|
|
546
|
+
lines.push(`# Activity log: ${active.name}`);
|
|
547
|
+
lines.push('');
|
|
548
|
+
if (!entries.length) {
|
|
549
|
+
lines.push('_(no entries)_');
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
for (const e of entries) {
|
|
553
|
+
const drone = droneById.get(e.drone_id);
|
|
554
|
+
const droneLabel = drone?.label ?? 'unknown-drone';
|
|
555
|
+
const roleName = drone ? roleById.get(drone.role_id)?.name ?? 'unknown' : 'unknown';
|
|
556
|
+
const ts = typeof e.created_at === 'string'
|
|
557
|
+
? e.created_at
|
|
558
|
+
: new Date(e.created_at).toISOString();
|
|
559
|
+
lines.push(`**[${ts}]** ${droneLabel} (${roleName}): ${e.message}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
563
|
+
}
|
|
564
|
+
case 'borg:log': {
|
|
565
|
+
const message = args?.message;
|
|
566
|
+
if (!message || typeof message !== 'string')
|
|
567
|
+
throw new Error('message is required');
|
|
568
|
+
const active = await getActiveCube();
|
|
569
|
+
if (!active)
|
|
570
|
+
throw new Error('Not assimilated to a cube. Use borg:assimilate <cube-name> first.');
|
|
571
|
+
const result = await appendLog(active.sessionToken, active.apiUrl, message);
|
|
572
|
+
const text = `Logged to cube "${active.name}" as ${active.droneLabel}. (entry id: ${result.entry.id})`;
|
|
573
|
+
return { content: [{ type: 'text', text }] };
|
|
574
|
+
}
|
|
575
|
+
case 'borg:list-cubes': {
|
|
576
|
+
const { cubes } = await listCubes();
|
|
577
|
+
if (!cubes.length) {
|
|
578
|
+
return { content: [{ type: 'text', text: 'No cubes yet. Use borg:create-cube to make your first one.' }] };
|
|
579
|
+
}
|
|
580
|
+
const lines = cubes.map((c) => `- **${c.name}** (id: ${c.id})\n ${(c.ground_rules || '_(no ground rules)_').split('\n')[0].slice(0, 120)}`);
|
|
581
|
+
return { content: [{ type: 'text', text: `Your cubes (${cubes.length}):\n\n${lines.join('\n\n')}` }] };
|
|
582
|
+
}
|
|
583
|
+
case 'borg:create-cube': {
|
|
584
|
+
const name = args?.name;
|
|
585
|
+
const groundRules = args?.ground_rules;
|
|
586
|
+
const templateName = args?.template;
|
|
587
|
+
if (!name)
|
|
588
|
+
throw new Error('name is required');
|
|
589
|
+
if (groundRules === undefined)
|
|
590
|
+
throw new Error('ground_rules is required (pass empty string if none)');
|
|
591
|
+
const { cube } = await createCube(name, groundRules);
|
|
592
|
+
// Apply template if requested. Merges by name: any role the
|
|
593
|
+
// server auto-seeded (e.g. "Drone") that the template doesn't
|
|
594
|
+
// also include stays put; templated roles upsert.
|
|
595
|
+
if (templateName) {
|
|
596
|
+
const template = getTemplate(templateName);
|
|
597
|
+
if (!template) {
|
|
598
|
+
throw new Error(`Unknown template "${templateName}". Available: ${listTemplateNames().join(', ')}`);
|
|
599
|
+
}
|
|
600
|
+
const summary = await applyTemplateToCube(cube.id, template.roles);
|
|
601
|
+
const text = `Created cube **${cube.name}** (id: ${cube.id}) with template **${templateName}** applied — ${summary.created} role(s) created, ${summary.updated} updated. Use borg:assimilate ${cube.name} to join as a drone.`;
|
|
602
|
+
return { content: [{ type: 'text', text }] };
|
|
603
|
+
}
|
|
604
|
+
const text = `Created cube **${cube.name}** (id: ${cube.id}). A default "Drone" role was seeded — rename or replace it via borg:update-role / borg:create-role / borg:delete-role. Use borg:assimilate ${cube.name} to join as a drone.`;
|
|
605
|
+
return { content: [{ type: 'text', text }] };
|
|
606
|
+
}
|
|
607
|
+
case 'borg:update-cube': {
|
|
608
|
+
const cubeId = args?.cube_id;
|
|
609
|
+
if (!cubeId)
|
|
610
|
+
throw new Error('cube_id is required');
|
|
611
|
+
const updates = {};
|
|
612
|
+
if (typeof args?.name === 'string')
|
|
613
|
+
updates.name = args.name;
|
|
614
|
+
if (typeof args?.ground_rules === 'string')
|
|
615
|
+
updates.ground_rules = args.ground_rules;
|
|
616
|
+
if (Object.keys(updates).length === 0)
|
|
617
|
+
throw new Error('Pass at least one of: name, ground_rules.');
|
|
618
|
+
const { cube } = await updateCube(cubeId, updates);
|
|
619
|
+
return { content: [{ type: 'text', text: `Updated cube **${cube.name}** (id: ${cube.id}).` }] };
|
|
620
|
+
}
|
|
621
|
+
case 'borg:delete-cube': {
|
|
622
|
+
const cubeId = args?.cube_id;
|
|
623
|
+
if (!cubeId)
|
|
624
|
+
throw new Error('cube_id is required');
|
|
625
|
+
await deleteCube(cubeId);
|
|
626
|
+
return { content: [{ type: 'text', text: `Deleted cube ${cubeId} (and all its roles, drones, log entries).` }] };
|
|
627
|
+
}
|
|
628
|
+
case 'borg:create-role': {
|
|
629
|
+
const cubeId = args?.cube_id;
|
|
630
|
+
const name = args?.name;
|
|
631
|
+
const shortDesc = args?.short_description;
|
|
632
|
+
const detailedDesc = args?.detailed_description;
|
|
633
|
+
if (!cubeId)
|
|
634
|
+
throw new Error('cube_id is required');
|
|
635
|
+
if (!name)
|
|
636
|
+
throw new Error('name is required');
|
|
637
|
+
if (shortDesc === undefined)
|
|
638
|
+
throw new Error('short_description is required (pass empty string if none)');
|
|
639
|
+
if (detailedDesc === undefined)
|
|
640
|
+
throw new Error('detailed_description is required (pass empty string if none)');
|
|
641
|
+
const isDefault = args?.is_default === true;
|
|
642
|
+
const isCoordinator = args?.is_coordinator === true;
|
|
643
|
+
const { role } = await createRole(cubeId, {
|
|
644
|
+
name,
|
|
645
|
+
short_description: shortDesc,
|
|
646
|
+
detailed_description: detailedDesc,
|
|
647
|
+
is_default: isDefault,
|
|
648
|
+
is_coordinator: isCoordinator,
|
|
649
|
+
});
|
|
650
|
+
const tags = [
|
|
651
|
+
role.is_coordinator ? 'Coordinator' : null,
|
|
652
|
+
role.is_default ? 'default' : null,
|
|
653
|
+
].filter(Boolean).join(', ');
|
|
654
|
+
const tag = tags ? ` (${tags})` : '';
|
|
655
|
+
return { content: [{ type: 'text', text: `Created role **${role.name}**${tag} (id: ${role.id}) in cube ${cubeId}.` }] };
|
|
656
|
+
}
|
|
657
|
+
case 'borg:update-role': {
|
|
658
|
+
const roleId = args?.role_id;
|
|
659
|
+
if (!roleId)
|
|
660
|
+
throw new Error('role_id is required');
|
|
661
|
+
const updates = {};
|
|
662
|
+
if (typeof args?.name === 'string')
|
|
663
|
+
updates.name = args.name;
|
|
664
|
+
if (typeof args?.short_description === 'string')
|
|
665
|
+
updates.short_description = args.short_description;
|
|
666
|
+
if (typeof args?.detailed_description === 'string')
|
|
667
|
+
updates.detailed_description = args.detailed_description;
|
|
668
|
+
if (typeof args?.is_default === 'boolean')
|
|
669
|
+
updates.is_default = args.is_default;
|
|
670
|
+
if (typeof args?.is_coordinator === 'boolean')
|
|
671
|
+
updates.is_coordinator = args.is_coordinator;
|
|
672
|
+
if (Object.keys(updates).length === 0)
|
|
673
|
+
throw new Error('Pass at least one of: name, short_description, detailed_description, is_default, is_coordinator.');
|
|
674
|
+
const { role } = await updateRole(roleId, updates);
|
|
675
|
+
const tags = [
|
|
676
|
+
role.is_coordinator ? 'Coordinator' : null,
|
|
677
|
+
role.is_default ? 'default' : null,
|
|
678
|
+
].filter(Boolean).join(', ');
|
|
679
|
+
const tag = tags ? ` (${tags})` : '';
|
|
680
|
+
return { content: [{ type: 'text', text: `Updated role **${role.name}**${tag} (id: ${role.id}).` }] };
|
|
681
|
+
}
|
|
682
|
+
case 'borg:delete-role': {
|
|
683
|
+
const roleId = args?.role_id;
|
|
684
|
+
if (!roleId)
|
|
685
|
+
throw new Error('role_id is required');
|
|
686
|
+
await deleteRole(roleId);
|
|
687
|
+
return { content: [{ type: 'text', text: `Deleted role ${roleId}.` }] };
|
|
688
|
+
}
|
|
689
|
+
case 'borg:reassign-drone': {
|
|
690
|
+
const droneId = args?.drone_id;
|
|
691
|
+
const roleId = args?.role_id;
|
|
692
|
+
if (!droneId)
|
|
693
|
+
throw new Error('drone_id is required');
|
|
694
|
+
if (!roleId)
|
|
695
|
+
throw new Error('role_id is required');
|
|
696
|
+
const { drone } = await reassignDrone(droneId, roleId);
|
|
697
|
+
return { content: [{ type: 'text', text: `Reassigned drone ${drone.label} (${drone.id}) to role ${drone.role_id}.` }] };
|
|
698
|
+
}
|
|
699
|
+
case 'borg:list-drones': {
|
|
700
|
+
const cubeId = args?.cube_id;
|
|
701
|
+
if (!cubeId)
|
|
702
|
+
throw new Error('cube_id is required');
|
|
703
|
+
const { drones, roles } = await getCube(cubeId);
|
|
704
|
+
if (!drones.length) {
|
|
705
|
+
return { content: [{ type: 'text', text: 'No drones in this cube yet.' }] };
|
|
706
|
+
}
|
|
707
|
+
const rolesById = new Map(roles.map((r) => [r.id, r]));
|
|
708
|
+
const lines = drones.map((d) => {
|
|
709
|
+
const r = rolesById.get(d.role_id);
|
|
710
|
+
return `- **${d.label}** (id: ${d.id}) — role: ${r?.name ?? '?'} (${d.role_id}) — last seen ${d.last_seen}`;
|
|
711
|
+
});
|
|
712
|
+
return { content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n${lines.join('\n')}` }] };
|
|
713
|
+
}
|
|
714
|
+
case 'borg:list-templates': {
|
|
715
|
+
const names = listTemplateNames();
|
|
716
|
+
const lines = names.map((n) => {
|
|
717
|
+
const t = getTemplate(n);
|
|
718
|
+
return `- **${n}**: ${t.description}`;
|
|
719
|
+
});
|
|
720
|
+
return { content: [{ type: 'text', text: `Available templates:\n\n${lines.join('\n')}` }] };
|
|
721
|
+
}
|
|
722
|
+
case 'borg:apply-template': {
|
|
723
|
+
const cubeId = args?.cube_id;
|
|
724
|
+
const templateName = args?.template_name;
|
|
725
|
+
if (!cubeId)
|
|
726
|
+
throw new Error('cube_id is required');
|
|
727
|
+
if (!templateName)
|
|
728
|
+
throw new Error('template_name is required');
|
|
729
|
+
const template = getTemplate(templateName);
|
|
730
|
+
if (!template) {
|
|
731
|
+
throw new Error(`Unknown template "${templateName}". Available: ${listTemplateNames().join(', ')}`);
|
|
732
|
+
}
|
|
733
|
+
const summary = await applyTemplateToCube(cubeId, template.roles);
|
|
734
|
+
return { content: [{ type: 'text', text: `Applied template **${templateName}** to cube ${cubeId} — ${summary.created} role(s) created, ${summary.updated} updated.` }] };
|
|
735
|
+
}
|
|
109
736
|
default:
|
|
110
737
|
throw new Error(`Unknown tool: ${name}`);
|
|
111
738
|
}
|
|
@@ -113,12 +740,13 @@ async function main() {
|
|
|
113
740
|
catch (error) {
|
|
114
741
|
// Better error messages for auth/subscription issues
|
|
115
742
|
if (error.message?.includes('Authentication required') ||
|
|
743
|
+
error.message?.includes('Authentication expired') ||
|
|
116
744
|
error.message?.includes('Failed to refresh')) {
|
|
117
745
|
return {
|
|
118
746
|
content: [
|
|
119
747
|
{
|
|
120
748
|
type: 'text',
|
|
121
|
-
text: '
|
|
749
|
+
text: '◼ Authentication expired. Run: borg setup',
|
|
122
750
|
},
|
|
123
751
|
],
|
|
124
752
|
isError: true,
|
|
@@ -135,15 +763,63 @@ async function main() {
|
|
|
135
763
|
};
|
|
136
764
|
}
|
|
137
765
|
});
|
|
766
|
+
// Register prompts listing
|
|
767
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
768
|
+
return {
|
|
769
|
+
prompts: [
|
|
770
|
+
{
|
|
771
|
+
name: 'subscribe',
|
|
772
|
+
description: 'Set up Borg MCP subscription ($1/month, 7-day trial)',
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: 'dashboard',
|
|
776
|
+
description: 'Open Borg MCP dashboard to manage cubes',
|
|
777
|
+
},
|
|
778
|
+
],
|
|
779
|
+
};
|
|
780
|
+
});
|
|
781
|
+
// Register prompt getter
|
|
782
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
783
|
+
const { name } = request.params;
|
|
784
|
+
switch (name) {
|
|
785
|
+
case 'subscribe':
|
|
786
|
+
return {
|
|
787
|
+
description: 'Set up Borg MCP subscription ($1/month, 7-day trial)',
|
|
788
|
+
messages: [
|
|
789
|
+
{
|
|
790
|
+
role: 'user',
|
|
791
|
+
content: {
|
|
792
|
+
type: 'text',
|
|
793
|
+
text: 'Please help me set up a Borg MCP subscription using the subscribe tool.',
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
};
|
|
798
|
+
case 'dashboard':
|
|
799
|
+
return {
|
|
800
|
+
description: 'Open Borg MCP dashboard to manage cubes',
|
|
801
|
+
messages: [
|
|
802
|
+
{
|
|
803
|
+
role: 'user',
|
|
804
|
+
content: {
|
|
805
|
+
type: 'text',
|
|
806
|
+
text: 'Please open the Borg MCP dashboard using the open_dashboard tool.',
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
],
|
|
810
|
+
};
|
|
811
|
+
default:
|
|
812
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
138
815
|
// Create stdio transport
|
|
139
816
|
const transport = new StdioServerTransport();
|
|
140
817
|
// Connect server to transport
|
|
141
818
|
await server.connect(transport);
|
|
142
|
-
console.error('
|
|
143
|
-
console.error('
|
|
144
|
-
console.error('
|
|
819
|
+
console.error('◼ Borg MCP Client started');
|
|
820
|
+
console.error('◼ Use borg:assimilate <cube-name> to join a cube as a drone');
|
|
821
|
+
console.error('◼ Manage your cubes at https://borgmcp.ai/dashboard');
|
|
145
822
|
}
|
|
146
|
-
// Start the server
|
|
147
823
|
main().catch((error) => {
|
|
148
824
|
console.error('Fatal error:', error);
|
|
149
825
|
process.exit(1);
|