apteva 0.4.8 → 0.4.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apteva",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Run AI agents locally. Multi-provider support for Claude, GPT, Gemini, Llama, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,499 @@
1
+ // Built-in MCP server that exposes the Apteva platform API as MCP tools
2
+ // This allows the meta agent (Apteva Assistant) to control the platform
3
+
4
+ import { AgentDB, ProjectDB, McpServerDB, TelemetryDB, generateId } from "./db";
5
+ import { getProvidersWithStatus, PROVIDERS } from "./providers";
6
+ import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch } from "./routes/api/agent-utils";
7
+ import { agentProcesses } from "./server";
8
+
9
+ // MCP Protocol version
10
+ const PROTOCOL_VERSION = "2024-11-05";
11
+
12
+ interface JsonRpcRequest {
13
+ jsonrpc: "2.0";
14
+ id: number;
15
+ method: string;
16
+ params?: any;
17
+ }
18
+
19
+ interface JsonRpcResponse {
20
+ jsonrpc: "2.0";
21
+ id: number;
22
+ result?: unknown;
23
+ error?: { code: number; message: string; data?: unknown };
24
+ }
25
+
26
+ // Tool definitions
27
+ const PLATFORM_TOOLS = [
28
+ {
29
+ name: "list_agents",
30
+ description: "List all agents on the platform. Optionally filter by project ID.",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ project_id: { type: "string", description: "Filter by project ID (optional)" },
35
+ },
36
+ },
37
+ },
38
+ {
39
+ name: "get_agent",
40
+ description: "Get detailed information about a specific agent by ID.",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ agent_id: { type: "string", description: "The agent ID" },
45
+ },
46
+ required: ["agent_id"],
47
+ },
48
+ },
49
+ {
50
+ name: "create_agent",
51
+ description: "Create a new AI agent. Requires a name, provider, and model. The provider must have an API key configured.",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ name: { type: "string", description: "Agent name" },
56
+ provider: { type: "string", description: "LLM provider ID (e.g. anthropic, openai, groq, gemini, xai, together, fireworks, ollama)" },
57
+ model: { type: "string", description: "Model ID (e.g. claude-sonnet-4-5, gpt-4o, llama-3.3-70b-versatile)" },
58
+ system_prompt: { type: "string", description: "System prompt for the agent (optional)" },
59
+ project_id: { type: "string", description: "Project ID to assign the agent to (optional)" },
60
+ features: {
61
+ type: "object",
62
+ description: "Feature flags (optional). All default to false.",
63
+ properties: {
64
+ memory: { type: "boolean" },
65
+ tasks: { type: "boolean" },
66
+ vision: { type: "boolean" },
67
+ mcp: { type: "boolean" },
68
+ files: { type: "boolean" },
69
+ },
70
+ },
71
+ },
72
+ required: ["name", "provider", "model"],
73
+ },
74
+ },
75
+ {
76
+ name: "update_agent",
77
+ description: "Update an existing agent's configuration. Only provide fields you want to change.",
78
+ inputSchema: {
79
+ type: "object",
80
+ properties: {
81
+ agent_id: { type: "string", description: "The agent ID to update" },
82
+ name: { type: "string", description: "New name" },
83
+ model: { type: "string", description: "New model ID" },
84
+ provider: { type: "string", description: "New provider ID" },
85
+ system_prompt: { type: "string", description: "New system prompt" },
86
+ project_id: { type: "string", description: "New project ID (or null to unassign)" },
87
+ features: { type: "object", description: "Feature flags to update" },
88
+ },
89
+ required: ["agent_id"],
90
+ },
91
+ },
92
+ {
93
+ name: "delete_agent",
94
+ description: "Delete an agent. The agent must be stopped first.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ agent_id: { type: "string", description: "The agent ID to delete" },
99
+ },
100
+ required: ["agent_id"],
101
+ },
102
+ },
103
+ {
104
+ name: "start_agent",
105
+ description: "Start a stopped agent. The agent's provider must have an API key configured.",
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ agent_id: { type: "string", description: "The agent ID to start" },
110
+ },
111
+ required: ["agent_id"],
112
+ },
113
+ },
114
+ {
115
+ name: "stop_agent",
116
+ description: "Stop a running agent.",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ agent_id: { type: "string", description: "The agent ID to stop" },
121
+ },
122
+ required: ["agent_id"],
123
+ },
124
+ },
125
+ {
126
+ name: "list_projects",
127
+ description: "List all projects with their agent counts.",
128
+ inputSchema: {
129
+ type: "object",
130
+ properties: {},
131
+ },
132
+ },
133
+ {
134
+ name: "create_project",
135
+ description: "Create a new project for organizing agents.",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {
139
+ name: { type: "string", description: "Project name" },
140
+ description: { type: "string", description: "Project description (optional)" },
141
+ color: { type: "string", description: "Hex color code (optional, default #6366f1)" },
142
+ },
143
+ required: ["name"],
144
+ },
145
+ },
146
+ {
147
+ name: "list_providers",
148
+ description: "List all available LLM providers and their configuration status (which have API keys).",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {},
152
+ },
153
+ },
154
+ {
155
+ name: "list_mcp_servers",
156
+ description: "List all configured MCP servers (tool integrations).",
157
+ inputSchema: {
158
+ type: "object",
159
+ properties: {},
160
+ },
161
+ },
162
+ {
163
+ name: "get_dashboard_stats",
164
+ description: "Get platform overview stats: agent counts, task counts, provider counts.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {},
168
+ },
169
+ },
170
+ {
171
+ name: "send_message",
172
+ description: "Send a chat message to a running agent and get the response.",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ agent_id: { type: "string", description: "The agent ID to message" },
177
+ message: { type: "string", description: "The message to send" },
178
+ },
179
+ required: ["agent_id", "message"],
180
+ },
181
+ },
182
+ ];
183
+
184
+ // Tool execution handlers
185
+ async function executeTool(name: string, args: Record<string, any>): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
186
+ try {
187
+ switch (name) {
188
+ case "list_agents": {
189
+ const agents = args.project_id
190
+ ? AgentDB.findByProject(args.project_id)
191
+ : AgentDB.findAll();
192
+ // Exclude meta agent from list
193
+ const filtered = agents.filter(a => a.id !== META_AGENT_ID);
194
+ const result = filtered.map(a => ({
195
+ id: a.id,
196
+ name: a.name,
197
+ provider: a.provider,
198
+ model: a.model,
199
+ status: a.status,
200
+ port: a.port,
201
+ projectId: a.project_id,
202
+ }));
203
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
204
+ }
205
+
206
+ case "get_agent": {
207
+ const agent = AgentDB.findById(args.agent_id);
208
+ if (!agent) {
209
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
210
+ }
211
+ return { content: [{ type: "text", text: JSON.stringify(toApiAgent(agent), null, 2) }] };
212
+ }
213
+
214
+ case "create_agent": {
215
+ // Validate provider exists
216
+ const providerId = args.provider as keyof typeof PROVIDERS;
217
+ const provider = PROVIDERS[providerId];
218
+ if (!provider || provider.type !== "llm") {
219
+ return { content: [{ type: "text", text: `Invalid provider: ${args.provider}. Valid providers: ${Object.values(PROVIDERS).filter(p => p.type === "llm").map(p => p.id).join(", ")}` }], isError: true };
220
+ }
221
+
222
+ const id = generateId();
223
+ const agent = AgentDB.create({
224
+ id,
225
+ name: args.name,
226
+ model: args.model,
227
+ provider: args.provider,
228
+ system_prompt: args.system_prompt || `You are ${args.name}, a helpful AI assistant.`,
229
+ features: {
230
+ memory: args.features?.memory ?? false,
231
+ tasks: args.features?.tasks ?? false,
232
+ vision: args.features?.vision ?? false,
233
+ operator: false,
234
+ mcp: args.features?.mcp ?? false,
235
+ realtime: false,
236
+ files: args.features?.files ?? false,
237
+ agents: false,
238
+ },
239
+ mcp_servers: [],
240
+ skills: [],
241
+ project_id: args.project_id || null,
242
+ });
243
+
244
+ return { content: [{ type: "text", text: `Agent created successfully:\n${JSON.stringify({ id: agent.id, name: agent.name, provider: agent.provider, model: agent.model, port: agent.port }, null, 2)}` }] };
245
+ }
246
+
247
+ case "update_agent": {
248
+ const agent = AgentDB.findById(args.agent_id);
249
+ if (!agent) {
250
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
251
+ }
252
+
253
+ const updates: any = {};
254
+ if (args.name !== undefined) updates.name = args.name;
255
+ if (args.model !== undefined) updates.model = args.model;
256
+ if (args.provider !== undefined) updates.provider = args.provider;
257
+ if (args.system_prompt !== undefined) updates.system_prompt = args.system_prompt;
258
+ if (args.project_id !== undefined) updates.project_id = args.project_id;
259
+ if (args.features !== undefined) {
260
+ updates.features = { ...agent.features, ...args.features };
261
+ }
262
+
263
+ const updated = AgentDB.update(args.agent_id, updates);
264
+ return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify({ id: updated?.id, name: updated?.name }, null, 2)}` }] };
265
+ }
266
+
267
+ case "delete_agent": {
268
+ const agent = AgentDB.findById(args.agent_id);
269
+ if (!agent) {
270
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
271
+ }
272
+ if (agent.status === "running") {
273
+ return { content: [{ type: "text", text: "Cannot delete a running agent. Stop it first." }], isError: true };
274
+ }
275
+ if (agent.id === META_AGENT_ID) {
276
+ return { content: [{ type: "text", text: "Cannot delete the Apteva Assistant." }], isError: true };
277
+ }
278
+ AgentDB.delete(args.agent_id);
279
+ return { content: [{ type: "text", text: `Agent deleted: ${agent.name} (${agent.id})` }] };
280
+ }
281
+
282
+ case "start_agent": {
283
+ const agent = AgentDB.findById(args.agent_id);
284
+ if (!agent) {
285
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
286
+ }
287
+ if (agent.status === "running") {
288
+ return { content: [{ type: "text", text: `Agent ${agent.name} is already running on port ${agent.port}` }] };
289
+ }
290
+
291
+ const result = await startAgentProcess(agent);
292
+ if (!result.success) {
293
+ return { content: [{ type: "text", text: `Failed to start agent: ${result.error}` }], isError: true };
294
+ }
295
+ return { content: [{ type: "text", text: `Agent ${agent.name} started on port ${result.port}` }] };
296
+ }
297
+
298
+ case "stop_agent": {
299
+ const agent = AgentDB.findById(args.agent_id);
300
+ if (!agent) {
301
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
302
+ }
303
+ if (agent.status === "stopped") {
304
+ return { content: [{ type: "text", text: `Agent ${agent.name} is already stopped` }] };
305
+ }
306
+ if (agent.id === META_AGENT_ID) {
307
+ return { content: [{ type: "text", text: "Cannot stop yourself (the Apteva Assistant)." }], isError: true };
308
+ }
309
+
310
+ const proc = agentProcesses.get(args.agent_id);
311
+ if (proc) {
312
+ proc.proc.kill();
313
+ agentProcesses.delete(args.agent_id);
314
+ }
315
+ setAgentStatus(args.agent_id, "stopped", "meta_agent");
316
+ return { content: [{ type: "text", text: `Agent ${agent.name} stopped` }] };
317
+ }
318
+
319
+ case "list_projects": {
320
+ const projects = ProjectDB.findAll();
321
+ const agentCounts = ProjectDB.getAgentCounts();
322
+ const result = projects.map(p => ({
323
+ id: p.id,
324
+ name: p.name,
325
+ description: p.description,
326
+ color: p.color,
327
+ agentCount: agentCounts.get(p.id) || 0,
328
+ }));
329
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
330
+ }
331
+
332
+ case "create_project": {
333
+ const project = ProjectDB.create({
334
+ name: args.name,
335
+ description: args.description || null,
336
+ color: args.color,
337
+ });
338
+ return { content: [{ type: "text", text: `Project created: ${JSON.stringify({ id: project.id, name: project.name, color: project.color }, null, 2)}` }] };
339
+ }
340
+
341
+ case "list_providers": {
342
+ const providers = getProvidersWithStatus();
343
+ const llmProviders = providers.filter(p => p.type === "llm");
344
+ const result = llmProviders.map(p => ({
345
+ id: p.id,
346
+ name: p.name,
347
+ hasKey: p.hasKey,
348
+ models: p.models,
349
+ }));
350
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
351
+ }
352
+
353
+ case "list_mcp_servers": {
354
+ const servers = McpServerDB.findAll();
355
+ const result = servers.map(s => ({
356
+ id: s.id,
357
+ name: s.name,
358
+ type: s.type,
359
+ status: s.status,
360
+ url: s.url,
361
+ }));
362
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
363
+ }
364
+
365
+ case "get_dashboard_stats": {
366
+ const agentCount = AgentDB.count();
367
+ const runningCount = AgentDB.countRunning();
368
+ const projectCount = ProjectDB.count();
369
+ const providers = getProvidersWithStatus().filter(p => p.type === "llm");
370
+ const configuredProviders = providers.filter(p => p.hasKey).length;
371
+ const mcpServers = McpServerDB.findAll().length;
372
+
373
+ return {
374
+ content: [{
375
+ type: "text",
376
+ text: JSON.stringify({
377
+ agents: { total: agentCount - 1, running: runningCount }, // -1 for meta agent
378
+ projects: projectCount,
379
+ providers: { total: providers.length, configured: configuredProviders },
380
+ mcpServers,
381
+ }, null, 2),
382
+ }],
383
+ };
384
+ }
385
+
386
+ case "send_message": {
387
+ const agent = AgentDB.findById(args.agent_id);
388
+ if (!agent) {
389
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
390
+ }
391
+ if (agent.status !== "running" || !agent.port) {
392
+ return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
393
+ }
394
+
395
+ try {
396
+ const res = await agentFetch(args.agent_id, agent.port, "/chat", {
397
+ method: "POST",
398
+ headers: { "Content-Type": "application/json" },
399
+ body: JSON.stringify({ message: args.message }),
400
+ signal: AbortSignal.timeout(60000),
401
+ });
402
+
403
+ if (!res.ok) {
404
+ const err = await res.text().catch(() => "Unknown error");
405
+ return { content: [{ type: "text", text: `Agent responded with error: ${err}` }], isError: true };
406
+ }
407
+
408
+ const data = await res.json();
409
+ const reply = data.response || data.message || JSON.stringify(data);
410
+ return { content: [{ type: "text", text: reply }] };
411
+ } catch (err) {
412
+ return { content: [{ type: "text", text: `Failed to communicate with agent: ${err}` }], isError: true };
413
+ }
414
+ }
415
+
416
+ default:
417
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
418
+ }
419
+ } catch (err) {
420
+ return { content: [{ type: "text", text: `Tool execution error: ${err}` }], isError: true };
421
+ }
422
+ }
423
+
424
+ // Main MCP request handler
425
+ export async function handlePlatformMcpRequest(req: Request): Promise<Response> {
426
+ const corsHeaders = {
427
+ "Access-Control-Allow-Origin": "*",
428
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
429
+ "Access-Control-Allow-Headers": "Content-Type, Mcp-Session-Id",
430
+ };
431
+
432
+ if (req.method === "OPTIONS") {
433
+ return new Response(null, { headers: corsHeaders });
434
+ }
435
+
436
+ let body: JsonRpcRequest;
437
+ try {
438
+ body = await req.json() as JsonRpcRequest;
439
+ } catch {
440
+ return new Response(JSON.stringify({
441
+ jsonrpc: "2.0",
442
+ id: 0,
443
+ error: { code: -32700, message: "Parse error" },
444
+ }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
445
+ }
446
+
447
+ const { method, params, id } = body;
448
+
449
+ let result: unknown;
450
+ let error: { code: number; message: string } | undefined;
451
+
452
+ switch (method) {
453
+ case "initialize": {
454
+ result = {
455
+ protocolVersion: PROTOCOL_VERSION,
456
+ capabilities: {
457
+ tools: { listChanged: false },
458
+ },
459
+ serverInfo: {
460
+ name: "apteva-platform",
461
+ version: "1.0.0",
462
+ },
463
+ instructions: "This MCP server provides tools to control the Apteva AI agent platform. You can create, start, stop, and manage agents, projects, and view system status.",
464
+ };
465
+ break;
466
+ }
467
+
468
+ case "notifications/initialized": {
469
+ // Acknowledgement - no response needed for notifications, but since this is HTTP we return ok
470
+ result = {};
471
+ break;
472
+ }
473
+
474
+ case "tools/list": {
475
+ result = { tools: PLATFORM_TOOLS };
476
+ break;
477
+ }
478
+
479
+ case "tools/call": {
480
+ const { name, arguments: args } = params as { name: string; arguments: Record<string, any> };
481
+ result = await executeTool(name, args || {});
482
+ break;
483
+ }
484
+
485
+ default: {
486
+ error = { code: -32601, message: `Method not found: ${method}` };
487
+ }
488
+ }
489
+
490
+ const response: JsonRpcResponse = {
491
+ jsonrpc: "2.0",
492
+ id: id || 0,
493
+ ...(error ? { error } : { result }),
494
+ };
495
+
496
+ return new Response(JSON.stringify(response), {
497
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
498
+ });
499
+ }
@@ -147,6 +147,18 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
147
147
  }
148
148
  }
149
149
 
150
+ // Auto-inject built-in platform MCP server for meta agent
151
+ if (agent.id === META_AGENT_ID) {
152
+ const baseUrl = `http://localhost:${process.env.PORT || 4280}`;
153
+ mcpServers.push({
154
+ name: "Apteva Platform",
155
+ type: "http",
156
+ url: `${baseUrl}/api/mcp/platform`,
157
+ headers: {},
158
+ enabled: true,
159
+ });
160
+ }
161
+
150
162
  return {
151
163
  id: agent.id,
152
164
  name: agent.name,
@@ -215,7 +227,7 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
215
227
  max_actions_per_turn: 5,
216
228
  },
217
229
  mcp: {
218
- enabled: features.mcp,
230
+ enabled: features.mcp || agent.id === META_AGENT_ID,
219
231
  base_url: "http://localhost:3000/mcp",
220
232
  timeout: "30s",
221
233
  retry_count: 3,
@@ -47,23 +47,29 @@ export async function handleMetaAgentRoutes(
47
47
  name: "Apteva Assistant",
48
48
  model: defaultModel,
49
49
  provider: providerId,
50
- system_prompt: `You are the Apteva Assistant, a helpful guide for users of the Apteva agent management platform.
50
+ system_prompt: `You are the Apteva Assistant, an AI that manages the Apteva agent platform.
51
51
 
52
- You can help users with:
53
- - Creating and configuring AI agents
54
- - Setting up MCP servers for tool integrations
55
- - Managing projects and organizing agents
56
- - Explaining features like Memory, Tasks, Vision, Operator, Files, and Multi-Agent
57
- - Troubleshooting common issues
52
+ You have tools to control the platform directly:
53
+ - Create, configure, start, and stop AI agents
54
+ - Manage projects and organize agents into them
55
+ - View system status, configured providers, and MCP servers
56
+ - Send messages to other running agents
58
57
 
59
- Be concise, friendly, and helpful. When users ask about creating something, guide them step by step.
60
- Keep responses short and actionable. Use markdown formatting when helpful.`,
58
+ Use your tools proactively when users ask you to do things. For example:
59
+ - "Create a GPT agent" use create_agent with provider "openai" and model "gpt-4o"
60
+ - "Start my agent" → use start_agent
61
+ - "How many agents do I have?" → use get_dashboard_stats or list_agents
62
+
63
+ Available providers: anthropic (Claude), openai (GPT), groq (Llama), gemini (Gemini), xai (Grok), together, fireworks, ollama (local).
64
+ Use list_providers to see which have API keys configured.
65
+
66
+ Be concise and action-oriented. Confirm what you did after taking actions. Use markdown formatting.`,
61
67
  features: {
62
68
  memory: false,
63
69
  tasks: false,
64
70
  vision: false,
65
71
  operator: false,
66
- mcp: false,
72
+ mcp: true,
67
73
  realtime: false,
68
74
  files: false,
69
75
  agents: false,
package/src/routes/api.ts CHANGED
@@ -10,6 +10,7 @@ import { handleSkillRoutes } from "./api/skills";
10
10
  import { handleIntegrationRoutes } from "./api/integrations";
11
11
  import { handleMetaAgentRoutes } from "./api/meta-agent";
12
12
  import { handleTelemetryRoutes } from "./api/telemetry";
13
+ import { handlePlatformMcpRequest } from "../mcp-platform";
13
14
 
14
15
  // Re-export for backward compatibility (server.ts dynamic import)
15
16
  export { startAgentProcess } from "./api/agent-utils";
@@ -21,6 +22,11 @@ export async function handleApiRequest(
21
22
  ): Promise<Response> {
22
23
  const method = req.method;
23
24
 
25
+ // Built-in platform MCP server (for meta agent)
26
+ if (path === "/api/mcp/platform" && method === "POST") {
27
+ return handlePlatformMcpRequest(req);
28
+ }
29
+
24
30
  return (
25
31
  (await handleSystemRoutes(req, path, method, authContext)) ??
26
32
  (await handleProviderRoutes(req, path, method, authContext)) ??