overmind-mcp
Version:
Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour
232 lines • 9.66 kB
JavaScript
/**
* overmind-client.ts — Native Node.js client for Overmind HTTP MCP
* ═══════════════════════════════════════════════════════════════════
*
* Import this module to programmatically control agents.
* No MCP library needed — raw HTTP + JSON-RPC 2.0.
*
* Usage:
* npm run build && node dist/overmind-client.js
*
* Or import in your TypeScript:
* import { OvermindClient } from './overmind-client.js';
*/
const DEFAULT_BASE = 'http://[::1]:3099/mcp';
const DEFAULT_AUTH = process.env.OVERMIND_AUTH || 'changeme';
const HEALTH_URL = 'http://[::1]:3099/health';
// ─── JSON-RPC helpers ─────────────────────────────────────────────────────────
function jsonrpc(id, method, params = {}) {
return { jsonrpc: '2.0', id, method, params };
}
async function callMcp(url, auth, method, params, timeoutMs = 60_000) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
Authorization: `Bearer ${auth}`,
},
body: JSON.stringify(jsonrpc(Date.now(), method, params)),
signal: AbortSignal.timeout(timeoutMs),
});
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
// SSE stream — read all chunks
const text = await res.text();
// Parse SSE: each line is "data: {json}"
const lines = text.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('data: ')) {
const data = JSON.parse(trimmed.slice(6));
if (data.result)
return { result: data.result };
if (data.error)
return { error: data.error };
}
}
throw new Error('No result in SSE response');
}
// ─── OvermindClient ───────────────────────────────────────────────────────────
export class OvermindClient {
url;
auth;
id = 0;
nextId() {
return ++this.id;
}
constructor(opts = {}) {
this.url = opts.url ?? DEFAULT_BASE;
this.auth = opts.auth ?? DEFAULT_AUTH;
}
// ─── Health ──────────────────────────────────────────────────────────────
async health() {
try {
const res = await fetch(HEALTH_URL, {
headers: { Authorization: `Bearer ${this.auth}` },
signal: AbortSignal.timeout(3000),
});
return res.ok;
}
catch {
return false;
}
}
// ─── Agent CRUD ──────────────────────────────────────────────────────────
/** Create a new agent definition (stored in registry) */
async createAgent(opts) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'create_agent',
arguments: opts,
});
if ('error' in res)
throw new Error(`create_agent failed: ${res.error?.message}`);
return { name: opts.name, runner: opts.runner };
}
/** List all registered agents */
async listAgents() {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'list_agents',
arguments: {},
});
if ('error' in res)
throw new Error(`list_agents failed: ${res.error?.message}`);
const text = res.result.content[0].text;
// Parse the markdown list
return text
.split('\n')
.filter((l) => l.trim().startsWith('- '))
.map((l) => l.trim().slice(2));
}
/** Delete an agent from the registry */
async deleteAgent(name) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'delete_agent',
arguments: { agentName: name },
});
if ('error' in res)
throw new Error(`delete_agent failed: ${res.error?.message}`);
}
// ─── Run agents ─────────────────────────────────────────────────────────
/**
* Run a single agent and wait for its output.
* Timeout is per-agent, not global.
*/
async runAgent(opts) {
const start = Date.now();
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'run_agent',
arguments: {
agentName: opts.agentName,
prompt: opts.prompt,
timeoutMs: opts.timeoutMs ?? 90_000,
},
}, opts.timeoutMs ?? 120_000);
if ('error' in res) {
return {
agentName: opts.agentName,
runner: 'claude',
output: '',
error: res.error?.message,
durationMs: Date.now() - start,
};
}
const result = res.result;
return {
agentName: opts.agentName,
runner: 'claude',
output: result.content?.[0]?.text ?? '',
sessionId: result.sessionId,
durationMs: Date.now() - start,
};
}
/**
* Run multiple agents in parallel. Returns results in the same order.
*/
async runPool(agents) {
return Promise.all(agents.map((a) => this.runAgent({ agentName: a.name, prompt: a.prompt, timeoutMs: a.timeoutMs })));
}
// ─── Lifecycle control ──────────────────────────────────────────────────
/** Get status of a running agent (reads from RAM, no disk I/O) */
async agentStatus(agentName, runner) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'agent_control',
arguments: { agentName, runner, action: 'status' },
});
if ('error' in res)
return `Error: ${res.error?.message}`;
const text = res.result.content[0].text;
return text;
}
/** Stream output from a running agent (non-blocking) */
async agentStream(agentName, runner) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'agent_control',
arguments: { agentName, runner, action: 'stream' },
});
if ('error' in res)
return { output: `Error: ${res.error?.message}`, isComplete: true };
const text = res.result.content[0].text;
const isComplete = text.includes('isComplete: true') || text.includes('**isComplete:** true');
return { output: text, isComplete };
}
/** Wait for an agent to finish (blocking, polls every 1s) */
async agentWait(agentName, timeoutMs = 900_000, runner) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'agent_control',
arguments: { agentName, runner, action: 'wait', timeoutMs },
}, timeoutMs + 10_000);
if ('error' in res)
return `Error: ${res.error?.message}`;
return res.result.content[0].text;
}
/** Kill a running agent */
async agentKill(agentName, runner) {
const res = await callMcp(this.url, this.auth, 'tools/call', {
name: 'agent_control',
arguments: { agentName, runner, action: 'kill' },
});
if ('error' in res)
return `Error: ${res.error?.message}`;
return res.result.content[0].text;
}
}
// ─── CLI demo ────────────────────────────────────────────────────────────────
async function demo() {
const client = new OvermindClient();
console.log('\n═══ Overmind Client Demo ═══\n');
// Health
const ok = await client.health();
console.log(`[health] ${ok ? '✓' : '✗'} Overmind MCP server`);
// List agents
const agents = await client.listAgents();
console.log(`[list_agents] ${agents.length} agents registered`);
// Demo: create + run a probe agent
const TEST_AGENT = 'client_demo_probe';
try {
await client.createAgent({
name: TEST_AGENT,
runner: 'claude',
prompt: 'Réponds exactement: PONG',
});
console.log(`[create_agent] ${TEST_AGENT} créé`);
const result = await client.runAgent({
agentName: TEST_AGENT,
prompt: 'PING',
timeoutMs: 30_000,
});
console.log(`[run_agent] ✓ (${result.durationMs}ms)`);
console.log(` Output: ${result.output.slice(0, 120).trim()}`);
await client.deleteAgent(TEST_AGENT);
console.log(`[delete_agent] ${TEST_AGENT} supprimé`);
}
catch (e) {
console.error('[demo] Erreur:', e);
}
}
// Run demo if executed directly
demo().catch(console.error);
// Export for use as module
export { OvermindClient as default };
//# sourceMappingURL=overmind-client.js.map