apteva 0.4.41 → 0.4.48

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.
Files changed (103) hide show
  1. package/dist/ActivityPage.sw9p594m.js +3 -0
  2. package/dist/ApiDocsPage.90e03bz7.js +4 -0
  3. package/dist/App.0ws87fpx.js +53 -0
  4. package/dist/App.3vnrera5.js +4 -0
  5. package/dist/App.94x6mh7f.js +20 -0
  6. package/dist/{App.7fb3e7mp.js → App.9sryp183.js} +1 -1
  7. package/dist/App.d9tny4t0.js +221 -0
  8. package/dist/App.jhb45d7r.js +8 -0
  9. package/dist/App.p7jjw1zf.js +4 -0
  10. package/dist/App.pfbdzrhh.js +4 -0
  11. package/dist/App.stgng5bx.js +13 -0
  12. package/dist/App.tm3k7h4b.js +4 -0
  13. package/dist/App.vkg121c6.js +4 -0
  14. package/dist/App.wghtdzsk.js +1 -0
  15. package/dist/App.xf7wsckg.js +4 -0
  16. package/dist/App.xva0tfzh.js +4 -0
  17. package/dist/App.ysxy7akk.js +61 -0
  18. package/dist/App.yzkh4gq2.js +4 -0
  19. package/dist/ConnectionsPage.q5f9fd37.js +3 -0
  20. package/dist/McpPage.f3ccrezb.js +3 -0
  21. package/dist/SettingsPage.3sqx6wm4.js +3 -0
  22. package/dist/SkillsPage.whxnez67.js +3 -0
  23. package/dist/TasksPage.zp4jfevw.js +3 -0
  24. package/dist/TelemetryPage.a9fmxq87.js +3 -0
  25. package/dist/TestsPage.18krj0d1.js +3 -0
  26. package/dist/ThreadsPage.nnphgy98.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +11 -10
  31. package/src/db.ts +61 -13
  32. package/src/integrations/agentdojo.ts +1 -0
  33. package/src/mcp-platform.ts +418 -63
  34. package/src/openapi.ts +96 -0
  35. package/src/providers.ts +55 -24
  36. package/src/routes/api/agent-utils.ts +25 -4
  37. package/src/routes/api/agents.ts +19 -1
  38. package/src/routes/api/meta-agent.ts +2 -0
  39. package/src/routes/api/system.ts +90 -1
  40. package/src/routes/api/telemetry.ts +38 -2
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +64 -1
  43. package/src/web/App.tsx +89 -11
  44. package/src/web/components/activity/ActivityPage.tsx +14 -14
  45. package/src/web/components/agents/AgentCard.tsx +19 -17
  46. package/src/web/components/agents/AgentPanel.tsx +541 -220
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +24 -82
  49. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  50. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  51. package/src/web/components/auth/LoginPage.tsx +10 -10
  52. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  53. package/src/web/components/common/Modal.tsx +9 -9
  54. package/src/web/components/common/Select.tsx +9 -9
  55. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  56. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  57. package/src/web/components/connections/OverviewTab.tsx +13 -13
  58. package/src/web/components/connections/TriggersTab.tsx +99 -99
  59. package/src/web/components/dashboard/Dashboard.tsx +32 -32
  60. package/src/web/components/layout/Header.tsx +50 -34
  61. package/src/web/components/layout/Sidebar.tsx +35 -15
  62. package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
  63. package/src/web/components/mcp/McpPage.tsx +208 -208
  64. package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
  65. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  66. package/src/web/components/settings/SettingsPage.tsx +291 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +539 -78
  69. package/src/web/components/telemetry/TelemetryPage.tsx +405 -65
  70. package/src/web/components/tests/TestsPage.tsx +50 -50
  71. package/src/web/components/threads/ThreadsPage.tsx +23 -21
  72. package/src/web/context/ProjectContext.tsx +6 -1
  73. package/src/web/context/ThemeContext.tsx +90 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/index.html +1 -6
  76. package/src/web/styles.css +52 -3
  77. package/src/web/themes.ts +162 -0
  78. package/src/web/types.ts +0 -4
  79. package/dist/ActivityPage.7907h64p.js +0 -3
  80. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  81. package/dist/App.01nq20st.js +0 -4
  82. package/dist/App.1maqvamf.js +0 -4
  83. package/dist/App.2yjrh32f.js +0 -4
  84. package/dist/App.3qw8nben.js +0 -20
  85. package/dist/App.7sy3wq8c.js +0 -4
  86. package/dist/App.apjrmctz.js +0 -57
  87. package/dist/App.av6t2yhe.js +0 -4
  88. package/dist/App.jqj5a094.js +0 -46
  89. package/dist/App.mc7xf85h.js +0 -4
  90. package/dist/App.myxqcj9x.js +0 -4
  91. package/dist/App.nm91r1mp.js +0 -13
  92. package/dist/App.p02f4ret.js +0 -1
  93. package/dist/App.qcknavjz.js +0 -221
  94. package/dist/App.vc7vfhg4.js +0 -4
  95. package/dist/App.z4s9zkw5.js +0 -4
  96. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  97. package/dist/McpPage.8vc97z0b.js +0 -3
  98. package/dist/SettingsPage.p61bz8kd.js +0 -3
  99. package/dist/SkillsPage.r9x43g3g.js +0 -3
  100. package/dist/TasksPage.1e0zkye4.js +0 -3
  101. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  102. package/dist/TestsPage.d4xy504e.js +0 -3
  103. package/dist/ThreadsPage.m016am3x.js +0 -3
@@ -1,11 +1,11 @@
1
1
  // Built-in MCP server that exposes the Apteva platform API as MCP tools
2
2
  // This allows the meta agent (Apteva Assistant) to control the platform
3
3
 
4
- import { AgentDB, ProjectDB, McpServerDB, SkillDB, TelemetryDB, SubscriptionDB, SettingsDB, generateId } from "./db";
4
+ import { AgentDB, ProjectDB, McpServerDB, McpServerToolDB, SkillDB, TelemetryDB, SubscriptionDB, SettingsDB, generateId } from "./db";
5
5
  import { TestCaseDB, TestRunDB } from "./db-tests";
6
6
  import { runTest, runAll } from "./test-runner";
7
7
  import { getProvidersWithStatus, PROVIDERS, ProviderKeys } from "./providers";
8
- import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch } from "./routes/api/agent-utils";
8
+ import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch, fetchFromAgent } from "./routes/api/agent-utils";
9
9
  import { agentProcesses } from "./server";
10
10
  import { getTriggerProvider, getTriggerProviderIds, registerTriggerProvider } from "./triggers";
11
11
  import { ComposioTriggerProvider } from "./triggers/composio";
@@ -216,6 +216,31 @@ SKILLS & MCP SERVERS:
216
216
  required: ["name"],
217
217
  },
218
218
  },
219
+ {
220
+ name: "update_project",
221
+ description: "Update an existing project's name, description, or color.",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ project_id: { type: "string", description: "The project ID to update" },
226
+ name: { type: "string", description: "New project name" },
227
+ description: { type: "string", description: "New project description" },
228
+ color: { type: "string", description: "New hex color code (e.g. #6366f1)" },
229
+ },
230
+ required: ["project_id"],
231
+ },
232
+ },
233
+ {
234
+ name: "delete_project",
235
+ description: "Delete a project. Agents, MCP servers, and skills in the project will be unassigned (not deleted).",
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ project_id: { type: "string", description: "The project ID to delete" },
240
+ },
241
+ required: ["project_id"],
242
+ },
243
+ },
219
244
  {
220
245
  name: "list_providers",
221
246
  description: "List all available LLM providers and their configuration status (which have API keys).",
@@ -250,12 +275,13 @@ SKILLS & MCP SERVERS:
250
275
  description: `Create a new MCP server configuration. MCP servers provide tools that agents can use (web search, file access, APIs, etc).
251
276
 
252
277
  SERVER TYPES:
278
+ - local: Runs inside the apteva server — no external process needed. Add tools with add_tool_to_local_server. Ready to use immediately after adding tools.
253
279
  - http: Remote MCP server accessible via URL. Provide url and optional auth headers. Ready to use immediately.
254
280
  - npm: Node.js MCP server from npm. Provide package name (e.g. '@modelcontextprotocol/server-filesystem'). Needs to be started.
255
281
  - pip: Python MCP server from PyPI. Provide package name. Needs to be started.
256
282
  - custom: Custom command. Provide command and args. Needs to be started.
257
283
 
258
- After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers work immediately; npm/pip/custom servers need to be started from the MCP page in the UI.`,
284
+ After creating, assign to agents with assign_mcp_server_to_agent. Local and HTTP servers work immediately; npm/pip/custom servers need to be started from the MCP page in the UI.`,
259
285
  inputSchema: {
260
286
  type: "object",
261
287
  properties: {
@@ -282,6 +308,59 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
282
308
  required: ["server_id"],
283
309
  },
284
310
  },
311
+ {
312
+ name: "add_tool_to_local_server",
313
+ description: `Add a tool to a local MCP server. Only works for servers with type "local".
314
+
315
+ HANDLER TYPES:
316
+ - mock: Returns a static/templated response. Use mock_response with template variables: {{args.field}}, {{uuid()}}, {{now}}, {{timestamp}}, {{random_int(min,max)}}.
317
+ - http: Makes a real HTTP API call. Provide http_config with method, url, headers, and optional body. Templates work in url/headers/body. Use {{credential.KEY}} to reference server env vars for auth.
318
+ - javascript: Runs custom JavaScript code. The code receives args, credentials, and helper functions (uuid, now, timestamp, random_int, random_float). Return a value or JSON object.
319
+
320
+ EXAMPLES:
321
+ - Mock: handler_type="mock", mock_response={"greeting": "Hello {{args.name}}!", "id": "{{uuid()}}"}
322
+ - HTTP: handler_type="http", http_config={"method": "GET", "url": "https://api.example.com/users/{{args.user_id}}", "headers": {"Authorization": "Bearer {{credential.API_KEY}}"}}
323
+ - JavaScript: handler_type="javascript", code="return { sum: args.a + args.b, computed_at: now }"`,
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: {
327
+ server_id: { type: "string", description: "The local MCP server ID" },
328
+ name: { type: "string", description: "Tool name (snake_case, e.g. 'get_weather', 'create_ticket')" },
329
+ description: { type: "string", description: "What this tool does (shown to the agent)" },
330
+ input_schema: {
331
+ type: "object",
332
+ description: "JSON Schema for tool parameters. Must have type='object' with properties.",
333
+ },
334
+ handler_type: { type: "string", description: "How the tool executes: mock, http, or javascript" },
335
+ mock_response: { type: "object", description: "For mock handler: the response template (supports {{args.field}}, {{uuid()}}, {{now}})" },
336
+ http_config: {
337
+ type: "object",
338
+ description: "For http handler: { method, url, headers?, body? }. Templates supported in all fields.",
339
+ },
340
+ code: { type: "string", description: "For javascript handler: JS code to execute. Has access to args, credentials, uuid, now, timestamp, random_int, random_float. Must return a value." },
341
+ },
342
+ required: ["server_id", "name", "description", "input_schema", "handler_type"],
343
+ },
344
+ },
345
+ {
346
+ name: "update_tool_on_local_server",
347
+ description: `Update an existing tool on a local MCP server. Only provide fields you want to change. Only works for servers with type "local".`,
348
+ inputSchema: {
349
+ type: "object",
350
+ properties: {
351
+ tool_id: { type: "string", description: "The tool ID to update" },
352
+ name: { type: "string", description: "New tool name" },
353
+ description: { type: "string", description: "New description" },
354
+ input_schema: { type: "object", description: "New JSON Schema for tool parameters" },
355
+ handler_type: { type: "string", description: "New handler type: mock, http, or javascript" },
356
+ mock_response: { type: "object", description: "New mock response template" },
357
+ http_config: { type: "object", description: "New HTTP config: { method, url, headers?, body? }" },
358
+ code: { type: "string", description: "New JavaScript code" },
359
+ enabled: { type: "boolean", description: "Enable or disable the tool" },
360
+ },
361
+ required: ["tool_id"],
362
+ },
363
+ },
285
364
  {
286
365
  name: "get_dashboard_stats",
287
366
  description: "Get platform overview stats: agent counts, task counts, provider counts.",
@@ -302,14 +381,69 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
302
381
  required: ["agent_id", "message"],
303
382
  },
304
383
  },
384
+ // Task management on agents
385
+ {
386
+ name: "list_agent_tasks",
387
+ description: "List tasks on a running agent. Tasks are scheduled work items that agents execute autonomously.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
391
+ agent_id: { type: "string", description: "The agent ID to list tasks from" },
392
+ status: { type: "string", description: "Filter by status: all, pending, running, completed, failed, cancelled (default: all)" },
393
+ },
394
+ required: ["agent_id"],
395
+ },
396
+ },
397
+ {
398
+ name: "create_agent_task",
399
+ description: "Create a new task on a running agent. The agent must have the tasks feature enabled.",
400
+ inputSchema: {
401
+ type: "object",
402
+ properties: {
403
+ agent_id: { type: "string", description: "The agent ID to create the task on" },
404
+ title: { type: "string", description: "Task title" },
405
+ description: { type: "string", description: "Task description / instructions for the agent" },
406
+ type: { type: "string", description: "Task type: once (one-time) or recurring (default: once)" },
407
+ priority: { type: "number", description: "Priority 1-10, higher = more important (default: 5)" },
408
+ execute_at: { type: "string", description: "ISO timestamp for scheduled execution (optional, for one-time tasks)" },
409
+ recurrence: { type: "string", description: "Cron expression for recurring tasks (e.g. '*/30 * * * *' = every 30 min)" },
410
+ },
411
+ required: ["agent_id", "title"],
412
+ },
413
+ },
414
+ {
415
+ name: "delete_agent_task",
416
+ description: "Delete a task from a running agent.",
417
+ inputSchema: {
418
+ type: "object",
419
+ properties: {
420
+ agent_id: { type: "string", description: "The agent ID" },
421
+ task_id: { type: "string", description: "The task ID to delete" },
422
+ },
423
+ required: ["agent_id", "task_id"],
424
+ },
425
+ },
426
+ {
427
+ name: "execute_agent_task",
428
+ description: "Immediately execute a task on a running agent, regardless of its schedule.",
429
+ inputSchema: {
430
+ type: "object",
431
+ properties: {
432
+ agent_id: { type: "string", description: "The agent ID" },
433
+ task_id: { type: "string", description: "The task ID to execute" },
434
+ },
435
+ required: ["agent_id", "task_id"],
436
+ },
437
+ },
305
438
  // Skills management
306
439
  {
307
440
  name: "list_skills",
308
- description: "List all installed skills. Skills are reusable instruction sets (like prompt templates with tool permissions) that give agents specialized capabilities. Skills can be installed from the SkillsMP marketplace or created locally.",
441
+ description: "List all installed skills. Skills are reusable instruction sets (like prompt templates with tool permissions) that give agents specialized capabilities. Skills can be installed from the SkillsMP marketplace or created locally. Pass project_id to only see skills scoped to that project (plus global skills).",
309
442
  inputSchema: {
310
443
  type: "object",
311
444
  properties: {
312
445
  enabled_only: { type: "boolean", description: "Only return enabled skills (optional, default false)" },
446
+ project_id: { type: "string", description: "Filter by project ID — returns skills scoped to this project plus global skills" },
313
447
  },
314
448
  },
315
449
  },
@@ -336,6 +470,25 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
336
470
  required: ["skill_id", "enabled"],
337
471
  },
338
472
  },
473
+ {
474
+ name: "update_skill",
475
+ description: "Update an existing skill. Only provide fields you want to change.",
476
+ inputSchema: {
477
+ type: "object",
478
+ properties: {
479
+ skill_id: { type: "string", description: "The skill ID to update" },
480
+ name: { type: "string", description: "New name" },
481
+ description: { type: "string", description: "New description" },
482
+ content: { type: "string", description: "New instructions content (markdown)" },
483
+ allowed_tools: {
484
+ type: "array",
485
+ items: { type: "string" },
486
+ description: "New list of allowed MCP tool names",
487
+ },
488
+ },
489
+ required: ["skill_id"],
490
+ },
491
+ },
339
492
  {
340
493
  name: "create_skill",
341
494
  description: "Create a new skill. Skills are reusable instruction sets (markdown content) that give agents specialized capabilities. Provide a name, description, and the full instructions content (markdown). Optionally specify allowed MCP tools. If the user is working within a project, set project_id to scope the skill to that project.",
@@ -468,18 +621,6 @@ Use this to find trigger slugs before creating a subscription.`,
468
621
  required: ["provider"],
469
622
  },
470
623
  },
471
- {
472
- name: "list_connected_accounts",
473
- description: "List connected accounts (OAuth connections) for a provider. You need a connected_account_id to create a subscription. Each account represents a user's authenticated connection to a service (e.g. GitHub, Slack, Stripe).",
474
- inputSchema: {
475
- type: "object",
476
- properties: {
477
- provider: { type: "string", description: "Integration provider ID: composio or agentdojo" },
478
- project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
479
- },
480
- required: ["provider"],
481
- },
482
- },
483
624
  {
484
625
  name: "list_subscriptions",
485
626
  description: "List local trigger subscriptions. Subscriptions route incoming webhook events to agents. Each subscription maps a trigger (e.g. github:push) to a specific agent.",
@@ -493,22 +634,21 @@ Use this to find trigger slugs before creating a subscription.`,
493
634
  },
494
635
  {
495
636
  name: "create_subscription",
496
- description: `Create a trigger subscription: registers a webhook with the external service (via the provider) and creates a local subscription to route events to an agent.
637
+ description: `Create a trigger subscription: registers a webhook with the external service and routes events to an agent. The webhook URL is auto-configured — you do NOT need to provide a callback URL.
497
638
 
498
- WORKFLOW:
499
- 1. Use list_trigger_providers to check which providers have keys
500
- 2. Use list_trigger_types to find the trigger slug you want
501
- 3. Use list_connected_accounts to find the connected_account_id
502
- 4. Use list_agents to find the agent_id to route events to
503
- 5. Call create_subscription with all the above
639
+ Just provide all 4 required fields in a single call:
640
+ - provider: "agentdojo" or "composio"
641
+ - trigger_slug: from list_trigger_types
642
+ - connected_account_id: from list_integration_connections
643
+ - agent_id: from list_agents
504
644
 
505
- IMPORTANT: Some triggers require extra config fields (e.g. GitHub triggers need "owner" and "repo"). Check the trigger type's config_schema and pass required fields in the config object.`,
645
+ Some triggers require extra config fields (e.g. GitHub needs "owner" and "repo") pass them in the config object.`,
506
646
  inputSchema: {
507
647
  type: "object",
508
648
  properties: {
509
649
  provider: { type: "string", description: "Trigger provider ID: composio or agentdojo" },
510
650
  trigger_slug: { type: "string", description: "Trigger type slug (e.g. 'github:push', 'GITHUB_PUSH_EVENT'). Use list_trigger_types to find slugs." },
511
- connected_account_id: { type: "string", description: "Connected account ID that owns the integration. Use list_connected_accounts to find IDs." },
651
+ connected_account_id: { type: "string", description: "Connected account ID that owns the integration. Use list_integration_connections to find IDs." },
512
652
  agent_id: { type: "string", description: "Agent ID to route trigger events to. Use list_agents to find IDs." },
513
653
  project_id: { type: "string", description: "Project ID for scoping (optional)" },
514
654
  config: {
@@ -690,10 +830,15 @@ After adding, use assign_mcp_server_to_agent to give an agent access to these to
690
830
  },
691
831
  ];
692
832
 
833
+ // Project-only tools — hidden entirely when PROJECTS_ENABLED is not set
834
+ const PROJECT_ONLY_TOOLS = new Set(["list_projects", "create_project", "update_project", "delete_project"]);
835
+
693
836
  // Build tools list — when PROJECTS_ENABLED, add project_id to required for all tools that accept it
694
837
  function getPlatformTools() {
695
838
  const projectsEnabled = process.env.PROJECTS_ENABLED === "true";
696
- if (!projectsEnabled) return PLATFORM_TOOLS;
839
+ if (!projectsEnabled) {
840
+ return PLATFORM_TOOLS.filter(tool => !PROJECT_ONLY_TOOLS.has(tool.name));
841
+ }
697
842
 
698
843
  return PLATFORM_TOOLS.map(tool => {
699
844
  const props = tool.inputSchema.properties as Record<string, unknown> | undefined;
@@ -907,6 +1052,36 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
907
1052
  return { content: [{ type: "text", text: `Project created: ${JSON.stringify({ id: project.id, name: project.name, color: project.color }, null, 2)}` }] };
908
1053
  }
909
1054
 
1055
+ case "update_project": {
1056
+ const project = ProjectDB.findById(args.project_id);
1057
+ if (!project) return { content: [{ type: "text", text: `Project not found: ${args.project_id}` }], isError: true };
1058
+
1059
+ const updates: Record<string, unknown> = {};
1060
+ if (args.name !== undefined) updates.name = args.name;
1061
+ if (args.description !== undefined) updates.description = args.description;
1062
+ if (args.color !== undefined) updates.color = args.color;
1063
+
1064
+ if (Object.keys(updates).length === 0) {
1065
+ return { content: [{ type: "text", text: "No updates provided" }], isError: true };
1066
+ }
1067
+
1068
+ const updated = ProjectDB.update(args.project_id, updates);
1069
+ return { content: [{ type: "text", text: `Project updated: ${JSON.stringify({ id: updated!.id, name: updated!.name, description: updated!.description, color: updated!.color }, null, 2)}` }] };
1070
+ }
1071
+
1072
+ case "delete_project": {
1073
+ const project = ProjectDB.findById(args.project_id);
1074
+ if (!project) return { content: [{ type: "text", text: `Project not found: ${args.project_id}` }], isError: true };
1075
+
1076
+ const agentCounts = ProjectDB.getAgentCounts();
1077
+ const agentCount = agentCounts.get(args.project_id) || 0;
1078
+
1079
+ const deleted = ProjectDB.delete(args.project_id);
1080
+ if (!deleted) return { content: [{ type: "text", text: "Failed to delete project" }], isError: true };
1081
+
1082
+ return { content: [{ type: "text", text: `Project "${project.name}" deleted. ${agentCount} agent(s) were unassigned.` }] };
1083
+ }
1084
+
910
1085
  case "list_providers": {
911
1086
  const providers = getProvidersWithStatus();
912
1087
  const llmProviders = providers.filter(p => p.type === "llm");
@@ -940,7 +1115,7 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
940
1115
  if (!server) {
941
1116
  return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
942
1117
  }
943
- return { content: [{ type: "text", text: JSON.stringify({
1118
+ const serverInfo: Record<string, any> = {
944
1119
  id: server.id,
945
1120
  name: server.name,
946
1121
  type: server.type,
@@ -952,7 +1127,23 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
952
1127
  port: server.port,
953
1128
  source: server.source,
954
1129
  projectId: server.project_id,
955
- }, null, 2) }] };
1130
+ };
1131
+ // Include tools with full details for local servers
1132
+ if (server.type === "local") {
1133
+ const tools = McpServerToolDB.findByServer(server.id);
1134
+ serverInfo.tools = tools.map(t => ({
1135
+ id: t.id,
1136
+ name: t.name,
1137
+ description: t.description,
1138
+ handler_type: t.handler_type,
1139
+ enabled: t.enabled,
1140
+ input_schema: t.input_schema,
1141
+ mock_response: t.mock_response,
1142
+ http_config: t.http_config,
1143
+ code: t.code,
1144
+ }));
1145
+ }
1146
+ return { content: [{ type: "text", text: JSON.stringify(serverInfo, null, 2) }] };
956
1147
  }
957
1148
 
958
1149
  case "create_mcp_server": {
@@ -986,6 +1177,59 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
986
1177
  return { content: [{ type: "text", text: `MCP server deleted: ${server.name} (${server.id})` }] };
987
1178
  }
988
1179
 
1180
+ case "add_tool_to_local_server": {
1181
+ const server = McpServerDB.findById(args.server_id);
1182
+ if (!server) {
1183
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
1184
+ }
1185
+ if (server.type !== "local") {
1186
+ return { content: [{ type: "text", text: `Server "${server.name}" is type "${server.type}" — add_tool_to_local_server only works for local servers.` }], isError: true };
1187
+ }
1188
+ // Check for duplicate tool name
1189
+ const existing = McpServerToolDB.findByServerAndName(args.server_id, args.name);
1190
+ if (existing) {
1191
+ return { content: [{ type: "text", text: `Tool "${args.name}" already exists on this server (ID: ${existing.id}). Use a different name or delete the existing tool first.` }], isError: true };
1192
+ }
1193
+ const tool = McpServerToolDB.create({
1194
+ id: generateId(),
1195
+ server_id: args.server_id,
1196
+ name: args.name,
1197
+ description: args.description,
1198
+ input_schema: args.input_schema || { type: "object", properties: {} },
1199
+ handler_type: args.handler_type || "mock",
1200
+ mock_response: args.mock_response || null,
1201
+ http_config: args.http_config || null,
1202
+ code: args.code || null,
1203
+ enabled: true,
1204
+ });
1205
+ return { content: [{ type: "text", text: `Tool added: ${JSON.stringify({ id: tool.id, name: tool.name, handler_type: tool.handler_type, server: server.name }, null, 2)}` }] };
1206
+ }
1207
+
1208
+ case "update_tool_on_local_server": {
1209
+ const tool = McpServerToolDB.findById(args.tool_id);
1210
+ if (!tool) {
1211
+ return { content: [{ type: "text", text: `Tool not found: ${args.tool_id}` }], isError: true };
1212
+ }
1213
+ const server = McpServerDB.findById(tool.server_id);
1214
+ if (!server || server.type !== "local") {
1215
+ return { content: [{ type: "text", text: `Tool belongs to a non-local server — update_tool_on_local_server only works for local servers.` }], isError: true };
1216
+ }
1217
+ const updates: Record<string, unknown> = {};
1218
+ if (args.name !== undefined) updates.name = args.name;
1219
+ if (args.description !== undefined) updates.description = args.description;
1220
+ if (args.input_schema !== undefined) updates.input_schema = args.input_schema;
1221
+ if (args.handler_type !== undefined) updates.handler_type = args.handler_type;
1222
+ if (args.mock_response !== undefined) updates.mock_response = args.mock_response;
1223
+ if (args.http_config !== undefined) updates.http_config = args.http_config;
1224
+ if (args.code !== undefined) updates.code = args.code;
1225
+ if (args.enabled !== undefined) updates.enabled = args.enabled;
1226
+ const updated = McpServerToolDB.update(args.tool_id, updates);
1227
+ if (!updated) {
1228
+ return { content: [{ type: "text", text: `Failed to update tool ${args.tool_id}` }], isError: true };
1229
+ }
1230
+ return { content: [{ type: "text", text: `Tool updated: ${JSON.stringify({ id: updated.id, name: updated.name, handler_type: updated.handler_type, enabled: updated.enabled, server: server.name }, null, 2)}` }] };
1231
+ }
1232
+
989
1233
  case "get_dashboard_stats": {
990
1234
  const agentCount = AgentDB.count();
991
1235
  const runningCount = AgentDB.countRunning();
@@ -1023,7 +1267,7 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1023
1267
  method: "POST",
1024
1268
  headers: { "Content-Type": "application/json" },
1025
1269
  body: JSON.stringify({ message: args.message }),
1026
- signal: AbortSignal.timeout(60000),
1270
+ signal: AbortSignal.timeout(120000),
1027
1271
  });
1028
1272
 
1029
1273
  if (!res.ok) {
@@ -1031,16 +1275,129 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1031
1275
  return { content: [{ type: "text", text: `Agent responded with error: ${err}` }], isError: true };
1032
1276
  }
1033
1277
 
1034
- const data = await res.json();
1035
- const reply = data.response || data.message || JSON.stringify(data);
1278
+ // Agent returns SSE stream — consume and assemble content chunks
1279
+ const reader = res.body?.getReader();
1280
+ if (!reader) {
1281
+ return { content: [{ type: "text", text: "No response stream from agent" }], isError: true };
1282
+ }
1283
+
1284
+ const decoder = new TextDecoder();
1285
+ const contentChunks: string[] = [];
1286
+ let raw = "";
1287
+
1288
+ while (true) {
1289
+ const { done, value } = await reader.read();
1290
+ if (done) break;
1291
+ raw += decoder.decode(value, { stream: true });
1292
+ }
1293
+
1294
+ // Parse SSE lines: "data: {...}"
1295
+ for (const line of raw.split("\n")) {
1296
+ const sseData = line.startsWith("data: ") ? line.slice(6) : line.trim();
1297
+ if (!sseData) continue;
1298
+ try {
1299
+ const evt = JSON.parse(sseData);
1300
+ if (evt.type === "content" && evt.content) {
1301
+ contentChunks.push(evt.content);
1302
+ }
1303
+ } catch {
1304
+ // Not valid JSON — skip
1305
+ }
1306
+ }
1307
+
1308
+ const reply = contentChunks.join("") || "(no response)";
1036
1309
  return { content: [{ type: "text", text: reply }] };
1037
1310
  } catch (err) {
1038
1311
  return { content: [{ type: "text", text: `Failed to communicate with agent: ${err}` }], isError: true };
1039
1312
  }
1040
1313
  }
1041
1314
 
1315
+ case "list_agent_tasks": {
1316
+ const agent = AgentDB.findById(args.agent_id);
1317
+ if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1318
+ if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
1319
+
1320
+ const status = args.status || "all";
1321
+ const data = await fetchFromAgent(agent.id, agent.port, `/tasks?status=${status}`);
1322
+ if (!data) return { content: [{ type: "text", text: "Failed to fetch tasks from agent" }], isError: true };
1323
+
1324
+ const tasks = (data.tasks || []).map((t: any) => ({
1325
+ id: t.id, title: t.title, type: t.type, status: t.status, priority: t.priority,
1326
+ recurrence: t.recurrence, next_run: t.next_run, execute_at: t.execute_at, created_at: t.created_at,
1327
+ }));
1328
+ return { content: [{ type: "text", text: JSON.stringify({ tasks, count: tasks.length }, null, 2) }] };
1329
+ }
1330
+
1331
+ case "create_agent_task": {
1332
+ const agent = AgentDB.findById(args.agent_id);
1333
+ if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1334
+ if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
1335
+
1336
+ const body: Record<string, unknown> = {
1337
+ title: args.title,
1338
+ description: args.description,
1339
+ type: args.type || "once",
1340
+ priority: args.priority || 5,
1341
+ };
1342
+ if (args.execute_at) body.execute_at = args.execute_at;
1343
+ if (args.recurrence) body.recurrence = args.recurrence;
1344
+
1345
+ try {
1346
+ const res = await agentFetch(agent.id, agent.port, "/tasks", {
1347
+ method: "POST",
1348
+ headers: { "Content-Type": "application/json" },
1349
+ body: JSON.stringify(body),
1350
+ signal: AbortSignal.timeout(5000),
1351
+ });
1352
+ const data = await res.json();
1353
+ if (!res.ok) return { content: [{ type: "text", text: `Failed to create task: ${data.error || res.status}` }], isError: true };
1354
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1355
+ } catch (err) {
1356
+ return { content: [{ type: "text", text: `Failed to create task: ${err}` }], isError: true };
1357
+ }
1358
+ }
1359
+
1360
+ case "delete_agent_task": {
1361
+ const agent = AgentDB.findById(args.agent_id);
1362
+ if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1363
+ if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
1364
+
1365
+ try {
1366
+ const res = await agentFetch(agent.id, agent.port, `/tasks/${args.task_id}`, {
1367
+ method: "DELETE",
1368
+ signal: AbortSignal.timeout(5000),
1369
+ });
1370
+ const data = await res.json();
1371
+ if (!res.ok) return { content: [{ type: "text", text: `Failed to delete task: ${data.error || res.status}` }], isError: true };
1372
+ return { content: [{ type: "text", text: `Task ${args.task_id} deleted successfully` }] };
1373
+ } catch (err) {
1374
+ return { content: [{ type: "text", text: `Failed to delete task: ${err}` }], isError: true };
1375
+ }
1376
+ }
1377
+
1378
+ case "execute_agent_task": {
1379
+ const agent = AgentDB.findById(args.agent_id);
1380
+ if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1381
+ if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
1382
+
1383
+ try {
1384
+ const res = await agentFetch(agent.id, agent.port, `/tasks/${args.task_id}/execute`, {
1385
+ method: "POST",
1386
+ signal: AbortSignal.timeout(5000),
1387
+ });
1388
+ const data = await res.json();
1389
+ if (!res.ok) return { content: [{ type: "text", text: `Failed to execute task: ${data.error || res.status}` }], isError: true };
1390
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1391
+ } catch (err) {
1392
+ return { content: [{ type: "text", text: `Failed to execute task: ${err}` }], isError: true };
1393
+ }
1394
+ }
1395
+
1042
1396
  case "list_skills": {
1043
- const skills = args.enabled_only ? SkillDB.findEnabled() : SkillDB.findAll();
1397
+ let skills = args.enabled_only ? SkillDB.findEnabled() : SkillDB.findAll();
1398
+ if (args.project_id) {
1399
+ skills = skills.filter(s => !s.project_id || s.project_id === args.project_id);
1400
+ }
1044
1401
  const result = skills.map(s => ({
1045
1402
  id: s.id,
1046
1403
  name: s.name,
@@ -1080,6 +1437,20 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1080
1437
  return { content: [{ type: "text", text: `Skill "${skill.name}" ${args.enabled ? "enabled" : "disabled"}` }] };
1081
1438
  }
1082
1439
 
1440
+ case "update_skill": {
1441
+ const skill = SkillDB.findById(args.skill_id);
1442
+ if (!skill) {
1443
+ return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
1444
+ }
1445
+ const updates: Record<string, any> = {};
1446
+ if (args.name !== undefined) updates.name = args.name;
1447
+ if (args.description !== undefined) updates.description = args.description;
1448
+ if (args.content !== undefined) updates.content = args.content;
1449
+ if (args.allowed_tools !== undefined) updates.allowed_tools = args.allowed_tools;
1450
+ const updated = SkillDB.update(args.skill_id, updates);
1451
+ return { content: [{ type: "text", text: `Skill "${updated?.name || skill.name}" updated.` }] };
1452
+ }
1453
+
1083
1454
  case "create_skill": {
1084
1455
  if (!args.name || !args.description || !args.content) {
1085
1456
  return { content: [{ type: "text", text: "name, description, and content are required" }], isError: true };
@@ -1241,26 +1612,6 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1241
1612
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1242
1613
  }
1243
1614
 
1244
- case "list_connected_accounts": {
1245
- const providerId = args.provider;
1246
- const projectId = args.project_id || null;
1247
- const integrationProvider = getProvider(providerId);
1248
- if (!integrationProvider) {
1249
- return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
1250
- }
1251
- const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1252
- if (!apiKey) {
1253
- return { content: [{ type: "text", text: `${integrationProvider.name} API key not configured` }], isError: true };
1254
- }
1255
- const accounts = await integrationProvider.listConnectedAccounts(apiKey, "platform-agent");
1256
- const result = accounts.map(a => ({
1257
- id: a.id,
1258
- appName: a.appName,
1259
- status: a.status,
1260
- createdAt: a.createdAt,
1261
- }));
1262
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1263
- }
1264
1615
 
1265
1616
  case "list_subscriptions": {
1266
1617
  let subscriptions;
@@ -1321,9 +1672,11 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1321
1672
  }
1322
1673
  }
1323
1674
 
1324
- // Create remote trigger on the provider
1675
+ // Create remote trigger on the provider — auto-inject callback_url
1676
+ const webhookUrl = SettingsDB.get(`${providerId}_webhook_url`);
1325
1677
  const config: Record<string, unknown> = {
1326
1678
  agent_id: args.agent_id,
1679
+ ...(webhookUrl ? { callback_url: webhookUrl } : {}),
1327
1680
  ...(args.config || {}),
1328
1681
  };
1329
1682
  const triggerResult = await triggerProvider.createTrigger(apiKey, args.trigger_slug, args.connected_account_id, config);
@@ -1732,20 +2085,22 @@ export async function handlePlatformMcpRequest(req: Request): Promise<Response>
1732
2085
  instructions: `This MCP server controls the Apteva AI agent management platform.
1733
2086
 
1734
2087
  You can manage:
1735
- - AGENTS: Create, configure, start, stop, and delete AI agents. Each agent has a provider (LLM), model, system prompt, and optional features (memory, tasks, vision, MCP tools, files).
1736
- - PROJECTS: Organize agents into projects for grouping.
1737
- - MCP SERVERS: Tool integrations that give agents capabilities (web search, file access, APIs). Assign servers to agents.
1738
- - SKILLS: Reusable instruction sets that specialize agent behavior. Use create_skill to create new skills (pass project_id from context to scope to the current project), then assign them to agents. Use list_skills, get_skill, create_skill, toggle_skill, assign_skill_to_agent, unassign_skill_from_agent, delete_skill.
2088
+ - AGENTS: Create, configure, start, stop, and delete AI agents. Each agent has a provider (LLM), model, system prompt, and optional features (memory, tasks, vision, MCP tools, files). Use update_agent to assign/remove MCP servers (add_mcp_servers, remove_mcp_servers) and skills (add_skills, remove_skills).
2089
+ - TASKS: List, create, delete, and execute tasks on running agents. Tasks are scheduled work items that agents execute autonomously. Agents must have the tasks feature enabled. Tools: list_agent_tasks, create_agent_task, delete_agent_task, execute_agent_task.
2090
+ - PROJECTS: Organize agents into projects. Tools: list_projects, create_project, update_project, delete_project. Use project tools when the user is viewing All Projects. Deleting a project unassigns its agents (does not delete them).
2091
+ - MCP SERVERS: Tool integrations that give agents capabilities (web search, file access, APIs). Use update_agent with add_mcp_servers to assign servers to agents.
2092
+ - SKILLS: Reusable instruction sets that specialize agent behavior. Use create_skill to create new skills (pass project_id from context to scope to the current project). Use update_agent with add_skills/remove_skills to assign/unassign skills to agents. Tools: list_skills, get_skill, create_skill, update_skill, toggle_skill, delete_skill.
1739
2093
  - PROVIDERS: View which LLM providers have API keys configured.
1740
- - TESTS: Create and run automated tests for agent workflows. Tests send a message to an agent, then an LLM judge evaluates the response against success criteria. Use list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
1741
- - SUBSCRIPTIONS & TRIGGERS: Subscribe agents to external events (webhooks). Supports multiple providers (composio, agentdojo). Use list_trigger_providers → list_trigger_types → list_connected_accounts → create_subscription. Manage with enable_subscription, disable_subscription, delete_subscription, list_subscriptions.
1742
- - INTEGRATIONS: Connect third-party apps and create MCP servers from them. Supports agentdojo and composio providers. Use list_integration_providers → list_integration_apps → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → assign_mcp_server_to_agent. For OAuth apps, direct the user to the Browse Toolkits UI.
2094
+ - TESTS: Create and run automated tests for agent workflows. Tests send a message to an agent, then an LLM judge evaluates the response against success criteria. Tools: list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
2095
+ - SUBSCRIPTIONS & TRIGGERS: Subscribe agents to external events (webhooks). Supports multiple providers (composio, agentdojo). Use list_trigger_providers → list_trigger_types → list_integration_connections → create_subscription. Manage with enable_subscription, disable_subscription, delete_subscription, list_subscriptions.
2096
+ - INTEGRATIONS: Connect third-party apps and create MCP servers from them. Supports agentdojo and composio providers. Use list_integration_providers → list_integration_apps → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → then update_agent with add_mcp_servers to assign to an agent. For OAuth apps, direct the user to the Browse Toolkits UI.
1743
2097
 
1744
2098
  CRITICAL: ALWAYS pass project_id to every tool call that accepts it. API keys and resources are scoped per project — calls without project_id will fail. The chat context tells you the current project id.
1745
2099
 
1746
- Typical workflow: list_providers → create_agent → assign MCP servers/skills → start_agent.
1747
- Integration workflow: list_integration_providerslist_integration_apps (browse) → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → assign_mcp_server_to_agent.
1748
- Subscription workflow: list_trigger_providerslist_trigger_types (pick trigger) → list_connected_accounts (pick account) → create_subscription (link trigger to agent).
2100
+ Typical workflow: list_providers → create_agent → update_agent (add_mcp_servers/add_skills) → start_agent.
2101
+ Task workflow: list_agent_taskscreate_agent_task (schedule work) → execute_agent_task (run immediately).
2102
+ Integration workflow: list_integration_providerslist_integration_apps (browse) → connect_integration_app (API key) → create_integration_config add_integration_config_locally update_agent (add_mcp_servers).
2103
+ Subscription workflow: list_trigger_providers → list_trigger_types (pick trigger) → list_integration_connections (pick account) → create_subscription (link trigger to agent).
1749
2104
  Test workflow: create_test (set agent, message, eval criteria) → run_test → check results.
1750
2105
  Always use list_providers first to check which providers have API keys before creating agents.`,
1751
2106
  };