@agilemonster/mcp-server 1.0.5 ā 1.0.7
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/mcp.js +5 -1
- package/dist/client.d.ts +9 -2
- package/dist/client.js +18 -2
- package/dist/orchestrator.js +38 -18
- package/dist/server.d.ts +1 -0
- package/dist/server.js +166 -7
- package/dist/types.d.ts +22 -0
- package/dist/worker.js +5 -1
- package/package.json +1 -1
package/bin/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runStdioServer } from '../dist/server.js';
|
|
3
|
+
import { MCP_SERVER_VERSION } from '../dist/client.js';
|
|
3
4
|
|
|
4
5
|
const cliCommands = new Set([
|
|
5
6
|
'bootstrap',
|
|
@@ -19,7 +20,10 @@ const cliCommands = new Set([
|
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
const firstArg = process.argv[2];
|
|
22
|
-
if (firstArg
|
|
23
|
+
if (firstArg === '--version' || firstArg === '-v' || firstArg === 'version') {
|
|
24
|
+
console.log(MCP_SERVER_VERSION);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} else if (firstArg && cliCommands.has(firstArg)) {
|
|
23
27
|
// Delegate to orchestrator CLI
|
|
24
28
|
await import('./orchestrator.js');
|
|
25
29
|
} else {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AgileMonsterProjectConfig, Board, BoardAgentConfig, Card, ContextResponse } from './types.js';
|
|
1
|
+
import { AgileMonsterProjectConfig, Board, BoardAgentConfig, Card, ContextResponse, PulseResponse } from './types.js';
|
|
2
|
+
export declare const MCP_SERVER_VERSION = "1.0.7";
|
|
2
3
|
export declare class AgileMonsterClient {
|
|
3
4
|
private apiKey;
|
|
4
5
|
private baseUrl;
|
|
@@ -29,11 +30,17 @@ export declare class AgileMonsterClient {
|
|
|
29
30
|
saveBoardAgents(boardId: string, agents: BoardAgentConfig[], projectName?: string, projectPath?: string, gitRemote?: string): Promise<{
|
|
30
31
|
agents: BoardAgentConfig[];
|
|
31
32
|
}>;
|
|
33
|
+
pulse(options: {
|
|
34
|
+
boardId: string;
|
|
35
|
+
provider?: string;
|
|
36
|
+
projectName?: string;
|
|
37
|
+
projectPath?: string;
|
|
38
|
+
}): Promise<PulseResponse>;
|
|
32
39
|
listAssigned(provider?: string, status?: string, boardId?: string, workspaceId?: string): Promise<{
|
|
33
40
|
provider: string;
|
|
34
41
|
cards: Card[];
|
|
35
42
|
}>;
|
|
36
|
-
claimTask(cardId: string, provider: string, runRef?: string): Promise<Card>;
|
|
43
|
+
claimTask(cardId: string, provider: string, runRef?: string, expectedBoardId?: string): Promise<Card>;
|
|
37
44
|
askQuestion(cardId: string, provider: string, question: string): Promise<Card>;
|
|
38
45
|
addProgress(cardId: string, provider: string, update: string, tokensUsed?: number, costUsd?: number): Promise<Card>;
|
|
39
46
|
recordSpend(cardId: string, provider: string, tokens: number, costUsd: number, note?: string): Promise<Card>;
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
export const MCP_SERVER_VERSION = '1.0.7';
|
|
3
4
|
export class AgileMonsterClient {
|
|
4
5
|
apiKey = '';
|
|
5
6
|
baseUrl;
|
|
@@ -122,6 +123,7 @@ export class AgileMonsterClient {
|
|
|
122
123
|
Authorization: `Bearer ${this.apiKey}`,
|
|
123
124
|
'Content-Type': 'application/json',
|
|
124
125
|
Accept: 'application/json',
|
|
126
|
+
'X-AgileMonster-MCP-Version': MCP_SERVER_VERSION,
|
|
125
127
|
...(options.headers || {}),
|
|
126
128
|
};
|
|
127
129
|
const res = await fetch(url, {
|
|
@@ -178,10 +180,23 @@ export class AgileMonsterClient {
|
|
|
178
180
|
project_name: projectName,
|
|
179
181
|
project_path: projectPath,
|
|
180
182
|
git_remote: gitRemote,
|
|
183
|
+
mcp_version: MCP_SERVER_VERSION,
|
|
181
184
|
connected: true,
|
|
182
185
|
}),
|
|
183
186
|
});
|
|
184
187
|
}
|
|
188
|
+
async pulse(options) {
|
|
189
|
+
return this.request('/api/agent/pulse', {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
board_id: options.boardId,
|
|
193
|
+
provider: options.provider || 'any',
|
|
194
|
+
project_name: options.projectName,
|
|
195
|
+
project_path: options.projectPath,
|
|
196
|
+
mcp_version: MCP_SERVER_VERSION,
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
185
200
|
async listAssigned(provider = 'any', status, boardId, workspaceId) {
|
|
186
201
|
const params = new URLSearchParams();
|
|
187
202
|
if (provider)
|
|
@@ -195,10 +210,11 @@ export class AgileMonsterClient {
|
|
|
195
210
|
const qs = params.toString();
|
|
196
211
|
return this.request(`/api/agent/assigned${qs ? `?${qs}` : ''}`);
|
|
197
212
|
}
|
|
198
|
-
async claimTask(cardId, provider, runRef) {
|
|
213
|
+
async claimTask(cardId, provider, runRef, expectedBoardId) {
|
|
214
|
+
const boardId = expectedBoardId || this.loadProjectConfig()?.board_id;
|
|
199
215
|
return this.request(`/api/agent/cards/${cardId}/claim`, {
|
|
200
216
|
method: 'POST',
|
|
201
|
-
body: JSON.stringify({ provider, run_ref: runRef }),
|
|
217
|
+
body: JSON.stringify({ provider, run_ref: runRef, board_id: boardId }),
|
|
202
218
|
});
|
|
203
219
|
}
|
|
204
220
|
async askQuestion(cardId, provider, question) {
|
package/dist/orchestrator.js
CHANGED
|
@@ -12,6 +12,10 @@ export class AgileMonsterOrchestrator {
|
|
|
12
12
|
const projectConfig = this.client.loadProjectConfig();
|
|
13
13
|
const provider = options?.provider || projectConfig?.provider || 'any';
|
|
14
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
|
+
}
|
|
15
19
|
const result = await this.client.listAssigned(provider, undefined, boardId);
|
|
16
20
|
return result.cards || [];
|
|
17
21
|
}
|
|
@@ -34,8 +38,10 @@ export class AgileMonsterOrchestrator {
|
|
|
34
38
|
console.log(`š¤ Default Provider: ${config.provider || 'any'}`);
|
|
35
39
|
}
|
|
36
40
|
else {
|
|
37
|
-
console.log(
|
|
38
|
-
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;
|
|
39
45
|
}
|
|
40
46
|
console.log('\nš Checking for assigned agent tasks...');
|
|
41
47
|
const tasks = await this.checkTasks();
|
|
@@ -217,13 +223,24 @@ export class AgileMonsterOrchestrator {
|
|
|
217
223
|
else {
|
|
218
224
|
const num = parseInt(choice, 10);
|
|
219
225
|
if (!Number.isNaN(num) && num >= 1 && num <= boards.length) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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;
|
|
224
240
|
}
|
|
225
241
|
else {
|
|
226
|
-
|
|
242
|
+
console.error(`ā Invalid selection "${choice}". Aborting so we never bind the wrong board.`);
|
|
243
|
+
return;
|
|
227
244
|
}
|
|
228
245
|
}
|
|
229
246
|
}
|
|
@@ -232,14 +249,15 @@ export class AgileMonsterOrchestrator {
|
|
|
232
249
|
}
|
|
233
250
|
}
|
|
234
251
|
else {
|
|
235
|
-
// Non-interactive
|
|
236
|
-
if
|
|
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') {
|
|
237
255
|
targetBoard = context.matched_board;
|
|
238
256
|
}
|
|
239
|
-
else
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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;
|
|
243
261
|
}
|
|
244
262
|
}
|
|
245
263
|
}
|
|
@@ -288,6 +306,8 @@ export class AgileMonsterOrchestrator {
|
|
|
288
306
|
workspace_id: targetBoard.workspace_id,
|
|
289
307
|
board_id: targetBoard.id,
|
|
290
308
|
board_name: targetBoard.name,
|
|
309
|
+
project_name: folderName,
|
|
310
|
+
project_path: process.cwd(),
|
|
291
311
|
provider: options?.provider || 'antigravity',
|
|
292
312
|
review_list: 'In Review',
|
|
293
313
|
done_list: 'Done',
|
|
@@ -341,7 +361,7 @@ export class AgileMonsterOrchestrator {
|
|
|
341
361
|
existing.mcpServers = existing.mcpServers || {};
|
|
342
362
|
existing.mcpServers.agilemonster = {
|
|
343
363
|
command: 'npx',
|
|
344
|
-
args: ['-y', '@agilemonster/mcp-server'],
|
|
364
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
345
365
|
env: {
|
|
346
366
|
AGILEMONSTER_API_KEY: apiKey,
|
|
347
367
|
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
|
@@ -368,7 +388,7 @@ export class AgileMonsterOrchestrator {
|
|
|
368
388
|
existing.mcpServers = existing.mcpServers || {};
|
|
369
389
|
existing.mcpServers.agilemonster = {
|
|
370
390
|
command: 'npx',
|
|
371
|
-
args: ['-y', '@agilemonster/mcp-server'],
|
|
391
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
372
392
|
env: {
|
|
373
393
|
AGILEMONSTER_API_KEY: apiKey,
|
|
374
394
|
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
|
@@ -399,7 +419,7 @@ export class AgileMonsterOrchestrator {
|
|
|
399
419
|
existing.mcpServers = existing.mcpServers || {};
|
|
400
420
|
existing.mcpServers.agilemonster = {
|
|
401
421
|
command: 'npx',
|
|
402
|
-
args: ['-y', '@agilemonster/mcp-server'],
|
|
422
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
403
423
|
env: {
|
|
404
424
|
AGILEMONSTER_API_KEY: apiKey,
|
|
405
425
|
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
|
@@ -430,7 +450,7 @@ export class AgileMonsterOrchestrator {
|
|
|
430
450
|
existing.mcpServers = existing.mcpServers || {};
|
|
431
451
|
existing.mcpServers.agilemonster = {
|
|
432
452
|
command: 'npx',
|
|
433
|
-
args: ['-y', '@agilemonster/mcp-server'],
|
|
453
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
434
454
|
env: {
|
|
435
455
|
AGILEMONSTER_API_KEY: apiKey,
|
|
436
456
|
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
|
@@ -459,7 +479,7 @@ export class AgileMonsterOrchestrator {
|
|
|
459
479
|
existing.mcpServers = existing.mcpServers || {};
|
|
460
480
|
existing.mcpServers.agilemonster = {
|
|
461
481
|
command: 'npx',
|
|
462
|
-
args: ['-y', '@agilemonster/mcp-server'],
|
|
482
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
463
483
|
env: {
|
|
464
484
|
AGILEMONSTER_API_KEY: apiKey,
|
|
465
485
|
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
package/dist/server.d.ts
CHANGED
|
@@ -34,4 +34,5 @@ export declare function createServer(client?: AgileMonsterClient): Server<{
|
|
|
34
34
|
} | undefined;
|
|
35
35
|
} | undefined;
|
|
36
36
|
}>;
|
|
37
|
+
export declare function startBuiltInPullPulse(server: Server, client: AgileMonsterClient, intervalMs?: number): NodeJS.Timeout;
|
|
37
38
|
export declare function runStdioServer(): Promise<void>;
|
package/dist/server.js
CHANGED
|
@@ -3,17 +3,18 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
-
import {
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { AgileMonsterClient, MCP_SERVER_VERSION } from './client.js';
|
|
7
8
|
import { AGENT_GUIDELINES_MARKDOWN } from './guidelines.js';
|
|
8
9
|
export function createServer(client = new AgileMonsterClient()) {
|
|
9
10
|
const server = new Server({
|
|
10
11
|
name: 'agilemonster-mcp-server',
|
|
11
|
-
version:
|
|
12
|
+
version: MCP_SERVER_VERSION,
|
|
12
13
|
}, {
|
|
13
14
|
capabilities: {
|
|
14
15
|
tools: {},
|
|
15
16
|
prompts: {},
|
|
16
|
-
resources: {},
|
|
17
|
+
resources: { subscribe: true, listChanged: true },
|
|
17
18
|
},
|
|
18
19
|
});
|
|
19
20
|
// -------------------------------------------------------------------------
|
|
@@ -512,12 +513,30 @@ Please strictly follow the Agile Monster Task Lifecycle Protocol:
|
|
|
512
513
|
const status = args.status;
|
|
513
514
|
const boardId = args.board_id || projectConfig?.board_id;
|
|
514
515
|
const workspaceId = projectConfig?.workspace_id;
|
|
516
|
+
if (!boardId) {
|
|
517
|
+
return {
|
|
518
|
+
isError: true,
|
|
519
|
+
content: [
|
|
520
|
+
{
|
|
521
|
+
type: 'text',
|
|
522
|
+
text: `ā ļø [BOARD SCOPE GUARD] Current project folder "${path.basename(process.cwd())}" is not bound to an Agile Monster board (.agilemonster.json is missing).\n` +
|
|
523
|
+
`Refusing to list tasks across all boards so you never accidentally pick up a task from the wrong project and burn tokens.\n` +
|
|
524
|
+
`Please run "npx -y @agilemonster/mcp-server bootstrap" in this folder or call agilemonster_get_context first to bind the right board.`,
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
515
529
|
const result = await client.listAssigned(provider, status, boardId, workspaceId);
|
|
516
530
|
return {
|
|
517
531
|
content: [
|
|
518
532
|
{
|
|
519
533
|
type: 'text',
|
|
520
|
-
text: JSON.stringify(
|
|
534
|
+
text: JSON.stringify({
|
|
535
|
+
bound_board_id: boardId,
|
|
536
|
+
bound_board_name: projectConfig?.board_name,
|
|
537
|
+
bound_project_name: projectConfig?.project_name || path.basename(process.cwd()),
|
|
538
|
+
...result,
|
|
539
|
+
}, null, 2),
|
|
521
540
|
},
|
|
522
541
|
],
|
|
523
542
|
};
|
|
@@ -526,12 +545,25 @@ Please strictly follow the Agile Monster Task Lifecycle Protocol:
|
|
|
526
545
|
const cardId = args.card_id;
|
|
527
546
|
const provider = args.provider || projectConfig?.provider || 'antigravity';
|
|
528
547
|
const runRef = args.run_ref;
|
|
529
|
-
const
|
|
548
|
+
const expectedBoardId = projectConfig?.board_id;
|
|
549
|
+
if (!expectedBoardId) {
|
|
550
|
+
return {
|
|
551
|
+
isError: true,
|
|
552
|
+
content: [
|
|
553
|
+
{
|
|
554
|
+
type: 'text',
|
|
555
|
+
text: `ā ļø [BOARD SCOPE GUARD] Cannot claim card ${cardId} because this repository folder ("${path.basename(process.cwd())}") has no .agilemonster.json board binding.\n` +
|
|
556
|
+
`Run "npx -y @agilemonster/mcp-server bootstrap" first so we verify this folder matches the card's board.`,
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const card = await client.claimTask(cardId, provider, runRef, expectedBoardId);
|
|
530
562
|
return {
|
|
531
563
|
content: [
|
|
532
564
|
{
|
|
533
565
|
type: 'text',
|
|
534
|
-
text: `Task claimed successfully. Status is now "in_progress".\n${JSON.stringify(card, null, 2)}`,
|
|
566
|
+
text: `Task claimed successfully on board "${card.board_name || expectedBoardId}". Status is now "in_progress".\n${JSON.stringify(card, null, 2)}`,
|
|
535
567
|
},
|
|
536
568
|
],
|
|
537
569
|
};
|
|
@@ -673,8 +705,135 @@ Please strictly follow the Agile Monster Task Lifecycle Protocol:
|
|
|
673
705
|
});
|
|
674
706
|
return server;
|
|
675
707
|
}
|
|
708
|
+
function sendDesktopNotification(title, message) {
|
|
709
|
+
if (process.platform === 'darwin') {
|
|
710
|
+
try {
|
|
711
|
+
const safeTitle = title.replace(/["\\]/g, '');
|
|
712
|
+
const safeMsg = message.replace(/["\\]/g, '').slice(0, 180);
|
|
713
|
+
const child = spawn('osascript', ['-e', `display notification "${safeMsg}" with title "${safeTitle}" sound name "Glass"`], { stdio: 'ignore', detached: true });
|
|
714
|
+
child.unref();
|
|
715
|
+
}
|
|
716
|
+
catch { }
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function syncIdeTaskRules(boardName, queuedCards) {
|
|
720
|
+
const cwd = process.cwd();
|
|
721
|
+
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
722
|
+
const cursorRuleFile = path.join(cursorRulesDir, 'agilemonster-queue.mdc');
|
|
723
|
+
if (queuedCards.length === 0) {
|
|
724
|
+
try {
|
|
725
|
+
if (fs.existsSync(cursorRuleFile)) {
|
|
726
|
+
fs.unlinkSync(cursorRuleFile);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch { }
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const lines = [
|
|
733
|
+
'---',
|
|
734
|
+
'description: Active Agile Monster queued tasks assigned to this repository',
|
|
735
|
+
'globs: ',
|
|
736
|
+
'alwaysApply: true',
|
|
737
|
+
'---',
|
|
738
|
+
`# š Queued Agile Monster Tasks for Board "${boardName}"`,
|
|
739
|
+
'',
|
|
740
|
+
`There ${queuedCards.length === 1 ? 'is 1 queued task' : `are ${queuedCards.length} queued tasks`} assigned on the "${boardName}" board waiting to be claimed:`,
|
|
741
|
+
'',
|
|
742
|
+
];
|
|
743
|
+
for (const c of queuedCards) {
|
|
744
|
+
const worker = c.agent_display_name || c.agent_provider || 'agent';
|
|
745
|
+
const model = c.agent_model && c.agent_model !== 'auto' ? ` (Preferred Model: ${c.agent_model})` : '';
|
|
746
|
+
lines.push(`- **${c.title}** (Card ID: \`${c.id}\` | Assigned to: \`${worker}\`${model})`);
|
|
747
|
+
if (c.description) {
|
|
748
|
+
lines.push(` - Details: ${c.description.replace(/\r?\n/g, ' ').slice(0, 280)}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
lines.push('');
|
|
752
|
+
lines.push('When the user starts a task or asks what is next, call `agilemonster_claim_task` with the Card ID above, implement the solution, and submit it for review with `agilemonster_submit_for_review`.');
|
|
753
|
+
try {
|
|
754
|
+
if (fs.existsSync(path.join(cwd, '.cursor'))) {
|
|
755
|
+
if (!fs.existsSync(cursorRulesDir)) {
|
|
756
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
757
|
+
}
|
|
758
|
+
fs.writeFileSync(cursorRuleFile, lines.join('\n') + '\n', 'utf8');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
catch { }
|
|
762
|
+
}
|
|
763
|
+
function triggerBackgroundSelfUpdate() {
|
|
764
|
+
try {
|
|
765
|
+
// 1. Pre-warm latest @agilemonster/mcp-server in npx cache
|
|
766
|
+
const child = spawn('npx', ['-y', '@agilemonster/mcp-server@latest', '--version'], {
|
|
767
|
+
stdio: 'ignore',
|
|
768
|
+
detached: true,
|
|
769
|
+
});
|
|
770
|
+
child.unref();
|
|
771
|
+
// 2. Ensure local .cursor/mcp.json and .mcp.json use @latest so next IDE reload uses latest
|
|
772
|
+
const cwd = process.cwd();
|
|
773
|
+
for (const rel of ['.cursor/mcp.json', '.mcp.json', '.vscode/mcp.json']) {
|
|
774
|
+
const p = path.join(cwd, rel);
|
|
775
|
+
if (fs.existsSync(p)) {
|
|
776
|
+
try {
|
|
777
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
778
|
+
if (parsed?.mcpServers?.agilemonster) {
|
|
779
|
+
parsed.mcpServers.agilemonster.args = ['-y', '@agilemonster/mcp-server@latest'];
|
|
780
|
+
fs.writeFileSync(p, JSON.stringify(parsed, null, 2), 'utf8');
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
catch { }
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch { }
|
|
788
|
+
}
|
|
789
|
+
export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
790
|
+
const notifiedCardIds = new Set();
|
|
791
|
+
let updateTriggered = false;
|
|
792
|
+
const runPulse = async () => {
|
|
793
|
+
try {
|
|
794
|
+
const config = client.loadProjectConfig();
|
|
795
|
+
if (!config?.board_id || !client.getApiKey()) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const res = await client.pulse({
|
|
799
|
+
boardId: config.board_id,
|
|
800
|
+
provider: 'any',
|
|
801
|
+
projectName: config.project_name || path.basename(process.cwd()),
|
|
802
|
+
projectPath: config.project_path || process.cwd(),
|
|
803
|
+
});
|
|
804
|
+
const queued = res.queued_cards || [];
|
|
805
|
+
syncIdeTaskRules(config.board_name || 'Board', queued);
|
|
806
|
+
for (const card of queued) {
|
|
807
|
+
if (!notifiedCardIds.has(card.id)) {
|
|
808
|
+
notifiedCardIds.add(card.id);
|
|
809
|
+
const worker = card.agent_display_name || card.agent_provider || 'AI Agent';
|
|
810
|
+
const modelTag = card.agent_model && card.agent_model !== 'auto' ? ` [${card.agent_model}]` : '';
|
|
811
|
+
sendDesktopNotification(`Agile Monster -> ${worker}${modelTag}`, `New task queued on ${config.board_name || 'Board'}: "${card.title}"`);
|
|
812
|
+
try {
|
|
813
|
+
await server.sendResourceUpdated({ uri: 'agilemonster://project/context' });
|
|
814
|
+
}
|
|
815
|
+
catch { }
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if ((res.version?.force_update_requested || res.version?.update_available) &&
|
|
819
|
+
!updateTriggered) {
|
|
820
|
+
updateTriggered = true;
|
|
821
|
+
triggerBackgroundSelfUpdate();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
catch {
|
|
825
|
+
// Silent during background pulse
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
setTimeout(runPulse, 2500).unref();
|
|
829
|
+
const timer = setInterval(runPulse, intervalMs);
|
|
830
|
+
timer.unref();
|
|
831
|
+
return timer;
|
|
832
|
+
}
|
|
676
833
|
export async function runStdioServer() {
|
|
677
|
-
const
|
|
834
|
+
const client = new AgileMonsterClient();
|
|
835
|
+
const server = createServer(client);
|
|
678
836
|
const transport = new StdioServerTransport();
|
|
679
837
|
await server.connect(transport);
|
|
838
|
+
startBuiltInPullPulse(server, client, 20000);
|
|
680
839
|
}
|
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;
|
|
@@ -16,6 +18,9 @@ export interface Card {
|
|
|
16
18
|
position?: number;
|
|
17
19
|
due_date?: string | null;
|
|
18
20
|
agent_provider?: string | null;
|
|
21
|
+
agent_id?: string | null;
|
|
22
|
+
agent_model?: string | null;
|
|
23
|
+
agent_display_name?: string | null;
|
|
19
24
|
agent_status?: string | null;
|
|
20
25
|
agent_run_ref?: string | null;
|
|
21
26
|
max_budget_usd?: number | null;
|
|
@@ -57,9 +62,26 @@ export interface BoardAgentConfig {
|
|
|
57
62
|
project_name?: string | null;
|
|
58
63
|
project_path?: string | null;
|
|
59
64
|
git_remote?: string | null;
|
|
65
|
+
mcp_version?: string | null;
|
|
60
66
|
connected?: boolean;
|
|
61
67
|
last_connected_at?: string | null;
|
|
62
68
|
}
|
|
69
|
+
export interface PulseVersionInfo {
|
|
70
|
+
client_version: string | null;
|
|
71
|
+
latest_version: string;
|
|
72
|
+
min_supported_version: string;
|
|
73
|
+
update_available: boolean;
|
|
74
|
+
update_required: boolean;
|
|
75
|
+
force_update_requested: boolean;
|
|
76
|
+
update_command: string;
|
|
77
|
+
}
|
|
78
|
+
export interface PulseResponse {
|
|
79
|
+
board_id: string;
|
|
80
|
+
pulsed_at: string;
|
|
81
|
+
queued_cards: Card[];
|
|
82
|
+
in_progress_cards: Card[];
|
|
83
|
+
version: PulseVersionInfo;
|
|
84
|
+
}
|
|
63
85
|
export interface ContextResponse {
|
|
64
86
|
matched_board: Board | null;
|
|
65
87
|
ai_match?: AiBoardMatch | null;
|
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.7",
|
|
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",
|