apteva 0.4.31 → 0.4.41

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 (89) hide show
  1. package/dist/ActivityPage.7907h64p.js +3 -0
  2. package/dist/ApiDocsPage.k3jjenpq.js +4 -0
  3. package/dist/App.01nq20st.js +4 -0
  4. package/dist/App.1maqvamf.js +4 -0
  5. package/dist/App.2yjrh32f.js +4 -0
  6. package/dist/App.3qw8nben.js +20 -0
  7. package/dist/App.7fb3e7mp.js +4 -0
  8. package/dist/App.7sy3wq8c.js +4 -0
  9. package/dist/App.apjrmctz.js +57 -0
  10. package/dist/App.av6t2yhe.js +4 -0
  11. package/dist/App.jqj5a094.js +46 -0
  12. package/dist/App.mc7xf85h.js +4 -0
  13. package/dist/App.myxqcj9x.js +4 -0
  14. package/dist/App.nm91r1mp.js +13 -0
  15. package/dist/App.qcknavjz.js +221 -0
  16. package/dist/App.vc7vfhg4.js +4 -0
  17. package/dist/App.z4s9zkw5.js +4 -0
  18. package/dist/ConnectionsPage.z1pw5xe2.js +3 -0
  19. package/dist/McpPage.8vc97z0b.js +3 -0
  20. package/dist/SettingsPage.p61bz8kd.js +3 -0
  21. package/dist/SkillsPage.r9x43g3g.js +3 -0
  22. package/dist/TasksPage.1e0zkye4.js +3 -0
  23. package/dist/TelemetryPage.p9vbe4gf.js +3 -0
  24. package/dist/TestsPage.d4xy504e.js +3 -0
  25. package/dist/ThreadsPage.m016am3x.js +3 -0
  26. package/dist/index.html +1 -1
  27. package/dist/styles.css +1 -1
  28. package/package.json +8 -7
  29. package/src/crypto.ts +4 -3
  30. package/src/db.ts +153 -28
  31. package/src/integrations/agentdojo.ts +94 -12
  32. package/src/integrations/index.ts +7 -0
  33. package/src/mcp-platform.ts +494 -121
  34. package/src/providers.ts +12 -12
  35. package/src/routes/api/agent-utils.ts +59 -46
  36. package/src/routes/api/agents.ts +52 -1
  37. package/src/routes/api/integrations.ts +11 -5
  38. package/src/routes/api/mcp.ts +5 -4
  39. package/src/routes/api/meta-agent.ts +35 -1
  40. package/src/routes/api/projects.ts +3 -3
  41. package/src/routes/api/providers.ts +121 -30
  42. package/src/routes/api/skills.ts +2 -3
  43. package/src/routes/api/system.ts +8 -13
  44. package/src/server.ts +31 -32
  45. package/src/triggers/agentdojo.ts +2 -2
  46. package/src/web/App.tsx +18 -10
  47. package/src/web/components/activity/ActivityPage.tsx +241 -388
  48. package/src/web/components/agents/AgentCard.tsx +5 -13
  49. package/src/web/components/common/Icons.tsx +8 -0
  50. package/src/web/components/common/Select.tsx +4 -3
  51. package/src/web/components/dashboard/Dashboard.tsx +155 -30
  52. package/src/web/components/index.ts +1 -1
  53. package/src/web/components/layout/Sidebar.tsx +7 -1
  54. package/src/web/components/mcp/IntegrationsPanel.tsx +126 -35
  55. package/src/web/components/mcp/McpPage.tsx +10 -1
  56. package/src/web/components/meta-agent/MetaAgent.tsx +4 -2
  57. package/src/web/components/settings/SettingsPage.tsx +133 -48
  58. package/src/web/components/tasks/TasksPage.tsx +48 -16
  59. package/src/web/components/telemetry/TelemetryPage.tsx +184 -0
  60. package/src/web/components/threads/ThreadsPage.tsx +313 -0
  61. package/src/web/context/AuthContext.tsx +3 -3
  62. package/src/web/context/ProjectContext.tsx +3 -3
  63. package/src/web/context/TelemetryContext.tsx +24 -6
  64. package/src/web/context/index.ts +1 -1
  65. package/src/web/styles.css +20 -4
  66. package/src/web/types.ts +4 -3
  67. package/dist/ActivityPage.41nbye4r.js +0 -3
  68. package/dist/ApiDocsPage.4smnt8m3.js +0 -4
  69. package/dist/App.0sbax9et.js +0 -4
  70. package/dist/App.0ws427h8.js +0 -4
  71. package/dist/App.6q6bar8b.js +0 -4
  72. package/dist/App.80301vdb.js +0 -4
  73. package/dist/App.af2wg84v.js +0 -267
  74. package/dist/App.ca1rz1ph.js +0 -4
  75. package/dist/App.ensa6z0r.js +0 -4
  76. package/dist/App.f8g7tych.js +0 -13
  77. package/dist/App.mvtqv6qc.js +0 -20
  78. package/dist/App.ncgc9cxy.js +0 -4
  79. package/dist/App.p0fb1pds.js +0 -4
  80. package/dist/App.pmaq48sj.js +0 -4
  81. package/dist/App.yv87t9m5.js +0 -4
  82. package/dist/App.zjmfm8p6.js +0 -4
  83. package/dist/ConnectionsPage.anb3rv9a.js +0 -3
  84. package/dist/McpPage.y396h6fy.js +0 -3
  85. package/dist/SettingsPage.p1hc60gk.js +0 -3
  86. package/dist/SkillsPage.yj3xdsay.js +0 -3
  87. package/dist/TasksPage.sjv0khtv.js +0 -3
  88. package/dist/TelemetryPage.2qm4w16r.js +0 -3
  89. package/dist/TestsPage.zzs4qfj8.js +0 -3
@@ -10,9 +10,21 @@ import { agentProcesses } from "./server";
10
10
  import { getTriggerProvider, getTriggerProviderIds, registerTriggerProvider } from "./triggers";
11
11
  import { ComposioTriggerProvider } from "./triggers/composio";
12
12
  import { AgentDojoTriggerProvider } from "./triggers/agentdojo";
13
- import { getProvider, registerProvider } from "./integrations";
13
+ import { getProvider, getProviderIds, registerProvider } from "./integrations";
14
14
  import { ComposioProvider } from "./integrations/composio";
15
- import { AgentDojoProvider } from "./integrations/agentdojo";
15
+ import {
16
+ AgentDojoProvider,
17
+ listServers as listAgentDojoServers,
18
+ createServer as createAgentDojoServer,
19
+ getServer as getAgentDojoServer,
20
+ } from "./integrations/agentdojo";
21
+ import {
22
+ listMcpServers as listComposioServers,
23
+ createMcpServer as createComposioServer,
24
+ getAuthConfigForToolkit as getComposioAuthConfig,
25
+ getUserIdForAuthConfig as getComposioUserForAuth,
26
+ createMcpServerInstance as createComposioInstance,
27
+ } from "./integrations/composio";
16
28
 
17
29
  // Register trigger + integration providers on module load
18
30
  registerTriggerProvider(ComposioTriggerProvider);
@@ -30,6 +42,7 @@ interface JsonRpcRequest {
30
42
  params?: any;
31
43
  }
32
44
 
45
+
33
46
  interface JsonRpcResponse {
34
47
  jsonrpc: "2.0";
35
48
  id: number;
@@ -65,7 +78,7 @@ const PLATFORM_TOOLS = [
65
78
  description: `Create a new AI agent. The provider must have an API key configured — use list_providers first to check.
66
79
 
67
80
  PROVIDERS & MODELS (use list_providers to see which have keys):
68
- - anthropic: claude-sonnet-4-5 (recommended), claude-haiku-4-5 (fast/cheap)
81
+ - anthropic: claude-sonnet-4-6 (recommended), claude-sonnet-4-5, claude-haiku-4-5 (fast/cheap)
69
82
  - openai: gpt-4o (recommended), gpt-4o-mini (fast/cheap)
70
83
  - groq: llama-3.3-70b-versatile (recommended), llama-3.1-8b-instant (fast)
71
84
  - gemini: gemini-3-pro-preview (recommended), gemini-3-flash-preview (fast)
@@ -111,7 +124,14 @@ TIPS:
111
124
  },
112
125
  {
113
126
  name: "update_agent",
114
- description: "Update an existing agent's configuration. Only provide fields you want to change. If the agent is running, restart it after updating for changes to take effect.",
127
+ description: `Update an existing agent's configuration. Only provide fields you want to change. If the agent is running, restart it after updating for changes to take effect.
128
+
129
+ SKILLS & MCP SERVERS:
130
+ - Pass skill_ids or mcp_server_ids to SET the full list (replaces existing).
131
+ - Use add_skills / remove_skills to add/remove individual skills without replacing the whole list.
132
+ - Use add_mcp_servers / remove_mcp_servers to add/remove individual MCP servers.
133
+ - Adding MCP servers automatically enables the mcp feature.
134
+ - Use list_skills and list_mcp_servers to find IDs.`,
115
135
  inputSchema: {
116
136
  type: "object",
117
137
  properties: {
@@ -132,6 +152,12 @@ TIPS:
132
152
  files: { type: "boolean" },
133
153
  },
134
154
  },
155
+ skill_ids: { type: "array", items: { type: "string" }, description: "Set the full list of skill IDs (replaces existing)" },
156
+ add_skills: { type: "array", items: { type: "string" }, description: "Skill IDs to add (keeps existing)" },
157
+ remove_skills: { type: "array", items: { type: "string" }, description: "Skill IDs to remove" },
158
+ mcp_server_ids: { type: "array", items: { type: "string" }, description: "Set the full list of MCP server IDs (replaces existing)" },
159
+ add_mcp_servers: { type: "array", items: { type: "string" }, description: "MCP server IDs to add (keeps existing)" },
160
+ remove_mcp_servers: { type: "array", items: { type: "string" }, description: "MCP server IDs to remove" },
135
161
  },
136
162
  required: ["agent_id"],
137
163
  },
@@ -256,30 +282,6 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
256
282
  required: ["server_id"],
257
283
  },
258
284
  },
259
- {
260
- name: "assign_mcp_server_to_agent",
261
- description: "Assign an MCP server to an agent so the agent can use its tools. This automatically enables the MCP feature on the agent. If the agent is running, restart it for changes to take effect.",
262
- inputSchema: {
263
- type: "object",
264
- properties: {
265
- agent_id: { type: "string", description: "The agent ID" },
266
- server_id: { type: "string", description: "The MCP server ID to assign" },
267
- },
268
- required: ["agent_id", "server_id"],
269
- },
270
- },
271
- {
272
- name: "unassign_mcp_server_from_agent",
273
- description: "Remove an MCP server from an agent.",
274
- inputSchema: {
275
- type: "object",
276
- properties: {
277
- agent_id: { type: "string", description: "The agent ID" },
278
- server_id: { type: "string", description: "The MCP server ID to remove" },
279
- },
280
- required: ["agent_id", "server_id"],
281
- },
282
- },
283
285
  {
284
286
  name: "get_dashboard_stats",
285
287
  description: "Get platform overview stats: agent counts, task counts, provider counts.",
@@ -334,30 +336,6 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
334
336
  required: ["skill_id", "enabled"],
335
337
  },
336
338
  },
337
- {
338
- name: "assign_skill_to_agent",
339
- description: "Assign a skill to an agent. The skill's instructions and tool permissions will be pushed to the agent on next start/restart.",
340
- inputSchema: {
341
- type: "object",
342
- properties: {
343
- agent_id: { type: "string", description: "The agent ID" },
344
- skill_id: { type: "string", description: "The skill ID to assign" },
345
- },
346
- required: ["agent_id", "skill_id"],
347
- },
348
- },
349
- {
350
- name: "unassign_skill_from_agent",
351
- description: "Remove a skill from an agent.",
352
- inputSchema: {
353
- type: "object",
354
- properties: {
355
- agent_id: { type: "string", description: "The agent ID" },
356
- skill_id: { type: "string", description: "The skill ID to remove" },
357
- },
358
- required: ["agent_id", "skill_id"],
359
- },
360
- },
361
339
  {
362
340
  name: "create_skill",
363
341
  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.",
@@ -581,8 +559,159 @@ IMPORTANT: Some triggers require extra config fields (e.g. GitHub triggers need
581
559
  required: ["subscription_id"],
582
560
  },
583
561
  },
562
+ // Integration management tools
563
+ {
564
+ name: "list_integration_providers",
565
+ description: "List available integration providers (e.g. agentdojo, composio) and whether they have API keys configured. Integration providers give access to third-party apps, OAuth connections, and MCP server creation.",
566
+ inputSchema: {
567
+ type: "object",
568
+ properties: {
569
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
570
+ },
571
+ },
572
+ },
573
+ {
574
+ name: "list_integration_apps",
575
+ description: `List available apps/toolkits from an integration provider. Each app represents a service (e.g. GitHub, Slack, Stripe) that can be connected via OAuth or API key.
576
+
577
+ Returns apps with their auth schemes (OAUTH2, API_KEY), connection status, and categories.
578
+ Use this to browse what's available before connecting an app.`,
579
+ inputSchema: {
580
+ type: "object",
581
+ properties: {
582
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
583
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
584
+ search: { type: "string", description: "Optional search filter for app name/slug" },
585
+ },
586
+ required: ["provider"],
587
+ },
588
+ },
589
+ {
590
+ name: "connect_integration_app",
591
+ description: `Connect/authenticate an app by storing credentials on the integration provider. This enables the app's tools to be used in MCP servers.
592
+
593
+ NOTE: Only API_KEY auth is supported from the assistant. For OAuth apps, direct the user to the Browse Toolkits UI.
594
+
595
+ Some apps require multiple credential fields (e.g. Pushover needs appToken + userKey). Use list_integration_apps to see credentialFields for each app — if present, pass all required fields in the "credentials" object. If the app has no credentialFields, pass a single "api_key".
596
+
597
+ WORKFLOW:
598
+ 1. list_integration_apps to find the app and its credentialFields
599
+ 2. connect_integration_app with the app slug and credentials
600
+ 3. create_integration_config to create an MCP server from the connected app
601
+ 4. add_integration_config_locally to add it as a local MCP server`,
602
+ inputSchema: {
603
+ type: "object",
604
+ properties: {
605
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
606
+ app_slug: { type: "string", description: "App slug (from list_integration_apps)" },
607
+ api_key: { type: "string", description: "Single API key (for apps with no credentialFields)" },
608
+ credentials: {
609
+ type: "object",
610
+ description: "Credential fields as key-value pairs (e.g. { appToken: '...', userKey: '...' }). Use this for apps with multiple credentialFields. Takes priority over api_key.",
611
+ },
612
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
613
+ },
614
+ required: ["provider", "app_slug"],
615
+ },
616
+ },
617
+ {
618
+ name: "list_integration_connections",
619
+ description: "List connected accounts (credentials) for an integration provider. Shows which apps have been authenticated and their status.",
620
+ inputSchema: {
621
+ type: "object",
622
+ properties: {
623
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
624
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
625
+ },
626
+ required: ["provider"],
627
+ },
628
+ },
629
+ {
630
+ name: "disconnect_integration_app",
631
+ description: "Disconnect/revoke a connected account (credential) from an integration provider.",
632
+ inputSchema: {
633
+ type: "object",
634
+ properties: {
635
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
636
+ connection_id: { type: "string", description: "Connection/credential ID (from list_integration_connections)" },
637
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
638
+ },
639
+ required: ["provider", "connection_id"],
640
+ },
641
+ },
642
+ {
643
+ name: "list_integration_configs",
644
+ description: "List MCP server configs on an integration provider. These are remote MCP servers hosted by the provider that can be added locally.",
645
+ inputSchema: {
646
+ type: "object",
647
+ properties: {
648
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
649
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
650
+ },
651
+ required: ["provider"],
652
+ },
653
+ },
654
+ {
655
+ name: "create_integration_config",
656
+ description: `Create an MCP server config on an integration provider from a connected app/toolkit. This creates a remote MCP server on the provider that bundles the app's tools.
657
+
658
+ After creation, use add_integration_config_locally to add it as a local MCP server.
659
+
660
+ WORKFLOW:
661
+ 1. list_integration_apps → find the app slug
662
+ 2. Ensure app is connected (list_integration_connections or connect_integration_app)
663
+ 3. create_integration_config → creates remote MCP server
664
+ 4. add_integration_config_locally → adds it locally so agents can use it`,
665
+ inputSchema: {
666
+ type: "object",
667
+ properties: {
668
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
669
+ name: { type: "string", description: "Name for the MCP config (e.g. 'GitHub MCP', 'Slack Tools')" },
670
+ toolkit_slug: { type: "string", description: "Toolkit/app slug to create the config from" },
671
+ project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
672
+ },
673
+ required: ["provider", "name", "toolkit_slug"],
674
+ },
675
+ },
676
+ {
677
+ name: "add_integration_config_locally",
678
+ description: `Add a remote integration MCP config as a local MCP server. This creates a local MCP server entry that connects to the provider's hosted MCP endpoint, making its tools available to agents.
679
+
680
+ After adding, use assign_mcp_server_to_agent to give an agent access to these tools.`,
681
+ inputSchema: {
682
+ type: "object",
683
+ properties: {
684
+ provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
685
+ config_id: { type: "string", description: "Config/server ID on the provider (from list_integration_configs or create_integration_config)" },
686
+ project_id: { type: "string", description: "Project ID to scope the local server to (optional)" },
687
+ },
688
+ required: ["provider", "config_id"],
689
+ },
690
+ },
584
691
  ];
585
692
 
693
+ // Build tools list — when PROJECTS_ENABLED, add project_id to required for all tools that accept it
694
+ function getPlatformTools() {
695
+ const projectsEnabled = process.env.PROJECTS_ENABLED === "true";
696
+ if (!projectsEnabled) return PLATFORM_TOOLS;
697
+
698
+ return PLATFORM_TOOLS.map(tool => {
699
+ const props = tool.inputSchema.properties as Record<string, unknown> | undefined;
700
+ if (!props || !("project_id" in props)) return tool;
701
+
702
+ const existing = (tool.inputSchema as any).required || [];
703
+ if (existing.includes("project_id")) return tool;
704
+
705
+ return {
706
+ ...tool,
707
+ inputSchema: {
708
+ ...tool.inputSchema,
709
+ required: [...existing, "project_id"],
710
+ },
711
+ };
712
+ });
713
+ }
714
+
586
715
  // Tool execution handlers
587
716
  async function executeTool(name: string, args: Record<string, any>): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
588
717
  try {
@@ -662,8 +791,46 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
662
791
  updates.features = { ...agent.features, ...args.features };
663
792
  }
664
793
 
794
+ // Skills: set, add, or remove
795
+ let skills = agent.skills || [];
796
+ if (args.skill_ids !== undefined) {
797
+ skills = args.skill_ids;
798
+ }
799
+ if (args.add_skills) {
800
+ for (const sid of args.add_skills) {
801
+ if (!skills.includes(sid)) skills.push(sid);
802
+ }
803
+ }
804
+ if (args.remove_skills) {
805
+ skills = skills.filter((id: string) => !args.remove_skills.includes(id));
806
+ }
807
+ if (args.skill_ids !== undefined || args.add_skills || args.remove_skills) {
808
+ updates.skills = skills;
809
+ }
810
+
811
+ // MCP servers: set, add, or remove
812
+ let mcpServers = agent.mcp_servers || [];
813
+ if (args.mcp_server_ids !== undefined) {
814
+ mcpServers = args.mcp_server_ids;
815
+ }
816
+ if (args.add_mcp_servers) {
817
+ for (const sid of args.add_mcp_servers) {
818
+ if (!mcpServers.includes(sid)) mcpServers.push(sid);
819
+ }
820
+ }
821
+ if (args.remove_mcp_servers) {
822
+ mcpServers = mcpServers.filter((id: string) => !args.remove_mcp_servers.includes(id));
823
+ }
824
+ if (args.mcp_server_ids !== undefined || args.add_mcp_servers || args.remove_mcp_servers) {
825
+ updates.mcp_servers = mcpServers;
826
+ // Auto-enable MCP feature if servers are being added
827
+ if (mcpServers.length > 0 && !agent.features.mcp) {
828
+ updates.features = { ...(updates.features || agent.features), mcp: true };
829
+ }
830
+ }
831
+
665
832
  const updated = AgentDB.update(args.agent_id, updates);
666
- return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify({ id: updated?.id, name: updated?.name }, null, 2)}` }] };
833
+ return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify({ id: updated?.id, name: updated?.name, skills: updates.skills !== undefined ? updates.skills.length + " skills" : "unchanged", mcp_servers: updates.mcp_servers !== undefined ? updates.mcp_servers.length + " servers" : "unchanged" }, null, 2)}` }] };
667
834
  }
668
835
 
669
836
  case "delete_agent": {
@@ -754,8 +921,8 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
754
921
 
755
922
  case "list_mcp_servers": {
756
923
  const servers = args.project_id
757
- ? McpServerDB.findByProject(args.project_id)
758
- : McpServerDB.findAll();
924
+ ? McpServerDB.findByProjectLight(args.project_id)
925
+ : McpServerDB.findAllLight();
759
926
  const result = servers.map(s => ({
760
927
  id: s.id,
761
928
  name: s.name,
@@ -819,47 +986,13 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
819
986
  return { content: [{ type: "text", text: `MCP server deleted: ${server.name} (${server.id})` }] };
820
987
  }
821
988
 
822
- case "assign_mcp_server_to_agent": {
823
- const agent = AgentDB.findById(args.agent_id);
824
- if (!agent) {
825
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
826
- }
827
- const server = McpServerDB.findById(args.server_id);
828
- if (!server) {
829
- return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
830
- }
831
- const mcpServers = agent.mcp_servers || [];
832
- if (mcpServers.includes(args.server_id)) {
833
- return { content: [{ type: "text", text: `Server ${server.name} is already assigned to ${agent.name}` }] };
834
- }
835
- AgentDB.update(args.agent_id, { mcp_servers: [...mcpServers, args.server_id] });
836
- // Enable MCP feature if not already
837
- if (!agent.features.mcp) {
838
- AgentDB.update(args.agent_id, { features: { ...agent.features, mcp: true } });
839
- }
840
- return { content: [{ type: "text", text: `Assigned MCP server "${server.name}" to agent "${agent.name}". Restart the agent for changes to take effect.` }] };
841
- }
842
-
843
- case "unassign_mcp_server_from_agent": {
844
- const agent = AgentDB.findById(args.agent_id);
845
- if (!agent) {
846
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
847
- }
848
- const mcpServers = agent.mcp_servers || [];
849
- if (!mcpServers.includes(args.server_id)) {
850
- return { content: [{ type: "text", text: `Server is not assigned to this agent` }] };
851
- }
852
- AgentDB.update(args.agent_id, { mcp_servers: mcpServers.filter((id: string) => id !== args.server_id) });
853
- return { content: [{ type: "text", text: `Removed MCP server from agent "${agent.name}". Restart the agent for changes to take effect.` }] };
854
- }
855
-
856
989
  case "get_dashboard_stats": {
857
990
  const agentCount = AgentDB.count();
858
991
  const runningCount = AgentDB.countRunning();
859
992
  const projectCount = ProjectDB.count();
860
993
  const providers = getProvidersWithStatus().filter(p => p.type === "llm");
861
994
  const configuredProviders = providers.filter(p => p.hasKey).length;
862
- const mcpServerCount = McpServerDB.findAll().length;
995
+ const mcpServerCount = McpServerDB.count();
863
996
  const skillCount = SkillDB.count();
864
997
 
865
998
  return {
@@ -947,36 +1080,6 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
947
1080
  return { content: [{ type: "text", text: `Skill "${skill.name}" ${args.enabled ? "enabled" : "disabled"}` }] };
948
1081
  }
949
1082
 
950
- case "assign_skill_to_agent": {
951
- const agent = AgentDB.findById(args.agent_id);
952
- if (!agent) {
953
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
954
- }
955
- const skill = SkillDB.findById(args.skill_id);
956
- if (!skill) {
957
- return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
958
- }
959
- const skills = agent.skills || [];
960
- if (skills.includes(args.skill_id)) {
961
- return { content: [{ type: "text", text: `Skill "${skill.name}" is already assigned to "${agent.name}"` }] };
962
- }
963
- AgentDB.update(args.agent_id, { skills: [...skills, args.skill_id] });
964
- return { content: [{ type: "text", text: `Assigned skill "${skill.name}" to agent "${agent.name}". Restart the agent for changes to take effect.` }] };
965
- }
966
-
967
- case "unassign_skill_from_agent": {
968
- const agent = AgentDB.findById(args.agent_id);
969
- if (!agent) {
970
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
971
- }
972
- const skills = agent.skills || [];
973
- if (!skills.includes(args.skill_id)) {
974
- return { content: [{ type: "text", text: `Skill is not assigned to this agent` }] };
975
- }
976
- AgentDB.update(args.agent_id, { skills: skills.filter((id: string) => id !== args.skill_id) });
977
- return { content: [{ type: "text", text: `Removed skill from agent "${agent.name}". Restart the agent for changes to take effect.` }] };
978
- }
979
-
980
1083
  case "create_skill": {
981
1084
  if (!args.name || !args.description || !args.content) {
982
1085
  return { content: [{ type: "text", text: "name, description, and content are required" }], isError: true };
@@ -1313,6 +1416,272 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1313
1416
  return { content: [{ type: "text", text: `Subscription "${sub.trigger_slug}" deleted` }] };
1314
1417
  }
1315
1418
 
1419
+ // Integration management tools
1420
+ case "list_integration_providers": {
1421
+
1422
+ const providerIds = getProviderIds();
1423
+ const projectId = args.project_id || null;
1424
+ const result = providerIds.map(id => {
1425
+ const provider = getProvider(id);
1426
+ const hasKey = !!ProviderKeys.getDecryptedForProject(id, projectId);
1427
+ return { id, name: provider?.name || id, hasKey };
1428
+ });
1429
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1430
+ }
1431
+
1432
+ case "list_integration_apps": {
1433
+
1434
+ const providerId = args.provider;
1435
+ const projectId = args.project_id || null;
1436
+ const provider = getProvider(providerId);
1437
+ if (!provider) {
1438
+ return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}. Available: ${getProviderIds().join(", ")}` }], isError: true };
1439
+ }
1440
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1441
+ if (!apiKey) {
1442
+ return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
1443
+ }
1444
+ let apps = await provider.listApps(apiKey);
1445
+ if (args.search) {
1446
+ const s = args.search.toLowerCase();
1447
+ apps = apps.filter(a =>
1448
+ a.name.toLowerCase().includes(s) ||
1449
+ a.slug.toLowerCase().includes(s) ||
1450
+ a.description?.toLowerCase().includes(s)
1451
+ );
1452
+ }
1453
+ // Also check which are connected
1454
+ let connectedIds = new Set<string>();
1455
+ try {
1456
+ const accounts = await provider.listConnectedAccounts(apiKey, "platform-agent");
1457
+ connectedIds = new Set(accounts.filter(a => a.status === "active").map(a => a.appId));
1458
+ } catch {}
1459
+ const result = apps.slice(0, 50).map(a => ({
1460
+ slug: a.slug,
1461
+ name: a.name,
1462
+ description: a.description,
1463
+ authSchemes: a.authSchemes,
1464
+ categories: a.categories,
1465
+ connected: connectedIds.has(a.slug) || (a.providerSlug ? connectedIds.has(a.providerSlug) : false),
1466
+ credentialFields: a.credentialFields || undefined,
1467
+ }));
1468
+ return { content: [{ type: "text", text: `Found ${apps.length} apps (showing ${result.length}):\n${JSON.stringify(result, null, 2)}` }] };
1469
+ }
1470
+
1471
+ case "connect_integration_app": {
1472
+ const providerId = args.provider;
1473
+ const projectId = args.project_id || null;
1474
+ const provider = getProvider(providerId);
1475
+ if (!provider) {
1476
+ return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
1477
+ }
1478
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1479
+ if (!apiKey) {
1480
+ return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
1481
+ }
1482
+ if (!args.api_key && !args.credentials) {
1483
+ return { content: [{ type: "text", text: `Either api_key or credentials is required` }], isError: true };
1484
+ }
1485
+ // Build credential object — prefer multi-field credentials over single api_key
1486
+ const creds: any = { authScheme: "API_KEY" as const };
1487
+ if (args.credentials && Object.keys(args.credentials).length > 0) {
1488
+ creds.fields = args.credentials;
1489
+ } else {
1490
+ creds.apiKey = args.api_key;
1491
+ }
1492
+ const connectionResult = await provider.initiateConnection(apiKey, "platform-agent", args.app_slug, "", creds);
1493
+ if (connectionResult.status === "active") {
1494
+ return { content: [{ type: "text", text: `Successfully connected "${args.app_slug}". Connection ID: ${connectionResult.connectionId || "N/A"}` }] };
1495
+ }
1496
+ return { content: [{ type: "text", text: `Connection initiated but status is ${connectionResult.status}. This may require OAuth — direct the user to the Browse Toolkits UI.` }] };
1497
+ }
1498
+
1499
+ case "list_integration_connections": {
1500
+ const providerId = args.provider;
1501
+ const projectId = args.project_id || null;
1502
+ const provider = getProvider(providerId);
1503
+ if (!provider) {
1504
+ return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
1505
+ }
1506
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1507
+ if (!apiKey) {
1508
+ return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
1509
+ }
1510
+ const accounts = await provider.listConnectedAccounts(apiKey, "platform-agent");
1511
+ const result = accounts.map(a => ({
1512
+ id: a.id,
1513
+ appName: a.appName,
1514
+ appId: a.appId,
1515
+ status: a.status,
1516
+ createdAt: a.createdAt,
1517
+ }));
1518
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1519
+ }
1520
+
1521
+ case "disconnect_integration_app": {
1522
+ const providerId = args.provider;
1523
+ const projectId = args.project_id || null;
1524
+ const provider = getProvider(providerId);
1525
+ if (!provider) {
1526
+ return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
1527
+ }
1528
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1529
+ if (!apiKey) {
1530
+ return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
1531
+ }
1532
+ const success = await provider.disconnect(apiKey, args.connection_id);
1533
+ return { content: [{ type: "text", text: success ? `Disconnected successfully` : `Failed to disconnect` }], isError: !success };
1534
+ }
1535
+
1536
+ case "list_integration_configs": {
1537
+ const providerId = args.provider;
1538
+ const projectId = args.project_id || null;
1539
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1540
+ if (!apiKey) {
1541
+ return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
1542
+ }
1543
+
1544
+ if (providerId === "agentdojo") {
1545
+ const servers = await listAgentDojoServers(apiKey, true);
1546
+ const result = servers.map(s => ({
1547
+ id: s.id,
1548
+ name: s.name,
1549
+ slug: s.slug,
1550
+ url: s.url,
1551
+ toolsCount: s.tools?.length || 0,
1552
+ }));
1553
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1554
+ } else if (providerId === "composio") {
1555
+ const servers = await listComposioServers(apiKey);
1556
+ const result = servers.map(s => ({
1557
+ id: s.id,
1558
+ name: s.name,
1559
+ url: s.mcpUrl,
1560
+ toolkits: s.toolkits,
1561
+ }));
1562
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1563
+ }
1564
+ return { content: [{ type: "text", text: `Config listing not supported for provider: ${providerId}` }], isError: true };
1565
+ }
1566
+
1567
+ case "create_integration_config": {
1568
+ const providerId = args.provider;
1569
+ const projectId = args.project_id || null;
1570
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1571
+ if (!apiKey) {
1572
+ return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
1573
+ }
1574
+
1575
+ if (providerId === "agentdojo") {
1576
+ const server = await createAgentDojoServer(apiKey, args.name, [args.toolkit_slug]);
1577
+ if (!server) {
1578
+ return { content: [{ type: "text", text: "Failed to create MCP config on AgentDojo" }], isError: true };
1579
+ }
1580
+ return { content: [{ type: "text", text: `MCP config created on AgentDojo:\n${JSON.stringify({ id: server.id, name: server.name, slug: server.slug, url: server.url }, null, 2)}\n\nUse add_integration_config_locally to add it as a local MCP server.` }] };
1581
+ } else if (providerId === "composio") {
1582
+ // For Composio, we need the authConfigId for the toolkit
1583
+ const authConfigId = await getComposioAuthConfig(apiKey, args.toolkit_slug);
1584
+ if (!authConfigId) {
1585
+ return { content: [{ type: "text", text: `No auth config found for toolkit "${args.toolkit_slug}". Make sure the app is connected first.` }], isError: true };
1586
+ }
1587
+ const server = await createComposioServer(apiKey, args.name, [authConfigId]);
1588
+ if (!server) {
1589
+ return { content: [{ type: "text", text: "Failed to create MCP config on Composio" }], isError: true };
1590
+ }
1591
+ // Create server instance for the user
1592
+ const userId = await getComposioUserForAuth(apiKey, authConfigId);
1593
+ if (userId) {
1594
+ await createComposioInstance(apiKey, server.id, userId);
1595
+ }
1596
+ return { content: [{ type: "text", text: `MCP config created on Composio:\n${JSON.stringify({ id: server.id, name: server.name, url: server.mcpUrl }, null, 2)}\n\nUse add_integration_config_locally to add it as a local MCP server.` }] };
1597
+ }
1598
+ return { content: [{ type: "text", text: `Config creation not supported for provider: ${providerId}` }], isError: true };
1599
+ }
1600
+
1601
+ case "add_integration_config_locally": {
1602
+ const providerId = args.provider;
1603
+ const projectId = args.project_id || null;
1604
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
1605
+ if (!apiKey) {
1606
+ return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
1607
+ }
1608
+
1609
+ const effectiveProjectId = projectId && projectId !== "unassigned" ? projectId : null;
1610
+
1611
+ if (providerId === "agentdojo") {
1612
+ const server = await getAgentDojoServer(apiKey, args.config_id);
1613
+ if (!server) {
1614
+ return { content: [{ type: "text", text: `Config not found on AgentDojo: ${args.config_id}` }], isError: true };
1615
+ }
1616
+ // Check if already exists locally
1617
+ const existing = McpServerDB.findAllLight().find(
1618
+ s => s.source === "agentdojo" && s.project_id === effectiveProjectId && s.url?.endsWith(`/${server.slug}`)
1619
+ );
1620
+ if (existing) {
1621
+ return { content: [{ type: "text", text: `Server "${server.name}" already exists locally (ID: ${existing.id})` }] };
1622
+ }
1623
+ const localServer = McpServerDB.create({
1624
+ id: generateId(),
1625
+ name: server.name,
1626
+ type: "http",
1627
+ package: null,
1628
+ command: null,
1629
+ args: null,
1630
+ pip_module: null,
1631
+ env: {},
1632
+ url: server.url,
1633
+ headers: { "X-API-Key": apiKey },
1634
+ source: "agentdojo",
1635
+ project_id: effectiveProjectId,
1636
+ });
1637
+ return { content: [{ type: "text", text: `Server "${server.name}" added locally (ID: ${localServer.id}). Use assign_mcp_server_to_agent to give agents access.` }] };
1638
+ } else if (providerId === "composio") {
1639
+ // Fetch config details from Composio
1640
+ const res = await fetch(`https://backend.composio.dev/api/v3/mcp/${args.config_id}`, {
1641
+ headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
1642
+ });
1643
+ if (!res.ok) {
1644
+ return { content: [{ type: "text", text: `Config not found on Composio: ${args.config_id}` }], isError: true };
1645
+ }
1646
+ const data = await res.json();
1647
+ const configName = data.name || `composio-${args.config_id.slice(0, 8)}`;
1648
+ const mcpUrl = data.mcp_url;
1649
+ if (!mcpUrl) {
1650
+ return { content: [{ type: "text", text: "Config does not have an MCP URL" }], isError: true };
1651
+ }
1652
+ // Check if already exists locally
1653
+ const existing = McpServerDB.findAllLight().find(
1654
+ s => s.source === "composio" && s.url?.includes(args.config_id)
1655
+ );
1656
+ if (existing) {
1657
+ return { content: [{ type: "text", text: `Server "${configName}" already exists locally (ID: ${existing.id})` }] };
1658
+ }
1659
+ // Get user_id for URL auth
1660
+ const authConfigIds = data.auth_config_ids || [];
1661
+ let userId: string | null = null;
1662
+ if (authConfigIds.length > 0) {
1663
+ userId = await getComposioUserForAuth(apiKey, authConfigIds[0]);
1664
+ }
1665
+ const mcpUrlWithUser = userId ? `${mcpUrl}?user_id=${encodeURIComponent(userId)}` : mcpUrl;
1666
+ const localServer = McpServerDB.create({
1667
+ id: generateId(),
1668
+ name: configName,
1669
+ type: "http",
1670
+ package: null,
1671
+ command: null,
1672
+ args: null,
1673
+ pip_module: null,
1674
+ env: {},
1675
+ url: mcpUrlWithUser,
1676
+ headers: { "x-api-key": apiKey },
1677
+ source: "composio",
1678
+ project_id: effectiveProjectId,
1679
+ });
1680
+ return { content: [{ type: "text", text: `Server "${configName}" added locally (ID: ${localServer.id}). Use assign_mcp_server_to_agent to give agents access.` }] };
1681
+ }
1682
+ return { content: [{ type: "text", text: `Local add not supported for provider: ${providerId}` }], isError: true };
1683
+ }
1684
+
1316
1685
  default:
1317
1686
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
1318
1687
  }
@@ -1370,8 +1739,12 @@ You can manage:
1370
1739
  - PROVIDERS: View which LLM providers have API keys configured.
1371
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.
1372
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.
1743
+
1744
+ 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.
1373
1745
 
1374
1746
  Typical workflow: list_providers → create_agent → assign MCP servers/skills → start_agent.
1747
+ Integration workflow: list_integration_providers → list_integration_apps (browse) → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → assign_mcp_server_to_agent.
1375
1748
  Subscription workflow: list_trigger_providers → list_trigger_types (pick trigger) → list_connected_accounts (pick account) → create_subscription (link trigger to agent).
1376
1749
  Test workflow: create_test (set agent, message, eval criteria) → run_test → check results.
1377
1750
  Always use list_providers first to check which providers have API keys before creating agents.`,
@@ -1386,7 +1759,7 @@ Always use list_providers first to check which providers have API keys before cr
1386
1759
  }
1387
1760
 
1388
1761
  case "tools/list": {
1389
- result = { tools: PLATFORM_TOOLS };
1762
+ result = { tools: getPlatformTools() };
1390
1763
  break;
1391
1764
  }
1392
1765