@agilemonster/mcp-server 0.0.0-stage → 1.0.1
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/README.md +130 -2
- package/bin/mcp.js +30 -0
- package/bin/orchestrator.js +92 -0
- package/bin/worker.js +69 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.js +245 -0
- package/dist/guidelines.d.ts +1 -0
- package/dist/guidelines.js +51 -0
- package/dist/orchestrator.d.ts +27 -0
- package/dist/orchestrator.js +306 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.js +680 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/worker.d.ts +40 -0
- package/dist/worker.js +227 -0
- package/package.json +47 -4
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface AgileMonsterProjectConfig {
|
|
2
|
+
workspace_id?: string;
|
|
3
|
+
board_id?: string;
|
|
4
|
+
board_name?: string;
|
|
5
|
+
provider?: string;
|
|
6
|
+
review_list?: string;
|
|
7
|
+
done_list?: string;
|
|
8
|
+
api_key?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Card {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string | null;
|
|
14
|
+
list_id: string;
|
|
15
|
+
board_id: string;
|
|
16
|
+
position?: number;
|
|
17
|
+
due_date?: string | null;
|
|
18
|
+
agent_provider?: string | null;
|
|
19
|
+
agent_status?: string | null;
|
|
20
|
+
agent_run_ref?: string | null;
|
|
21
|
+
max_budget_usd?: number | null;
|
|
22
|
+
max_tokens?: number | null;
|
|
23
|
+
spent_tokens?: number;
|
|
24
|
+
spent_cost_usd?: number;
|
|
25
|
+
board_name?: string;
|
|
26
|
+
list_name?: string;
|
|
27
|
+
created_by?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Board {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description?: string | null;
|
|
33
|
+
workspace_id: string;
|
|
34
|
+
workspace_name?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ColumnList {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
position: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ContextResponse {
|
|
42
|
+
matched_board: Board | null;
|
|
43
|
+
available_boards: Board[];
|
|
44
|
+
lists: ColumnList[];
|
|
45
|
+
active_agent_tasks: Card[];
|
|
46
|
+
supported_providers: string[];
|
|
47
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AgileMonsterClient } from './client.js';
|
|
2
|
+
import { Card } from './types.js';
|
|
3
|
+
export interface WorkerOptions {
|
|
4
|
+
intervalMs?: number;
|
|
5
|
+
heartbeatMs?: number;
|
|
6
|
+
provider?: string;
|
|
7
|
+
boardId?: string;
|
|
8
|
+
runner?: 'aider' | 'claude' | 'custom' | 'shell' | 'simulated';
|
|
9
|
+
customCommand?: string;
|
|
10
|
+
gitBranch?: boolean;
|
|
11
|
+
maxBudgetUsd?: number;
|
|
12
|
+
maxTokens?: number;
|
|
13
|
+
once?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class AgileMonsterWorker {
|
|
16
|
+
private client;
|
|
17
|
+
private isRunning;
|
|
18
|
+
private activeCardId;
|
|
19
|
+
private heartbeatTimer;
|
|
20
|
+
constructor(client?: AgileMonsterClient);
|
|
21
|
+
/**
|
|
22
|
+
* Run one iteration or continuous daemon loop.
|
|
23
|
+
*/
|
|
24
|
+
start(options?: WorkerOptions): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Check for queued tasks and process the first available card.
|
|
27
|
+
*/
|
|
28
|
+
pollAndExecute(options: WorkerOptions): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Execute one assigned task autonomously.
|
|
31
|
+
*/
|
|
32
|
+
executeTask(task: Card, options: WorkerOptions): Promise<void>;
|
|
33
|
+
private startHeartbeat;
|
|
34
|
+
private stopHeartbeat;
|
|
35
|
+
/**
|
|
36
|
+
* Run the configured agent execution engine.
|
|
37
|
+
*/
|
|
38
|
+
private runExecutionEngine;
|
|
39
|
+
private execCommand;
|
|
40
|
+
}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { AgileMonsterClient } from './client.js';
|
|
3
|
+
export class AgileMonsterWorker {
|
|
4
|
+
client;
|
|
5
|
+
isRunning = false;
|
|
6
|
+
activeCardId = null;
|
|
7
|
+
heartbeatTimer = null;
|
|
8
|
+
constructor(client = new AgileMonsterClient()) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run one iteration or continuous daemon loop.
|
|
13
|
+
*/
|
|
14
|
+
async start(options = {}) {
|
|
15
|
+
const projectConfig = this.client.loadProjectConfig();
|
|
16
|
+
const provider = options.provider || projectConfig?.provider || 'antigravity';
|
|
17
|
+
const interval = options.intervalMs || 15000;
|
|
18
|
+
const runner = options.runner || 'simulated';
|
|
19
|
+
console.log('\n======================================================');
|
|
20
|
+
console.log(' 👾 AGILE MONSTER AUTONOMOUS AGENT WORKER ');
|
|
21
|
+
console.log('======================================================\n');
|
|
22
|
+
console.log(`🤖 Agent Provider: ${provider.toUpperCase()}`);
|
|
23
|
+
console.log(`⚙️ Execution Engine: ${runner}`);
|
|
24
|
+
console.log(`🛡️ Circuit-Breaker: Max Budget: $${options.maxBudgetUsd || 5.0}, Max Tokens: ${options.maxTokens || 200000}`);
|
|
25
|
+
console.log(`⏱️ Polling Interval: ${interval / 1000}s`);
|
|
26
|
+
console.log(`🔒 Human Gate: Autonomous tasks are submitted to Review (never self-closed to Done)\n`);
|
|
27
|
+
this.isRunning = true;
|
|
28
|
+
const tick = async () => {
|
|
29
|
+
if (!this.isRunning)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
await this.pollAndExecute(options);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error(`[Worker Error]: ${err.message || err}`);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
await tick();
|
|
39
|
+
if (options.once) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const timer = setInterval(tick, interval);
|
|
43
|
+
process.on('SIGINT', () => {
|
|
44
|
+
console.log('\n🛑 Stopping Agile Monster Worker daemon...');
|
|
45
|
+
this.isRunning = false;
|
|
46
|
+
clearInterval(timer);
|
|
47
|
+
this.stopHeartbeat();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check for queued tasks and process the first available card.
|
|
53
|
+
*/
|
|
54
|
+
async pollAndExecute(options) {
|
|
55
|
+
if (this.activeCardId) {
|
|
56
|
+
// Already running a task
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const projectConfig = this.client.loadProjectConfig();
|
|
60
|
+
const provider = options.provider || projectConfig?.provider || 'antigravity';
|
|
61
|
+
const boardId = options.boardId || projectConfig?.board_id;
|
|
62
|
+
const res = await this.client.listAssigned(provider, 'queued', boardId);
|
|
63
|
+
const tasks = res.cards || [];
|
|
64
|
+
if (tasks.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const task = tasks[0];
|
|
68
|
+
await this.executeTask(task, options);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Execute one assigned task autonomously.
|
|
72
|
+
*/
|
|
73
|
+
async executeTask(task, options) {
|
|
74
|
+
this.activeCardId = task.id;
|
|
75
|
+
const provider = task.agent_provider || options.provider || 'antigravity';
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
78
|
+
console.log(`🚀 Claiming Task: "${task.title}" (ID: ${task.id})`);
|
|
79
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
80
|
+
try {
|
|
81
|
+
// 1. Claim task on board
|
|
82
|
+
const runRef = `worker-${process.pid}-${Date.now().toString(36)}`;
|
|
83
|
+
await this.client.claimTask(task.id, provider, runRef);
|
|
84
|
+
// 2. Configure circuit breaker budget on card if specified
|
|
85
|
+
if (options.maxBudgetUsd || options.maxTokens) {
|
|
86
|
+
await this.client.setBudget(task.id, options.maxBudgetUsd, options.maxTokens);
|
|
87
|
+
}
|
|
88
|
+
// 3. Optional Git Branch isolation
|
|
89
|
+
let previousBranch = '';
|
|
90
|
+
const branchName = `agilemonster/task-${task.id.slice(0, 8)}`;
|
|
91
|
+
if (options.gitBranch !== false) {
|
|
92
|
+
try {
|
|
93
|
+
previousBranch = await this.execCommand('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
94
|
+
previousBranch = previousBranch.trim();
|
|
95
|
+
await this.execCommand('git', ['checkout', '-B', branchName]);
|
|
96
|
+
console.log(`🌿 Switched to isolated git branch: ${branchName}`);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
console.warn(`Git branch switch warning: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 4. Start progress heartbeat
|
|
103
|
+
await this.client.addProgress(task.id, provider, `🚀 Autonomous agent worker started on host. Created isolated branch \`${branchName}\`. Beginning execution.`);
|
|
104
|
+
this.startHeartbeat(task.id, provider, options.heartbeatMs || 30000);
|
|
105
|
+
// 5. Execute runner engine
|
|
106
|
+
console.log(`⚡ Dispatching task to runner: ${options.runner || 'simulated'}`);
|
|
107
|
+
const executionResult = await this.runExecutionEngine(task, options);
|
|
108
|
+
this.stopHeartbeat();
|
|
109
|
+
// 6. Inspect git diff and test output
|
|
110
|
+
let gitDiffSummary = '';
|
|
111
|
+
try {
|
|
112
|
+
const diffStat = await this.execCommand('git', ['diff', '--stat']);
|
|
113
|
+
gitDiffSummary = diffStat.trim();
|
|
114
|
+
}
|
|
115
|
+
catch { }
|
|
116
|
+
const durationSec = Math.round((Date.now() - startTime) / 1000);
|
|
117
|
+
const summary = executionResult.summary ||
|
|
118
|
+
`Autonomous implementation finished in ${durationSec}s.\n\n### Changed Files\n${gitDiffSummary || 'Code modifications applied.'}`;
|
|
119
|
+
const verificationInstructions = executionResult.verificationSteps ||
|
|
120
|
+
`1. Review git branch: \`git checkout ${branchName}\`\n2. Run automated test suite: \`npm test\` or \`flutter test\`\n3. Verify implementation matches acceptance criteria.`;
|
|
121
|
+
// 7. Enforce Human Verification Gate: Submit for Review
|
|
122
|
+
console.log(`\n📋 Submitting task for Human Verification & Testing...`);
|
|
123
|
+
await this.client.submitReview(task.id, provider, summary, verificationInstructions);
|
|
124
|
+
console.log(`✅ Task "${task.title}" successfully moved to Review/Testing list!`);
|
|
125
|
+
console.log(` Reassigned to human requester with verification steps.\n`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error(`❌ Autonomous Worker failed on task ${task.id}:`, err.message || err);
|
|
129
|
+
try {
|
|
130
|
+
await this.client.addProgress(task.id, provider, `⚠️ Autonomous worker encountered an error: ${err.message || err}`);
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
this.stopHeartbeat();
|
|
136
|
+
this.activeCardId = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
startHeartbeat(cardId, provider, intervalMs) {
|
|
140
|
+
let tickCount = 0;
|
|
141
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
142
|
+
tickCount++;
|
|
143
|
+
const elapsedMins = Math.round((tickCount * intervalMs) / 60000);
|
|
144
|
+
try {
|
|
145
|
+
await this.client.addProgress(cardId, provider, `💓 [Worker Heartbeat] Task running autonomously (~${elapsedMins || 1} min elapsed)...`);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
console.warn(`Heartbeat update error: ${e.message}`);
|
|
149
|
+
}
|
|
150
|
+
}, intervalMs);
|
|
151
|
+
}
|
|
152
|
+
stopHeartbeat() {
|
|
153
|
+
if (this.heartbeatTimer) {
|
|
154
|
+
clearInterval(this.heartbeatTimer);
|
|
155
|
+
this.heartbeatTimer = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Run the configured agent execution engine.
|
|
160
|
+
*/
|
|
161
|
+
async runExecutionEngine(task, options) {
|
|
162
|
+
const runner = options.runner || 'simulated';
|
|
163
|
+
switch (runner) {
|
|
164
|
+
case 'aider': {
|
|
165
|
+
const msg = `Implement task: ${task.title}\n\n${task.description || ''}`;
|
|
166
|
+
await this.execCommand('aider', ['--yes', '--message', msg]);
|
|
167
|
+
return {
|
|
168
|
+
summary: `Implemented via headless Aider: ${task.title}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
case 'claude': {
|
|
172
|
+
const prompt = `Implement task: ${task.title}\n\n${task.description || ''}`;
|
|
173
|
+
await this.execCommand('claude', ['-p', prompt]);
|
|
174
|
+
return {
|
|
175
|
+
summary: `Implemented via Claude Code headless CLI: ${task.title}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
case 'shell':
|
|
179
|
+
case 'custom': {
|
|
180
|
+
if (!options.customCommand) {
|
|
181
|
+
throw new Error('customCommand must be specified when using shell/custom runner');
|
|
182
|
+
}
|
|
183
|
+
const [cmd, ...cmdArgs] = options.customCommand.split(' ');
|
|
184
|
+
await this.execCommand(cmd, cmdArgs);
|
|
185
|
+
return {
|
|
186
|
+
summary: `Executed custom command: ${options.customCommand}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
case 'simulated':
|
|
190
|
+
default: {
|
|
191
|
+
// Safe simulated execution for testing worker daemon lifecycle
|
|
192
|
+
console.log(` [Simulated Runner] Analyzing task requirements...`);
|
|
193
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
194
|
+
console.log(` [Simulated Runner] Applying code modifications...`);
|
|
195
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
196
|
+
return {
|
|
197
|
+
summary: `Automated agent pass completed for "${task.title}".\nAll requirements processed.`,
|
|
198
|
+
verificationSteps: `1. Run project test suite.\n2. Confirm acceptance criteria for "${task.title}".\n3. Mark Done once verified.`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
execCommand(command, args) {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const proc = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
206
|
+
let stdout = '';
|
|
207
|
+
let stderr = '';
|
|
208
|
+
proc.stdout.on('data', (d) => {
|
|
209
|
+
stdout += d.toString();
|
|
210
|
+
});
|
|
211
|
+
proc.stderr.on('data', (d) => {
|
|
212
|
+
stderr += d.toString();
|
|
213
|
+
});
|
|
214
|
+
proc.on('close', (code) => {
|
|
215
|
+
if (code === 0) {
|
|
216
|
+
resolve(stdout);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
reject(new Error(`Command "${command} ${args.join(' ')}" failed with code ${code}: ${stderr || stdout}`));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
proc.on('error', (err) => {
|
|
223
|
+
reject(err);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Official Model Context Protocol (MCP) server & Orchestrator for Agile Monster (Cursor, Claude Code, Antigravity, Codex, Hermes, Windsurf)",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"types": "dist/server.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"agilemonster": "bin/mcp.js",
|
|
13
|
+
"agilemonster-mcp": "bin/mcp.js",
|
|
14
|
+
"agilemonster-orchestrator": "bin/orchestrator.js",
|
|
15
|
+
"agilemonster-worker": "bin/worker.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"bin",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"start": "node bin/mcp.js",
|
|
26
|
+
"orchestrator": "node bin/orchestrator.js",
|
|
27
|
+
"worker": "node bin/worker.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"agilemonster",
|
|
33
|
+
"ai-agent",
|
|
34
|
+
"cursor",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"antigravity",
|
|
37
|
+
"codex",
|
|
38
|
+
"hermes-agent"
|
|
39
|
+
],
|
|
40
|
+
"author": "Agile Monster",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.6.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.17.0",
|
|
47
|
+
"typescript": "^5.7.0"
|
|
48
|
+
}
|
|
49
|
+
}
|