@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.
Files changed (92) hide show
  1. package/.agentic-flow/intelligence.json +16 -0
  2. package/README.md +428 -0
  3. package/__tests__/integration.test.ts +449 -0
  4. package/__tests__/mcp.test.ts +641 -0
  5. package/dist/connection-pool.d.ts +36 -0
  6. package/dist/connection-pool.d.ts.map +1 -0
  7. package/dist/connection-pool.js +273 -0
  8. package/dist/connection-pool.js.map +1 -0
  9. package/dist/index.d.ts +75 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +85 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/oauth.d.ts +146 -0
  14. package/dist/oauth.d.ts.map +1 -0
  15. package/dist/oauth.js +318 -0
  16. package/dist/oauth.js.map +1 -0
  17. package/dist/prompt-registry.d.ts +90 -0
  18. package/dist/prompt-registry.d.ts.map +1 -0
  19. package/dist/prompt-registry.js +209 -0
  20. package/dist/prompt-registry.js.map +1 -0
  21. package/dist/rate-limiter.d.ts +86 -0
  22. package/dist/rate-limiter.d.ts.map +1 -0
  23. package/dist/rate-limiter.js +197 -0
  24. package/dist/rate-limiter.js.map +1 -0
  25. package/dist/resource-registry.d.ts +144 -0
  26. package/dist/resource-registry.d.ts.map +1 -0
  27. package/dist/resource-registry.js +405 -0
  28. package/dist/resource-registry.js.map +1 -0
  29. package/dist/sampling.d.ts +102 -0
  30. package/dist/sampling.d.ts.map +1 -0
  31. package/dist/sampling.js +268 -0
  32. package/dist/sampling.js.map +1 -0
  33. package/dist/schema-validator.d.ts +30 -0
  34. package/dist/schema-validator.d.ts.map +1 -0
  35. package/dist/schema-validator.js +182 -0
  36. package/dist/schema-validator.js.map +1 -0
  37. package/dist/server.d.ts +122 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +829 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/session-manager.d.ts +55 -0
  42. package/dist/session-manager.d.ts.map +1 -0
  43. package/dist/session-manager.js +252 -0
  44. package/dist/session-manager.js.map +1 -0
  45. package/dist/task-manager.d.ts +81 -0
  46. package/dist/task-manager.d.ts.map +1 -0
  47. package/dist/task-manager.js +337 -0
  48. package/dist/task-manager.js.map +1 -0
  49. package/dist/tool-registry.d.ts +88 -0
  50. package/dist/tool-registry.d.ts.map +1 -0
  51. package/dist/tool-registry.js +353 -0
  52. package/dist/tool-registry.js.map +1 -0
  53. package/dist/transport/http.d.ts +55 -0
  54. package/dist/transport/http.d.ts.map +1 -0
  55. package/dist/transport/http.js +446 -0
  56. package/dist/transport/http.js.map +1 -0
  57. package/dist/transport/index.d.ts +50 -0
  58. package/dist/transport/index.d.ts.map +1 -0
  59. package/dist/transport/index.js +181 -0
  60. package/dist/transport/index.js.map +1 -0
  61. package/dist/transport/stdio.d.ts +43 -0
  62. package/dist/transport/stdio.d.ts.map +1 -0
  63. package/dist/transport/stdio.js +194 -0
  64. package/dist/transport/stdio.js.map +1 -0
  65. package/dist/transport/websocket.d.ts +65 -0
  66. package/dist/transport/websocket.d.ts.map +1 -0
  67. package/dist/transport/websocket.js +314 -0
  68. package/dist/transport/websocket.js.map +1 -0
  69. package/dist/types.d.ts +473 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +40 -0
  72. package/dist/types.js.map +1 -0
  73. package/package.json +42 -0
  74. package/src/connection-pool.ts +344 -0
  75. package/src/index.ts +253 -0
  76. package/src/oauth.ts +447 -0
  77. package/src/prompt-registry.ts +296 -0
  78. package/src/rate-limiter.ts +266 -0
  79. package/src/resource-registry.ts +530 -0
  80. package/src/sampling.ts +363 -0
  81. package/src/schema-validator.ts +213 -0
  82. package/src/server.ts +1134 -0
  83. package/src/session-manager.ts +339 -0
  84. package/src/task-manager.ts +427 -0
  85. package/src/tool-registry.ts +475 -0
  86. package/src/transport/http.ts +532 -0
  87. package/src/transport/index.ts +233 -0
  88. package/src/transport/stdio.ts +252 -0
  89. package/src/transport/websocket.ts +396 -0
  90. package/src/types.ts +664 -0
  91. package/tsconfig.json +20 -0
  92. 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
+ }