@desplega.ai/agent-swarm 1.52.1 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/openapi.json +1517 -488
  2. package/package.json +5 -2
  3. package/src/be/db.ts +530 -0
  4. package/src/be/events.ts +322 -0
  5. package/src/be/migrations/021_events.sql +24 -0
  6. package/src/be/migrations/022_context_usage.sql +34 -0
  7. package/src/be/migrations/023_mcp_servers.sql +44 -0
  8. package/src/commands/runner.ts +348 -1
  9. package/src/http/context.ts +118 -0
  10. package/src/http/events.ts +188 -0
  11. package/src/http/index.ts +6 -0
  12. package/src/http/mcp-servers.ts +364 -0
  13. package/src/http/tasks.ts +33 -0
  14. package/src/linear/outbound.ts +8 -1
  15. package/src/linear/sync.ts +3 -0
  16. package/src/oauth/ensure-token.ts +50 -0
  17. package/src/prompts/base-prompt.ts +7 -0
  18. package/src/providers/claude-adapter.ts +156 -15
  19. package/src/providers/pi-mono-adapter.ts +68 -0
  20. package/src/providers/pi-mono-extension.ts +56 -2
  21. package/src/providers/pi-mono-mcp-client.ts +10 -1
  22. package/src/providers/types.ts +14 -1
  23. package/src/server.ts +19 -0
  24. package/src/tests/context-window.test.ts +66 -0
  25. package/src/tests/ensure-token.test.ts +170 -0
  26. package/src/tests/events-db.test.ts +314 -0
  27. package/src/tests/events-http.test.ts +267 -0
  28. package/src/tests/prompt-template-remaining.test.ts +5 -5
  29. package/src/tests/tool-annotations.test.ts +2 -2
  30. package/src/tests/vcs-tracking.test.ts +176 -0
  31. package/src/tests/workflow-executors.test.ts +8 -1
  32. package/src/tools/mcp-servers/index.ts +7 -0
  33. package/src/tools/mcp-servers/mcp-server-create.ts +138 -0
  34. package/src/tools/mcp-servers/mcp-server-delete.ts +72 -0
  35. package/src/tools/mcp-servers/mcp-server-get.ts +80 -0
  36. package/src/tools/mcp-servers/mcp-server-install.ts +110 -0
  37. package/src/tools/mcp-servers/mcp-server-list.ts +67 -0
  38. package/src/tools/mcp-servers/mcp-server-uninstall.ts +71 -0
  39. package/src/tools/mcp-servers/mcp-server-update.ts +120 -0
  40. package/src/tools/tool-config.ts +9 -0
  41. package/src/types.ts +153 -0
  42. package/src/utils/context-window.ts +41 -0
  43. package/src/workflows/executors/base.ts +9 -1
@@ -0,0 +1,72 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { deleteMcpServer, getAgentById, getMcpServerById } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerDeleteTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-delete",
9
+ {
10
+ title: "Delete MCP Server",
11
+ annotations: { destructiveHint: true },
12
+ description: "Delete an MCP server definition. Only the owning agent or lead can delete.",
13
+ inputSchema: z.object({
14
+ id: z.string().describe("ID of the MCP server to delete"),
15
+ }),
16
+ outputSchema: z.object({
17
+ yourAgentId: z.string().uuid().optional(),
18
+ success: z.boolean(),
19
+ message: z.string(),
20
+ }),
21
+ },
22
+ async (args, requestInfo, _meta) => {
23
+ if (!requestInfo.agentId) {
24
+ return {
25
+ content: [{ type: "text", text: "Agent ID not found." }],
26
+ structuredContent: { success: false, message: "Agent ID not found." },
27
+ };
28
+ }
29
+
30
+ const existing = getMcpServerById(args.id);
31
+ if (!existing) {
32
+ return {
33
+ content: [{ type: "text", text: "MCP server not found." }],
34
+ structuredContent: {
35
+ yourAgentId: requestInfo.agentId,
36
+ success: false,
37
+ message: "MCP server not found.",
38
+ },
39
+ };
40
+ }
41
+
42
+ const agent = getAgentById(requestInfo.agentId);
43
+ if (existing.ownerAgentId !== requestInfo.agentId && !agent?.isLead) {
44
+ return {
45
+ content: [
46
+ { type: "text", text: "Only the owning agent or lead can delete this MCP server." },
47
+ ],
48
+ structuredContent: {
49
+ yourAgentId: requestInfo.agentId,
50
+ success: false,
51
+ message: "Permission denied.",
52
+ },
53
+ };
54
+ }
55
+
56
+ const deleted = deleteMcpServer(args.id);
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed.",
62
+ },
63
+ ],
64
+ structuredContent: {
65
+ yourAgentId: requestInfo.agentId,
66
+ success: deleted,
67
+ message: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed.",
68
+ },
69
+ };
70
+ },
71
+ );
72
+ };
@@ -0,0 +1,80 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getMcpServerById, getMcpServerByName } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerGetTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-get",
9
+ {
10
+ title: "Get MCP Server",
11
+ annotations: { destructiveHint: false },
12
+ description:
13
+ "Get MCP server details by ID or name. Name resolution uses scope cascade: agent > swarm > global.",
14
+ inputSchema: z.object({
15
+ id: z.string().optional().describe("MCP server ID"),
16
+ name: z.string().optional().describe("MCP server name (resolved with scope cascade)"),
17
+ }),
18
+ outputSchema: z.object({
19
+ yourAgentId: z.string().uuid().optional(),
20
+ success: z.boolean(),
21
+ message: z.string(),
22
+ server: z.any().optional(),
23
+ }),
24
+ },
25
+ async (args, requestInfo, _meta) => {
26
+ if (!args.id && !args.name) {
27
+ return {
28
+ content: [{ type: "text", text: "Provide id or name." }],
29
+ structuredContent: {
30
+ yourAgentId: requestInfo.agentId,
31
+ success: false,
32
+ message: "Provide id or name.",
33
+ },
34
+ };
35
+ }
36
+
37
+ let mcpServer = null;
38
+
39
+ if (args.id) {
40
+ mcpServer = getMcpServerById(args.id);
41
+ } else if (args.name && requestInfo.agentId) {
42
+ // Scope cascade: agent > swarm > global
43
+ mcpServer =
44
+ getMcpServerByName(args.name, "agent", requestInfo.agentId) ||
45
+ getMcpServerByName(args.name, "swarm", null) ||
46
+ getMcpServerByName(args.name, "global", null);
47
+ } else if (args.name) {
48
+ mcpServer =
49
+ getMcpServerByName(args.name, "swarm", null) ||
50
+ getMcpServerByName(args.name, "global", null);
51
+ }
52
+
53
+ if (!mcpServer) {
54
+ return {
55
+ content: [{ type: "text", text: "MCP server not found." }],
56
+ structuredContent: {
57
+ yourAgentId: requestInfo.agentId,
58
+ success: false,
59
+ message: "MCP server not found.",
60
+ },
61
+ };
62
+ }
63
+
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: `MCP server "${mcpServer.name}" (${mcpServer.id}): ${mcpServer.transport} transport, scope=${mcpServer.scope}`,
69
+ },
70
+ ],
71
+ structuredContent: {
72
+ yourAgentId: requestInfo.agentId,
73
+ success: true,
74
+ message: `Found MCP server "${mcpServer.name}".`,
75
+ server: mcpServer,
76
+ },
77
+ };
78
+ },
79
+ );
80
+ };
@@ -0,0 +1,110 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById, getMcpServerById, installMcpServer } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerInstallTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-install",
9
+ {
10
+ title: "Install MCP Server",
11
+ annotations: { destructiveHint: false },
12
+ description:
13
+ "Install an MCP server for an agent. Self-install is always allowed; cross-agent install requires lead.",
14
+ inputSchema: z.object({
15
+ mcpServerId: z.string().describe("ID of the MCP server to install"),
16
+ agentId: z
17
+ .string()
18
+ .optional()
19
+ .describe("Target agent (default: calling agent). Lead can install for others."),
20
+ }),
21
+ outputSchema: z.object({
22
+ yourAgentId: z.string().uuid().optional(),
23
+ success: z.boolean(),
24
+ message: z.string(),
25
+ installation: z.any().optional(),
26
+ }),
27
+ },
28
+ async (args, requestInfo, _meta) => {
29
+ if (!requestInfo.agentId) {
30
+ return {
31
+ content: [{ type: "text", text: "Agent ID not found." }],
32
+ structuredContent: { success: false, message: "Agent ID not found." },
33
+ };
34
+ }
35
+
36
+ const targetAgentId = args.agentId ?? requestInfo.agentId;
37
+
38
+ // Cross-agent install requires lead
39
+ if (targetAgentId !== requestInfo.agentId) {
40
+ const agent = getAgentById(requestInfo.agentId);
41
+ if (!agent?.isLead) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: "Only leads can install MCP servers for other agents.",
47
+ },
48
+ ],
49
+ structuredContent: {
50
+ yourAgentId: requestInfo.agentId,
51
+ success: false,
52
+ message: "Permission denied.",
53
+ },
54
+ };
55
+ }
56
+ }
57
+
58
+ const mcpServer = getMcpServerById(args.mcpServerId);
59
+ if (!mcpServer) {
60
+ return {
61
+ content: [{ type: "text", text: "MCP server not found." }],
62
+ structuredContent: {
63
+ yourAgentId: requestInfo.agentId,
64
+ success: false,
65
+ message: "MCP server not found.",
66
+ },
67
+ };
68
+ }
69
+
70
+ if (!mcpServer.isEnabled) {
71
+ return {
72
+ content: [{ type: "text", text: "MCP server is disabled." }],
73
+ structuredContent: {
74
+ yourAgentId: requestInfo.agentId,
75
+ success: false,
76
+ message: "MCP server is disabled.",
77
+ },
78
+ };
79
+ }
80
+
81
+ try {
82
+ const installation = installMcpServer(targetAgentId, args.mcpServerId);
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: `Installed MCP server "${mcpServer.name}" for agent ${targetAgentId}.`,
88
+ },
89
+ ],
90
+ structuredContent: {
91
+ yourAgentId: requestInfo.agentId,
92
+ success: true,
93
+ message: `Installed MCP server "${mcpServer.name}".`,
94
+ installation,
95
+ },
96
+ };
97
+ } catch (error) {
98
+ const message = error instanceof Error ? error.message : "Unknown error";
99
+ return {
100
+ content: [{ type: "text", text: `Failed: ${message}` }],
101
+ structuredContent: {
102
+ yourAgentId: requestInfo.agentId,
103
+ success: false,
104
+ message: `Failed: ${message}`,
105
+ },
106
+ };
107
+ }
108
+ },
109
+ );
110
+ };
@@ -0,0 +1,67 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentMcpServers, listMcpServers } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerListTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-list",
9
+ {
10
+ title: "List MCP Servers",
11
+ annotations: { destructiveHint: false },
12
+ description:
13
+ "List MCP servers with optional filters. Use installedOnly to see servers installed for the calling agent.",
14
+ inputSchema: z.object({
15
+ scope: z.enum(["global", "swarm", "agent"]).optional().describe("Filter by scope"),
16
+ transport: z.enum(["stdio", "http", "sse"]).optional().describe("Filter by transport type"),
17
+ search: z.string().optional().describe("Search by name or description"),
18
+ installedOnly: z
19
+ .boolean()
20
+ .optional()
21
+ .describe("Only show servers installed for the calling agent"),
22
+ }),
23
+ outputSchema: z.object({
24
+ yourAgentId: z.string().uuid().optional(),
25
+ success: z.boolean(),
26
+ message: z.string(),
27
+ servers: z.array(z.any()),
28
+ total: z.number(),
29
+ }),
30
+ },
31
+ async (args, requestInfo, _meta) => {
32
+ try {
33
+ const servers =
34
+ args.installedOnly && requestInfo.agentId
35
+ ? getAgentMcpServers(requestInfo.agentId)
36
+ : listMcpServers({
37
+ scope: args.scope,
38
+ transport: args.transport,
39
+ search: args.search,
40
+ });
41
+
42
+ return {
43
+ content: [{ type: "text", text: `Found ${servers.length} MCP server(s).` }],
44
+ structuredContent: {
45
+ yourAgentId: requestInfo.agentId,
46
+ success: true,
47
+ message: `Found ${servers.length} MCP server(s).`,
48
+ servers,
49
+ total: servers.length,
50
+ },
51
+ };
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : "Unknown error";
54
+ return {
55
+ content: [{ type: "text", text: `Failed: ${message}` }],
56
+ structuredContent: {
57
+ yourAgentId: requestInfo.agentId,
58
+ success: false,
59
+ message: `Failed: ${message}`,
60
+ servers: [],
61
+ total: 0,
62
+ },
63
+ };
64
+ }
65
+ },
66
+ );
67
+ };
@@ -0,0 +1,71 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById, uninstallMcpServer } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerUninstallTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-uninstall",
9
+ {
10
+ title: "Uninstall MCP Server",
11
+ annotations: { destructiveHint: true },
12
+ description:
13
+ "Uninstall an MCP server from an agent. Self-uninstall is always allowed; cross-agent requires lead.",
14
+ inputSchema: z.object({
15
+ mcpServerId: z.string().describe("ID of the MCP server to uninstall"),
16
+ agentId: z.string().optional().describe("Target agent (default: calling agent)"),
17
+ }),
18
+ outputSchema: z.object({
19
+ yourAgentId: z.string().uuid().optional(),
20
+ success: z.boolean(),
21
+ message: z.string(),
22
+ }),
23
+ },
24
+ async (args, requestInfo, _meta) => {
25
+ if (!requestInfo.agentId) {
26
+ return {
27
+ content: [{ type: "text", text: "Agent ID not found." }],
28
+ structuredContent: { success: false, message: "Agent ID not found." },
29
+ };
30
+ }
31
+
32
+ const targetAgentId = args.agentId ?? requestInfo.agentId;
33
+
34
+ if (targetAgentId !== requestInfo.agentId) {
35
+ const agent = getAgentById(requestInfo.agentId);
36
+ if (!agent?.isLead) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: "Only leads can uninstall MCP servers for other agents.",
42
+ },
43
+ ],
44
+ structuredContent: {
45
+ yourAgentId: requestInfo.agentId,
46
+ success: false,
47
+ message: "Permission denied.",
48
+ },
49
+ };
50
+ }
51
+ }
52
+
53
+ const removed = uninstallMcpServer(targetAgentId, args.mcpServerId);
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: removed ? "MCP server uninstalled." : "MCP server was not installed.",
59
+ },
60
+ ],
61
+ structuredContent: {
62
+ yourAgentId: requestInfo.agentId,
63
+ success: removed,
64
+ message: removed
65
+ ? "MCP server uninstalled."
66
+ : "MCP server was not installed for this agent.",
67
+ },
68
+ };
69
+ },
70
+ );
71
+ };
@@ -0,0 +1,120 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById, getMcpServerById, updateMcpServer } from "@/be/db";
4
+ import { createToolRegistrar } from "@/tools/utils";
5
+
6
+ export const registerMcpServerUpdateTool = (server: McpServer) => {
7
+ createToolRegistrar(server)(
8
+ "mcp-server-update",
9
+ {
10
+ title: "Update MCP Server",
11
+ annotations: { destructiveHint: false },
12
+ description: "Update an MCP server's configuration. Only the owner or lead can update.",
13
+ inputSchema: z.object({
14
+ id: z.string().describe("ID of the MCP server to update"),
15
+ name: z.string().optional().describe("New name"),
16
+ description: z.string().optional().describe("New description"),
17
+ transport: z.enum(["stdio", "http", "sse"]).optional().describe("New transport type"),
18
+ command: z.string().optional().describe("New command (stdio)"),
19
+ args: z.string().optional().describe("New JSON array of arguments (stdio)"),
20
+ url: z.string().optional().describe("New URL (http/sse)"),
21
+ headers: z.string().optional().describe("New JSON object of non-secret headers"),
22
+ envConfigKeys: z.string().optional().describe("New env config key mappings"),
23
+ headerConfigKeys: z.string().optional().describe("New header config key mappings"),
24
+ isEnabled: z.boolean().optional().describe("Toggle enabled/disabled"),
25
+ }),
26
+ outputSchema: z.object({
27
+ yourAgentId: z.string().uuid().optional(),
28
+ success: z.boolean(),
29
+ message: z.string(),
30
+ server: z.any().optional(),
31
+ }),
32
+ },
33
+ async (args, requestInfo, _meta) => {
34
+ if (!requestInfo.agentId) {
35
+ return {
36
+ content: [{ type: "text", text: "Agent ID not found." }],
37
+ structuredContent: { success: false, message: "Agent ID not found." },
38
+ };
39
+ }
40
+
41
+ try {
42
+ const existing = getMcpServerById(args.id);
43
+ if (!existing) {
44
+ return {
45
+ content: [{ type: "text", text: "MCP server not found." }],
46
+ structuredContent: {
47
+ yourAgentId: requestInfo.agentId,
48
+ success: false,
49
+ message: "MCP server not found.",
50
+ },
51
+ };
52
+ }
53
+
54
+ // Only owner or lead can update
55
+ const agent = getAgentById(requestInfo.agentId);
56
+ if (existing.ownerAgentId !== requestInfo.agentId && !agent?.isLead) {
57
+ return {
58
+ content: [
59
+ { type: "text", text: "Only the owning agent or lead can update this MCP server." },
60
+ ],
61
+ structuredContent: {
62
+ yourAgentId: requestInfo.agentId,
63
+ success: false,
64
+ message: "Permission denied.",
65
+ },
66
+ };
67
+ }
68
+
69
+ const updates: Parameters<typeof updateMcpServer>[1] = {};
70
+ if (args.name !== undefined) updates.name = args.name;
71
+ if (args.description !== undefined) updates.description = args.description;
72
+ if (args.transport !== undefined) updates.transport = args.transport;
73
+ if (args.command !== undefined) updates.command = args.command;
74
+ if (args.args !== undefined) updates.args = args.args;
75
+ if (args.url !== undefined) updates.url = args.url;
76
+ if (args.headers !== undefined) updates.headers = args.headers;
77
+ if (args.envConfigKeys !== undefined) updates.envConfigKeys = args.envConfigKeys;
78
+ if (args.headerConfigKeys !== undefined) updates.headerConfigKeys = args.headerConfigKeys;
79
+ if (args.isEnabled !== undefined) updates.isEnabled = args.isEnabled;
80
+
81
+ const updated = updateMcpServer(args.id, updates);
82
+ if (!updated) {
83
+ return {
84
+ content: [{ type: "text", text: "Failed to update MCP server." }],
85
+ structuredContent: {
86
+ yourAgentId: requestInfo.agentId,
87
+ success: false,
88
+ message: "Update failed.",
89
+ },
90
+ };
91
+ }
92
+
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: `Updated MCP server "${updated.name}" (v${updated.version})`,
98
+ },
99
+ ],
100
+ structuredContent: {
101
+ yourAgentId: requestInfo.agentId,
102
+ success: true,
103
+ message: `Updated MCP server "${updated.name}" to version ${updated.version}.`,
104
+ server: updated,
105
+ },
106
+ };
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : "Unknown error";
109
+ return {
110
+ content: [{ type: "text", text: `Failed: ${message}` }],
111
+ structuredContent: {
112
+ yourAgentId: requestInfo.agentId,
113
+ success: false,
114
+ message: `Failed: ${message}`,
115
+ },
116
+ };
117
+ }
118
+ },
119
+ );
120
+ };
@@ -131,6 +131,15 @@ export const DEFERRED_TOOLS = new Set([
131
131
  "skill-sync-remote",
132
132
  "skill-publish",
133
133
 
134
+ // MCP Servers (7)
135
+ "mcp-server-create",
136
+ "mcp-server-delete",
137
+ "mcp-server-get",
138
+ "mcp-server-install",
139
+ "mcp-server-list",
140
+ "mcp-server-uninstall",
141
+ "mcp-server-update",
142
+
134
143
  // Other (3)
135
144
  "cancel-task",
136
145
  "inject-learning",