@desplega.ai/agent-swarm 1.0.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/.claude/settings.local.json +80 -0
- package/.editorconfig +15 -0
- package/.env.example +9 -0
- package/CLAUDE.md +111 -0
- package/README.md +141 -0
- package/biome.json +36 -0
- package/example-req-meta.json +24 -0
- package/package.json +46 -0
- package/src/be/db.ts +313 -0
- package/src/claude.ts +80 -0
- package/src/cli.tsx +357 -0
- package/src/commands/hook.ts +6 -0
- package/src/commands/setup.tsx +577 -0
- package/src/hooks/hook.ts +198 -0
- package/src/http.ts +262 -0
- package/src/server.ts +41 -0
- package/src/stdio.ts +21 -0
- package/src/tools/get-swarm.ts +37 -0
- package/src/tools/get-task-details.ts +48 -0
- package/src/tools/get-tasks.ts +78 -0
- package/src/tools/join-swarm.ts +102 -0
- package/src/tools/my-agent-info.ts +61 -0
- package/src/tools/poll-task.ts +109 -0
- package/src/tools/send-task.ts +62 -0
- package/src/tools/store-progress.ts +94 -0
- package/src/tools/utils.ts +115 -0
- package/src/types.ts +42 -0
- package/tsconfig.json +35 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { createAgent, getAllAgents, getDb } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
import { AgentSchema } from "@/types";
|
|
6
|
+
|
|
7
|
+
export const registerJoinSwarmTool = (server: McpServer) => {
|
|
8
|
+
createToolRegistrar(server)(
|
|
9
|
+
"join-swarm",
|
|
10
|
+
{
|
|
11
|
+
title: "Join the agent swarm",
|
|
12
|
+
description: "Tool for an agent to join the swarm of agents.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
lead: z.boolean().default(false).describe("Whether this agent should be the lead."),
|
|
15
|
+
name: z.string().min(1).describe("The name of the agent joining the swarm."),
|
|
16
|
+
}),
|
|
17
|
+
outputSchema: z.object({
|
|
18
|
+
success: z.boolean(),
|
|
19
|
+
message: z.string(),
|
|
20
|
+
agent: AgentSchema.optional(),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
async ({ lead, name }, requestInfo, _meta) => {
|
|
24
|
+
// Check if agent ID is set
|
|
25
|
+
if (!requestInfo.agentId) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
structuredContent: {
|
|
34
|
+
yourAgentId: requestInfo.agentId,
|
|
35
|
+
success: false,
|
|
36
|
+
message: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const agentId = requestInfo.agentId;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const agentTx = getDb().transaction(() => {
|
|
45
|
+
const agents = getAllAgents();
|
|
46
|
+
|
|
47
|
+
const existingAgent = agents.find((agent) => agent.name === name);
|
|
48
|
+
const existingLead = agents.find((agent) => agent.isLead);
|
|
49
|
+
|
|
50
|
+
if (existingAgent) {
|
|
51
|
+
throw new Error(`Agent with name "${name}" already exists.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If lead is true, demote e
|
|
55
|
+
if (lead && existingLead) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Lead agent "${existingLead.name}" already exists. Only one lead agent is allowed.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return createAgent({
|
|
62
|
+
id: agentId,
|
|
63
|
+
name,
|
|
64
|
+
isLead: lead,
|
|
65
|
+
status: "idle",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const agent = agentTx();
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Successfully joined swarm as agent "${agent.name}" (ID: ${agent.id}).`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
structuredContent: {
|
|
79
|
+
yourAgentId: requestInfo.agentId,
|
|
80
|
+
success: true,
|
|
81
|
+
message: `Successfully joined swarm as agent "${agent.name}" (ID: ${agent.id}).`,
|
|
82
|
+
agent,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `Failed to join swarm: ${(error as Error).message}`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
structuredContent: {
|
|
94
|
+
yourAgentId: requestInfo.agentId,
|
|
95
|
+
success: false,
|
|
96
|
+
message: `Failed to join swarm: ${(error as Error).message}`,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerMyAgentInfoTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"my-agent-info",
|
|
9
|
+
{
|
|
10
|
+
title: "Get your agent info",
|
|
11
|
+
description: "Returns your agent ID based on the X-Agent-ID header.",
|
|
12
|
+
inputSchema: z.object({}),
|
|
13
|
+
outputSchema: z.object({
|
|
14
|
+
success: z.boolean(),
|
|
15
|
+
message: z.string(),
|
|
16
|
+
agentId: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
async (_input, requestInfo, _meta) => {
|
|
20
|
+
if (!requestInfo.agentId) {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
structuredContent: {
|
|
29
|
+
yourAgentId: requestInfo.agentId,
|
|
30
|
+
success: false,
|
|
31
|
+
message: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const maybeAgent = getAgentById(requestInfo.agentId);
|
|
37
|
+
|
|
38
|
+
let registeredMessage =
|
|
39
|
+
" You are not registered as an agent, use the 'join-swarm' tool to register, use a nice name related to the project you are working on if not provided by the user.";
|
|
40
|
+
|
|
41
|
+
if (maybeAgent) {
|
|
42
|
+
registeredMessage = ` You are registered as agent "${maybeAgent.name}".`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `Your agent ID is: ${requestInfo.agentId}.${registeredMessage}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
structuredContent: {
|
|
53
|
+
yourAgentId: requestInfo.agentId,
|
|
54
|
+
yourAgentInfo: maybeAgent,
|
|
55
|
+
success: true,
|
|
56
|
+
message: `Your agent ID is: ${requestInfo.agentId}.${registeredMessage}`,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { addMinutes } from "date-fns";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { getDb, getPendingTaskForAgent, startTask } from "@/be/db";
|
|
5
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
6
|
+
import { AgentTaskSchema } from "@/types";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
9
|
+
const MAX_POLL_DURATION_MS = 1 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
export const registerPollTaskTool = (server: McpServer) => {
|
|
12
|
+
createToolRegistrar(server)(
|
|
13
|
+
"poll-task",
|
|
14
|
+
{
|
|
15
|
+
title: "Poll for a task",
|
|
16
|
+
description:
|
|
17
|
+
"Tool for an agent to poll for a new task assignment, to be used recursively until a task is assigned.",
|
|
18
|
+
inputSchema: z.object({}),
|
|
19
|
+
outputSchema: z.object({
|
|
20
|
+
success: z.boolean(),
|
|
21
|
+
message: z.string(),
|
|
22
|
+
task: AgentTaskSchema.optional(),
|
|
23
|
+
waitedForSeconds: z.number().describe("Seconds waited before receiving the task."),
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
async (_input, requestInfo, meta) => {
|
|
27
|
+
// Check if agent ID is set
|
|
28
|
+
if (!requestInfo.agentId) {
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
structuredContent: {
|
|
37
|
+
yourAgentId: requestInfo.agentId,
|
|
38
|
+
success: false,
|
|
39
|
+
message: 'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
40
|
+
waitedForSeconds: 0,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agentId = requestInfo.agentId;
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const maxTime = addMinutes(now, MAX_POLL_DURATION_MS / 60000);
|
|
48
|
+
|
|
49
|
+
// Poll for pending tasks
|
|
50
|
+
while (new Date() < maxTime) {
|
|
51
|
+
// Fetch and update in a single transaction to avoid race conditions
|
|
52
|
+
const startedTask = getDb().transaction(() => {
|
|
53
|
+
const pendingTask = getPendingTaskForAgent(agentId);
|
|
54
|
+
if (!pendingTask) return null;
|
|
55
|
+
return startTask(pendingTask.id);
|
|
56
|
+
})();
|
|
57
|
+
|
|
58
|
+
if (startedTask) {
|
|
59
|
+
const waitedFor = Math.round((Date.now() - now.getTime()) / 1000);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Task "${startedTask.id}" assigned and started.`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
structuredContent: {
|
|
69
|
+
yourAgentId: requestInfo.agentId,
|
|
70
|
+
success: true,
|
|
71
|
+
message: `Task "${startedTask.id}" assigned and started.`,
|
|
72
|
+
task: startedTask,
|
|
73
|
+
waitedForSeconds: waitedFor,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await meta.sendNotification({
|
|
79
|
+
method: "notifications/message",
|
|
80
|
+
params: {
|
|
81
|
+
level: "info",
|
|
82
|
+
data: `Polling for task assignment...`,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Wait for a short period before polling again
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, DEFAULT_POLL_INTERVAL_MS));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const waitedForSeconds = Math.round((Date.now() - now.getTime()) / 1000);
|
|
91
|
+
|
|
92
|
+
// If no task was found within the time limit
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `No task assigned within the polling duration.`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
structuredContent: {
|
|
101
|
+
yourAgentId: requestInfo.agentId,
|
|
102
|
+
success: false,
|
|
103
|
+
message: `No task assigned within the polling duration, please keep polling until a task is assigned.`,
|
|
104
|
+
waitedForSeconds,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { createTask, getAgentById, getDb, updateAgentStatus } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
import { AgentTaskSchema } from "@/types";
|
|
6
|
+
|
|
7
|
+
export const registerSendTaskTool = (server: McpServer) => {
|
|
8
|
+
createToolRegistrar(server)(
|
|
9
|
+
"send-task",
|
|
10
|
+
{
|
|
11
|
+
title: "Send a task",
|
|
12
|
+
description: "Sends a task to a specific agent in the swarm.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
agentId: z.uuid().describe("The ID of the agent to send the task to."),
|
|
15
|
+
task: z.string().min(1).describe("The task description to send."),
|
|
16
|
+
}),
|
|
17
|
+
outputSchema: z.object({
|
|
18
|
+
success: z.boolean(),
|
|
19
|
+
message: z.string(),
|
|
20
|
+
task: AgentTaskSchema.optional(),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
async ({ agentId, task }, requestInfo, _meta) => {
|
|
24
|
+
const txn = getDb().transaction(() => {
|
|
25
|
+
const agent = getAgentById(agentId);
|
|
26
|
+
|
|
27
|
+
if (!agent) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
message: `Agent with ID "${agentId}" not found.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (agent.status !== "idle") {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
message: `Agent "${agent.name}" is not idle (status: ${agent.status}). Cannot assign task.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const newTask = createTask(agentId, task);
|
|
42
|
+
updateAgentStatus(agentId, "busy");
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
message: `Task "${newTask.id}" sent to agent "${agent.name}".`,
|
|
47
|
+
task: newTask,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = txn();
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: result.message }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
yourAgentId: requestInfo.agentId,
|
|
57
|
+
...result,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import {
|
|
4
|
+
completeTask,
|
|
5
|
+
failTask,
|
|
6
|
+
getDb,
|
|
7
|
+
getTaskById,
|
|
8
|
+
updateAgentStatus,
|
|
9
|
+
updateTaskProgress,
|
|
10
|
+
} from "@/be/db";
|
|
11
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
12
|
+
import { AgentTaskSchema } from "@/types";
|
|
13
|
+
|
|
14
|
+
export const registerStoreProgressTool = (server: McpServer) => {
|
|
15
|
+
createToolRegistrar(server)(
|
|
16
|
+
"store-progress",
|
|
17
|
+
{
|
|
18
|
+
title: "Store task progress",
|
|
19
|
+
description:
|
|
20
|
+
"Stores the progress of a specific task. Can also mark task as completed or failed, which will set the agent back to idle.",
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
taskId: z.uuid().describe("The ID of the task to update progress for."),
|
|
23
|
+
progress: z.string().optional().describe("The progress update to store."),
|
|
24
|
+
status: z
|
|
25
|
+
.enum(["completed", "failed"])
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Set to 'completed' or 'failed' to finish the task."),
|
|
28
|
+
output: z.string().optional().describe("The output of the task (used when completing)."),
|
|
29
|
+
failureReason: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("The reason for failure (used when failing)."),
|
|
33
|
+
}),
|
|
34
|
+
outputSchema: z.object({
|
|
35
|
+
success: z.boolean(),
|
|
36
|
+
message: z.string(),
|
|
37
|
+
task: AgentTaskSchema.optional(),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
async ({ taskId, progress, status, output, failureReason }, requestInfo, _meta) => {
|
|
41
|
+
const txn = getDb().transaction(() => {
|
|
42
|
+
const existingTask = getTaskById(taskId);
|
|
43
|
+
|
|
44
|
+
if (!existingTask) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
message: `Task with ID "${taskId}" not found.`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let updatedTask = existingTask;
|
|
52
|
+
|
|
53
|
+
// Update progress if provided
|
|
54
|
+
if (progress) {
|
|
55
|
+
const result = updateTaskProgress(taskId, progress);
|
|
56
|
+
if (result) updatedTask = result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle status change
|
|
60
|
+
if (status === "completed") {
|
|
61
|
+
const result = completeTask(taskId, output);
|
|
62
|
+
if (result) {
|
|
63
|
+
updatedTask = result;
|
|
64
|
+
updateAgentStatus(existingTask.agentId, "idle");
|
|
65
|
+
}
|
|
66
|
+
} else if (status === "failed") {
|
|
67
|
+
const result = failTask(taskId, failureReason ?? "Unknown failure");
|
|
68
|
+
if (result) {
|
|
69
|
+
updatedTask = result;
|
|
70
|
+
updateAgentStatus(existingTask.agentId, "idle");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
message: status
|
|
77
|
+
? `Task "${taskId}" marked as ${status}.`
|
|
78
|
+
: `Progress stored for task "${taskId}".`,
|
|
79
|
+
task: updatedTask,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = txn();
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: result.message }],
|
|
87
|
+
structuredContent: {
|
|
88
|
+
yourAgentId: requestInfo.agentId,
|
|
89
|
+
...result,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type {
|
|
3
|
+
AnySchema,
|
|
4
|
+
SchemaOutput,
|
|
5
|
+
ShapeOutput,
|
|
6
|
+
ZodRawShapeCompat,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
8
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
9
|
+
import type {
|
|
10
|
+
CallToolResult,
|
|
11
|
+
ServerNotification,
|
|
12
|
+
ServerRequest,
|
|
13
|
+
ToolAnnotations,
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
|
|
16
|
+
type Meta = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
17
|
+
|
|
18
|
+
export type RequestInfo = {
|
|
19
|
+
sessionId: string | undefined;
|
|
20
|
+
agentId: string | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getRequestInfo = (req: Meta): RequestInfo => {
|
|
24
|
+
const agentIdHeader = req.requestInfo?.headers?.["x-agent-id"];
|
|
25
|
+
|
|
26
|
+
let agentId: string | undefined;
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(agentIdHeader)) {
|
|
29
|
+
agentId = agentIdHeader?.[0];
|
|
30
|
+
} else if (typeof agentIdHeader === "string") {
|
|
31
|
+
agentId = agentIdHeader;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
sessionId: req.sessionId || undefined,
|
|
36
|
+
agentId,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Infer the input type from the schema
|
|
41
|
+
type InferInput<Args extends undefined | ZodRawShapeCompat | AnySchema> =
|
|
42
|
+
Args extends ZodRawShapeCompat
|
|
43
|
+
? ShapeOutput<Args>
|
|
44
|
+
: Args extends AnySchema
|
|
45
|
+
? SchemaOutput<Args>
|
|
46
|
+
: undefined;
|
|
47
|
+
|
|
48
|
+
// Callback type with requestInfo injected as second parameter
|
|
49
|
+
type ToolCallbackWithInfo<Args extends undefined | ZodRawShapeCompat | AnySchema = undefined> =
|
|
50
|
+
Args extends undefined
|
|
51
|
+
? (requestInfo: RequestInfo, meta: Meta) => CallToolResult | Promise<CallToolResult>
|
|
52
|
+
: (
|
|
53
|
+
args: InferInput<Args>,
|
|
54
|
+
requestInfo: RequestInfo,
|
|
55
|
+
meta: Meta,
|
|
56
|
+
) => CallToolResult | Promise<CallToolResult>;
|
|
57
|
+
|
|
58
|
+
type ToolConfig<
|
|
59
|
+
InputArgs extends undefined | ZodRawShapeCompat | AnySchema,
|
|
60
|
+
OutputArgs extends ZodRawShapeCompat | AnySchema,
|
|
61
|
+
> = {
|
|
62
|
+
title?: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
inputSchema?: InputArgs;
|
|
65
|
+
outputSchema?: OutputArgs;
|
|
66
|
+
annotations?: ToolAnnotations;
|
|
67
|
+
_meta?: Record<string, unknown>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a tool registration helper that automatically extracts request info
|
|
72
|
+
* and passes it as the second parameter to the callback.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const registerTool = createToolRegistrar(server);
|
|
76
|
+
*
|
|
77
|
+
* registerTool(
|
|
78
|
+
* "my-tool",
|
|
79
|
+
* { inputSchema: z.object({ name: z.string() }) },
|
|
80
|
+
* async ({ name }, requestInfo, meta) => {
|
|
81
|
+
* // requestInfo.sessionId and requestInfo.agentId are available
|
|
82
|
+
* return { content: [{ type: "text", text: `Hello ${name}` }] };
|
|
83
|
+
* }
|
|
84
|
+
* );
|
|
85
|
+
*/
|
|
86
|
+
export const createToolRegistrar = (server: McpServer) => {
|
|
87
|
+
return <
|
|
88
|
+
OutputArgs extends ZodRawShapeCompat | AnySchema,
|
|
89
|
+
InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined,
|
|
90
|
+
>(
|
|
91
|
+
name: string,
|
|
92
|
+
config: ToolConfig<InputArgs, OutputArgs>,
|
|
93
|
+
cb: ToolCallbackWithInfo<InputArgs>,
|
|
94
|
+
) => {
|
|
95
|
+
return server.registerTool(name, config, ((args: InferInput<InputArgs>, meta: Meta) => {
|
|
96
|
+
const requestInfo = getRequestInfo(meta);
|
|
97
|
+
|
|
98
|
+
// Handle zero-argument case
|
|
99
|
+
if (config.inputSchema === undefined) {
|
|
100
|
+
return (
|
|
101
|
+
cb as (requestInfo: RequestInfo, meta: Meta) => CallToolResult | Promise<CallToolResult>
|
|
102
|
+
)(requestInfo, meta);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle with-arguments case
|
|
106
|
+
return (
|
|
107
|
+
cb as (
|
|
108
|
+
args: InferInput<InputArgs>,
|
|
109
|
+
requestInfo: RequestInfo,
|
|
110
|
+
meta: Meta,
|
|
111
|
+
) => CallToolResult | Promise<CallToolResult>
|
|
112
|
+
)(args, requestInfo, meta);
|
|
113
|
+
}) as Parameters<typeof server.registerTool>[2]);
|
|
114
|
+
};
|
|
115
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
export const AgentTaskStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
|
|
4
|
+
|
|
5
|
+
export const AgentTaskSchema = z.object({
|
|
6
|
+
id: z.uuid(),
|
|
7
|
+
agentId: z.uuid(),
|
|
8
|
+
task: z.string().min(1),
|
|
9
|
+
status: AgentTaskStatusSchema,
|
|
10
|
+
|
|
11
|
+
createdAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
12
|
+
lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
13
|
+
|
|
14
|
+
finishedAt: z.iso.datetime().optional(),
|
|
15
|
+
|
|
16
|
+
failureReason: z.string().optional(),
|
|
17
|
+
output: z.string().optional(),
|
|
18
|
+
progress: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const AgentStatusSchema = z.enum(["idle", "busy", "offline"]);
|
|
22
|
+
|
|
23
|
+
export const AgentSchema = z.object({
|
|
24
|
+
id: z.uuid(),
|
|
25
|
+
name: z.string().min(1),
|
|
26
|
+
isLead: z.boolean().default(false),
|
|
27
|
+
status: AgentStatusSchema,
|
|
28
|
+
|
|
29
|
+
createdAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
30
|
+
lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const AgentWithTasksSchema = AgentSchema.extend({
|
|
34
|
+
tasks: z.array(AgentTaskSchema).default([]),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type AgentTaskStatus = z.infer<typeof AgentTaskStatusSchema>;
|
|
38
|
+
export type AgentTask = z.infer<typeof AgentTaskSchema>;
|
|
39
|
+
|
|
40
|
+
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
41
|
+
export type Agent = z.infer<typeof AgentSchema>;
|
|
42
|
+
export type AgentWithTasks = z.infer<typeof AgentWithTasksSchema>;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
|
|
15
|
+
// Path aliases
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["src/*"]
|
|
19
|
+
},
|
|
20
|
+
"verbatimModuleSyntax": true,
|
|
21
|
+
"noEmit": true,
|
|
22
|
+
|
|
23
|
+
// Best practices
|
|
24
|
+
"strict": true,
|
|
25
|
+
"skipLibCheck": true,
|
|
26
|
+
"noFallthroughCasesInSwitch": true,
|
|
27
|
+
"noUncheckedIndexedAccess": true,
|
|
28
|
+
"noImplicitOverride": true,
|
|
29
|
+
|
|
30
|
+
// Some stricter flags (disabled by default)
|
|
31
|
+
"noUnusedLocals": false,
|
|
32
|
+
"noUnusedParameters": false,
|
|
33
|
+
"noPropertyAccessFromIndexSignature": false
|
|
34
|
+
}
|
|
35
|
+
}
|