@agilemonster/mcp-server 1.0.8 → 1.0.10

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 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, DiscoveredProject, PulseResponse } from './types.js';
2
- export declare const MCP_SERVER_VERSION = "1.0.8";
1
+ import { AgileMonsterProjectConfig, AutonomousStepResponse, Board, BoardAgentConfig, Card, ContextResponse, DiscoveredProject, PulseResponse } from './types.js';
2
+ export declare const MCP_SERVER_VERSION = "1.0.10";
3
3
  export declare class AgileMonsterClient {
4
4
  private apiKey;
5
5
  private baseUrl;
@@ -62,4 +62,10 @@ export declare class AgileMonsterClient {
62
62
  }>): Promise<{
63
63
  tasks: Card[];
64
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>;
65
71
  }
package/dist/client.js CHANGED
@@ -1,7 +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.8';
4
+ export const MCP_SERVER_VERSION = '1.0.10';
5
5
  export class AgileMonsterClient {
6
6
  apiKey = '';
7
7
  baseUrl;
@@ -376,4 +376,10 @@ export class AgileMonsterClient {
376
376
  body: JSON.stringify({ provider, tasks }),
377
377
  });
378
378
  }
379
+ async autonomousStep(payload) {
380
+ return this.request('/api/agent/autonomous-step', {
381
+ method: 'POST',
382
+ body: JSON.stringify(payload),
383
+ });
384
+ }
379
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 startBuiltInPullPulse(server: Server, client: AgileMonsterClient, intervalMs?: number): NodeJS.Timeout;
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,6 +786,214 @@ 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
+ // Bonus score for UI components / screens when task mentions UX or UI
866
+ if (ext === '.tsx' || ext === '.jsx' || ext === '.dart' || ext === '.vue') {
867
+ if (lowerRel.includes('component') || lowerRel.includes('screen') || lowerRel.includes('widget') || lowerRel.includes('modal')) {
868
+ score += 8;
869
+ }
870
+ }
871
+ results.push({ rel, score });
872
+ }
873
+ }
874
+ };
875
+ walk(projectPath, 0);
876
+ results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
877
+ return results.slice(0, 220).map((r) => r.rel);
878
+ }
879
+ export async function executeQueuedCardAutonomously(client, card, projectPath) {
880
+ if (!projectPath || !fs.existsSync(projectPath))
881
+ return false;
882
+ if (activeAutonomousCards.has(card.id))
883
+ return false;
884
+ const lockFile = path.join(projectPath, `.agilemonster-task-${card.id.slice(0, 8)}.lock`);
885
+ try {
886
+ if (fs.existsSync(lockFile)) {
887
+ const stat = fs.statSync(lockFile);
888
+ if (Date.now() - stat.mtimeMs < 10 * 60 * 1000) {
889
+ return false; // Already running or recently processed
890
+ }
891
+ }
892
+ fs.writeFileSync(lockFile, JSON.stringify({ card_id: card.id, started_at: new Date().toISOString() }), 'utf8');
893
+ }
894
+ catch {
895
+ return false;
896
+ }
897
+ activeAutonomousCards.add(card.id);
898
+ const provider = card.agent_provider || 'cursor';
899
+ const workerLabel = card.agent_display_name || provider;
900
+ try {
901
+ // 1. Claim the card (automatically moves it from ToDo -> In Progress on the board!)
902
+ await client.claimTask(card.id, provider, `auto-pulse-${Date.now()}`, card.board_id);
903
+ sendDesktopNotification(`Agile Monster -> ${workerLabel}`, `Claimed & moved to In Progress: "${card.title}"`);
904
+ await client.addProgress(card.id, provider, `Autonomous background runner claimed task in \`${projectPath}\` and moved card to **In Progress**. Scanning repository files...`);
905
+ // 2. Build keyword list from card title + description and collect project source files
906
+ const rawText = `${card.title || ''} ${card.description || ''}`.toLowerCase();
907
+ const stopWords = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'need', 'make', 'user', 'use', 'into', 'when', 'through']);
908
+ const keywords = Array.from(new Set(rawText
909
+ .split(/[^a-z0-9]+/)
910
+ .filter((w) => w.length >= 3 && !stopWords.has(w))));
911
+ const fileIndex = collectProjectSourceFiles(projectPath, keywords);
912
+ if (fileIndex.length === 0) {
913
+ await client.addProgress(card.id, provider, `Card is now **In Progress**, but no matching source files were found in \`${projectPath}\`.`);
914
+ return false;
915
+ }
916
+ // 3. Ask Agile Monster Autonomous Step to pick the 1-4 most relevant files
917
+ const selectionRes = await client.autonomousStep({
918
+ card_id: card.id,
919
+ action: 'select_files',
920
+ file_index: fileIndex,
921
+ });
922
+ const selectedFiles = (selectionRes.target_files || selectionRes.selected_files || []).slice(0, 4);
923
+ if (selectedFiles.length === 0) {
924
+ selectedFiles.push(...fileIndex.slice(0, 3));
925
+ }
926
+ const filesPayload = {};
927
+ for (const rel of selectedFiles) {
928
+ const full = path.join(projectPath, rel);
929
+ if (fs.existsSync(full)) {
930
+ try {
931
+ filesPayload[rel] = fs.readFileSync(full, 'utf8');
932
+ }
933
+ catch { }
934
+ }
935
+ }
936
+ if (Object.keys(filesPayload).length === 0) {
937
+ return false;
938
+ }
939
+ await client.addProgress(card.id, provider, `Selected target files for implementation: ${Object.keys(filesPayload)
940
+ .map((f) => `\`${f}\``)
941
+ .join(', ')}. Generating code changes...`);
942
+ // 4. Generate surgical code edits
943
+ const editsRes = await client.autonomousStep({
944
+ card_id: card.id,
945
+ action: 'generate_edits',
946
+ files: filesPayload,
947
+ });
948
+ const edits = editsRes.edits || [];
949
+ const modifiedFiles = [];
950
+ for (const edit of edits) {
951
+ const relPath = edit.file || edit.path;
952
+ if (!relPath || !edit.search || typeof edit.replace !== 'string')
953
+ continue;
954
+ const fullPath = path.resolve(projectPath, relPath);
955
+ if (!fullPath.startsWith(path.resolve(projectPath)))
956
+ continue;
957
+ if (!fs.existsSync(fullPath))
958
+ continue;
959
+ const currentContent = fs.readFileSync(fullPath, 'utf8');
960
+ if (currentContent.includes(edit.search)) {
961
+ const updatedContent = currentContent.replace(edit.search, edit.replace);
962
+ if (updatedContent !== currentContent) {
963
+ fs.writeFileSync(fullPath, updatedContent, 'utf8');
964
+ if (!modifiedFiles.includes(relPath)) {
965
+ modifiedFiles.push(relPath);
966
+ }
967
+ }
968
+ }
969
+ }
970
+ if (modifiedFiles.length > 0) {
971
+ const summary = editsRes.summary ||
972
+ `Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
973
+ const verificationSteps = editsRes.verification_steps ||
974
+ `1. Open ${projectPath} and inspect changes in ${modifiedFiles.join(', ')}\n2. Run the app and verify "${card.title}".`;
975
+ await client.submitReview(card.id, provider, summary, verificationSteps);
976
+ sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
977
+ return true;
978
+ }
979
+ else {
980
+ await client.addProgress(card.id, provider, `Task claimed and moved to **In Progress**. Inspected ${Object.keys(filesPayload)
981
+ .map((f) => `\`${f}\``)
982
+ .join(', ')}. Ready for final IDE review.`);
983
+ return false;
984
+ }
985
+ }
986
+ catch (err) {
987
+ try {
988
+ await client.addProgress(card.id, provider, `Autonomous runner note: ${err?.message || String(err)}`);
989
+ }
990
+ catch { }
991
+ return false;
992
+ }
993
+ finally {
994
+ activeAutonomousCards.delete(card.id);
995
+ }
996
+ }
789
997
  export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
790
998
  const notifiedCardIds = new Set();
791
999
  let updateTriggered = false;
@@ -874,11 +1082,22 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
874
1082
  const modelTag = card.agent_model && card.agent_model !== 'auto' ? ` [${card.agent_model}]` : '';
875
1083
  sendDesktopNotification(`Agile Monster -> ${worker}${modelTag}`, `New task queued on ${config.board_name || 'Board'}: "${card.title}"`);
876
1084
  try {
877
- await server.sendResourceUpdated({ uri: 'agilemonster://project/context' });
1085
+ if (server) {
1086
+ await server.sendResourceUpdated({ uri: 'agilemonster://project/context' });
1087
+ }
878
1088
  }
879
1089
  catch { }
880
1090
  }
881
1091
  }
1092
+ // Zero-touch autonomous execution for any queued cards bound to local folders on this machine
1093
+ const boundQueued = res.all_bound_queued_cards || [];
1094
+ for (const qCard of boundQueued) {
1095
+ const targetPath = (qCard.bound_project_path || '').trim() ||
1096
+ (qCard.board_id === config.board_id ? config.project_path || process.cwd() : '');
1097
+ if (targetPath && fs.existsSync(targetPath)) {
1098
+ await executeQueuedCardAutonomously(client, qCard, targetPath);
1099
+ }
1100
+ }
882
1101
  if ((res.version?.force_update_requested || res.version?.update_available) &&
883
1102
  !updateTriggered) {
884
1103
  updateTriggered = true;
@@ -901,3 +1120,10 @@ export async function runStdioServer() {
901
1120
  await server.connect(transport);
902
1121
  startBuiltInPullPulse(server, client, 20000);
903
1122
  }
1123
+ export async function runBackgroundPulseDaemon(intervalMs = 20000) {
1124
+ const client = new AgileMonsterClient();
1125
+ console.log(`[Agile Monster Pulse Daemon v${MCP_SERVER_VERSION}] Listening for queued agent tasks across all bound repositories (interval: ${intervalMs / 1000}s)...`);
1126
+ startBuiltInPullPulse(null, client, intervalMs);
1127
+ // Keep process alive
1128
+ setInterval(() => { }, 60000);
1129
+ }
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;
@@ -85,6 +87,7 @@ export interface PulseResponse {
85
87
  pulsed_at: string;
86
88
  queued_cards: Card[];
87
89
  in_progress_cards: Card[];
90
+ all_bound_queued_cards?: Card[];
88
91
  version: PulseVersionInfo;
89
92
  saved_bindings?: Array<{
90
93
  board_id: string;
@@ -95,6 +98,22 @@ export interface PulseResponse {
95
98
  project_path?: string | null;
96
99
  }>;
97
100
  }
101
+ export interface AutonomousEdit {
102
+ path?: string;
103
+ file?: string;
104
+ search: string;
105
+ replace: string;
106
+ explanation?: string;
107
+ }
108
+ export interface AutonomousStepResponse {
109
+ card_id: string;
110
+ action?: 'select_files' | 'generate_edits';
111
+ selected_files?: string[];
112
+ target_files?: string[];
113
+ edits?: AutonomousEdit[];
114
+ summary?: string;
115
+ verification_steps?: string;
116
+ }
98
117
  export interface ContextResponse {
99
118
  matched_board: Board | null;
100
119
  ai_match?: AiBoardMatch | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilemonster/mcp-server",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
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",