claude-flow-novice 1.5.12 → 1.5.13
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/.claude-flow-novice/dist/mcp/auth.js +347 -0
- package/.claude-flow-novice/dist/mcp/claude-code-wrapper.js +717 -0
- package/.claude-flow-novice/dist/mcp/claude-flow-tools.js +1365 -0
- package/.claude-flow-novice/dist/mcp/client.js +201 -0
- package/.claude-flow-novice/dist/mcp/index.js +192 -0
- package/.claude-flow-novice/dist/mcp/integrate-wrapper.js +85 -0
- package/.claude-flow-novice/dist/mcp/lifecycle-manager.js +348 -0
- package/.claude-flow-novice/dist/mcp/load-balancer.js +386 -0
- package/.claude-flow-novice/dist/mcp/mcp-config-manager.js +1362 -0
- package/.claude-flow-novice/dist/mcp/mcp-server-novice-simplified.js +583 -0
- package/.claude-flow-novice/dist/mcp/mcp-server-novice.js +723 -0
- package/.claude-flow-novice/dist/mcp/mcp-server-sdk.js +649 -0
- package/.claude-flow-novice/dist/mcp/mcp-server.js +2256 -0
- package/.claude-flow-novice/dist/mcp/orchestration-integration.js +800 -0
- package/.claude-flow-novice/dist/mcp/performance-monitor.js +489 -0
- package/.claude-flow-novice/dist/mcp/protocol-manager.js +376 -0
- package/.claude-flow-novice/dist/mcp/router.js +220 -0
- package/.claude-flow-novice/dist/mcp/ruv-swarm-tools.js +671 -0
- package/.claude-flow-novice/dist/mcp/ruv-swarm-wrapper.js +254 -0
- package/.claude-flow-novice/dist/mcp/server-with-wrapper.js +32 -0
- package/.claude-flow-novice/dist/mcp/server-wrapper-mode.js +26 -0
- package/.claude-flow-novice/dist/mcp/server.js +539 -0
- package/.claude-flow-novice/dist/mcp/session-manager.js +338 -0
- package/.claude-flow-novice/dist/mcp/sparc-modes.js +455 -0
- package/.claude-flow-novice/dist/mcp/swarm-tools.js +903 -0
- package/.claude-flow-novice/dist/mcp/tools.js +426 -0
- package/.claude-flow-novice/dist/src/cli/commands/swarm.js +23 -1
- package/.claude-flow-novice/dist/src/cli/commands/swarm.js.map +1 -1
- package/.claude-flow-novice/dist/src/cli/simple-commands/init/templates/CLAUDE.md +40 -101
- package/.claude-flow-novice/dist/src/coordination/swarm-coordinator-factory.js +36 -0
- package/.claude-flow-novice/dist/src/coordination/swarm-coordinator-factory.js.map +1 -0
- package/.claude-flow-novice/dist/src/validators/index.js +12 -0
- package/.claude-flow-novice/dist/src/validators/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/validators/swarm-init-validator.js +261 -0
- package/.claude-flow-novice/dist/src/validators/swarm-init-validator.js.map +1 -0
- package/.claude-flow-novice/dist/src/validators/todowrite-batching-validator.js +204 -0
- package/.claude-flow-novice/dist/src/validators/todowrite-batching-validator.js.map +1 -0
- package/.claude-flow-novice/dist/src/validators/todowrite-integration.js +189 -0
- package/.claude-flow-novice/dist/src/validators/todowrite-integration.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load balancer and rate limiting for MCP
|
|
3
|
+
*/ import { MCPError } from '../utils/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Circuit breaker state
|
|
6
|
+
*/ var CircuitBreakerState = /*#__PURE__*/ function(CircuitBreakerState) {
|
|
7
|
+
CircuitBreakerState["CLOSED"] = "closed";
|
|
8
|
+
CircuitBreakerState["OPEN"] = "open";
|
|
9
|
+
CircuitBreakerState["HALF_OPEN"] = "half_open";
|
|
10
|
+
return CircuitBreakerState;
|
|
11
|
+
}(CircuitBreakerState || {});
|
|
12
|
+
/**
|
|
13
|
+
* Rate limiter using token bucket algorithm
|
|
14
|
+
*/ let RateLimiter = class RateLimiter {
|
|
15
|
+
maxTokens;
|
|
16
|
+
refillRate;
|
|
17
|
+
tokens;
|
|
18
|
+
lastRefill;
|
|
19
|
+
constructor(maxTokens, refillRate){
|
|
20
|
+
this.maxTokens = maxTokens;
|
|
21
|
+
this.refillRate = refillRate;
|
|
22
|
+
this.tokens = maxTokens;
|
|
23
|
+
this.lastRefill = Date.now();
|
|
24
|
+
}
|
|
25
|
+
tryConsume(tokens = 1) {
|
|
26
|
+
this.refill();
|
|
27
|
+
if (this.tokens >= tokens) {
|
|
28
|
+
this.tokens -= tokens;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
refill() {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const timePassed = (now - this.lastRefill) / 1000;
|
|
36
|
+
const tokensToAdd = Math.floor(timePassed * this.refillRate);
|
|
37
|
+
if (tokensToAdd > 0) {
|
|
38
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
|
|
39
|
+
this.lastRefill = now;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getTokens() {
|
|
43
|
+
this.refill();
|
|
44
|
+
return this.tokens;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Circuit breaker implementation
|
|
49
|
+
*/ let CircuitBreaker = class CircuitBreaker {
|
|
50
|
+
failureThreshold;
|
|
51
|
+
recoveryTimeout;
|
|
52
|
+
halfOpenMaxRequests;
|
|
53
|
+
state = "closed";
|
|
54
|
+
failureCount = 0;
|
|
55
|
+
lastFailureTime = 0;
|
|
56
|
+
successCount = 0;
|
|
57
|
+
constructor(failureThreshold, recoveryTimeout, halfOpenMaxRequests = 3){
|
|
58
|
+
this.failureThreshold = failureThreshold;
|
|
59
|
+
this.recoveryTimeout = recoveryTimeout;
|
|
60
|
+
this.halfOpenMaxRequests = halfOpenMaxRequests;
|
|
61
|
+
}
|
|
62
|
+
canExecute() {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
switch(this.state){
|
|
65
|
+
case "closed":
|
|
66
|
+
return true;
|
|
67
|
+
case "open":
|
|
68
|
+
if (now - this.lastFailureTime >= this.recoveryTimeout) {
|
|
69
|
+
this.state = "half_open";
|
|
70
|
+
this.successCount = 0;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
case "half_open":
|
|
75
|
+
return this.successCount < this.halfOpenMaxRequests;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
recordSuccess() {
|
|
81
|
+
if (this.state === "half_open") {
|
|
82
|
+
this.successCount++;
|
|
83
|
+
if (this.successCount >= this.halfOpenMaxRequests) {
|
|
84
|
+
this.state = "closed";
|
|
85
|
+
this.failureCount = 0;
|
|
86
|
+
}
|
|
87
|
+
} else if (this.state === "closed") {
|
|
88
|
+
this.failureCount = 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
recordFailure() {
|
|
92
|
+
this.failureCount++;
|
|
93
|
+
this.lastFailureTime = Date.now();
|
|
94
|
+
if (this.state === "half_open") {
|
|
95
|
+
this.state = "open";
|
|
96
|
+
} else if (this.state === "closed" && this.failureCount >= this.failureThreshold) {
|
|
97
|
+
this.state = "open";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
getState() {
|
|
101
|
+
return this.state;
|
|
102
|
+
}
|
|
103
|
+
getMetrics() {
|
|
104
|
+
return {
|
|
105
|
+
state: this.state,
|
|
106
|
+
failureCount: this.failureCount,
|
|
107
|
+
successCount: this.successCount
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Load balancer implementation
|
|
113
|
+
*/ export class LoadBalancer {
|
|
114
|
+
config;
|
|
115
|
+
logger;
|
|
116
|
+
rateLimiter;
|
|
117
|
+
circuitBreaker;
|
|
118
|
+
sessionRateLimiters = new Map();
|
|
119
|
+
metrics;
|
|
120
|
+
requestTimes = [];
|
|
121
|
+
requestsInLastSecond = 0;
|
|
122
|
+
lastSecondTimestamp = 0;
|
|
123
|
+
constructor(config, logger){
|
|
124
|
+
this.config = config;
|
|
125
|
+
this.logger = logger;
|
|
126
|
+
this.rateLimiter = new RateLimiter(config.maxRequestsPerSecond, config.maxRequestsPerSecond);
|
|
127
|
+
this.circuitBreaker = new CircuitBreaker(config.circuitBreakerThreshold, 30000);
|
|
128
|
+
this.metrics = {
|
|
129
|
+
totalRequests: 0,
|
|
130
|
+
successfulRequests: 0,
|
|
131
|
+
failedRequests: 0,
|
|
132
|
+
rateLimitedRequests: 0,
|
|
133
|
+
averageResponseTime: 0,
|
|
134
|
+
requestsPerSecond: 0,
|
|
135
|
+
circuitBreakerTrips: 0,
|
|
136
|
+
lastReset: new Date()
|
|
137
|
+
};
|
|
138
|
+
// Clean up old session rate limiters periodically
|
|
139
|
+
setInterval(()=>{
|
|
140
|
+
this.cleanupSessionRateLimiters();
|
|
141
|
+
}, 300000); // Every 5 minutes
|
|
142
|
+
}
|
|
143
|
+
async shouldAllowRequest(session, request) {
|
|
144
|
+
if (!this.config.enabled) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
// Check circuit breaker
|
|
148
|
+
if (!this.circuitBreaker.canExecute()) {
|
|
149
|
+
this.logger.warn('Request rejected by circuit breaker', {
|
|
150
|
+
sessionId: session.id,
|
|
151
|
+
method: request.method,
|
|
152
|
+
circuitState: this.circuitBreaker.getState()
|
|
153
|
+
});
|
|
154
|
+
this.metrics.circuitBreakerTrips++;
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// Check global rate limit
|
|
158
|
+
if (!this.rateLimiter.tryConsume()) {
|
|
159
|
+
this.logger.warn('Request rejected by global rate limiter', {
|
|
160
|
+
sessionId: session.id,
|
|
161
|
+
method: request.method,
|
|
162
|
+
remainingTokens: this.rateLimiter.getTokens()
|
|
163
|
+
});
|
|
164
|
+
this.metrics.rateLimitedRequests++;
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// Check per-session rate limit
|
|
168
|
+
const sessionRateLimiter = this.getSessionRateLimiter(session.id);
|
|
169
|
+
if (!sessionRateLimiter.tryConsume()) {
|
|
170
|
+
this.logger.warn('Request rejected by session rate limiter', {
|
|
171
|
+
sessionId: session.id,
|
|
172
|
+
method: request.method,
|
|
173
|
+
remainingTokens: sessionRateLimiter.getTokens()
|
|
174
|
+
});
|
|
175
|
+
this.metrics.rateLimitedRequests++;
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
recordRequestStart(session, request) {
|
|
181
|
+
const requestMetrics = {
|
|
182
|
+
requestId: request.id.toString(),
|
|
183
|
+
sessionId: session.id,
|
|
184
|
+
method: request.method,
|
|
185
|
+
startTime: Date.now()
|
|
186
|
+
};
|
|
187
|
+
this.metrics.totalRequests++;
|
|
188
|
+
this.updateRequestsPerSecond();
|
|
189
|
+
this.logger.debug('Request started', {
|
|
190
|
+
requestId: requestMetrics.requestId,
|
|
191
|
+
sessionId: session.id,
|
|
192
|
+
method: request.method
|
|
193
|
+
});
|
|
194
|
+
return requestMetrics;
|
|
195
|
+
}
|
|
196
|
+
recordRequestEnd(metrics, response, error) {
|
|
197
|
+
metrics.endTime = Date.now();
|
|
198
|
+
const duration = metrics.endTime - metrics.startTime;
|
|
199
|
+
// Update response time tracking
|
|
200
|
+
this.requestTimes.push(duration);
|
|
201
|
+
if (this.requestTimes.length > 1000) {
|
|
202
|
+
this.requestTimes.shift(); // Keep only last 1000 requests
|
|
203
|
+
}
|
|
204
|
+
const success = !error && (!response || !response.error);
|
|
205
|
+
metrics.success = success;
|
|
206
|
+
const errorMessage = error?.message || response?.error?.message;
|
|
207
|
+
if (errorMessage) {
|
|
208
|
+
metrics.error = errorMessage;
|
|
209
|
+
}
|
|
210
|
+
if (success) {
|
|
211
|
+
this.metrics.successfulRequests++;
|
|
212
|
+
this.circuitBreaker.recordSuccess();
|
|
213
|
+
} else {
|
|
214
|
+
this.metrics.failedRequests++;
|
|
215
|
+
this.circuitBreaker.recordFailure();
|
|
216
|
+
}
|
|
217
|
+
// Update average response time
|
|
218
|
+
this.metrics.averageResponseTime = this.calculateAverageResponseTime();
|
|
219
|
+
this.logger.debug('Request completed', {
|
|
220
|
+
requestId: metrics.requestId,
|
|
221
|
+
sessionId: metrics.sessionId,
|
|
222
|
+
method: metrics.method,
|
|
223
|
+
duration,
|
|
224
|
+
success,
|
|
225
|
+
error: metrics.error
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
getMetrics() {
|
|
229
|
+
return {
|
|
230
|
+
...this.metrics
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
resetMetrics() {
|
|
234
|
+
this.metrics = {
|
|
235
|
+
totalRequests: 0,
|
|
236
|
+
successfulRequests: 0,
|
|
237
|
+
failedRequests: 0,
|
|
238
|
+
rateLimitedRequests: 0,
|
|
239
|
+
averageResponseTime: 0,
|
|
240
|
+
requestsPerSecond: 0,
|
|
241
|
+
circuitBreakerTrips: 0,
|
|
242
|
+
lastReset: new Date()
|
|
243
|
+
};
|
|
244
|
+
this.requestTimes = [];
|
|
245
|
+
this.logger.info('Load balancer metrics reset');
|
|
246
|
+
}
|
|
247
|
+
isCircuitBreakerOpen() {
|
|
248
|
+
return this.circuitBreaker.getState() === "open";
|
|
249
|
+
}
|
|
250
|
+
getDetailedMetrics() {
|
|
251
|
+
return {
|
|
252
|
+
loadBalancer: this.getMetrics(),
|
|
253
|
+
circuitBreaker: this.circuitBreaker.getMetrics(),
|
|
254
|
+
rateLimiter: {
|
|
255
|
+
tokens: this.rateLimiter.getTokens(),
|
|
256
|
+
maxTokens: this.config.maxRequestsPerSecond
|
|
257
|
+
},
|
|
258
|
+
sessions: this.sessionRateLimiters.size
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
getSessionRateLimiter(sessionId) {
|
|
262
|
+
let rateLimiter = this.sessionRateLimiters.get(sessionId);
|
|
263
|
+
if (!rateLimiter) {
|
|
264
|
+
// Create a per-session rate limiter (more restrictive than global)
|
|
265
|
+
const sessionLimit = Math.max(1, Math.floor(this.config.maxRequestsPerSecond / 10));
|
|
266
|
+
rateLimiter = new RateLimiter(sessionLimit, sessionLimit);
|
|
267
|
+
this.sessionRateLimiters.set(sessionId, rateLimiter);
|
|
268
|
+
}
|
|
269
|
+
return rateLimiter;
|
|
270
|
+
}
|
|
271
|
+
calculateAverageResponseTime() {
|
|
272
|
+
if (this.requestTimes.length === 0) {
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
const sum = this.requestTimes.reduce((acc, time)=>acc + time, 0);
|
|
276
|
+
return sum / this.requestTimes.length;
|
|
277
|
+
}
|
|
278
|
+
updateRequestsPerSecond() {
|
|
279
|
+
const now = Math.floor(Date.now() / 1000);
|
|
280
|
+
if (now !== this.lastSecondTimestamp) {
|
|
281
|
+
this.metrics.requestsPerSecond = this.requestsInLastSecond;
|
|
282
|
+
this.requestsInLastSecond = 1;
|
|
283
|
+
this.lastSecondTimestamp = now;
|
|
284
|
+
} else {
|
|
285
|
+
this.requestsInLastSecond++;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
cleanupSessionRateLimiters() {
|
|
289
|
+
// Remove rate limiters for sessions that haven't been used recently
|
|
290
|
+
const cutoffTime = Date.now() - 300000; // 5 minutes ago
|
|
291
|
+
let cleaned = 0;
|
|
292
|
+
for (const [sessionId, rateLimiter] of this.sessionRateLimiters.entries()){
|
|
293
|
+
// If the rate limiter has full tokens, it hasn't been used recently
|
|
294
|
+
if (rateLimiter.getTokens() === this.config.maxRequestsPerSecond) {
|
|
295
|
+
this.sessionRateLimiters.delete(sessionId);
|
|
296
|
+
cleaned++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (cleaned > 0) {
|
|
300
|
+
this.logger.debug('Cleaned up session rate limiters', {
|
|
301
|
+
count: cleaned
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Request queue for handling backpressure
|
|
308
|
+
*/ export class RequestQueue {
|
|
309
|
+
logger;
|
|
310
|
+
queue = [];
|
|
311
|
+
processing = false;
|
|
312
|
+
maxQueueSize;
|
|
313
|
+
requestTimeout;
|
|
314
|
+
constructor(maxQueueSize = 1000, requestTimeout = 30000, logger){
|
|
315
|
+
this.logger = logger;
|
|
316
|
+
this.maxQueueSize = maxQueueSize;
|
|
317
|
+
this.requestTimeout = requestTimeout;
|
|
318
|
+
// Clean up expired requests periodically
|
|
319
|
+
setInterval(()=>{
|
|
320
|
+
this.cleanupExpiredRequests();
|
|
321
|
+
}, 10000); // Every 10 seconds
|
|
322
|
+
}
|
|
323
|
+
async enqueue(session, request, processor) {
|
|
324
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
325
|
+
throw new MCPError('Request queue is full');
|
|
326
|
+
}
|
|
327
|
+
return new Promise((resolve, reject)=>{
|
|
328
|
+
this.queue.push({
|
|
329
|
+
session,
|
|
330
|
+
request,
|
|
331
|
+
resolve,
|
|
332
|
+
reject,
|
|
333
|
+
timestamp: Date.now()
|
|
334
|
+
});
|
|
335
|
+
if (!this.processing) {
|
|
336
|
+
this.processQueue(processor);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async processQueue(processor) {
|
|
341
|
+
if (this.processing) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.processing = true;
|
|
345
|
+
while(this.queue.length > 0){
|
|
346
|
+
const item = this.queue.shift();
|
|
347
|
+
// Check if request has expired
|
|
348
|
+
if (Date.now() - item.timestamp > this.requestTimeout) {
|
|
349
|
+
item.reject(new MCPError('Request timeout'));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const result = await processor(item.session, item.request);
|
|
354
|
+
item.resolve(result);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
this.processing = false;
|
|
360
|
+
}
|
|
361
|
+
cleanupExpiredRequests() {
|
|
362
|
+
const now = Date.now();
|
|
363
|
+
let cleaned = 0;
|
|
364
|
+
this.queue = this.queue.filter((item)=>{
|
|
365
|
+
if (now - item.timestamp > this.requestTimeout) {
|
|
366
|
+
item.reject(new MCPError('Request timeout'));
|
|
367
|
+
cleaned++;
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
});
|
|
372
|
+
if (cleaned > 0) {
|
|
373
|
+
this.logger.warn('Cleaned up expired requests from queue', {
|
|
374
|
+
count: cleaned
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
getQueueSize() {
|
|
379
|
+
return this.queue.length;
|
|
380
|
+
}
|
|
381
|
+
isProcessing() {
|
|
382
|
+
return this.processing;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
//# sourceMappingURL=load-balancer.js.map
|