@agilemonster/mcp-server 1.0.10 → 1.0.11

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.11";
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.11';
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()) {
@@ -876,12 +877,22 @@ function collectProjectSourceFiles(projectPath, keywords) {
876
877
  results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
877
878
  return results.slice(0, 220).map((r) => r.rel);
878
879
  }
880
+ function getAgileMonsterLocksDir() {
881
+ const dir = path.join(os.homedir(), '.agilemonster', 'locks');
882
+ try {
883
+ if (!fs.existsSync(dir)) {
884
+ fs.mkdirSync(dir, { recursive: true });
885
+ }
886
+ }
887
+ catch { }
888
+ return dir;
889
+ }
879
890
  export async function executeQueuedCardAutonomously(client, card, projectPath) {
880
891
  if (!projectPath || !fs.existsSync(projectPath))
881
892
  return false;
882
893
  if (activeAutonomousCards.has(card.id))
883
894
  return false;
884
- const lockFile = path.join(projectPath, `.agilemonster-task-${card.id.slice(0, 8)}.lock`);
895
+ const lockFile = path.join(getAgileMonsterLocksDir(), `task-${card.id.slice(0, 8)}.lock`);
885
896
  try {
886
897
  if (fs.existsSync(lockFile)) {
887
898
  const stat = fs.statSync(lockFile);
@@ -947,6 +958,7 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
947
958
  });
948
959
  const edits = editsRes.edits || [];
949
960
  const modifiedFiles = [];
961
+ const originalContents = new Map();
950
962
  for (const edit of edits) {
951
963
  const relPath = edit.file || edit.path;
952
964
  if (!relPath || !edit.search || typeof edit.replace !== 'string')
@@ -957,9 +969,19 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
957
969
  if (!fs.existsSync(fullPath))
958
970
  continue;
959
971
  const currentContent = fs.readFileSync(fullPath, 'utf8');
972
+ if (!originalContents.has(fullPath)) {
973
+ originalContents.set(fullPath, currentContent);
974
+ }
960
975
  if (currentContent.includes(edit.search)) {
961
976
  const updatedContent = currentContent.replace(edit.search, edit.replace);
962
977
  if (updatedContent !== currentContent) {
978
+ // Guard against unbalanced braces introduced by partial block replacement
979
+ const countChar = (s, ch) => s.split(ch).length - 1;
980
+ const searchBraceDelta = countChar(edit.search, '{') - countChar(edit.search, '}');
981
+ const replaceBraceDelta = countChar(edit.replace, '{') - countChar(edit.replace, '}');
982
+ if (searchBraceDelta !== replaceBraceDelta) {
983
+ continue;
984
+ }
963
985
  fs.writeFileSync(fullPath, updatedContent, 'utf8');
964
986
  if (!modifiedFiles.includes(relPath)) {
965
987
  modifiedFiles.push(relPath);
@@ -968,10 +990,25 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
968
990
  }
969
991
  }
970
992
  if (modifiedFiles.length > 0) {
971
- const summary = editsRes.summary ||
993
+ let diffSnippet = '';
994
+ try {
995
+ const quotedFiles = modifiedFiles.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ');
996
+ const rawDiff = execSync(`git diff -- ${quotedFiles}`, {
997
+ cwd: projectPath,
998
+ encoding: 'utf8',
999
+ stdio: ['ignore', 'pipe', 'ignore'],
1000
+ }).trim();
1001
+ if (rawDiff) {
1002
+ const clipped = rawDiff.length > 2500 ? rawDiff.slice(0, 2500) + '\n... (diff truncated)' : rawDiff;
1003
+ diffSnippet = `\n\n### Code Changes (\`git diff\`)\n\`\`\`diff\n${clipped}\n\`\`\``;
1004
+ }
1005
+ }
1006
+ catch { }
1007
+ const baseSummary = editsRes.summary ||
972
1008
  `Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
1009
+ const summary = `${baseSummary}${diffSnippet}`;
973
1010
  const verificationSteps = editsRes.verification_steps ||
974
- `1. Open ${projectPath} and inspect changes in ${modifiedFiles.join(', ')}\n2. Run the app and verify "${card.title}".`;
1011
+ `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
1012
  await client.submitReview(card.id, provider, summary, verificationSteps);
976
1013
  sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
977
1014
  return true;
@@ -994,6 +1031,87 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
994
1031
  activeAutonomousCards.delete(card.id);
995
1032
  }
996
1033
  }
1034
+ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
1035
+ if (!projectPath || !fs.existsSync(projectPath))
1036
+ return false;
1037
+ const shortId = card.id.slice(0, 8);
1038
+ const deployLock = path.join(getAgileMonsterLocksDir(), `deployed-${shortId}.json`);
1039
+ if (fs.existsSync(deployLock)) {
1040
+ return false;
1041
+ }
1042
+ const provider = card.agent_provider || 'cursor';
1043
+ const cleanTitle = (card.title || 'update').replace(/["`\r\n]+/g, ' ').trim();
1044
+ try {
1045
+ fs.writeFileSync(deployLock, JSON.stringify({ card_id: card.id, title: card.title, started_at: new Date().toISOString() }), 'utf8');
1046
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
1047
+ cwd: projectPath,
1048
+ encoding: 'utf8',
1049
+ stdio: ['ignore', 'pipe', 'ignore'],
1050
+ }).trim();
1051
+ if (!branch || branch === 'HEAD')
1052
+ return false;
1053
+ const statusOut = execSync('git status --porcelain -uno', {
1054
+ cwd: projectPath,
1055
+ encoding: 'utf8',
1056
+ stdio: ['ignore', 'pipe', 'ignore'],
1057
+ }).trim();
1058
+ const rawText = `${card.title || ''} ${card.description || ''}`;
1059
+ const isContainerRestartCard = /\b(restart|redeploy|rebuild)\b.*\b(container|docker|server|prod)/i.test(rawText);
1060
+ if (statusOut.length > 0) {
1061
+ execSync('git add -u', { cwd: projectPath, stdio: 'ignore' });
1062
+ execSync(`git commit -m "feat: ${cleanTitle} [AM-${shortId}]"`, {
1063
+ cwd: projectPath,
1064
+ stdio: 'ignore',
1065
+ });
1066
+ execSync(`git push origin ${branch}`, {
1067
+ cwd: projectPath,
1068
+ stdio: 'ignore',
1069
+ });
1070
+ const commitHash = execSync('git rev-parse --short HEAD', {
1071
+ cwd: projectPath,
1072
+ encoding: 'utf8',
1073
+ stdio: ['ignore', 'pipe', 'ignore'],
1074
+ }).trim();
1075
+ 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!`);
1076
+ sendDesktopNotification(`Agile Monster -> Production Deploy`, `Pushed "${card.title}" (${commitHash}) to origin/${branch}!`);
1077
+ return true;
1078
+ }
1079
+ else if (isContainerRestartCard) {
1080
+ execSync(`git commit --allow-empty -m "chore: trigger container redeploy [AM-${shortId}]"`, {
1081
+ cwd: projectPath,
1082
+ stdio: 'ignore',
1083
+ });
1084
+ execSync(`git push origin ${branch}`, {
1085
+ cwd: projectPath,
1086
+ stdio: 'ignore',
1087
+ });
1088
+ const commitHash = execSync('git rev-parse --short HEAD', {
1089
+ cwd: projectPath,
1090
+ encoding: 'utf8',
1091
+ stdio: ['ignore', 'pipe', 'ignore'],
1092
+ }).trim();
1093
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Triggered production container rebuild & restart via \`${commitHash}\` on \`origin/${branch}\`.`);
1094
+ sendDesktopNotification(`Agile Monster -> Container Redeploy`, `Triggered container restart (${commitHash}) on origin/${branch}!`);
1095
+ return true;
1096
+ }
1097
+ else {
1098
+ const headHash = execSync('git rev-parse --short HEAD', {
1099
+ cwd: projectPath,
1100
+ encoding: 'utf8',
1101
+ stdio: ['ignore', 'pipe', 'ignore'],
1102
+ }).trim();
1103
+ 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.`);
1104
+ return true;
1105
+ }
1106
+ }
1107
+ catch (err) {
1108
+ try {
1109
+ await client.addProgress(card.id, provider, `⚠️ **[Auto-Deploy Note]** Attempted to auto-push approved changes in \`${projectPath}\`: ${err?.message || String(err)}`);
1110
+ }
1111
+ catch { }
1112
+ return false;
1113
+ }
1114
+ }
997
1115
  export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
998
1116
  const notifiedCardIds = new Set();
999
1117
  let updateTriggered = false;
@@ -1098,6 +1216,15 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
1098
1216
  await executeQueuedCardAutonomously(client, qCard, targetPath);
1099
1217
  }
1100
1218
  }
1219
+ // Zero-touch auto-commit & push (deploy to production) for cards approved into Production / Done!
1220
+ const boundDone = res.all_bound_done_cards || [];
1221
+ for (const dCard of boundDone) {
1222
+ const targetPath = (dCard.bound_project_path || '').trim() ||
1223
+ (dCard.board_id === config.board_id ? config.project_path || process.cwd() : '');
1224
+ if (targetPath && fs.existsSync(targetPath)) {
1225
+ await deployApprovedCardIfNeeded(client, dCard, targetPath);
1226
+ }
1227
+ }
1101
1228
  if ((res.version?.force_update_requested || res.version?.update_available) &&
1102
1229
  !updateTriggered) {
1103
1230
  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.11",
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",