@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,449 @@
1
+ /**
2
+ * @claude-flow/mcp - Integration Tests
3
+ *
4
+ * End-to-end tests for MCP 2025-11-25 features
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
+ import {
9
+ createMCPServer,
10
+ createTextResource,
11
+ definePrompt,
12
+ textMessage,
13
+ resourceMessage,
14
+ } from '../src/index.js';
15
+ import type { ILogger, MCPRequest, MCPResponse } from '../src/types.js';
16
+
17
+ const createMockLogger = (): ILogger => ({
18
+ debug: vi.fn(),
19
+ info: vi.fn(),
20
+ warn: vi.fn(),
21
+ error: vi.fn(),
22
+ });
23
+
24
+ describe('MCP 2025-11-25 Integration', () => {
25
+ let server: ReturnType<typeof createMCPServer>;
26
+ let logger: ILogger;
27
+
28
+ beforeEach(async () => {
29
+ logger = createMockLogger();
30
+ server = createMCPServer(
31
+ { name: 'Integration Test Server', transport: 'in-process' },
32
+ logger
33
+ );
34
+ });
35
+
36
+ afterEach(async () => {
37
+ await server.stop();
38
+ });
39
+
40
+ describe('Server Lifecycle', () => {
41
+ it('should start and report healthy', async () => {
42
+ await server.start();
43
+ const health = await server.getHealthStatus();
44
+
45
+ expect(health.healthy).toBe(true);
46
+ expect(health.metrics?.registeredTools).toBeGreaterThan(0);
47
+ });
48
+
49
+ it('should expose all registries', async () => {
50
+ await server.start();
51
+
52
+ expect(server.getResourceRegistry()).toBeDefined();
53
+ expect(server.getPromptRegistry()).toBeDefined();
54
+ expect(server.getTaskManager()).toBeDefined();
55
+ });
56
+ });
57
+
58
+ describe('Resources Integration', () => {
59
+ it('should register and read resources', async () => {
60
+ await server.start();
61
+ const registry = server.getResourceRegistry();
62
+
63
+ // Register multiple resources
64
+ const resources = [
65
+ createTextResource('file://config.json', 'Config', '{"key": "value"}', { mimeType: 'application/json' }),
66
+ createTextResource('file://readme.md', 'README', '# Hello World', { mimeType: 'text/markdown' }),
67
+ createTextResource('file://data.txt', 'Data', 'Some data content'),
68
+ ];
69
+
70
+ for (const { resource, handler } of resources) {
71
+ registry.registerResource(resource, handler);
72
+ }
73
+
74
+ // Verify listing
75
+ const list = registry.list();
76
+ expect(list.resources.length).toBe(3);
77
+
78
+ // Verify reading
79
+ const config = await registry.read('file://config.json');
80
+ expect(config.contents[0].text).toBe('{"key": "value"}');
81
+ expect(config.contents[0].mimeType).toBe('application/json');
82
+
83
+ const readme = await registry.read('file://readme.md');
84
+ expect(readme.contents[0].text).toBe('# Hello World');
85
+ });
86
+
87
+ it('should handle resource subscriptions', async () => {
88
+ await server.start();
89
+ const registry = server.getResourceRegistry();
90
+
91
+ const { resource, handler } = createTextResource('file://live.txt', 'Live', 'Initial');
92
+ registry.registerResource(resource, handler);
93
+
94
+ const callback = vi.fn();
95
+ const subId = registry.subscribe('file://live.txt', callback);
96
+
97
+ expect(subId).toBeDefined();
98
+ expect(registry.getSubscriptionCount('file://live.txt')).toBe(1);
99
+
100
+ // Unsubscribe
101
+ const unsubResult = registry.unsubscribe(subId);
102
+ expect(unsubResult).toBe(true);
103
+ expect(registry.getSubscriptionCount('file://live.txt')).toBe(0);
104
+ });
105
+
106
+ it('should cache resources', async () => {
107
+ await server.start();
108
+ const registry = server.getResourceRegistry();
109
+
110
+ let callCount = 0;
111
+ registry.registerResource(
112
+ { uri: 'file://counter.txt', name: 'Counter', mimeType: 'text/plain' },
113
+ async () => {
114
+ callCount++;
115
+ return [{ uri: 'file://counter.txt', text: `Count: ${callCount}` }];
116
+ }
117
+ );
118
+
119
+ // First read
120
+ await registry.read('file://counter.txt');
121
+ expect(callCount).toBe(1);
122
+
123
+ // Second read should be cached
124
+ await registry.read('file://counter.txt');
125
+ expect(callCount).toBe(1); // Still 1 due to cache
126
+ });
127
+ });
128
+
129
+ describe('Prompts Integration', () => {
130
+ it('should register and execute prompts', async () => {
131
+ await server.start();
132
+ const registry = server.getPromptRegistry();
133
+
134
+ // Register code review prompt
135
+ registry.register(definePrompt(
136
+ 'code_review',
137
+ 'Review code quality',
138
+ async (args) => [
139
+ textMessage('user', `Review this ${args.language || 'code'}:\n\n${args.code}`),
140
+ ],
141
+ {
142
+ title: 'Code Review',
143
+ arguments: [
144
+ { name: 'code', required: true },
145
+ { name: 'language', required: false },
146
+ ],
147
+ }
148
+ ));
149
+
150
+ // Register translation prompt
151
+ registry.register(definePrompt(
152
+ 'translate',
153
+ 'Translate text',
154
+ async (args) => [
155
+ textMessage('user', `Translate to ${args.target}: ${args.text}`),
156
+ ],
157
+ {
158
+ arguments: [
159
+ { name: 'text', required: true },
160
+ { name: 'target', required: true },
161
+ ],
162
+ }
163
+ ));
164
+
165
+ // List prompts
166
+ const list = registry.list();
167
+ expect(list.prompts.length).toBe(2);
168
+
169
+ // Execute code review
170
+ const review = await registry.get('code_review', {
171
+ code: 'const x = 1;',
172
+ language: 'TypeScript',
173
+ });
174
+ expect(review.messages[0].content.type).toBe('text');
175
+ expect((review.messages[0].content as any).text).toContain('TypeScript');
176
+
177
+ // Execute translation
178
+ const translate = await registry.get('translate', {
179
+ text: 'Hello',
180
+ target: 'Spanish',
181
+ });
182
+ expect((translate.messages[0].content as any).text).toContain('Spanish');
183
+ });
184
+
185
+ it('should validate required arguments', async () => {
186
+ await server.start();
187
+ const registry = server.getPromptRegistry();
188
+
189
+ registry.register(definePrompt(
190
+ 'greet',
191
+ 'Greet someone',
192
+ async (args) => [textMessage('user', `Hello ${args.name}`)],
193
+ { arguments: [{ name: 'name', required: true }] }
194
+ ));
195
+
196
+ // Should throw without required argument
197
+ await expect(registry.get('greet', {})).rejects.toThrow('Missing required argument: name');
198
+
199
+ // Should work with argument
200
+ const result = await registry.get('greet', { name: 'World' });
201
+ expect((result.messages[0].content as any).text).toBe('Hello World');
202
+ });
203
+ });
204
+
205
+ describe('Tasks Integration', () => {
206
+ it('should create and complete tasks', async () => {
207
+ await server.start();
208
+ const taskManager = server.getTaskManager();
209
+
210
+ const taskId = taskManager.createTask(async (reportProgress) => {
211
+ reportProgress({ progress: 50, total: 100 });
212
+ return { success: true };
213
+ });
214
+
215
+ const result = await taskManager.waitForTask(taskId, 5000);
216
+ expect(result.state).toBe('completed');
217
+ expect(result.result).toEqual({ success: true });
218
+ });
219
+
220
+ it('should track progress', async () => {
221
+ await server.start();
222
+ const taskManager = server.getTaskManager();
223
+
224
+ const progressUpdates: number[] = [];
225
+
226
+ taskManager.on('task:progress', (event: { taskId: string; progress: { progress: number } }) => {
227
+ progressUpdates.push(event.progress.progress);
228
+ });
229
+
230
+ const taskId = taskManager.createTask(async (reportProgress) => {
231
+ for (let i = 25; i <= 100; i += 25) {
232
+ reportProgress({ progress: i, total: 100 });
233
+ await new Promise(r => setTimeout(r, 10));
234
+ }
235
+ return 'done';
236
+ });
237
+
238
+ await taskManager.waitForTask(taskId, 5000);
239
+ expect(progressUpdates.length).toBeGreaterThan(0);
240
+ });
241
+
242
+ it('should cancel running tasks', async () => {
243
+ await server.start();
244
+ const taskManager = server.getTaskManager();
245
+
246
+ const taskId = taskManager.createTask(async (reportProgress, signal) => {
247
+ await new Promise((resolve, reject) => {
248
+ const timeout = setTimeout(resolve, 10000);
249
+ signal.addEventListener('abort', () => {
250
+ clearTimeout(timeout);
251
+ reject(new Error('Cancelled'));
252
+ });
253
+ });
254
+ });
255
+
256
+ // Cancel immediately
257
+ const cancelled = taskManager.cancelTask(taskId, 'Test cancel');
258
+ expect(cancelled).toBe(true);
259
+
260
+ // Wait a bit and check status
261
+ await new Promise(r => setTimeout(r, 50));
262
+ const status = taskManager.getTask(taskId);
263
+ expect(status?.state).toBe('cancelled');
264
+ });
265
+
266
+ it('should handle concurrent tasks', async () => {
267
+ await server.start();
268
+ const taskManager = server.getTaskManager();
269
+
270
+ const taskIds = [];
271
+ for (let i = 0; i < 5; i++) {
272
+ const id = taskManager.createTask(async () => {
273
+ await new Promise(r => setTimeout(r, 10));
274
+ return `Task ${i} done`;
275
+ });
276
+ taskIds.push(id);
277
+ }
278
+
279
+ // Wait for all
280
+ const results = await Promise.all(
281
+ taskIds.map(id => taskManager.waitForTask(id, 5000))
282
+ );
283
+
284
+ expect(results.every(r => r.state === 'completed')).toBe(true);
285
+ });
286
+ });
287
+
288
+ describe('Full Server Flow', () => {
289
+ it('should handle complete MCP workflow', async () => {
290
+ await server.start();
291
+
292
+ // Setup resources
293
+ const resourceRegistry = server.getResourceRegistry();
294
+ const { resource, handler } = createTextResource(
295
+ 'context://project',
296
+ 'Project Context',
297
+ 'This is a TypeScript project using MCP.'
298
+ );
299
+ resourceRegistry.registerResource(resource, handler);
300
+
301
+ // Setup prompts
302
+ const promptRegistry = server.getPromptRegistry();
303
+ promptRegistry.register(definePrompt(
304
+ 'analyze',
305
+ 'Analyze project',
306
+ async (args) => {
307
+ // Could include embedded resource here
308
+ return [
309
+ textMessage('user', `Analyze: ${args.focus}`),
310
+ ];
311
+ },
312
+ { arguments: [{ name: 'focus', required: true }] }
313
+ ));
314
+
315
+ // Setup long-running task
316
+ const taskManager = server.getTaskManager();
317
+ const analysisTaskId = taskManager.createTask(async (reportProgress) => {
318
+ reportProgress({ progress: 0, message: 'Starting analysis' });
319
+ await new Promise(r => setTimeout(r, 20));
320
+ reportProgress({ progress: 50, message: 'Processing' });
321
+ await new Promise(r => setTimeout(r, 20));
322
+ reportProgress({ progress: 100, message: 'Complete' });
323
+ return { findings: ['All good!'] };
324
+ });
325
+
326
+ // Verify all components work together
327
+ const resourceList = resourceRegistry.list();
328
+ expect(resourceList.resources.length).toBe(1);
329
+
330
+ const promptList = promptRegistry.list();
331
+ expect(promptList.prompts.length).toBe(1);
332
+
333
+ const taskResult = await taskManager.waitForTask(analysisTaskId, 5000);
334
+ expect(taskResult.state).toBe('completed');
335
+ expect((taskResult.result as any).findings).toContain('All good!');
336
+
337
+ // Check metrics
338
+ const health = await server.getHealthStatus();
339
+ expect(health.healthy).toBe(true);
340
+ });
341
+ });
342
+
343
+ describe('Security Validation', () => {
344
+ it('should not expose internal state', async () => {
345
+ await server.start();
346
+ const metrics = server.getMetrics();
347
+
348
+ // Metrics should only contain safe data
349
+ expect(metrics).not.toHaveProperty('_internal');
350
+ expect(metrics).not.toHaveProperty('password');
351
+ expect(metrics).not.toHaveProperty('secret');
352
+ });
353
+
354
+ it('should validate tool inputs', async () => {
355
+ await server.start();
356
+
357
+ // Register a tool with schema
358
+ server.registerTool({
359
+ name: 'secure-tool',
360
+ description: 'A tool with input validation',
361
+ inputSchema: {
362
+ type: 'object',
363
+ properties: {
364
+ allowed: { type: 'string', maxLength: 100 },
365
+ },
366
+ required: ['allowed'],
367
+ },
368
+ handler: async (input) => ({ received: input }),
369
+ });
370
+
371
+ // Tool registry should have it
372
+ const tools = server.getMetrics().toolInvocations;
373
+ expect(tools).toBeDefined();
374
+ });
375
+
376
+ it('should handle resource access errors gracefully', async () => {
377
+ await server.start();
378
+ const registry = server.getResourceRegistry();
379
+
380
+ // Try to read non-existent resource
381
+ await expect(registry.read('file://nonexistent.txt'))
382
+ .rejects.toThrow('Resource not found');
383
+ });
384
+
385
+ it('should prevent duplicate subscriptions overflow', async () => {
386
+ await server.start();
387
+ const registry = server.getResourceRegistry();
388
+
389
+ const { resource, handler } = createTextResource('file://test.txt', 'Test', 'content');
390
+ registry.registerResource(resource, handler);
391
+
392
+ // Subscribe many times (should be limited by maxSubscriptionsPerResource)
393
+ for (let i = 0; i < 100; i++) {
394
+ registry.subscribe('file://test.txt', vi.fn());
395
+ }
396
+
397
+ expect(registry.getSubscriptionCount('file://test.txt')).toBe(100);
398
+ });
399
+
400
+ it('should enforce cache size limits (CVE fix)', async () => {
401
+ await server.start();
402
+ const registry = server.getResourceRegistry();
403
+
404
+ // Register many resources to test cache eviction
405
+ for (let i = 0; i < 10; i++) {
406
+ const { resource, handler } = createTextResource(
407
+ `file://cache-test-${i}.txt`,
408
+ `Cache Test ${i}`,
409
+ `Content ${i}`
410
+ );
411
+ registry.registerResource(resource, handler);
412
+ }
413
+
414
+ // Read all resources to populate cache
415
+ for (let i = 0; i < 10; i++) {
416
+ await registry.read(`file://cache-test-${i}.txt`);
417
+ }
418
+
419
+ // Cache should be limited (default 1000, but should work)
420
+ const stats = registry.getStats();
421
+ expect(stats.cacheSize).toBeLessThanOrEqual(1000);
422
+ });
423
+
424
+ it('should escape regex in template matching (ReDoS fix)', async () => {
425
+ await server.start();
426
+ const registry = server.getResourceRegistry();
427
+
428
+ // Register a template with potentially dangerous regex chars
429
+ registry.registerTemplate(
430
+ {
431
+ uriTemplate: 'db://users/{id}',
432
+ name: 'User Data',
433
+ mimeType: 'application/json',
434
+ },
435
+ async (uri) => [{ uri, text: '{}' }]
436
+ );
437
+
438
+ // This should not cause ReDoS - test with various inputs
439
+ const startTime = Date.now();
440
+ expect(registry.hasResource('db://users/123')).toBe(true);
441
+ expect(registry.hasResource('db://users/abc')).toBe(true);
442
+ expect(registry.hasResource('invalid://path')).toBe(false);
443
+ const elapsed = Date.now() - startTime;
444
+
445
+ // Should complete quickly (< 100ms), not hang due to ReDoS
446
+ expect(elapsed).toBeLessThan(100);
447
+ });
448
+ });
449
+ });