apteva 0.4.57 → 0.7.1
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/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +15 -76
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
package/src/mcp-platform.ts
DELETED
|
@@ -1,2403 +0,0 @@
|
|
|
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, McpServerToolDB, SkillDB, TelemetryDB, SubscriptionDB, SettingsDB, generateId } from "./db";
|
|
5
|
-
import { TestCaseDB, TestRunDB } from "./db-tests";
|
|
6
|
-
import { runTest, runAll } from "./test-runner";
|
|
7
|
-
import { getProvidersWithStatus, PROVIDERS, ProviderKeys } from "./providers";
|
|
8
|
-
import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch, fetchFromAgent, buildAgentConfig, pushConfigToAgent, pushSkillsToAgent } from "./routes/api/agent-utils";
|
|
9
|
-
import { agentProcesses } from "./server";
|
|
10
|
-
import { getTriggerProvider, getTriggerProviderIds, registerTriggerProvider } from "./triggers";
|
|
11
|
-
import { ComposioTriggerProvider } from "./triggers/composio";
|
|
12
|
-
import { AgentDojoTriggerProvider } from "./triggers/agentdojo";
|
|
13
|
-
import { getProvider, getProviderIds, registerProvider } from "./integrations";
|
|
14
|
-
import { ComposioProvider } from "./integrations/composio";
|
|
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";
|
|
28
|
-
|
|
29
|
-
// Register trigger + integration providers on module load
|
|
30
|
-
registerTriggerProvider(ComposioTriggerProvider);
|
|
31
|
-
registerTriggerProvider(AgentDojoTriggerProvider);
|
|
32
|
-
registerProvider(ComposioProvider);
|
|
33
|
-
registerProvider(AgentDojoProvider);
|
|
34
|
-
|
|
35
|
-
// MCP Protocol version
|
|
36
|
-
const PROTOCOL_VERSION = "2024-11-05";
|
|
37
|
-
|
|
38
|
-
interface JsonRpcRequest {
|
|
39
|
-
jsonrpc: "2.0";
|
|
40
|
-
id: number;
|
|
41
|
-
method: string;
|
|
42
|
-
params?: any;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
interface JsonRpcResponse {
|
|
47
|
-
jsonrpc: "2.0";
|
|
48
|
-
id: number;
|
|
49
|
-
result?: unknown;
|
|
50
|
-
error?: { code: number; message: string; data?: unknown };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Tool definitions
|
|
54
|
-
const PLATFORM_TOOLS = [
|
|
55
|
-
{
|
|
56
|
-
name: "list_agents",
|
|
57
|
-
description: "List all agents on the platform. Optionally filter by project ID.",
|
|
58
|
-
inputSchema: {
|
|
59
|
-
type: "object",
|
|
60
|
-
properties: {
|
|
61
|
-
project_id: { type: "string", description: "Filter by project ID (optional)" },
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "get_agent",
|
|
67
|
-
description: "Get detailed information about a specific agent by ID.",
|
|
68
|
-
inputSchema: {
|
|
69
|
-
type: "object",
|
|
70
|
-
properties: {
|
|
71
|
-
agent_id: { type: "string", description: "The agent ID" },
|
|
72
|
-
},
|
|
73
|
-
required: ["agent_id"],
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: "create_agent",
|
|
78
|
-
description: `Create a new AI agent. The provider must have an API key configured — use list_providers first to check.
|
|
79
|
-
|
|
80
|
-
PROVIDERS & MODELS (use list_providers to see which have keys):
|
|
81
|
-
- anthropic: claude-sonnet-4-6 (recommended), claude-sonnet-4-5, claude-haiku-4-5 (fast/cheap)
|
|
82
|
-
- openai: gpt-4o (recommended), gpt-4o-mini (fast/cheap)
|
|
83
|
-
- groq: llama-3.3-70b-versatile (recommended), llama-3.1-8b-instant (fast)
|
|
84
|
-
- gemini: gemini-3-pro-preview (recommended), gemini-3-flash-preview (fast)
|
|
85
|
-
- xai: grok-2 (recommended), grok-2-mini (fast)
|
|
86
|
-
- together: moonshotai/Kimi-K2.5 (recommended), moonshotai/Kimi-K2-Thinking (reasoning)
|
|
87
|
-
- fireworks: accounts/fireworks/models/kimi-k2p5, accounts/fireworks/models/kimi-k2-thinking
|
|
88
|
-
- moonshot: moonshot-v1-128k (recommended), moonshot-v1-32k (fast)
|
|
89
|
-
- ollama: llama3.3, llama3.2, qwen2.5, mistral, deepseek-r1 (local, no API key needed)
|
|
90
|
-
|
|
91
|
-
FEATURES (all optional, default false):
|
|
92
|
-
- memory: Persistent memory across conversations — agent remembers past interactions. Requires OpenAI key for embeddings.
|
|
93
|
-
- tasks: Task scheduling — agent can create, schedule, and track tasks. Supports recurring tasks.
|
|
94
|
-
- vision: Image & PDF understanding — agent can analyze uploaded images and PDFs.
|
|
95
|
-
- mcp: MCP tool use — agent can use tools from assigned MCP servers. Enable this if you plan to assign MCP servers.
|
|
96
|
-
- files: File management — agent can read, write, and manage files in its workspace.
|
|
97
|
-
- agents: Multi-agent communication — agent can call and delegate tasks to peer agents in the same project. Enables call_agent and list_available_agents tools.
|
|
98
|
-
|
|
99
|
-
TIPS:
|
|
100
|
-
- Always provide a descriptive system_prompt that tells the agent what it does and how to behave.
|
|
101
|
-
- Assign to a project_id to organize agents. Use list_projects to see available projects.
|
|
102
|
-
- After creating, use start_agent to run it. Then assign MCP servers or skills as needed.`,
|
|
103
|
-
inputSchema: {
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
name: { type: "string", description: "Agent name (e.g. 'Customer Support', 'Code Reviewer')" },
|
|
107
|
-
provider: { type: "string", description: "LLM provider ID: anthropic, openai, groq, gemini, xai, together, fireworks, moonshot, ollama" },
|
|
108
|
-
model: { type: "string", description: "Model ID — see tool description for full list per provider" },
|
|
109
|
-
system_prompt: { type: "string", description: "Instructions for the agent. Describe its role, personality, and capabilities. This is the most important field for agent behavior." },
|
|
110
|
-
project_id: { type: "string", description: "Project ID to assign the agent to (optional). Use list_projects to find IDs." },
|
|
111
|
-
features: {
|
|
112
|
-
type: "object",
|
|
113
|
-
description: "Feature flags to enable. All default to false. See tool description for details on each feature.",
|
|
114
|
-
properties: {
|
|
115
|
-
memory: { type: "boolean", description: "Persistent memory across conversations (requires OpenAI key for embeddings)" },
|
|
116
|
-
tasks: { type: "boolean", description: "Task scheduling and tracking" },
|
|
117
|
-
vision: { type: "boolean", description: "Image and PDF understanding" },
|
|
118
|
-
mcp: { type: "boolean", description: "MCP tool use — required if assigning MCP servers" },
|
|
119
|
-
files: { type: "boolean", description: "File read/write in agent workspace" },
|
|
120
|
-
agents: { type: "boolean", description: "Multi-agent communication — call and delegate to peer agents in the same project" },
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
required: ["name", "provider", "model"],
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: "update_agent",
|
|
129
|
-
description: `Update an existing agent's configuration. Only provide fields you want to change. Changes are applied live — if the agent is running, the new config is pushed automatically. No restart needed.
|
|
130
|
-
|
|
131
|
-
SKILLS & MCP SERVERS:
|
|
132
|
-
- Pass skill_ids or mcp_server_ids to SET the full list (replaces existing).
|
|
133
|
-
- Use add_skills / remove_skills to add/remove individual skills without replacing the whole list.
|
|
134
|
-
- Use add_mcp_servers / remove_mcp_servers to add/remove individual MCP servers.
|
|
135
|
-
- Adding MCP servers automatically enables the mcp feature.
|
|
136
|
-
- Use list_skills and list_mcp_servers to find IDs.`,
|
|
137
|
-
inputSchema: {
|
|
138
|
-
type: "object",
|
|
139
|
-
properties: {
|
|
140
|
-
agent_id: { type: "string", description: "The agent ID to update" },
|
|
141
|
-
name: { type: "string", description: "New display name" },
|
|
142
|
-
model: { type: "string", description: "New model ID (see create_agent for available models per provider)" },
|
|
143
|
-
provider: { type: "string", description: "New provider ID (the new provider must have an API key configured)" },
|
|
144
|
-
system_prompt: { type: "string", description: "New system prompt / instructions" },
|
|
145
|
-
project_id: { type: "string", description: "New project ID, or null to unassign from project" },
|
|
146
|
-
features: {
|
|
147
|
-
type: "object",
|
|
148
|
-
description: "Feature flags to update (only provided flags are changed, others remain as-is)",
|
|
149
|
-
properties: {
|
|
150
|
-
memory: { type: "boolean" },
|
|
151
|
-
tasks: { type: "boolean" },
|
|
152
|
-
vision: { type: "boolean" },
|
|
153
|
-
mcp: { type: "boolean" },
|
|
154
|
-
files: { type: "boolean" },
|
|
155
|
-
agents: { type: "boolean" },
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
skill_ids: { type: "array", items: { type: "string" }, description: "Set the full list of skill IDs (replaces existing)" },
|
|
159
|
-
add_skills: { type: "array", items: { type: "string" }, description: "Skill IDs to add (keeps existing)" },
|
|
160
|
-
remove_skills: { type: "array", items: { type: "string" }, description: "Skill IDs to remove" },
|
|
161
|
-
mcp_server_ids: { type: "array", items: { type: "string" }, description: "Set the full list of MCP server IDs (replaces existing)" },
|
|
162
|
-
add_mcp_servers: { type: "array", items: { type: "string" }, description: "MCP server IDs to add (keeps existing)" },
|
|
163
|
-
remove_mcp_servers: { type: "array", items: { type: "string" }, description: "MCP server IDs to remove" },
|
|
164
|
-
},
|
|
165
|
-
required: ["agent_id"],
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "delete_agent",
|
|
170
|
-
description: "Delete an agent. The agent must be stopped first.",
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: {
|
|
174
|
-
agent_id: { type: "string", description: "The agent ID to delete" },
|
|
175
|
-
},
|
|
176
|
-
required: ["agent_id"],
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
name: "start_agent",
|
|
181
|
-
description: "Start a stopped agent. The agent's provider must have an API key configured. Starting spawns a process, waits for health check, and pushes configuration (model, features, MCP servers, skills). Takes a few seconds.",
|
|
182
|
-
inputSchema: {
|
|
183
|
-
type: "object",
|
|
184
|
-
properties: {
|
|
185
|
-
agent_id: { type: "string", description: "The agent ID to start" },
|
|
186
|
-
},
|
|
187
|
-
required: ["agent_id"],
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
name: "stop_agent",
|
|
192
|
-
description: "Stop a running agent.",
|
|
193
|
-
inputSchema: {
|
|
194
|
-
type: "object",
|
|
195
|
-
properties: {
|
|
196
|
-
agent_id: { type: "string", description: "The agent ID to stop" },
|
|
197
|
-
},
|
|
198
|
-
required: ["agent_id"],
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: "list_projects",
|
|
203
|
-
description: "List all projects with their agent counts.",
|
|
204
|
-
inputSchema: {
|
|
205
|
-
type: "object",
|
|
206
|
-
properties: {},
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: "create_project",
|
|
211
|
-
description: "Create a new project for organizing agents.",
|
|
212
|
-
inputSchema: {
|
|
213
|
-
type: "object",
|
|
214
|
-
properties: {
|
|
215
|
-
name: { type: "string", description: "Project name" },
|
|
216
|
-
description: { type: "string", description: "Project description (optional)" },
|
|
217
|
-
color: { type: "string", description: "Hex color code (optional, default #6366f1)" },
|
|
218
|
-
},
|
|
219
|
-
required: ["name"],
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
name: "update_project",
|
|
224
|
-
description: "Update an existing project's name, description, or color.",
|
|
225
|
-
inputSchema: {
|
|
226
|
-
type: "object",
|
|
227
|
-
properties: {
|
|
228
|
-
project_id: { type: "string", description: "The project ID to update" },
|
|
229
|
-
name: { type: "string", description: "New project name" },
|
|
230
|
-
description: { type: "string", description: "New project description" },
|
|
231
|
-
color: { type: "string", description: "New hex color code (e.g. #6366f1)" },
|
|
232
|
-
},
|
|
233
|
-
required: ["project_id"],
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
name: "delete_project",
|
|
238
|
-
description: "Delete a project. Agents, MCP servers, and skills in the project will be unassigned (not deleted).",
|
|
239
|
-
inputSchema: {
|
|
240
|
-
type: "object",
|
|
241
|
-
properties: {
|
|
242
|
-
project_id: { type: "string", description: "The project ID to delete" },
|
|
243
|
-
},
|
|
244
|
-
required: ["project_id"],
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
name: "list_providers",
|
|
249
|
-
description: "List all available LLM providers and their configuration status (which have API keys).",
|
|
250
|
-
inputSchema: {
|
|
251
|
-
type: "object",
|
|
252
|
-
properties: {},
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: "list_mcp_servers",
|
|
257
|
-
description: "List all configured MCP servers (tool integrations). Optionally filter by project.",
|
|
258
|
-
inputSchema: {
|
|
259
|
-
type: "object",
|
|
260
|
-
properties: {
|
|
261
|
-
project_id: { type: "string", description: "Filter by project ID (optional)" },
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: "get_mcp_server",
|
|
267
|
-
description: "Get detailed information about an MCP server by ID.",
|
|
268
|
-
inputSchema: {
|
|
269
|
-
type: "object",
|
|
270
|
-
properties: {
|
|
271
|
-
server_id: { type: "string", description: "The MCP server ID" },
|
|
272
|
-
},
|
|
273
|
-
required: ["server_id"],
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
name: "create_mcp_server",
|
|
278
|
-
description: `Create a new MCP server configuration. MCP servers provide tools that agents can use (web search, file access, APIs, etc).
|
|
279
|
-
|
|
280
|
-
SERVER TYPES:
|
|
281
|
-
- 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.
|
|
282
|
-
- http: Remote MCP server accessible via URL. Provide url and optional auth headers. Ready to use immediately.
|
|
283
|
-
- npm: Node.js MCP server from npm. Provide package name (e.g. '@modelcontextprotocol/server-filesystem'). Needs to be started.
|
|
284
|
-
- pip: Python MCP server from PyPI. Provide package name. Needs to be started.
|
|
285
|
-
- custom: Custom command. Provide command and args. Needs to be started.
|
|
286
|
-
|
|
287
|
-
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.`,
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: "object",
|
|
290
|
-
properties: {
|
|
291
|
-
name: { type: "string", description: "Display name (e.g. 'Filesystem', 'Web Search', 'GitHub')" },
|
|
292
|
-
type: { type: "string", description: "Server type: http, npm, pip, or custom" },
|
|
293
|
-
url: { type: "string", description: "For http type: the remote MCP server URL (e.g. 'https://mcp.example.com/sse')" },
|
|
294
|
-
headers: { type: "object", description: "For http type: auth headers as key-value pairs" },
|
|
295
|
-
package: { type: "string", description: "For npm/pip type: package name" },
|
|
296
|
-
command: { type: "string", description: "For custom type: executable command" },
|
|
297
|
-
args: { type: "string", description: "Command arguments string (optional)" },
|
|
298
|
-
project_id: { type: "string", description: "Scope to a project (optional). null = available globally to all agents." },
|
|
299
|
-
},
|
|
300
|
-
required: ["name", "type"],
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
name: "delete_mcp_server",
|
|
305
|
-
description: "Delete an MCP server. It must be stopped first.",
|
|
306
|
-
inputSchema: {
|
|
307
|
-
type: "object",
|
|
308
|
-
properties: {
|
|
309
|
-
server_id: { type: "string", description: "The MCP server ID to delete" },
|
|
310
|
-
},
|
|
311
|
-
required: ["server_id"],
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
name: "add_tool_to_local_server",
|
|
316
|
-
description: `Add a tool to a local MCP server. Only works for servers with type "local".
|
|
317
|
-
|
|
318
|
-
HANDLER TYPES:
|
|
319
|
-
- mock: Returns a static/templated response. Use mock_response with template variables: {{args.field}}, {{uuid()}}, {{now}}, {{timestamp}}, {{random_int(min,max)}}.
|
|
320
|
-
- 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.
|
|
321
|
-
- 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.
|
|
322
|
-
|
|
323
|
-
EXAMPLES:
|
|
324
|
-
- Mock: handler_type="mock", mock_response={"greeting": "Hello {{args.name}}!", "id": "{{uuid()}}"}
|
|
325
|
-
- HTTP: handler_type="http", http_config={"method": "GET", "url": "https://api.example.com/users/{{args.user_id}}", "headers": {"Authorization": "Bearer {{credential.API_KEY}}"}}
|
|
326
|
-
- JavaScript: handler_type="javascript", code="return { sum: args.a + args.b, computed_at: now }"`,
|
|
327
|
-
inputSchema: {
|
|
328
|
-
type: "object",
|
|
329
|
-
properties: {
|
|
330
|
-
server_id: { type: "string", description: "The local MCP server ID" },
|
|
331
|
-
name: { type: "string", description: "Tool name (snake_case, e.g. 'get_weather', 'create_ticket')" },
|
|
332
|
-
description: { type: "string", description: "What this tool does (shown to the agent)" },
|
|
333
|
-
input_schema: {
|
|
334
|
-
type: "object",
|
|
335
|
-
description: "JSON Schema for tool parameters. Must have type='object' with properties.",
|
|
336
|
-
},
|
|
337
|
-
handler_type: { type: "string", description: "How the tool executes: mock, http, or javascript" },
|
|
338
|
-
mock_response: { type: "object", description: "For mock handler: the response template (supports {{args.field}}, {{uuid()}}, {{now}})" },
|
|
339
|
-
http_config: {
|
|
340
|
-
type: "object",
|
|
341
|
-
description: "For http handler: { method, url, headers?, body? }. Templates supported in all fields.",
|
|
342
|
-
},
|
|
343
|
-
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." },
|
|
344
|
-
},
|
|
345
|
-
required: ["server_id", "name", "description", "input_schema", "handler_type"],
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "update_tool_on_local_server",
|
|
350
|
-
description: `Update an existing tool on a local MCP server. Only provide fields you want to change. Only works for servers with type "local".`,
|
|
351
|
-
inputSchema: {
|
|
352
|
-
type: "object",
|
|
353
|
-
properties: {
|
|
354
|
-
tool_id: { type: "string", description: "The tool ID to update" },
|
|
355
|
-
name: { type: "string", description: "New tool name" },
|
|
356
|
-
description: { type: "string", description: "New description" },
|
|
357
|
-
input_schema: { type: "object", description: "New JSON Schema for tool parameters" },
|
|
358
|
-
handler_type: { type: "string", description: "New handler type: mock, http, or javascript" },
|
|
359
|
-
mock_response: { type: "object", description: "New mock response template" },
|
|
360
|
-
http_config: { type: "object", description: "New HTTP config: { method, url, headers?, body? }" },
|
|
361
|
-
code: { type: "string", description: "New JavaScript code" },
|
|
362
|
-
enabled: { type: "boolean", description: "Enable or disable the tool" },
|
|
363
|
-
},
|
|
364
|
-
required: ["tool_id"],
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
name: "get_dashboard_stats",
|
|
369
|
-
description: "Get platform overview stats: agent counts, task counts, provider counts.",
|
|
370
|
-
inputSchema: {
|
|
371
|
-
type: "object",
|
|
372
|
-
properties: {},
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
name: "send_message",
|
|
377
|
-
description: "Send a chat message to a running agent and get the response.",
|
|
378
|
-
inputSchema: {
|
|
379
|
-
type: "object",
|
|
380
|
-
properties: {
|
|
381
|
-
agent_id: { type: "string", description: "The agent ID to message" },
|
|
382
|
-
message: { type: "string", description: "The message to send" },
|
|
383
|
-
},
|
|
384
|
-
required: ["agent_id", "message"],
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
// Task management on agents
|
|
388
|
-
{
|
|
389
|
-
name: "list_tasks",
|
|
390
|
-
description: "List all tasks across ALL running agents in the project. Returns tasks from every agent, each tagged with agentId and agentName. Use this to get a project-wide overview of all tasks.",
|
|
391
|
-
inputSchema: {
|
|
392
|
-
type: "object",
|
|
393
|
-
properties: {
|
|
394
|
-
project_id: { type: "string", description: "Filter to a specific project (optional). If omitted, returns tasks from all running agents." },
|
|
395
|
-
status: { type: "string", description: "Filter by status: all, pending, running, completed, failed, cancelled (default: all)" },
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: "list_agent_tasks",
|
|
401
|
-
description: "List tasks on a specific running agent. Use list_tasks instead for a project-wide view.",
|
|
402
|
-
inputSchema: {
|
|
403
|
-
type: "object",
|
|
404
|
-
properties: {
|
|
405
|
-
agent_id: { type: "string", description: "The agent ID to list tasks from" },
|
|
406
|
-
status: { type: "string", description: "Filter by status: all, pending, running, completed, failed, cancelled (default: all)" },
|
|
407
|
-
},
|
|
408
|
-
required: ["agent_id"],
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
name: "create_agent_task",
|
|
413
|
-
description: "Create a new task on a running agent. The agent must have the tasks feature enabled.",
|
|
414
|
-
inputSchema: {
|
|
415
|
-
type: "object",
|
|
416
|
-
properties: {
|
|
417
|
-
agent_id: { type: "string", description: "The agent ID to create the task on" },
|
|
418
|
-
title: { type: "string", description: "Task title" },
|
|
419
|
-
description: { type: "string", description: "Task description / instructions for the agent" },
|
|
420
|
-
type: { type: "string", description: "Task type: once (one-time) or recurring (default: once)" },
|
|
421
|
-
priority: { type: "number", description: "Priority 1-10, higher = more important (default: 5)" },
|
|
422
|
-
execute_at: { type: "string", description: "ISO timestamp for scheduled execution (optional, for one-time tasks)" },
|
|
423
|
-
recurrence: { type: "string", description: "Cron expression for recurring tasks (e.g. '*/30 * * * *' = every 30 min)" },
|
|
424
|
-
},
|
|
425
|
-
required: ["agent_id", "title"],
|
|
426
|
-
},
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
name: "delete_agent_task",
|
|
430
|
-
description: "Delete a task from a running agent.",
|
|
431
|
-
inputSchema: {
|
|
432
|
-
type: "object",
|
|
433
|
-
properties: {
|
|
434
|
-
agent_id: { type: "string", description: "The agent ID" },
|
|
435
|
-
task_id: { type: "string", description: "The task ID to delete" },
|
|
436
|
-
},
|
|
437
|
-
required: ["agent_id", "task_id"],
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: "execute_agent_task",
|
|
442
|
-
description: "Immediately execute a task on a running agent, regardless of its schedule.",
|
|
443
|
-
inputSchema: {
|
|
444
|
-
type: "object",
|
|
445
|
-
properties: {
|
|
446
|
-
agent_id: { type: "string", description: "The agent ID" },
|
|
447
|
-
task_id: { type: "string", description: "The task ID to execute" },
|
|
448
|
-
},
|
|
449
|
-
required: ["agent_id", "task_id"],
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
// Skills management
|
|
453
|
-
{
|
|
454
|
-
name: "list_skills",
|
|
455
|
-
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).",
|
|
456
|
-
inputSchema: {
|
|
457
|
-
type: "object",
|
|
458
|
-
properties: {
|
|
459
|
-
enabled_only: { type: "boolean", description: "Only return enabled skills (optional, default false)" },
|
|
460
|
-
project_id: { type: "string", description: "Filter by project ID — returns skills scoped to this project plus global skills" },
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
name: "get_skill",
|
|
466
|
-
description: "Get detailed information about a skill by ID, including its full instructions content.",
|
|
467
|
-
inputSchema: {
|
|
468
|
-
type: "object",
|
|
469
|
-
properties: {
|
|
470
|
-
skill_id: { type: "string", description: "The skill ID" },
|
|
471
|
-
},
|
|
472
|
-
required: ["skill_id"],
|
|
473
|
-
},
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
name: "toggle_skill",
|
|
477
|
-
description: "Enable or disable a skill.",
|
|
478
|
-
inputSchema: {
|
|
479
|
-
type: "object",
|
|
480
|
-
properties: {
|
|
481
|
-
skill_id: { type: "string", description: "The skill ID" },
|
|
482
|
-
enabled: { type: "boolean", description: "Whether to enable (true) or disable (false) the skill" },
|
|
483
|
-
},
|
|
484
|
-
required: ["skill_id", "enabled"],
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
name: "update_skill",
|
|
489
|
-
description: "Update an existing skill. Only provide fields you want to change.",
|
|
490
|
-
inputSchema: {
|
|
491
|
-
type: "object",
|
|
492
|
-
properties: {
|
|
493
|
-
skill_id: { type: "string", description: "The skill ID to update" },
|
|
494
|
-
name: { type: "string", description: "New name" },
|
|
495
|
-
description: { type: "string", description: "New description" },
|
|
496
|
-
content: { type: "string", description: "New instructions content (markdown)" },
|
|
497
|
-
allowed_tools: {
|
|
498
|
-
type: "array",
|
|
499
|
-
items: { type: "string" },
|
|
500
|
-
description: "New list of allowed MCP tool names",
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
required: ["skill_id"],
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: "create_skill",
|
|
508
|
-
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.",
|
|
509
|
-
inputSchema: {
|
|
510
|
-
type: "object",
|
|
511
|
-
properties: {
|
|
512
|
-
name: { type: "string", description: "The skill name" },
|
|
513
|
-
description: { type: "string", description: "Short description of what the skill does" },
|
|
514
|
-
content: { type: "string", description: "Full skill instructions in markdown format" },
|
|
515
|
-
allowed_tools: {
|
|
516
|
-
type: "array",
|
|
517
|
-
items: { type: "string" },
|
|
518
|
-
description: "Optional list of MCP tool names this skill is allowed to use",
|
|
519
|
-
},
|
|
520
|
-
project_id: { type: "string", description: "Project ID to scope the skill to. Use the current project ID from context when the user is working within a project. Omit for a global skill." },
|
|
521
|
-
},
|
|
522
|
-
required: ["name", "description", "content"],
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
name: "delete_skill",
|
|
527
|
-
description: "Delete a skill. It will be unassigned from all agents.",
|
|
528
|
-
inputSchema: {
|
|
529
|
-
type: "object",
|
|
530
|
-
properties: {
|
|
531
|
-
skill_id: { type: "string", description: "The skill ID to delete" },
|
|
532
|
-
},
|
|
533
|
-
required: ["skill_id"],
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
// Test tools
|
|
537
|
-
{
|
|
538
|
-
name: "list_tests",
|
|
539
|
-
description: "List all test cases. Tests validate agent workflows by sending a message and using an LLM judge to evaluate the result.",
|
|
540
|
-
inputSchema: {
|
|
541
|
-
type: "object",
|
|
542
|
-
properties: {
|
|
543
|
-
project_id: { type: "string", description: "Optional project ID to filter tests" },
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
name: "create_test",
|
|
549
|
-
description: "Create a behavior-driven test case. Describe what the agent should do in natural language — the system will auto-select the best agent and generate the test message. Optionally pin to a specific agent.",
|
|
550
|
-
inputSchema: {
|
|
551
|
-
type: "object",
|
|
552
|
-
properties: {
|
|
553
|
-
name: { type: "string", description: "Test name" },
|
|
554
|
-
behavior: { type: "string", description: "Natural language description of what the agent should do. E.g. 'Search the web for the latest news about AI and summarize it'" },
|
|
555
|
-
agent_id: { type: "string", description: "Optional: pin to a specific agent ID. If omitted, the AI planner auto-selects the best agent." },
|
|
556
|
-
project_id: { type: "string", description: "Optional: project ID to scope agent selection" },
|
|
557
|
-
timeout_ms: { type: "number", description: "Timeout in ms (default 300000 = 5 min)" },
|
|
558
|
-
},
|
|
559
|
-
required: ["name", "behavior"],
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
{
|
|
563
|
-
name: "run_test",
|
|
564
|
-
description: "Run a test case. The agent must be running. Returns pass/fail with LLM judge reasoning.",
|
|
565
|
-
inputSchema: {
|
|
566
|
-
type: "object",
|
|
567
|
-
properties: {
|
|
568
|
-
test_id: { type: "string", description: "Test case ID to run. Use list_tests to find IDs." },
|
|
569
|
-
},
|
|
570
|
-
required: ["test_id"],
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
name: "run_all_tests",
|
|
575
|
-
description: "Run all test cases (or specific ones). Returns summary of pass/fail results.",
|
|
576
|
-
inputSchema: {
|
|
577
|
-
type: "object",
|
|
578
|
-
properties: {
|
|
579
|
-
test_case_ids: { type: "array", items: { type: "string" }, description: "Optional array of test case IDs. If empty, runs all tests." },
|
|
580
|
-
},
|
|
581
|
-
},
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
name: "get_test_results",
|
|
585
|
-
description: "Get run history for a test case. Shows pass/fail status, judge reasoning, and duration.",
|
|
586
|
-
inputSchema: {
|
|
587
|
-
type: "object",
|
|
588
|
-
properties: {
|
|
589
|
-
test_id: { type: "string", description: "Test case ID" },
|
|
590
|
-
limit: { type: "number", description: "Max results to return (default 10)" },
|
|
591
|
-
},
|
|
592
|
-
required: ["test_id"],
|
|
593
|
-
},
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
name: "delete_test",
|
|
597
|
-
description: "Delete a test case and all its run history.",
|
|
598
|
-
inputSchema: {
|
|
599
|
-
type: "object",
|
|
600
|
-
properties: {
|
|
601
|
-
test_id: { type: "string", description: "Test case ID to delete" },
|
|
602
|
-
},
|
|
603
|
-
required: ["test_id"],
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
// Subscription & Trigger management
|
|
607
|
-
{
|
|
608
|
-
name: "list_trigger_providers",
|
|
609
|
-
description: "List available trigger/webhook providers (e.g. composio, agentdojo) and whether they have API keys configured.",
|
|
610
|
-
inputSchema: {
|
|
611
|
-
type: "object",
|
|
612
|
-
properties: {
|
|
613
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
614
|
-
},
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
name: "list_trigger_types",
|
|
619
|
-
description: `Browse available trigger types from a provider. Trigger types are events you can subscribe to (e.g. github:push, stripe:payment_intent, slack:message).
|
|
620
|
-
|
|
621
|
-
Each trigger type has:
|
|
622
|
-
- slug: unique identifier (e.g. "github:push")
|
|
623
|
-
- name: display name
|
|
624
|
-
- description: what the trigger does
|
|
625
|
-
- config_schema: JSON schema of required config fields (e.g. owner, repo for GitHub triggers)
|
|
626
|
-
|
|
627
|
-
Use this to find trigger slugs before creating a subscription.`,
|
|
628
|
-
inputSchema: {
|
|
629
|
-
type: "object",
|
|
630
|
-
properties: {
|
|
631
|
-
provider: { type: "string", description: "Trigger provider ID: composio or agentdojo" },
|
|
632
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
633
|
-
},
|
|
634
|
-
required: ["provider"],
|
|
635
|
-
},
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: "list_subscriptions",
|
|
639
|
-
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.",
|
|
640
|
-
inputSchema: {
|
|
641
|
-
type: "object",
|
|
642
|
-
properties: {
|
|
643
|
-
agent_id: { type: "string", description: "Filter by agent ID (optional)" },
|
|
644
|
-
project_id: { type: "string", description: "Filter by project ID (optional)" },
|
|
645
|
-
},
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
name: "create_subscription",
|
|
650
|
-
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.
|
|
651
|
-
|
|
652
|
-
Just provide all 4 required fields in a single call:
|
|
653
|
-
- provider: "agentdojo" or "composio"
|
|
654
|
-
- trigger_slug: from list_trigger_types
|
|
655
|
-
- connected_account_id: from list_integration_connections
|
|
656
|
-
- agent_id: from list_agents
|
|
657
|
-
|
|
658
|
-
Some triggers require extra config fields (e.g. GitHub needs "owner" and "repo") — pass them in the config object.`,
|
|
659
|
-
inputSchema: {
|
|
660
|
-
type: "object",
|
|
661
|
-
properties: {
|
|
662
|
-
provider: { type: "string", description: "Trigger provider ID: composio or agentdojo" },
|
|
663
|
-
trigger_slug: { type: "string", description: "Trigger type slug (e.g. 'github:push', 'GITHUB_PUSH_EVENT'). Use list_trigger_types to find slugs." },
|
|
664
|
-
connected_account_id: { type: "string", description: "Connected account ID that owns the integration. Use list_integration_connections to find IDs." },
|
|
665
|
-
agent_id: { type: "string", description: "Agent ID to route trigger events to. Use list_agents to find IDs." },
|
|
666
|
-
project_id: { type: "string", description: "Project ID for scoping (optional)" },
|
|
667
|
-
config: {
|
|
668
|
-
type: "object",
|
|
669
|
-
description: "Extra config fields required by the trigger type (e.g. { owner: 'myorg', repo: 'myrepo' } for GitHub). Check config_schema from list_trigger_types.",
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
required: ["provider", "trigger_slug", "connected_account_id", "agent_id"],
|
|
673
|
-
},
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
name: "enable_subscription",
|
|
677
|
-
description: "Enable a disabled subscription so it starts routing events to the agent again. Optionally also enables the remote trigger on the provider.",
|
|
678
|
-
inputSchema: {
|
|
679
|
-
type: "object",
|
|
680
|
-
properties: {
|
|
681
|
-
subscription_id: { type: "string", description: "Local subscription ID" },
|
|
682
|
-
provider: { type: "string", description: "Provider ID to also enable the remote trigger (optional)" },
|
|
683
|
-
project_id: { type: "string", description: "Project ID for API key resolution (optional)" },
|
|
684
|
-
},
|
|
685
|
-
required: ["subscription_id"],
|
|
686
|
-
},
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
name: "disable_subscription",
|
|
690
|
-
description: "Disable a subscription so it stops routing events to the agent. Optionally also disables the remote trigger on the provider.",
|
|
691
|
-
inputSchema: {
|
|
692
|
-
type: "object",
|
|
693
|
-
properties: {
|
|
694
|
-
subscription_id: { type: "string", description: "Local subscription ID" },
|
|
695
|
-
provider: { type: "string", description: "Provider ID to also disable the remote trigger (optional)" },
|
|
696
|
-
project_id: { type: "string", description: "Project ID for API key resolution (optional)" },
|
|
697
|
-
},
|
|
698
|
-
required: ["subscription_id"],
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
name: "delete_subscription",
|
|
703
|
-
description: "Delete a local subscription. Optionally also deletes the remote trigger on the provider.",
|
|
704
|
-
inputSchema: {
|
|
705
|
-
type: "object",
|
|
706
|
-
properties: {
|
|
707
|
-
subscription_id: { type: "string", description: "Local subscription ID" },
|
|
708
|
-
delete_remote: { type: "boolean", description: "Also delete the remote trigger on the provider (default false)" },
|
|
709
|
-
provider: { type: "string", description: "Provider ID (required if delete_remote is true)" },
|
|
710
|
-
project_id: { type: "string", description: "Project ID for API key resolution (optional)" },
|
|
711
|
-
},
|
|
712
|
-
required: ["subscription_id"],
|
|
713
|
-
},
|
|
714
|
-
},
|
|
715
|
-
// Integration management tools
|
|
716
|
-
{
|
|
717
|
-
name: "list_integration_providers",
|
|
718
|
-
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.",
|
|
719
|
-
inputSchema: {
|
|
720
|
-
type: "object",
|
|
721
|
-
properties: {
|
|
722
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
723
|
-
},
|
|
724
|
-
},
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
name: "list_integration_apps",
|
|
728
|
-
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.
|
|
729
|
-
|
|
730
|
-
Returns apps with their auth schemes (OAUTH2, API_KEY), connection status, and categories.
|
|
731
|
-
Use this to browse what's available before connecting an app.`,
|
|
732
|
-
inputSchema: {
|
|
733
|
-
type: "object",
|
|
734
|
-
properties: {
|
|
735
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
736
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
737
|
-
search: { type: "string", description: "Optional search filter for app name/slug" },
|
|
738
|
-
},
|
|
739
|
-
required: ["provider"],
|
|
740
|
-
},
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
name: "connect_integration_app",
|
|
744
|
-
description: `Connect/authenticate an app by storing credentials on the integration provider. This enables the app's tools to be used in MCP servers.
|
|
745
|
-
|
|
746
|
-
NOTE: Only API_KEY auth is supported from the assistant. For OAuth apps, direct the user to the Browse Toolkits UI.
|
|
747
|
-
|
|
748
|
-
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".
|
|
749
|
-
|
|
750
|
-
WORKFLOW:
|
|
751
|
-
1. list_integration_apps to find the app and its credentialFields
|
|
752
|
-
2. connect_integration_app with the app slug and credentials
|
|
753
|
-
3. create_integration_config to create an MCP server from the connected app
|
|
754
|
-
4. add_integration_config_locally to add it as a local MCP server`,
|
|
755
|
-
inputSchema: {
|
|
756
|
-
type: "object",
|
|
757
|
-
properties: {
|
|
758
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
759
|
-
app_slug: { type: "string", description: "App slug (from list_integration_apps)" },
|
|
760
|
-
api_key: { type: "string", description: "Single API key (for apps with no credentialFields)" },
|
|
761
|
-
credentials: {
|
|
762
|
-
type: "object",
|
|
763
|
-
description: "Credential fields as key-value pairs (e.g. { appToken: '...', userKey: '...' }). Use this for apps with multiple credentialFields. Takes priority over api_key.",
|
|
764
|
-
},
|
|
765
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
766
|
-
},
|
|
767
|
-
required: ["provider", "app_slug"],
|
|
768
|
-
},
|
|
769
|
-
},
|
|
770
|
-
{
|
|
771
|
-
name: "list_integration_connections",
|
|
772
|
-
description: "List connected accounts (credentials) for an integration provider. Shows which apps have been authenticated and their status.",
|
|
773
|
-
inputSchema: {
|
|
774
|
-
type: "object",
|
|
775
|
-
properties: {
|
|
776
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
777
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
778
|
-
},
|
|
779
|
-
required: ["provider"],
|
|
780
|
-
},
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
name: "disconnect_integration_app",
|
|
784
|
-
description: "Disconnect/revoke a connected account (credential) from an integration provider.",
|
|
785
|
-
inputSchema: {
|
|
786
|
-
type: "object",
|
|
787
|
-
properties: {
|
|
788
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
789
|
-
connection_id: { type: "string", description: "Connection/credential ID (from list_integration_connections)" },
|
|
790
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
791
|
-
},
|
|
792
|
-
required: ["provider", "connection_id"],
|
|
793
|
-
},
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
name: "list_integration_configs",
|
|
797
|
-
description: "List MCP server configs on an integration provider. These are remote MCP servers hosted by the provider that can be added locally.",
|
|
798
|
-
inputSchema: {
|
|
799
|
-
type: "object",
|
|
800
|
-
properties: {
|
|
801
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
802
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
803
|
-
},
|
|
804
|
-
required: ["provider"],
|
|
805
|
-
},
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
name: "create_integration_config",
|
|
809
|
-
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.
|
|
810
|
-
|
|
811
|
-
After creation, use add_integration_config_locally to add it as a local MCP server.
|
|
812
|
-
|
|
813
|
-
WORKFLOW:
|
|
814
|
-
1. list_integration_apps → find the app slug
|
|
815
|
-
2. Ensure app is connected (list_integration_connections or connect_integration_app)
|
|
816
|
-
3. create_integration_config → creates remote MCP server
|
|
817
|
-
4. add_integration_config_locally → adds it locally so agents can use it`,
|
|
818
|
-
inputSchema: {
|
|
819
|
-
type: "object",
|
|
820
|
-
properties: {
|
|
821
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
822
|
-
name: { type: "string", description: "Name for the MCP config (e.g. 'GitHub MCP', 'Slack Tools')" },
|
|
823
|
-
toolkit_slug: { type: "string", description: "Toolkit/app slug to create the config from" },
|
|
824
|
-
project_id: { type: "string", description: "Project ID for project-scoped API keys (optional)" },
|
|
825
|
-
},
|
|
826
|
-
required: ["provider", "name", "toolkit_slug"],
|
|
827
|
-
},
|
|
828
|
-
},
|
|
829
|
-
{
|
|
830
|
-
name: "add_integration_config_locally",
|
|
831
|
-
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.
|
|
832
|
-
|
|
833
|
-
After adding, use assign_mcp_server_to_agent to give an agent access to these tools.`,
|
|
834
|
-
inputSchema: {
|
|
835
|
-
type: "object",
|
|
836
|
-
properties: {
|
|
837
|
-
provider: { type: "string", description: "Integration provider ID: agentdojo or composio" },
|
|
838
|
-
config_id: { type: "string", description: "Config/server ID on the provider (from list_integration_configs or create_integration_config)" },
|
|
839
|
-
project_id: { type: "string", description: "Project ID to scope the local server to (optional)" },
|
|
840
|
-
},
|
|
841
|
-
required: ["provider", "config_id"],
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
|
-
// Provider key management
|
|
845
|
-
{
|
|
846
|
-
name: "set_provider_key",
|
|
847
|
-
description: "Set or update an API key for a provider (LLM or integration). Use list_providers or list_integration_providers to see which providers need keys. Ask the user for the key — never guess or fabricate one.",
|
|
848
|
-
inputSchema: {
|
|
849
|
-
type: "object",
|
|
850
|
-
properties: {
|
|
851
|
-
provider_id: { type: "string", description: "Provider ID (e.g. 'anthropic', 'openai', 'agentdojo', 'composio')" },
|
|
852
|
-
key: { type: "string", description: "The API key value" },
|
|
853
|
-
project_id: { type: "string", description: "Project ID to scope the key to (optional — omit for global key)" },
|
|
854
|
-
},
|
|
855
|
-
required: ["provider_id", "key"],
|
|
856
|
-
},
|
|
857
|
-
},
|
|
858
|
-
// MCP server lifecycle
|
|
859
|
-
{
|
|
860
|
-
name: "start_mcp_server",
|
|
861
|
-
description: "Start an MCP server. npm/pip servers will be installed and launched; local servers are activated immediately; HTTP servers are always available.",
|
|
862
|
-
inputSchema: {
|
|
863
|
-
type: "object",
|
|
864
|
-
properties: {
|
|
865
|
-
server_id: { type: "string", description: "The MCP server ID to start" },
|
|
866
|
-
},
|
|
867
|
-
required: ["server_id"],
|
|
868
|
-
},
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
name: "stop_mcp_server",
|
|
872
|
-
description: "Stop a running MCP server.",
|
|
873
|
-
inputSchema: {
|
|
874
|
-
type: "object",
|
|
875
|
-
properties: {
|
|
876
|
-
server_id: { type: "string", description: "The MCP server ID to stop" },
|
|
877
|
-
},
|
|
878
|
-
required: ["server_id"],
|
|
879
|
-
},
|
|
880
|
-
},
|
|
881
|
-
// Analytics
|
|
882
|
-
{
|
|
883
|
-
name: "get_analytics",
|
|
884
|
-
description: "Get analytics summary: total events, LLM calls, tool calls, errors, token usage (input/output/cache/reasoning), and cost. Optionally filter by agent, project, or time range.",
|
|
885
|
-
inputSchema: {
|
|
886
|
-
type: "object",
|
|
887
|
-
properties: {
|
|
888
|
-
agent_id: { type: "string", description: "Filter by agent ID (optional)" },
|
|
889
|
-
project_id: { type: "string", description: "Filter by project ID (optional)" },
|
|
890
|
-
since: { type: "string", description: "ISO timestamp — only include events after this time (optional)" },
|
|
891
|
-
},
|
|
892
|
-
},
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
name: "get_usage_by_agent",
|
|
896
|
-
description: "Get token usage and cost breakdown per agent. Returns LLM calls, tool calls, input/output/cache/reasoning tokens, errors, and cost for each agent.",
|
|
897
|
-
inputSchema: {
|
|
898
|
-
type: "object",
|
|
899
|
-
properties: {
|
|
900
|
-
project_id: { type: "string", description: "Filter by project ID (optional)" },
|
|
901
|
-
since: { type: "string", description: "ISO timestamp — only include events after this time (optional)" },
|
|
902
|
-
},
|
|
903
|
-
},
|
|
904
|
-
},
|
|
905
|
-
];
|
|
906
|
-
|
|
907
|
-
// Project-only tools — hidden entirely when PROJECTS_ENABLED is not set
|
|
908
|
-
const PROJECT_ONLY_TOOLS = new Set(["list_projects", "create_project", "update_project", "delete_project"]);
|
|
909
|
-
|
|
910
|
-
// Build tools list — when PROJECTS_ENABLED, add project_id to required for all tools that accept it
|
|
911
|
-
function getPlatformTools() {
|
|
912
|
-
const projectsEnabled = process.env.PROJECTS_ENABLED === "true";
|
|
913
|
-
if (!projectsEnabled) {
|
|
914
|
-
return PLATFORM_TOOLS.filter(tool => !PROJECT_ONLY_TOOLS.has(tool.name));
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
return PLATFORM_TOOLS.map(tool => {
|
|
918
|
-
const props = tool.inputSchema.properties as Record<string, unknown> | undefined;
|
|
919
|
-
if (!props || !("project_id" in props)) return tool;
|
|
920
|
-
|
|
921
|
-
const existing = (tool.inputSchema as any).required || [];
|
|
922
|
-
if (existing.includes("project_id")) return tool;
|
|
923
|
-
|
|
924
|
-
return {
|
|
925
|
-
...tool,
|
|
926
|
-
inputSchema: {
|
|
927
|
-
...tool.inputSchema,
|
|
928
|
-
required: [...existing, "project_id"],
|
|
929
|
-
},
|
|
930
|
-
};
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Tool execution handlers
|
|
935
|
-
async function executeTool(name: string, args: Record<string, any>): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
936
|
-
try {
|
|
937
|
-
switch (name) {
|
|
938
|
-
case "list_agents": {
|
|
939
|
-
const agents = args.project_id
|
|
940
|
-
? AgentDB.findByProject(args.project_id)
|
|
941
|
-
: AgentDB.findAll();
|
|
942
|
-
// Exclude meta agent from list
|
|
943
|
-
const filtered = agents.filter(a => a.id !== META_AGENT_ID);
|
|
944
|
-
const mcpIds = [...new Set(filtered.flatMap(a => a.mcp_servers || []))];
|
|
945
|
-
const skillIds = [...new Set(filtered.flatMap(a => a.skills || []))];
|
|
946
|
-
const mcpMap = McpServerDB.findByIdsLight(mcpIds);
|
|
947
|
-
const skillMap = SkillDB.findByIds(skillIds);
|
|
948
|
-
const result = filtered.map(a => {
|
|
949
|
-
const mcpNames = (a.mcp_servers || [])
|
|
950
|
-
.map(id => mcpMap.get(id)?.name)
|
|
951
|
-
.filter(Boolean);
|
|
952
|
-
const skillNames = (a.skills || [])
|
|
953
|
-
.map(id => skillMap.get(id)?.name)
|
|
954
|
-
.filter(Boolean);
|
|
955
|
-
const enabledFeatures = Object.entries(a.features || {})
|
|
956
|
-
.filter(([, v]) => v === true || (typeof v === "object" && v !== null && (v as any).enabled))
|
|
957
|
-
.map(([k]) => k);
|
|
958
|
-
return {
|
|
959
|
-
id: a.id,
|
|
960
|
-
name: a.name,
|
|
961
|
-
provider: a.provider,
|
|
962
|
-
model: a.model,
|
|
963
|
-
status: a.status,
|
|
964
|
-
projectId: a.project_id,
|
|
965
|
-
features: enabledFeatures,
|
|
966
|
-
mcpServers: mcpNames,
|
|
967
|
-
skills: skillNames,
|
|
968
|
-
systemPrompt: a.system_prompt?.slice(0, 200) + (a.system_prompt && a.system_prompt.length > 200 ? "..." : ""),
|
|
969
|
-
};
|
|
970
|
-
});
|
|
971
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
case "get_agent": {
|
|
975
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
976
|
-
if (!agent) {
|
|
977
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
978
|
-
}
|
|
979
|
-
return { content: [{ type: "text", text: JSON.stringify(toApiAgent(agent), null, 2) }] };
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
case "create_agent": {
|
|
983
|
-
// Validate provider exists
|
|
984
|
-
const providerId = args.provider as keyof typeof PROVIDERS;
|
|
985
|
-
const provider = PROVIDERS[providerId];
|
|
986
|
-
if (!provider || provider.type !== "llm") {
|
|
987
|
-
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 };
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
const id = generateId();
|
|
991
|
-
const agent = AgentDB.create({
|
|
992
|
-
id,
|
|
993
|
-
name: args.name,
|
|
994
|
-
model: args.model,
|
|
995
|
-
provider: args.provider,
|
|
996
|
-
system_prompt: args.system_prompt || `You are ${args.name}, a helpful AI assistant.`,
|
|
997
|
-
features: {
|
|
998
|
-
memory: args.features?.memory ?? false,
|
|
999
|
-
tasks: args.features?.tasks ?? false,
|
|
1000
|
-
vision: args.features?.vision ?? false,
|
|
1001
|
-
operator: false,
|
|
1002
|
-
mcp: args.features?.mcp ?? false,
|
|
1003
|
-
realtime: false,
|
|
1004
|
-
files: args.features?.files ?? false,
|
|
1005
|
-
agents: args.features?.agents ? { enabled: true, group: args.project_id || undefined } : false,
|
|
1006
|
-
},
|
|
1007
|
-
mcp_servers: [],
|
|
1008
|
-
skills: [],
|
|
1009
|
-
project_id: args.project_id || null,
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
const enabledFeatures = Object.entries(agent.features)
|
|
1013
|
-
.filter(([_, v]) => v === true || (typeof v === "object" && v?.enabled))
|
|
1014
|
-
.map(([k]) => k === "agents" ? "multi-agent (call_agent, delegate_task, list_available_agents)" : k);
|
|
1015
|
-
return { content: [{ type: "text", text: `Agent created successfully:\n${JSON.stringify({ id: agent.id, name: agent.name, provider: agent.provider, model: agent.model, features: enabledFeatures.length > 0 ? enabledFeatures.join(", ") : "none" }, null, 2)}` }] };
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
case "update_agent": {
|
|
1019
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1020
|
-
if (!agent) {
|
|
1021
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
const updates: any = {};
|
|
1025
|
-
if (args.name !== undefined) updates.name = args.name;
|
|
1026
|
-
if (args.model !== undefined) updates.model = args.model;
|
|
1027
|
-
if (args.provider !== undefined) updates.provider = args.provider;
|
|
1028
|
-
if (args.system_prompt !== undefined) updates.system_prompt = args.system_prompt;
|
|
1029
|
-
if (args.project_id !== undefined) updates.project_id = args.project_id;
|
|
1030
|
-
if (args.features !== undefined) {
|
|
1031
|
-
const mergedFeatures = { ...agent.features, ...args.features };
|
|
1032
|
-
// Convert agents boolean to MultiAgentConfig
|
|
1033
|
-
if (typeof mergedFeatures.agents === "boolean") {
|
|
1034
|
-
mergedFeatures.agents = mergedFeatures.agents
|
|
1035
|
-
? { enabled: true, group: args.project_id || agent.project_id || undefined }
|
|
1036
|
-
: false;
|
|
1037
|
-
}
|
|
1038
|
-
updates.features = mergedFeatures;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
// Skills: set, add, or remove
|
|
1042
|
-
let skills = agent.skills || [];
|
|
1043
|
-
if (args.skill_ids !== undefined) {
|
|
1044
|
-
skills = args.skill_ids;
|
|
1045
|
-
}
|
|
1046
|
-
if (args.add_skills) {
|
|
1047
|
-
for (const sid of args.add_skills) {
|
|
1048
|
-
if (!skills.includes(sid)) skills.push(sid);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
if (args.remove_skills) {
|
|
1052
|
-
skills = skills.filter((id: string) => !args.remove_skills.includes(id));
|
|
1053
|
-
}
|
|
1054
|
-
if (args.skill_ids !== undefined || args.add_skills || args.remove_skills) {
|
|
1055
|
-
updates.skills = skills;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// MCP servers: set, add, or remove
|
|
1059
|
-
let mcpServers = agent.mcp_servers || [];
|
|
1060
|
-
if (args.mcp_server_ids !== undefined) {
|
|
1061
|
-
mcpServers = args.mcp_server_ids;
|
|
1062
|
-
}
|
|
1063
|
-
if (args.add_mcp_servers) {
|
|
1064
|
-
for (const sid of args.add_mcp_servers) {
|
|
1065
|
-
if (!mcpServers.includes(sid)) mcpServers.push(sid);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (args.remove_mcp_servers) {
|
|
1069
|
-
mcpServers = mcpServers.filter((id: string) => !args.remove_mcp_servers.includes(id));
|
|
1070
|
-
}
|
|
1071
|
-
if (args.mcp_server_ids !== undefined || args.add_mcp_servers || args.remove_mcp_servers) {
|
|
1072
|
-
updates.mcp_servers = mcpServers;
|
|
1073
|
-
// Auto-enable MCP feature if servers are being added
|
|
1074
|
-
if (mcpServers.length > 0 && !agent.features.mcp) {
|
|
1075
|
-
updates.features = { ...(updates.features || agent.features), mcp: true };
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
const updated = AgentDB.update(args.agent_id, updates);
|
|
1080
|
-
|
|
1081
|
-
// Push config to running agent (live update, no restart needed)
|
|
1082
|
-
let configPushed = false;
|
|
1083
|
-
if (updated && updated.status === "running" && updated.port) {
|
|
1084
|
-
const providerKey = ProviderKeys.getDecrypted(updated.provider);
|
|
1085
|
-
if (providerKey) {
|
|
1086
|
-
const config = buildAgentConfig(updated, providerKey);
|
|
1087
|
-
const configResult = await pushConfigToAgent(updated.id, updated.port, config);
|
|
1088
|
-
configPushed = configResult.success;
|
|
1089
|
-
// Push skills if any
|
|
1090
|
-
if (config.skills?.definitions?.length > 0) {
|
|
1091
|
-
await pushSkillsToAgent(updated.id, updated.port, config.skills.definitions);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Build a clear summary of what changed
|
|
1097
|
-
const summary: Record<string, any> = { id: updated?.id, name: updated?.name };
|
|
1098
|
-
if (updates.features) {
|
|
1099
|
-
const enabledFeatures = Object.entries(updates.features)
|
|
1100
|
-
.filter(([_, v]) => v === true || (typeof v === "object" && v?.enabled))
|
|
1101
|
-
.map(([k]) => k === "agents" ? "multi-agent (call_agent, delegate_task, list_available_agents)" : k);
|
|
1102
|
-
summary.features = enabledFeatures.join(", ") || "none";
|
|
1103
|
-
}
|
|
1104
|
-
if (updates.skills !== undefined) summary.skills = updates.skills.length + " skills";
|
|
1105
|
-
if (updates.mcp_servers !== undefined) summary.mcp_servers = updates.mcp_servers.length + " servers";
|
|
1106
|
-
if (updates.name) summary.name = updates.name;
|
|
1107
|
-
if (updates.model) summary.model = updates.model;
|
|
1108
|
-
if (updates.system_prompt) summary.system_prompt = "updated";
|
|
1109
|
-
if (configPushed) summary.config_status = "applied live (no restart needed)";
|
|
1110
|
-
return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify(summary, null, 2)}` }] };
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
case "delete_agent": {
|
|
1114
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1115
|
-
if (!agent) {
|
|
1116
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1117
|
-
}
|
|
1118
|
-
if (agent.status === "running") {
|
|
1119
|
-
return { content: [{ type: "text", text: "Cannot delete a running agent. Stop it first." }], isError: true };
|
|
1120
|
-
}
|
|
1121
|
-
if (agent.id === META_AGENT_ID) {
|
|
1122
|
-
return { content: [{ type: "text", text: "Cannot delete the Apteva Assistant." }], isError: true };
|
|
1123
|
-
}
|
|
1124
|
-
AgentDB.delete(args.agent_id);
|
|
1125
|
-
return { content: [{ type: "text", text: `Agent deleted: ${agent.name} (${agent.id})` }] };
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
case "start_agent": {
|
|
1129
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1130
|
-
if (!agent) {
|
|
1131
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1132
|
-
}
|
|
1133
|
-
if (agent.status === "running") {
|
|
1134
|
-
return { content: [{ type: "text", text: `Agent ${agent.name} is already running on port ${agent.port}` }] };
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const result = await startAgentProcess(agent);
|
|
1138
|
-
if (!result.success) {
|
|
1139
|
-
return { content: [{ type: "text", text: `Failed to start agent: ${result.error}` }], isError: true };
|
|
1140
|
-
}
|
|
1141
|
-
return { content: [{ type: "text", text: `Agent ${agent.name} started on port ${result.port}` }] };
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
case "stop_agent": {
|
|
1145
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1146
|
-
if (!agent) {
|
|
1147
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1148
|
-
}
|
|
1149
|
-
if (agent.status === "stopped") {
|
|
1150
|
-
return { content: [{ type: "text", text: `Agent ${agent.name} is already stopped` }] };
|
|
1151
|
-
}
|
|
1152
|
-
if (agent.id === META_AGENT_ID) {
|
|
1153
|
-
return { content: [{ type: "text", text: "Cannot stop yourself (the Apteva Assistant)." }], isError: true };
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
const proc = agentProcesses.get(args.agent_id);
|
|
1157
|
-
if (proc) {
|
|
1158
|
-
proc.proc.kill();
|
|
1159
|
-
agentProcesses.delete(args.agent_id);
|
|
1160
|
-
}
|
|
1161
|
-
setAgentStatus(args.agent_id, "stopped", "meta_agent");
|
|
1162
|
-
return { content: [{ type: "text", text: `Agent ${agent.name} stopped` }] };
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
case "list_projects": {
|
|
1166
|
-
const projects = ProjectDB.findAll();
|
|
1167
|
-
const agentCounts = ProjectDB.getAgentCounts();
|
|
1168
|
-
const result = projects.map(p => ({
|
|
1169
|
-
id: p.id,
|
|
1170
|
-
name: p.name,
|
|
1171
|
-
description: p.description,
|
|
1172
|
-
color: p.color,
|
|
1173
|
-
agentCount: agentCounts.get(p.id) || 0,
|
|
1174
|
-
}));
|
|
1175
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
case "create_project": {
|
|
1179
|
-
const project = ProjectDB.create({
|
|
1180
|
-
name: args.name,
|
|
1181
|
-
description: args.description || null,
|
|
1182
|
-
color: args.color,
|
|
1183
|
-
});
|
|
1184
|
-
return { content: [{ type: "text", text: `Project created: ${JSON.stringify({ id: project.id, name: project.name, color: project.color }, null, 2)}` }] };
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
case "update_project": {
|
|
1188
|
-
const project = ProjectDB.findById(args.project_id);
|
|
1189
|
-
if (!project) return { content: [{ type: "text", text: `Project not found: ${args.project_id}` }], isError: true };
|
|
1190
|
-
|
|
1191
|
-
const updates: Record<string, unknown> = {};
|
|
1192
|
-
if (args.name !== undefined) updates.name = args.name;
|
|
1193
|
-
if (args.description !== undefined) updates.description = args.description;
|
|
1194
|
-
if (args.color !== undefined) updates.color = args.color;
|
|
1195
|
-
|
|
1196
|
-
if (Object.keys(updates).length === 0) {
|
|
1197
|
-
return { content: [{ type: "text", text: "No updates provided" }], isError: true };
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
const updated = ProjectDB.update(args.project_id, updates);
|
|
1201
|
-
return { content: [{ type: "text", text: `Project updated: ${JSON.stringify({ id: updated!.id, name: updated!.name, description: updated!.description, color: updated!.color }, null, 2)}` }] };
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
case "delete_project": {
|
|
1205
|
-
const project = ProjectDB.findById(args.project_id);
|
|
1206
|
-
if (!project) return { content: [{ type: "text", text: `Project not found: ${args.project_id}` }], isError: true };
|
|
1207
|
-
|
|
1208
|
-
const agentCounts = ProjectDB.getAgentCounts();
|
|
1209
|
-
const agentCount = agentCounts.get(args.project_id) || 0;
|
|
1210
|
-
|
|
1211
|
-
const deleted = ProjectDB.delete(args.project_id);
|
|
1212
|
-
if (!deleted) return { content: [{ type: "text", text: "Failed to delete project" }], isError: true };
|
|
1213
|
-
|
|
1214
|
-
return { content: [{ type: "text", text: `Project "${project.name}" deleted. ${agentCount} agent(s) were unassigned.` }] };
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
case "list_providers": {
|
|
1218
|
-
const providers = getProvidersWithStatus();
|
|
1219
|
-
const llmProviders = providers.filter(p => p.type === "llm");
|
|
1220
|
-
const result = llmProviders.map(p => ({
|
|
1221
|
-
id: p.id,
|
|
1222
|
-
name: p.name,
|
|
1223
|
-
hasKey: p.hasKey,
|
|
1224
|
-
models: p.models,
|
|
1225
|
-
}));
|
|
1226
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
case "list_mcp_servers": {
|
|
1230
|
-
const servers = args.project_id
|
|
1231
|
-
? McpServerDB.findByProjectLight(args.project_id)
|
|
1232
|
-
: McpServerDB.findAllLight();
|
|
1233
|
-
const result = servers.map(s => ({
|
|
1234
|
-
id: s.id,
|
|
1235
|
-
name: s.name,
|
|
1236
|
-
type: s.type,
|
|
1237
|
-
status: s.status,
|
|
1238
|
-
url: s.url,
|
|
1239
|
-
package: s.package,
|
|
1240
|
-
projectId: s.project_id,
|
|
1241
|
-
}));
|
|
1242
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
case "get_mcp_server": {
|
|
1246
|
-
const server = McpServerDB.findById(args.server_id);
|
|
1247
|
-
if (!server) {
|
|
1248
|
-
return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
|
|
1249
|
-
}
|
|
1250
|
-
const serverInfo: Record<string, any> = {
|
|
1251
|
-
id: server.id,
|
|
1252
|
-
name: server.name,
|
|
1253
|
-
type: server.type,
|
|
1254
|
-
status: server.status,
|
|
1255
|
-
url: server.url,
|
|
1256
|
-
package: server.package,
|
|
1257
|
-
command: server.command,
|
|
1258
|
-
args: server.args,
|
|
1259
|
-
port: server.port,
|
|
1260
|
-
source: server.source,
|
|
1261
|
-
projectId: server.project_id,
|
|
1262
|
-
};
|
|
1263
|
-
// Include tools with full details for local servers
|
|
1264
|
-
if (server.type === "local") {
|
|
1265
|
-
const tools = McpServerToolDB.findByServer(server.id);
|
|
1266
|
-
serverInfo.tools = tools.map(t => ({
|
|
1267
|
-
id: t.id,
|
|
1268
|
-
name: t.name,
|
|
1269
|
-
description: t.description,
|
|
1270
|
-
handler_type: t.handler_type,
|
|
1271
|
-
enabled: t.enabled,
|
|
1272
|
-
input_schema: t.input_schema,
|
|
1273
|
-
mock_response: t.mock_response,
|
|
1274
|
-
http_config: t.http_config,
|
|
1275
|
-
code: t.code,
|
|
1276
|
-
}));
|
|
1277
|
-
}
|
|
1278
|
-
return { content: [{ type: "text", text: JSON.stringify(serverInfo, null, 2) }] };
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
case "create_mcp_server": {
|
|
1282
|
-
const id = generateId();
|
|
1283
|
-
const server = McpServerDB.create({
|
|
1284
|
-
id,
|
|
1285
|
-
name: args.name,
|
|
1286
|
-
type: args.type || "http",
|
|
1287
|
-
package: args.package || null,
|
|
1288
|
-
pip_module: null,
|
|
1289
|
-
command: args.command || null,
|
|
1290
|
-
args: args.args || null,
|
|
1291
|
-
env: {},
|
|
1292
|
-
url: args.url || null,
|
|
1293
|
-
headers: args.headers || {},
|
|
1294
|
-
source: null,
|
|
1295
|
-
project_id: args.project_id || null,
|
|
1296
|
-
});
|
|
1297
|
-
return { content: [{ type: "text", text: `MCP server created: ${JSON.stringify({ id: server.id, name: server.name, type: server.type }, null, 2)}` }] };
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
case "delete_mcp_server": {
|
|
1301
|
-
const server = McpServerDB.findById(args.server_id);
|
|
1302
|
-
if (!server) {
|
|
1303
|
-
return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
|
|
1304
|
-
}
|
|
1305
|
-
if (server.status === "running") {
|
|
1306
|
-
return { content: [{ type: "text", text: "Cannot delete a running MCP server. Stop it first." }], isError: true };
|
|
1307
|
-
}
|
|
1308
|
-
McpServerDB.delete(args.server_id);
|
|
1309
|
-
return { content: [{ type: "text", text: `MCP server deleted: ${server.name} (${server.id})` }] };
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
case "add_tool_to_local_server": {
|
|
1313
|
-
const server = McpServerDB.findById(args.server_id);
|
|
1314
|
-
if (!server) {
|
|
1315
|
-
return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
|
|
1316
|
-
}
|
|
1317
|
-
if (server.type !== "local") {
|
|
1318
|
-
return { content: [{ type: "text", text: `Server "${server.name}" is type "${server.type}" — add_tool_to_local_server only works for local servers.` }], isError: true };
|
|
1319
|
-
}
|
|
1320
|
-
// Check for duplicate tool name
|
|
1321
|
-
const existing = McpServerToolDB.findByServerAndName(args.server_id, args.name);
|
|
1322
|
-
if (existing) {
|
|
1323
|
-
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 };
|
|
1324
|
-
}
|
|
1325
|
-
const tool = McpServerToolDB.create({
|
|
1326
|
-
id: generateId(),
|
|
1327
|
-
server_id: args.server_id,
|
|
1328
|
-
name: args.name,
|
|
1329
|
-
description: args.description,
|
|
1330
|
-
input_schema: args.input_schema || { type: "object", properties: {} },
|
|
1331
|
-
handler_type: args.handler_type || "mock",
|
|
1332
|
-
mock_response: args.mock_response || null,
|
|
1333
|
-
http_config: args.http_config || null,
|
|
1334
|
-
code: args.code || null,
|
|
1335
|
-
enabled: true,
|
|
1336
|
-
});
|
|
1337
|
-
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)}` }] };
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
case "update_tool_on_local_server": {
|
|
1341
|
-
const tool = McpServerToolDB.findById(args.tool_id);
|
|
1342
|
-
if (!tool) {
|
|
1343
|
-
return { content: [{ type: "text", text: `Tool not found: ${args.tool_id}` }], isError: true };
|
|
1344
|
-
}
|
|
1345
|
-
const server = McpServerDB.findById(tool.server_id);
|
|
1346
|
-
if (!server || server.type !== "local") {
|
|
1347
|
-
return { content: [{ type: "text", text: `Tool belongs to a non-local server — update_tool_on_local_server only works for local servers.` }], isError: true };
|
|
1348
|
-
}
|
|
1349
|
-
const updates: Record<string, unknown> = {};
|
|
1350
|
-
if (args.name !== undefined) updates.name = args.name;
|
|
1351
|
-
if (args.description !== undefined) updates.description = args.description;
|
|
1352
|
-
if (args.input_schema !== undefined) updates.input_schema = args.input_schema;
|
|
1353
|
-
if (args.handler_type !== undefined) updates.handler_type = args.handler_type;
|
|
1354
|
-
if (args.mock_response !== undefined) updates.mock_response = args.mock_response;
|
|
1355
|
-
if (args.http_config !== undefined) updates.http_config = args.http_config;
|
|
1356
|
-
if (args.code !== undefined) updates.code = args.code;
|
|
1357
|
-
if (args.enabled !== undefined) updates.enabled = args.enabled;
|
|
1358
|
-
const updated = McpServerToolDB.update(args.tool_id, updates);
|
|
1359
|
-
if (!updated) {
|
|
1360
|
-
return { content: [{ type: "text", text: `Failed to update tool ${args.tool_id}` }], isError: true };
|
|
1361
|
-
}
|
|
1362
|
-
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)}` }] };
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
case "get_dashboard_stats": {
|
|
1366
|
-
const agentCount = AgentDB.count();
|
|
1367
|
-
const runningCount = AgentDB.countRunning();
|
|
1368
|
-
const projectCount = ProjectDB.count();
|
|
1369
|
-
const providers = getProvidersWithStatus().filter(p => p.type === "llm");
|
|
1370
|
-
const configuredProviders = providers.filter(p => p.hasKey).length;
|
|
1371
|
-
const mcpServerCount = McpServerDB.count();
|
|
1372
|
-
const skillCount = SkillDB.count();
|
|
1373
|
-
|
|
1374
|
-
return {
|
|
1375
|
-
content: [{
|
|
1376
|
-
type: "text",
|
|
1377
|
-
text: JSON.stringify({
|
|
1378
|
-
agents: { total: agentCount - 1, running: runningCount }, // -1 for meta agent
|
|
1379
|
-
projects: projectCount,
|
|
1380
|
-
providers: { total: providers.length, configured: configuredProviders },
|
|
1381
|
-
mcpServers: mcpServerCount,
|
|
1382
|
-
skills: skillCount,
|
|
1383
|
-
}, null, 2),
|
|
1384
|
-
}],
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
case "send_message": {
|
|
1389
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1390
|
-
if (!agent) {
|
|
1391
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1392
|
-
}
|
|
1393
|
-
if (agent.status !== "running" || !agent.port) {
|
|
1394
|
-
return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
try {
|
|
1398
|
-
const res = await agentFetch(args.agent_id, agent.port, "/chat", {
|
|
1399
|
-
method: "POST",
|
|
1400
|
-
headers: { "Content-Type": "application/json" },
|
|
1401
|
-
body: JSON.stringify({ message: args.message }),
|
|
1402
|
-
signal: AbortSignal.timeout(120000),
|
|
1403
|
-
});
|
|
1404
|
-
|
|
1405
|
-
if (!res.ok) {
|
|
1406
|
-
const err = await res.text().catch(() => "Unknown error");
|
|
1407
|
-
return { content: [{ type: "text", text: `Agent responded with error: ${err}` }], isError: true };
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
// Agent returns SSE stream — consume and assemble content chunks
|
|
1411
|
-
const reader = res.body?.getReader();
|
|
1412
|
-
if (!reader) {
|
|
1413
|
-
return { content: [{ type: "text", text: "No response stream from agent" }], isError: true };
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const decoder = new TextDecoder();
|
|
1417
|
-
const contentChunks: string[] = [];
|
|
1418
|
-
let raw = "";
|
|
1419
|
-
|
|
1420
|
-
while (true) {
|
|
1421
|
-
const { done, value } = await reader.read();
|
|
1422
|
-
if (done) break;
|
|
1423
|
-
raw += decoder.decode(value, { stream: true });
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// Parse SSE lines: "data: {...}"
|
|
1427
|
-
for (const line of raw.split("\n")) {
|
|
1428
|
-
const sseData = line.startsWith("data: ") ? line.slice(6) : line.trim();
|
|
1429
|
-
if (!sseData) continue;
|
|
1430
|
-
try {
|
|
1431
|
-
const evt = JSON.parse(sseData);
|
|
1432
|
-
if (evt.type === "content" && evt.content) {
|
|
1433
|
-
contentChunks.push(evt.content);
|
|
1434
|
-
}
|
|
1435
|
-
} catch {
|
|
1436
|
-
// Not valid JSON — skip
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
const reply = contentChunks.join("") || "(no response)";
|
|
1441
|
-
return { content: [{ type: "text", text: reply }] };
|
|
1442
|
-
} catch (err) {
|
|
1443
|
-
return { content: [{ type: "text", text: `Failed to communicate with agent: ${err}` }], isError: true };
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
case "list_tasks": {
|
|
1448
|
-
const status = args.status || "all";
|
|
1449
|
-
let runningAgents = AgentDB.findAll().filter(a => a.status === "running" && a.port);
|
|
1450
|
-
|
|
1451
|
-
if (args.project_id === "unassigned") {
|
|
1452
|
-
runningAgents = runningAgents.filter(a => !a.project_id);
|
|
1453
|
-
} else if (args.project_id) {
|
|
1454
|
-
runningAgents = runningAgents.filter(a => a.project_id === args.project_id);
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
const allTasks: any[] = [];
|
|
1458
|
-
const results = await Promise.all(
|
|
1459
|
-
runningAgents.map(async (agent) => {
|
|
1460
|
-
try {
|
|
1461
|
-
const data = await fetchFromAgent(agent.id, agent.port!, `/tasks?status=${status}`);
|
|
1462
|
-
return { agent, tasks: data?.tasks || [] };
|
|
1463
|
-
} catch {
|
|
1464
|
-
return { agent, tasks: [] };
|
|
1465
|
-
}
|
|
1466
|
-
})
|
|
1467
|
-
);
|
|
1468
|
-
|
|
1469
|
-
for (const { agent, tasks } of results) {
|
|
1470
|
-
for (const task of tasks) {
|
|
1471
|
-
allTasks.push({
|
|
1472
|
-
id: task.id, title: task.title, type: task.type, status: task.status, priority: task.priority,
|
|
1473
|
-
recurrence: task.recurrence, next_run: task.next_run, execute_at: task.execute_at, created_at: task.created_at,
|
|
1474
|
-
agentId: agent.id, agentName: agent.name,
|
|
1475
|
-
});
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
allTasks.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
1480
|
-
return { content: [{ type: "text", text: JSON.stringify({ tasks: allTasks, count: allTasks.length }, null, 2) }] };
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
case "list_agent_tasks": {
|
|
1484
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1485
|
-
if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1486
|
-
if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
|
|
1487
|
-
|
|
1488
|
-
const status = args.status || "all";
|
|
1489
|
-
const data = await fetchFromAgent(agent.id, agent.port, `/tasks?status=${status}`);
|
|
1490
|
-
if (!data) return { content: [{ type: "text", text: "Failed to fetch tasks from agent" }], isError: true };
|
|
1491
|
-
|
|
1492
|
-
const tasks = (data.tasks || []).map((t: any) => ({
|
|
1493
|
-
id: t.id, title: t.title, type: t.type, status: t.status, priority: t.priority,
|
|
1494
|
-
recurrence: t.recurrence, next_run: t.next_run, execute_at: t.execute_at, created_at: t.created_at,
|
|
1495
|
-
}));
|
|
1496
|
-
return { content: [{ type: "text", text: JSON.stringify({ tasks, count: tasks.length }, null, 2) }] };
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
case "create_agent_task": {
|
|
1500
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1501
|
-
if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1502
|
-
if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
|
|
1503
|
-
|
|
1504
|
-
const body: Record<string, unknown> = {
|
|
1505
|
-
title: args.title,
|
|
1506
|
-
description: args.description,
|
|
1507
|
-
type: args.type || "once",
|
|
1508
|
-
priority: args.priority || 5,
|
|
1509
|
-
};
|
|
1510
|
-
if (args.execute_at) body.execute_at = args.execute_at;
|
|
1511
|
-
if (args.recurrence) body.recurrence = args.recurrence;
|
|
1512
|
-
|
|
1513
|
-
try {
|
|
1514
|
-
const res = await agentFetch(agent.id, agent.port, "/tasks", {
|
|
1515
|
-
method: "POST",
|
|
1516
|
-
headers: { "Content-Type": "application/json" },
|
|
1517
|
-
body: JSON.stringify(body),
|
|
1518
|
-
signal: AbortSignal.timeout(5000),
|
|
1519
|
-
});
|
|
1520
|
-
const data = await res.json();
|
|
1521
|
-
if (!res.ok) return { content: [{ type: "text", text: `Failed to create task: ${data.error || res.status}` }], isError: true };
|
|
1522
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1523
|
-
} catch (err) {
|
|
1524
|
-
return { content: [{ type: "text", text: `Failed to create task: ${err}` }], isError: true };
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
case "delete_agent_task": {
|
|
1529
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1530
|
-
if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1531
|
-
if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
|
|
1532
|
-
|
|
1533
|
-
try {
|
|
1534
|
-
const res = await agentFetch(agent.id, agent.port, `/tasks/${args.task_id}`, {
|
|
1535
|
-
method: "DELETE",
|
|
1536
|
-
signal: AbortSignal.timeout(5000),
|
|
1537
|
-
});
|
|
1538
|
-
const data = await res.json();
|
|
1539
|
-
if (!res.ok) return { content: [{ type: "text", text: `Failed to delete task: ${data.error || res.status}` }], isError: true };
|
|
1540
|
-
return { content: [{ type: "text", text: `Task ${args.task_id} deleted successfully` }] };
|
|
1541
|
-
} catch (err) {
|
|
1542
|
-
return { content: [{ type: "text", text: `Failed to delete task: ${err}` }], isError: true };
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
case "execute_agent_task": {
|
|
1547
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1548
|
-
if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1549
|
-
if (agent.status !== "running" || !agent.port) return { content: [{ type: "text", text: `Agent ${agent.name} is not running` }], isError: true };
|
|
1550
|
-
|
|
1551
|
-
try {
|
|
1552
|
-
const res = await agentFetch(agent.id, agent.port, `/tasks/${args.task_id}/execute`, {
|
|
1553
|
-
method: "POST",
|
|
1554
|
-
signal: AbortSignal.timeout(5000),
|
|
1555
|
-
});
|
|
1556
|
-
const data = await res.json();
|
|
1557
|
-
if (!res.ok) return { content: [{ type: "text", text: `Failed to execute task: ${data.error || res.status}` }], isError: true };
|
|
1558
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1559
|
-
} catch (err) {
|
|
1560
|
-
return { content: [{ type: "text", text: `Failed to execute task: ${err}` }], isError: true };
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
case "list_skills": {
|
|
1565
|
-
let skills = args.enabled_only ? SkillDB.findEnabled() : SkillDB.findAll();
|
|
1566
|
-
if (args.project_id) {
|
|
1567
|
-
skills = skills.filter(s => !s.project_id || s.project_id === args.project_id);
|
|
1568
|
-
}
|
|
1569
|
-
const result = skills.map(s => ({
|
|
1570
|
-
id: s.id,
|
|
1571
|
-
name: s.name,
|
|
1572
|
-
description: s.description,
|
|
1573
|
-
version: s.version,
|
|
1574
|
-
enabled: s.enabled,
|
|
1575
|
-
source: s.source,
|
|
1576
|
-
projectId: s.project_id,
|
|
1577
|
-
}));
|
|
1578
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
case "get_skill": {
|
|
1582
|
-
const skill = SkillDB.findById(args.skill_id);
|
|
1583
|
-
if (!skill) {
|
|
1584
|
-
return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
|
|
1585
|
-
}
|
|
1586
|
-
return { content: [{ type: "text", text: JSON.stringify({
|
|
1587
|
-
id: skill.id,
|
|
1588
|
-
name: skill.name,
|
|
1589
|
-
description: skill.description,
|
|
1590
|
-
content: skill.content.slice(0, 500) + (skill.content.length > 500 ? "..." : ""),
|
|
1591
|
-
version: skill.version,
|
|
1592
|
-
enabled: skill.enabled,
|
|
1593
|
-
source: skill.source,
|
|
1594
|
-
allowedTools: skill.allowed_tools,
|
|
1595
|
-
projectId: skill.project_id,
|
|
1596
|
-
}, null, 2) }] };
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
case "toggle_skill": {
|
|
1600
|
-
const skill = SkillDB.findById(args.skill_id);
|
|
1601
|
-
if (!skill) {
|
|
1602
|
-
return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
|
|
1603
|
-
}
|
|
1604
|
-
SkillDB.setEnabled(args.skill_id, args.enabled);
|
|
1605
|
-
return { content: [{ type: "text", text: `Skill "${skill.name}" ${args.enabled ? "enabled" : "disabled"}` }] };
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
case "update_skill": {
|
|
1609
|
-
const skill = SkillDB.findById(args.skill_id);
|
|
1610
|
-
if (!skill) {
|
|
1611
|
-
return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
|
|
1612
|
-
}
|
|
1613
|
-
const updates: Record<string, any> = {};
|
|
1614
|
-
if (args.name !== undefined) updates.name = args.name;
|
|
1615
|
-
if (args.description !== undefined) updates.description = args.description;
|
|
1616
|
-
if (args.content !== undefined) updates.content = args.content;
|
|
1617
|
-
if (args.allowed_tools !== undefined) updates.allowed_tools = args.allowed_tools;
|
|
1618
|
-
const updated = SkillDB.update(args.skill_id, updates);
|
|
1619
|
-
return { content: [{ type: "text", text: `Skill "${updated?.name || skill.name}" updated.` }] };
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
case "create_skill": {
|
|
1623
|
-
if (!args.name || !args.description || !args.content) {
|
|
1624
|
-
return { content: [{ type: "text", text: "name, description, and content are required" }], isError: true };
|
|
1625
|
-
}
|
|
1626
|
-
const newSkill = SkillDB.create({
|
|
1627
|
-
name: args.name,
|
|
1628
|
-
description: args.description,
|
|
1629
|
-
content: args.content,
|
|
1630
|
-
version: "1.0.0",
|
|
1631
|
-
license: null,
|
|
1632
|
-
compatibility: null,
|
|
1633
|
-
metadata: {},
|
|
1634
|
-
allowed_tools: args.allowed_tools || [],
|
|
1635
|
-
source: "local",
|
|
1636
|
-
source_url: null,
|
|
1637
|
-
enabled: true,
|
|
1638
|
-
project_id: args.project_id || null,
|
|
1639
|
-
});
|
|
1640
|
-
return { content: [{ type: "text", text: `Skill "${newSkill.name}" created (ID: ${newSkill.id}). You can now assign it to an agent with assign_skill_to_agent.` }] };
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
case "delete_skill": {
|
|
1644
|
-
const skill = SkillDB.findById(args.skill_id);
|
|
1645
|
-
if (!skill) {
|
|
1646
|
-
return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
|
|
1647
|
-
}
|
|
1648
|
-
// Unassign from all agents first
|
|
1649
|
-
const agentsWithSkill = AgentDB.findBySkill(args.skill_id);
|
|
1650
|
-
for (const agent of agentsWithSkill) {
|
|
1651
|
-
const updated = (agent.skills || []).filter((id: string) => id !== args.skill_id);
|
|
1652
|
-
AgentDB.update(agent.id, { skills: updated });
|
|
1653
|
-
}
|
|
1654
|
-
SkillDB.delete(args.skill_id);
|
|
1655
|
-
return { content: [{ type: "text", text: `Skill "${skill.name}" deleted${agentsWithSkill.length > 0 ? ` (unassigned from ${agentsWithSkill.length} agent(s))` : ""}` }] };
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// Test tools
|
|
1659
|
-
case "list_tests": {
|
|
1660
|
-
const tests = TestCaseDB.findAll(args.project_id);
|
|
1661
|
-
const result = tests.map(tc => {
|
|
1662
|
-
const agent = tc.agent_id ? AgentDB.findById(tc.agent_id) : null;
|
|
1663
|
-
const lastRun = TestRunDB.getLatestByTestCase(tc.id);
|
|
1664
|
-
return {
|
|
1665
|
-
id: tc.id,
|
|
1666
|
-
name: tc.name,
|
|
1667
|
-
behavior: tc.behavior || null,
|
|
1668
|
-
agent_id: tc.agent_id || null,
|
|
1669
|
-
agent_name: agent?.name || (tc.agent_id ? "Unknown" : "Auto-select"),
|
|
1670
|
-
last_status: lastRun?.status || null,
|
|
1671
|
-
last_score: lastRun?.score || null,
|
|
1672
|
-
last_reasoning: lastRun?.judge_reasoning || null,
|
|
1673
|
-
last_selected_agent: lastRun?.selected_agent_name || null,
|
|
1674
|
-
};
|
|
1675
|
-
});
|
|
1676
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
case "create_test": {
|
|
1680
|
-
// Validate agent if explicitly provided
|
|
1681
|
-
if (args.agent_id) {
|
|
1682
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1683
|
-
if (!agent) {
|
|
1684
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
const tc = TestCaseDB.create({
|
|
1688
|
-
name: args.name,
|
|
1689
|
-
behavior: args.behavior,
|
|
1690
|
-
agent_id: args.agent_id || null,
|
|
1691
|
-
input_message: null,
|
|
1692
|
-
eval_criteria: args.behavior,
|
|
1693
|
-
timeout_ms: args.timeout_ms || 300000,
|
|
1694
|
-
project_id: args.project_id || null,
|
|
1695
|
-
});
|
|
1696
|
-
const agentNote = args.agent_id
|
|
1697
|
-
? `pinned to agent "${AgentDB.findById(args.agent_id)?.name}"`
|
|
1698
|
-
: "AI will auto-select the best agent";
|
|
1699
|
-
return { content: [{ type: "text", text: `Test "${tc.name}" created (id: ${tc.id}). ${agentNote}. Use run_test to execute it.` }] };
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
case "run_test": {
|
|
1703
|
-
const tc = TestCaseDB.findById(args.test_id);
|
|
1704
|
-
if (!tc) {
|
|
1705
|
-
return { content: [{ type: "text", text: `Test not found: ${args.test_id}` }], isError: true };
|
|
1706
|
-
}
|
|
1707
|
-
const result = await runTest(tc);
|
|
1708
|
-
const agentName = result.selected_agent_name || (tc.agent_id ? AgentDB.findById(tc.agent_id)?.name : null) || "unknown";
|
|
1709
|
-
const score = result.score != null ? ` (score: ${result.score}/10)` : "";
|
|
1710
|
-
const duration = result.duration_ms ? ` in ${(result.duration_ms / 1000).toFixed(1)}s` : "";
|
|
1711
|
-
return { content: [{ type: "text", text: `Test "${tc.name}" → ${result.status.toUpperCase()}${score}${duration}\nAgent: ${agentName}\n\nJudge: ${result.judge_reasoning || result.error || "No reasoning"}` }] };
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
case "run_all_tests": {
|
|
1715
|
-
const results = await runAll(args.test_case_ids);
|
|
1716
|
-
const passed = results.filter(r => r.status === "passed").length;
|
|
1717
|
-
const failed = results.filter(r => r.status === "failed").length;
|
|
1718
|
-
const errors = results.filter(r => r.status === "error").length;
|
|
1719
|
-
const lines = results.map(r => {
|
|
1720
|
-
const tc = TestCaseDB.findById(r.test_case_id);
|
|
1721
|
-
return `- ${tc?.name || r.test_case_id}: ${r.status.toUpperCase()}${r.judge_reasoning ? ` — ${r.judge_reasoning}` : ""}${r.error ? ` — Error: ${r.error}` : ""}`;
|
|
1722
|
-
});
|
|
1723
|
-
return { content: [{ type: "text", text: `Test Results: ${passed} passed, ${failed} failed, ${errors} errors (${results.length} total)\n\n${lines.join("\n")}` }] };
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
case "get_test_results": {
|
|
1727
|
-
const tc = TestCaseDB.findById(args.test_id);
|
|
1728
|
-
if (!tc) {
|
|
1729
|
-
return { content: [{ type: "text", text: `Test not found: ${args.test_id}` }], isError: true };
|
|
1730
|
-
}
|
|
1731
|
-
const runs = TestRunDB.findByTestCase(args.test_id, args.limit || 10);
|
|
1732
|
-
const result = runs.map(r => ({
|
|
1733
|
-
id: r.id,
|
|
1734
|
-
status: r.status,
|
|
1735
|
-
score: r.score,
|
|
1736
|
-
duration_ms: r.duration_ms,
|
|
1737
|
-
judge_reasoning: r.judge_reasoning,
|
|
1738
|
-
selected_agent: r.selected_agent_name || null,
|
|
1739
|
-
generated_message: r.generated_message || null,
|
|
1740
|
-
error: r.error,
|
|
1741
|
-
created_at: r.created_at,
|
|
1742
|
-
}));
|
|
1743
|
-
return { content: [{ type: "text", text: `Run history for "${tc.name}":\n${JSON.stringify(result, null, 2)}` }] };
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
case "delete_test": {
|
|
1747
|
-
const tc = TestCaseDB.findById(args.test_id);
|
|
1748
|
-
if (!tc) {
|
|
1749
|
-
return { content: [{ type: "text", text: `Test not found: ${args.test_id}` }], isError: true };
|
|
1750
|
-
}
|
|
1751
|
-
TestCaseDB.delete(args.test_id);
|
|
1752
|
-
return { content: [{ type: "text", text: `Test "${tc.name}" deleted.` }] };
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// Subscription & Trigger tools
|
|
1756
|
-
case "list_trigger_providers": {
|
|
1757
|
-
const providerIds = getTriggerProviderIds();
|
|
1758
|
-
const projectId = args.project_id || null;
|
|
1759
|
-
const result = providerIds.map(id => {
|
|
1760
|
-
const provider = getTriggerProvider(id);
|
|
1761
|
-
const hasKey = !!ProviderKeys.getDecryptedForProject(id, projectId);
|
|
1762
|
-
return {
|
|
1763
|
-
id,
|
|
1764
|
-
name: provider?.name || id,
|
|
1765
|
-
hasKey,
|
|
1766
|
-
};
|
|
1767
|
-
});
|
|
1768
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
case "list_trigger_types": {
|
|
1772
|
-
const providerId = args.provider;
|
|
1773
|
-
const projectId = args.project_id || null;
|
|
1774
|
-
const triggerProvider = getTriggerProvider(providerId);
|
|
1775
|
-
if (!triggerProvider) {
|
|
1776
|
-
return { content: [{ type: "text", text: `Unknown trigger provider: ${providerId}. Available: ${getTriggerProviderIds().join(", ")}` }], isError: true };
|
|
1777
|
-
}
|
|
1778
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
1779
|
-
if (!apiKey) {
|
|
1780
|
-
return { content: [{ type: "text", text: `${triggerProvider.name} API key not configured` }], isError: true };
|
|
1781
|
-
}
|
|
1782
|
-
const types = await triggerProvider.listTriggerTypes(apiKey);
|
|
1783
|
-
const result = types.map(t => ({
|
|
1784
|
-
slug: t.slug,
|
|
1785
|
-
name: t.name,
|
|
1786
|
-
description: t.description,
|
|
1787
|
-
type: t.type,
|
|
1788
|
-
toolkit_slug: t.toolkit_slug,
|
|
1789
|
-
toolkit_name: t.toolkit_name,
|
|
1790
|
-
config_schema: t.config_schema,
|
|
1791
|
-
}));
|
|
1792
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
case "list_subscriptions": {
|
|
1797
|
-
let subscriptions;
|
|
1798
|
-
if (args.agent_id) {
|
|
1799
|
-
subscriptions = SubscriptionDB.findByAgentId(args.agent_id);
|
|
1800
|
-
} else {
|
|
1801
|
-
subscriptions = SubscriptionDB.findAll(args.project_id || null);
|
|
1802
|
-
}
|
|
1803
|
-
const result = subscriptions.map(s => {
|
|
1804
|
-
const agent = AgentDB.findById(s.agent_id);
|
|
1805
|
-
return {
|
|
1806
|
-
id: s.id,
|
|
1807
|
-
trigger_slug: s.trigger_slug,
|
|
1808
|
-
trigger_instance_id: s.trigger_instance_id,
|
|
1809
|
-
agent_id: s.agent_id,
|
|
1810
|
-
agent_name: agent?.name || "Unknown",
|
|
1811
|
-
enabled: s.enabled,
|
|
1812
|
-
project_id: s.project_id,
|
|
1813
|
-
created_at: s.created_at,
|
|
1814
|
-
};
|
|
1815
|
-
});
|
|
1816
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
case "create_subscription": {
|
|
1820
|
-
const providerId = args.provider;
|
|
1821
|
-
const projectId = args.project_id || null;
|
|
1822
|
-
const triggerProvider = getTriggerProvider(providerId);
|
|
1823
|
-
if (!triggerProvider) {
|
|
1824
|
-
return { content: [{ type: "text", text: `Unknown trigger provider: ${providerId}. Available: ${getTriggerProviderIds().join(", ")}` }], isError: true };
|
|
1825
|
-
}
|
|
1826
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
1827
|
-
if (!apiKey) {
|
|
1828
|
-
return { content: [{ type: "text", text: `${triggerProvider.name} API key not configured` }], isError: true };
|
|
1829
|
-
}
|
|
1830
|
-
// Validate agent exists
|
|
1831
|
-
const agent = AgentDB.findById(args.agent_id);
|
|
1832
|
-
if (!agent) {
|
|
1833
|
-
return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
// Auto-setup webhook if not already configured for this provider
|
|
1837
|
-
const existingWebhook = SettingsDB.get(`${providerId}_webhook_url`);
|
|
1838
|
-
if (!existingWebhook) {
|
|
1839
|
-
try {
|
|
1840
|
-
const instanceUrl = SettingsDB.get("instance_url");
|
|
1841
|
-
if (instanceUrl) {
|
|
1842
|
-
const webhookUrl = `${instanceUrl}/api/webhooks/${providerId}`;
|
|
1843
|
-
const webhookResult = await triggerProvider.setupWebhook(apiKey, webhookUrl);
|
|
1844
|
-
if (webhookResult.secret) {
|
|
1845
|
-
SettingsDB.set(`${providerId}_webhook_secret`, webhookResult.secret);
|
|
1846
|
-
}
|
|
1847
|
-
SettingsDB.set(`${providerId}_webhook_url`, webhookUrl);
|
|
1848
|
-
console.log(`[platform-mcp] Auto-configured ${providerId} webhook: ${webhookUrl}`);
|
|
1849
|
-
}
|
|
1850
|
-
} catch (e) {
|
|
1851
|
-
console.warn(`[platform-mcp] Failed to auto-setup ${providerId} webhook:`, e);
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// Create remote trigger on the provider — auto-inject callback_url
|
|
1856
|
-
const webhookUrl = SettingsDB.get(`${providerId}_webhook_url`);
|
|
1857
|
-
const config: Record<string, unknown> = {
|
|
1858
|
-
agent_id: args.agent_id,
|
|
1859
|
-
...(webhookUrl ? { callback_url: webhookUrl } : {}),
|
|
1860
|
-
...(args.config || {}),
|
|
1861
|
-
};
|
|
1862
|
-
const triggerResult = await triggerProvider.createTrigger(apiKey, args.trigger_slug, args.connected_account_id, config);
|
|
1863
|
-
|
|
1864
|
-
// Create local subscription for webhook routing
|
|
1865
|
-
const subscription = SubscriptionDB.create({
|
|
1866
|
-
trigger_slug: args.trigger_slug,
|
|
1867
|
-
trigger_instance_id: triggerResult.triggerId || null,
|
|
1868
|
-
agent_id: args.agent_id,
|
|
1869
|
-
enabled: true,
|
|
1870
|
-
project_id: projectId,
|
|
1871
|
-
});
|
|
1872
|
-
|
|
1873
|
-
console.log(`[platform-mcp] Created subscription: ${args.trigger_slug} (instance=${triggerResult.triggerId}) → agent ${agent.name} (${agent.id})`);
|
|
1874
|
-
return { content: [{ type: "text", text: `Subscription created:\n${JSON.stringify({
|
|
1875
|
-
subscription_id: subscription.id,
|
|
1876
|
-
trigger_slug: args.trigger_slug,
|
|
1877
|
-
trigger_instance_id: triggerResult.triggerId,
|
|
1878
|
-
agent: agent.name,
|
|
1879
|
-
provider: providerId,
|
|
1880
|
-
enabled: true,
|
|
1881
|
-
}, null, 2)}` }] };
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
case "enable_subscription": {
|
|
1885
|
-
const sub = SubscriptionDB.findById(args.subscription_id);
|
|
1886
|
-
if (!sub) {
|
|
1887
|
-
return { content: [{ type: "text", text: `Subscription not found: ${args.subscription_id}` }], isError: true };
|
|
1888
|
-
}
|
|
1889
|
-
SubscriptionDB.update(args.subscription_id, { enabled: true });
|
|
1890
|
-
|
|
1891
|
-
// Also enable remote trigger if provider specified
|
|
1892
|
-
if (args.provider && sub.trigger_instance_id) {
|
|
1893
|
-
const triggerProvider = getTriggerProvider(args.provider);
|
|
1894
|
-
const apiKey = triggerProvider ? ProviderKeys.getDecryptedForProject(args.provider, args.project_id || null) : null;
|
|
1895
|
-
if (triggerProvider && apiKey) {
|
|
1896
|
-
try {
|
|
1897
|
-
await triggerProvider.enableTrigger(apiKey, sub.trigger_instance_id);
|
|
1898
|
-
} catch (e) {
|
|
1899
|
-
console.warn(`[platform-mcp] Failed to enable remote trigger ${sub.trigger_instance_id}:`, e);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
return { content: [{ type: "text", text: `Subscription "${sub.trigger_slug}" enabled` }] };
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
case "disable_subscription": {
|
|
1907
|
-
const sub = SubscriptionDB.findById(args.subscription_id);
|
|
1908
|
-
if (!sub) {
|
|
1909
|
-
return { content: [{ type: "text", text: `Subscription not found: ${args.subscription_id}` }], isError: true };
|
|
1910
|
-
}
|
|
1911
|
-
SubscriptionDB.update(args.subscription_id, { enabled: false });
|
|
1912
|
-
|
|
1913
|
-
// Also disable remote trigger if provider specified
|
|
1914
|
-
if (args.provider && sub.trigger_instance_id) {
|
|
1915
|
-
const triggerProvider = getTriggerProvider(args.provider);
|
|
1916
|
-
const apiKey = triggerProvider ? ProviderKeys.getDecryptedForProject(args.provider, args.project_id || null) : null;
|
|
1917
|
-
if (triggerProvider && apiKey) {
|
|
1918
|
-
try {
|
|
1919
|
-
await triggerProvider.disableTrigger(apiKey, sub.trigger_instance_id);
|
|
1920
|
-
} catch (e) {
|
|
1921
|
-
console.warn(`[platform-mcp] Failed to disable remote trigger ${sub.trigger_instance_id}:`, e);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
return { content: [{ type: "text", text: `Subscription "${sub.trigger_slug}" disabled` }] };
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
case "delete_subscription": {
|
|
1929
|
-
const sub = SubscriptionDB.findById(args.subscription_id);
|
|
1930
|
-
if (!sub) {
|
|
1931
|
-
return { content: [{ type: "text", text: `Subscription not found: ${args.subscription_id}` }], isError: true };
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
// Delete remote trigger if requested
|
|
1935
|
-
if (args.delete_remote && args.provider && sub.trigger_instance_id) {
|
|
1936
|
-
const triggerProvider = getTriggerProvider(args.provider);
|
|
1937
|
-
const apiKey = triggerProvider ? ProviderKeys.getDecryptedForProject(args.provider, args.project_id || null) : null;
|
|
1938
|
-
if (triggerProvider && apiKey) {
|
|
1939
|
-
try {
|
|
1940
|
-
await triggerProvider.deleteTrigger(apiKey, sub.trigger_instance_id);
|
|
1941
|
-
console.log(`[platform-mcp] Deleted remote trigger ${sub.trigger_instance_id} on ${args.provider}`);
|
|
1942
|
-
} catch (e) {
|
|
1943
|
-
console.warn(`[platform-mcp] Failed to delete remote trigger ${sub.trigger_instance_id}:`, e);
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
SubscriptionDB.delete(args.subscription_id);
|
|
1949
|
-
return { content: [{ type: "text", text: `Subscription "${sub.trigger_slug}" deleted` }] };
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Integration management tools
|
|
1953
|
-
case "list_integration_providers": {
|
|
1954
|
-
|
|
1955
|
-
const providerIds = getProviderIds();
|
|
1956
|
-
const projectId = args.project_id || null;
|
|
1957
|
-
const result = providerIds.map(id => {
|
|
1958
|
-
const provider = getProvider(id);
|
|
1959
|
-
const hasKey = !!ProviderKeys.getDecryptedForProject(id, projectId);
|
|
1960
|
-
return { id, name: provider?.name || id, hasKey };
|
|
1961
|
-
});
|
|
1962
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
case "list_integration_apps": {
|
|
1966
|
-
|
|
1967
|
-
const providerId = args.provider;
|
|
1968
|
-
const projectId = args.project_id || null;
|
|
1969
|
-
const provider = getProvider(providerId);
|
|
1970
|
-
if (!provider) {
|
|
1971
|
-
return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}. Available: ${getProviderIds().join(", ")}` }], isError: true };
|
|
1972
|
-
}
|
|
1973
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
1974
|
-
if (!apiKey) {
|
|
1975
|
-
return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
|
|
1976
|
-
}
|
|
1977
|
-
let apps = await provider.listApps(apiKey);
|
|
1978
|
-
if (args.search) {
|
|
1979
|
-
const s = args.search.toLowerCase();
|
|
1980
|
-
apps = apps.filter(a =>
|
|
1981
|
-
a.name.toLowerCase().includes(s) ||
|
|
1982
|
-
a.slug.toLowerCase().includes(s) ||
|
|
1983
|
-
a.description?.toLowerCase().includes(s)
|
|
1984
|
-
);
|
|
1985
|
-
}
|
|
1986
|
-
// Also check which are connected
|
|
1987
|
-
let connectedIds = new Set<string>();
|
|
1988
|
-
try {
|
|
1989
|
-
const accounts = await provider.listConnectedAccounts(apiKey, "platform-agent");
|
|
1990
|
-
connectedIds = new Set(accounts.filter(a => a.status === "active").map(a => a.appId));
|
|
1991
|
-
} catch {}
|
|
1992
|
-
const result = apps.slice(0, 50).map(a => ({
|
|
1993
|
-
slug: a.slug,
|
|
1994
|
-
name: a.name,
|
|
1995
|
-
description: a.description,
|
|
1996
|
-
authSchemes: a.authSchemes,
|
|
1997
|
-
categories: a.categories,
|
|
1998
|
-
connected: connectedIds.has(a.slug) || (a.providerSlug ? connectedIds.has(a.providerSlug) : false),
|
|
1999
|
-
credentialFields: a.credentialFields || undefined,
|
|
2000
|
-
}));
|
|
2001
|
-
return { content: [{ type: "text", text: `Found ${apps.length} apps (showing ${result.length}):\n${JSON.stringify(result, null, 2)}` }] };
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
case "connect_integration_app": {
|
|
2005
|
-
const providerId = args.provider;
|
|
2006
|
-
const projectId = args.project_id || null;
|
|
2007
|
-
const provider = getProvider(providerId);
|
|
2008
|
-
if (!provider) {
|
|
2009
|
-
return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
|
|
2010
|
-
}
|
|
2011
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2012
|
-
if (!apiKey) {
|
|
2013
|
-
return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
|
|
2014
|
-
}
|
|
2015
|
-
if (!args.api_key && !args.credentials) {
|
|
2016
|
-
return { content: [{ type: "text", text: `Either api_key or credentials is required` }], isError: true };
|
|
2017
|
-
}
|
|
2018
|
-
// Build credential object — prefer multi-field credentials over single api_key
|
|
2019
|
-
const creds: any = { authScheme: "API_KEY" as const };
|
|
2020
|
-
if (args.credentials && Object.keys(args.credentials).length > 0) {
|
|
2021
|
-
creds.fields = args.credentials;
|
|
2022
|
-
} else {
|
|
2023
|
-
creds.apiKey = args.api_key;
|
|
2024
|
-
}
|
|
2025
|
-
const connectionResult = await provider.initiateConnection(apiKey, "platform-agent", args.app_slug, "", creds);
|
|
2026
|
-
if (connectionResult.status === "active") {
|
|
2027
|
-
return { content: [{ type: "text", text: `Successfully connected "${args.app_slug}". Connection ID: ${connectionResult.connectionId || "N/A"}` }] };
|
|
2028
|
-
}
|
|
2029
|
-
return { content: [{ type: "text", text: `Connection initiated but status is ${connectionResult.status}. This may require OAuth — direct the user to the Browse Toolkits UI.` }] };
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
case "list_integration_connections": {
|
|
2033
|
-
const providerId = args.provider;
|
|
2034
|
-
const projectId = args.project_id || null;
|
|
2035
|
-
const provider = getProvider(providerId);
|
|
2036
|
-
if (!provider) {
|
|
2037
|
-
return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
|
|
2038
|
-
}
|
|
2039
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2040
|
-
if (!apiKey) {
|
|
2041
|
-
return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
|
|
2042
|
-
}
|
|
2043
|
-
const accounts = await provider.listConnectedAccounts(apiKey, "platform-agent");
|
|
2044
|
-
const result = accounts.map(a => ({
|
|
2045
|
-
id: a.id,
|
|
2046
|
-
appName: a.appName,
|
|
2047
|
-
appId: a.appId,
|
|
2048
|
-
status: a.status,
|
|
2049
|
-
createdAt: a.createdAt,
|
|
2050
|
-
}));
|
|
2051
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
case "disconnect_integration_app": {
|
|
2055
|
-
const providerId = args.provider;
|
|
2056
|
-
const projectId = args.project_id || null;
|
|
2057
|
-
const provider = getProvider(providerId);
|
|
2058
|
-
if (!provider) {
|
|
2059
|
-
return { content: [{ type: "text", text: `Unknown integration provider: ${providerId}` }], isError: true };
|
|
2060
|
-
}
|
|
2061
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2062
|
-
if (!apiKey) {
|
|
2063
|
-
return { content: [{ type: "text", text: `${provider.name} API key not configured` }], isError: true };
|
|
2064
|
-
}
|
|
2065
|
-
const success = await provider.disconnect(apiKey, args.connection_id);
|
|
2066
|
-
return { content: [{ type: "text", text: success ? `Disconnected successfully` : `Failed to disconnect` }], isError: !success };
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
case "list_integration_configs": {
|
|
2070
|
-
const providerId = args.provider;
|
|
2071
|
-
const projectId = args.project_id || null;
|
|
2072
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2073
|
-
if (!apiKey) {
|
|
2074
|
-
return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
if (providerId === "agentdojo") {
|
|
2078
|
-
const servers = await listAgentDojoServers(apiKey, true);
|
|
2079
|
-
const result = servers.map(s => ({
|
|
2080
|
-
id: s.id,
|
|
2081
|
-
name: s.name,
|
|
2082
|
-
slug: s.slug,
|
|
2083
|
-
url: s.url,
|
|
2084
|
-
toolsCount: s.tools?.length || 0,
|
|
2085
|
-
}));
|
|
2086
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2087
|
-
} else if (providerId === "composio") {
|
|
2088
|
-
const servers = await listComposioServers(apiKey);
|
|
2089
|
-
const result = servers.map(s => ({
|
|
2090
|
-
id: s.id,
|
|
2091
|
-
name: s.name,
|
|
2092
|
-
url: s.mcpUrl,
|
|
2093
|
-
toolkits: s.toolkits,
|
|
2094
|
-
}));
|
|
2095
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2096
|
-
}
|
|
2097
|
-
return { content: [{ type: "text", text: `Config listing not supported for provider: ${providerId}` }], isError: true };
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
case "create_integration_config": {
|
|
2101
|
-
const providerId = args.provider;
|
|
2102
|
-
const projectId = args.project_id || null;
|
|
2103
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2104
|
-
if (!apiKey) {
|
|
2105
|
-
return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
if (providerId === "agentdojo") {
|
|
2109
|
-
const server = await createAgentDojoServer(apiKey, args.name, [args.toolkit_slug]);
|
|
2110
|
-
if (!server) {
|
|
2111
|
-
return { content: [{ type: "text", text: "Failed to create MCP config on AgentDojo" }], isError: true };
|
|
2112
|
-
}
|
|
2113
|
-
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.` }] };
|
|
2114
|
-
} else if (providerId === "composio") {
|
|
2115
|
-
// For Composio, we need the authConfigId for the toolkit
|
|
2116
|
-
const authConfigId = await getComposioAuthConfig(apiKey, args.toolkit_slug);
|
|
2117
|
-
if (!authConfigId) {
|
|
2118
|
-
return { content: [{ type: "text", text: `No auth config found for toolkit "${args.toolkit_slug}". Make sure the app is connected first.` }], isError: true };
|
|
2119
|
-
}
|
|
2120
|
-
const server = await createComposioServer(apiKey, args.name, [authConfigId]);
|
|
2121
|
-
if (!server) {
|
|
2122
|
-
return { content: [{ type: "text", text: "Failed to create MCP config on Composio" }], isError: true };
|
|
2123
|
-
}
|
|
2124
|
-
// Create server instance for the user
|
|
2125
|
-
const userId = await getComposioUserForAuth(apiKey, authConfigId);
|
|
2126
|
-
if (userId) {
|
|
2127
|
-
await createComposioInstance(apiKey, server.id, userId);
|
|
2128
|
-
}
|
|
2129
|
-
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.` }] };
|
|
2130
|
-
}
|
|
2131
|
-
return { content: [{ type: "text", text: `Config creation not supported for provider: ${providerId}` }], isError: true };
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
case "add_integration_config_locally": {
|
|
2135
|
-
const providerId = args.provider;
|
|
2136
|
-
const projectId = args.project_id || null;
|
|
2137
|
-
const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
|
|
2138
|
-
if (!apiKey) {
|
|
2139
|
-
return { content: [{ type: "text", text: `${providerId} API key not configured` }], isError: true };
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
const effectiveProjectId = projectId && projectId !== "unassigned" ? projectId : null;
|
|
2143
|
-
|
|
2144
|
-
if (providerId === "agentdojo") {
|
|
2145
|
-
const server = await getAgentDojoServer(apiKey, args.config_id);
|
|
2146
|
-
if (!server) {
|
|
2147
|
-
return { content: [{ type: "text", text: `Config not found on AgentDojo: ${args.config_id}` }], isError: true };
|
|
2148
|
-
}
|
|
2149
|
-
// Check if already exists locally
|
|
2150
|
-
const existing = McpServerDB.findAllLight().find(
|
|
2151
|
-
s => s.source === "agentdojo" && s.project_id === effectiveProjectId && s.url?.endsWith(`/${server.slug}`)
|
|
2152
|
-
);
|
|
2153
|
-
if (existing) {
|
|
2154
|
-
return { content: [{ type: "text", text: `Server "${server.name}" already exists locally (ID: ${existing.id})` }] };
|
|
2155
|
-
}
|
|
2156
|
-
const localServer = McpServerDB.create({
|
|
2157
|
-
id: generateId(),
|
|
2158
|
-
name: server.name,
|
|
2159
|
-
type: "http",
|
|
2160
|
-
package: null,
|
|
2161
|
-
command: null,
|
|
2162
|
-
args: null,
|
|
2163
|
-
pip_module: null,
|
|
2164
|
-
env: {},
|
|
2165
|
-
url: server.url,
|
|
2166
|
-
headers: { "X-API-Key": apiKey },
|
|
2167
|
-
source: "agentdojo",
|
|
2168
|
-
project_id: effectiveProjectId,
|
|
2169
|
-
});
|
|
2170
|
-
return { content: [{ type: "text", text: `Server "${server.name}" added locally (ID: ${localServer.id}). Use assign_mcp_server_to_agent to give agents access.` }] };
|
|
2171
|
-
} else if (providerId === "composio") {
|
|
2172
|
-
// Fetch config details from Composio
|
|
2173
|
-
const res = await fetch(`https://backend.composio.dev/api/v3/mcp/${args.config_id}`, {
|
|
2174
|
-
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
|
|
2175
|
-
});
|
|
2176
|
-
if (!res.ok) {
|
|
2177
|
-
return { content: [{ type: "text", text: `Config not found on Composio: ${args.config_id}` }], isError: true };
|
|
2178
|
-
}
|
|
2179
|
-
const data = await res.json();
|
|
2180
|
-
const configName = data.name || `composio-${args.config_id.slice(0, 8)}`;
|
|
2181
|
-
const mcpUrl = data.mcp_url;
|
|
2182
|
-
if (!mcpUrl) {
|
|
2183
|
-
return { content: [{ type: "text", text: "Config does not have an MCP URL" }], isError: true };
|
|
2184
|
-
}
|
|
2185
|
-
// Check if already exists locally
|
|
2186
|
-
const existing = McpServerDB.findAllLight().find(
|
|
2187
|
-
s => s.source === "composio" && s.url?.includes(args.config_id)
|
|
2188
|
-
);
|
|
2189
|
-
if (existing) {
|
|
2190
|
-
return { content: [{ type: "text", text: `Server "${configName}" already exists locally (ID: ${existing.id})` }] };
|
|
2191
|
-
}
|
|
2192
|
-
// Get user_id for URL auth
|
|
2193
|
-
const authConfigIds = data.auth_config_ids || [];
|
|
2194
|
-
let userId: string | null = null;
|
|
2195
|
-
if (authConfigIds.length > 0) {
|
|
2196
|
-
userId = await getComposioUserForAuth(apiKey, authConfigIds[0]);
|
|
2197
|
-
}
|
|
2198
|
-
const mcpUrlWithUser = userId ? `${mcpUrl}?user_id=${encodeURIComponent(userId)}` : mcpUrl;
|
|
2199
|
-
const localServer = McpServerDB.create({
|
|
2200
|
-
id: generateId(),
|
|
2201
|
-
name: configName,
|
|
2202
|
-
type: "http",
|
|
2203
|
-
package: null,
|
|
2204
|
-
command: null,
|
|
2205
|
-
args: null,
|
|
2206
|
-
pip_module: null,
|
|
2207
|
-
env: {},
|
|
2208
|
-
url: mcpUrlWithUser,
|
|
2209
|
-
headers: { "x-api-key": apiKey },
|
|
2210
|
-
source: "composio",
|
|
2211
|
-
project_id: effectiveProjectId,
|
|
2212
|
-
});
|
|
2213
|
-
return { content: [{ type: "text", text: `Server "${configName}" added locally (ID: ${localServer.id}). Use assign_mcp_server_to_agent to give agents access.` }] };
|
|
2214
|
-
}
|
|
2215
|
-
return { content: [{ type: "text", text: `Local add not supported for provider: ${providerId}` }], isError: true };
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
case "set_provider_key": {
|
|
2219
|
-
const providerId = args.provider_id;
|
|
2220
|
-
if (!PROVIDERS[providerId as keyof typeof PROVIDERS]) {
|
|
2221
|
-
return { content: [{ type: "text", text: `Unknown provider: ${providerId}. Use list_providers or list_integration_providers to see available providers.` }], isError: true };
|
|
2222
|
-
}
|
|
2223
|
-
if (!args.key) {
|
|
2224
|
-
return { content: [{ type: "text", text: "API key is required" }], isError: true };
|
|
2225
|
-
}
|
|
2226
|
-
const result = await ProviderKeys.save(providerId, args.key, args.project_id || null, null);
|
|
2227
|
-
if (!result.success) {
|
|
2228
|
-
return { content: [{ type: "text", text: `Failed to save key: ${result.error}` }], isError: true };
|
|
2229
|
-
}
|
|
2230
|
-
const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
|
|
2231
|
-
return { content: [{ type: "text", text: `API key saved for ${provider.name}.` }] };
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
case "start_mcp_server": {
|
|
2235
|
-
const server = McpServerDB.findById(args.server_id);
|
|
2236
|
-
if (!server) {
|
|
2237
|
-
return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
|
|
2238
|
-
}
|
|
2239
|
-
if (server.status === "running") {
|
|
2240
|
-
return { content: [{ type: "text", text: `MCP server "${server.name}" is already running` }] };
|
|
2241
|
-
}
|
|
2242
|
-
try {
|
|
2243
|
-
const port = process.env.PORT || "4280";
|
|
2244
|
-
const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/start`, { method: "POST" });
|
|
2245
|
-
const data = await res.json() as Record<string, unknown>;
|
|
2246
|
-
if (!res.ok) {
|
|
2247
|
-
return { content: [{ type: "text", text: `Failed to start: ${data.error || "unknown error"}` }], isError: true };
|
|
2248
|
-
}
|
|
2249
|
-
return { content: [{ type: "text", text: `MCP server "${server.name}" started. ${data.message || ""}` }] };
|
|
2250
|
-
} catch (err) {
|
|
2251
|
-
return { content: [{ type: "text", text: `Failed to start MCP server: ${err}` }], isError: true };
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
case "stop_mcp_server": {
|
|
2256
|
-
const server = McpServerDB.findById(args.server_id);
|
|
2257
|
-
if (!server) {
|
|
2258
|
-
return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
|
|
2259
|
-
}
|
|
2260
|
-
if (server.status !== "running") {
|
|
2261
|
-
return { content: [{ type: "text", text: `MCP server "${server.name}" is not running` }] };
|
|
2262
|
-
}
|
|
2263
|
-
try {
|
|
2264
|
-
const port = process.env.PORT || "4280";
|
|
2265
|
-
const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/stop`, { method: "POST" });
|
|
2266
|
-
const data = await res.json() as Record<string, unknown>;
|
|
2267
|
-
if (!res.ok) {
|
|
2268
|
-
return { content: [{ type: "text", text: `Failed to stop: ${data.error || "unknown error"}` }], isError: true };
|
|
2269
|
-
}
|
|
2270
|
-
return { content: [{ type: "text", text: `MCP server "${server.name}" stopped.` }] };
|
|
2271
|
-
} catch (err) {
|
|
2272
|
-
return { content: [{ type: "text", text: `Failed to stop MCP server: ${err}` }], isError: true };
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
case "get_analytics": {
|
|
2277
|
-
const stats = TelemetryDB.getStats({
|
|
2278
|
-
agentId: args.agent_id || undefined,
|
|
2279
|
-
projectId: args.project_id || undefined,
|
|
2280
|
-
since: args.since || undefined,
|
|
2281
|
-
});
|
|
2282
|
-
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
case "get_usage_by_agent": {
|
|
2286
|
-
const usage = TelemetryDB.getUsage({
|
|
2287
|
-
project_id: args.project_id || undefined,
|
|
2288
|
-
since: args.since || undefined,
|
|
2289
|
-
group_by: "agent",
|
|
2290
|
-
});
|
|
2291
|
-
// Enrich with agent names
|
|
2292
|
-
const enriched = usage.map(u => {
|
|
2293
|
-
const agent = AgentDB.findById(u.agent_id || "");
|
|
2294
|
-
return { ...u, agent_name: agent?.name || u.agent_id };
|
|
2295
|
-
});
|
|
2296
|
-
return { content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }] };
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
default:
|
|
2300
|
-
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
2301
|
-
}
|
|
2302
|
-
} catch (err) {
|
|
2303
|
-
return { content: [{ type: "text", text: `Tool execution error: ${err}` }], isError: true };
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// Main MCP request handler
|
|
2308
|
-
export async function handlePlatformMcpRequest(req: Request): Promise<Response> {
|
|
2309
|
-
const corsHeaders = {
|
|
2310
|
-
"Access-Control-Allow-Origin": "*",
|
|
2311
|
-
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
2312
|
-
"Access-Control-Allow-Headers": "Content-Type, Mcp-Session-Id",
|
|
2313
|
-
};
|
|
2314
|
-
|
|
2315
|
-
if (req.method === "OPTIONS") {
|
|
2316
|
-
return new Response(null, { headers: corsHeaders });
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
let body: JsonRpcRequest;
|
|
2320
|
-
try {
|
|
2321
|
-
body = await req.json() as JsonRpcRequest;
|
|
2322
|
-
} catch {
|
|
2323
|
-
return new Response(JSON.stringify({
|
|
2324
|
-
jsonrpc: "2.0",
|
|
2325
|
-
id: 0,
|
|
2326
|
-
error: { code: -32700, message: "Parse error" },
|
|
2327
|
-
}), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
const { method, params, id } = body;
|
|
2331
|
-
|
|
2332
|
-
let result: unknown;
|
|
2333
|
-
let error: { code: number; message: string } | undefined;
|
|
2334
|
-
|
|
2335
|
-
switch (method) {
|
|
2336
|
-
case "initialize": {
|
|
2337
|
-
result = {
|
|
2338
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
2339
|
-
capabilities: {
|
|
2340
|
-
tools: { listChanged: false },
|
|
2341
|
-
},
|
|
2342
|
-
serverInfo: {
|
|
2343
|
-
name: "apteva-platform",
|
|
2344
|
-
version: "1.0.0",
|
|
2345
|
-
},
|
|
2346
|
-
instructions: `This MCP server controls the Apteva AI agent management platform.
|
|
2347
|
-
|
|
2348
|
-
You can manage:
|
|
2349
|
-
- 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, agents/multi-agent). The "agents" feature enables multi-agent communication — agents can call and delegate tasks to peer agents in the same project. Use update_agent to assign/remove MCP servers (add_mcp_servers, remove_mcp_servers) and skills (add_skills, remove_skills).
|
|
2350
|
-
- TASKS: List, create, delete, and execute tasks. Use list_tasks for a project-wide overview of all tasks across all running agents. Use list_agent_tasks for tasks on a specific agent. Agents must have the tasks feature enabled. Tools: list_tasks, list_agent_tasks, create_agent_task, delete_agent_task, execute_agent_task.
|
|
2351
|
-
- 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).
|
|
2352
|
-
- 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. Use start_mcp_server/stop_mcp_server to activate/deactivate servers.
|
|
2353
|
-
- 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.
|
|
2354
|
-
- ANALYTICS: View token usage, costs, and activity stats across agents. Tools: get_analytics (summary totals), get_usage_by_agent (per-agent breakdown). Both support filtering by agent, project, or time range.
|
|
2355
|
-
- PROVIDERS: View and configure API keys for LLM and integration providers. Tools: list_providers, set_provider_key. If a provider key is missing, ask the user for it and save with set_provider_key.
|
|
2356
|
-
- TESTS: Create behavior-driven tests for agent workflows. Describe what the agent should do in natural language — the system auto-selects the best agent and generates a test message. An LLM judge evaluates the result and scores 1-10. Tools: list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
|
|
2357
|
-
- 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.
|
|
2358
|
-
- 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.
|
|
2359
|
-
|
|
2360
|
-
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.
|
|
2361
|
-
|
|
2362
|
-
Typical workflow: list_providers → create_agent → update_agent (add_mcp_servers/add_skills) → start_agent.
|
|
2363
|
-
Task workflow: list_tasks (project-wide overview) or list_agent_tasks (single agent) → create_agent_task (schedule work) → execute_agent_task (run immediately).
|
|
2364
|
-
Integration workflow: list_integration_providers → if no key, ask user and set_provider_key → list_integration_apps (browse) → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → start_mcp_server → update_agent (add_mcp_servers).
|
|
2365
|
-
Subscription workflow: list_trigger_providers → list_trigger_types (pick trigger) → list_integration_connections (pick account) → create_subscription (link trigger to agent).
|
|
2366
|
-
Test workflow: create_test (describe behavior) → run_test (AI picks agent, generates message, judges result) → get_test_results.
|
|
2367
|
-
Always use list_providers first to check which providers have API keys before creating agents.`,
|
|
2368
|
-
};
|
|
2369
|
-
break;
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
case "notifications/initialized": {
|
|
2373
|
-
// Acknowledgement - no response needed for notifications, but since this is HTTP we return ok
|
|
2374
|
-
result = {};
|
|
2375
|
-
break;
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
|
-
case "tools/list": {
|
|
2379
|
-
result = { tools: getPlatformTools() };
|
|
2380
|
-
break;
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
case "tools/call": {
|
|
2384
|
-
const { name, arguments: args } = params as { name: string; arguments: Record<string, any> };
|
|
2385
|
-
result = await executeTool(name, args || {});
|
|
2386
|
-
break;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
default: {
|
|
2390
|
-
error = { code: -32601, message: `Method not found: ${method}` };
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
const response: JsonRpcResponse = {
|
|
2395
|
-
jsonrpc: "2.0",
|
|
2396
|
-
id: id || 0,
|
|
2397
|
-
...(error ? { error } : { result }),
|
|
2398
|
-
};
|
|
2399
|
-
|
|
2400
|
-
return new Response(JSON.stringify(response), {
|
|
2401
|
-
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
2402
|
-
});
|
|
2403
|
-
}
|