@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 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` once to sign in |
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
- If the card's own agent has no CLI here, any other installed one is used and the card says which. The agent
107
- gets the card, the requester's replies on it, and three rules: decide open details the way the codebase
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);
@@ -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
@@ -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
- args: (opts, scratch) => ['exec', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-o', scratch.lastMessageFile, opts.prompt],
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.bins.map((b) => which(b)).find(Boolean) || null;
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.24";
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.24';
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
- 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.`);
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.24",
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",