agent-orchestration 0.5.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.
- package/.cursor/rules/orchestrator-auto.mdc +107 -0
- package/.cursor/rules/orchestrator-main.mdc +86 -0
- package/.cursor/rules/orchestrator-sub.mdc +114 -0
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/activeContext.md +37 -0
- package/dist/bin/cli.d.ts +11 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +410 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/database.d.ts +91 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +522 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +132 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +124 -0
- package/dist/models.js.map +1 -0
- package/dist/tools/agent.d.ts +10 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +185 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/coordination.d.ts +6 -0
- package/dist/tools/coordination.d.ts.map +1 -0
- package/dist/tools/coordination.js +114 -0
- package/dist/tools/coordination.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +108 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/task.d.ts +6 -0
- package/dist/tools/task.d.ts.map +1 -0
- package/dist/tools/task.js +267 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/utility.d.ts +6 -0
- package/dist/tools/utility.d.ts.map +1 -0
- package/dist/tools/utility.js +162 -0
- package/dist/tools/utility.js.map +1 -0
- package/dist/utils/contextSync.d.ts +12 -0
- package/dist/utils/contextSync.d.ts.map +1 -0
- package/dist/utils/contextSync.js +124 -0
- package/dist/utils/contextSync.js.map +1 -0
- package/package.json +54 -0
- package/src/bin/cli.ts +430 -0
- package/src/database.ts +764 -0
- package/src/index.ts +71 -0
- package/src/models.ts +226 -0
- package/src/tools/agent.ts +241 -0
- package/src/tools/coordination.ts +152 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/memory.ts +150 -0
- package/src/tools/task.ts +334 -0
- package/src/tools/utility.ts +202 -0
- package/src/utils/contextSync.ts +144 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent Orchestration Server
|
|
4
|
+
*
|
|
5
|
+
* A Model Context Protocol server that enables multiple AI agents to share
|
|
6
|
+
* memory, coordinate tasks, and collaborate effectively across IDEs and CLI tools.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
+
import { getDatabase, closeDatabase } from './database.js';
|
|
12
|
+
import {
|
|
13
|
+
registerAgentTools,
|
|
14
|
+
registerMemoryTools,
|
|
15
|
+
registerTaskTools,
|
|
16
|
+
registerCoordinationTools,
|
|
17
|
+
registerUtilityTools,
|
|
18
|
+
} from './tools/index.js';
|
|
19
|
+
|
|
20
|
+
// Create server instance
|
|
21
|
+
const server = new McpServer({
|
|
22
|
+
name: 'agent-orchestration',
|
|
23
|
+
version: '0.5.0',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Register all tools
|
|
27
|
+
registerAgentTools(server);
|
|
28
|
+
registerMemoryTools(server);
|
|
29
|
+
registerTaskTools(server);
|
|
30
|
+
registerCoordinationTools(server);
|
|
31
|
+
registerUtilityTools(server);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Start the MCP server
|
|
35
|
+
*/
|
|
36
|
+
export async function startServer(): Promise<void> {
|
|
37
|
+
// Initialize database
|
|
38
|
+
const db = getDatabase();
|
|
39
|
+
console.error(`Agent Orchestration server started. Database: ${db.dbPath}`);
|
|
40
|
+
|
|
41
|
+
// Create stdio transport
|
|
42
|
+
const transport = new StdioServerTransport();
|
|
43
|
+
|
|
44
|
+
// Handle shutdown
|
|
45
|
+
process.on('SIGINT', () => {
|
|
46
|
+
console.error('Shutting down...');
|
|
47
|
+
closeDatabase();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
process.on('SIGTERM', () => {
|
|
52
|
+
console.error('Shutting down...');
|
|
53
|
+
closeDatabase();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Connect and run
|
|
58
|
+
await server.connect(transport);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Auto-run when executed directly (node dist/index.js)
|
|
62
|
+
// Check if this file is the main module being run
|
|
63
|
+
const isMainModule = process.argv[1]?.endsWith('index.js') && !process.argv[1]?.includes('cli.js');
|
|
64
|
+
|
|
65
|
+
if (isMainModule) {
|
|
66
|
+
startServer().catch((error) => {
|
|
67
|
+
console.error('Fatal error:', error);
|
|
68
|
+
closeDatabase();
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for MCP Memory Orchestrator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ulid } from 'ulid';
|
|
6
|
+
|
|
7
|
+
// ==================== Enums ====================
|
|
8
|
+
|
|
9
|
+
export enum AgentRole {
|
|
10
|
+
MAIN = 'main',
|
|
11
|
+
SUB = 'sub',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum AgentStatus {
|
|
15
|
+
ACTIVE = 'active',
|
|
16
|
+
IDLE = 'idle',
|
|
17
|
+
BUSY = 'busy',
|
|
18
|
+
OFFLINE = 'offline',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export enum TaskStatus {
|
|
22
|
+
PENDING = 'pending',
|
|
23
|
+
ASSIGNED = 'assigned',
|
|
24
|
+
IN_PROGRESS = 'in_progress',
|
|
25
|
+
COMPLETED = 'completed',
|
|
26
|
+
FAILED = 'failed',
|
|
27
|
+
CANCELLED = 'cancelled',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum TaskPriority {
|
|
31
|
+
LOW = 'low',
|
|
32
|
+
NORMAL = 'normal',
|
|
33
|
+
HIGH = 'high',
|
|
34
|
+
URGENT = 'urgent',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export enum EventType {
|
|
38
|
+
AGENT_REGISTERED = 'agent_registered',
|
|
39
|
+
AGENT_UNREGISTERED = 'agent_unregistered',
|
|
40
|
+
AGENT_HEARTBEAT = 'agent_heartbeat',
|
|
41
|
+
TASK_CREATED = 'task_created',
|
|
42
|
+
TASK_ASSIGNED = 'task_assigned',
|
|
43
|
+
TASK_CLAIMED = 'task_claimed',
|
|
44
|
+
TASK_UPDATED = 'task_updated',
|
|
45
|
+
TASK_COMPLETED = 'task_completed',
|
|
46
|
+
MEMORY_SET = 'memory_set',
|
|
47
|
+
MEMORY_DELETE = 'memory_delete',
|
|
48
|
+
LOCK_ACQUIRED = 'lock_acquired',
|
|
49
|
+
LOCK_RELEASED = 'lock_released',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ==================== Core Types ====================
|
|
53
|
+
|
|
54
|
+
export interface Agent {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
role: AgentRole;
|
|
58
|
+
status: AgentStatus;
|
|
59
|
+
capabilities: string[];
|
|
60
|
+
metadata: Record<string, unknown>;
|
|
61
|
+
registeredAt: Date;
|
|
62
|
+
lastHeartbeat: Date;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Task {
|
|
66
|
+
id: string;
|
|
67
|
+
title: string;
|
|
68
|
+
description: string;
|
|
69
|
+
status: TaskStatus;
|
|
70
|
+
priority: TaskPriority;
|
|
71
|
+
createdBy: string | null;
|
|
72
|
+
assignedTo: string | null;
|
|
73
|
+
dependencies: string[];
|
|
74
|
+
metadata: Record<string, unknown>;
|
|
75
|
+
output: string | null;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
updatedAt: Date;
|
|
78
|
+
startedAt: Date | null;
|
|
79
|
+
completedAt: Date | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface MemoryEntry {
|
|
83
|
+
id: string;
|
|
84
|
+
key: string;
|
|
85
|
+
value: unknown;
|
|
86
|
+
namespace: string;
|
|
87
|
+
createdBy: string | null;
|
|
88
|
+
ttlSeconds: number | null;
|
|
89
|
+
createdAt: Date;
|
|
90
|
+
updatedAt: Date;
|
|
91
|
+
expiresAt: Date | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface Lock {
|
|
95
|
+
id: string;
|
|
96
|
+
resource: string;
|
|
97
|
+
heldBy: string;
|
|
98
|
+
acquiredAt: Date;
|
|
99
|
+
expiresAt: Date | null;
|
|
100
|
+
metadata: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface Event {
|
|
104
|
+
id: string;
|
|
105
|
+
eventType: EventType;
|
|
106
|
+
agentId: string | null;
|
|
107
|
+
resourceId: string | null;
|
|
108
|
+
details: Record<string, unknown>;
|
|
109
|
+
timestamp: Date;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ==================== Factory Functions ====================
|
|
113
|
+
|
|
114
|
+
export function createAgent(params: {
|
|
115
|
+
name: string;
|
|
116
|
+
role?: AgentRole;
|
|
117
|
+
capabilities?: string[];
|
|
118
|
+
status?: AgentStatus;
|
|
119
|
+
metadata?: Record<string, unknown>;
|
|
120
|
+
}): Agent {
|
|
121
|
+
const now = new Date();
|
|
122
|
+
return {
|
|
123
|
+
id: ulid(),
|
|
124
|
+
name: params.name,
|
|
125
|
+
role: params.role ?? AgentRole.SUB,
|
|
126
|
+
status: params.status ?? AgentStatus.IDLE,
|
|
127
|
+
capabilities: params.capabilities ?? [],
|
|
128
|
+
metadata: params.metadata ?? {},
|
|
129
|
+
registeredAt: now,
|
|
130
|
+
lastHeartbeat: now,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function createTask(params: {
|
|
135
|
+
title: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
priority?: TaskPriority;
|
|
138
|
+
createdBy?: string | null;
|
|
139
|
+
assignedTo?: string | null;
|
|
140
|
+
dependencies?: string[];
|
|
141
|
+
metadata?: Record<string, unknown>;
|
|
142
|
+
status?: TaskStatus;
|
|
143
|
+
startedAt?: Date | null;
|
|
144
|
+
}): Task {
|
|
145
|
+
const now = new Date();
|
|
146
|
+
return {
|
|
147
|
+
id: ulid(),
|
|
148
|
+
title: params.title,
|
|
149
|
+
description: params.description ?? '',
|
|
150
|
+
status: params.status ?? TaskStatus.PENDING,
|
|
151
|
+
priority: params.priority ?? TaskPriority.NORMAL,
|
|
152
|
+
createdBy: params.createdBy ?? null,
|
|
153
|
+
assignedTo: params.assignedTo ?? null,
|
|
154
|
+
dependencies: params.dependencies ?? [],
|
|
155
|
+
metadata: params.metadata ?? {},
|
|
156
|
+
output: null,
|
|
157
|
+
createdAt: now,
|
|
158
|
+
updatedAt: now,
|
|
159
|
+
startedAt: params.startedAt ?? null,
|
|
160
|
+
completedAt: null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createMemoryEntry(params: {
|
|
165
|
+
key: string;
|
|
166
|
+
value: unknown;
|
|
167
|
+
namespace?: string;
|
|
168
|
+
createdBy?: string | null;
|
|
169
|
+
ttlSeconds?: number | null;
|
|
170
|
+
}): MemoryEntry {
|
|
171
|
+
const now = new Date();
|
|
172
|
+
let expiresAt: Date | null = null;
|
|
173
|
+
|
|
174
|
+
if (params.ttlSeconds) {
|
|
175
|
+
expiresAt = new Date(now.getTime() + params.ttlSeconds * 1000);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
id: ulid(),
|
|
180
|
+
key: params.key,
|
|
181
|
+
value: params.value,
|
|
182
|
+
namespace: params.namespace ?? 'default',
|
|
183
|
+
createdBy: params.createdBy ?? null,
|
|
184
|
+
ttlSeconds: params.ttlSeconds ?? null,
|
|
185
|
+
createdAt: now,
|
|
186
|
+
updatedAt: now,
|
|
187
|
+
expiresAt,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function createLock(params: {
|
|
192
|
+
resource: string;
|
|
193
|
+
heldBy: string;
|
|
194
|
+
timeoutSeconds?: number;
|
|
195
|
+
metadata?: Record<string, unknown>;
|
|
196
|
+
}): Lock {
|
|
197
|
+
const now = new Date();
|
|
198
|
+
const expiresAt = params.timeoutSeconds
|
|
199
|
+
? new Date(now.getTime() + params.timeoutSeconds * 1000)
|
|
200
|
+
: null;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
id: ulid(),
|
|
204
|
+
resource: params.resource,
|
|
205
|
+
heldBy: params.heldBy,
|
|
206
|
+
acquiredAt: now,
|
|
207
|
+
expiresAt,
|
|
208
|
+
metadata: params.metadata ?? {},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function createEvent(params: {
|
|
213
|
+
eventType: EventType;
|
|
214
|
+
agentId?: string | null;
|
|
215
|
+
resourceId?: string | null;
|
|
216
|
+
details?: Record<string, unknown>;
|
|
217
|
+
}): Event {
|
|
218
|
+
return {
|
|
219
|
+
id: ulid(),
|
|
220
|
+
eventType: params.eventType,
|
|
221
|
+
agentId: params.agentId ?? null,
|
|
222
|
+
resourceId: params.resourceId ?? null,
|
|
223
|
+
details: params.details ?? {},
|
|
224
|
+
timestamp: new Date(),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent management tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { getDatabase } from '../database.js';
|
|
8
|
+
import { AgentRole, AgentStatus } from '../models.js';
|
|
9
|
+
|
|
10
|
+
// Current agent state (per server instance)
|
|
11
|
+
let currentAgentId: string | null = null;
|
|
12
|
+
let currentAgentName: string | null = null;
|
|
13
|
+
|
|
14
|
+
export function getCurrentAgentId(): string | null {
|
|
15
|
+
return currentAgentId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getCurrentAgentName(): string | null {
|
|
19
|
+
return currentAgentName;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function setCurrentAgent(id: string, name: string): void {
|
|
23
|
+
currentAgentId = id;
|
|
24
|
+
currentAgentName = name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function clearCurrentAgent(): void {
|
|
28
|
+
currentAgentId = null;
|
|
29
|
+
currentAgentName = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerAgentTools(server: McpServer): void {
|
|
33
|
+
// agent_register
|
|
34
|
+
server.tool(
|
|
35
|
+
'agent_register',
|
|
36
|
+
'Register this agent with the orchestration system. Call this at the start of your session.',
|
|
37
|
+
{
|
|
38
|
+
name: z.string().describe('A unique name for this agent'),
|
|
39
|
+
role: z.enum(['main', 'sub']).optional().default('sub').describe('Agent role'),
|
|
40
|
+
capabilities: z
|
|
41
|
+
.array(z.string())
|
|
42
|
+
.optional()
|
|
43
|
+
.default(['code'])
|
|
44
|
+
.describe('List of capabilities'),
|
|
45
|
+
},
|
|
46
|
+
async ({ name, role, capabilities }) => {
|
|
47
|
+
const db = getDatabase();
|
|
48
|
+
|
|
49
|
+
// Check if agent with this name already exists
|
|
50
|
+
const existing = db.getAgentByName(name);
|
|
51
|
+
if (existing) {
|
|
52
|
+
// Reconnect to existing agent
|
|
53
|
+
setCurrentAgent(existing.id, existing.name);
|
|
54
|
+
db.updateAgentHeartbeat(existing.id, AgentStatus.ACTIVE);
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: `Reconnected as '${existing.name}' (${existing.id})`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const agent = db.createAgent({
|
|
66
|
+
name,
|
|
67
|
+
role: role === 'main' ? AgentRole.MAIN : AgentRole.SUB,
|
|
68
|
+
capabilities,
|
|
69
|
+
status: AgentStatus.ACTIVE,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
setCurrentAgent(agent.id, agent.name);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: `Registered as '${agent.name}' (${agent.id})`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// agent_heartbeat
|
|
86
|
+
server.tool(
|
|
87
|
+
'agent_heartbeat',
|
|
88
|
+
'Send a heartbeat to indicate agent is still active. Call periodically during long operations.',
|
|
89
|
+
{
|
|
90
|
+
status: z
|
|
91
|
+
.enum(['active', 'idle', 'busy'])
|
|
92
|
+
.optional()
|
|
93
|
+
.describe('Current status (active, idle, busy)'),
|
|
94
|
+
},
|
|
95
|
+
async ({ status }) => {
|
|
96
|
+
if (!currentAgentId) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: 'text', text: 'Error: Not registered. Call agent_register first.' }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const agentStatus = status
|
|
103
|
+
? (status as AgentStatus)
|
|
104
|
+
: undefined;
|
|
105
|
+
|
|
106
|
+
const updated = getDatabase().updateAgentHeartbeat(currentAgentId, agentStatus);
|
|
107
|
+
|
|
108
|
+
if (updated) {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: 'text', text: `Heartbeat recorded${status ? ` (${status})` : ''}.` }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: 'Error: Agent not found.' }],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// agent_list
|
|
121
|
+
server.tool(
|
|
122
|
+
'agent_list',
|
|
123
|
+
'List all registered agents in the orchestration system.',
|
|
124
|
+
{
|
|
125
|
+
status: z
|
|
126
|
+
.enum(['active', 'idle', 'busy', 'offline'])
|
|
127
|
+
.optional()
|
|
128
|
+
.describe('Filter by status'),
|
|
129
|
+
role: z.enum(['main', 'sub']).optional().describe('Filter by role'),
|
|
130
|
+
},
|
|
131
|
+
async ({ status, role }) => {
|
|
132
|
+
const db = getDatabase();
|
|
133
|
+
|
|
134
|
+
// Cleanup stale agents first
|
|
135
|
+
db.cleanupStaleAgents();
|
|
136
|
+
|
|
137
|
+
const agents = db.listAgents({
|
|
138
|
+
status: status as AgentStatus | undefined,
|
|
139
|
+
role: role === 'main' ? AgentRole.MAIN : role === 'sub' ? AgentRole.SUB : undefined,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (agents.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: 'text', text: 'No agents registered.' }],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lines = ['# Registered Agents\n'];
|
|
149
|
+
for (const agent of agents) {
|
|
150
|
+
const isMe = agent.id === currentAgentId ? ' (you)' : '';
|
|
151
|
+
const statusEmoji = {
|
|
152
|
+
active: '🟢',
|
|
153
|
+
idle: '🟡',
|
|
154
|
+
busy: '🔵',
|
|
155
|
+
offline: '⚫',
|
|
156
|
+
}[agent.status];
|
|
157
|
+
|
|
158
|
+
lines.push(`${statusEmoji} **${agent.name}**${isMe} - ${agent.role}`);
|
|
159
|
+
lines.push(` ID: \`${agent.id}\``);
|
|
160
|
+
lines.push(` Capabilities: ${agent.capabilities.join(', ') || 'none'}`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// agent_unregister
|
|
171
|
+
server.tool(
|
|
172
|
+
'agent_unregister',
|
|
173
|
+
'Unregister this agent and release all held locks. Call at the end of your session.',
|
|
174
|
+
{},
|
|
175
|
+
async () => {
|
|
176
|
+
if (!currentAgentId) {
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: 'text', text: 'Not registered.' }],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const db = getDatabase();
|
|
183
|
+
const deleted = db.deleteAgent(currentAgentId);
|
|
184
|
+
|
|
185
|
+
if (deleted) {
|
|
186
|
+
const name = currentAgentName;
|
|
187
|
+
clearCurrentAgent();
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: 'text', text: `Agent '${name}' unregistered. All locks released.` }],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: 'text', text: 'Error: Could not unregister agent.' }],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// agent_whoami
|
|
200
|
+
server.tool(
|
|
201
|
+
'agent_whoami',
|
|
202
|
+
'Get information about your current agent identity.',
|
|
203
|
+
{},
|
|
204
|
+
async () => {
|
|
205
|
+
if (!currentAgentId) {
|
|
206
|
+
return {
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: 'Not registered. Use `bootstrap` or `agent_register` to register.',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const agent = getDatabase().getAgent(currentAgentId);
|
|
217
|
+
if (!agent) {
|
|
218
|
+
clearCurrentAgent();
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: 'Agent not found in database. Registration expired.' }],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const lines = [
|
|
225
|
+
'# Your Agent Info',
|
|
226
|
+
'',
|
|
227
|
+
`**Name**: ${agent.name}`,
|
|
228
|
+
`**ID**: \`${agent.id}\``,
|
|
229
|
+
`**Role**: ${agent.role}`,
|
|
230
|
+
`**Status**: ${agent.status}`,
|
|
231
|
+
`**Capabilities**: ${agent.capabilities.join(', ') || 'none'}`,
|
|
232
|
+
`**Registered**: ${agent.registeredAt.toISOString()}`,
|
|
233
|
+
`**Last Heartbeat**: ${agent.lastHeartbeat.toISOString()}`,
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordination tools (locks and status)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { getDatabase } from '../database.js';
|
|
8
|
+
import { TaskStatus } from '../models.js';
|
|
9
|
+
import { getCurrentAgentId } from './agent.js';
|
|
10
|
+
|
|
11
|
+
export function registerCoordinationTools(server: McpServer): void {
|
|
12
|
+
// lock_acquire
|
|
13
|
+
server.tool(
|
|
14
|
+
'lock_acquire',
|
|
15
|
+
'Acquire a lock on a resource to prevent concurrent access.',
|
|
16
|
+
{
|
|
17
|
+
resource: z.string().describe("The resource to lock (file path, 'namespace:key', etc.)"),
|
|
18
|
+
timeout_seconds: z
|
|
19
|
+
.number()
|
|
20
|
+
.optional()
|
|
21
|
+
.default(300)
|
|
22
|
+
.describe('Lock timeout. Auto-releases after this time.'),
|
|
23
|
+
reason: z.string().optional().describe('Why you need this lock'),
|
|
24
|
+
},
|
|
25
|
+
async ({ resource, timeout_seconds, reason }) => {
|
|
26
|
+
const agentId = getCurrentAgentId();
|
|
27
|
+
if (!agentId) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: 'text', text: 'Error: Not registered.' }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const db = getDatabase();
|
|
34
|
+
|
|
35
|
+
// Check if already locked
|
|
36
|
+
const existing = db.checkLock(resource);
|
|
37
|
+
if (existing) {
|
|
38
|
+
if (existing.heldBy === agentId) {
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: 'text', text: 'You already hold this lock.' }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: 'text', text: `Lock denied: held by ${existing.heldBy}` }],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const lock = db.acquireLock({
|
|
49
|
+
resource,
|
|
50
|
+
heldBy: agentId,
|
|
51
|
+
timeoutSeconds: timeout_seconds,
|
|
52
|
+
metadata: { reason: reason ?? '' },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (lock) {
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: `Lock acquired on '${resource}' (expires in ${timeout_seconds}s)`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: 'Failed to acquire lock.' }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// lock_release
|
|
73
|
+
server.tool(
|
|
74
|
+
'lock_release',
|
|
75
|
+
'Release a lock you are holding.',
|
|
76
|
+
{
|
|
77
|
+
resource: z.string().describe('The resource to unlock'),
|
|
78
|
+
},
|
|
79
|
+
async ({ resource }) => {
|
|
80
|
+
const agentId = getCurrentAgentId();
|
|
81
|
+
if (!agentId) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: 'text', text: 'Error: Not registered.' }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const released = getDatabase().releaseLock(resource, agentId);
|
|
88
|
+
|
|
89
|
+
if (released) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: 'text', text: 'Lock released.' }],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: 'text', text: 'Could not release lock. You may not hold it.' }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// lock_check
|
|
102
|
+
server.tool(
|
|
103
|
+
'lock_check',
|
|
104
|
+
'Check if a resource is currently locked.',
|
|
105
|
+
{
|
|
106
|
+
resource: z.string().describe('The resource to check'),
|
|
107
|
+
},
|
|
108
|
+
async ({ resource }) => {
|
|
109
|
+
const agentId = getCurrentAgentId();
|
|
110
|
+
const lock = getDatabase().checkLock(resource);
|
|
111
|
+
|
|
112
|
+
if (lock) {
|
|
113
|
+
const isYou = lock.heldBy === agentId ? ' (you)' : '';
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: `Locked by ${lock.heldBy}${isYou}` }],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text', text: 'Not locked.' }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// coordination_status
|
|
126
|
+
server.tool(
|
|
127
|
+
'coordination_status',
|
|
128
|
+
'Get overall coordination status: active agents, pending tasks, held locks.',
|
|
129
|
+
{},
|
|
130
|
+
async () => {
|
|
131
|
+
const db = getDatabase();
|
|
132
|
+
|
|
133
|
+
const agents = db.listAgents();
|
|
134
|
+
const active = agents.filter((a) => ['active', 'busy'].includes(a.status));
|
|
135
|
+
const pending = db.listTasks({ status: TaskStatus.PENDING });
|
|
136
|
+
const inProgress = db.listTasks({ status: TaskStatus.IN_PROGRESS });
|
|
137
|
+
const stats = db.getStats();
|
|
138
|
+
|
|
139
|
+
const lines = [
|
|
140
|
+
'# Coordination Status\n',
|
|
141
|
+
`**Agents**: ${active.length} active / ${agents.length} total`,
|
|
142
|
+
`**Tasks**: ${pending.length} pending, ${inProgress.length} in progress`,
|
|
143
|
+
`**Locks**: ${stats.locks} active`,
|
|
144
|
+
`**Memory**: ${stats.memory} entries`,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registration index
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { registerAgentTools, getCurrentAgentId, getCurrentAgentName, setCurrentAgent, clearCurrentAgent } from './agent.js';
|
|
6
|
+
export { registerMemoryTools } from './memory.js';
|
|
7
|
+
export { registerTaskTools } from './task.js';
|
|
8
|
+
export { registerCoordinationTools } from './coordination.js';
|
|
9
|
+
export { registerUtilityTools } from './utility.js';
|