@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 +1 -1
- package/dist/client.js +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +159 -7
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
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.
|
|
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
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
|
|
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
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
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",
|