@agilemonster/mcp-server 1.0.4 → 1.0.6
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/bin/orchestrator.js +13 -2
- package/dist/client.d.ts +7 -3
- package/dist/client.js +32 -3
- package/dist/orchestrator.d.ts +4 -0
- package/dist/orchestrator.js +186 -18
- package/dist/server.js +34 -3
- package/dist/types.d.ts +30 -0
- package/dist/worker.js +5 -1
- package/package.json +1 -1
package/bin/orchestrator.js
CHANGED
|
@@ -26,8 +26,19 @@ async function main() {
|
|
|
26
26
|
case 'bootstrap':
|
|
27
27
|
case 'setup':
|
|
28
28
|
case 'init': {
|
|
29
|
-
const ideArg = args.find((a) => a.startsWith('--ide='))?.split('=')
|
|
30
|
-
|
|
29
|
+
const ideArg = args.find((a) => a.startsWith('--ide='))?.split('=').slice(1).join('=');
|
|
30
|
+
const boardArg = args
|
|
31
|
+
.find((a) => a.startsWith('--board='))
|
|
32
|
+
?.split('=')
|
|
33
|
+
.slice(1)
|
|
34
|
+
.join('=')
|
|
35
|
+
?.replace(/^["']|["']$/g, '');
|
|
36
|
+
const providerArg = args.find((a) => a.startsWith('--provider='))?.split('=').slice(1).join('=');
|
|
37
|
+
await orchestrator.initInteractive({
|
|
38
|
+
ide: ideArg,
|
|
39
|
+
board: boardArg,
|
|
40
|
+
provider: providerArg,
|
|
41
|
+
});
|
|
31
42
|
break;
|
|
32
43
|
}
|
|
33
44
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgileMonsterProjectConfig, Card, ContextResponse } from './types.js';
|
|
1
|
+
import { AgileMonsterProjectConfig, Board, BoardAgentConfig, Card, ContextResponse } from './types.js';
|
|
2
2
|
export declare class AgileMonsterClient {
|
|
3
3
|
private apiKey;
|
|
4
4
|
private baseUrl;
|
|
@@ -24,12 +24,16 @@ export declare class AgileMonsterClient {
|
|
|
24
24
|
first_name?: string;
|
|
25
25
|
last_name?: string;
|
|
26
26
|
}>;
|
|
27
|
-
getContext(projectName?: string, boardId?: string): Promise<ContextResponse>;
|
|
27
|
+
getContext(projectName?: string, boardId?: string, gitRemote?: string, detectedIdes?: string[]): Promise<ContextResponse>;
|
|
28
|
+
createBoard(workspaceId: string, name: string, description?: string): Promise<Board>;
|
|
29
|
+
saveBoardAgents(boardId: string, agents: BoardAgentConfig[], projectName?: string, projectPath?: string, gitRemote?: string): Promise<{
|
|
30
|
+
agents: BoardAgentConfig[];
|
|
31
|
+
}>;
|
|
28
32
|
listAssigned(provider?: string, status?: string, boardId?: string, workspaceId?: string): Promise<{
|
|
29
33
|
provider: string;
|
|
30
34
|
cards: Card[];
|
|
31
35
|
}>;
|
|
32
|
-
claimTask(cardId: string, provider: string, runRef?: string): Promise<Card>;
|
|
36
|
+
claimTask(cardId: string, provider: string, runRef?: string, expectedBoardId?: string): Promise<Card>;
|
|
33
37
|
askQuestion(cardId: string, provider: string, question: string): Promise<Card>;
|
|
34
38
|
addProgress(cardId: string, provider: string, update: string, tokensUsed?: number, costUsd?: number): Promise<Card>;
|
|
35
39
|
recordSpend(cardId: string, provider: string, tokens: number, costUsd: number, note?: string): Promise<Card>;
|
package/dist/client.js
CHANGED
|
@@ -145,15 +145,43 @@ export class AgileMonsterClient {
|
|
|
145
145
|
async me() {
|
|
146
146
|
return this.request('/api/agent/me');
|
|
147
147
|
}
|
|
148
|
-
async getContext(projectName, boardId) {
|
|
148
|
+
async getContext(projectName, boardId, gitRemote, detectedIdes) {
|
|
149
149
|
const params = new URLSearchParams();
|
|
150
150
|
if (projectName)
|
|
151
151
|
params.set('project_name', projectName);
|
|
152
152
|
if (boardId)
|
|
153
153
|
params.set('board_id', boardId);
|
|
154
|
+
if (gitRemote)
|
|
155
|
+
params.set('git_remote', gitRemote);
|
|
156
|
+
if (detectedIdes && detectedIdes.length > 0) {
|
|
157
|
+
params.set('detected_ides', detectedIdes.join(','));
|
|
158
|
+
}
|
|
154
159
|
const qs = params.toString();
|
|
155
160
|
return this.request(`/api/agent/context${qs ? `?${qs}` : ''}`);
|
|
156
161
|
}
|
|
162
|
+
async createBoard(workspaceId, name, description) {
|
|
163
|
+
return this.request('/api/boards', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
workspace_id: workspaceId,
|
|
167
|
+
name,
|
|
168
|
+
description,
|
|
169
|
+
default_lists: true,
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async saveBoardAgents(boardId, agents, projectName, projectPath, gitRemote) {
|
|
174
|
+
return this.request(`/api/boards/${boardId}/agents`, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
agents,
|
|
178
|
+
project_name: projectName,
|
|
179
|
+
project_path: projectPath,
|
|
180
|
+
git_remote: gitRemote,
|
|
181
|
+
connected: true,
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
157
185
|
async listAssigned(provider = 'any', status, boardId, workspaceId) {
|
|
158
186
|
const params = new URLSearchParams();
|
|
159
187
|
if (provider)
|
|
@@ -167,10 +195,11 @@ export class AgileMonsterClient {
|
|
|
167
195
|
const qs = params.toString();
|
|
168
196
|
return this.request(`/api/agent/assigned${qs ? `?${qs}` : ''}`);
|
|
169
197
|
}
|
|
170
|
-
async claimTask(cardId, provider, runRef) {
|
|
198
|
+
async claimTask(cardId, provider, runRef, expectedBoardId) {
|
|
199
|
+
const boardId = expectedBoardId || this.loadProjectConfig()?.board_id;
|
|
171
200
|
return this.request(`/api/agent/cards/${cardId}/claim`, {
|
|
172
201
|
method: 'POST',
|
|
173
|
-
body: JSON.stringify({ provider, run_ref: runRef }),
|
|
202
|
+
body: JSON.stringify({ provider, run_ref: runRef, board_id: boardId }),
|
|
174
203
|
});
|
|
175
204
|
}
|
|
176
205
|
async askQuestion(cardId, provider, question) {
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -15,13 +15,17 @@ export declare class AgileMonsterOrchestrator {
|
|
|
15
15
|
}): Promise<Card[]>;
|
|
16
16
|
printDashboard(): Promise<void>;
|
|
17
17
|
runDaemon(options?: OrchestratorOptions): Promise<void>;
|
|
18
|
+
private detectGitRemote;
|
|
19
|
+
private detectInstalledIdes;
|
|
18
20
|
initInteractive(options?: {
|
|
19
21
|
provider?: string;
|
|
20
22
|
ide?: string;
|
|
23
|
+
board?: string;
|
|
21
24
|
}): Promise<void>;
|
|
22
25
|
bootstrapInteractive(options?: {
|
|
23
26
|
provider?: string;
|
|
24
27
|
ide?: string;
|
|
28
|
+
board?: string;
|
|
25
29
|
}): Promise<void>;
|
|
26
30
|
configureIdes(apiKey: string, selectedIde?: string): void;
|
|
27
31
|
private ensureGitignore;
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
3
4
|
import { AgileMonsterClient } from './client.js';
|
|
4
5
|
import { AGENT_GUIDELINES_MARKDOWN } from './guidelines.js';
|
|
5
6
|
export class AgileMonsterOrchestrator {
|
|
@@ -11,6 +12,10 @@ export class AgileMonsterOrchestrator {
|
|
|
11
12
|
const projectConfig = this.client.loadProjectConfig();
|
|
12
13
|
const provider = options?.provider || projectConfig?.provider || 'any';
|
|
13
14
|
const boardId = options?.boardId || projectConfig?.board_id;
|
|
15
|
+
// STRICT BOARD SCOPE GUARD: Never poll across all boards when current folder is unbound!
|
|
16
|
+
if (!boardId) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
14
19
|
const result = await this.client.listAssigned(provider, undefined, boardId);
|
|
15
20
|
return result.cards || [];
|
|
16
21
|
}
|
|
@@ -33,8 +38,10 @@ export class AgileMonsterOrchestrator {
|
|
|
33
38
|
console.log(`🤖 Default Provider: ${config.provider || 'any'}`);
|
|
34
39
|
}
|
|
35
40
|
else {
|
|
36
|
-
console.log(
|
|
37
|
-
console.log('
|
|
41
|
+
console.log(`⚠️ [UNBOUND REPOSITORY] No .agilemonster.json found in "${cwdName}".`);
|
|
42
|
+
console.log(' To prevent agents from picking up tasks from the wrong board and burning tokens:');
|
|
43
|
+
console.log(' Run "npx -y @agilemonster/mcp-server bootstrap" in this folder first!\n');
|
|
44
|
+
return;
|
|
38
45
|
}
|
|
39
46
|
console.log('\n🔍 Checking for assigned agent tasks...');
|
|
40
47
|
const tasks = await this.checkTasks();
|
|
@@ -99,17 +106,54 @@ export class AgileMonsterOrchestrator {
|
|
|
99
106
|
await poll();
|
|
100
107
|
setInterval(poll, interval);
|
|
101
108
|
}
|
|
109
|
+
detectGitRemote() {
|
|
110
|
+
try {
|
|
111
|
+
const out = execSync('git config --get remote.origin.url', {
|
|
112
|
+
cwd: process.cwd(),
|
|
113
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
114
|
+
})
|
|
115
|
+
.toString()
|
|
116
|
+
.trim();
|
|
117
|
+
return out || undefined;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
detectInstalledIdes() {
|
|
124
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || '';
|
|
125
|
+
const detected = [];
|
|
126
|
+
if (fs.existsSync(path.resolve(process.cwd(), '.cursor')) ||
|
|
127
|
+
(homedir && fs.existsSync(path.join(homedir, '.cursor')))) {
|
|
128
|
+
detected.push('cursor');
|
|
129
|
+
}
|
|
130
|
+
if (fs.existsSync(path.resolve(process.cwd(), '.mcp.json')) ||
|
|
131
|
+
(homedir && fs.existsSync(path.join(homedir, '.claude')))) {
|
|
132
|
+
detected.push('claude_code');
|
|
133
|
+
}
|
|
134
|
+
if (homedir && fs.existsSync(path.join(homedir, '.gemini'))) {
|
|
135
|
+
detected.push('antigravity');
|
|
136
|
+
}
|
|
137
|
+
if (homedir && fs.existsSync(path.join(homedir, '.codeium', 'windsurf'))) {
|
|
138
|
+
detected.push('windsurf');
|
|
139
|
+
}
|
|
140
|
+
if (fs.existsSync(path.resolve(process.cwd(), '.vscode'))) {
|
|
141
|
+
detected.push('codex');
|
|
142
|
+
}
|
|
143
|
+
return detected.length > 0 ? detected : ['cursor', 'claude_code', 'antigravity'];
|
|
144
|
+
}
|
|
102
145
|
async initInteractive(options) {
|
|
103
146
|
console.log('\n======================================================');
|
|
104
147
|
console.log(' 👾 AGILE MONSTER 1-CLICK AGENT SETUP ');
|
|
105
148
|
console.log('======================================================\n');
|
|
149
|
+
const readline = await import('node:readline/promises');
|
|
150
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
106
151
|
let apiKey = this.client.getApiKey();
|
|
107
152
|
if (!apiKey) {
|
|
108
153
|
console.log('🔑 No Agile Monster API key detected.');
|
|
109
154
|
console.log(' 1. Open: https://app.agilemonster.com/profile?setup=agents');
|
|
110
155
|
console.log(' 2. Click "Generate API key", then "Copy API key"');
|
|
111
156
|
console.log(' 3. Paste it below\n');
|
|
112
|
-
const readline = await import('node:readline/promises');
|
|
113
157
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
114
158
|
try {
|
|
115
159
|
const inputKey = await rl.question('👉 Paste your Personal API Key (am_live_...): ');
|
|
@@ -128,23 +172,148 @@ export class AgileMonsterOrchestrator {
|
|
|
128
172
|
rl.close();
|
|
129
173
|
}
|
|
130
174
|
}
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
175
|
+
const folderName = path.basename(process.cwd());
|
|
176
|
+
const gitRemote = this.detectGitRemote();
|
|
177
|
+
const detectedIdes = this.detectInstalledIdes();
|
|
178
|
+
console.log(`\n🔍 Analyzing repository "${folderName}" with Agile Monster AI...`);
|
|
179
|
+
const context = await this.client.getContext(folderName, options?.board, gitRemote, detectedIdes);
|
|
180
|
+
const boards = context.available_boards || [];
|
|
181
|
+
const aiMatch = context.ai_match;
|
|
182
|
+
const suggestedNewBoardName = aiMatch?.suggested_new_board_name || folderName.replace(/\.(com|org|net)$/i, '');
|
|
183
|
+
let targetBoard = null;
|
|
184
|
+
if (options?.board) {
|
|
185
|
+
const needle = options.board.trim().toLowerCase();
|
|
186
|
+
targetBoard =
|
|
187
|
+
boards.find((b) => b.id.toLowerCase() === needle || b.name.trim().toLowerCase() === needle) || context.matched_board;
|
|
188
|
+
}
|
|
189
|
+
if (!targetBoard) {
|
|
190
|
+
if (aiMatch?.reason) {
|
|
191
|
+
console.log(`\n🤖 AI Guidance: ${aiMatch.reason}\n`);
|
|
192
|
+
}
|
|
193
|
+
if (isInteractive) {
|
|
194
|
+
console.log('📋 Which Agile Monster board should connect to this project folder?');
|
|
195
|
+
boards.forEach((b, idx) => {
|
|
196
|
+
const isAiMatched = aiMatch?.matched_board_id === b.id;
|
|
197
|
+
const badge = isAiMatched ? ` ✨ [AI Recommended Match]` : '';
|
|
198
|
+
console.log(` [${idx + 1}] ${b.name} (${b.workspace_name || 'Workspace'})${badge}`);
|
|
199
|
+
});
|
|
200
|
+
console.log(` [N] + Create new board "${suggestedNewBoardName}"`);
|
|
201
|
+
const defaultIdx = aiMatch?.matched_board_id
|
|
202
|
+
? boards.findIndex((b) => b.id === aiMatch.matched_board_id) + 1
|
|
203
|
+
: 0;
|
|
204
|
+
const defaultChoice = defaultIdx > 0 ? String(defaultIdx) : 'N';
|
|
205
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
206
|
+
try {
|
|
207
|
+
const answer = (await rl.question(`\n👉 Select board [1-${boards.length} or N] (default: ${defaultChoice}): `))
|
|
208
|
+
.trim()
|
|
209
|
+
.toUpperCase();
|
|
210
|
+
const choice = answer || defaultChoice;
|
|
211
|
+
if (choice === 'N' || boards.length === 0) {
|
|
212
|
+
const customTitle = (await rl.question(`👉 New board name (Press Enter for "${suggestedNewBoardName}"): `)).trim();
|
|
213
|
+
const finalBoardName = customTitle || suggestedNewBoardName;
|
|
214
|
+
const workspaceId = boards[0]?.workspace_id || context.workspaces?.[0]?.id || '';
|
|
215
|
+
if (!workspaceId) {
|
|
216
|
+
console.error('❌ No workspace found in your account. Please create a workspace on https://app.agilemonster.com first.');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
console.log(`✨ Creating board "${finalBoardName}" with default Kanban columns...`);
|
|
220
|
+
targetBoard = await this.client.createBoard(workspaceId, finalBoardName, `Connected to repository ${folderName}`);
|
|
221
|
+
console.log(`✅ Created board "${targetBoard.name}" (${targetBoard.id})`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const num = parseInt(choice, 10);
|
|
225
|
+
if (!Number.isNaN(num) && num >= 1 && num <= boards.length) {
|
|
226
|
+
const candidate = boards[num - 1];
|
|
227
|
+
// Safety confirmation if user picks an existing board that AI said didn't match this folder
|
|
228
|
+
if (aiMatch &&
|
|
229
|
+
aiMatch.matched_board_id !== candidate.id &&
|
|
230
|
+
candidate.name.toLowerCase() !== folderName.toLowerCase()) {
|
|
231
|
+
const confirm = (await rl.question(`⚠️ Safety Check: Folder is "${folderName}" but board is "${candidate.name}". Are you sure they are the same project? [y/N]: `))
|
|
232
|
+
.trim()
|
|
233
|
+
.toLowerCase();
|
|
234
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
235
|
+
console.log('❌ Cancelled to prevent connecting the wrong board.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
targetBoard = candidate;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.error(`❌ Invalid selection "${choice}". Aborting so we never bind the wrong board.`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
rl.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Non-interactive mode: ONLY auto-bind if AI matched with high confidence OR --board was explicitly passed.
|
|
253
|
+
// NEVER auto-pick a board or create a board silently if ambiguous!
|
|
254
|
+
if (context.matched_board && aiMatch?.confidence === 'high') {
|
|
255
|
+
targetBoard = context.matched_board;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.error(`❌ Ambiguous board match for folder "${folderName}" in non-interactive mode.`);
|
|
259
|
+
console.error(` Please run interactively in your terminal or pass --board="<Board Name>" explicitly so we never connect the wrong board.`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!targetBoard) {
|
|
265
|
+
console.log('No board selected. Run "npx -y @agilemonster/mcp-server bootstrap" again to connect a board.');
|
|
135
266
|
return;
|
|
136
267
|
}
|
|
137
|
-
|
|
268
|
+
// Prompt for optional short custom agent tag on this board
|
|
269
|
+
let shortTag = '';
|
|
270
|
+
if (isInteractive) {
|
|
271
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
272
|
+
try {
|
|
273
|
+
console.log(`\n🏷️ On board "${targetBoard.name}", your agents will appear as:`);
|
|
274
|
+
console.log(` "Cursor: ${targetBoard.name}", "Claude Code: ${targetBoard.name}", "Antigravity: ${targetBoard.name}"`);
|
|
275
|
+
const tagInput = (await rl.question(`👉 Optional: Enter a shorter project tag (e.g. "PG" -> "Cursor-PG"), or press Enter to keep "${targetBoard.name}": `)).trim();
|
|
276
|
+
shortTag = tagInput;
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
rl.close();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const providerTitles = {
|
|
283
|
+
cursor: 'Cursor',
|
|
284
|
+
claude_code: 'Claude Code',
|
|
285
|
+
antigravity: 'Antigravity',
|
|
286
|
+
windsurf: 'Windsurf',
|
|
287
|
+
codex: 'Codex',
|
|
288
|
+
};
|
|
289
|
+
try {
|
|
290
|
+
const agentEntries = Object.entries(providerTitles).map(([prov, label]) => ({
|
|
291
|
+
provider: prov,
|
|
292
|
+
custom_name: `${label}: ${targetBoard.name}`,
|
|
293
|
+
short_name: shortTag ? `${label}-${shortTag}` : null,
|
|
294
|
+
project_name: folderName,
|
|
295
|
+
project_path: process.cwd(),
|
|
296
|
+
git_remote: gitRemote || null,
|
|
297
|
+
connected: true,
|
|
298
|
+
}));
|
|
299
|
+
await this.client.saveBoardAgents(targetBoard.id, agentEntries, folderName, process.cwd(), gitRemote);
|
|
300
|
+
console.log(`🔗 Registered board agents for "${targetBoard.name}" on Agile Monster.`);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// Non-blocking if backend is older
|
|
304
|
+
}
|
|
138
305
|
const savedPath = this.client.saveProjectConfig({
|
|
139
306
|
workspace_id: targetBoard.workspace_id,
|
|
140
307
|
board_id: targetBoard.id,
|
|
141
308
|
board_name: targetBoard.name,
|
|
142
|
-
|
|
309
|
+
project_name: folderName,
|
|
310
|
+
project_path: process.cwd(),
|
|
311
|
+
provider: options?.provider || 'antigravity',
|
|
143
312
|
review_list: 'In Review',
|
|
144
313
|
done_list: 'Done',
|
|
145
314
|
api_key: apiKey,
|
|
146
315
|
});
|
|
147
|
-
console.log(`✅ Bound repository
|
|
316
|
+
console.log(`✅ Bound repository "${folderName}" -> Board "${targetBoard.name}" (${targetBoard.id})`);
|
|
148
317
|
console.log(` Config written to: ${savedPath}`);
|
|
149
318
|
// Ensure AGENTS.md exists
|
|
150
319
|
const agentsPath = path.resolve(process.cwd(), 'AGENTS.md');
|
|
@@ -156,19 +325,18 @@ export class AgileMonsterOrchestrator {
|
|
|
156
325
|
console.log(`📄 AGENTS.md already exists in current directory.`);
|
|
157
326
|
}
|
|
158
327
|
// Configure IDEs (Cursor, Claude Code, Windsurf, Antigravity, VS Code)
|
|
159
|
-
this.configureIdes(apiKey, options?.provider);
|
|
328
|
+
this.configureIdes(apiKey, options?.ide || options?.provider);
|
|
160
329
|
this.ensureGitignore();
|
|
161
|
-
console.log('\n🎉 Project successfully
|
|
330
|
+
console.log('\n🎉 Project successfully connected to Agile Monster!');
|
|
162
331
|
console.log('\n======================================================');
|
|
163
332
|
console.log(' 🚀 HOW TO USE IT NOW ');
|
|
164
333
|
console.log('======================================================');
|
|
165
|
-
console.log(`1.
|
|
166
|
-
console.log(
|
|
167
|
-
console.log('3. In your AI IDE chat, simply say:');
|
|
334
|
+
console.log(`1. Open board "${targetBoard.name}" on https://app.agilemonster.com`);
|
|
335
|
+
console.log(`2. Open any card and assign Worker to "${shortTag ? `Cursor-${shortTag}` : `Cursor: ${targetBoard.name}`}" (or Claude Code / Antigravity).`);
|
|
336
|
+
console.log('3. In your AI IDE chat inside this project, simply say:');
|
|
168
337
|
console.log(' "Check my Agile Monster tasks and start the next queued card."');
|
|
169
|
-
console.log(' Or
|
|
170
|
-
console.log('
|
|
171
|
-
console.log(' • Auto-watch & run daemon: npx -y @agilemonster/mcp-server watch\n');
|
|
338
|
+
console.log(' Or connect another project folder (e.g. Planogram.ai in Cursor) anytime by running:');
|
|
339
|
+
console.log(' cd /path/to/other-project && npx -y @agilemonster/mcp-server bootstrap\n');
|
|
172
340
|
}
|
|
173
341
|
async bootstrapInteractive(options) {
|
|
174
342
|
return this.initInteractive(options);
|
package/dist/server.js
CHANGED
|
@@ -512,12 +512,30 @@ Please strictly follow the Agile Monster Task Lifecycle Protocol:
|
|
|
512
512
|
const status = args.status;
|
|
513
513
|
const boardId = args.board_id || projectConfig?.board_id;
|
|
514
514
|
const workspaceId = projectConfig?.workspace_id;
|
|
515
|
+
if (!boardId) {
|
|
516
|
+
return {
|
|
517
|
+
isError: true,
|
|
518
|
+
content: [
|
|
519
|
+
{
|
|
520
|
+
type: 'text',
|
|
521
|
+
text: `⚠️ [BOARD SCOPE GUARD] Current project folder "${path.basename(process.cwd())}" is not bound to an Agile Monster board (.agilemonster.json is missing).\n` +
|
|
522
|
+
`Refusing to list tasks across all boards so you never accidentally pick up a task from the wrong project and burn tokens.\n` +
|
|
523
|
+
`Please run "npx -y @agilemonster/mcp-server bootstrap" in this folder or call agilemonster_get_context first to bind the right board.`,
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
515
528
|
const result = await client.listAssigned(provider, status, boardId, workspaceId);
|
|
516
529
|
return {
|
|
517
530
|
content: [
|
|
518
531
|
{
|
|
519
532
|
type: 'text',
|
|
520
|
-
text: JSON.stringify(
|
|
533
|
+
text: JSON.stringify({
|
|
534
|
+
bound_board_id: boardId,
|
|
535
|
+
bound_board_name: projectConfig?.board_name,
|
|
536
|
+
bound_project_name: projectConfig?.project_name || path.basename(process.cwd()),
|
|
537
|
+
...result,
|
|
538
|
+
}, null, 2),
|
|
521
539
|
},
|
|
522
540
|
],
|
|
523
541
|
};
|
|
@@ -526,12 +544,25 @@ Please strictly follow the Agile Monster Task Lifecycle Protocol:
|
|
|
526
544
|
const cardId = args.card_id;
|
|
527
545
|
const provider = args.provider || projectConfig?.provider || 'antigravity';
|
|
528
546
|
const runRef = args.run_ref;
|
|
529
|
-
const
|
|
547
|
+
const expectedBoardId = projectConfig?.board_id;
|
|
548
|
+
if (!expectedBoardId) {
|
|
549
|
+
return {
|
|
550
|
+
isError: true,
|
|
551
|
+
content: [
|
|
552
|
+
{
|
|
553
|
+
type: 'text',
|
|
554
|
+
text: `⚠️ [BOARD SCOPE GUARD] Cannot claim card ${cardId} because this repository folder ("${path.basename(process.cwd())}") has no .agilemonster.json board binding.\n` +
|
|
555
|
+
`Run "npx -y @agilemonster/mcp-server bootstrap" first so we verify this folder matches the card's board.`,
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const card = await client.claimTask(cardId, provider, runRef, expectedBoardId);
|
|
530
561
|
return {
|
|
531
562
|
content: [
|
|
532
563
|
{
|
|
533
564
|
type: 'text',
|
|
534
|
-
text: `Task claimed successfully. Status is now "in_progress".\n${JSON.stringify(card, null, 2)}`,
|
|
565
|
+
text: `Task claimed successfully on board "${card.board_name || expectedBoardId}". Status is now "in_progress".\n${JSON.stringify(card, null, 2)}`,
|
|
535
566
|
},
|
|
536
567
|
],
|
|
537
568
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface AgileMonsterProjectConfig {
|
|
|
2
2
|
workspace_id?: string;
|
|
3
3
|
board_id?: string;
|
|
4
4
|
board_name?: string;
|
|
5
|
+
project_name?: string;
|
|
6
|
+
project_path?: string;
|
|
5
7
|
provider?: string;
|
|
6
8
|
review_list?: string;
|
|
7
9
|
done_list?: string;
|
|
@@ -38,10 +40,38 @@ export interface ColumnList {
|
|
|
38
40
|
name: string;
|
|
39
41
|
position: number;
|
|
40
42
|
}
|
|
43
|
+
export interface AiBoardMatch {
|
|
44
|
+
matched_board_id: string | null;
|
|
45
|
+
confidence: 'high' | 'medium' | 'none';
|
|
46
|
+
reason: string;
|
|
47
|
+
suggested_new_board_name: string;
|
|
48
|
+
suggested_short_code: string;
|
|
49
|
+
}
|
|
50
|
+
export interface BoardAgentConfig {
|
|
51
|
+
provider: string;
|
|
52
|
+
provider_label?: string;
|
|
53
|
+
board_id?: string;
|
|
54
|
+
board_name?: string;
|
|
55
|
+
default_name?: string;
|
|
56
|
+
custom_name?: string;
|
|
57
|
+
short_name?: string | null;
|
|
58
|
+
display_name?: string;
|
|
59
|
+
project_name?: string | null;
|
|
60
|
+
project_path?: string | null;
|
|
61
|
+
git_remote?: string | null;
|
|
62
|
+
connected?: boolean;
|
|
63
|
+
last_connected_at?: string | null;
|
|
64
|
+
}
|
|
41
65
|
export interface ContextResponse {
|
|
42
66
|
matched_board: Board | null;
|
|
67
|
+
ai_match?: AiBoardMatch | null;
|
|
43
68
|
available_boards: Board[];
|
|
69
|
+
workspaces?: Array<{
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
}>;
|
|
44
73
|
lists: ColumnList[];
|
|
74
|
+
board_agents?: BoardAgentConfig[];
|
|
45
75
|
active_agent_tasks: Card[];
|
|
46
76
|
supported_providers: string[];
|
|
47
77
|
}
|
package/dist/worker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { AgileMonsterClient } from './client.js';
|
|
3
4
|
export class AgileMonsterWorker {
|
|
4
5
|
client;
|
|
@@ -59,8 +60,11 @@ export class AgileMonsterWorker {
|
|
|
59
60
|
const projectConfig = this.client.loadProjectConfig();
|
|
60
61
|
const provider = options.provider || projectConfig?.provider || 'antigravity';
|
|
61
62
|
const boardId = options.boardId || projectConfig?.board_id;
|
|
63
|
+
if (!boardId) {
|
|
64
|
+
throw new Error(`[Board Scope Guard] No .agilemonster.json board binding found in "${path.basename(process.cwd())}". Refusing to poll tasks without an explicit board binding.`);
|
|
65
|
+
}
|
|
62
66
|
const res = await this.client.listAssigned(provider, 'queued', boardId);
|
|
63
|
-
const tasks = res.cards || [];
|
|
67
|
+
const tasks = (res.cards || []).filter((c) => !c.board_id || c.board_id === boardId);
|
|
64
68
|
if (tasks.length === 0) {
|
|
65
69
|
return;
|
|
66
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Official Model Context Protocol (MCP) server & Orchestrator for Agile Monster (Cursor, Claude Code, Antigravity, Codex, Hermes, Windsurf)",
|
|
6
6
|
"main": "dist/server.js",
|