@agent-deck/backend 1.2.9 → 1.3.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/dist/.tsbuildinfo +1 -1
- package/dist/cli-runtime.d.ts +40 -0
- package/dist/cli-runtime.d.ts.map +1 -1
- package/dist/cli-runtime.js +67 -0
- package/dist/cli-runtime.js.map +1 -1
- package/dist/lib/agent-deck-context.d.ts +1 -1
- package/dist/lib/agent-deck-context.d.ts.map +1 -1
- package/dist/lib/agent-deck-context.js +7 -26
- package/dist/lib/agent-deck-context.js.map +1 -1
- package/dist/mcp-server.d.ts +9 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +64 -835
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-session-binding.d.ts +3 -4
- package/dist/mcp-session-binding.d.ts.map +1 -1
- package/dist/mcp-session-binding.js +3 -10
- package/dist/mcp-session-binding.js.map +1 -1
- package/dist/mcp-tools/deck-card-ops.d.ts +21 -0
- package/dist/mcp-tools/deck-card-ops.d.ts.map +1 -0
- package/dist/mcp-tools/deck-card-ops.js +94 -0
- package/dist/mcp-tools/deck-card-ops.js.map +1 -0
- package/dist/mcp-tools/profile.d.ts +17 -0
- package/dist/mcp-tools/profile.d.ts.map +1 -0
- package/dist/mcp-tools/profile.js +25 -0
- package/dist/mcp-tools/profile.js.map +1 -0
- package/dist/mcp-tools/register.d.ts +49 -0
- package/dist/mcp-tools/register.d.ts.map +1 -0
- package/dist/mcp-tools/register.js +635 -0
- package/dist/mcp-tools/register.js.map +1 -0
- package/dist/mcp-tools/test-harness.d.ts +31 -0
- package/dist/mcp-tools/test-harness.d.ts.map +1 -0
- package/dist/mcp-tools/test-harness.js +108 -0
- package/dist/mcp-tools/test-harness.js.map +1 -0
- package/dist/routes/scope.d.ts.map +1 -1
- package/dist/routes/scope.js +29 -59
- package/dist/routes/scope.js.map +1 -1
- package/dist/scope/badge.d.ts +3 -0
- package/dist/scope/badge.d.ts.map +1 -0
- package/dist/scope/badge.js +23 -0
- package/dist/scope/badge.js.map +1 -0
- package/dist/scope/live-display-registry.d.ts +7 -1
- package/dist/scope/live-display-registry.d.ts.map +1 -1
- package/dist/scope/live-display-registry.js +22 -2
- package/dist/scope/live-display-registry.js.map +1 -1
- package/package.json +2 -2
- package/static-ui/assets/index-CQoBRW1p.js +409 -0
- package/static-ui/assets/index-wleyGzod.css +1 -0
- package/static-ui/index.html +2 -2
- package/static-ui/assets/index-BnvBkGNu.js +0 -404
- package/static-ui/assets/index-D2upmaZj.css +0 -1
package/dist/mcp-server.js
CHANGED
|
@@ -10,12 +10,10 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
|
10
10
|
const shared_1 = require("@agent-deck/shared");
|
|
11
11
|
const express_1 = __importDefault(require("express"));
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
14
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
-
const zod_1 = require("zod");
|
|
16
|
-
const repo_deck_1 = require("./scope/repo-deck");
|
|
17
13
|
const version_1 = require("./lib/version");
|
|
18
14
|
const mcp_session_binding_1 = require("./mcp-session-binding");
|
|
15
|
+
const register_1 = require("./mcp-tools/register");
|
|
16
|
+
const profile_1 = require("./mcp-tools/profile");
|
|
19
17
|
const agentClientHeaders = {
|
|
20
18
|
[shared_1.AGENT_DECK_CLIENT_HEADER]: shared_1.AGENT_DECK_AGENT_CLIENT,
|
|
21
19
|
Accept: 'application/json',
|
|
@@ -24,21 +22,26 @@ class AgentDeckMCPServer {
|
|
|
24
22
|
port;
|
|
25
23
|
app;
|
|
26
24
|
backendUrl;
|
|
25
|
+
toolProfile;
|
|
27
26
|
sessions = new Map();
|
|
28
27
|
/** Set only while registerTools/registerResources run for a new session server. */
|
|
29
28
|
mcpServerForRegistration;
|
|
30
29
|
/** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
|
|
31
30
|
sessionBinding;
|
|
32
31
|
activeSessionId;
|
|
32
|
+
/** Session badge from the backend registry (POST /api/scope/live-display response). */
|
|
33
|
+
badgeBySession = new Map();
|
|
34
|
+
lastTouchAtMs = new Map();
|
|
33
35
|
get server() {
|
|
34
36
|
if (!this.mcpServerForRegistration) {
|
|
35
37
|
throw new Error('Internal: MCP server not in registration context');
|
|
36
38
|
}
|
|
37
39
|
return this.mcpServerForRegistration;
|
|
38
40
|
}
|
|
39
|
-
constructor(port = 3001, backendUrl = 'http://localhost:8000') {
|
|
41
|
+
constructor(port = 3001, backendUrl = 'http://localhost:8000', toolProfile) {
|
|
40
42
|
this.port = port;
|
|
41
43
|
this.backendUrl = backendUrl;
|
|
44
|
+
this.toolProfile = toolProfile ?? (0, profile_1.resolveMcpToolProfile)();
|
|
42
45
|
this.app = (0, express_1.default)();
|
|
43
46
|
this.app.use(express_1.default.json());
|
|
44
47
|
this.setupRoutes();
|
|
@@ -77,18 +80,22 @@ class AgentDeckMCPServer {
|
|
|
77
80
|
}
|
|
78
81
|
return body.data;
|
|
79
82
|
}
|
|
80
|
-
async buildBindingPayload(sessionId
|
|
83
|
+
async buildBindingPayload(sessionId) {
|
|
81
84
|
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
82
85
|
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
86
|
+
const badge = this.badgeBySession.get(sessionId);
|
|
87
|
+
const cardCounts = deck ? (0, shared_1.countDeckCards)(deck) : { mcp: 0, credentials: 0, playbooks: 0 };
|
|
83
88
|
return {
|
|
84
89
|
workspaceRoot: snapshot.workspaceRoot,
|
|
85
90
|
deck_id: deck?.id,
|
|
86
91
|
deck_name: deck?.name,
|
|
87
|
-
deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot
|
|
92
|
+
deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
|
|
88
93
|
session_deck_override: this.sessionBinding.hasSessionDeckOverride(sessionId),
|
|
94
|
+
badge,
|
|
95
|
+
display_summary: (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts, { badge }),
|
|
89
96
|
};
|
|
90
97
|
}
|
|
91
|
-
async registerLiveDisplay(sessionId
|
|
98
|
+
async registerLiveDisplay(sessionId) {
|
|
92
99
|
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
93
100
|
if (!snapshot.workspaceRoot) {
|
|
94
101
|
return;
|
|
@@ -97,7 +104,8 @@ class AgentDeckMCPServer {
|
|
|
97
104
|
if (!deck?.id || !deck?.name) {
|
|
98
105
|
return;
|
|
99
106
|
}
|
|
100
|
-
|
|
107
|
+
const clientName = this.sessions.get(sessionId)?.server.server.getClientVersion()?.name;
|
|
108
|
+
const result = await this.callBackendAPI('/api/scope/live-display', {
|
|
101
109
|
method: 'POST',
|
|
102
110
|
headers: { 'Content-Type': 'application/json' },
|
|
103
111
|
body: JSON.stringify({
|
|
@@ -105,11 +113,32 @@ class AgentDeckMCPServer {
|
|
|
105
113
|
workspaceRoot: snapshot.workspaceRoot,
|
|
106
114
|
deckId: deck.id,
|
|
107
115
|
deckName: deck.name,
|
|
108
|
-
source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot
|
|
116
|
+
source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
|
|
117
|
+
clientName,
|
|
109
118
|
cardCounts: (0, shared_1.countDeckCards)(deck),
|
|
110
119
|
updatedAt: new Date().toISOString(),
|
|
111
120
|
}),
|
|
112
121
|
});
|
|
122
|
+
if (result && typeof result.badge === 'string') {
|
|
123
|
+
this.badgeBySession.set(sessionId, result.badge);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
static TOUCH_DEBOUNCE_MS = 5_000;
|
|
127
|
+
/** Fire-and-forget lastActivityAt bump; only for sessions the registry knows. */
|
|
128
|
+
touchLiveDisplay(sessionId) {
|
|
129
|
+
if (!this.badgeBySession.has(sessionId)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (now - (this.lastTouchAtMs.get(sessionId) ?? 0) < AgentDeckMCPServer.TOUCH_DEBOUNCE_MS) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.lastTouchAtMs.set(sessionId, now);
|
|
137
|
+
void this.callBackendAPI(`/api/scope/live-display/${encodeURIComponent(sessionId)}/touch`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify({ at: new Date().toISOString() }),
|
|
141
|
+
}).catch(() => { });
|
|
113
142
|
}
|
|
114
143
|
async unregisterLiveDisplay(sessionId) {
|
|
115
144
|
try {
|
|
@@ -180,824 +209,21 @@ class AgentDeckMCPServer {
|
|
|
180
209
|
this.server.registerTool(name, config, handler);
|
|
181
210
|
}
|
|
182
211
|
setupTools() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
await this.registerLiveDisplay(sessionId);
|
|
199
|
-
return {
|
|
200
|
-
content: [{
|
|
201
|
-
type: "text",
|
|
202
|
-
text: JSON.stringify({
|
|
203
|
-
...payload,
|
|
204
|
-
deck_name: deck.name,
|
|
205
|
-
message: 'Session bound with deck override. .agent-deck/deck.yaml is unchanged; other MCP sessions can use a different deck at the same path.',
|
|
206
|
-
}, null, 2),
|
|
207
|
-
}],
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
this.sessionBinding.clearDeckId(sessionId);
|
|
211
|
-
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
214
|
-
body: JSON.stringify({ workspaceRoot }),
|
|
215
|
-
});
|
|
216
|
-
const body = (await resolved.json());
|
|
217
|
-
if (!body.success || !body.data) {
|
|
218
|
-
throw new Error(body.error ??
|
|
219
|
-
'Failed to resolve workspace deck. Pass deckId to bind without deck.yaml, or call setup_repo_deck.');
|
|
220
|
-
}
|
|
221
|
-
const { data } = body;
|
|
222
|
-
const payload = await this.buildBindingPayload(sessionId, data.manifest.deck_id);
|
|
223
|
-
await this.registerLiveDisplay(sessionId, data.manifest.deck_id);
|
|
224
|
-
return {
|
|
225
|
-
content: [{
|
|
226
|
-
type: "text",
|
|
227
|
-
text: JSON.stringify({
|
|
228
|
-
...payload,
|
|
229
|
-
manifestPath: data.manifestPath,
|
|
230
|
-
message: 'Session bound via .agent-deck/deck.yaml.',
|
|
231
|
-
}, null, 2),
|
|
232
|
-
}],
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
const message = String(error);
|
|
237
|
-
const hint = message.includes('deck.yaml') || message.includes('No .agent-deck')
|
|
238
|
-
? ' Pass deckId on bind_workspace, call setup_repo_deck, or get_repo_deck_status to diagnose.'
|
|
239
|
-
: '';
|
|
240
|
-
return {
|
|
241
|
-
content: [{ type: "text", text: JSON.stringify({ error: message + hint }, null, 2) }],
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
this.registerTool("switch_bound_deck", {
|
|
246
|
-
title: "Switch Bound Deck",
|
|
247
|
-
description: "Switch the deck for this MCP session only. Does not modify .agent-deck/deck.yaml. Use when multiple agent sessions share the same workspace path.",
|
|
248
|
-
inputSchema: { deckId: zod_1.z.string().uuid() },
|
|
249
|
-
}, async ({ deckId }) => {
|
|
250
|
-
try {
|
|
251
|
-
const sessionId = this.getSessionId();
|
|
252
|
-
const deck = await this.fetchDeck(deckId);
|
|
253
|
-
this.sessionBinding.setDeckId(sessionId, deckId);
|
|
254
|
-
const payload = await this.buildBindingPayload(sessionId);
|
|
255
|
-
await this.registerLiveDisplay(sessionId);
|
|
256
|
-
return {
|
|
257
|
-
content: [{
|
|
258
|
-
type: "text",
|
|
259
|
-
text: JSON.stringify({
|
|
260
|
-
...payload,
|
|
261
|
-
deck_name: deck.name,
|
|
262
|
-
message: 'Session deck switched. Other sessions and deck.yaml are unchanged.',
|
|
263
|
-
}, null, 2),
|
|
264
|
-
}],
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
return this.toolError(error);
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
this.registerTool("get_session_binding", {
|
|
272
|
-
title: "Get Session Binding",
|
|
273
|
-
description: "Show workspace and effective deck for this MCP session, including whether the deck comes from a session override or repo deck.yaml.",
|
|
274
|
-
inputSchema: {},
|
|
275
|
-
}, async () => {
|
|
276
|
-
try {
|
|
277
|
-
const sessionId = this.getSessionId();
|
|
278
|
-
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
279
|
-
let manifestDeckId;
|
|
280
|
-
if (snapshot.workspaceRoot && !snapshot.deckSource) {
|
|
281
|
-
const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(snapshot.workspaceRoot);
|
|
282
|
-
manifestDeckId = manifest?.deck_id;
|
|
283
|
-
}
|
|
284
|
-
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
285
|
-
const cardCounts = deck ? (0, shared_1.countDeckCards)(deck) : { mcp: 0, credentials: 0, playbooks: 0 };
|
|
286
|
-
const displaySummary = (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts);
|
|
287
|
-
return {
|
|
288
|
-
content: [{
|
|
289
|
-
type: "text",
|
|
290
|
-
text: JSON.stringify({
|
|
291
|
-
workspaceRoot: snapshot.workspaceRoot,
|
|
292
|
-
session_deck_override: snapshot.deckId,
|
|
293
|
-
session_deck_source: snapshot.deckSource,
|
|
294
|
-
repo_manifest_deck_id: manifestDeckId,
|
|
295
|
-
effective_deck_id: deck?.id,
|
|
296
|
-
effective_deck_name: deck?.name,
|
|
297
|
-
effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
|
|
298
|
-
display_summary: displaySummary,
|
|
299
|
-
}, null, 2),
|
|
300
|
-
}],
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
return this.toolError(error);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
this.registerTool("get_repo_deck_status", {
|
|
308
|
-
title: "Get Repo Deck Status",
|
|
309
|
-
description: "Check whether .agent-deck/deck.yaml exists in a workspace and whether it links to a valid deck",
|
|
310
|
-
inputSchema: { workspaceRoot: zod_1.z.string() },
|
|
311
|
-
}, async ({ workspaceRoot }) => {
|
|
312
|
-
try {
|
|
313
|
-
const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
|
|
314
|
-
const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
315
|
-
if (!manifest) {
|
|
316
|
-
const decks = await this.callBackendAPI('/api/decks');
|
|
317
|
-
return {
|
|
318
|
-
content: [{
|
|
319
|
-
type: "text",
|
|
320
|
-
text: JSON.stringify({
|
|
321
|
-
status: 'missing',
|
|
322
|
-
workspaceRoot,
|
|
323
|
-
expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
|
|
324
|
-
message: 'No .agent-deck/deck.yaml found. Use setup_repo_deck to create one.',
|
|
325
|
-
availableDecks: Array.isArray(decks)
|
|
326
|
-
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
327
|
-
: decks,
|
|
328
|
-
}, null, 2),
|
|
329
|
-
}],
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
333
|
-
method: 'POST',
|
|
334
|
-
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
335
|
-
body: JSON.stringify({ workspaceRoot }),
|
|
336
|
-
});
|
|
337
|
-
const body = (await resolved.json());
|
|
338
|
-
return {
|
|
339
|
-
content: [{
|
|
340
|
-
type: "text",
|
|
341
|
-
text: JSON.stringify({
|
|
342
|
-
status: body.success ? 'ok' : 'invalid',
|
|
343
|
-
workspaceRoot,
|
|
344
|
-
filePath,
|
|
345
|
-
manifest,
|
|
346
|
-
deck: body.data?.deck,
|
|
347
|
-
error: body.success ? undefined : body.error,
|
|
348
|
-
}, null, 2),
|
|
349
|
-
}],
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
catch (error) {
|
|
353
|
-
return {
|
|
354
|
-
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
this.registerTool("setup_repo_deck", {
|
|
359
|
-
title: "Setup Repo Deck",
|
|
360
|
-
description: "Create or verify .agent-deck/deck.yaml in a repo. Links the workspace to an Agent Deck by deck_id. Optionally writes the file.",
|
|
361
|
-
inputSchema: {
|
|
362
|
-
workspaceRoot: zod_1.z.string(),
|
|
363
|
-
deckId: zod_1.z.string().uuid().optional(),
|
|
364
|
-
deckName: zod_1.z.string().optional(),
|
|
365
|
-
writeFile: zod_1.z.boolean().optional(),
|
|
366
|
-
},
|
|
367
|
-
}, async ({ workspaceRoot, deckId, deckName, writeFile = true }) => {
|
|
368
|
-
try {
|
|
369
|
-
const sessionId = this.getSessionId();
|
|
370
|
-
const existing = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
371
|
-
if (existing) {
|
|
372
|
-
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
373
|
-
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
374
|
-
method: 'POST',
|
|
375
|
-
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
376
|
-
body: JSON.stringify({ workspaceRoot }),
|
|
377
|
-
});
|
|
378
|
-
const body = (await resolved.json());
|
|
379
|
-
if (!body.success || !body.data) {
|
|
380
|
-
throw new Error(body.error ?? 'Existing manifest is invalid');
|
|
381
|
-
}
|
|
382
|
-
return {
|
|
383
|
-
content: [{
|
|
384
|
-
type: "text",
|
|
385
|
-
text: JSON.stringify({
|
|
386
|
-
status: 'existing',
|
|
387
|
-
workspaceRoot,
|
|
388
|
-
manifestPath: body.data.manifestPath,
|
|
389
|
-
deck_id: body.data.manifest.deck_id,
|
|
390
|
-
deck_name: body.data.deck.name,
|
|
391
|
-
message: 'Workspace already linked. Session bound to this deck.',
|
|
392
|
-
}, null, 2),
|
|
393
|
-
}],
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
if (!deckId) {
|
|
397
|
-
const decks = await this.callBackendAPI('/api/decks');
|
|
398
|
-
return {
|
|
399
|
-
content: [{
|
|
400
|
-
type: "text",
|
|
401
|
-
text: JSON.stringify({
|
|
402
|
-
status: 'needs_deck_id',
|
|
403
|
-
workspaceRoot,
|
|
404
|
-
expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
|
|
405
|
-
message: 'Pick a deck id from the dashboard (My Decks → copy icon) and call setup_repo_deck again with deckId.',
|
|
406
|
-
availableDecks: Array.isArray(decks)
|
|
407
|
-
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
408
|
-
: decks,
|
|
409
|
-
}, null, 2),
|
|
410
|
-
}],
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
const deck = await this.callBackendAPI(`/api/decks/${deckId}`);
|
|
414
|
-
if (!deck?.id) {
|
|
415
|
-
throw new Error(`Deck not found: ${deckId}`);
|
|
416
|
-
}
|
|
417
|
-
const content = (0, repo_deck_1.formatRepoDeckManifest)(deckId, deckName ?? deck.name);
|
|
418
|
-
const dirPath = node_path_1.default.join(node_path_1.default.resolve(workspaceRoot), '.agent-deck');
|
|
419
|
-
const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
|
|
420
|
-
if (writeFile) {
|
|
421
|
-
await promises_1.default.mkdir(dirPath, { recursive: true });
|
|
422
|
-
await promises_1.default.writeFile(filePath, content, 'utf8');
|
|
423
|
-
}
|
|
424
|
-
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
425
|
-
return {
|
|
426
|
-
content: [{
|
|
427
|
-
type: "text",
|
|
428
|
-
text: JSON.stringify({
|
|
429
|
-
status: writeFile ? 'created' : 'preview',
|
|
430
|
-
workspaceRoot,
|
|
431
|
-
filePath,
|
|
432
|
-
deck_id: deckId,
|
|
433
|
-
deck_name: deck.name,
|
|
434
|
-
manifestContent: content,
|
|
435
|
-
message: writeFile
|
|
436
|
-
? 'Created .agent-deck/deck.yaml and bound this session to the deck.'
|
|
437
|
-
: 'Preview only — set writeFile true to write the file.',
|
|
438
|
-
}, null, 2),
|
|
439
|
-
}],
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
catch (error) {
|
|
443
|
-
return {
|
|
444
|
-
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
// Get all decks (metadata only; credentials stripped unless bound)
|
|
449
|
-
this.registerTool("get_decks", {
|
|
450
|
-
title: "Get Decks",
|
|
451
|
-
description: "Get all available decks from Agent Deck",
|
|
452
|
-
inputSchema: {}
|
|
453
|
-
}, async () => {
|
|
454
|
-
try {
|
|
455
|
-
const decks = await this.callBackendAPI('/api/decks');
|
|
456
|
-
return {
|
|
457
|
-
content: [{ type: "text", text: JSON.stringify(decks, null, 2) }]
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
catch (error) {
|
|
461
|
-
return {
|
|
462
|
-
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }]
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
this.registerTool("get_bound_deck", {
|
|
467
|
-
title: "Get Bound Deck",
|
|
468
|
-
description: "Get the deck bound to this session (session deckId override, env, or .agent-deck/deck.yaml)",
|
|
469
|
-
inputSchema: {},
|
|
470
|
-
}, async () => {
|
|
471
|
-
try {
|
|
472
|
-
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
473
|
-
return {
|
|
474
|
-
content: [{ type: "text", text: JSON.stringify(deck, null, 2) }],
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
catch (error) {
|
|
478
|
-
return {
|
|
479
|
-
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
// Deprecated alias
|
|
484
|
-
this.registerTool("get_active_deck", {
|
|
485
|
-
title: "Get Active Deck (deprecated)",
|
|
486
|
-
description: "Deprecated — use get_bound_deck. Returns the workspace-bound deck.",
|
|
487
|
-
inputSchema: {},
|
|
488
|
-
}, async () => {
|
|
489
|
-
try {
|
|
490
|
-
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
491
|
-
return {
|
|
492
|
-
content: [{ type: "text", text: JSON.stringify(deck, null, 2) }],
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
return {
|
|
497
|
-
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
this.registerTool("list_bound_deck_services", {
|
|
502
|
-
title: "List Bound Deck Services",
|
|
503
|
-
description: "List MCP services on the workspace-bound deck",
|
|
504
|
-
inputSchema: {},
|
|
505
|
-
}, async () => {
|
|
506
|
-
try {
|
|
507
|
-
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
508
|
-
const services = deck?.services ?? [];
|
|
509
|
-
return { content: [{ type: "text", text: JSON.stringify(services, null, 2) }] };
|
|
510
|
-
}
|
|
511
|
-
catch (error) {
|
|
512
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
this.registerTool("list_active_deck_services", {
|
|
516
|
-
title: "List Active Deck Services (deprecated)",
|
|
517
|
-
description: "Deprecated — use list_bound_deck_services",
|
|
518
|
-
inputSchema: {},
|
|
519
|
-
}, async () => {
|
|
520
|
-
try {
|
|
521
|
-
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
522
|
-
const services = deck?.services ?? [];
|
|
523
|
-
return { content: [{ type: "text", text: JSON.stringify(services, null, 2) }] };
|
|
524
|
-
}
|
|
525
|
-
catch (error) {
|
|
526
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
this.registerTool("list_bound_deck_credentials", {
|
|
530
|
-
title: "List Bound Deck Credentials",
|
|
531
|
-
description: "List API key metadata on the workspace-bound deck",
|
|
532
|
-
inputSchema: {},
|
|
533
|
-
}, async () => {
|
|
534
|
-
try {
|
|
535
|
-
const credentials = await this.callBackendAPI('/api/credentials');
|
|
536
|
-
return { content: [{ type: "text", text: JSON.stringify(credentials, null, 2) }] };
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
this.registerTool("list_active_deck_credentials", {
|
|
543
|
-
title: "List Active Deck Credentials (deprecated)",
|
|
544
|
-
description: "Deprecated — use list_bound_deck_credentials",
|
|
545
|
-
inputSchema: {},
|
|
546
|
-
}, async () => {
|
|
547
|
-
try {
|
|
548
|
-
const credentials = await this.callBackendAPI('/api/credentials');
|
|
549
|
-
return { content: [{ type: "text", text: JSON.stringify(credentials, null, 2) }] };
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
this.registerTool("list_playbooks", {
|
|
556
|
-
title: "List Playbooks",
|
|
557
|
-
description: "List playbook cards on the bound deck (id, title, triggers)",
|
|
558
|
-
inputSchema: {},
|
|
559
|
-
}, async () => {
|
|
560
|
-
try {
|
|
561
|
-
const playbooks = await this.callBackendAPI('/api/playbooks/summaries');
|
|
562
|
-
return { content: [{ type: "text", text: JSON.stringify(playbooks, null, 2) }] };
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
this.registerTool("get_playbook", {
|
|
569
|
-
title: "Get Playbook",
|
|
570
|
-
description: "Get full markdown body, metadata, and dependencies for a playbook card by id",
|
|
571
|
-
inputSchema: { playbook_id: zod_1.z.string() },
|
|
572
|
-
}, async ({ playbook_id }) => {
|
|
573
|
-
try {
|
|
574
|
-
const playbook = await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`);
|
|
575
|
-
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
576
|
-
}
|
|
577
|
-
catch (error) {
|
|
578
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
this.registerTool("register_playbook", {
|
|
582
|
-
title: "Register Playbook",
|
|
583
|
-
description: "Create a playbook card, auto-detect API key and MCP dependencies from the content, and add it to the bound deck by default",
|
|
584
|
-
inputSchema: {
|
|
585
|
-
title: zod_1.z.string(),
|
|
586
|
-
body: zod_1.z.string(),
|
|
587
|
-
triggers: zod_1.z.array(zod_1.z.string()).optional(),
|
|
588
|
-
playbook_id: zod_1.z.string().optional(),
|
|
589
|
-
exec: zod_1.z.string().optional(),
|
|
590
|
-
skill: zod_1.z.string().optional(),
|
|
591
|
-
depends_on_credential_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
592
|
-
depends_on_service_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
593
|
-
add_to_bound_deck: zod_1.z.boolean().optional(),
|
|
594
|
-
auto_detect_dependencies: zod_1.z.boolean().optional(),
|
|
595
|
-
},
|
|
596
|
-
}, async (args) => {
|
|
597
|
-
try {
|
|
598
|
-
const playbook = await this.callBackendAPI("/api/playbooks", {
|
|
599
|
-
method: "POST",
|
|
600
|
-
headers: { "Content-Type": "application/json" },
|
|
601
|
-
body: JSON.stringify({
|
|
602
|
-
title: args.title,
|
|
603
|
-
body: args.body,
|
|
604
|
-
triggers: args.triggers,
|
|
605
|
-
id: args.playbook_id,
|
|
606
|
-
exec: args.exec,
|
|
607
|
-
skill: args.skill,
|
|
608
|
-
dependsOnCredentialIds: args.depends_on_credential_ids,
|
|
609
|
-
dependsOnServiceIds: args.depends_on_service_ids,
|
|
610
|
-
addToBoundDeck: args.add_to_bound_deck,
|
|
611
|
-
autoDetectDependencies: args.auto_detect_dependencies,
|
|
612
|
-
}),
|
|
613
|
-
});
|
|
614
|
-
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
615
|
-
}
|
|
616
|
-
catch (error) {
|
|
617
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
this.registerTool("update_playbook", {
|
|
621
|
-
title: "Update Playbook",
|
|
622
|
-
description: "Update a playbook on the bound deck. Dependencies are re-detected from the updated content by default.",
|
|
623
|
-
inputSchema: {
|
|
624
|
-
playbook_id: zod_1.z.string(),
|
|
625
|
-
title: zod_1.z.string().optional(),
|
|
626
|
-
body: zod_1.z.string().optional(),
|
|
627
|
-
triggers: zod_1.z.array(zod_1.z.string()).optional(),
|
|
628
|
-
exec: zod_1.z.string().optional(),
|
|
629
|
-
skill: zod_1.z.string().optional(),
|
|
630
|
-
depends_on_credential_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
631
|
-
depends_on_service_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
632
|
-
auto_detect_dependencies: zod_1.z.boolean().optional(),
|
|
633
|
-
},
|
|
634
|
-
}, async ({ playbook_id, ...args }) => {
|
|
635
|
-
try {
|
|
636
|
-
const playbook = await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`, {
|
|
637
|
-
method: "PUT",
|
|
638
|
-
headers: { "Content-Type": "application/json" },
|
|
639
|
-
body: JSON.stringify({
|
|
640
|
-
title: args.title,
|
|
641
|
-
body: args.body,
|
|
642
|
-
triggers: args.triggers,
|
|
643
|
-
exec: args.exec,
|
|
644
|
-
skill: args.skill,
|
|
645
|
-
dependsOnCredentialIds: args.depends_on_credential_ids,
|
|
646
|
-
dependsOnServiceIds: args.depends_on_service_ids,
|
|
647
|
-
autoDetectDependencies: args.auto_detect_dependencies,
|
|
648
|
-
}),
|
|
649
|
-
});
|
|
650
|
-
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
651
|
-
}
|
|
652
|
-
catch (error) {
|
|
653
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
this.registerTool("list_collection_services", {
|
|
657
|
-
title: "List Collection Services",
|
|
658
|
-
description: "List all registered MCP services in the collection (metadata only)",
|
|
659
|
-
inputSchema: {},
|
|
660
|
-
}, async () => {
|
|
661
|
-
try {
|
|
662
|
-
const services = await this.callBackendAPI('/api/services');
|
|
663
|
-
return this.toolResult(services);
|
|
664
|
-
}
|
|
665
|
-
catch (error) {
|
|
666
|
-
return this.toolError(error);
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
this.registerTool("register_service", {
|
|
670
|
-
title: "Register Service",
|
|
671
|
-
description: "Register an MCP service in the collection. Optionally add it to the bound deck. OAuth setup still requires the dashboard browser flow.",
|
|
672
|
-
inputSchema: {
|
|
673
|
-
name: zod_1.z.string(),
|
|
674
|
-
type: zod_1.z.enum(['mcp', 'a2a', 'local-mcp']),
|
|
675
|
-
url: zod_1.z.string(),
|
|
676
|
-
description: zod_1.z.string().optional(),
|
|
677
|
-
cardColor: zod_1.z.string().optional(),
|
|
678
|
-
credentialId: zod_1.z.string().optional(),
|
|
679
|
-
headers: zod_1.z.record(zod_1.z.string()).optional(),
|
|
680
|
-
localCommand: zod_1.z.string().optional(),
|
|
681
|
-
localArgs: zod_1.z.array(zod_1.z.string()).optional(),
|
|
682
|
-
localWorkingDir: zod_1.z.string().optional(),
|
|
683
|
-
localEnv: zod_1.z.record(zod_1.z.string()).optional(),
|
|
684
|
-
add_to_bound_deck: zod_1.z.boolean().optional(),
|
|
685
|
-
position: zod_1.z.number().optional(),
|
|
686
|
-
},
|
|
687
|
-
}, async (args) => {
|
|
688
|
-
try {
|
|
689
|
-
const { add_to_bound_deck, position, ...serviceInput } = args;
|
|
690
|
-
const service = await this.callBackendAPI('/api/services', {
|
|
691
|
-
method: 'POST',
|
|
692
|
-
headers: { 'Content-Type': 'application/json' },
|
|
693
|
-
body: JSON.stringify(serviceInput),
|
|
694
|
-
});
|
|
695
|
-
if (add_to_bound_deck !== false) {
|
|
696
|
-
const deckId = await this.getBoundDeckId();
|
|
697
|
-
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
698
|
-
method: 'POST',
|
|
699
|
-
headers: { 'Content-Type': 'application/json' },
|
|
700
|
-
body: JSON.stringify({ serviceId: service.id, position }),
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
return this.toolResult(service);
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
return this.toolError(error);
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
this.registerTool("update_service", {
|
|
710
|
-
title: "Update Service",
|
|
711
|
-
description: "Update MCP service metadata in the collection (not OAuth tokens — use dashboard for OAuth)",
|
|
712
|
-
inputSchema: {
|
|
713
|
-
service_id: zod_1.z.string(),
|
|
714
|
-
name: zod_1.z.string().optional(),
|
|
715
|
-
type: zod_1.z.enum(['mcp', 'a2a', 'local-mcp']).optional(),
|
|
716
|
-
url: zod_1.z.string().optional(),
|
|
717
|
-
description: zod_1.z.string().optional(),
|
|
718
|
-
cardColor: zod_1.z.string().optional(),
|
|
719
|
-
credentialId: zod_1.z.string().optional(),
|
|
720
|
-
headers: zod_1.z.record(zod_1.z.string()).optional(),
|
|
721
|
-
localCommand: zod_1.z.string().optional(),
|
|
722
|
-
localArgs: zod_1.z.array(zod_1.z.string()).optional(),
|
|
723
|
-
localWorkingDir: zod_1.z.string().optional(),
|
|
724
|
-
localEnv: zod_1.z.record(zod_1.z.string()).optional(),
|
|
725
|
-
},
|
|
726
|
-
}, async ({ service_id, ...updates }) => {
|
|
727
|
-
try {
|
|
728
|
-
const service = await this.callBackendAPI(`/api/services/${service_id}`, {
|
|
729
|
-
method: 'PUT',
|
|
730
|
-
headers: { 'Content-Type': 'application/json' },
|
|
731
|
-
body: JSON.stringify(updates),
|
|
732
|
-
});
|
|
733
|
-
return this.toolResult(service);
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
return this.toolError(error);
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
this.registerTool("delete_service", {
|
|
740
|
-
title: "Delete Service",
|
|
741
|
-
description: "Remove an MCP service from the collection entirely",
|
|
742
|
-
inputSchema: { service_id: zod_1.z.string() },
|
|
743
|
-
}, async ({ service_id }) => {
|
|
744
|
-
try {
|
|
745
|
-
await this.callBackendAPI(`/api/services/${service_id}`, { method: 'DELETE' });
|
|
746
|
-
return this.toolResult({ success: true, service_id });
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
return this.toolError(error);
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
this.registerTool("add_service_to_bound_deck", {
|
|
753
|
-
title: "Add Service To Bound Deck",
|
|
754
|
-
description: "Link an existing MCP service from the collection onto the bound deck",
|
|
755
|
-
inputSchema: {
|
|
756
|
-
service_id: zod_1.z.string(),
|
|
757
|
-
position: zod_1.z.number().optional(),
|
|
758
|
-
},
|
|
759
|
-
}, async ({ service_id, position }) => {
|
|
760
|
-
try {
|
|
761
|
-
const deckId = await this.getBoundDeckId();
|
|
762
|
-
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
763
|
-
method: 'POST',
|
|
764
|
-
headers: { 'Content-Type': 'application/json' },
|
|
765
|
-
body: JSON.stringify({ serviceId: service_id, position }),
|
|
766
|
-
});
|
|
767
|
-
return this.toolResult({ success: true, deck_id: deckId, service_id });
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
return this.toolError(error);
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
this.registerTool("remove_service_from_bound_deck", {
|
|
774
|
-
title: "Remove Service From Bound Deck",
|
|
775
|
-
description: "Unlink an MCP service from the bound deck (service stays in the collection)",
|
|
776
|
-
inputSchema: { service_id: zod_1.z.string() },
|
|
777
|
-
}, async ({ service_id }) => {
|
|
778
|
-
try {
|
|
779
|
-
const deckId = await this.getBoundDeckId();
|
|
780
|
-
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
781
|
-
method: 'DELETE',
|
|
782
|
-
headers: { 'Content-Type': 'application/json' },
|
|
783
|
-
body: JSON.stringify({ serviceId: service_id }),
|
|
784
|
-
});
|
|
785
|
-
return this.toolResult({ success: true, deck_id: deckId, service_id });
|
|
786
|
-
}
|
|
787
|
-
catch (error) {
|
|
788
|
-
return this.toolError(error);
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
this.registerTool("update_service_tool_settings", {
|
|
792
|
-
title: "Update Service Tool Settings",
|
|
793
|
-
description: "Enable or disable individual tools for an MCP service on the bound deck",
|
|
794
|
-
inputSchema: {
|
|
795
|
-
service_id: zod_1.z.string(),
|
|
796
|
-
disabled_tools: zod_1.z.array(zod_1.z.string()),
|
|
797
|
-
},
|
|
798
|
-
}, async ({ service_id, disabled_tools }) => {
|
|
799
|
-
try {
|
|
800
|
-
const service = await this.callBackendAPI(`/api/services/${service_id}/tool-settings`, {
|
|
801
|
-
method: 'PUT',
|
|
802
|
-
headers: { 'Content-Type': 'application/json' },
|
|
803
|
-
body: JSON.stringify({ disabledTools: disabled_tools }),
|
|
804
|
-
});
|
|
805
|
-
return this.toolResult(service);
|
|
806
|
-
}
|
|
807
|
-
catch (error) {
|
|
808
|
-
return this.toolError(error);
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
this.registerTool("list_collection_credentials", {
|
|
812
|
-
title: "List Collection Credentials",
|
|
813
|
-
description: "List all API key metadata in the collection (no secret values). Use credential ids to link keys to the bound deck after the user stores the secret in the dashboard.",
|
|
814
|
-
inputSchema: {},
|
|
815
|
-
}, async () => {
|
|
816
|
-
try {
|
|
817
|
-
const credentials = await this.callBackendAPI('/api/credentials/collection');
|
|
818
|
-
return this.toolResult(credentials);
|
|
819
|
-
}
|
|
820
|
-
catch (error) {
|
|
821
|
-
return this.toolError(error);
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
this.registerTool("add_credential_to_bound_deck", {
|
|
825
|
-
title: "Add Credential To Bound Deck",
|
|
826
|
-
description: "Link an existing API key (by credential id) to the bound deck. The secret must already be stored via the dashboard or CLI.",
|
|
827
|
-
inputSchema: {
|
|
828
|
-
credential_id: zod_1.z.string(),
|
|
829
|
-
position: zod_1.z.number().optional(),
|
|
830
|
-
},
|
|
831
|
-
}, async ({ credential_id, position }) => {
|
|
832
|
-
try {
|
|
833
|
-
const deckId = await this.getBoundDeckId();
|
|
834
|
-
await this.callBackendAPI(`/api/decks/${deckId}/credentials`, {
|
|
835
|
-
method: 'POST',
|
|
836
|
-
headers: { 'Content-Type': 'application/json' },
|
|
837
|
-
body: JSON.stringify({ credentialId: credential_id, position }),
|
|
838
|
-
});
|
|
839
|
-
return this.toolResult({ success: true, deck_id: deckId, credential_id });
|
|
840
|
-
}
|
|
841
|
-
catch (error) {
|
|
842
|
-
return this.toolError(error);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
this.registerTool("remove_credential_from_bound_deck", {
|
|
846
|
-
title: "Remove Credential From Bound Deck",
|
|
847
|
-
description: "Unlink an API key from the bound deck (credential stays in the vault)",
|
|
848
|
-
inputSchema: { credential_id: zod_1.z.string() },
|
|
849
|
-
}, async ({ credential_id }) => {
|
|
850
|
-
try {
|
|
851
|
-
const deckId = await this.getBoundDeckId();
|
|
852
|
-
await this.callBackendAPI(`/api/decks/${deckId}/credentials`, {
|
|
853
|
-
method: 'DELETE',
|
|
854
|
-
headers: { 'Content-Type': 'application/json' },
|
|
855
|
-
body: JSON.stringify({ credentialId: credential_id }),
|
|
856
|
-
});
|
|
857
|
-
return this.toolResult({ success: true, deck_id: deckId, credential_id });
|
|
858
|
-
}
|
|
859
|
-
catch (error) {
|
|
860
|
-
return this.toolError(error);
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
this.registerTool("list_collection_playbooks", {
|
|
864
|
-
title: "List Collection Playbooks",
|
|
865
|
-
description: "List all playbook cards in the collection (for linking existing playbooks to the bound deck)",
|
|
866
|
-
inputSchema: {},
|
|
867
|
-
}, async () => {
|
|
868
|
-
try {
|
|
869
|
-
const playbooks = await this.callBackendAPI('/api/playbooks/collection');
|
|
870
|
-
return this.toolResult(playbooks);
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
return this.toolError(error);
|
|
874
|
-
}
|
|
875
|
-
});
|
|
876
|
-
this.registerTool("add_playbook_to_bound_deck", {
|
|
877
|
-
title: "Add Playbook To Bound Deck",
|
|
878
|
-
description: "Link an existing playbook card from the collection onto the bound deck",
|
|
879
|
-
inputSchema: {
|
|
880
|
-
playbook_id: zod_1.z.string(),
|
|
881
|
-
position: zod_1.z.number().optional(),
|
|
882
|
-
},
|
|
883
|
-
}, async ({ playbook_id, position }) => {
|
|
884
|
-
try {
|
|
885
|
-
const deckId = await this.getBoundDeckId();
|
|
886
|
-
await this.callBackendAPI(`/api/decks/${deckId}/playbooks`, {
|
|
887
|
-
method: 'POST',
|
|
888
|
-
headers: { 'Content-Type': 'application/json' },
|
|
889
|
-
body: JSON.stringify({ playbookId: playbook_id, position }),
|
|
890
|
-
});
|
|
891
|
-
return this.toolResult({ success: true, deck_id: deckId, playbook_id });
|
|
892
|
-
}
|
|
893
|
-
catch (error) {
|
|
894
|
-
return this.toolError(error);
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
this.registerTool("remove_playbook_from_bound_deck", {
|
|
898
|
-
title: "Remove Playbook From Bound Deck",
|
|
899
|
-
description: "Unlink a playbook from the bound deck (playbook stays in the collection)",
|
|
900
|
-
inputSchema: { playbook_id: zod_1.z.string() },
|
|
901
|
-
}, async ({ playbook_id }) => {
|
|
902
|
-
try {
|
|
903
|
-
const deckId = await this.getBoundDeckId();
|
|
904
|
-
await this.callBackendAPI(`/api/decks/${deckId}/playbooks`, {
|
|
905
|
-
method: 'DELETE',
|
|
906
|
-
headers: { 'Content-Type': 'application/json' },
|
|
907
|
-
body: JSON.stringify({ playbookId: playbook_id }),
|
|
908
|
-
});
|
|
909
|
-
return this.toolResult({ success: true, deck_id: deckId, playbook_id });
|
|
910
|
-
}
|
|
911
|
-
catch (error) {
|
|
912
|
-
return this.toolError(error);
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
this.registerTool("delete_playbook", {
|
|
916
|
-
title: "Delete Playbook",
|
|
917
|
-
description: "Delete a playbook from the collection (must be on the bound deck for agent clients)",
|
|
918
|
-
inputSchema: { playbook_id: zod_1.z.string() },
|
|
919
|
-
}, async ({ playbook_id }) => {
|
|
920
|
-
try {
|
|
921
|
-
await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`, {
|
|
922
|
-
method: 'DELETE',
|
|
923
|
-
});
|
|
924
|
-
return this.toolResult({ success: true, playbook_id });
|
|
925
|
-
}
|
|
926
|
-
catch (error) {
|
|
927
|
-
return this.toolError(error);
|
|
928
|
-
}
|
|
929
|
-
});
|
|
930
|
-
this.registerTool("create_deck", {
|
|
931
|
-
title: "Create Deck",
|
|
932
|
-
description: "Create a new deck in Agent Deck",
|
|
933
|
-
inputSchema: {
|
|
934
|
-
name: zod_1.z.string(),
|
|
935
|
-
description: zod_1.z.string().optional(),
|
|
936
|
-
},
|
|
937
|
-
}, async ({ name, description }) => {
|
|
938
|
-
try {
|
|
939
|
-
const deck = await this.callBackendAPI('/api/decks', {
|
|
940
|
-
method: 'POST',
|
|
941
|
-
headers: { 'Content-Type': 'application/json' },
|
|
942
|
-
body: JSON.stringify({ name, description }),
|
|
943
|
-
});
|
|
944
|
-
return this.toolResult(deck);
|
|
945
|
-
}
|
|
946
|
-
catch (error) {
|
|
947
|
-
return this.toolError(error);
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
// Remove duplicate old tools block - list_service_tools follows
|
|
951
|
-
this.registerTool("list_service_tools", {
|
|
952
|
-
title: "List Service Tools",
|
|
953
|
-
description: "List all available tools for a specific service in the active deck",
|
|
954
|
-
inputSchema: { serviceId: zod_1.z.string() }
|
|
955
|
-
}, async ({ serviceId }) => {
|
|
956
|
-
try {
|
|
957
|
-
const tools = await this.callBackendAPI(`/api/services/${serviceId}/tools`);
|
|
958
|
-
return { content: [{ type: "text", text: JSON.stringify(tools, null, 2) }] };
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
// Call a tool on a service in the active deck
|
|
965
|
-
this.registerTool("call_service_tool", {
|
|
966
|
-
title: "Call Service Tool",
|
|
967
|
-
description: "Call a tool on a service from the active deck",
|
|
968
|
-
inputSchema: {
|
|
969
|
-
serviceId: zod_1.z.string(),
|
|
970
|
-
toolName: zod_1.z.string(),
|
|
971
|
-
arguments: zod_1.z.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()]).optional()
|
|
972
|
-
}
|
|
973
|
-
}, async ({ serviceId, toolName, arguments: args = {} }) => {
|
|
974
|
-
try {
|
|
975
|
-
// Support both object and JSON string inputs for arguments
|
|
976
|
-
let normalizedArgs = args;
|
|
977
|
-
if (typeof normalizedArgs === 'string' && normalizedArgs.length > 0) {
|
|
978
|
-
try {
|
|
979
|
-
normalizedArgs = JSON.parse(normalizedArgs);
|
|
980
|
-
}
|
|
981
|
-
catch (e) {
|
|
982
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `Invalid JSON in arguments: ${String(e)}` }, null, 2) }] };
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
const res = await fetch(`${this.backendUrl}/api/services/${serviceId}/call`, {
|
|
986
|
-
method: 'POST',
|
|
987
|
-
headers: { ...this.getAgentHeaders(), 'Content-Type': 'application/json' },
|
|
988
|
-
body: JSON.stringify({ toolName, arguments: normalizedArgs ?? {} })
|
|
989
|
-
});
|
|
990
|
-
if (!res.ok) {
|
|
991
|
-
const text = await res.text();
|
|
992
|
-
throw new Error(`Backend API error ${res.status}: ${text}`);
|
|
993
|
-
}
|
|
994
|
-
const body = await res.json();
|
|
995
|
-
const data = (body && body.success) ? (body.data ?? body.result ?? body) : body;
|
|
996
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
997
|
-
}
|
|
998
|
-
catch (error) {
|
|
999
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
1000
|
-
}
|
|
212
|
+
(0, register_1.registerMcpTools)({
|
|
213
|
+
registerTool: (name, config, handler) => this.registerTool(name, config, handler),
|
|
214
|
+
profile: this.toolProfile,
|
|
215
|
+
getSessionId: () => this.getSessionId(),
|
|
216
|
+
getAgentHeaders: () => this.getAgentHeaders(),
|
|
217
|
+
getBoundDeckId: () => this.getBoundDeckId(),
|
|
218
|
+
callBackendAPI: (endpoint, init) => this.callBackendAPI(endpoint, init),
|
|
219
|
+
fetchDeck: (deckId) => this.fetchDeck(deckId),
|
|
220
|
+
buildBindingPayload: (sessionId) => this.buildBindingPayload(sessionId),
|
|
221
|
+
registerLiveDisplay: (sessionId) => this.registerLiveDisplay(sessionId),
|
|
222
|
+
sessionBinding: this.sessionBinding,
|
|
223
|
+
badgeBySession: this.badgeBySession,
|
|
224
|
+
backendUrl: this.backendUrl,
|
|
225
|
+
toolResult: (data) => this.toolResult(data),
|
|
226
|
+
toolError: (error) => this.toolError(error),
|
|
1001
227
|
});
|
|
1002
228
|
}
|
|
1003
229
|
setupResources() {
|
|
@@ -1125,7 +351,7 @@ class AgentDeckMCPServer {
|
|
|
1125
351
|
}
|
|
1126
352
|
});
|
|
1127
353
|
this.server.resource("bound_deck", "agent-deck://bound-deck", {
|
|
1128
|
-
description: "Deck bound
|
|
354
|
+
description: "Deck bound to this MCP session",
|
|
1129
355
|
mimeType: "application/json"
|
|
1130
356
|
}, async () => {
|
|
1131
357
|
try {
|
|
@@ -1213,7 +439,8 @@ class AgentDeckMCPServer {
|
|
|
1213
439
|
res.json({
|
|
1214
440
|
status: 'ok',
|
|
1215
441
|
service: 'agent-deck-mcp-server',
|
|
1216
|
-
backendUrl: this.backendUrl
|
|
442
|
+
backendUrl: this.backendUrl,
|
|
443
|
+
toolProfile: this.toolProfile,
|
|
1217
444
|
});
|
|
1218
445
|
});
|
|
1219
446
|
// Backend connectivity check
|
|
@@ -1244,8 +471,9 @@ class AgentDeckMCPServer {
|
|
|
1244
471
|
async handleMcpPost(req, res) {
|
|
1245
472
|
const sessionIdHeader = this.getSessionIdHeader(req);
|
|
1246
473
|
const existing = sessionIdHeader ? this.sessions.get(sessionIdHeader) : undefined;
|
|
1247
|
-
if (existing) {
|
|
474
|
+
if (existing && sessionIdHeader) {
|
|
1248
475
|
this.activeSessionId = sessionIdHeader;
|
|
476
|
+
this.touchLiveDisplay(sessionIdHeader);
|
|
1249
477
|
await existing.transport.handleRequest(req, res, req.body);
|
|
1250
478
|
return;
|
|
1251
479
|
}
|
|
@@ -1277,6 +505,8 @@ class AgentDeckMCPServer {
|
|
|
1277
505
|
if (sessionId) {
|
|
1278
506
|
this.sessions.delete(sessionId);
|
|
1279
507
|
this.sessionBinding.clearSession(sessionId);
|
|
508
|
+
this.badgeBySession.delete(sessionId);
|
|
509
|
+
this.lastTouchAtMs.delete(sessionId);
|
|
1280
510
|
void this.unregisterLiveDisplay(sessionId);
|
|
1281
511
|
}
|
|
1282
512
|
};
|
|
@@ -1299,6 +529,7 @@ class AgentDeckMCPServer {
|
|
|
1299
529
|
return;
|
|
1300
530
|
}
|
|
1301
531
|
this.activeSessionId = sessionId;
|
|
532
|
+
this.touchLiveDisplay(sessionId);
|
|
1302
533
|
await session.transport.handleRequest(req, res);
|
|
1303
534
|
}
|
|
1304
535
|
async start() {
|
|
@@ -1308,12 +539,10 @@ class AgentDeckMCPServer {
|
|
|
1308
539
|
this.app.listen(this.port, () => {
|
|
1309
540
|
console.log(`✅ Agent Deck MCP Server is ready to accept connections`);
|
|
1310
541
|
console.log(`📋 Available tools:`);
|
|
1311
|
-
console.log(` - bind_workspace: Bind session to workspace
|
|
542
|
+
console.log(` - bind_workspace: Bind session to workspace + deck (deckId required)`);
|
|
1312
543
|
console.log(` - switch_bound_deck: Switch deck for this session only`);
|
|
1313
544
|
console.log(` - get_session_binding: Show session workspace + effective deck`);
|
|
1314
|
-
console.log(` -
|
|
1315
|
-
console.log(` - setup_repo_deck: Create .agent-deck/deck.yaml for a repo`);
|
|
1316
|
-
console.log(` - get_bound_deck: Get workspace-bound deck`);
|
|
545
|
+
console.log(` - get_bound_deck: Get session-bound deck`);
|
|
1317
546
|
console.log(` - list_service_tools: List tools for a specific service`);
|
|
1318
547
|
console.log(` - call_service_tool: Call a tool on a service`);
|
|
1319
548
|
console.log(`📋 Available resources:`);
|