@agent-deck/backend 1.2.9 → 1.2.10
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/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 +6 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +61 -210
- 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/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
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AgentDeckContextError = void 0;
|
|
4
4
|
exports.resolveAgentDeckId = resolveAgentDeckId;
|
|
5
5
|
const shared_1 = require("@agent-deck/shared");
|
|
6
|
-
const repo_deck_1 = require("../scope/repo-deck");
|
|
7
6
|
class AgentDeckContextError extends Error {
|
|
8
7
|
constructor(message) {
|
|
9
8
|
super(message);
|
|
@@ -15,34 +14,16 @@ function headerValue(request, name) {
|
|
|
15
14
|
const value = request.headers[name];
|
|
16
15
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
17
16
|
}
|
|
18
|
-
/** Resolve which deck an agent request is scoped to (
|
|
17
|
+
/** Resolve which deck an agent request is scoped to (session deck id header only). */
|
|
19
18
|
async function resolveAgentDeckId(request, db) {
|
|
20
19
|
const deckIdHeader = headerValue(request, shared_1.AGENT_DECK_DECK_ID_HEADER);
|
|
21
|
-
if (deckIdHeader) {
|
|
22
|
-
|
|
23
|
-
if (!deck) {
|
|
24
|
-
throw new AgentDeckContextError(`Deck not found: ${deckIdHeader}`);
|
|
25
|
-
}
|
|
26
|
-
return deckIdHeader;
|
|
20
|
+
if (!deckIdHeader) {
|
|
21
|
+
throw new AgentDeckContextError('No bound deck — call bind_workspace with deckId (use get_decks to list decks) or switch_bound_deck');
|
|
27
22
|
}
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
manifest = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
throw new AgentDeckContextError(error instanceof repo_deck_1.RepoDeckManifestError ? error.message : 'Failed to load repo deck manifest');
|
|
36
|
-
}
|
|
37
|
-
if (!manifest) {
|
|
38
|
-
throw new AgentDeckContextError(`No ${'.agent-deck/deck.yaml'} found in workspace: ${workspaceRoot}`);
|
|
39
|
-
}
|
|
40
|
-
const deck = await db.getDeck(manifest.deck_id);
|
|
41
|
-
if (!deck) {
|
|
42
|
-
throw new AgentDeckContextError(`deck_id in repo manifest not found in Agent Deck: ${manifest.deck_id}`);
|
|
43
|
-
}
|
|
44
|
-
return manifest.deck_id;
|
|
23
|
+
const deck = await db.getDeck(deckIdHeader);
|
|
24
|
+
if (!deck) {
|
|
25
|
+
throw new AgentDeckContextError(`Deck not found: ${deckIdHeader}`);
|
|
45
26
|
}
|
|
46
|
-
|
|
27
|
+
return deckIdHeader;
|
|
47
28
|
}
|
|
48
29
|
//# sourceMappingURL=agent-deck-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-deck-context.js","sourceRoot":"","sources":["../../src/lib/agent-deck-context.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"agent-deck-context.js","sourceRoot":"","sources":["../../src/lib/agent-deck-context.ts"],"names":[],"mappings":";;;AAiBA,gDAgBC;AAhCD,+CAA+D;AAG/D,MAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED,SAAS,WAAW,CAAC,OAAuB,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,sFAAsF;AAC/E,KAAK,UAAU,kBAAkB,CACtC,OAAuB,EACvB,EAAmB;IAEnB,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,EAAE,kCAAyB,CAAC,CAAC;IACrE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,qBAAqB,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export declare class AgentDeckMCPServer {
|
|
|
9
9
|
/** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
|
|
10
10
|
private sessionBinding;
|
|
11
11
|
private activeSessionId;
|
|
12
|
+
/** Session badge from the backend registry (POST /api/scope/live-display response). */
|
|
13
|
+
private badgeBySession;
|
|
14
|
+
private lastTouchAtMs;
|
|
12
15
|
private get server();
|
|
13
16
|
constructor(port?: number, backendUrl?: string);
|
|
14
17
|
private createMcpServer;
|
|
@@ -17,6 +20,9 @@ export declare class AgentDeckMCPServer {
|
|
|
17
20
|
private fetchDeck;
|
|
18
21
|
private buildBindingPayload;
|
|
19
22
|
private registerLiveDisplay;
|
|
23
|
+
private static readonly TOUCH_DEBOUNCE_MS;
|
|
24
|
+
/** Fire-and-forget lastActivityAt bump; only for sessions the registry knows. */
|
|
25
|
+
private touchLiveDisplay;
|
|
20
26
|
private unregisterLiveDisplay;
|
|
21
27
|
private callBackendAPI;
|
|
22
28
|
private getBoundDeckId;
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AASA,OAAO,OAA8B,MAAM,SAAS,CAAC;AAmBrD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,mFAAmF;IACnF,OAAO,CAAC,wBAAwB,CAAwB;IACxD,mFAAmF;IACnF,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAqB;IAC5C,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,aAAa,CAA6B;IAElD,OAAO,KAAK,MAAM,GAKjB;gBAEW,IAAI,GAAE,MAAa,EAAE,UAAU,GAAE,MAAgC;IAc7E,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;YAIT,SAAS;YAkBT,mBAAmB;YAgBnB,mBAAmB;IA+BjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAElD,iFAAiF;IACjF,OAAO,CAAC,gBAAgB;YAmBV,qBAAqB;YAUrB,cAAc;YAwCd,cAAc;IAQ5B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAIjB,6EAA6E;IAC7E,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,UAAU;IAkpBlB,OAAO,CAAC,cAAc;IA8MtB,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,kBAAkB;YAKZ,aAAa;YA2Db,uBAAuB;IAkB/B,KAAK;IAiCL,IAAI;CAeX"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -10,10 +10,7 @@ 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
13
|
const zod_1 = require("zod");
|
|
16
|
-
const repo_deck_1 = require("./scope/repo-deck");
|
|
17
14
|
const version_1 = require("./lib/version");
|
|
18
15
|
const mcp_session_binding_1 = require("./mcp-session-binding");
|
|
19
16
|
const agentClientHeaders = {
|
|
@@ -30,6 +27,9 @@ class AgentDeckMCPServer {
|
|
|
30
27
|
/** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
|
|
31
28
|
sessionBinding;
|
|
32
29
|
activeSessionId;
|
|
30
|
+
/** Session badge from the backend registry (POST /api/scope/live-display response). */
|
|
31
|
+
badgeBySession = new Map();
|
|
32
|
+
lastTouchAtMs = new Map();
|
|
33
33
|
get server() {
|
|
34
34
|
if (!this.mcpServerForRegistration) {
|
|
35
35
|
throw new Error('Internal: MCP server not in registration context');
|
|
@@ -77,18 +77,22 @@ class AgentDeckMCPServer {
|
|
|
77
77
|
}
|
|
78
78
|
return body.data;
|
|
79
79
|
}
|
|
80
|
-
async buildBindingPayload(sessionId
|
|
80
|
+
async buildBindingPayload(sessionId) {
|
|
81
81
|
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
82
82
|
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
83
|
+
const badge = this.badgeBySession.get(sessionId);
|
|
84
|
+
const cardCounts = deck ? (0, shared_1.countDeckCards)(deck) : { mcp: 0, credentials: 0, playbooks: 0 };
|
|
83
85
|
return {
|
|
84
86
|
workspaceRoot: snapshot.workspaceRoot,
|
|
85
87
|
deck_id: deck?.id,
|
|
86
88
|
deck_name: deck?.name,
|
|
87
|
-
deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot
|
|
89
|
+
deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
|
|
88
90
|
session_deck_override: this.sessionBinding.hasSessionDeckOverride(sessionId),
|
|
91
|
+
badge,
|
|
92
|
+
display_summary: (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts, { badge }),
|
|
89
93
|
};
|
|
90
94
|
}
|
|
91
|
-
async registerLiveDisplay(sessionId
|
|
95
|
+
async registerLiveDisplay(sessionId) {
|
|
92
96
|
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
93
97
|
if (!snapshot.workspaceRoot) {
|
|
94
98
|
return;
|
|
@@ -97,7 +101,8 @@ class AgentDeckMCPServer {
|
|
|
97
101
|
if (!deck?.id || !deck?.name) {
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
|
-
|
|
104
|
+
const clientName = this.sessions.get(sessionId)?.server.server.getClientVersion()?.name;
|
|
105
|
+
const result = await this.callBackendAPI('/api/scope/live-display', {
|
|
101
106
|
method: 'POST',
|
|
102
107
|
headers: { 'Content-Type': 'application/json' },
|
|
103
108
|
body: JSON.stringify({
|
|
@@ -105,11 +110,32 @@ class AgentDeckMCPServer {
|
|
|
105
110
|
workspaceRoot: snapshot.workspaceRoot,
|
|
106
111
|
deckId: deck.id,
|
|
107
112
|
deckName: deck.name,
|
|
108
|
-
source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot
|
|
113
|
+
source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
|
|
114
|
+
clientName,
|
|
109
115
|
cardCounts: (0, shared_1.countDeckCards)(deck),
|
|
110
116
|
updatedAt: new Date().toISOString(),
|
|
111
117
|
}),
|
|
112
118
|
});
|
|
119
|
+
if (result && typeof result.badge === 'string') {
|
|
120
|
+
this.badgeBySession.set(sessionId, result.badge);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
static TOUCH_DEBOUNCE_MS = 5_000;
|
|
124
|
+
/** Fire-and-forget lastActivityAt bump; only for sessions the registry knows. */
|
|
125
|
+
touchLiveDisplay(sessionId) {
|
|
126
|
+
if (!this.badgeBySession.has(sessionId)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
if (now - (this.lastTouchAtMs.get(sessionId) ?? 0) < AgentDeckMCPServer.TOUCH_DEBOUNCE_MS) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.lastTouchAtMs.set(sessionId, now);
|
|
134
|
+
void this.callBackendAPI(`/api/scope/live-display/${encodeURIComponent(sessionId)}/touch`, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: { 'Content-Type': 'application/json' },
|
|
137
|
+
body: JSON.stringify({ at: new Date().toISOString() }),
|
|
138
|
+
}).catch(() => { });
|
|
113
139
|
}
|
|
114
140
|
async unregisterLiveDisplay(sessionId) {
|
|
115
141
|
try {
|
|
@@ -182,84 +208,52 @@ class AgentDeckMCPServer {
|
|
|
182
208
|
setupTools() {
|
|
183
209
|
this.registerTool("bind_workspace", {
|
|
184
210
|
title: "Bind Workspace",
|
|
185
|
-
description: "Bind this MCP session to a workspace root
|
|
211
|
+
description: "Bind this MCP session to a workspace root and deck. Use get_decks to list deck ids.",
|
|
186
212
|
inputSchema: {
|
|
187
213
|
workspaceRoot: zod_1.z.string(),
|
|
188
|
-
deckId: zod_1.z.string().uuid()
|
|
214
|
+
deckId: zod_1.z.string().uuid(),
|
|
189
215
|
},
|
|
190
216
|
}, async ({ workspaceRoot, deckId }) => {
|
|
191
217
|
try {
|
|
192
218
|
const sessionId = this.getSessionId();
|
|
193
219
|
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
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);
|
|
220
|
+
const deck = await this.fetchDeck(deckId);
|
|
221
|
+
this.sessionBinding.setDeckId(sessionId, deckId);
|
|
222
|
+
await this.registerLiveDisplay(sessionId);
|
|
223
|
+
const payload = await this.buildBindingPayload(sessionId);
|
|
224
224
|
return {
|
|
225
225
|
content: [{
|
|
226
226
|
type: "text",
|
|
227
227
|
text: JSON.stringify({
|
|
228
228
|
...payload,
|
|
229
|
-
|
|
230
|
-
message: 'Session bound
|
|
229
|
+
deck_name: deck.name,
|
|
230
|
+
message: 'Session bound to deck.',
|
|
231
231
|
}, null, 2),
|
|
232
232
|
}],
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
catch (error) {
|
|
236
|
-
|
|
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
|
-
};
|
|
236
|
+
return this.toolError(error);
|
|
243
237
|
}
|
|
244
238
|
});
|
|
245
239
|
this.registerTool("switch_bound_deck", {
|
|
246
240
|
title: "Switch Bound Deck",
|
|
247
|
-
description: "Switch the deck for this MCP session only.
|
|
241
|
+
description: "Switch the deck for this MCP session only. Use when multiple agent sessions share the same workspace path.",
|
|
248
242
|
inputSchema: { deckId: zod_1.z.string().uuid() },
|
|
249
243
|
}, async ({ deckId }) => {
|
|
250
244
|
try {
|
|
251
245
|
const sessionId = this.getSessionId();
|
|
252
246
|
const deck = await this.fetchDeck(deckId);
|
|
253
247
|
this.sessionBinding.setDeckId(sessionId, deckId);
|
|
254
|
-
const payload = await this.buildBindingPayload(sessionId);
|
|
255
248
|
await this.registerLiveDisplay(sessionId);
|
|
249
|
+
const payload = await this.buildBindingPayload(sessionId);
|
|
256
250
|
return {
|
|
257
251
|
content: [{
|
|
258
252
|
type: "text",
|
|
259
253
|
text: JSON.stringify({
|
|
260
254
|
...payload,
|
|
261
255
|
deck_name: deck.name,
|
|
262
|
-
message: 'Session deck switched. Other sessions
|
|
256
|
+
message: 'Session deck switched. Other sessions are unchanged.',
|
|
263
257
|
}, null, 2),
|
|
264
258
|
}],
|
|
265
259
|
};
|
|
@@ -270,31 +264,27 @@ class AgentDeckMCPServer {
|
|
|
270
264
|
});
|
|
271
265
|
this.registerTool("get_session_binding", {
|
|
272
266
|
title: "Get Session Binding",
|
|
273
|
-
description: "Show workspace and effective deck for this MCP session
|
|
267
|
+
description: "Show workspace and effective deck for this MCP session (session override or env default).",
|
|
274
268
|
inputSchema: {},
|
|
275
269
|
}, async () => {
|
|
276
270
|
try {
|
|
277
271
|
const sessionId = this.getSessionId();
|
|
278
272
|
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
273
|
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
274
|
+
const badge = this.badgeBySession.get(sessionId);
|
|
285
275
|
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);
|
|
276
|
+
const displaySummary = (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts, { badge });
|
|
287
277
|
return {
|
|
288
278
|
content: [{
|
|
289
279
|
type: "text",
|
|
290
280
|
text: JSON.stringify({
|
|
291
281
|
workspaceRoot: snapshot.workspaceRoot,
|
|
292
|
-
|
|
282
|
+
session_deck_id: snapshot.deckId,
|
|
293
283
|
session_deck_source: snapshot.deckSource,
|
|
294
|
-
repo_manifest_deck_id: manifestDeckId,
|
|
295
284
|
effective_deck_id: deck?.id,
|
|
296
285
|
effective_deck_name: deck?.name,
|
|
297
|
-
effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot
|
|
286
|
+
effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
|
|
287
|
+
badge,
|
|
298
288
|
display_summary: displaySummary,
|
|
299
289
|
}, null, 2),
|
|
300
290
|
}],
|
|
@@ -304,147 +294,6 @@ class AgentDeckMCPServer {
|
|
|
304
294
|
return this.toolError(error);
|
|
305
295
|
}
|
|
306
296
|
});
|
|
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
297
|
// Get all decks (metadata only; credentials stripped unless bound)
|
|
449
298
|
this.registerTool("get_decks", {
|
|
450
299
|
title: "Get Decks",
|
|
@@ -465,7 +314,7 @@ class AgentDeckMCPServer {
|
|
|
465
314
|
});
|
|
466
315
|
this.registerTool("get_bound_deck", {
|
|
467
316
|
title: "Get Bound Deck",
|
|
468
|
-
description: "Get the deck bound to this session (session deckId
|
|
317
|
+
description: "Get the deck bound to this session (session deckId or env AGENT_DECK_DECK_ID)",
|
|
469
318
|
inputSchema: {},
|
|
470
319
|
}, async () => {
|
|
471
320
|
try {
|
|
@@ -1125,7 +974,7 @@ class AgentDeckMCPServer {
|
|
|
1125
974
|
}
|
|
1126
975
|
});
|
|
1127
976
|
this.server.resource("bound_deck", "agent-deck://bound-deck", {
|
|
1128
|
-
description: "Deck bound
|
|
977
|
+
description: "Deck bound to this MCP session",
|
|
1129
978
|
mimeType: "application/json"
|
|
1130
979
|
}, async () => {
|
|
1131
980
|
try {
|
|
@@ -1244,8 +1093,9 @@ class AgentDeckMCPServer {
|
|
|
1244
1093
|
async handleMcpPost(req, res) {
|
|
1245
1094
|
const sessionIdHeader = this.getSessionIdHeader(req);
|
|
1246
1095
|
const existing = sessionIdHeader ? this.sessions.get(sessionIdHeader) : undefined;
|
|
1247
|
-
if (existing) {
|
|
1096
|
+
if (existing && sessionIdHeader) {
|
|
1248
1097
|
this.activeSessionId = sessionIdHeader;
|
|
1098
|
+
this.touchLiveDisplay(sessionIdHeader);
|
|
1249
1099
|
await existing.transport.handleRequest(req, res, req.body);
|
|
1250
1100
|
return;
|
|
1251
1101
|
}
|
|
@@ -1277,6 +1127,8 @@ class AgentDeckMCPServer {
|
|
|
1277
1127
|
if (sessionId) {
|
|
1278
1128
|
this.sessions.delete(sessionId);
|
|
1279
1129
|
this.sessionBinding.clearSession(sessionId);
|
|
1130
|
+
this.badgeBySession.delete(sessionId);
|
|
1131
|
+
this.lastTouchAtMs.delete(sessionId);
|
|
1280
1132
|
void this.unregisterLiveDisplay(sessionId);
|
|
1281
1133
|
}
|
|
1282
1134
|
};
|
|
@@ -1299,6 +1151,7 @@ class AgentDeckMCPServer {
|
|
|
1299
1151
|
return;
|
|
1300
1152
|
}
|
|
1301
1153
|
this.activeSessionId = sessionId;
|
|
1154
|
+
this.touchLiveDisplay(sessionId);
|
|
1302
1155
|
await session.transport.handleRequest(req, res);
|
|
1303
1156
|
}
|
|
1304
1157
|
async start() {
|
|
@@ -1308,12 +1161,10 @@ class AgentDeckMCPServer {
|
|
|
1308
1161
|
this.app.listen(this.port, () => {
|
|
1309
1162
|
console.log(`✅ Agent Deck MCP Server is ready to accept connections`);
|
|
1310
1163
|
console.log(`📋 Available tools:`);
|
|
1311
|
-
console.log(` - bind_workspace: Bind session to workspace
|
|
1164
|
+
console.log(` - bind_workspace: Bind session to workspace + deck (deckId required)`);
|
|
1312
1165
|
console.log(` - switch_bound_deck: Switch deck for this session only`);
|
|
1313
1166
|
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`);
|
|
1167
|
+
console.log(` - get_bound_deck: Get session-bound deck`);
|
|
1317
1168
|
console.log(` - list_service_tools: List tools for a specific service`);
|
|
1318
1169
|
console.log(` - call_service_tool: Call a tool on a service`);
|
|
1319
1170
|
console.log(`📋 Available resources:`);
|