@agilemonster/mcp-server 1.0.10 → 1.0.12

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/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AgileMonsterProjectConfig, AutonomousStepResponse, Board, BoardAgentConfig, Card, ContextResponse, DiscoveredProject, PulseResponse } from './types.js';
2
- export declare const MCP_SERVER_VERSION = "1.0.10";
2
+ export declare const MCP_SERVER_VERSION = "1.0.12";
3
3
  export declare class AgileMonsterClient {
4
4
  private apiKey;
5
5
  private baseUrl;
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.10';
4
+ export const MCP_SERVER_VERSION = '1.0.12';
5
5
  export class AgileMonsterClient {
6
6
  apiKey = '';
7
7
  baseUrl;
package/dist/server.d.ts CHANGED
@@ -36,6 +36,7 @@ export declare function createServer(client?: AgileMonsterClient): Server<{
36
36
  } | undefined;
37
37
  }>;
38
38
  export declare function executeQueuedCardAutonomously(client: AgileMonsterClient, card: Card, projectPath: string): Promise<boolean>;
39
+ export declare function deployApprovedCardIfNeeded(client: AgileMonsterClient, card: Card, projectPath: string): Promise<boolean>;
39
40
  export declare function startBuiltInPullPulse(server: Server | null, client: AgileMonsterClient, intervalMs?: number): NodeJS.Timeout;
40
41
  export declare function runStdioServer(): Promise<void>;
41
42
  export declare function runBackgroundPulseDaemon(intervalMs?: number): Promise<void>;
package/dist/server.js CHANGED
@@ -3,7 +3,8 @@ 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 { spawn } from 'node:child_process';
6
+ import os from 'node:os';
7
+ import { spawn, execSync } from 'node:child_process';
7
8
  import { AgileMonsterClient, MCP_SERVER_VERSION } from './client.js';
8
9
  import { AGENT_GUIDELINES_MARKDOWN } from './guidelines.js';
9
10
  export function createServer(client = new AgileMonsterClient()) {
@@ -862,26 +863,41 @@ function collectProjectSourceFiles(projectPath, keywords) {
862
863
  }
863
864
  catch { }
864
865
  }
865
- // Bonus score for UI components / screens when task mentions UX or UI
866
+ // Bonus score for UI components / screens AND backend controllers / models
866
867
  if (ext === '.tsx' || ext === '.jsx' || ext === '.dart' || ext === '.vue') {
867
- if (lowerRel.includes('component') || lowerRel.includes('screen') || lowerRel.includes('widget') || lowerRel.includes('modal')) {
868
+ if (lowerRel.includes('component') || lowerRel.includes('screen') || lowerRel.includes('widget') || lowerRel.includes('modal') || lowerRel.includes('model')) {
868
869
  score += 8;
869
870
  }
870
871
  }
872
+ if (ext === '.php' || ext === '.py' || ext === '.ts') {
873
+ if (lowerRel.includes('controller') || lowerRel.includes('service') || lowerRel.includes('route')) {
874
+ score += 10;
875
+ }
876
+ }
871
877
  results.push({ rel, score });
872
878
  }
873
879
  }
874
880
  };
875
881
  walk(projectPath, 0);
876
882
  results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
877
- return results.slice(0, 220).map((r) => r.rel);
883
+ return results.slice(0, 250).map((r) => r.rel);
884
+ }
885
+ function getAgileMonsterLocksDir() {
886
+ const dir = path.join(os.homedir(), '.agilemonster', 'locks');
887
+ try {
888
+ if (!fs.existsSync(dir)) {
889
+ fs.mkdirSync(dir, { recursive: true });
890
+ }
891
+ }
892
+ catch { }
893
+ return dir;
878
894
  }
879
895
  export async function executeQueuedCardAutonomously(client, card, projectPath) {
880
896
  if (!projectPath || !fs.existsSync(projectPath))
881
897
  return false;
882
898
  if (activeAutonomousCards.has(card.id))
883
899
  return false;
884
- const lockFile = path.join(projectPath, `.agilemonster-task-${card.id.slice(0, 8)}.lock`);
900
+ const lockFile = path.join(getAgileMonsterLocksDir(), `task-${card.id.slice(0, 8)}.lock`);
885
901
  try {
886
902
  if (fs.existsSync(lockFile)) {
887
903
  const stat = fs.statSync(lockFile);
@@ -947,6 +963,7 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
947
963
  });
948
964
  const edits = editsRes.edits || [];
949
965
  const modifiedFiles = [];
966
+ const originalContents = new Map();
950
967
  for (const edit of edits) {
951
968
  const relPath = edit.file || edit.path;
952
969
  if (!relPath || !edit.search || typeof edit.replace !== 'string')
@@ -957,10 +974,40 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
957
974
  if (!fs.existsSync(fullPath))
958
975
  continue;
959
976
  const currentContent = fs.readFileSync(fullPath, 'utf8');
977
+ if (!originalContents.has(fullPath)) {
978
+ originalContents.set(fullPath, currentContent);
979
+ }
960
980
  if (currentContent.includes(edit.search)) {
961
981
  const updatedContent = currentContent.replace(edit.search, edit.replace);
962
982
  if (updatedContent !== currentContent) {
983
+ // Guard against unbalanced braces/parens/brackets introduced by partial block replacement
984
+ const countChar = (s, ch) => s.split(ch).length - 1;
985
+ const searchBraceDelta = countChar(edit.search, '{') - countChar(edit.search, '}');
986
+ const replaceBraceDelta = countChar(edit.replace, '{') - countChar(edit.replace, '}');
987
+ const searchParenDelta = countChar(edit.search, '(') - countChar(edit.search, ')');
988
+ const replaceParenDelta = countChar(edit.replace, '(') - countChar(edit.replace, ')');
989
+ if (searchBraceDelta !== replaceBraceDelta || searchParenDelta !== replaceParenDelta) {
990
+ continue;
991
+ }
963
992
  fs.writeFileSync(fullPath, updatedContent, 'utf8');
993
+ // Pre-submit static analysis gate for Dart/Flutter files
994
+ if (relPath.endsWith('.dart')) {
995
+ try {
996
+ execSync(`dart analyze "${fullPath}"`, {
997
+ cwd: projectPath,
998
+ stdio: ['ignore', 'pipe', 'pipe'],
999
+ timeout: 25000,
1000
+ });
1001
+ }
1002
+ catch (analyzeErr) {
1003
+ const out = String(analyzeErr?.stdout || '') + String(analyzeErr?.stderr || '');
1004
+ if (/\berror\s+[•-]/i.test(out)) {
1005
+ // Revert broken Dart edit so it never breaks the build
1006
+ fs.writeFileSync(fullPath, currentContent, 'utf8');
1007
+ continue;
1008
+ }
1009
+ }
1010
+ }
964
1011
  if (!modifiedFiles.includes(relPath)) {
965
1012
  modifiedFiles.push(relPath);
966
1013
  }
@@ -968,10 +1015,25 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
968
1015
  }
969
1016
  }
970
1017
  if (modifiedFiles.length > 0) {
971
- const summary = editsRes.summary ||
1018
+ let diffSnippet = '';
1019
+ try {
1020
+ const quotedFiles = modifiedFiles.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ');
1021
+ const rawDiff = execSync(`git diff -- ${quotedFiles}`, {
1022
+ cwd: projectPath,
1023
+ encoding: 'utf8',
1024
+ stdio: ['ignore', 'pipe', 'ignore'],
1025
+ }).trim();
1026
+ if (rawDiff) {
1027
+ const clipped = rawDiff.length > 2500 ? rawDiff.slice(0, 2500) + '\n... (diff truncated)' : rawDiff;
1028
+ diffSnippet = `\n\n### Code Changes (\`git diff\`)\n\`\`\`diff\n${clipped}\n\`\`\``;
1029
+ }
1030
+ }
1031
+ catch { }
1032
+ const baseSummary = editsRes.summary ||
972
1033
  `Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
1034
+ const summary = `${baseSummary}${diffSnippet}`;
973
1035
  const verificationSteps = editsRes.verification_steps ||
974
- `1. Open ${projectPath} and inspect changes in ${modifiedFiles.join(', ')}\n2. Run the app and verify "${card.title}".`;
1036
+ `1. Review the code diff above in ${modifiedFiles.join(', ')}\n2. Click **Approve & Move to Production** (or drag to Production) to automatically commit, push, and deploy!`;
975
1037
  await client.submitReview(card.id, provider, summary, verificationSteps);
976
1038
  sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
977
1039
  return true;
@@ -994,6 +1056,87 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
994
1056
  activeAutonomousCards.delete(card.id);
995
1057
  }
996
1058
  }
1059
+ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
1060
+ if (!projectPath || !fs.existsSync(projectPath))
1061
+ return false;
1062
+ const shortId = card.id.slice(0, 8);
1063
+ const deployLock = path.join(getAgileMonsterLocksDir(), `deployed-${shortId}.json`);
1064
+ if (fs.existsSync(deployLock)) {
1065
+ return false;
1066
+ }
1067
+ const provider = card.agent_provider || 'cursor';
1068
+ const cleanTitle = (card.title || 'update').replace(/["`\r\n]+/g, ' ').trim();
1069
+ try {
1070
+ fs.writeFileSync(deployLock, JSON.stringify({ card_id: card.id, title: card.title, started_at: new Date().toISOString() }), 'utf8');
1071
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
1072
+ cwd: projectPath,
1073
+ encoding: 'utf8',
1074
+ stdio: ['ignore', 'pipe', 'ignore'],
1075
+ }).trim();
1076
+ if (!branch || branch === 'HEAD')
1077
+ return false;
1078
+ const statusOut = execSync('git status --porcelain -uno', {
1079
+ cwd: projectPath,
1080
+ encoding: 'utf8',
1081
+ stdio: ['ignore', 'pipe', 'ignore'],
1082
+ }).trim();
1083
+ const rawText = `${card.title || ''} ${card.description || ''}`;
1084
+ const isContainerRestartCard = /\b(restart|redeploy|rebuild)\b.*\b(container|docker|server|prod)/i.test(rawText);
1085
+ if (statusOut.length > 0) {
1086
+ execSync('git add -u', { cwd: projectPath, stdio: 'ignore' });
1087
+ execSync(`git commit -m "feat: ${cleanTitle} [AM-${shortId}]"`, {
1088
+ cwd: projectPath,
1089
+ stdio: 'ignore',
1090
+ });
1091
+ execSync(`git push origin ${branch}`, {
1092
+ cwd: projectPath,
1093
+ stdio: 'ignore',
1094
+ });
1095
+ const commitHash = execSync('git rev-parse --short HEAD', {
1096
+ cwd: projectPath,
1097
+ encoding: 'utf8',
1098
+ stdio: ['ignore', 'pipe', 'ignore'],
1099
+ }).trim();
1100
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Card approved into **${card.list_name || 'Production'}** — automatically committed and pushed \`${commitHash}\` to \`origin/${branch}\` to trigger the production CI/CD & container deployment pipeline!`);
1101
+ sendDesktopNotification(`Agile Monster -> Production Deploy`, `Pushed "${card.title}" (${commitHash}) to origin/${branch}!`);
1102
+ return true;
1103
+ }
1104
+ else if (isContainerRestartCard) {
1105
+ execSync(`git commit --allow-empty -m "chore: trigger container redeploy [AM-${shortId}]"`, {
1106
+ cwd: projectPath,
1107
+ stdio: 'ignore',
1108
+ });
1109
+ execSync(`git push origin ${branch}`, {
1110
+ cwd: projectPath,
1111
+ stdio: 'ignore',
1112
+ });
1113
+ const commitHash = execSync('git rev-parse --short HEAD', {
1114
+ cwd: projectPath,
1115
+ encoding: 'utf8',
1116
+ stdio: ['ignore', 'pipe', 'ignore'],
1117
+ }).trim();
1118
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Triggered production container rebuild & restart via \`${commitHash}\` on \`origin/${branch}\`.`);
1119
+ sendDesktopNotification(`Agile Monster -> Container Redeploy`, `Triggered container restart (${commitHash}) on origin/${branch}!`);
1120
+ return true;
1121
+ }
1122
+ else {
1123
+ const headHash = execSync('git rev-parse --short HEAD', {
1124
+ cwd: projectPath,
1125
+ encoding: 'utf8',
1126
+ stdio: ['ignore', 'pipe', 'ignore'],
1127
+ }).trim();
1128
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Verified latest commit \`${headHash}\` is pushed to \`origin/${branch}\` and live in the production CI/CD pipeline.`);
1129
+ return true;
1130
+ }
1131
+ }
1132
+ catch (err) {
1133
+ try {
1134
+ await client.addProgress(card.id, provider, `⚠️ **[Auto-Deploy Note]** Attempted to auto-push approved changes in \`${projectPath}\`: ${err?.message || String(err)}`);
1135
+ }
1136
+ catch { }
1137
+ return false;
1138
+ }
1139
+ }
997
1140
  export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
998
1141
  const notifiedCardIds = new Set();
999
1142
  let updateTriggered = false;
@@ -1098,6 +1241,15 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
1098
1241
  await executeQueuedCardAutonomously(client, qCard, targetPath);
1099
1242
  }
1100
1243
  }
1244
+ // Zero-touch auto-commit & push (deploy to production) for cards approved into Production / Done!
1245
+ const boundDone = res.all_bound_done_cards || [];
1246
+ for (const dCard of boundDone) {
1247
+ const targetPath = (dCard.bound_project_path || '').trim() ||
1248
+ (dCard.board_id === config.board_id ? config.project_path || process.cwd() : '');
1249
+ if (targetPath && fs.existsSync(targetPath)) {
1250
+ await deployApprovedCardIfNeeded(client, dCard, targetPath);
1251
+ }
1252
+ }
1101
1253
  if ((res.version?.force_update_requested || res.version?.update_available) &&
1102
1254
  !updateTriggered) {
1103
1255
  updateTriggered = true;
package/dist/types.d.ts CHANGED
@@ -88,6 +88,7 @@ export interface PulseResponse {
88
88
  queued_cards: Card[];
89
89
  in_progress_cards: Card[];
90
90
  all_bound_queued_cards?: Card[];
91
+ all_bound_done_cards?: Card[];
91
92
  version: PulseVersionInfo;
92
93
  saved_bindings?: Array<{
93
94
  board_id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilemonster/mcp-server",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
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",