@agilemonster/mcp-server 1.0.9 → 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 +1 -1
- package/dist/client.js +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +144 -10
- package/dist/types.d.ts +6 -2
- 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.11";
|
|
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,6 +863,12 @@ function collectProjectSourceFiles(projectPath, keywords) {
|
|
|
862
863
|
}
|
|
863
864
|
catch { }
|
|
864
865
|
}
|
|
866
|
+
// Bonus score for UI components / screens when task mentions UX or UI
|
|
867
|
+
if (ext === '.tsx' || ext === '.jsx' || ext === '.dart' || ext === '.vue') {
|
|
868
|
+
if (lowerRel.includes('component') || lowerRel.includes('screen') || lowerRel.includes('widget') || lowerRel.includes('modal')) {
|
|
869
|
+
score += 8;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
865
872
|
results.push({ rel, score });
|
|
866
873
|
}
|
|
867
874
|
}
|
|
@@ -870,12 +877,22 @@ function collectProjectSourceFiles(projectPath, keywords) {
|
|
|
870
877
|
results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
|
|
871
878
|
return results.slice(0, 220).map((r) => r.rel);
|
|
872
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
|
+
}
|
|
873
890
|
export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
874
891
|
if (!projectPath || !fs.existsSync(projectPath))
|
|
875
892
|
return false;
|
|
876
893
|
if (activeAutonomousCards.has(card.id))
|
|
877
894
|
return false;
|
|
878
|
-
const lockFile = path.join(
|
|
895
|
+
const lockFile = path.join(getAgileMonsterLocksDir(), `task-${card.id.slice(0, 8)}.lock`);
|
|
879
896
|
try {
|
|
880
897
|
if (fs.existsSync(lockFile)) {
|
|
881
898
|
const stat = fs.statSync(lockFile);
|
|
@@ -913,9 +930,9 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
|
913
930
|
action: 'select_files',
|
|
914
931
|
file_index: fileIndex,
|
|
915
932
|
});
|
|
916
|
-
const selectedFiles = (selectionRes.selected_files || []).slice(0, 4);
|
|
933
|
+
const selectedFiles = (selectionRes.target_files || selectionRes.selected_files || []).slice(0, 4);
|
|
917
934
|
if (selectedFiles.length === 0) {
|
|
918
|
-
selectedFiles.push(...fileIndex.slice(0,
|
|
935
|
+
selectedFiles.push(...fileIndex.slice(0, 3));
|
|
919
936
|
}
|
|
920
937
|
const filesPayload = {};
|
|
921
938
|
for (const rel of selectedFiles) {
|
|
@@ -941,30 +958,57 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
|
941
958
|
});
|
|
942
959
|
const edits = editsRes.edits || [];
|
|
943
960
|
const modifiedFiles = [];
|
|
961
|
+
const originalContents = new Map();
|
|
944
962
|
for (const edit of edits) {
|
|
945
|
-
|
|
963
|
+
const relPath = edit.file || edit.path;
|
|
964
|
+
if (!relPath || !edit.search || typeof edit.replace !== 'string')
|
|
946
965
|
continue;
|
|
947
|
-
const fullPath = path.resolve(projectPath,
|
|
966
|
+
const fullPath = path.resolve(projectPath, relPath);
|
|
948
967
|
if (!fullPath.startsWith(path.resolve(projectPath)))
|
|
949
968
|
continue;
|
|
950
969
|
if (!fs.existsSync(fullPath))
|
|
951
970
|
continue;
|
|
952
971
|
const currentContent = fs.readFileSync(fullPath, 'utf8');
|
|
972
|
+
if (!originalContents.has(fullPath)) {
|
|
973
|
+
originalContents.set(fullPath, currentContent);
|
|
974
|
+
}
|
|
953
975
|
if (currentContent.includes(edit.search)) {
|
|
954
976
|
const updatedContent = currentContent.replace(edit.search, edit.replace);
|
|
955
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
|
+
}
|
|
956
985
|
fs.writeFileSync(fullPath, updatedContent, 'utf8');
|
|
957
|
-
if (!modifiedFiles.includes(
|
|
958
|
-
modifiedFiles.push(
|
|
986
|
+
if (!modifiedFiles.includes(relPath)) {
|
|
987
|
+
modifiedFiles.push(relPath);
|
|
959
988
|
}
|
|
960
989
|
}
|
|
961
990
|
}
|
|
962
991
|
}
|
|
963
992
|
if (modifiedFiles.length > 0) {
|
|
964
|
-
|
|
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 ||
|
|
965
1008
|
`Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
|
|
1009
|
+
const summary = `${baseSummary}${diffSnippet}`;
|
|
966
1010
|
const verificationSteps = editsRes.verification_steps ||
|
|
967
|
-
`1.
|
|
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!`;
|
|
968
1012
|
await client.submitReview(card.id, provider, summary, verificationSteps);
|
|
969
1013
|
sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
|
|
970
1014
|
return true;
|
|
@@ -987,6 +1031,87 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
|
987
1031
|
activeAutonomousCards.delete(card.id);
|
|
988
1032
|
}
|
|
989
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
|
+
}
|
|
990
1115
|
export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
991
1116
|
const notifiedCardIds = new Set();
|
|
992
1117
|
let updateTriggered = false;
|
|
@@ -1091,6 +1216,15 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
|
1091
1216
|
await executeQueuedCardAutonomously(client, qCard, targetPath);
|
|
1092
1217
|
}
|
|
1093
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
|
+
}
|
|
1094
1228
|
if ((res.version?.force_update_requested || res.version?.update_available) &&
|
|
1095
1229
|
!updateTriggered) {
|
|
1096
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;
|
|
@@ -99,14 +100,17 @@ export interface PulseResponse {
|
|
|
99
100
|
}>;
|
|
100
101
|
}
|
|
101
102
|
export interface AutonomousEdit {
|
|
102
|
-
path
|
|
103
|
+
path?: string;
|
|
104
|
+
file?: string;
|
|
103
105
|
search: string;
|
|
104
106
|
replace: string;
|
|
107
|
+
explanation?: string;
|
|
105
108
|
}
|
|
106
109
|
export interface AutonomousStepResponse {
|
|
107
110
|
card_id: string;
|
|
108
|
-
action
|
|
111
|
+
action?: 'select_files' | 'generate_edits';
|
|
109
112
|
selected_files?: string[];
|
|
113
|
+
target_files?: string[];
|
|
110
114
|
edits?: AutonomousEdit[];
|
|
111
115
|
summary?: string;
|
|
112
116
|
verification_steps?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
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",
|