@agilemonster/mcp-server 1.0.24 → 1.0.25
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/README.md +12 -3
- package/bin/mcp.js +8 -0
- package/dist/agent-engine.d.ts +13 -0
- package/dist/agent-engine.js +105 -2
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/server.js +15 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,11 +100,20 @@ in the IDE, with the same ability to search the code, open what it finds, run th
|
|
|
100
100
|
| Card's agent | Command line agent used | One-time setup |
|
|
101
101
|
| :--- | :--- | :--- |
|
|
102
102
|
| Cursor | `cursor-agent -p --force` | `curl https://cursor.com/install -fsS \| bash`, then `cursor-agent login` |
|
|
103
|
-
| Claude Code | `claude -p --permission-mode acceptEdits` | `npm install -g @anthropic-ai/claude-code`, then `claude
|
|
103
|
+
| Claude Code | `claude -p --permission-mode acceptEdits` | `npm install -g @anthropic-ai/claude-code`, then `claude auth login` |
|
|
104
104
|
| Codex | `codex exec --sandbox workspace-write` | `npm install -g @openai/codex`, then `codex login` |
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
The runner also finds an agent that is not on your `PATH` — for example the Codex CLI that the Codex app and
|
|
107
|
+
its IDE plugin install, already signed in with your ChatGPT account — so there is often nothing to set up.
|
|
108
|
+
If the card's own agent cannot run here, any other ready one stands in and the card says which, and why.
|
|
109
|
+
|
|
110
|
+
To see what this machine will use, and the one step that would fix an agent that is not ready:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx @agilemonster/mcp-server agents
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The agent gets the card, the requester's replies on it, and three rules: decide open details the way the codebase
|
|
108
117
|
already does instead of asking, check your own work, and do not commit — the runner commits and deploys
|
|
109
118
|
exactly the files the agent changed, after its own build check.
|
|
110
119
|
|
package/bin/mcp.js
CHANGED
|
@@ -23,6 +23,14 @@ 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 === 'agents' || firstArg === 'doctor') {
|
|
27
|
+
// Which coding agent on this machine works a card queued from the board, and what would fix the others
|
|
28
|
+
const { describeAgents, forgetEngineReadiness } = await import('../dist/agent-engine.js');
|
|
29
|
+
const { ensureUserPath } = await import('../dist/runtime.js');
|
|
30
|
+
await ensureUserPath();
|
|
31
|
+
forgetEngineReadiness();
|
|
32
|
+
console.log(await describeAgents());
|
|
33
|
+
process.exit(0);
|
|
26
34
|
} else if (firstArg === 'pulse-daemon' || firstArg === '--pulse-daemon') {
|
|
27
35
|
runBackgroundPulseDaemon(20000).catch((err) => {
|
|
28
36
|
console.error('Fatal Pulse Daemon Error:', err);
|
package/dist/agent-engine.d.ts
CHANGED
|
@@ -35,6 +35,19 @@ export interface ResolvedEngine {
|
|
|
35
35
|
label: string;
|
|
36
36
|
bin: string;
|
|
37
37
|
}
|
|
38
|
+
/** Forget cached sign-in checks (a developer who just signed in should not wait out the cache; tests). */
|
|
39
|
+
export declare function forgetEngineReadiness(): void;
|
|
40
|
+
export interface EngineStatus {
|
|
41
|
+
id: EngineId;
|
|
42
|
+
label: string;
|
|
43
|
+
bin: string | null;
|
|
44
|
+
/** Null when this agent can take a card now, otherwise what stands in the way, in words for the developer. */
|
|
45
|
+
problem: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** Every agent CLI this runner knows, and whether each can take a card on this machine right now. */
|
|
48
|
+
export declare function engineStatuses(): Promise<EngineStatus[]>;
|
|
49
|
+
/** What `agilemonster agents` prints: which agent works a queued card here, and the one step that would fix the others. */
|
|
50
|
+
export declare function describeAgents(): Promise<string>;
|
|
38
51
|
export declare function engineLabelFor(provider: string): string | null;
|
|
39
52
|
/**
|
|
40
53
|
* The agent CLIs this machine can run for a card, best first: the card's own agent, then any other one that
|
package/dist/agent-engine.js
CHANGED
|
@@ -41,7 +41,18 @@ const ENGINES = [
|
|
|
41
41
|
label: 'Claude Code',
|
|
42
42
|
providers: ['claude_code'],
|
|
43
43
|
bins: ['claude'],
|
|
44
|
+
locations: () => [path.join(os.homedir(), '.local', 'bin', 'claude'), path.join(os.homedir(), '.claude', 'local', 'claude')],
|
|
44
45
|
installHint: 'npm install -g @anthropic-ai/claude-code',
|
|
46
|
+
ready: async (bin) => {
|
|
47
|
+
if ((process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN || '').trim())
|
|
48
|
+
return null;
|
|
49
|
+
const status = await exec(bin, ['auth', 'status'], { timeoutMs: 20000 });
|
|
50
|
+
const info = lastJsonValue(status.stdout);
|
|
51
|
+
// Only a plain "not signed in to Anthropic" is certain. Bedrock, Vertex and older CLIs answer differently: let the run tell.
|
|
52
|
+
return info && typeof info === 'object' && info.loggedIn === false && (!info.apiProvider || info.apiProvider === 'firstParty')
|
|
53
|
+
? 'it is installed but not signed in — run `claude auth login` once in a terminal'
|
|
54
|
+
: null;
|
|
55
|
+
},
|
|
45
56
|
args: (opts) => {
|
|
46
57
|
// The prompt directly follows -p; the list-valued flags come last so they cannot swallow it.
|
|
47
58
|
const args = ['-p', opts.prompt, '--output-format', 'json', '--permission-mode', 'acceptEdits'];
|
|
@@ -71,6 +82,7 @@ const ENGINES = [
|
|
|
71
82
|
label: 'Cursor Agent',
|
|
72
83
|
providers: ['cursor'],
|
|
73
84
|
bins: ['cursor-agent'],
|
|
85
|
+
locations: () => [path.join(os.homedir(), '.local', 'bin', 'cursor-agent')],
|
|
74
86
|
installHint: 'curl https://cursor.com/install -fsS | bash',
|
|
75
87
|
ready: async (bin) => {
|
|
76
88
|
if ((process.env.CURSOR_API_KEY || '').trim())
|
|
@@ -106,8 +118,28 @@ const ENGINES = [
|
|
|
106
118
|
label: 'Codex',
|
|
107
119
|
providers: ['codex'],
|
|
108
120
|
bins: ['codex'],
|
|
121
|
+
// The Codex app and its IDE plugin ship the same CLI, already signed in with the developer's ChatGPT account
|
|
122
|
+
locations: () => [
|
|
123
|
+
path.join(os.homedir(), '.codex', 'plugins', '.plugin-appserver', 'codex-cli', 'bin', 'codex'),
|
|
124
|
+
'/Applications/Codex.app/Contents/Resources/codex',
|
|
125
|
+
],
|
|
109
126
|
installHint: 'npm install -g @openai/codex',
|
|
110
|
-
|
|
127
|
+
ready: async (bin) => {
|
|
128
|
+
if ((process.env.CODEX_API_KEY || '').trim())
|
|
129
|
+
return null;
|
|
130
|
+
const status = await exec(bin, ['login', 'status'], { timeoutMs: 20000 });
|
|
131
|
+
return /not logged in/i.test(`${status.stdout}\n${status.stderr}`)
|
|
132
|
+
? 'it is installed but not signed in — run `codex login` once in a terminal'
|
|
133
|
+
: null;
|
|
134
|
+
},
|
|
135
|
+
args: (opts, scratch) => {
|
|
136
|
+
// workspace-write keeps its edits inside the project; network stays on so it can install and build like the IDE does
|
|
137
|
+
const args = ['exec', '--sandbox', 'workspace-write', '-c', 'sandbox_workspace_write.network_access=true', '--skip-git-repo-check'];
|
|
138
|
+
if (opts.model && /^gpt-/i.test(opts.model))
|
|
139
|
+
args.push('-m', opts.model);
|
|
140
|
+
args.push('-o', scratch.lastMessageFile, opts.prompt);
|
|
141
|
+
return args;
|
|
142
|
+
},
|
|
111
143
|
parse: (res, scratch) => {
|
|
112
144
|
let text = '';
|
|
113
145
|
try {
|
|
@@ -124,6 +156,77 @@ function customCommand() {
|
|
|
124
156
|
}
|
|
125
157
|
const READY_TTL_MS = 2 * 60 * 1000;
|
|
126
158
|
const readyCache = new Map();
|
|
159
|
+
/** Forget cached sign-in checks (a developer who just signed in should not wait out the cache; tests). */
|
|
160
|
+
export function forgetEngineReadiness() {
|
|
161
|
+
readyCache.clear();
|
|
162
|
+
}
|
|
163
|
+
function isExecutableFile(file) {
|
|
164
|
+
try {
|
|
165
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
166
|
+
return fs.statSync(file).isFile();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** The engine's CLI: on PATH, or where its installer, IDE plugin or desktop app keeps it. */
|
|
173
|
+
function findBin(spec) {
|
|
174
|
+
for (const bin of spec.bins) {
|
|
175
|
+
const onPath = which(bin);
|
|
176
|
+
if (onPath)
|
|
177
|
+
return onPath;
|
|
178
|
+
}
|
|
179
|
+
for (const candidate of spec.locations ? spec.locations() : []) {
|
|
180
|
+
if (isExecutableFile(candidate))
|
|
181
|
+
return candidate;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
/** Every agent CLI this runner knows, and whether each can take a card on this machine right now. */
|
|
186
|
+
export async function engineStatuses() {
|
|
187
|
+
const out = [];
|
|
188
|
+
for (const spec of ENGINES) {
|
|
189
|
+
const bin = findBin(spec);
|
|
190
|
+
out.push({
|
|
191
|
+
id: spec.id,
|
|
192
|
+
label: spec.label,
|
|
193
|
+
bin,
|
|
194
|
+
problem: bin ? await readiness(spec, bin) : `it is not installed (\`${spec.installHint}\`)`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
/** What `agilemonster agents` prints: which agent works a queued card here, and the one step that would fix the others. */
|
|
200
|
+
export async function describeAgents() {
|
|
201
|
+
const pinned = (process.env.AGILEMONSTER_AGENT_ENGINE || '').trim().toLowerCase();
|
|
202
|
+
const lines = ['Coding agents on this machine that can work a card queued from the board:', ''];
|
|
203
|
+
if (customCommand())
|
|
204
|
+
lines.push(` ✓ Your own command AGILEMONSTER_AGENT_COMMAND=${customCommand()}`, '');
|
|
205
|
+
const statuses = await engineStatuses();
|
|
206
|
+
for (const s of statuses) {
|
|
207
|
+
lines.push(` ${s.problem ? '✗' : '✓'} ${s.label.padEnd(14)} ${s.problem ? s.problem : s.bin}`);
|
|
208
|
+
}
|
|
209
|
+
lines.push('');
|
|
210
|
+
const usable = statuses.filter((s) => !s.problem);
|
|
211
|
+
if (['builtin', 'built-in', 'off', 'none', '0', 'false'].includes(pinned)) {
|
|
212
|
+
lines.push('AGILEMONSTER_AGENT_ENGINE switches agents off: every card runs on the built-in engine (a few files, one pass).');
|
|
213
|
+
}
|
|
214
|
+
else if (customCommand()) {
|
|
215
|
+
lines.push('Every card you created is handed to your own command.');
|
|
216
|
+
}
|
|
217
|
+
else if (usable.length === 0) {
|
|
218
|
+
lines.push('None is ready, so cards run on the built-in engine, which edits a few files in one pass and cannot explore or build.');
|
|
219
|
+
lines.push('Fix any one line above and the next card is worked by a real agent.');
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
for (const s of statuses) {
|
|
223
|
+
const worker = s.problem ? usable[0] : s;
|
|
224
|
+
lines.push(`A card assigned to ${s.label.replace(/ Agent$/, '')} is worked by ${worker.label}${s.problem ? ` (standing in)` : ''}.`);
|
|
225
|
+
}
|
|
226
|
+
lines.push('Cards a teammate created always use the built-in engine: an agent with a shell only works cards you wrote.');
|
|
227
|
+
}
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
127
230
|
async function readiness(spec, bin) {
|
|
128
231
|
if (!spec.ready)
|
|
129
232
|
return null;
|
|
@@ -167,7 +270,7 @@ export async function resolveEngines(provider) {
|
|
|
167
270
|
const notes = [];
|
|
168
271
|
for (const spec of ordered) {
|
|
169
272
|
const isNative = native.includes(spec);
|
|
170
|
-
const bin = spec
|
|
273
|
+
const bin = findBin(spec);
|
|
171
274
|
if (!bin) {
|
|
172
275
|
if (isNative)
|
|
173
276
|
notes.push(`${spec.label}'s command line agent is not installed on this machine (\`${spec.installHint}\`)`);
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ActiveClaim, AgileMonsterProjectConfig, AutonomousStepResponse, CardComment, DeployReport, 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.25";
|
|
3
3
|
/** An API call the server answered with an error status. `status` lets callers tell "refused" from "broken". */
|
|
4
4
|
export declare class ApiError extends Error {
|
|
5
5
|
readonly status: number;
|
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { execSync } from 'node:child_process';
|
|
|
4
4
|
import { resolveDeployProfile } from './deploy-profile.js';
|
|
5
5
|
import { loadMemory } from './deploy-memory.js';
|
|
6
6
|
import { stripRemoteCredentials } from './redact.js';
|
|
7
|
-
export const MCP_SERVER_VERSION = '1.0.
|
|
7
|
+
export const MCP_SERVER_VERSION = '1.0.25';
|
|
8
8
|
/** An API call the server answered with an error status. `status` lets callers tell "refused" from "broken". */
|
|
9
9
|
export class ApiError extends Error {
|
|
10
10
|
status;
|
package/dist/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import { knownSecrets, redactResult } from './redact.js';
|
|
|
13
13
|
import { describeProfile, methodLabel, normalizeMethod, resolveDeployProfile, trustBoundRoute, } from './deploy-profile.js';
|
|
14
14
|
import { addPendingVerification, getCardState, loadMemory, recordAttempt, removePendingVerification, setCardState, setExplicitProfile, } from './deploy-memory.js';
|
|
15
15
|
import { checkCiOnce, describeFailureForHuman, ensureLocalGitExcludes, parseDeployDirective, runDeployment, } from './deploy-runner.js';
|
|
16
|
-
import { baselineFor, buildAgentPrompt, changesSince, clearBaseline, engineLabelFor, markNewFilesForDiff, parseAgentReport, proseOf, resolveEngines, restoreWorkingTree, runEngine, } from './agent-engine.js';
|
|
16
|
+
import { baselineFor, buildAgentPrompt, changesSince, clearBaseline, engineLabelFor, engineStatuses, markNewFilesForDiff, parseAgentReport, proseOf, resolveEngines, restoreWorkingTree, runEngine, } from './agent-engine.js';
|
|
17
17
|
import { addWellKnownToolDirs, agileMonsterHome, configureLogger, ensureUserPath, errorMessage, exec, log, readJson, which, writeJsonAtomic, } from './runtime.js';
|
|
18
18
|
export function createServer(client = new AgileMonsterClient()) {
|
|
19
19
|
const server = new Server({
|
|
@@ -1878,8 +1878,10 @@ async function produceChangeWithAgent(run) {
|
|
|
1878
1878
|
const { client, card, projectPath, provider, workerLabel, lockFile, carryOverFiles } = run;
|
|
1879
1879
|
const { engines, notes } = await resolveEngines(provider);
|
|
1880
1880
|
run.engineNotes.push(...notes);
|
|
1881
|
-
if (engines.length === 0)
|
|
1881
|
+
if (engines.length === 0) {
|
|
1882
|
+
log.info('agent.unavailable', { card: card.id, provider, notes });
|
|
1882
1883
|
return 'unavailable';
|
|
1884
|
+
}
|
|
1883
1885
|
// The agent gets a shell. Like a deploy command, what runs on this machine is for its owner to decide:
|
|
1884
1886
|
// only cards its owner wrote are handed over (and only the owner's replies were kept in the conversation).
|
|
1885
1887
|
if (!run.ownerId || !card.created_by || card.created_by !== run.ownerId) {
|
|
@@ -1931,8 +1933,14 @@ async function produceChangeWithAgent(run) {
|
|
|
1931
1933
|
// 1. Run the first agent that actually starts. One that is logged out or crashes on launch touches nothing.
|
|
1932
1934
|
let engine = engines[0];
|
|
1933
1935
|
let result = null;
|
|
1936
|
+
const nativeLabel = engineLabelFor(provider);
|
|
1934
1937
|
for (const candidate of engines) {
|
|
1935
|
-
|
|
1938
|
+
// A card assigned to Cursor that Codex ends up working must say so, with the one step that fixes it
|
|
1939
|
+
const passedOver = nativeLabel && candidate.label !== nativeLabel ? run.engineNotes.filter((n) => n.startsWith(nativeLabel)) : [];
|
|
1940
|
+
await client.addProgress(card.id, provider, `Autonomous background runner claimed task in \`${projectPath}\` and moved card to **In Progress**. **${candidate.label}** is working on it in the project folder — reading, searching, editing and building, as it would in the IDE.` +
|
|
1941
|
+
(passedOver.length > 0
|
|
1942
|
+
? `\n\n${candidate.label} is standing in for ${nativeLabel}, which cannot run on this machine yet:\n${passedOver.map((n) => `- ${n}`).join('\n')}`
|
|
1943
|
+
: ''));
|
|
1936
1944
|
log.info('agent.start', { card: card.id, engine: candidate.id, project: projectPath });
|
|
1937
1945
|
const attempt = await runEngine(candidate, { prompt, projectPath, model });
|
|
1938
1946
|
log.info('agent.end', { card: card.id, engine: candidate.id, ok: attempt.ok, seconds: Math.round(attempt.durationMs / 1000), failure: attempt.failure });
|
|
@@ -3079,6 +3087,10 @@ export async function runBackgroundPulseDaemon(intervalMs = 20000) {
|
|
|
3079
3087
|
log.error('daemon.uncaught_exception', { error: errorMessage(err), stack: err instanceof Error ? err.stack : undefined });
|
|
3080
3088
|
process.exit(1);
|
|
3081
3089
|
});
|
|
3090
|
+
// Which agent will work a queued card here, on record before the first card arrives
|
|
3091
|
+
engineStatuses()
|
|
3092
|
+
.then((agents) => log.info('daemon.agents', { agents: agents.map((a) => ({ id: a.id, ready: !a.problem, bin: a.bin, problem: a.problem })) }))
|
|
3093
|
+
.catch(() => { });
|
|
3082
3094
|
startBuiltInPullPulse(null, client, intervalMs, 'daemon');
|
|
3083
3095
|
// Watch dist/server.js for updates so launchd reloads new code after a rebuild — but only between jobs,
|
|
3084
3096
|
// never in the middle of an edit or a deployment.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
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",
|