@claude-flow/mcp 3.0.0-alpha.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/.agentic-flow/intelligence.json +16 -0
- package/README.md +428 -0
- package/__tests__/integration.test.ts +449 -0
- package/__tests__/mcp.test.ts +641 -0
- package/dist/connection-pool.d.ts +36 -0
- package/dist/connection-pool.d.ts.map +1 -0
- package/dist/connection-pool.js +273 -0
- package/dist/connection-pool.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +146 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +318 -0
- package/dist/oauth.js.map +1 -0
- package/dist/prompt-registry.d.ts +90 -0
- package/dist/prompt-registry.d.ts.map +1 -0
- package/dist/prompt-registry.js +209 -0
- package/dist/prompt-registry.js.map +1 -0
- package/dist/rate-limiter.d.ts +86 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +197 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resource-registry.d.ts +144 -0
- package/dist/resource-registry.d.ts.map +1 -0
- package/dist/resource-registry.js +405 -0
- package/dist/resource-registry.js.map +1 -0
- package/dist/sampling.d.ts +102 -0
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +268 -0
- package/dist/sampling.js.map +1 -0
- package/dist/schema-validator.d.ts +30 -0
- package/dist/schema-validator.d.ts.map +1 -0
- package/dist/schema-validator.js +182 -0
- package/dist/schema-validator.js.map +1 -0
- package/dist/server.d.ts +122 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +829 -0
- package/dist/server.js.map +1 -0
- package/dist/session-manager.d.ts +55 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +252 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/task-manager.d.ts +81 -0
- package/dist/task-manager.d.ts.map +1 -0
- package/dist/task-manager.js +337 -0
- package/dist/task-manager.js.map +1 -0
- package/dist/tool-registry.d.ts +88 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +353 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/transport/http.d.ts +55 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +446 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/index.d.ts +50 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +181 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/stdio.d.ts +43 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +194 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport/websocket.d.ts +65 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +314 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/types.d.ts +473 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
- package/src/connection-pool.ts +344 -0
- package/src/index.ts +253 -0
- package/src/oauth.ts +447 -0
- package/src/prompt-registry.ts +296 -0
- package/src/rate-limiter.ts +266 -0
- package/src/resource-registry.ts +530 -0
- package/src/sampling.ts +363 -0
- package/src/schema-validator.ts +213 -0
- package/src/server.ts +1134 -0
- package/src/session-manager.ts +339 -0
- package/src/task-manager.ts +427 -0
- package/src/tool-registry.ts +475 -0
- package/src/transport/http.ts +532 -0
- package/src/transport/index.ts +233 -0
- package/src/transport/stdio.ts +252 -0
- package/src/transport/websocket.ts +396 -0
- package/src/types.ts +664 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @claude-flow/mcp - Task Manager
|
|
3
|
+
*
|
|
4
|
+
* MCP 2025-11-25 compliant async task management
|
|
5
|
+
* Supports: task tracking, progress reporting, cancellation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import type {
|
|
10
|
+
MCPTask,
|
|
11
|
+
TaskState,
|
|
12
|
+
TaskProgress,
|
|
13
|
+
TaskResult,
|
|
14
|
+
MCPError,
|
|
15
|
+
ILogger,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
|
|
18
|
+
export type TaskExecutor<T = unknown> = (
|
|
19
|
+
reportProgress: (progress: TaskProgress) => void,
|
|
20
|
+
signal: AbortSignal
|
|
21
|
+
) => Promise<T>;
|
|
22
|
+
|
|
23
|
+
export interface TaskManagerOptions {
|
|
24
|
+
maxConcurrentTasks?: number;
|
|
25
|
+
taskTimeout?: number;
|
|
26
|
+
cleanupInterval?: number;
|
|
27
|
+
taskRetentionTime?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ManagedTask extends MCPTask {
|
|
31
|
+
executor?: TaskExecutor;
|
|
32
|
+
abortController?: AbortController;
|
|
33
|
+
promise?: Promise<unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TaskManager extends EventEmitter {
|
|
37
|
+
private tasks: Map<string, ManagedTask> = new Map();
|
|
38
|
+
private runningCount = 0;
|
|
39
|
+
private taskCounter = 0;
|
|
40
|
+
private cleanupTimer?: NodeJS.Timeout;
|
|
41
|
+
|
|
42
|
+
private readonly options: Required<TaskManagerOptions>;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
private readonly logger: ILogger,
|
|
46
|
+
options: TaskManagerOptions = {}
|
|
47
|
+
) {
|
|
48
|
+
super();
|
|
49
|
+
this.options = {
|
|
50
|
+
maxConcurrentTasks: options.maxConcurrentTasks ?? 10,
|
|
51
|
+
taskTimeout: options.taskTimeout ?? 300000, // 5 minutes
|
|
52
|
+
cleanupInterval: options.cleanupInterval ?? 60000, // 1 minute
|
|
53
|
+
taskRetentionTime: options.taskRetentionTime ?? 3600000, // 1 hour
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.startCleanupTimer();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new task
|
|
61
|
+
*/
|
|
62
|
+
createTask<T>(executor: TaskExecutor<T>, metadata?: Record<string, unknown>): string {
|
|
63
|
+
const taskId = `task-${++this.taskCounter}-${Date.now()}`;
|
|
64
|
+
const now = new Date();
|
|
65
|
+
|
|
66
|
+
const task: ManagedTask = {
|
|
67
|
+
id: taskId,
|
|
68
|
+
state: 'pending',
|
|
69
|
+
createdAt: now,
|
|
70
|
+
updatedAt: now,
|
|
71
|
+
metadata,
|
|
72
|
+
executor: executor as TaskExecutor,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.tasks.set(taskId, task);
|
|
76
|
+
|
|
77
|
+
this.logger.debug('Task created', { taskId });
|
|
78
|
+
this.emit('task:created', { taskId });
|
|
79
|
+
|
|
80
|
+
// Auto-start if we have capacity
|
|
81
|
+
if (this.runningCount < this.options.maxConcurrentTasks) {
|
|
82
|
+
this.startTask(taskId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return taskId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Start a pending task
|
|
90
|
+
*/
|
|
91
|
+
private async startTask(taskId: string): Promise<void> {
|
|
92
|
+
const task = this.tasks.get(taskId);
|
|
93
|
+
if (!task || task.state !== 'pending') {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (this.runningCount >= this.options.maxConcurrentTasks) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
task.state = 'running';
|
|
102
|
+
task.updatedAt = new Date();
|
|
103
|
+
task.abortController = new AbortController();
|
|
104
|
+
this.runningCount++;
|
|
105
|
+
|
|
106
|
+
this.logger.debug('Task started', { taskId });
|
|
107
|
+
this.emit('task:started', { taskId });
|
|
108
|
+
|
|
109
|
+
// Set up timeout
|
|
110
|
+
const timeoutId = setTimeout(() => {
|
|
111
|
+
this.cancelTask(taskId, 'Task timeout');
|
|
112
|
+
}, this.options.taskTimeout);
|
|
113
|
+
|
|
114
|
+
// Progress reporter
|
|
115
|
+
const reportProgress = (progress: TaskProgress) => {
|
|
116
|
+
task.progress = progress;
|
|
117
|
+
task.updatedAt = new Date();
|
|
118
|
+
this.emit('task:progress', { taskId, progress });
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const result = await task.executor!(reportProgress, task.abortController.signal);
|
|
123
|
+
|
|
124
|
+
clearTimeout(timeoutId);
|
|
125
|
+
|
|
126
|
+
if (task.state === 'running') {
|
|
127
|
+
task.state = 'completed';
|
|
128
|
+
task.result = result;
|
|
129
|
+
task.updatedAt = new Date();
|
|
130
|
+
task.progress = { progress: 100, total: 100 };
|
|
131
|
+
|
|
132
|
+
this.logger.debug('Task completed', { taskId });
|
|
133
|
+
this.emit('task:completed', { taskId, result });
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
clearTimeout(timeoutId);
|
|
137
|
+
|
|
138
|
+
if (task.state === 'running') {
|
|
139
|
+
if (task.abortController.signal.aborted) {
|
|
140
|
+
task.state = 'cancelled';
|
|
141
|
+
this.logger.debug('Task cancelled', { taskId });
|
|
142
|
+
this.emit('task:cancelled', { taskId });
|
|
143
|
+
} else {
|
|
144
|
+
task.state = 'failed';
|
|
145
|
+
task.error = {
|
|
146
|
+
code: -32603,
|
|
147
|
+
message: error instanceof Error ? error.message : 'Task failed',
|
|
148
|
+
};
|
|
149
|
+
this.logger.error('Task failed', { taskId, error });
|
|
150
|
+
this.emit('task:failed', { taskId, error: task.error });
|
|
151
|
+
}
|
|
152
|
+
task.updatedAt = new Date();
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
this.runningCount--;
|
|
156
|
+
task.abortController = undefined;
|
|
157
|
+
|
|
158
|
+
// Start next pending task
|
|
159
|
+
this.startNextPendingTask();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Start next pending task if available
|
|
165
|
+
*/
|
|
166
|
+
private startNextPendingTask(): void {
|
|
167
|
+
for (const [taskId, task] of this.tasks) {
|
|
168
|
+
if (task.state === 'pending') {
|
|
169
|
+
this.startTask(taskId);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Cancel a task
|
|
177
|
+
*/
|
|
178
|
+
cancelTask(taskId: string, reason?: string): boolean {
|
|
179
|
+
const task = this.tasks.get(taskId);
|
|
180
|
+
if (!task) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (task.state !== 'pending' && task.state !== 'running') {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (task.abortController) {
|
|
189
|
+
task.abortController.abort(reason);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (task.state === 'pending') {
|
|
193
|
+
task.state = 'cancelled';
|
|
194
|
+
task.updatedAt = new Date();
|
|
195
|
+
task.error = { code: -32800, message: reason || 'Cancelled' };
|
|
196
|
+
this.emit('task:cancelled', { taskId, reason });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.logger.debug('Task cancel requested', { taskId, reason });
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get task status
|
|
205
|
+
*/
|
|
206
|
+
getTask(taskId: string): TaskResult | undefined {
|
|
207
|
+
const task = this.tasks.get(taskId);
|
|
208
|
+
if (!task) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
taskId: task.id,
|
|
214
|
+
state: task.state,
|
|
215
|
+
progress: task.progress,
|
|
216
|
+
result: task.result,
|
|
217
|
+
error: task.error,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get all tasks
|
|
223
|
+
*/
|
|
224
|
+
getAllTasks(): TaskResult[] {
|
|
225
|
+
return Array.from(this.tasks.values()).map((task) => ({
|
|
226
|
+
taskId: task.id,
|
|
227
|
+
state: task.state,
|
|
228
|
+
progress: task.progress,
|
|
229
|
+
result: task.result,
|
|
230
|
+
error: task.error,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get tasks by state
|
|
236
|
+
*/
|
|
237
|
+
getTasksByState(state: TaskState): TaskResult[] {
|
|
238
|
+
return Array.from(this.tasks.values())
|
|
239
|
+
.filter((task) => task.state === state)
|
|
240
|
+
.map((task) => ({
|
|
241
|
+
taskId: task.id,
|
|
242
|
+
state: task.state,
|
|
243
|
+
progress: task.progress,
|
|
244
|
+
result: task.result,
|
|
245
|
+
error: task.error,
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Wait for task completion
|
|
251
|
+
*/
|
|
252
|
+
async waitForTask(taskId: string, timeout?: number): Promise<TaskResult> {
|
|
253
|
+
const task = this.tasks.get(taskId);
|
|
254
|
+
if (!task) {
|
|
255
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const effectiveTimeout = timeout ?? this.options.taskTimeout;
|
|
259
|
+
|
|
260
|
+
return new Promise((resolve, reject) => {
|
|
261
|
+
const checkState = () => {
|
|
262
|
+
const result = this.getTask(taskId);
|
|
263
|
+
if (!result) {
|
|
264
|
+
reject(new Error(`Task not found: ${taskId}`));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
if (result.state === 'completed' || result.state === 'failed' || result.state === 'cancelled') {
|
|
268
|
+
resolve(result);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (checkState()) return;
|
|
275
|
+
|
|
276
|
+
const timeoutId = setTimeout(() => {
|
|
277
|
+
this.off('task:completed', onComplete);
|
|
278
|
+
this.off('task:failed', onFail);
|
|
279
|
+
this.off('task:cancelled', onCancel);
|
|
280
|
+
reject(new Error(`Wait timeout for task: ${taskId}`));
|
|
281
|
+
}, effectiveTimeout);
|
|
282
|
+
|
|
283
|
+
const cleanup = () => {
|
|
284
|
+
clearTimeout(timeoutId);
|
|
285
|
+
this.off('task:completed', onComplete);
|
|
286
|
+
this.off('task:failed', onFail);
|
|
287
|
+
this.off('task:cancelled', onCancel);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const onComplete = (event: { taskId: string }) => {
|
|
291
|
+
if (event.taskId === taskId) {
|
|
292
|
+
cleanup();
|
|
293
|
+
resolve(this.getTask(taskId)!);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const onFail = (event: { taskId: string }) => {
|
|
298
|
+
if (event.taskId === taskId) {
|
|
299
|
+
cleanup();
|
|
300
|
+
resolve(this.getTask(taskId)!);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const onCancel = (event: { taskId: string }) => {
|
|
305
|
+
if (event.taskId === taskId) {
|
|
306
|
+
cleanup();
|
|
307
|
+
resolve(this.getTask(taskId)!);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
this.on('task:completed', onComplete);
|
|
312
|
+
this.on('task:failed', onFail);
|
|
313
|
+
this.on('task:cancelled', onCancel);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get stats
|
|
319
|
+
*/
|
|
320
|
+
getStats(): {
|
|
321
|
+
totalTasks: number;
|
|
322
|
+
pendingTasks: number;
|
|
323
|
+
runningTasks: number;
|
|
324
|
+
completedTasks: number;
|
|
325
|
+
failedTasks: number;
|
|
326
|
+
cancelledTasks: number;
|
|
327
|
+
} {
|
|
328
|
+
let pending = 0;
|
|
329
|
+
let running = 0;
|
|
330
|
+
let completed = 0;
|
|
331
|
+
let failed = 0;
|
|
332
|
+
let cancelled = 0;
|
|
333
|
+
|
|
334
|
+
for (const task of this.tasks.values()) {
|
|
335
|
+
switch (task.state) {
|
|
336
|
+
case 'pending':
|
|
337
|
+
pending++;
|
|
338
|
+
break;
|
|
339
|
+
case 'running':
|
|
340
|
+
running++;
|
|
341
|
+
break;
|
|
342
|
+
case 'completed':
|
|
343
|
+
completed++;
|
|
344
|
+
break;
|
|
345
|
+
case 'failed':
|
|
346
|
+
failed++;
|
|
347
|
+
break;
|
|
348
|
+
case 'cancelled':
|
|
349
|
+
cancelled++;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
totalTasks: this.tasks.size,
|
|
356
|
+
pendingTasks: pending,
|
|
357
|
+
runningTasks: running,
|
|
358
|
+
completedTasks: completed,
|
|
359
|
+
failedTasks: failed,
|
|
360
|
+
cancelledTasks: cancelled,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Start cleanup timer
|
|
366
|
+
*/
|
|
367
|
+
private startCleanupTimer(): void {
|
|
368
|
+
this.cleanupTimer = setInterval(() => {
|
|
369
|
+
this.cleanupOldTasks();
|
|
370
|
+
}, this.options.cleanupInterval);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Cleanup old completed/failed/cancelled tasks
|
|
375
|
+
*/
|
|
376
|
+
private cleanupOldTasks(): void {
|
|
377
|
+
const now = Date.now();
|
|
378
|
+
const toDelete: string[] = [];
|
|
379
|
+
|
|
380
|
+
for (const [taskId, task] of this.tasks) {
|
|
381
|
+
if (
|
|
382
|
+
(task.state === 'completed' || task.state === 'failed' || task.state === 'cancelled') &&
|
|
383
|
+
now - task.updatedAt.getTime() > this.options.taskRetentionTime
|
|
384
|
+
) {
|
|
385
|
+
toDelete.push(taskId);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const taskId of toDelete) {
|
|
390
|
+
this.tasks.delete(taskId);
|
|
391
|
+
this.logger.debug('Task cleaned up', { taskId });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (toDelete.length > 0) {
|
|
395
|
+
this.emit('tasks:cleaned', { count: toDelete.length });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Destroy the task manager
|
|
401
|
+
*/
|
|
402
|
+
destroy(): void {
|
|
403
|
+
if (this.cleanupTimer) {
|
|
404
|
+
clearInterval(this.cleanupTimer);
|
|
405
|
+
this.cleanupTimer = undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Cancel all running tasks
|
|
409
|
+
for (const task of this.tasks.values()) {
|
|
410
|
+
if (task.abortController) {
|
|
411
|
+
task.abortController.abort('Task manager destroyed');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.tasks.clear();
|
|
416
|
+
this.removeAllListeners();
|
|
417
|
+
|
|
418
|
+
this.logger.debug('Task manager destroyed');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function createTaskManager(
|
|
423
|
+
logger: ILogger,
|
|
424
|
+
options?: TaskManagerOptions
|
|
425
|
+
): TaskManager {
|
|
426
|
+
return new TaskManager(logger, options);
|
|
427
|
+
}
|