feat: replace SpawnDetector with MCP server for spawn1337 protocol

Instead of parsing terminal output for <spawn1337> tags, spawn capabilities
are now exposed as native MCP tools that Claude Code can call directly.
The MCP server (stdio transport) proxies requests to the existing REST API.

- Add src/mcp-server.ts with 6 tools: spawn_agent, list_agents,
  get_agent_status, get_agent_result, send_agent_message, cancel_agent
- Remove src/spawn-detector.ts and all references in session.ts/server.ts
- Add CLAUDEMAN_API_URL env var propagation to sessions and screens
- Write .mcp.json to case directories during creation
- Remove spawn1337 tag documentation from case-template.md
- Add claudeman-mcp bin entry to package.json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-23 23:16:21 +01:00
co-authored by Claude Opus 4.5
parent 7c72802886
commit 99919d6dfb
11 changed files with 1549 additions and 710 deletions
+26 -28
View File
@@ -1036,6 +1036,9 @@ export class WebServer extends EventEmitter {
const claudeMd = generateClaudeMd(name, description || '', templatePath);
writeFileSync(join(casePath, 'CLAUDE.md'), claudeMd);
// Write .mcp.json for Claude Code to discover spawn tools
this.writeMcpConfig(casePath);
this.broadcast('case:created', { name, path: casePath });
return { success: true, case: { name, path: casePath } };
@@ -1173,6 +1176,9 @@ export class WebServer extends EventEmitter {
const claudeMd = generateClaudeMd(caseName, '', templatePath);
writeFileSync(join(casePath, 'CLAUDE.md'), claudeMd);
// Write .mcp.json for Claude Code to discover spawn tools
this.writeMcpConfig(casePath);
this.broadcast('case:created', { name: caseName, path: casePath });
} catch (err) {
return { success: false, error: `Failed to create case: ${getErrorMessage(err)}` };
@@ -1636,34 +1642,6 @@ export class WebServer extends EventEmitter {
this.broadcast('session:ralphCompletionDetected', { sessionId: session.id, phrase });
});
// Spawn1337 protocol events
session.on('spawnRequested', (filePath: string) => {
this.spawnOrchestrator.handleSpawnRequest(
filePath,
session.id,
session.workingDir,
session.parentAgentId ? 1 : 0 // Simple depth tracking
).catch(err => {
console.error(`[Server] Spawn request failed for session ${session.id}:`, getErrorMessage(err));
});
});
session.on('spawnStatusRequested', (agentId: string) => {
const status = this.spawnOrchestrator.getAgentStatus(agentId);
this.broadcast('spawn:statusResponse', { sessionId: session.id, agentId, status });
});
session.on('spawnCancelRequested', (agentId: string) => {
this.spawnOrchestrator.cancelAgent(agentId, 'Cancelled by parent session').catch(err => {
console.error(`[Server] Spawn cancel failed:`, getErrorMessage(err));
});
});
session.on('spawnMessageToChild', (agentId: string, content: string) => {
this.spawnOrchestrator.sendMessageToAgent(agentId, content).catch(err => {
console.error(`[Server] Spawn message failed:`, getErrorMessage(err));
});
});
}
private setupRespawnListeners(sessionId: string, controller: RespawnController): void {
@@ -1829,6 +1807,23 @@ export class WebServer extends EventEmitter {
return undefined;
}
/**
* Write .mcp.json to a case directory for Claude Code to discover spawn MCP tools.
*/
private writeMcpConfig(casePath: string): void {
const projectRoot = join(__dirname, '..', '..');
const mcpServerPath = join(projectRoot, 'dist', 'mcp-server.js');
const mcpConfig = {
mcpServers: {
'claudeman-spawn': {
command: 'node',
args: [mcpServerPath],
},
},
};
writeFileSync(join(casePath, '.mcp.json'), JSON.stringify(mcpConfig, null, 2) + '\n');
}
private async startScheduledRun(prompt: string, workingDir: string, durationMinutes: number): Promise<ScheduledRun> {
const id = uuidv4();
const now = Date.now();
@@ -2164,6 +2159,9 @@ export class WebServer extends EventEmitter {
await this.app.listen({ port: this.port, host: '0.0.0.0' });
console.log(`Claudeman web interface running at http://localhost:${this.port}`);
// Set API URL for child processes (MCP server, spawned sessions)
process.env.CLAUDEMAN_API_URL = `http://localhost:${this.port}`;
// Start scheduled runs cleanup timer
this.scheduledCleanupTimer = setInterval(() => {
this.cleanupScheduledRuns();