@agilemonster/mcp-server 1.0.7 → 1.0.9
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 +6 -1
- package/dist/client.d.ts +14 -2
- package/dist/client.js +97 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +285 -2
- package/dist/types.d.ts +29 -0
- package/package.json +1 -1
package/bin/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runStdioServer } from '../dist/server.js';
|
|
2
|
+
import { runStdioServer, runBackgroundPulseDaemon } from '../dist/server.js';
|
|
3
3
|
import { MCP_SERVER_VERSION } from '../dist/client.js';
|
|
4
4
|
|
|
5
5
|
const cliCommands = new Set([
|
|
@@ -23,6 +23,11 @@ const firstArg = process.argv[2];
|
|
|
23
23
|
if (firstArg === '--version' || firstArg === '-v' || firstArg === 'version') {
|
|
24
24
|
console.log(MCP_SERVER_VERSION);
|
|
25
25
|
process.exit(0);
|
|
26
|
+
} else if (firstArg === 'pulse-daemon' || firstArg === '--pulse-daemon') {
|
|
27
|
+
runBackgroundPulseDaemon(20000).catch((err) => {
|
|
28
|
+
console.error('Fatal Pulse Daemon Error:', err);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
26
31
|
} else if (firstArg && cliCommands.has(firstArg)) {
|
|
27
32
|
// Delegate to orchestrator CLI
|
|
28
33
|
await import('./orchestrator.js');
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AgileMonsterProjectConfig, Board, BoardAgentConfig, Card, ContextResponse, PulseResponse } from './types.js';
|
|
2
|
-
export declare const MCP_SERVER_VERSION = "1.0.
|
|
1
|
+
import { AgileMonsterProjectConfig, AutonomousStepResponse, Board, BoardAgentConfig, Card, ContextResponse, DiscoveredProject, PulseResponse } from './types.js';
|
|
2
|
+
export declare const MCP_SERVER_VERSION = "1.0.9";
|
|
3
3
|
export declare class AgileMonsterClient {
|
|
4
4
|
private apiKey;
|
|
5
5
|
private baseUrl;
|
|
@@ -35,7 +35,13 @@ export declare class AgileMonsterClient {
|
|
|
35
35
|
provider?: string;
|
|
36
36
|
projectName?: string;
|
|
37
37
|
projectPath?: string;
|
|
38
|
+
discoveredProjects?: DiscoveredProject[];
|
|
38
39
|
}): Promise<PulseResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Scan the developer's local workspace roots (sibling folders in parent dev dir, ~/dev, ~/Projects, etc.)
|
|
42
|
+
* to discover real project folders so the web UI can auto-match and let the user pick from a dropdown.
|
|
43
|
+
*/
|
|
44
|
+
discoverLocalProjects(): DiscoveredProject[];
|
|
39
45
|
listAssigned(provider?: string, status?: string, boardId?: string, workspaceId?: string): Promise<{
|
|
40
46
|
provider: string;
|
|
41
47
|
cards: Card[];
|
|
@@ -56,4 +62,10 @@ export declare class AgileMonsterClient {
|
|
|
56
62
|
}>): Promise<{
|
|
57
63
|
tasks: Card[];
|
|
58
64
|
}>;
|
|
65
|
+
autonomousStep(payload: {
|
|
66
|
+
card_id: string;
|
|
67
|
+
action: 'select_files' | 'generate_edits';
|
|
68
|
+
file_index?: string[];
|
|
69
|
+
files?: Record<string, string>;
|
|
70
|
+
}): Promise<AutonomousStepResponse>;
|
|
59
71
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
export const MCP_SERVER_VERSION = '1.0.9';
|
|
4
5
|
export class AgileMonsterClient {
|
|
5
6
|
apiKey = '';
|
|
6
7
|
baseUrl;
|
|
@@ -194,9 +195,98 @@ export class AgileMonsterClient {
|
|
|
194
195
|
project_name: options.projectName,
|
|
195
196
|
project_path: options.projectPath,
|
|
196
197
|
mcp_version: MCP_SERVER_VERSION,
|
|
198
|
+
discovered_projects: options.discoveredProjects,
|
|
197
199
|
}),
|
|
198
200
|
});
|
|
199
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Scan the developer's local workspace roots (sibling folders in parent dev dir, ~/dev, ~/Projects, etc.)
|
|
204
|
+
* to discover real project folders so the web UI can auto-match and let the user pick from a dropdown.
|
|
205
|
+
*/
|
|
206
|
+
discoverLocalProjects() {
|
|
207
|
+
const roots = new Set();
|
|
208
|
+
const parentDir = path.dirname(this.cwd);
|
|
209
|
+
if (parentDir && parentDir !== '/' && parentDir !== this.cwd) {
|
|
210
|
+
roots.add(parentDir);
|
|
211
|
+
}
|
|
212
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || '';
|
|
213
|
+
if (homedir) {
|
|
214
|
+
for (const sub of ['dev', 'Projects', 'Code', 'repos', 'Developer']) {
|
|
215
|
+
const candidate = path.join(homedir, sub);
|
|
216
|
+
if (fs.existsSync(candidate)) {
|
|
217
|
+
roots.add(candidate);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const projectMarkers = [
|
|
222
|
+
'.git',
|
|
223
|
+
'.agilemonster.json',
|
|
224
|
+
'package.json',
|
|
225
|
+
'pubspec.yaml',
|
|
226
|
+
'composer.json',
|
|
227
|
+
'pyproject.toml',
|
|
228
|
+
'requirements.txt',
|
|
229
|
+
'Cargo.toml',
|
|
230
|
+
'go.mod',
|
|
231
|
+
'Podfile',
|
|
232
|
+
'CMakeLists.txt',
|
|
233
|
+
'frontend-flutter',
|
|
234
|
+
'frontend-nextjs',
|
|
235
|
+
'backend-php',
|
|
236
|
+
'src',
|
|
237
|
+
'lib',
|
|
238
|
+
'app',
|
|
239
|
+
];
|
|
240
|
+
const ignoreNames = /^(untitled folder.*|node_modules|vendor|dist|build|archive.*|.*backup.*|\..*)$/i;
|
|
241
|
+
const found = new Map();
|
|
242
|
+
for (const root of roots) {
|
|
243
|
+
let entries = [];
|
|
244
|
+
try {
|
|
245
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
if (!entry.isDirectory())
|
|
252
|
+
continue;
|
|
253
|
+
const name = entry.name.trim();
|
|
254
|
+
if (!name || ignoreNames.test(name))
|
|
255
|
+
continue;
|
|
256
|
+
if (/\s\d+$/.test(name))
|
|
257
|
+
continue; // skip "Folder 2", "Folder 3" copies
|
|
258
|
+
const fullPath = path.join(root, name);
|
|
259
|
+
let isProject = false;
|
|
260
|
+
for (const marker of projectMarkers) {
|
|
261
|
+
if (fs.existsSync(path.join(fullPath, marker))) {
|
|
262
|
+
isProject = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!isProject)
|
|
267
|
+
continue;
|
|
268
|
+
let gitRemote = null;
|
|
269
|
+
if (fs.existsSync(path.join(fullPath, '.git'))) {
|
|
270
|
+
try {
|
|
271
|
+
gitRemote =
|
|
272
|
+
execSync('git config --get remote.origin.url', {
|
|
273
|
+
cwd: fullPath,
|
|
274
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
275
|
+
})
|
|
276
|
+
.toString()
|
|
277
|
+
.trim() || null;
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
}
|
|
281
|
+
found.set(fullPath, {
|
|
282
|
+
name,
|
|
283
|
+
path: fullPath,
|
|
284
|
+
git_remote: gitRemote,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return Array.from(found.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
289
|
+
}
|
|
200
290
|
async listAssigned(provider = 'any', status, boardId, workspaceId) {
|
|
201
291
|
const params = new URLSearchParams();
|
|
202
292
|
if (provider)
|
|
@@ -286,4 +376,10 @@ export class AgileMonsterClient {
|
|
|
286
376
|
body: JSON.stringify({ provider, tasks }),
|
|
287
377
|
});
|
|
288
378
|
}
|
|
379
|
+
async autonomousStep(payload) {
|
|
380
|
+
return this.request('/api/agent/autonomous-step', {
|
|
381
|
+
method: 'POST',
|
|
382
|
+
body: JSON.stringify(payload),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
289
385
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { AgileMonsterClient } from './client.js';
|
|
3
|
+
import { Card } from './types.js';
|
|
3
4
|
export declare function createServer(client?: AgileMonsterClient): Server<{
|
|
4
5
|
method: string;
|
|
5
6
|
params?: {
|
|
@@ -34,5 +35,7 @@ export declare function createServer(client?: AgileMonsterClient): Server<{
|
|
|
34
35
|
} | undefined;
|
|
35
36
|
} | undefined;
|
|
36
37
|
}>;
|
|
37
|
-
export declare function
|
|
38
|
+
export declare function executeQueuedCardAutonomously(client: AgileMonsterClient, card: Card, projectPath: string): Promise<boolean>;
|
|
39
|
+
export declare function startBuiltInPullPulse(server: Server | null, client: AgileMonsterClient, intervalMs?: number): NodeJS.Timeout;
|
|
38
40
|
export declare function runStdioServer(): Promise<void>;
|
|
41
|
+
export declare function runBackgroundPulseDaemon(intervalMs?: number): Promise<void>;
|
package/dist/server.js
CHANGED
|
@@ -786,21 +786,286 @@ function triggerBackgroundSelfUpdate() {
|
|
|
786
786
|
}
|
|
787
787
|
catch { }
|
|
788
788
|
}
|
|
789
|
+
const activeAutonomousCards = new Set();
|
|
790
|
+
function collectProjectSourceFiles(projectPath, keywords) {
|
|
791
|
+
const skipDirs = new Set([
|
|
792
|
+
'node_modules',
|
|
793
|
+
'.git',
|
|
794
|
+
'dist',
|
|
795
|
+
'build',
|
|
796
|
+
'.next',
|
|
797
|
+
'.nuxt',
|
|
798
|
+
'.dart_tool',
|
|
799
|
+
'vendor',
|
|
800
|
+
'coverage',
|
|
801
|
+
'ios',
|
|
802
|
+
'android',
|
|
803
|
+
'macos',
|
|
804
|
+
'linux',
|
|
805
|
+
'windows',
|
|
806
|
+
'web',
|
|
807
|
+
'public',
|
|
808
|
+
'assets',
|
|
809
|
+
'archive',
|
|
810
|
+
'backups',
|
|
811
|
+
]);
|
|
812
|
+
const validExts = new Set(['.tsx', '.ts', '.jsx', '.js', '.dart', '.php', '.py', '.css', '.scss', '.html', '.vue']);
|
|
813
|
+
const results = [];
|
|
814
|
+
const walk = (dir, depth) => {
|
|
815
|
+
if (depth > 6 || results.length > 600)
|
|
816
|
+
return;
|
|
817
|
+
let entries = [];
|
|
818
|
+
try {
|
|
819
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
for (const entry of entries) {
|
|
825
|
+
const name = entry.name;
|
|
826
|
+
if (name.startsWith('.'))
|
|
827
|
+
continue;
|
|
828
|
+
if (/backup|copy|\s\d+/i.test(name))
|
|
829
|
+
continue;
|
|
830
|
+
const full = path.join(dir, name);
|
|
831
|
+
if (entry.isDirectory()) {
|
|
832
|
+
if (skipDirs.has(name.toLowerCase()))
|
|
833
|
+
continue;
|
|
834
|
+
walk(full, depth + 1);
|
|
835
|
+
}
|
|
836
|
+
else if (entry.isFile()) {
|
|
837
|
+
const ext = path.extname(name).toLowerCase();
|
|
838
|
+
if (!validExts.has(ext))
|
|
839
|
+
continue;
|
|
840
|
+
if (/\.(test|spec|min|d)\./i.test(name))
|
|
841
|
+
continue;
|
|
842
|
+
const rel = path.relative(projectPath, full).replace(/\\/g, '/');
|
|
843
|
+
const lowerRel = rel.toLowerCase();
|
|
844
|
+
let score = 0;
|
|
845
|
+
for (const kw of keywords) {
|
|
846
|
+
if (kw.length >= 3 && lowerRel.includes(kw)) {
|
|
847
|
+
score += 25;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// Check file content for keyword hits on promising source files
|
|
851
|
+
if (ext === '.tsx' || ext === '.ts' || ext === '.dart' || ext === '.php' || ext === '.jsx') {
|
|
852
|
+
try {
|
|
853
|
+
const stat = fs.statSync(full);
|
|
854
|
+
if (stat.size < 120000) {
|
|
855
|
+
const contentLower = fs.readFileSync(full, 'utf8').toLowerCase();
|
|
856
|
+
for (const kw of keywords) {
|
|
857
|
+
if (kw.length >= 4 && contentLower.includes(kw)) {
|
|
858
|
+
score += 10;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
catch { }
|
|
864
|
+
}
|
|
865
|
+
results.push({ rel, score });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
walk(projectPath, 0);
|
|
870
|
+
results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
|
|
871
|
+
return results.slice(0, 220).map((r) => r.rel);
|
|
872
|
+
}
|
|
873
|
+
export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
874
|
+
if (!projectPath || !fs.existsSync(projectPath))
|
|
875
|
+
return false;
|
|
876
|
+
if (activeAutonomousCards.has(card.id))
|
|
877
|
+
return false;
|
|
878
|
+
const lockFile = path.join(projectPath, `.agilemonster-task-${card.id.slice(0, 8)}.lock`);
|
|
879
|
+
try {
|
|
880
|
+
if (fs.existsSync(lockFile)) {
|
|
881
|
+
const stat = fs.statSync(lockFile);
|
|
882
|
+
if (Date.now() - stat.mtimeMs < 10 * 60 * 1000) {
|
|
883
|
+
return false; // Already running or recently processed
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
fs.writeFileSync(lockFile, JSON.stringify({ card_id: card.id, started_at: new Date().toISOString() }), 'utf8');
|
|
887
|
+
}
|
|
888
|
+
catch {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
activeAutonomousCards.add(card.id);
|
|
892
|
+
const provider = card.agent_provider || 'cursor';
|
|
893
|
+
const workerLabel = card.agent_display_name || provider;
|
|
894
|
+
try {
|
|
895
|
+
// 1. Claim the card (automatically moves it from ToDo -> In Progress on the board!)
|
|
896
|
+
await client.claimTask(card.id, provider, `auto-pulse-${Date.now()}`, card.board_id);
|
|
897
|
+
sendDesktopNotification(`Agile Monster -> ${workerLabel}`, `Claimed & moved to In Progress: "${card.title}"`);
|
|
898
|
+
await client.addProgress(card.id, provider, `Autonomous background runner claimed task in \`${projectPath}\` and moved card to **In Progress**. Scanning repository files...`);
|
|
899
|
+
// 2. Build keyword list from card title + description and collect project source files
|
|
900
|
+
const rawText = `${card.title || ''} ${card.description || ''}`.toLowerCase();
|
|
901
|
+
const stopWords = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'need', 'make', 'user', 'use', 'into', 'when', 'through']);
|
|
902
|
+
const keywords = Array.from(new Set(rawText
|
|
903
|
+
.split(/[^a-z0-9]+/)
|
|
904
|
+
.filter((w) => w.length >= 3 && !stopWords.has(w))));
|
|
905
|
+
const fileIndex = collectProjectSourceFiles(projectPath, keywords);
|
|
906
|
+
if (fileIndex.length === 0) {
|
|
907
|
+
await client.addProgress(card.id, provider, `Card is now **In Progress**, but no matching source files were found in \`${projectPath}\`.`);
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
// 3. Ask Agile Monster Autonomous Step to pick the 1-4 most relevant files
|
|
911
|
+
const selectionRes = await client.autonomousStep({
|
|
912
|
+
card_id: card.id,
|
|
913
|
+
action: 'select_files',
|
|
914
|
+
file_index: fileIndex,
|
|
915
|
+
});
|
|
916
|
+
const selectedFiles = (selectionRes.selected_files || []).slice(0, 4);
|
|
917
|
+
if (selectedFiles.length === 0) {
|
|
918
|
+
selectedFiles.push(...fileIndex.slice(0, 2));
|
|
919
|
+
}
|
|
920
|
+
const filesPayload = {};
|
|
921
|
+
for (const rel of selectedFiles) {
|
|
922
|
+
const full = path.join(projectPath, rel);
|
|
923
|
+
if (fs.existsSync(full)) {
|
|
924
|
+
try {
|
|
925
|
+
filesPayload[rel] = fs.readFileSync(full, 'utf8');
|
|
926
|
+
}
|
|
927
|
+
catch { }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (Object.keys(filesPayload).length === 0) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
await client.addProgress(card.id, provider, `Selected target files for implementation: ${Object.keys(filesPayload)
|
|
934
|
+
.map((f) => `\`${f}\``)
|
|
935
|
+
.join(', ')}. Generating code changes...`);
|
|
936
|
+
// 4. Generate surgical code edits
|
|
937
|
+
const editsRes = await client.autonomousStep({
|
|
938
|
+
card_id: card.id,
|
|
939
|
+
action: 'generate_edits',
|
|
940
|
+
files: filesPayload,
|
|
941
|
+
});
|
|
942
|
+
const edits = editsRes.edits || [];
|
|
943
|
+
const modifiedFiles = [];
|
|
944
|
+
for (const edit of edits) {
|
|
945
|
+
if (!edit.path || !edit.search || typeof edit.replace !== 'string')
|
|
946
|
+
continue;
|
|
947
|
+
const fullPath = path.resolve(projectPath, edit.path);
|
|
948
|
+
if (!fullPath.startsWith(path.resolve(projectPath)))
|
|
949
|
+
continue;
|
|
950
|
+
if (!fs.existsSync(fullPath))
|
|
951
|
+
continue;
|
|
952
|
+
const currentContent = fs.readFileSync(fullPath, 'utf8');
|
|
953
|
+
if (currentContent.includes(edit.search)) {
|
|
954
|
+
const updatedContent = currentContent.replace(edit.search, edit.replace);
|
|
955
|
+
if (updatedContent !== currentContent) {
|
|
956
|
+
fs.writeFileSync(fullPath, updatedContent, 'utf8');
|
|
957
|
+
if (!modifiedFiles.includes(edit.path)) {
|
|
958
|
+
modifiedFiles.push(edit.path);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (modifiedFiles.length > 0) {
|
|
964
|
+
const summary = editsRes.summary ||
|
|
965
|
+
`Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
|
|
966
|
+
const verificationSteps = editsRes.verification_steps ||
|
|
967
|
+
`1. Open ${projectPath} and inspect changes in ${modifiedFiles.join(', ')}\n2. Run the app and verify "${card.title}".`;
|
|
968
|
+
await client.submitReview(card.id, provider, summary, verificationSteps);
|
|
969
|
+
sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
await client.addProgress(card.id, provider, `Task claimed and moved to **In Progress**. Inspected ${Object.keys(filesPayload)
|
|
974
|
+
.map((f) => `\`${f}\``)
|
|
975
|
+
.join(', ')}. Ready for final IDE review.`);
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
catch (err) {
|
|
980
|
+
try {
|
|
981
|
+
await client.addProgress(card.id, provider, `Autonomous runner note: ${err?.message || String(err)}`);
|
|
982
|
+
}
|
|
983
|
+
catch { }
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
finally {
|
|
987
|
+
activeAutonomousCards.delete(card.id);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
789
990
|
export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
790
991
|
const notifiedCardIds = new Set();
|
|
791
992
|
let updateTriggered = false;
|
|
993
|
+
let pulseCount = 0;
|
|
994
|
+
const autoProvisionSavedBindings = (apiKey, bindings) => {
|
|
995
|
+
for (const b of bindings) {
|
|
996
|
+
const projPath = (b.project_path || '').trim();
|
|
997
|
+
if (!projPath || !fs.existsSync(projPath))
|
|
998
|
+
continue;
|
|
999
|
+
const amConfigPath = path.join(projPath, '.agilemonster.json');
|
|
1000
|
+
let needsWrite = true;
|
|
1001
|
+
if (fs.existsSync(amConfigPath)) {
|
|
1002
|
+
try {
|
|
1003
|
+
const existing = JSON.parse(fs.readFileSync(amConfigPath, 'utf8'));
|
|
1004
|
+
if (existing?.board_id === b.board_id) {
|
|
1005
|
+
needsWrite = false;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
catch { }
|
|
1009
|
+
}
|
|
1010
|
+
if (needsWrite) {
|
|
1011
|
+
try {
|
|
1012
|
+
fs.writeFileSync(amConfigPath, JSON.stringify({
|
|
1013
|
+
workspace_id: b.workspace_id,
|
|
1014
|
+
board_id: b.board_id,
|
|
1015
|
+
board_name: b.board_name,
|
|
1016
|
+
project_name: b.project_name || path.basename(projPath),
|
|
1017
|
+
project_path: projPath,
|
|
1018
|
+
provider: b.provider || 'cursor',
|
|
1019
|
+
review_list: 'In Review',
|
|
1020
|
+
done_list: 'Done',
|
|
1021
|
+
api_key: apiKey,
|
|
1022
|
+
}, null, 2), 'utf8');
|
|
1023
|
+
// Also ensure .cursor/mcp.json and .mcp.json exist in that bound project folder
|
|
1024
|
+
const mcpPayload = JSON.stringify({
|
|
1025
|
+
mcpServers: {
|
|
1026
|
+
agilemonster: {
|
|
1027
|
+
command: 'npx',
|
|
1028
|
+
args: ['-y', '@agilemonster/mcp-server@latest'],
|
|
1029
|
+
env: {
|
|
1030
|
+
AGILEMONSTER_API_KEY: apiKey,
|
|
1031
|
+
AGILEMONSTER_API_URL: 'https://api.agilemonster.com',
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
}, null, 2);
|
|
1036
|
+
const cursorDir = path.join(projPath, '.cursor');
|
|
1037
|
+
if (!fs.existsSync(cursorDir))
|
|
1038
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
1039
|
+
const cursorMcp = path.join(cursorDir, 'mcp.json');
|
|
1040
|
+
if (!fs.existsSync(cursorMcp))
|
|
1041
|
+
fs.writeFileSync(cursorMcp, mcpPayload, 'utf8');
|
|
1042
|
+
const claudeMcp = path.join(projPath, '.mcp.json');
|
|
1043
|
+
if (!fs.existsSync(claudeMcp))
|
|
1044
|
+
fs.writeFileSync(claudeMcp, mcpPayload, 'utf8');
|
|
1045
|
+
}
|
|
1046
|
+
catch { }
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
792
1050
|
const runPulse = async () => {
|
|
793
1051
|
try {
|
|
794
1052
|
const config = client.loadProjectConfig();
|
|
795
|
-
|
|
1053
|
+
const apiKey = client.getApiKey();
|
|
1054
|
+
if (!config?.board_id || !apiKey) {
|
|
796
1055
|
return;
|
|
797
1056
|
}
|
|
1057
|
+
const includeDiscovery = pulseCount === 0 || pulseCount % 15 === 0;
|
|
1058
|
+
pulseCount++;
|
|
798
1059
|
const res = await client.pulse({
|
|
799
1060
|
boardId: config.board_id,
|
|
800
1061
|
provider: 'any',
|
|
801
1062
|
projectName: config.project_name || path.basename(process.cwd()),
|
|
802
1063
|
projectPath: config.project_path || process.cwd(),
|
|
1064
|
+
discoveredProjects: includeDiscovery ? client.discoverLocalProjects() : undefined,
|
|
803
1065
|
});
|
|
1066
|
+
if (res.saved_bindings && res.saved_bindings.length > 0) {
|
|
1067
|
+
autoProvisionSavedBindings(apiKey, res.saved_bindings);
|
|
1068
|
+
}
|
|
804
1069
|
const queued = res.queued_cards || [];
|
|
805
1070
|
syncIdeTaskRules(config.board_name || 'Board', queued);
|
|
806
1071
|
for (const card of queued) {
|
|
@@ -810,11 +1075,22 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
|
810
1075
|
const modelTag = card.agent_model && card.agent_model !== 'auto' ? ` [${card.agent_model}]` : '';
|
|
811
1076
|
sendDesktopNotification(`Agile Monster -> ${worker}${modelTag}`, `New task queued on ${config.board_name || 'Board'}: "${card.title}"`);
|
|
812
1077
|
try {
|
|
813
|
-
|
|
1078
|
+
if (server) {
|
|
1079
|
+
await server.sendResourceUpdated({ uri: 'agilemonster://project/context' });
|
|
1080
|
+
}
|
|
814
1081
|
}
|
|
815
1082
|
catch { }
|
|
816
1083
|
}
|
|
817
1084
|
}
|
|
1085
|
+
// Zero-touch autonomous execution for any queued cards bound to local folders on this machine
|
|
1086
|
+
const boundQueued = res.all_bound_queued_cards || [];
|
|
1087
|
+
for (const qCard of boundQueued) {
|
|
1088
|
+
const targetPath = (qCard.bound_project_path || '').trim() ||
|
|
1089
|
+
(qCard.board_id === config.board_id ? config.project_path || process.cwd() : '');
|
|
1090
|
+
if (targetPath && fs.existsSync(targetPath)) {
|
|
1091
|
+
await executeQueuedCardAutonomously(client, qCard, targetPath);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
818
1094
|
if ((res.version?.force_update_requested || res.version?.update_available) &&
|
|
819
1095
|
!updateTriggered) {
|
|
820
1096
|
updateTriggered = true;
|
|
@@ -837,3 +1113,10 @@ export async function runStdioServer() {
|
|
|
837
1113
|
await server.connect(transport);
|
|
838
1114
|
startBuiltInPullPulse(server, client, 20000);
|
|
839
1115
|
}
|
|
1116
|
+
export async function runBackgroundPulseDaemon(intervalMs = 20000) {
|
|
1117
|
+
const client = new AgileMonsterClient();
|
|
1118
|
+
console.log(`[Agile Monster Pulse Daemon v${MCP_SERVER_VERSION}] Listening for queued agent tasks across all bound repositories (interval: ${intervalMs / 1000}s)...`);
|
|
1119
|
+
startBuiltInPullPulse(null, client, intervalMs);
|
|
1120
|
+
// Keep process alive
|
|
1121
|
+
setInterval(() => { }, 60000);
|
|
1122
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface Card {
|
|
|
30
30
|
board_name?: string;
|
|
31
31
|
list_name?: string;
|
|
32
32
|
created_by?: string;
|
|
33
|
+
bound_project_path?: string | null;
|
|
34
|
+
bound_project_name?: string | null;
|
|
33
35
|
}
|
|
34
36
|
export interface Board {
|
|
35
37
|
id: string;
|
|
@@ -75,12 +77,39 @@ export interface PulseVersionInfo {
|
|
|
75
77
|
force_update_requested: boolean;
|
|
76
78
|
update_command: string;
|
|
77
79
|
}
|
|
80
|
+
export interface DiscoveredProject {
|
|
81
|
+
name: string;
|
|
82
|
+
path: string;
|
|
83
|
+
git_remote?: string | null;
|
|
84
|
+
}
|
|
78
85
|
export interface PulseResponse {
|
|
79
86
|
board_id: string;
|
|
80
87
|
pulsed_at: string;
|
|
81
88
|
queued_cards: Card[];
|
|
82
89
|
in_progress_cards: Card[];
|
|
90
|
+
all_bound_queued_cards?: Card[];
|
|
83
91
|
version: PulseVersionInfo;
|
|
92
|
+
saved_bindings?: Array<{
|
|
93
|
+
board_id: string;
|
|
94
|
+
board_name: string;
|
|
95
|
+
workspace_id: string;
|
|
96
|
+
provider: string;
|
|
97
|
+
project_name: string;
|
|
98
|
+
project_path?: string | null;
|
|
99
|
+
}>;
|
|
100
|
+
}
|
|
101
|
+
export interface AutonomousEdit {
|
|
102
|
+
path: string;
|
|
103
|
+
search: string;
|
|
104
|
+
replace: string;
|
|
105
|
+
}
|
|
106
|
+
export interface AutonomousStepResponse {
|
|
107
|
+
card_id: string;
|
|
108
|
+
action: 'select_files' | 'generate_edits';
|
|
109
|
+
selected_files?: string[];
|
|
110
|
+
edits?: AutonomousEdit[];
|
|
111
|
+
summary?: string;
|
|
112
|
+
verification_steps?: string;
|
|
84
113
|
}
|
|
85
114
|
export interface ContextResponse {
|
|
86
115
|
matched_board: Board | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
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",
|