@desplega.ai/agent-swarm 1.51.2 → 1.52.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 +131 -0
- package/openapi.json +767 -4
- package/package.json +3 -1
- package/src/be/db.ts +669 -0
- package/src/be/migrations/019_skills.sql +65 -0
- package/src/be/migrations/020_approval_requests.sql +41 -0
- package/src/be/migrations/runner.ts +4 -4
- package/src/be/skill-parser.ts +70 -0
- package/src/be/skill-sync.ts +106 -0
- package/src/commands/runner.ts +299 -52
- package/src/http/agents.ts +29 -0
- package/src/http/approval-requests.ts +310 -0
- package/src/http/config.ts +3 -3
- package/src/http/index.ts +26 -2
- package/src/http/poll.ts +15 -0
- package/src/http/skills.ts +479 -0
- package/src/http/tasks.ts +94 -0
- package/src/linear/outbound.ts +12 -12
- package/src/prompts/base-prompt.ts +8 -0
- package/src/providers/claude-adapter.ts +19 -3
- package/src/scheduler/scheduler.ts +24 -1
- package/src/server.ts +29 -0
- package/src/slack/blocks.ts +1 -1
- package/src/tests/approval-requests.test.ts +948 -0
- package/src/tests/skill-parser.test.ts +178 -0
- package/src/tests/skill-sync.test.ts +171 -0
- package/src/tests/slack-blocks.test.ts +3 -2
- package/src/tests/structured-output.test.ts +1 -0
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/tool-call-progress.test.ts +207 -0
- package/src/tests/tool-registrar-no-input.test.ts +114 -0
- package/src/tests/update-profile-auth.test.ts +1 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tools/request-human-input.ts +117 -0
- package/src/tools/skills/index.ts +11 -0
- package/src/tools/skills/skill-create.ts +105 -0
- package/src/tools/skills/skill-delete.ts +67 -0
- package/src/tools/skills/skill-get.ts +75 -0
- package/src/tools/skills/skill-install-remote.ts +152 -0
- package/src/tools/skills/skill-install.ts +101 -0
- package/src/tools/skills/skill-list.ts +77 -0
- package/src/tools/skills/skill-publish.ts +123 -0
- package/src/tools/skills/skill-search.ts +43 -0
- package/src/tools/skills/skill-sync-remote.ts +128 -0
- package/src/tools/skills/skill-uninstall.ts +60 -0
- package/src/tools/skills/skill-update.ts +128 -0
- package/src/tools/store-progress.ts +31 -0
- package/src/tools/templates.ts +28 -0
- package/src/tools/tool-config.ts +16 -0
- package/src/tools/utils.ts +9 -7
- package/src/types.ts +54 -0
- package/src/workflows/executors/human-in-the-loop.ts +273 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/recovery.ts +72 -0
- package/src/workflows/resume.ts +65 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById, getSkillById, installSkill } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillInstallTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-install",
|
|
9
|
+
{
|
|
10
|
+
title: "Install Skill",
|
|
11
|
+
annotations: { destructiveHint: false },
|
|
12
|
+
description: "Install/assign a skill to an agent. Leads can install for other agents.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
skillId: z.string().describe("ID of the skill to install"),
|
|
15
|
+
agentId: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Target agent (default: calling agent). Lead can install for others."),
|
|
19
|
+
}),
|
|
20
|
+
outputSchema: z.object({
|
|
21
|
+
yourAgentId: z.string().uuid().optional(),
|
|
22
|
+
success: z.boolean(),
|
|
23
|
+
message: z.string(),
|
|
24
|
+
agentSkill: z.any().optional(),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
async (args, requestInfo, _meta) => {
|
|
28
|
+
if (!requestInfo.agentId) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: "Agent ID not found." }],
|
|
31
|
+
structuredContent: { success: false, message: "Agent ID not found." },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const targetAgentId = args.agentId ?? requestInfo.agentId;
|
|
36
|
+
|
|
37
|
+
// If installing for another agent, must be lead
|
|
38
|
+
if (targetAgentId !== requestInfo.agentId) {
|
|
39
|
+
const agent = getAgentById(requestInfo.agentId);
|
|
40
|
+
if (!agent?.isLead) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: "Only leads can install skills for other agents." }],
|
|
43
|
+
structuredContent: {
|
|
44
|
+
yourAgentId: requestInfo.agentId,
|
|
45
|
+
success: false,
|
|
46
|
+
message: "Permission denied.",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const skill = getSkillById(args.skillId);
|
|
53
|
+
if (!skill) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: "Skill not found." }],
|
|
56
|
+
structuredContent: {
|
|
57
|
+
yourAgentId: requestInfo.agentId,
|
|
58
|
+
success: false,
|
|
59
|
+
message: "Skill not found.",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!skill.isEnabled) {
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: "Skill is disabled." }],
|
|
67
|
+
structuredContent: {
|
|
68
|
+
yourAgentId: requestInfo.agentId,
|
|
69
|
+
success: false,
|
|
70
|
+
message: "Skill is disabled.",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const agentSkill = installSkill(targetAgentId, args.skillId);
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{ type: "text", text: `Installed skill "${skill.name}" for agent ${targetAgentId}.` },
|
|
80
|
+
],
|
|
81
|
+
structuredContent: {
|
|
82
|
+
yourAgentId: requestInfo.agentId,
|
|
83
|
+
success: true,
|
|
84
|
+
message: `Installed skill "${skill.name}".`,
|
|
85
|
+
agentSkill,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: `Failed: ${message}` }],
|
|
92
|
+
structuredContent: {
|
|
93
|
+
yourAgentId: requestInfo.agentId,
|
|
94
|
+
success: false,
|
|
95
|
+
message: `Failed: ${message}`,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentSkills, listSkills } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillListTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-list",
|
|
9
|
+
{
|
|
10
|
+
title: "List Skills",
|
|
11
|
+
annotations: { destructiveHint: false },
|
|
12
|
+
description: "List available skills with optional filters.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
type: z.enum(["remote", "personal"]).optional().describe("Filter by type"),
|
|
15
|
+
scope: z.enum(["global", "swarm", "agent"]).optional().describe("Filter by scope"),
|
|
16
|
+
agentId: z.string().optional().describe("Filter by owning agent"),
|
|
17
|
+
installedOnly: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Only show skills installed for calling agent"),
|
|
21
|
+
includeContent: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.default(false)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Include full content (default false)"),
|
|
26
|
+
}),
|
|
27
|
+
outputSchema: z.object({
|
|
28
|
+
yourAgentId: z.string().uuid().optional(),
|
|
29
|
+
success: z.boolean(),
|
|
30
|
+
message: z.string(),
|
|
31
|
+
skills: z.array(z.any()),
|
|
32
|
+
total: z.number(),
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
async (args, requestInfo, _meta) => {
|
|
36
|
+
try {
|
|
37
|
+
const skills =
|
|
38
|
+
args.installedOnly && requestInfo.agentId
|
|
39
|
+
? getAgentSkills(requestInfo.agentId)
|
|
40
|
+
: listSkills({
|
|
41
|
+
type: args.type,
|
|
42
|
+
scope: args.scope,
|
|
43
|
+
ownerAgentId: args.agentId,
|
|
44
|
+
includeContent: args.includeContent,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Strip content if not requested
|
|
48
|
+
const result = args.includeContent
|
|
49
|
+
? skills
|
|
50
|
+
: skills.map(({ content: _content, ...rest }) => rest);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: `Found ${result.length} skill(s).` }],
|
|
54
|
+
structuredContent: {
|
|
55
|
+
yourAgentId: requestInfo.agentId,
|
|
56
|
+
success: true,
|
|
57
|
+
message: `Found ${result.length} skill(s).`,
|
|
58
|
+
skills: result,
|
|
59
|
+
total: result.length,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: `Failed: ${message}` }],
|
|
66
|
+
structuredContent: {
|
|
67
|
+
yourAgentId: requestInfo.agentId,
|
|
68
|
+
success: false,
|
|
69
|
+
message: `Failed: ${message}`,
|
|
70
|
+
skills: [],
|
|
71
|
+
total: 0,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { createTaskExtended, getAgentById, getLeadAgent, getSkillById } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillPublishTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-publish",
|
|
9
|
+
{
|
|
10
|
+
title: "Publish Skill",
|
|
11
|
+
annotations: { destructiveHint: false },
|
|
12
|
+
description:
|
|
13
|
+
"Publish a personal skill to swarm scope. Creates an approval task for the lead agent.",
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
skillId: z.string().describe("ID of the personal skill to publish"),
|
|
16
|
+
}),
|
|
17
|
+
outputSchema: z.object({
|
|
18
|
+
yourAgentId: z.string().uuid().optional(),
|
|
19
|
+
success: z.boolean(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
taskId: z.string().optional(),
|
|
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 skill = getSkillById(args.skillId);
|
|
33
|
+
if (!skill) {
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: "text", text: "Skill not found." }],
|
|
36
|
+
structuredContent: {
|
|
37
|
+
yourAgentId: requestInfo.agentId,
|
|
38
|
+
success: false,
|
|
39
|
+
message: "Skill not found.",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (skill.type !== "personal") {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: "Only personal skills can be published." }],
|
|
47
|
+
structuredContent: {
|
|
48
|
+
yourAgentId: requestInfo.agentId,
|
|
49
|
+
success: false,
|
|
50
|
+
message: "Only personal skills can be published.",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (skill.ownerAgentId !== requestInfo.agentId) {
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: "text", text: "You can only publish your own skills." }],
|
|
58
|
+
structuredContent: {
|
|
59
|
+
yourAgentId: requestInfo.agentId,
|
|
60
|
+
success: false,
|
|
61
|
+
message: "You can only publish your own skills.",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find the lead agent
|
|
67
|
+
const leadAgent = getLeadAgent();
|
|
68
|
+
|
|
69
|
+
if (!leadAgent) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: "No lead agent found to approve the skill." }],
|
|
72
|
+
structuredContent: {
|
|
73
|
+
yourAgentId: requestInfo.agentId,
|
|
74
|
+
success: false,
|
|
75
|
+
message: "No lead agent available.",
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create an approval task for the lead
|
|
81
|
+
const agent = getAgentById(requestInfo.agentId);
|
|
82
|
+
const taskDescription = `Skill Approval Request: "${skill.name}"
|
|
83
|
+
|
|
84
|
+
Agent ${agent?.name ?? requestInfo.agentId} wants to publish a personal skill to swarm scope.
|
|
85
|
+
|
|
86
|
+
**Skill Name:** ${skill.name}
|
|
87
|
+
**Description:** ${skill.description}
|
|
88
|
+
**Version:** ${skill.version}
|
|
89
|
+
|
|
90
|
+
**Content:**
|
|
91
|
+
\`\`\`
|
|
92
|
+
${skill.content}
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
To approve: update the skill's scope to "swarm" using skill-update.
|
|
96
|
+
To reject: close this task with a rejection reason.`;
|
|
97
|
+
|
|
98
|
+
const task = createTaskExtended(taskDescription, {
|
|
99
|
+
agentId: leadAgent.id,
|
|
100
|
+
creatorAgentId: requestInfo.agentId,
|
|
101
|
+
source: "mcp",
|
|
102
|
+
taskType: "skill-approval",
|
|
103
|
+
tags: ["skill-approval", skill.name],
|
|
104
|
+
priority: 60,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: `Skill publish request created. Task ${task.id} sent to lead for approval.`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
structuredContent: {
|
|
115
|
+
yourAgentId: requestInfo.agentId,
|
|
116
|
+
success: true,
|
|
117
|
+
message: `Publish request sent to lead. Track via task ${task.id}.`,
|
|
118
|
+
taskId: task.id,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { searchSkills } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillSearchTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-search",
|
|
9
|
+
{
|
|
10
|
+
title: "Search Skills",
|
|
11
|
+
annotations: { destructiveHint: false },
|
|
12
|
+
description: "Search skills by keyword (name and description).",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
query: z.string().min(1).describe("Search query"),
|
|
15
|
+
limit: z.number().int().min(1).max(100).default(20).optional(),
|
|
16
|
+
}),
|
|
17
|
+
outputSchema: z.object({
|
|
18
|
+
yourAgentId: z.string().uuid().optional(),
|
|
19
|
+
success: z.boolean(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
skills: z.array(z.any()),
|
|
22
|
+
total: z.number(),
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
async (args, requestInfo, _meta) => {
|
|
26
|
+
const skills = searchSkills(args.query, args.limit ?? 20);
|
|
27
|
+
const result = skills.map(({ content: _content, ...rest }) => rest);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{ type: "text", text: `Found ${result.length} skill(s) matching "${args.query}".` },
|
|
32
|
+
],
|
|
33
|
+
structuredContent: {
|
|
34
|
+
yourAgentId: requestInfo.agentId,
|
|
35
|
+
success: true,
|
|
36
|
+
message: `Found ${result.length} skill(s).`,
|
|
37
|
+
skills: result,
|
|
38
|
+
total: result.length,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getSkillById, listSkills, updateSkill } from "@/be/db";
|
|
4
|
+
import { parseSkillContent } from "@/be/skill-parser";
|
|
5
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
6
|
+
|
|
7
|
+
function contentHash(content: string): string {
|
|
8
|
+
const hash = new Bun.CryptoHasher("sha256").update(content).digest("hex");
|
|
9
|
+
return hash;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const registerSkillSyncRemoteTool = (server: McpServer) => {
|
|
13
|
+
createToolRegistrar(server)(
|
|
14
|
+
"skill-sync-remote",
|
|
15
|
+
{
|
|
16
|
+
title: "Sync Remote Skills",
|
|
17
|
+
annotations: { destructiveHint: false },
|
|
18
|
+
description:
|
|
19
|
+
"Check and update remote skills from their GitHub sources. Compares content and updates if changed.",
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
skillId: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Sync a specific skill, or all remote skills if omitted"),
|
|
25
|
+
force: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.default(false)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Force re-fetch even if hash matches"),
|
|
30
|
+
}),
|
|
31
|
+
outputSchema: z.object({
|
|
32
|
+
yourAgentId: z.string().uuid().optional(),
|
|
33
|
+
success: z.boolean(),
|
|
34
|
+
message: z.string(),
|
|
35
|
+
updated: z.number(),
|
|
36
|
+
checked: z.number(),
|
|
37
|
+
errors: z.array(z.string()),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
async (args, requestInfo, _meta) => {
|
|
41
|
+
try {
|
|
42
|
+
const skills = args.skillId
|
|
43
|
+
? (() => {
|
|
44
|
+
const skill = getSkillById(args.skillId!);
|
|
45
|
+
return skill && skill.type === "remote" ? [skill] : [];
|
|
46
|
+
})()
|
|
47
|
+
: listSkills({ type: "remote" });
|
|
48
|
+
|
|
49
|
+
let updated = 0;
|
|
50
|
+
const errors: string[] = [];
|
|
51
|
+
|
|
52
|
+
for (const skill of skills) {
|
|
53
|
+
if (skill.isComplex) continue; // Skip complex skills (handled by npx)
|
|
54
|
+
if (!skill.sourceRepo) continue;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const filePath = skill.sourcePath ? `${skill.sourcePath}/SKILL.md` : "SKILL.md";
|
|
58
|
+
const rawUrl = `https://raw.githubusercontent.com/${skill.sourceRepo}/${skill.sourceBranch}/${filePath}`;
|
|
59
|
+
|
|
60
|
+
const response = await fetch(rawUrl);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
errors.push(`${skill.name}: HTTP ${response.status}`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const newContent = await response.text();
|
|
67
|
+
const newHash = contentHash(newContent);
|
|
68
|
+
const now = new Date().toISOString();
|
|
69
|
+
|
|
70
|
+
if (args.force || newHash !== skill.sourceHash) {
|
|
71
|
+
const parsed = parseSkillContent(newContent);
|
|
72
|
+
updateSkill(skill.id, {
|
|
73
|
+
content: newContent,
|
|
74
|
+
name: parsed.name,
|
|
75
|
+
description: parsed.description,
|
|
76
|
+
allowedTools: parsed.allowedTools,
|
|
77
|
+
model: parsed.model,
|
|
78
|
+
effort: parsed.effort,
|
|
79
|
+
context: parsed.context,
|
|
80
|
+
agent: parsed.agent,
|
|
81
|
+
disableModelInvocation: parsed.disableModelInvocation,
|
|
82
|
+
userInvocable: parsed.userInvocable,
|
|
83
|
+
sourceHash: newHash,
|
|
84
|
+
lastFetchedAt: now,
|
|
85
|
+
});
|
|
86
|
+
updated++;
|
|
87
|
+
} else {
|
|
88
|
+
// Content unchanged — still update lastFetchedAt
|
|
89
|
+
updateSkill(skill.id, { lastFetchedAt: now });
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
errors.push(`${skill.name}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Synced remote skills: ${updated} updated, ${skills.length} checked, ${errors.length} errors.`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
structuredContent: {
|
|
104
|
+
yourAgentId: requestInfo.agentId,
|
|
105
|
+
success: true,
|
|
106
|
+
message: `${updated} updated, ${skills.length} checked.`,
|
|
107
|
+
updated,
|
|
108
|
+
checked: skills.length,
|
|
109
|
+
errors,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: `Failed: ${message}` }],
|
|
116
|
+
structuredContent: {
|
|
117
|
+
yourAgentId: requestInfo.agentId,
|
|
118
|
+
success: false,
|
|
119
|
+
message: `Failed: ${message}`,
|
|
120
|
+
updated: 0,
|
|
121
|
+
checked: 0,
|
|
122
|
+
errors: [message],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById, uninstallSkill } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillUninstallTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-uninstall",
|
|
9
|
+
{
|
|
10
|
+
title: "Uninstall Skill",
|
|
11
|
+
annotations: { destructiveHint: true },
|
|
12
|
+
description: "Remove a skill from an agent.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
skillId: z.string().describe("ID of the skill to uninstall"),
|
|
15
|
+
agentId: z.string().optional().describe("Target agent (default: calling agent)"),
|
|
16
|
+
}),
|
|
17
|
+
outputSchema: z.object({
|
|
18
|
+
yourAgentId: z.string().uuid().optional(),
|
|
19
|
+
success: z.boolean(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
async (args, requestInfo, _meta) => {
|
|
24
|
+
if (!requestInfo.agentId) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: "Agent ID not found." }],
|
|
27
|
+
structuredContent: { success: false, message: "Agent ID not found." },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const targetAgentId = args.agentId ?? requestInfo.agentId;
|
|
32
|
+
|
|
33
|
+
if (targetAgentId !== requestInfo.agentId) {
|
|
34
|
+
const agent = getAgentById(requestInfo.agentId);
|
|
35
|
+
if (!agent?.isLead) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: "Only leads can uninstall skills for other agents." }],
|
|
38
|
+
structuredContent: {
|
|
39
|
+
yourAgentId: requestInfo.agentId,
|
|
40
|
+
success: false,
|
|
41
|
+
message: "Permission denied.",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const removed = uninstallSkill(targetAgentId, args.skillId);
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{ type: "text", text: removed ? "Skill uninstalled." : "Skill was not installed." },
|
|
51
|
+
],
|
|
52
|
+
structuredContent: {
|
|
53
|
+
yourAgentId: requestInfo.agentId,
|
|
54
|
+
success: removed,
|
|
55
|
+
message: removed ? "Skill uninstalled." : "Skill was not installed for this agent.",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById, getSkillById, updateSkill } from "@/be/db";
|
|
4
|
+
import { parseSkillContent } from "@/be/skill-parser";
|
|
5
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
6
|
+
|
|
7
|
+
export const registerSkillUpdateTool = (server: McpServer) => {
|
|
8
|
+
createToolRegistrar(server)(
|
|
9
|
+
"skill-update",
|
|
10
|
+
{
|
|
11
|
+
title: "Update Skill",
|
|
12
|
+
annotations: { destructiveHint: false },
|
|
13
|
+
description:
|
|
14
|
+
"Update a skill's content or settings. Re-parses frontmatter if content changes.",
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
skillId: z.string().optional().describe("Skill ID to update"),
|
|
17
|
+
content: z.string().optional().describe("New SKILL.md content (re-parses frontmatter)"),
|
|
18
|
+
isEnabled: z.boolean().optional().describe("Toggle enabled/disabled"),
|
|
19
|
+
}),
|
|
20
|
+
outputSchema: z.object({
|
|
21
|
+
yourAgentId: z.string().uuid().optional(),
|
|
22
|
+
success: z.boolean(),
|
|
23
|
+
message: z.string(),
|
|
24
|
+
skill: z.any().optional(),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
async (args, requestInfo, _meta) => {
|
|
28
|
+
if (!requestInfo.agentId) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: "Agent ID not found." }],
|
|
31
|
+
structuredContent: { success: false, message: "Agent ID not found." },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!args.skillId) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: "skillId is required." }],
|
|
38
|
+
structuredContent: {
|
|
39
|
+
yourAgentId: requestInfo.agentId,
|
|
40
|
+
success: false,
|
|
41
|
+
message: "skillId is required.",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const existing = getSkillById(args.skillId);
|
|
48
|
+
if (!existing) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: "Skill not found." }],
|
|
51
|
+
structuredContent: {
|
|
52
|
+
yourAgentId: requestInfo.agentId,
|
|
53
|
+
success: false,
|
|
54
|
+
message: "Skill not found.",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Only owner or lead can update
|
|
60
|
+
const agent = getAgentById(requestInfo.agentId);
|
|
61
|
+
if (existing.ownerAgentId !== requestInfo.agentId && !agent?.isLead) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{ type: "text", text: "Only the owning agent or lead can update this skill." },
|
|
65
|
+
],
|
|
66
|
+
structuredContent: {
|
|
67
|
+
yourAgentId: requestInfo.agentId,
|
|
68
|
+
success: false,
|
|
69
|
+
message: "Permission denied.",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const updates: Parameters<typeof updateSkill>[1] = {};
|
|
75
|
+
|
|
76
|
+
if (args.content !== undefined) {
|
|
77
|
+
const parsed = parseSkillContent(args.content);
|
|
78
|
+
updates.content = args.content;
|
|
79
|
+
updates.name = parsed.name;
|
|
80
|
+
updates.description = parsed.description;
|
|
81
|
+
updates.allowedTools = parsed.allowedTools;
|
|
82
|
+
updates.model = parsed.model;
|
|
83
|
+
updates.effort = parsed.effort;
|
|
84
|
+
updates.context = parsed.context;
|
|
85
|
+
updates.agent = parsed.agent;
|
|
86
|
+
updates.disableModelInvocation = parsed.disableModelInvocation;
|
|
87
|
+
updates.userInvocable = parsed.userInvocable;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (args.isEnabled !== undefined) {
|
|
91
|
+
updates.isEnabled = args.isEnabled;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const skill = updateSkill(args.skillId, updates);
|
|
95
|
+
if (!skill) {
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text: "Failed to update skill." }],
|
|
98
|
+
structuredContent: {
|
|
99
|
+
yourAgentId: requestInfo.agentId,
|
|
100
|
+
success: false,
|
|
101
|
+
message: "Update failed.",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: `Updated skill "${skill.name}" (v${skill.version})` }],
|
|
108
|
+
structuredContent: {
|
|
109
|
+
yourAgentId: requestInfo.agentId,
|
|
110
|
+
success: true,
|
|
111
|
+
message: `Updated skill "${skill.name}" to version ${skill.version}.`,
|
|
112
|
+
skill,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: "text", text: `Failed: ${message}` }],
|
|
119
|
+
structuredContent: {
|
|
120
|
+
yourAgentId: requestInfo.agentId,
|
|
121
|
+
success: false,
|
|
122
|
+
message: `Failed: ${message}`,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
};
|