@agent-deck/backend 1.1.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 -0
- package/dist/cli-runtime.d.ts +4 -0
- package/dist/cli-runtime.d.ts.map +1 -0
- package/dist/cli-runtime.js +12 -0
- package/dist/cli-runtime.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/agent-deck-context.d.ts +8 -0
- package/dist/lib/agent-deck-context.d.ts.map +1 -0
- package/dist/lib/agent-deck-context.js +48 -0
- package/dist/lib/agent-deck-context.js.map +1 -0
- package/dist/lib/bound-deck-scope.d.ts +15 -0
- package/dist/lib/bound-deck-scope.d.ts.map +1 -0
- package/dist/lib/bound-deck-scope.js +68 -0
- package/dist/lib/bound-deck-scope.js.map +1 -0
- package/dist/lib/client-scope.d.ts +14 -0
- package/dist/lib/client-scope.d.ts.map +1 -0
- package/dist/lib/client-scope.js +46 -0
- package/dist/lib/client-scope.js.map +1 -0
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +24 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/version.d.ts +3 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +20 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/mcp-index.d.ts +2 -0
- package/dist/mcp-index.d.ts.map +1 -0
- package/dist/mcp-index.js +32 -0
- package/dist/mcp-index.js.map +1 -0
- package/dist/mcp-server.d.ts +25 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1121 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp-stdio.d.ts +2 -0
- package/dist/mcp-stdio.d.ts.map +1 -0
- package/dist/mcp-stdio.js.map +1 -0
- package/dist/models/database.d.ts +64 -0
- package/dist/models/database.d.ts.map +1 -0
- package/dist/models/database.js +965 -0
- package/dist/models/database.js.map +1 -0
- package/dist/playbooks/playbook-manager.d.ts +29 -0
- package/dist/playbooks/playbook-manager.d.ts.map +1 -0
- package/dist/playbooks/playbook-manager.js +198 -0
- package/dist/playbooks/playbook-manager.js.map +1 -0
- package/dist/playbooks/playbook-parser.d.ts +8 -0
- package/dist/playbooks/playbook-parser.d.ts.map +1 -0
- package/dist/playbooks/playbook-parser.js +76 -0
- package/dist/playbooks/playbook-parser.js.map +1 -0
- package/dist/playbooks/playbook-service.d.ts +9 -0
- package/dist/playbooks/playbook-service.d.ts.map +1 -0
- package/dist/playbooks/playbook-service.js +107 -0
- package/dist/playbooks/playbook-service.js.map +1 -0
- package/dist/routes/collection.d.ts +3 -0
- package/dist/routes/collection.d.ts.map +1 -0
- package/dist/routes/collection.js +34 -0
- package/dist/routes/collection.js.map +1 -0
- package/dist/routes/credentials.d.ts +3 -0
- package/dist/routes/credentials.d.ts.map +1 -0
- package/dist/routes/credentials.js +241 -0
- package/dist/routes/credentials.js.map +1 -0
- package/dist/routes/decks.d.ts +3 -0
- package/dist/routes/decks.d.ts.map +1 -0
- package/dist/routes/decks.js +430 -0
- package/dist/routes/decks.js.map +1 -0
- package/dist/routes/local-mcp.d.ts +3 -0
- package/dist/routes/local-mcp.d.ts.map +1 -0
- package/dist/routes/local-mcp.js +189 -0
- package/dist/routes/local-mcp.js.map +1 -0
- package/dist/routes/mcp.d.ts +3 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +170 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/oauth.d.ts +3 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +242 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/playbooks.d.ts +5 -0
- package/dist/routes/playbooks.d.ts.map +1 -0
- package/dist/routes/playbooks.js +220 -0
- package/dist/routes/playbooks.js.map +1 -0
- package/dist/routes/scope.d.ts +3 -0
- package/dist/routes/scope.d.ts.map +1 -0
- package/dist/routes/scope.js +107 -0
- package/dist/routes/scope.js.map +1 -0
- package/dist/routes/services.d.ts +3 -0
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +281 -0
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/websocket.d.ts +11 -0
- package/dist/routes/websocket.d.ts.map +1 -0
- package/dist/routes/websocket.js +154 -0
- package/dist/routes/websocket.js.map +1 -0
- package/dist/scope/repo-deck.d.ts +10 -0
- package/dist/scope/repo-deck.d.ts.map +1 -0
- package/dist/scope/repo-deck.js +63 -0
- package/dist/scope/repo-deck.js.map +1 -0
- package/dist/server/index.d.ts +24 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +111 -0
- package/dist/server/index.js.map +1 -0
- package/dist/services/collection-warning-service.d.ts +18 -0
- package/dist/services/collection-warning-service.d.ts.map +1 -0
- package/dist/services/collection-warning-service.js +129 -0
- package/dist/services/collection-warning-service.js.map +1 -0
- package/dist/services/config-manager.d.ts +32 -0
- package/dist/services/config-manager.d.ts.map +1 -0
- package/dist/services/config-manager.js +119 -0
- package/dist/services/config-manager.js.map +1 -0
- package/dist/services/icon-resolver.d.ts +20 -0
- package/dist/services/icon-resolver.d.ts.map +1 -0
- package/dist/services/icon-resolver.js +224 -0
- package/dist/services/icon-resolver.js.map +1 -0
- package/dist/services/local-mcp-server-manager.d.ts +51 -0
- package/dist/services/local-mcp-server-manager.d.ts.map +1 -0
- package/dist/services/local-mcp-server-manager.js +246 -0
- package/dist/services/local-mcp-server-manager.js.map +1 -0
- package/dist/services/mcp-client-manager.d.ts +22 -0
- package/dist/services/mcp-client-manager.d.ts.map +1 -0
- package/dist/services/mcp-client-manager.js +257 -0
- package/dist/services/mcp-client-manager.js.map +1 -0
- package/dist/services/mcp-discovery-service.d.ts +31 -0
- package/dist/services/mcp-discovery-service.d.ts.map +1 -0
- package/dist/services/mcp-discovery-service.js +164 -0
- package/dist/services/mcp-discovery-service.js.map +1 -0
- package/dist/services/oauth-manager.d.ts +25 -0
- package/dist/services/oauth-manager.d.ts.map +1 -0
- package/dist/services/oauth-manager.js +365 -0
- package/dist/services/oauth-manager.js.map +1 -0
- package/dist/services/service-manager.d.ts +61 -0
- package/dist/services/service-manager.d.ts.map +1 -0
- package/dist/services/service-manager.js +447 -0
- package/dist/services/service-manager.js.map +1 -0
- package/dist/test-local-mcp-e2e.d.ts +3 -0
- package/dist/test-local-mcp-e2e.d.ts.map +1 -0
- package/dist/test-local-mcp-e2e.js +104 -0
- package/dist/test-local-mcp-e2e.js.map +1 -0
- package/dist/test-local-mcp.d.ts +3 -0
- package/dist/test-local-mcp.d.ts.map +1 -0
- package/dist/test-local-mcp.js +54 -0
- package/dist/test-local-mcp.js.map +1 -0
- package/dist/vault/credential-manager.d.ts +45 -0
- package/dist/vault/credential-manager.d.ts.map +1 -0
- package/dist/vault/credential-manager.js +237 -0
- package/dist/vault/credential-manager.js.map +1 -0
- package/dist/vault/index.d.ts +4 -0
- package/dist/vault/index.d.ts.map +1 -0
- package/dist/vault/index.js +20 -0
- package/dist/vault/index.js.map +1 -0
- package/dist/vault/secret-store.d.ts +36 -0
- package/dist/vault/secret-store.d.ts.map +1 -0
- package/dist/vault/secret-store.js +207 -0
- package/dist/vault/secret-store.js.map +1 -0
- package/dist/vault/yaml-sync.d.ts +8 -0
- package/dist/vault/yaml-sync.d.ts.map +1 -0
- package/dist/vault/yaml-sync.js +60 -0
- package/dist/vault/yaml-sync.js.map +1 -0
- package/package.json +64 -0
- package/static-ui/assets/AgentDeckLogo2-z3pVqJJ3.png +0 -0
- package/static-ui/assets/index-BnA3AsqY.css +1 -0
- package/static-ui/assets/index-D1IuraRt.js +334 -0
- package/static-ui/favicon.png +0 -0
- package/static-ui/index.html +17 -0
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AgentDeckMCPServer = void 0;
|
|
7
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
9
|
+
const shared_1 = require("@agent-deck/shared");
|
|
10
|
+
const express_1 = __importDefault(require("express"));
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const repo_deck_1 = require("./scope/repo-deck");
|
|
16
|
+
const shared_2 = require("@agent-deck/shared");
|
|
17
|
+
const version_1 = require("./lib/version");
|
|
18
|
+
const agentClientHeaders = {
|
|
19
|
+
[shared_1.AGENT_DECK_CLIENT_HEADER]: shared_1.AGENT_DECK_AGENT_CLIENT,
|
|
20
|
+
Accept: 'application/json',
|
|
21
|
+
};
|
|
22
|
+
class AgentDeckMCPServer {
|
|
23
|
+
port;
|
|
24
|
+
server;
|
|
25
|
+
transport;
|
|
26
|
+
app;
|
|
27
|
+
backendUrl;
|
|
28
|
+
/** Workspace root per MCP session → resolves `.agent-deck/deck.yaml`. */
|
|
29
|
+
workspaceBySession = new Map();
|
|
30
|
+
activeSessionId;
|
|
31
|
+
constructor(port = 3001, backendUrl = 'http://localhost:8000') {
|
|
32
|
+
this.port = port;
|
|
33
|
+
this.backendUrl = backendUrl;
|
|
34
|
+
// Create Express app
|
|
35
|
+
this.app = (0, express_1.default)();
|
|
36
|
+
this.app.use(express_1.default.json());
|
|
37
|
+
// Create MCP server with official SDK
|
|
38
|
+
this.server = new mcp_js_1.McpServer({
|
|
39
|
+
name: "agent-deck-server",
|
|
40
|
+
version: (0, version_1.getAgentDeckVersion)(),
|
|
41
|
+
});
|
|
42
|
+
// Create HTTP transport
|
|
43
|
+
this.transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
44
|
+
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
|
|
45
|
+
enableJsonResponse: true
|
|
46
|
+
});
|
|
47
|
+
this.setupTools();
|
|
48
|
+
this.setupResources();
|
|
49
|
+
this.setupRoutes();
|
|
50
|
+
const envWorkspace = process.env.AGENT_DECK_WORKSPACE?.trim();
|
|
51
|
+
if (envWorkspace) {
|
|
52
|
+
this.workspaceBySession.set('default', envWorkspace);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
getSessionId() {
|
|
56
|
+
return this.activeSessionId ?? 'default';
|
|
57
|
+
}
|
|
58
|
+
getBoundWorkspace() {
|
|
59
|
+
const sessionId = this.getSessionId();
|
|
60
|
+
return this.workspaceBySession.get(sessionId) ?? process.env.AGENT_DECK_WORKSPACE?.trim();
|
|
61
|
+
}
|
|
62
|
+
getAgentHeaders() {
|
|
63
|
+
const workspace = this.getBoundWorkspace();
|
|
64
|
+
return {
|
|
65
|
+
...agentClientHeaders,
|
|
66
|
+
...(workspace ? { [shared_1.AGENT_DECK_WORKSPACE_HEADER]: workspace } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async callBackendAPI(endpoint, init = {}) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(`${this.backendUrl}${endpoint}`, {
|
|
72
|
+
...init,
|
|
73
|
+
headers: {
|
|
74
|
+
...this.getAgentHeaders(),
|
|
75
|
+
...(init.headers ?? {}),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const text = await response.text();
|
|
80
|
+
let message = `Backend API error: ${response.status} ${response.statusText}`;
|
|
81
|
+
try {
|
|
82
|
+
const body = JSON.parse(text);
|
|
83
|
+
if (body.error)
|
|
84
|
+
message = String(body.error);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
if (text.trim())
|
|
88
|
+
message = text;
|
|
89
|
+
}
|
|
90
|
+
throw new Error(message);
|
|
91
|
+
}
|
|
92
|
+
const body = await response.json();
|
|
93
|
+
// Unwrap ApiResponse shape { success, data?, error? }
|
|
94
|
+
if (typeof body === 'object' && body !== null && 'success' in body) {
|
|
95
|
+
if (body.success) {
|
|
96
|
+
// Some endpoints return the raw data (legacy); fallback to body if data missing
|
|
97
|
+
return 'data' in body ? body.data : body;
|
|
98
|
+
}
|
|
99
|
+
const message = 'error' in body ? body.error : 'Unknown backend error';
|
|
100
|
+
throw new Error(String(message));
|
|
101
|
+
}
|
|
102
|
+
// Fallback: return as-is if not wrapped
|
|
103
|
+
return body;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(`Failed to call backend API ${endpoint}:`, error);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async getBoundDeckId() {
|
|
111
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
112
|
+
if (!deck?.id) {
|
|
113
|
+
throw new Error('No bound deck — call bind_workspace or setup_repo_deck first');
|
|
114
|
+
}
|
|
115
|
+
return deck.id;
|
|
116
|
+
}
|
|
117
|
+
toolResult(data) {
|
|
118
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
119
|
+
}
|
|
120
|
+
toolError(error) {
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
122
|
+
}
|
|
123
|
+
setupTools() {
|
|
124
|
+
this.server.registerTool("bind_workspace", {
|
|
125
|
+
title: "Bind Workspace",
|
|
126
|
+
description: "Bind an MCP session to a repo workspace root. Reads .agent-deck/deck.yaml to resolve the deck for this project.",
|
|
127
|
+
inputSchema: { workspaceRoot: zod_1.z.string() },
|
|
128
|
+
}, async ({ workspaceRoot }) => {
|
|
129
|
+
try {
|
|
130
|
+
const sessionId = this.getSessionId();
|
|
131
|
+
this.workspaceBySession.set(sessionId, workspaceRoot);
|
|
132
|
+
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
135
|
+
body: JSON.stringify({ workspaceRoot }),
|
|
136
|
+
});
|
|
137
|
+
const body = (await resolved.json());
|
|
138
|
+
if (!body.success || !body.data) {
|
|
139
|
+
throw new Error(body.error ?? 'Failed to resolve workspace deck');
|
|
140
|
+
}
|
|
141
|
+
const { data } = body;
|
|
142
|
+
return {
|
|
143
|
+
content: [{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: JSON.stringify({
|
|
146
|
+
workspaceRoot,
|
|
147
|
+
manifestPath: data.manifestPath,
|
|
148
|
+
deck_id: data.manifest.deck_id,
|
|
149
|
+
deck_name: data.deck.name,
|
|
150
|
+
}, null, 2),
|
|
151
|
+
}],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const message = String(error);
|
|
156
|
+
const hint = message.includes('deck.yaml')
|
|
157
|
+
? ' Call setup_repo_deck with workspaceRoot and deckId to create .agent-deck/deck.yaml, or get_repo_deck_status to diagnose.'
|
|
158
|
+
: '';
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: "text", text: JSON.stringify({ error: message + hint }, null, 2) }],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
this.server.registerTool("get_repo_deck_status", {
|
|
165
|
+
title: "Get Repo Deck Status",
|
|
166
|
+
description: "Check whether .agent-deck/deck.yaml exists in a workspace and whether it links to a valid deck",
|
|
167
|
+
inputSchema: { workspaceRoot: zod_1.z.string() },
|
|
168
|
+
}, async ({ workspaceRoot }) => {
|
|
169
|
+
try {
|
|
170
|
+
const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
|
|
171
|
+
const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
172
|
+
if (!manifest) {
|
|
173
|
+
const decks = await this.callBackendAPI('/api/decks');
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify({
|
|
178
|
+
status: 'missing',
|
|
179
|
+
workspaceRoot,
|
|
180
|
+
expectedPath: shared_2.REPO_DECK_MANIFEST_PATH,
|
|
181
|
+
message: 'No .agent-deck/deck.yaml found. Use setup_repo_deck to create one.',
|
|
182
|
+
availableDecks: Array.isArray(decks)
|
|
183
|
+
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
184
|
+
: decks,
|
|
185
|
+
}, null, 2),
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
192
|
+
body: JSON.stringify({ workspaceRoot }),
|
|
193
|
+
});
|
|
194
|
+
const body = (await resolved.json());
|
|
195
|
+
return {
|
|
196
|
+
content: [{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: JSON.stringify({
|
|
199
|
+
status: body.success ? 'ok' : 'invalid',
|
|
200
|
+
workspaceRoot,
|
|
201
|
+
filePath,
|
|
202
|
+
manifest,
|
|
203
|
+
deck: body.data?.deck,
|
|
204
|
+
error: body.success ? undefined : body.error,
|
|
205
|
+
}, null, 2),
|
|
206
|
+
}],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
this.server.registerTool("setup_repo_deck", {
|
|
216
|
+
title: "Setup Repo Deck",
|
|
217
|
+
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.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
workspaceRoot: zod_1.z.string(),
|
|
220
|
+
deckId: zod_1.z.string().uuid().optional(),
|
|
221
|
+
deckName: zod_1.z.string().optional(),
|
|
222
|
+
writeFile: zod_1.z.boolean().optional(),
|
|
223
|
+
},
|
|
224
|
+
}, async ({ workspaceRoot, deckId, deckName, writeFile = true }) => {
|
|
225
|
+
try {
|
|
226
|
+
const sessionId = this.getSessionId();
|
|
227
|
+
const existing = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
228
|
+
if (existing) {
|
|
229
|
+
this.workspaceBySession.set(sessionId, workspaceRoot);
|
|
230
|
+
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
233
|
+
body: JSON.stringify({ workspaceRoot }),
|
|
234
|
+
});
|
|
235
|
+
const body = (await resolved.json());
|
|
236
|
+
if (!body.success || !body.data) {
|
|
237
|
+
throw new Error(body.error ?? 'Existing manifest is invalid');
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify({
|
|
243
|
+
status: 'existing',
|
|
244
|
+
workspaceRoot,
|
|
245
|
+
manifestPath: body.data.manifestPath,
|
|
246
|
+
deck_id: body.data.manifest.deck_id,
|
|
247
|
+
deck_name: body.data.deck.name,
|
|
248
|
+
message: 'Workspace already linked. Session bound to this deck.',
|
|
249
|
+
}, null, 2),
|
|
250
|
+
}],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (!deckId) {
|
|
254
|
+
const decks = await this.callBackendAPI('/api/decks');
|
|
255
|
+
return {
|
|
256
|
+
content: [{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: JSON.stringify({
|
|
259
|
+
status: 'needs_deck_id',
|
|
260
|
+
workspaceRoot,
|
|
261
|
+
expectedPath: shared_2.REPO_DECK_MANIFEST_PATH,
|
|
262
|
+
message: 'Pick a deck id from the dashboard (My Decks → copy icon) and call setup_repo_deck again with deckId.',
|
|
263
|
+
availableDecks: Array.isArray(decks)
|
|
264
|
+
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
265
|
+
: decks,
|
|
266
|
+
}, null, 2),
|
|
267
|
+
}],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const deck = await this.callBackendAPI(`/api/decks/${deckId}`);
|
|
271
|
+
if (!deck?.id) {
|
|
272
|
+
throw new Error(`Deck not found: ${deckId}`);
|
|
273
|
+
}
|
|
274
|
+
const content = (0, repo_deck_1.formatRepoDeckManifest)(deckId, deckName ?? deck.name);
|
|
275
|
+
const dirPath = node_path_1.default.join(node_path_1.default.resolve(workspaceRoot), '.agent-deck');
|
|
276
|
+
const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
|
|
277
|
+
if (writeFile) {
|
|
278
|
+
await promises_1.default.mkdir(dirPath, { recursive: true });
|
|
279
|
+
await promises_1.default.writeFile(filePath, content, 'utf8');
|
|
280
|
+
}
|
|
281
|
+
this.workspaceBySession.set(sessionId, workspaceRoot);
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: JSON.stringify({
|
|
286
|
+
status: writeFile ? 'created' : 'preview',
|
|
287
|
+
workspaceRoot,
|
|
288
|
+
filePath,
|
|
289
|
+
deck_id: deckId,
|
|
290
|
+
deck_name: deck.name,
|
|
291
|
+
manifestContent: content,
|
|
292
|
+
message: writeFile
|
|
293
|
+
? 'Created .agent-deck/deck.yaml and bound this session to the deck.'
|
|
294
|
+
: 'Preview only — set writeFile true to write the file.',
|
|
295
|
+
}, null, 2),
|
|
296
|
+
}],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// Get all decks (metadata only; credentials stripped unless bound)
|
|
306
|
+
this.server.registerTool("get_decks", {
|
|
307
|
+
title: "Get Decks",
|
|
308
|
+
description: "Get all available decks from Agent Deck",
|
|
309
|
+
inputSchema: {}
|
|
310
|
+
}, async () => {
|
|
311
|
+
try {
|
|
312
|
+
const decks = await this.callBackendAPI('/api/decks');
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: "text", text: JSON.stringify(decks, null, 2) }]
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }]
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
this.server.registerTool("get_bound_deck", {
|
|
324
|
+
title: "Get Bound Deck",
|
|
325
|
+
description: "Get the deck bound to this session via bind_workspace or AGENT_DECK_WORKSPACE (.agent-deck/deck.yaml)",
|
|
326
|
+
inputSchema: {},
|
|
327
|
+
}, async () => {
|
|
328
|
+
try {
|
|
329
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: "text", text: JSON.stringify(deck, null, 2) }],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// Deprecated alias
|
|
341
|
+
this.server.registerTool("get_active_deck", {
|
|
342
|
+
title: "Get Active Deck (deprecated)",
|
|
343
|
+
description: "Deprecated — use get_bound_deck. Returns the workspace-bound deck.",
|
|
344
|
+
inputSchema: {},
|
|
345
|
+
}, async () => {
|
|
346
|
+
try {
|
|
347
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
348
|
+
return {
|
|
349
|
+
content: [{ type: "text", text: JSON.stringify(deck, null, 2) }],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
this.server.registerTool("list_bound_deck_services", {
|
|
359
|
+
title: "List Bound Deck Services",
|
|
360
|
+
description: "List MCP services on the workspace-bound deck",
|
|
361
|
+
inputSchema: {},
|
|
362
|
+
}, async () => {
|
|
363
|
+
try {
|
|
364
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
365
|
+
const services = deck?.services ?? [];
|
|
366
|
+
return { content: [{ type: "text", text: JSON.stringify(services, null, 2) }] };
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
this.server.registerTool("list_active_deck_services", {
|
|
373
|
+
title: "List Active Deck Services (deprecated)",
|
|
374
|
+
description: "Deprecated — use list_bound_deck_services",
|
|
375
|
+
inputSchema: {},
|
|
376
|
+
}, async () => {
|
|
377
|
+
try {
|
|
378
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
379
|
+
const services = deck?.services ?? [];
|
|
380
|
+
return { content: [{ type: "text", text: JSON.stringify(services, null, 2) }] };
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
this.server.registerTool("list_bound_deck_credentials", {
|
|
387
|
+
title: "List Bound Deck Credentials",
|
|
388
|
+
description: "List API key metadata on the workspace-bound deck",
|
|
389
|
+
inputSchema: {},
|
|
390
|
+
}, async () => {
|
|
391
|
+
try {
|
|
392
|
+
const credentials = await this.callBackendAPI('/api/credentials');
|
|
393
|
+
return { content: [{ type: "text", text: JSON.stringify(credentials, null, 2) }] };
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
this.server.registerTool("list_active_deck_credentials", {
|
|
400
|
+
title: "List Active Deck Credentials (deprecated)",
|
|
401
|
+
description: "Deprecated — use list_bound_deck_credentials",
|
|
402
|
+
inputSchema: {},
|
|
403
|
+
}, async () => {
|
|
404
|
+
try {
|
|
405
|
+
const credentials = await this.callBackendAPI('/api/credentials');
|
|
406
|
+
return { content: [{ type: "text", text: JSON.stringify(credentials, null, 2) }] };
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
this.server.registerTool("list_playbooks", {
|
|
413
|
+
title: "List Playbooks",
|
|
414
|
+
description: "List playbook cards on the bound deck (id, title, triggers)",
|
|
415
|
+
inputSchema: {},
|
|
416
|
+
}, async () => {
|
|
417
|
+
try {
|
|
418
|
+
const playbooks = await this.callBackendAPI('/api/playbooks/summaries');
|
|
419
|
+
return { content: [{ type: "text", text: JSON.stringify(playbooks, null, 2) }] };
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
this.server.registerTool("get_playbook", {
|
|
426
|
+
title: "Get Playbook",
|
|
427
|
+
description: "Get full markdown body, metadata, and dependencies for a playbook card by id",
|
|
428
|
+
inputSchema: { playbook_id: zod_1.z.string() },
|
|
429
|
+
}, async ({ playbook_id }) => {
|
|
430
|
+
try {
|
|
431
|
+
const playbook = await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`);
|
|
432
|
+
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
this.server.registerTool("register_playbook", {
|
|
439
|
+
title: "Register Playbook",
|
|
440
|
+
description: "Create a playbook card, auto-detect API key and MCP dependencies from the content, and add it to the bound deck by default",
|
|
441
|
+
inputSchema: {
|
|
442
|
+
title: zod_1.z.string(),
|
|
443
|
+
body: zod_1.z.string(),
|
|
444
|
+
triggers: zod_1.z.array(zod_1.z.string()).optional(),
|
|
445
|
+
playbook_id: zod_1.z.string().optional(),
|
|
446
|
+
exec: zod_1.z.string().optional(),
|
|
447
|
+
skill: zod_1.z.string().optional(),
|
|
448
|
+
depends_on_credential_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
449
|
+
depends_on_service_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
450
|
+
add_to_bound_deck: zod_1.z.boolean().optional(),
|
|
451
|
+
auto_detect_dependencies: zod_1.z.boolean().optional(),
|
|
452
|
+
},
|
|
453
|
+
}, async (args) => {
|
|
454
|
+
try {
|
|
455
|
+
const playbook = await this.callBackendAPI("/api/playbooks", {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: { "Content-Type": "application/json" },
|
|
458
|
+
body: JSON.stringify({
|
|
459
|
+
title: args.title,
|
|
460
|
+
body: args.body,
|
|
461
|
+
triggers: args.triggers,
|
|
462
|
+
id: args.playbook_id,
|
|
463
|
+
exec: args.exec,
|
|
464
|
+
skill: args.skill,
|
|
465
|
+
dependsOnCredentialIds: args.depends_on_credential_ids,
|
|
466
|
+
dependsOnServiceIds: args.depends_on_service_ids,
|
|
467
|
+
addToBoundDeck: args.add_to_bound_deck,
|
|
468
|
+
autoDetectDependencies: args.auto_detect_dependencies,
|
|
469
|
+
}),
|
|
470
|
+
});
|
|
471
|
+
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
this.server.registerTool("update_playbook", {
|
|
478
|
+
title: "Update Playbook",
|
|
479
|
+
description: "Update a playbook on the bound deck. Dependencies are re-detected from the updated content by default.",
|
|
480
|
+
inputSchema: {
|
|
481
|
+
playbook_id: zod_1.z.string(),
|
|
482
|
+
title: zod_1.z.string().optional(),
|
|
483
|
+
body: zod_1.z.string().optional(),
|
|
484
|
+
triggers: zod_1.z.array(zod_1.z.string()).optional(),
|
|
485
|
+
exec: zod_1.z.string().optional(),
|
|
486
|
+
skill: zod_1.z.string().optional(),
|
|
487
|
+
depends_on_credential_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
488
|
+
depends_on_service_ids: zod_1.z.array(zod_1.z.string()).optional(),
|
|
489
|
+
auto_detect_dependencies: zod_1.z.boolean().optional(),
|
|
490
|
+
},
|
|
491
|
+
}, async ({ playbook_id, ...args }) => {
|
|
492
|
+
try {
|
|
493
|
+
const playbook = await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`, {
|
|
494
|
+
method: "PUT",
|
|
495
|
+
headers: { "Content-Type": "application/json" },
|
|
496
|
+
body: JSON.stringify({
|
|
497
|
+
title: args.title,
|
|
498
|
+
body: args.body,
|
|
499
|
+
triggers: args.triggers,
|
|
500
|
+
exec: args.exec,
|
|
501
|
+
skill: args.skill,
|
|
502
|
+
dependsOnCredentialIds: args.depends_on_credential_ids,
|
|
503
|
+
dependsOnServiceIds: args.depends_on_service_ids,
|
|
504
|
+
autoDetectDependencies: args.auto_detect_dependencies,
|
|
505
|
+
}),
|
|
506
|
+
});
|
|
507
|
+
return { content: [{ type: "text", text: JSON.stringify(playbook, null, 2) }] };
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
this.server.registerTool("list_collection_services", {
|
|
514
|
+
title: "List Collection Services",
|
|
515
|
+
description: "List all registered MCP services in the collection (metadata only)",
|
|
516
|
+
inputSchema: {},
|
|
517
|
+
}, async () => {
|
|
518
|
+
try {
|
|
519
|
+
const services = await this.callBackendAPI('/api/services');
|
|
520
|
+
return this.toolResult(services);
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
return this.toolError(error);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
this.server.registerTool("register_service", {
|
|
527
|
+
title: "Register Service",
|
|
528
|
+
description: "Register an MCP service in the collection. Optionally add it to the bound deck. OAuth setup still requires the dashboard browser flow.",
|
|
529
|
+
inputSchema: {
|
|
530
|
+
name: zod_1.z.string(),
|
|
531
|
+
type: zod_1.z.enum(['mcp', 'a2a', 'local-mcp']),
|
|
532
|
+
url: zod_1.z.string(),
|
|
533
|
+
description: zod_1.z.string().optional(),
|
|
534
|
+
cardColor: zod_1.z.string().optional(),
|
|
535
|
+
credentialId: zod_1.z.string().optional(),
|
|
536
|
+
headers: zod_1.z.record(zod_1.z.string()).optional(),
|
|
537
|
+
localCommand: zod_1.z.string().optional(),
|
|
538
|
+
localArgs: zod_1.z.array(zod_1.z.string()).optional(),
|
|
539
|
+
localWorkingDir: zod_1.z.string().optional(),
|
|
540
|
+
localEnv: zod_1.z.record(zod_1.z.string()).optional(),
|
|
541
|
+
add_to_bound_deck: zod_1.z.boolean().optional(),
|
|
542
|
+
position: zod_1.z.number().optional(),
|
|
543
|
+
},
|
|
544
|
+
}, async (args) => {
|
|
545
|
+
try {
|
|
546
|
+
const { add_to_bound_deck, position, ...serviceInput } = args;
|
|
547
|
+
const service = await this.callBackendAPI('/api/services', {
|
|
548
|
+
method: 'POST',
|
|
549
|
+
headers: { 'Content-Type': 'application/json' },
|
|
550
|
+
body: JSON.stringify(serviceInput),
|
|
551
|
+
});
|
|
552
|
+
if (add_to_bound_deck !== false) {
|
|
553
|
+
const deckId = await this.getBoundDeckId();
|
|
554
|
+
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
555
|
+
method: 'POST',
|
|
556
|
+
headers: { 'Content-Type': 'application/json' },
|
|
557
|
+
body: JSON.stringify({ serviceId: service.id, position }),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return this.toolResult(service);
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
return this.toolError(error);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
this.server.registerTool("update_service", {
|
|
567
|
+
title: "Update Service",
|
|
568
|
+
description: "Update MCP service metadata in the collection (not OAuth tokens — use dashboard for OAuth)",
|
|
569
|
+
inputSchema: {
|
|
570
|
+
service_id: zod_1.z.string(),
|
|
571
|
+
name: zod_1.z.string().optional(),
|
|
572
|
+
type: zod_1.z.enum(['mcp', 'a2a', 'local-mcp']).optional(),
|
|
573
|
+
url: zod_1.z.string().optional(),
|
|
574
|
+
description: zod_1.z.string().optional(),
|
|
575
|
+
cardColor: zod_1.z.string().optional(),
|
|
576
|
+
credentialId: zod_1.z.string().optional(),
|
|
577
|
+
headers: zod_1.z.record(zod_1.z.string()).optional(),
|
|
578
|
+
localCommand: zod_1.z.string().optional(),
|
|
579
|
+
localArgs: zod_1.z.array(zod_1.z.string()).optional(),
|
|
580
|
+
localWorkingDir: zod_1.z.string().optional(),
|
|
581
|
+
localEnv: zod_1.z.record(zod_1.z.string()).optional(),
|
|
582
|
+
},
|
|
583
|
+
}, async ({ service_id, ...updates }) => {
|
|
584
|
+
try {
|
|
585
|
+
const service = await this.callBackendAPI(`/api/services/${service_id}`, {
|
|
586
|
+
method: 'PUT',
|
|
587
|
+
headers: { 'Content-Type': 'application/json' },
|
|
588
|
+
body: JSON.stringify(updates),
|
|
589
|
+
});
|
|
590
|
+
return this.toolResult(service);
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
return this.toolError(error);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
this.server.registerTool("delete_service", {
|
|
597
|
+
title: "Delete Service",
|
|
598
|
+
description: "Remove an MCP service from the collection entirely",
|
|
599
|
+
inputSchema: { service_id: zod_1.z.string() },
|
|
600
|
+
}, async ({ service_id }) => {
|
|
601
|
+
try {
|
|
602
|
+
await this.callBackendAPI(`/api/services/${service_id}`, { method: 'DELETE' });
|
|
603
|
+
return this.toolResult({ success: true, service_id });
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
return this.toolError(error);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
this.server.registerTool("add_service_to_bound_deck", {
|
|
610
|
+
title: "Add Service To Bound Deck",
|
|
611
|
+
description: "Link an existing MCP service from the collection onto the bound deck",
|
|
612
|
+
inputSchema: {
|
|
613
|
+
service_id: zod_1.z.string(),
|
|
614
|
+
position: zod_1.z.number().optional(),
|
|
615
|
+
},
|
|
616
|
+
}, async ({ service_id, position }) => {
|
|
617
|
+
try {
|
|
618
|
+
const deckId = await this.getBoundDeckId();
|
|
619
|
+
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
620
|
+
method: 'POST',
|
|
621
|
+
headers: { 'Content-Type': 'application/json' },
|
|
622
|
+
body: JSON.stringify({ serviceId: service_id, position }),
|
|
623
|
+
});
|
|
624
|
+
return this.toolResult({ success: true, deck_id: deckId, service_id });
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
return this.toolError(error);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
this.server.registerTool("remove_service_from_bound_deck", {
|
|
631
|
+
title: "Remove Service From Bound Deck",
|
|
632
|
+
description: "Unlink an MCP service from the bound deck (service stays in the collection)",
|
|
633
|
+
inputSchema: { service_id: zod_1.z.string() },
|
|
634
|
+
}, async ({ service_id }) => {
|
|
635
|
+
try {
|
|
636
|
+
const deckId = await this.getBoundDeckId();
|
|
637
|
+
await this.callBackendAPI(`/api/decks/${deckId}/services`, {
|
|
638
|
+
method: 'DELETE',
|
|
639
|
+
headers: { 'Content-Type': 'application/json' },
|
|
640
|
+
body: JSON.stringify({ serviceId: service_id }),
|
|
641
|
+
});
|
|
642
|
+
return this.toolResult({ success: true, deck_id: deckId, service_id });
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
return this.toolError(error);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
this.server.registerTool("update_service_tool_settings", {
|
|
649
|
+
title: "Update Service Tool Settings",
|
|
650
|
+
description: "Enable or disable individual tools for an MCP service on the bound deck",
|
|
651
|
+
inputSchema: {
|
|
652
|
+
service_id: zod_1.z.string(),
|
|
653
|
+
disabled_tools: zod_1.z.array(zod_1.z.string()),
|
|
654
|
+
},
|
|
655
|
+
}, async ({ service_id, disabled_tools }) => {
|
|
656
|
+
try {
|
|
657
|
+
const service = await this.callBackendAPI(`/api/services/${service_id}/tool-settings`, {
|
|
658
|
+
method: 'PUT',
|
|
659
|
+
headers: { 'Content-Type': 'application/json' },
|
|
660
|
+
body: JSON.stringify({ disabledTools: disabled_tools }),
|
|
661
|
+
});
|
|
662
|
+
return this.toolResult(service);
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
return this.toolError(error);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
this.server.registerTool("list_collection_credentials", {
|
|
669
|
+
title: "List Collection Credentials",
|
|
670
|
+
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.",
|
|
671
|
+
inputSchema: {},
|
|
672
|
+
}, async () => {
|
|
673
|
+
try {
|
|
674
|
+
const credentials = await this.callBackendAPI('/api/credentials/collection');
|
|
675
|
+
return this.toolResult(credentials);
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
return this.toolError(error);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
this.server.registerTool("add_credential_to_bound_deck", {
|
|
682
|
+
title: "Add Credential To Bound Deck",
|
|
683
|
+
description: "Link an existing API key (by credential id) to the bound deck. The secret must already be stored via the dashboard or CLI.",
|
|
684
|
+
inputSchema: {
|
|
685
|
+
credential_id: zod_1.z.string(),
|
|
686
|
+
position: zod_1.z.number().optional(),
|
|
687
|
+
},
|
|
688
|
+
}, async ({ credential_id, position }) => {
|
|
689
|
+
try {
|
|
690
|
+
const deckId = await this.getBoundDeckId();
|
|
691
|
+
await this.callBackendAPI(`/api/decks/${deckId}/credentials`, {
|
|
692
|
+
method: 'POST',
|
|
693
|
+
headers: { 'Content-Type': 'application/json' },
|
|
694
|
+
body: JSON.stringify({ credentialId: credential_id, position }),
|
|
695
|
+
});
|
|
696
|
+
return this.toolResult({ success: true, deck_id: deckId, credential_id });
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
return this.toolError(error);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
this.server.registerTool("remove_credential_from_bound_deck", {
|
|
703
|
+
title: "Remove Credential From Bound Deck",
|
|
704
|
+
description: "Unlink an API key from the bound deck (credential stays in the vault)",
|
|
705
|
+
inputSchema: { credential_id: zod_1.z.string() },
|
|
706
|
+
}, async ({ credential_id }) => {
|
|
707
|
+
try {
|
|
708
|
+
const deckId = await this.getBoundDeckId();
|
|
709
|
+
await this.callBackendAPI(`/api/decks/${deckId}/credentials`, {
|
|
710
|
+
method: 'DELETE',
|
|
711
|
+
headers: { 'Content-Type': 'application/json' },
|
|
712
|
+
body: JSON.stringify({ credentialId: credential_id }),
|
|
713
|
+
});
|
|
714
|
+
return this.toolResult({ success: true, deck_id: deckId, credential_id });
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
return this.toolError(error);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
this.server.registerTool("list_collection_playbooks", {
|
|
721
|
+
title: "List Collection Playbooks",
|
|
722
|
+
description: "List all playbook cards in the collection (for linking existing playbooks to the bound deck)",
|
|
723
|
+
inputSchema: {},
|
|
724
|
+
}, async () => {
|
|
725
|
+
try {
|
|
726
|
+
const playbooks = await this.callBackendAPI('/api/playbooks/collection');
|
|
727
|
+
return this.toolResult(playbooks);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
return this.toolError(error);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
this.server.registerTool("add_playbook_to_bound_deck", {
|
|
734
|
+
title: "Add Playbook To Bound Deck",
|
|
735
|
+
description: "Link an existing playbook card from the collection onto the bound deck",
|
|
736
|
+
inputSchema: {
|
|
737
|
+
playbook_id: zod_1.z.string(),
|
|
738
|
+
position: zod_1.z.number().optional(),
|
|
739
|
+
},
|
|
740
|
+
}, async ({ playbook_id, position }) => {
|
|
741
|
+
try {
|
|
742
|
+
const deckId = await this.getBoundDeckId();
|
|
743
|
+
await this.callBackendAPI(`/api/decks/${deckId}/playbooks`, {
|
|
744
|
+
method: 'POST',
|
|
745
|
+
headers: { 'Content-Type': 'application/json' },
|
|
746
|
+
body: JSON.stringify({ playbookId: playbook_id, position }),
|
|
747
|
+
});
|
|
748
|
+
return this.toolResult({ success: true, deck_id: deckId, playbook_id });
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
return this.toolError(error);
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
this.server.registerTool("remove_playbook_from_bound_deck", {
|
|
755
|
+
title: "Remove Playbook From Bound Deck",
|
|
756
|
+
description: "Unlink a playbook from the bound deck (playbook stays in the collection)",
|
|
757
|
+
inputSchema: { playbook_id: zod_1.z.string() },
|
|
758
|
+
}, async ({ playbook_id }) => {
|
|
759
|
+
try {
|
|
760
|
+
const deckId = await this.getBoundDeckId();
|
|
761
|
+
await this.callBackendAPI(`/api/decks/${deckId}/playbooks`, {
|
|
762
|
+
method: 'DELETE',
|
|
763
|
+
headers: { 'Content-Type': 'application/json' },
|
|
764
|
+
body: JSON.stringify({ playbookId: playbook_id }),
|
|
765
|
+
});
|
|
766
|
+
return this.toolResult({ success: true, deck_id: deckId, playbook_id });
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
return this.toolError(error);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
this.server.registerTool("delete_playbook", {
|
|
773
|
+
title: "Delete Playbook",
|
|
774
|
+
description: "Delete a playbook from the collection (must be on the bound deck for agent clients)",
|
|
775
|
+
inputSchema: { playbook_id: zod_1.z.string() },
|
|
776
|
+
}, async ({ playbook_id }) => {
|
|
777
|
+
try {
|
|
778
|
+
await this.callBackendAPI(`/api/playbooks/${encodeURIComponent(playbook_id)}`, {
|
|
779
|
+
method: 'DELETE',
|
|
780
|
+
});
|
|
781
|
+
return this.toolResult({ success: true, playbook_id });
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
return this.toolError(error);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
this.server.registerTool("create_deck", {
|
|
788
|
+
title: "Create Deck",
|
|
789
|
+
description: "Create a new deck in Agent Deck",
|
|
790
|
+
inputSchema: {
|
|
791
|
+
name: zod_1.z.string(),
|
|
792
|
+
description: zod_1.z.string().optional(),
|
|
793
|
+
},
|
|
794
|
+
}, async ({ name, description }) => {
|
|
795
|
+
try {
|
|
796
|
+
const deck = await this.callBackendAPI('/api/decks', {
|
|
797
|
+
method: 'POST',
|
|
798
|
+
headers: { 'Content-Type': 'application/json' },
|
|
799
|
+
body: JSON.stringify({ name, description }),
|
|
800
|
+
});
|
|
801
|
+
return this.toolResult(deck);
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
return this.toolError(error);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
// Remove duplicate old tools block - list_service_tools follows
|
|
808
|
+
this.server.registerTool("list_service_tools", {
|
|
809
|
+
title: "List Service Tools",
|
|
810
|
+
description: "List all available tools for a specific service in the active deck",
|
|
811
|
+
inputSchema: { serviceId: zod_1.z.string() }
|
|
812
|
+
}, async ({ serviceId }) => {
|
|
813
|
+
try {
|
|
814
|
+
const tools = await this.callBackendAPI(`/api/services/${serviceId}/tools`);
|
|
815
|
+
return { content: [{ type: "text", text: JSON.stringify(tools, null, 2) }] };
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
// Call a tool on a service in the active deck
|
|
822
|
+
this.server.registerTool("call_service_tool", {
|
|
823
|
+
title: "Call Service Tool",
|
|
824
|
+
description: "Call a tool on a service from the active deck",
|
|
825
|
+
inputSchema: {
|
|
826
|
+
serviceId: zod_1.z.string(),
|
|
827
|
+
toolName: zod_1.z.string(),
|
|
828
|
+
arguments: zod_1.z.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()]).optional()
|
|
829
|
+
}
|
|
830
|
+
}, async ({ serviceId, toolName, arguments: args = {} }) => {
|
|
831
|
+
try {
|
|
832
|
+
// Support both object and JSON string inputs for arguments
|
|
833
|
+
let normalizedArgs = args;
|
|
834
|
+
if (typeof normalizedArgs === 'string' && normalizedArgs.length > 0) {
|
|
835
|
+
try {
|
|
836
|
+
normalizedArgs = JSON.parse(normalizedArgs);
|
|
837
|
+
}
|
|
838
|
+
catch (e) {
|
|
839
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Invalid JSON in arguments: ${String(e)}` }, null, 2) }] };
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const res = await fetch(`${this.backendUrl}/api/services/${serviceId}/call`, {
|
|
843
|
+
method: 'POST',
|
|
844
|
+
headers: { ...this.getAgentHeaders(), 'Content-Type': 'application/json' },
|
|
845
|
+
body: JSON.stringify({ toolName, arguments: normalizedArgs ?? {} })
|
|
846
|
+
});
|
|
847
|
+
if (!res.ok) {
|
|
848
|
+
const text = await res.text();
|
|
849
|
+
throw new Error(`Backend API error ${res.status}: ${text}`);
|
|
850
|
+
}
|
|
851
|
+
const body = await res.json();
|
|
852
|
+
const data = (body && body.success) ? (body.data ?? body.result ?? body) : body;
|
|
853
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }] };
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
setupResources() {
|
|
861
|
+
this.server.resource("bound_deck_services", "agent-deck://bound-deck/services", {
|
|
862
|
+
name: "Bound Deck Services",
|
|
863
|
+
description: "MCP services on the workspace-bound deck",
|
|
864
|
+
mimeType: "application/json"
|
|
865
|
+
}, async () => {
|
|
866
|
+
try {
|
|
867
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
868
|
+
const services = deck?.services ?? [];
|
|
869
|
+
return {
|
|
870
|
+
contents: [{
|
|
871
|
+
uri: "agent-deck://bound-deck/services",
|
|
872
|
+
mimeType: "application/json",
|
|
873
|
+
text: JSON.stringify(services, null, 2)
|
|
874
|
+
}]
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
return {
|
|
879
|
+
contents: [{
|
|
880
|
+
uri: "agent-deck://bound-deck/services",
|
|
881
|
+
mimeType: "application/json",
|
|
882
|
+
text: JSON.stringify({ error: `Failed to get bound deck services: ${error}` }, null, 2)
|
|
883
|
+
}]
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
this.server.resource("active_deck_services", "agent-deck://active-deck/services", {
|
|
888
|
+
name: "Active Deck Services (deprecated)",
|
|
889
|
+
description: "Deprecated — use agent-deck://bound-deck/services",
|
|
890
|
+
mimeType: "application/json"
|
|
891
|
+
}, async () => {
|
|
892
|
+
try {
|
|
893
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
894
|
+
const services = deck?.services ?? [];
|
|
895
|
+
return {
|
|
896
|
+
contents: [{
|
|
897
|
+
uri: "agent-deck://active-deck/services",
|
|
898
|
+
mimeType: "application/json",
|
|
899
|
+
text: JSON.stringify(services, null, 2)
|
|
900
|
+
}]
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
return {
|
|
905
|
+
contents: [{
|
|
906
|
+
uri: "agent-deck://active-deck/services",
|
|
907
|
+
mimeType: "application/json",
|
|
908
|
+
text: JSON.stringify({ error: `Failed to get bound deck services: ${error}` }, null, 2)
|
|
909
|
+
}]
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
this.server.resource("bound_deck_credentials", "agent-deck://bound-deck/credentials", {
|
|
914
|
+
name: "Bound Deck Credentials",
|
|
915
|
+
description: "API key metadata on the workspace-bound deck",
|
|
916
|
+
mimeType: "application/json"
|
|
917
|
+
}, async () => {
|
|
918
|
+
try {
|
|
919
|
+
const credentials = await this.callBackendAPI('/api/credentials');
|
|
920
|
+
return {
|
|
921
|
+
contents: [{
|
|
922
|
+
uri: "agent-deck://bound-deck/credentials",
|
|
923
|
+
mimeType: "application/json",
|
|
924
|
+
text: JSON.stringify(credentials, null, 2)
|
|
925
|
+
}]
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
return {
|
|
930
|
+
contents: [{
|
|
931
|
+
uri: "agent-deck://bound-deck/credentials",
|
|
932
|
+
mimeType: "application/json",
|
|
933
|
+
text: JSON.stringify({ error: `Failed to get bound deck credentials: ${error}` }, null, 2)
|
|
934
|
+
}]
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
this.server.resource("active_deck_credentials", "agent-deck://active-deck/credentials", {
|
|
939
|
+
name: "Active Deck Credentials (deprecated)",
|
|
940
|
+
description: "Deprecated — use agent-deck://bound-deck/credentials",
|
|
941
|
+
mimeType: "application/json"
|
|
942
|
+
}, async () => {
|
|
943
|
+
try {
|
|
944
|
+
const credentials = await this.callBackendAPI('/api/credentials');
|
|
945
|
+
return {
|
|
946
|
+
contents: [{
|
|
947
|
+
uri: "agent-deck://active-deck/credentials",
|
|
948
|
+
mimeType: "application/json",
|
|
949
|
+
text: JSON.stringify(credentials, null, 2)
|
|
950
|
+
}]
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
return {
|
|
955
|
+
contents: [{
|
|
956
|
+
uri: "agent-deck://active-deck/credentials",
|
|
957
|
+
mimeType: "application/json",
|
|
958
|
+
text: JSON.stringify({ error: `Failed to get bound deck credentials: ${error}` }, null, 2)
|
|
959
|
+
}]
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
this.server.resource("bound_deck", "agent-deck://bound-deck", {
|
|
964
|
+
name: "Bound Deck",
|
|
965
|
+
description: "Deck bound via .agent-deck/deck.yaml in the workspace",
|
|
966
|
+
mimeType: "application/json"
|
|
967
|
+
}, async () => {
|
|
968
|
+
try {
|
|
969
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
970
|
+
return {
|
|
971
|
+
contents: [{
|
|
972
|
+
uri: "agent-deck://bound-deck",
|
|
973
|
+
mimeType: "application/json",
|
|
974
|
+
text: JSON.stringify(deck, null, 2)
|
|
975
|
+
}]
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
return {
|
|
980
|
+
contents: [{
|
|
981
|
+
uri: "agent-deck://bound-deck",
|
|
982
|
+
mimeType: "application/json",
|
|
983
|
+
text: JSON.stringify({ error: `Failed to get bound deck: ${error}` }, null, 2)
|
|
984
|
+
}]
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
this.server.resource("active_deck", "agent-deck://active-deck", {
|
|
989
|
+
name: "Active Deck (deprecated)",
|
|
990
|
+
description: "Deprecated — use agent-deck://bound-deck",
|
|
991
|
+
mimeType: "application/json"
|
|
992
|
+
}, async () => {
|
|
993
|
+
try {
|
|
994
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
995
|
+
return {
|
|
996
|
+
contents: [{
|
|
997
|
+
uri: "agent-deck://active-deck",
|
|
998
|
+
mimeType: "application/json",
|
|
999
|
+
text: JSON.stringify(deck, null, 2)
|
|
1000
|
+
}]
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
return {
|
|
1005
|
+
contents: [{
|
|
1006
|
+
uri: "agent-deck://active-deck",
|
|
1007
|
+
mimeType: "application/json",
|
|
1008
|
+
text: JSON.stringify({ error: `Failed to get bound deck: ${error}` }, null, 2)
|
|
1009
|
+
}]
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
// Register resource for all decks
|
|
1014
|
+
this.server.resource("decks", "agent-deck://decks", {
|
|
1015
|
+
name: "Agent Deck Decks",
|
|
1016
|
+
description: "List of all available decks",
|
|
1017
|
+
mimeType: "application/json"
|
|
1018
|
+
}, async () => {
|
|
1019
|
+
try {
|
|
1020
|
+
const decks = await this.callBackendAPI('/api/decks');
|
|
1021
|
+
return {
|
|
1022
|
+
contents: [{
|
|
1023
|
+
uri: "agent-deck://decks",
|
|
1024
|
+
mimeType: "application/json",
|
|
1025
|
+
text: JSON.stringify(decks, null, 2)
|
|
1026
|
+
}]
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
catch (error) {
|
|
1030
|
+
return {
|
|
1031
|
+
contents: [{
|
|
1032
|
+
uri: "agent-deck://decks",
|
|
1033
|
+
mimeType: "application/json",
|
|
1034
|
+
text: JSON.stringify({ error: `Failed to get decks: ${error}` }, null, 2)
|
|
1035
|
+
}]
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
setupRoutes() {
|
|
1041
|
+
// Handle MCP requests
|
|
1042
|
+
this.app.post('/mcp', async (req, res) => {
|
|
1043
|
+
const sessionHeader = req.headers['mcp-session-id'];
|
|
1044
|
+
this.activeSessionId = typeof sessionHeader === 'string' ? sessionHeader : 'default';
|
|
1045
|
+
await this.transport.handleRequest(req, res, req.body);
|
|
1046
|
+
});
|
|
1047
|
+
// Health check endpoint
|
|
1048
|
+
this.app.get('/health', (req, res) => {
|
|
1049
|
+
res.json({
|
|
1050
|
+
status: 'ok',
|
|
1051
|
+
service: 'agent-deck-mcp-server',
|
|
1052
|
+
backendUrl: this.backendUrl
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
// Backend connectivity check
|
|
1056
|
+
this.app.get('/backend-status', async (req, res) => {
|
|
1057
|
+
try {
|
|
1058
|
+
const response = await fetch(`${this.backendUrl}/health`);
|
|
1059
|
+
const backendStatus = await response.json();
|
|
1060
|
+
res.json({
|
|
1061
|
+
mcpServer: 'ok',
|
|
1062
|
+
backend: backendStatus,
|
|
1063
|
+
connected: response.ok
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
catch (error) {
|
|
1067
|
+
res.json({
|
|
1068
|
+
mcpServer: 'ok',
|
|
1069
|
+
backend: 'unreachable',
|
|
1070
|
+
connected: false,
|
|
1071
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
async start() {
|
|
1077
|
+
try {
|
|
1078
|
+
console.log(`🚀 Starting Agent Deck MCP Server on port ${this.port}...`);
|
|
1079
|
+
console.log(`🔗 Backend API URL: ${this.backendUrl}`);
|
|
1080
|
+
// Connect the server to the transport (this will start the transport)
|
|
1081
|
+
await this.server.connect(this.transport);
|
|
1082
|
+
// Start the Express server
|
|
1083
|
+
this.app.listen(this.port, () => {
|
|
1084
|
+
console.log(`✅ Agent Deck MCP Server is ready to accept connections`);
|
|
1085
|
+
console.log(`📋 Available tools:`);
|
|
1086
|
+
console.log(` - bind_workspace: Bind session to repo via .agent-deck/deck.yaml`);
|
|
1087
|
+
console.log(` - get_repo_deck_status: Check repo deck manifest`);
|
|
1088
|
+
console.log(` - setup_repo_deck: Create .agent-deck/deck.yaml for a repo`);
|
|
1089
|
+
console.log(` - get_bound_deck: Get workspace-bound deck`);
|
|
1090
|
+
console.log(` - list_service_tools: List tools for a specific service`);
|
|
1091
|
+
console.log(` - call_service_tool: Call a tool on a service`);
|
|
1092
|
+
console.log(`📋 Available resources:`);
|
|
1093
|
+
console.log(` - agent-deck://decks: List of all available decks`);
|
|
1094
|
+
console.log(` - agent-deck://active-deck: The currently active deck`);
|
|
1095
|
+
console.log(` - agent-deck://active-deck/credentials: API keys on the active deck`);
|
|
1096
|
+
console.log(` - agent-deck://active-deck/services: Services in the active deck`);
|
|
1097
|
+
console.log(`🌐 Server running on http://localhost:${this.port}`);
|
|
1098
|
+
console.log(`🔧 MCP endpoint: http://localhost:${this.port}/mcp`);
|
|
1099
|
+
console.log(`❤️ Health check: http://localhost:${this.port}/health`);
|
|
1100
|
+
console.log(`🔗 Backend status: http://localhost:${this.port}/backend-status`);
|
|
1101
|
+
console.log(`📝 Architecture: MCP Server → Backend API → Active Deck Services`);
|
|
1102
|
+
});
|
|
1103
|
+
return this.app;
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
console.error(`❌ Failed to start MCP server:`, error);
|
|
1107
|
+
throw error;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
async stop() {
|
|
1111
|
+
try {
|
|
1112
|
+
await this.transport.close();
|
|
1113
|
+
console.log(`🛑 MCP server stopped`);
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
console.error(`❌ Error stopping MCP server:`, error);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
exports.AgentDeckMCPServer = AgentDeckMCPServer;
|
|
1121
|
+
//# sourceMappingURL=mcp-server.js.map
|