@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,363 @@
1
+ /**
2
+ * @claude-flow/mcp - Sampling (Server-Initiated LLM)
3
+ *
4
+ * MCP 2025-11-25 compliant sampling for server-initiated LLM calls
5
+ */
6
+
7
+ import { EventEmitter } from 'events';
8
+ import type {
9
+ SamplingMessage,
10
+ ModelPreferences,
11
+ CreateMessageRequest,
12
+ CreateMessageResult,
13
+ PromptContent,
14
+ ILogger,
15
+ } from './types.js';
16
+
17
+ /**
18
+ * External LLM provider interface
19
+ */
20
+ export interface LLMProvider {
21
+ name: string;
22
+ createMessage(request: CreateMessageRequest): Promise<CreateMessageResult>;
23
+ isAvailable(): Promise<boolean>;
24
+ }
25
+
26
+ /**
27
+ * Sampling configuration
28
+ */
29
+ export interface SamplingConfig {
30
+ /** Default model preferences */
31
+ defaultModelPreferences?: ModelPreferences;
32
+ /** Maximum tokens for any request */
33
+ maxTokensLimit?: number;
34
+ /** Default temperature */
35
+ defaultTemperature?: number;
36
+ /** Timeout for LLM calls (ms) */
37
+ timeout?: number;
38
+ /** Enable request logging */
39
+ enableLogging?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Sampling request context
44
+ */
45
+ export interface SamplingContext {
46
+ sessionId: string;
47
+ serverId?: string;
48
+ metadata?: Record<string, unknown>;
49
+ }
50
+
51
+ const DEFAULT_CONFIG: Required<SamplingConfig> = {
52
+ defaultModelPreferences: {
53
+ intelligencePriority: 0.5,
54
+ speedPriority: 0.3,
55
+ costPriority: 0.2,
56
+ },
57
+ maxTokensLimit: 4096,
58
+ defaultTemperature: 0.7,
59
+ timeout: 30000,
60
+ enableLogging: true,
61
+ };
62
+
63
+ export class SamplingManager extends EventEmitter {
64
+ private readonly config: Required<SamplingConfig>;
65
+ private providers: Map<string, LLMProvider> = new Map();
66
+ private defaultProvider?: string;
67
+ private requestCount = 0;
68
+ private totalTokens = 0;
69
+
70
+ constructor(
71
+ private readonly logger: ILogger,
72
+ config: Partial<SamplingConfig> = {}
73
+ ) {
74
+ super();
75
+ this.config = { ...DEFAULT_CONFIG, ...config };
76
+ }
77
+
78
+ /**
79
+ * Register an LLM provider
80
+ */
81
+ registerProvider(provider: LLMProvider, isDefault: boolean = false): void {
82
+ this.providers.set(provider.name, provider);
83
+ if (isDefault || !this.defaultProvider) {
84
+ this.defaultProvider = provider.name;
85
+ }
86
+ this.logger.info('LLM provider registered', { name: provider.name, isDefault });
87
+ this.emit('provider:registered', { name: provider.name });
88
+ }
89
+
90
+ /**
91
+ * Unregister a provider
92
+ */
93
+ unregisterProvider(name: string): boolean {
94
+ const removed = this.providers.delete(name);
95
+ if (removed && this.defaultProvider === name) {
96
+ this.defaultProvider = this.providers.keys().next().value;
97
+ }
98
+ return removed;
99
+ }
100
+
101
+ /**
102
+ * Create a message (sampling/createMessage)
103
+ */
104
+ async createMessage(
105
+ request: CreateMessageRequest,
106
+ context?: SamplingContext
107
+ ): Promise<CreateMessageResult> {
108
+ const startTime = Date.now();
109
+ this.requestCount++;
110
+
111
+ // Validate request
112
+ this.validateRequest(request);
113
+
114
+ // Select provider
115
+ const provider = this.selectProvider(request.modelPreferences);
116
+ if (!provider) {
117
+ throw new Error('No LLM provider available');
118
+ }
119
+
120
+ // Apply defaults
121
+ const fullRequest = this.applyDefaults(request);
122
+
123
+ if (this.config.enableLogging) {
124
+ this.logger.debug('Sampling request', {
125
+ provider: provider.name,
126
+ messageCount: request.messages.length,
127
+ maxTokens: fullRequest.maxTokens,
128
+ sessionId: context?.sessionId,
129
+ });
130
+ }
131
+
132
+ this.emit('sampling:start', { provider: provider.name, context });
133
+
134
+ try {
135
+ // Call provider with timeout
136
+ const result = await this.callWithTimeout(
137
+ provider.createMessage(fullRequest),
138
+ this.config.timeout
139
+ );
140
+
141
+ const duration = Date.now() - startTime;
142
+
143
+ if (this.config.enableLogging) {
144
+ this.logger.info('Sampling complete', {
145
+ provider: provider.name,
146
+ duration: `${duration}ms`,
147
+ stopReason: result.stopReason,
148
+ });
149
+ }
150
+
151
+ this.emit('sampling:complete', {
152
+ provider: provider.name,
153
+ duration,
154
+ result,
155
+ context,
156
+ });
157
+
158
+ return result;
159
+ } catch (error) {
160
+ const duration = Date.now() - startTime;
161
+
162
+ this.logger.error('Sampling failed', {
163
+ provider: provider.name,
164
+ duration: `${duration}ms`,
165
+ error,
166
+ });
167
+
168
+ this.emit('sampling:error', {
169
+ provider: provider.name,
170
+ duration,
171
+ error,
172
+ context,
173
+ });
174
+
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Check if sampling is available
181
+ */
182
+ async isAvailable(): Promise<boolean> {
183
+ if (this.providers.size === 0) {
184
+ return false;
185
+ }
186
+
187
+ for (const provider of this.providers.values()) {
188
+ if (await provider.isAvailable()) {
189
+ return true;
190
+ }
191
+ }
192
+
193
+ return false;
194
+ }
195
+
196
+ /**
197
+ * Get available providers
198
+ */
199
+ getProviders(): string[] {
200
+ return Array.from(this.providers.keys());
201
+ }
202
+
203
+ /**
204
+ * Get stats
205
+ */
206
+ getStats(): {
207
+ requestCount: number;
208
+ totalTokens: number;
209
+ providerCount: number;
210
+ defaultProvider?: string;
211
+ } {
212
+ return {
213
+ requestCount: this.requestCount,
214
+ totalTokens: this.totalTokens,
215
+ providerCount: this.providers.size,
216
+ defaultProvider: this.defaultProvider,
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Validate sampling request
222
+ */
223
+ private validateRequest(request: CreateMessageRequest): void {
224
+ if (!request.messages || request.messages.length === 0) {
225
+ throw new Error('Messages are required');
226
+ }
227
+
228
+ if (request.maxTokens > this.config.maxTokensLimit) {
229
+ throw new Error(`maxTokens exceeds limit of ${this.config.maxTokensLimit}`);
230
+ }
231
+
232
+ if (request.temperature !== undefined && (request.temperature < 0 || request.temperature > 2)) {
233
+ throw new Error('Temperature must be between 0 and 2');
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Select provider based on preferences
239
+ */
240
+ private selectProvider(preferences?: ModelPreferences): LLMProvider | undefined {
241
+ // If hints provided, try to find matching provider
242
+ if (preferences?.hints) {
243
+ for (const hint of preferences.hints) {
244
+ if (hint.name && this.providers.has(hint.name)) {
245
+ return this.providers.get(hint.name);
246
+ }
247
+ }
248
+ }
249
+
250
+ // Use default provider
251
+ if (this.defaultProvider) {
252
+ return this.providers.get(this.defaultProvider);
253
+ }
254
+
255
+ // Return first available
256
+ return this.providers.values().next().value;
257
+ }
258
+
259
+ /**
260
+ * Apply default values to request
261
+ */
262
+ private applyDefaults(request: CreateMessageRequest): CreateMessageRequest {
263
+ return {
264
+ ...request,
265
+ modelPreferences: request.modelPreferences || this.config.defaultModelPreferences,
266
+ temperature: request.temperature ?? this.config.defaultTemperature,
267
+ maxTokens: Math.min(request.maxTokens, this.config.maxTokensLimit),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Call with timeout
273
+ */
274
+ private async callWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
275
+ return Promise.race([
276
+ promise,
277
+ new Promise<T>((_, reject) => {
278
+ setTimeout(() => reject(new Error('Sampling timeout')), timeout);
279
+ }),
280
+ ]);
281
+ }
282
+ }
283
+
284
+ export function createSamplingManager(
285
+ logger: ILogger,
286
+ config?: Partial<SamplingConfig>
287
+ ): SamplingManager {
288
+ return new SamplingManager(logger, config);
289
+ }
290
+
291
+ /**
292
+ * Create a mock LLM provider for testing
293
+ */
294
+ export function createMockProvider(name: string = 'mock'): LLMProvider {
295
+ return {
296
+ name,
297
+ async createMessage(request: CreateMessageRequest): Promise<CreateMessageResult> {
298
+ // Simulate processing time
299
+ await new Promise((r) => setTimeout(r, 100));
300
+
301
+ return {
302
+ role: 'assistant',
303
+ content: {
304
+ type: 'text',
305
+ text: `Mock response to: ${JSON.stringify(request.messages[0]?.content)}`,
306
+ },
307
+ model: `${name}-model`,
308
+ stopReason: 'endTurn',
309
+ };
310
+ },
311
+ async isAvailable(): Promise<boolean> {
312
+ return true;
313
+ },
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Create an Anthropic provider (requires API key)
319
+ */
320
+ export function createAnthropicProvider(apiKey: string): LLMProvider {
321
+ return {
322
+ name: 'anthropic',
323
+ async createMessage(request: CreateMessageRequest): Promise<CreateMessageResult> {
324
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
325
+ method: 'POST',
326
+ headers: {
327
+ 'Content-Type': 'application/json',
328
+ 'x-api-key': apiKey,
329
+ 'anthropic-version': '2023-06-01',
330
+ },
331
+ body: JSON.stringify({
332
+ model: 'claude-3-haiku-20240307',
333
+ max_tokens: request.maxTokens,
334
+ temperature: request.temperature,
335
+ system: request.systemPrompt,
336
+ messages: request.messages.map((m) => ({
337
+ role: m.role,
338
+ content: m.content.type === 'text' ? (m.content as any).text : m.content,
339
+ })),
340
+ }),
341
+ });
342
+
343
+ if (!response.ok) {
344
+ throw new Error(`Anthropic API error: ${response.status}`);
345
+ }
346
+
347
+ const data = await response.json() as any;
348
+
349
+ return {
350
+ role: 'assistant',
351
+ content: {
352
+ type: 'text',
353
+ text: data.content[0]?.text || '',
354
+ },
355
+ model: data.model,
356
+ stopReason: data.stop_reason === 'end_turn' ? 'endTurn' : 'maxTokens',
357
+ };
358
+ },
359
+ async isAvailable(): Promise<boolean> {
360
+ return !!apiKey;
361
+ },
362
+ };
363
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @claude-flow/mcp - JSON Schema Validator
3
+ *
4
+ * Lightweight JSON Schema validation for tool inputs
5
+ * Implements JSON Schema Draft 2020-12 subset
6
+ */
7
+
8
+ import type { JSONSchema } from './types.js';
9
+
10
+ export interface ValidationError {
11
+ path: string;
12
+ message: string;
13
+ keyword: string;
14
+ params?: Record<string, unknown>;
15
+ }
16
+
17
+ export interface ValidationResult {
18
+ valid: boolean;
19
+ errors: ValidationError[];
20
+ }
21
+
22
+ /**
23
+ * Validate data against JSON Schema
24
+ */
25
+ export function validateSchema(
26
+ data: unknown,
27
+ schema: JSONSchema,
28
+ path: string = ''
29
+ ): ValidationResult {
30
+ const errors: ValidationError[] = [];
31
+
32
+ // Type validation
33
+ if (schema.type) {
34
+ const typeValid = validateType(data, schema.type);
35
+ if (!typeValid) {
36
+ errors.push({
37
+ path: path || 'root',
38
+ message: `Expected type "${schema.type}", got "${typeof data}"`,
39
+ keyword: 'type',
40
+ params: { expected: schema.type, actual: typeof data },
41
+ });
42
+ return { valid: false, errors };
43
+ }
44
+ }
45
+
46
+ // Null check
47
+ if (data === null || data === undefined) {
48
+ if (schema.type && schema.type !== 'null') {
49
+ errors.push({
50
+ path: path || 'root',
51
+ message: 'Value cannot be null or undefined',
52
+ keyword: 'type',
53
+ });
54
+ }
55
+ return { valid: errors.length === 0, errors };
56
+ }
57
+
58
+ // String validations
59
+ if (schema.type === 'string' && typeof data === 'string') {
60
+ if (schema.minLength !== undefined && data.length < schema.minLength) {
61
+ errors.push({
62
+ path,
63
+ message: `String length must be >= ${schema.minLength}`,
64
+ keyword: 'minLength',
65
+ params: { limit: schema.minLength, actual: data.length },
66
+ });
67
+ }
68
+ if (schema.maxLength !== undefined && data.length > schema.maxLength) {
69
+ errors.push({
70
+ path,
71
+ message: `String length must be <= ${schema.maxLength}`,
72
+ keyword: 'maxLength',
73
+ params: { limit: schema.maxLength, actual: data.length },
74
+ });
75
+ }
76
+ if (schema.pattern) {
77
+ const regex = new RegExp(schema.pattern);
78
+ if (!regex.test(data)) {
79
+ errors.push({
80
+ path,
81
+ message: `String must match pattern "${schema.pattern}"`,
82
+ keyword: 'pattern',
83
+ params: { pattern: schema.pattern },
84
+ });
85
+ }
86
+ }
87
+ if (schema.enum && !schema.enum.includes(data)) {
88
+ errors.push({
89
+ path,
90
+ message: `Value must be one of: ${schema.enum.join(', ')}`,
91
+ keyword: 'enum',
92
+ params: { allowedValues: schema.enum },
93
+ });
94
+ }
95
+ }
96
+
97
+ // Number validations
98
+ if ((schema.type === 'number' || schema.type === 'integer') && typeof data === 'number') {
99
+ if (schema.type === 'integer' && !Number.isInteger(data)) {
100
+ errors.push({
101
+ path,
102
+ message: 'Value must be an integer',
103
+ keyword: 'type',
104
+ });
105
+ }
106
+ if (schema.minimum !== undefined && data < schema.minimum) {
107
+ errors.push({
108
+ path,
109
+ message: `Value must be >= ${schema.minimum}`,
110
+ keyword: 'minimum',
111
+ params: { limit: schema.minimum, actual: data },
112
+ });
113
+ }
114
+ if (schema.maximum !== undefined && data > schema.maximum) {
115
+ errors.push({
116
+ path,
117
+ message: `Value must be <= ${schema.maximum}`,
118
+ keyword: 'maximum',
119
+ params: { limit: schema.maximum, actual: data },
120
+ });
121
+ }
122
+ }
123
+
124
+ // Array validations
125
+ if (schema.type === 'array' && Array.isArray(data)) {
126
+ if (schema.items) {
127
+ for (let i = 0; i < data.length; i++) {
128
+ const itemResult = validateSchema(data[i], schema.items, `${path}[${i}]`);
129
+ errors.push(...itemResult.errors);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Object validations
135
+ if (schema.type === 'object' && typeof data === 'object' && !Array.isArray(data)) {
136
+ const obj = data as Record<string, unknown>;
137
+
138
+ // Required properties
139
+ if (schema.required) {
140
+ for (const requiredProp of schema.required) {
141
+ if (!(requiredProp in obj)) {
142
+ errors.push({
143
+ path: path ? `${path}.${requiredProp}` : requiredProp,
144
+ message: `Required property "${requiredProp}" is missing`,
145
+ keyword: 'required',
146
+ params: { missingProperty: requiredProp },
147
+ });
148
+ }
149
+ }
150
+ }
151
+
152
+ // Property validations
153
+ if (schema.properties) {
154
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
155
+ if (propName in obj) {
156
+ const propPath = path ? `${path}.${propName}` : propName;
157
+ const propResult = validateSchema(obj[propName], propSchema, propPath);
158
+ errors.push(...propResult.errors);
159
+ }
160
+ }
161
+ }
162
+
163
+ // Additional properties check
164
+ if (schema.additionalProperties === false && schema.properties) {
165
+ const allowedProps = new Set(Object.keys(schema.properties));
166
+ for (const propName of Object.keys(obj)) {
167
+ if (!allowedProps.has(propName)) {
168
+ errors.push({
169
+ path: path ? `${path}.${propName}` : propName,
170
+ message: `Additional property "${propName}" is not allowed`,
171
+ keyword: 'additionalProperties',
172
+ params: { additionalProperty: propName },
173
+ });
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ return { valid: errors.length === 0, errors };
180
+ }
181
+
182
+ /**
183
+ * Validate type
184
+ */
185
+ function validateType(data: unknown, expectedType: string): boolean {
186
+ if (expectedType === 'null') {
187
+ return data === null;
188
+ }
189
+ if (expectedType === 'array') {
190
+ return Array.isArray(data);
191
+ }
192
+ if (expectedType === 'integer') {
193
+ return typeof data === 'number' && Number.isInteger(data);
194
+ }
195
+ if (expectedType === 'object') {
196
+ return typeof data === 'object' && data !== null && !Array.isArray(data);
197
+ }
198
+ return typeof data === expectedType;
199
+ }
200
+
201
+ /**
202
+ * Format validation errors for display
203
+ */
204
+ export function formatValidationErrors(errors: ValidationError[]): string {
205
+ return errors.map((e) => `${e.path}: ${e.message}`).join('; ');
206
+ }
207
+
208
+ /**
209
+ * Create a validator function for a specific schema
210
+ */
211
+ export function createValidator(schema: JSONSchema): (data: unknown) => ValidationResult {
212
+ return (data: unknown) => validateSchema(data, schema);
213
+ }