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,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session manager for MCP connections
|
|
3
|
+
*/ import { MCPError } from '../utils/errors.js';
|
|
4
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
5
|
+
/**
|
|
6
|
+
* Session manager implementation
|
|
7
|
+
*/ export class SessionManager {
|
|
8
|
+
config;
|
|
9
|
+
logger;
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
authConfig;
|
|
12
|
+
sessionTimeout;
|
|
13
|
+
maxSessions;
|
|
14
|
+
cleanupInterval;
|
|
15
|
+
constructor(config, logger){
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.authConfig = config.auth || {
|
|
19
|
+
enabled: false,
|
|
20
|
+
method: 'token'
|
|
21
|
+
};
|
|
22
|
+
this.sessionTimeout = config.sessionTimeout || 3600000; // 1 hour default
|
|
23
|
+
this.maxSessions = config.maxSessions || 100;
|
|
24
|
+
// Start cleanup timer
|
|
25
|
+
this.cleanupInterval = setInterval(()=>{
|
|
26
|
+
this.cleanupExpiredSessions();
|
|
27
|
+
}, 60000); // Clean up every minute
|
|
28
|
+
}
|
|
29
|
+
createSession(transport) {
|
|
30
|
+
// Check session limit
|
|
31
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
32
|
+
// Try to clean up expired sessions first
|
|
33
|
+
this.cleanupExpiredSessions();
|
|
34
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
35
|
+
throw new MCPError('Maximum number of sessions reached');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const sessionId = this.generateSessionId();
|
|
39
|
+
const now = new Date();
|
|
40
|
+
const session = {
|
|
41
|
+
id: sessionId,
|
|
42
|
+
clientInfo: {
|
|
43
|
+
name: 'unknown',
|
|
44
|
+
version: 'unknown'
|
|
45
|
+
},
|
|
46
|
+
protocolVersion: {
|
|
47
|
+
major: 0,
|
|
48
|
+
minor: 0,
|
|
49
|
+
patch: 0
|
|
50
|
+
},
|
|
51
|
+
capabilities: {},
|
|
52
|
+
isInitialized: false,
|
|
53
|
+
createdAt: now,
|
|
54
|
+
lastActivity: now,
|
|
55
|
+
transport,
|
|
56
|
+
authenticated: !this.authConfig.enabled
|
|
57
|
+
};
|
|
58
|
+
this.sessions.set(sessionId, session);
|
|
59
|
+
this.logger.info('Session created', {
|
|
60
|
+
sessionId,
|
|
61
|
+
transport,
|
|
62
|
+
totalSessions: this.sessions.size
|
|
63
|
+
});
|
|
64
|
+
return session;
|
|
65
|
+
}
|
|
66
|
+
getSession(id) {
|
|
67
|
+
const session = this.sessions.get(id);
|
|
68
|
+
if (session && this.isSessionExpired(session)) {
|
|
69
|
+
this.removeSession(id);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return session;
|
|
73
|
+
}
|
|
74
|
+
initializeSession(sessionId, params) {
|
|
75
|
+
const session = this.getSession(sessionId);
|
|
76
|
+
if (!session) {
|
|
77
|
+
throw new MCPError(`Session not found: ${sessionId}`);
|
|
78
|
+
}
|
|
79
|
+
// Validate protocol version
|
|
80
|
+
this.validateProtocolVersion(params.protocolVersion);
|
|
81
|
+
// Update session with initialization params
|
|
82
|
+
session.clientInfo = params.clientInfo;
|
|
83
|
+
session.protocolVersion = params.protocolVersion;
|
|
84
|
+
session.capabilities = params.capabilities;
|
|
85
|
+
session.isInitialized = true;
|
|
86
|
+
session.lastActivity = new Date();
|
|
87
|
+
this.logger.info('Session initialized', {
|
|
88
|
+
sessionId,
|
|
89
|
+
clientInfo: params.clientInfo,
|
|
90
|
+
protocolVersion: params.protocolVersion
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
authenticateSession(sessionId, credentials) {
|
|
94
|
+
const session = this.getSession(sessionId);
|
|
95
|
+
if (!session) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (!this.authConfig.enabled) {
|
|
99
|
+
session.authenticated = true;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
let authenticated = false;
|
|
103
|
+
switch(this.authConfig.method){
|
|
104
|
+
case 'token':
|
|
105
|
+
authenticated = this.authenticateToken(credentials);
|
|
106
|
+
break;
|
|
107
|
+
case 'basic':
|
|
108
|
+
authenticated = this.authenticateBasic(credentials);
|
|
109
|
+
break;
|
|
110
|
+
case 'oauth':
|
|
111
|
+
authenticated = this.authenticateOAuth(credentials);
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
this.logger.warn('Unknown authentication method', {
|
|
115
|
+
method: this.authConfig.method
|
|
116
|
+
});
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (authenticated) {
|
|
120
|
+
session.authenticated = true;
|
|
121
|
+
session.authData = this.extractAuthData(credentials);
|
|
122
|
+
session.lastActivity = new Date();
|
|
123
|
+
this.logger.info('Session authenticated', {
|
|
124
|
+
sessionId,
|
|
125
|
+
method: this.authConfig.method
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
this.logger.warn('Session authentication failed', {
|
|
129
|
+
sessionId,
|
|
130
|
+
method: this.authConfig.method
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return authenticated;
|
|
134
|
+
}
|
|
135
|
+
updateActivity(sessionId) {
|
|
136
|
+
const session = this.getSession(sessionId);
|
|
137
|
+
if (session) {
|
|
138
|
+
session.lastActivity = new Date();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
removeSession(sessionId) {
|
|
142
|
+
const session = this.sessions.get(sessionId);
|
|
143
|
+
if (session) {
|
|
144
|
+
this.sessions.delete(sessionId);
|
|
145
|
+
this.logger.info('Session removed', {
|
|
146
|
+
sessionId,
|
|
147
|
+
duration: Date.now() - session.createdAt.getTime(),
|
|
148
|
+
transport: session.transport
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
getActiveSessions() {
|
|
153
|
+
const activeSessions = [];
|
|
154
|
+
for (const session of this.sessions.values()){
|
|
155
|
+
if (!this.isSessionExpired(session)) {
|
|
156
|
+
activeSessions.push(session);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return activeSessions;
|
|
160
|
+
}
|
|
161
|
+
cleanupExpiredSessions() {
|
|
162
|
+
const expiredSessions = [];
|
|
163
|
+
for (const [sessionId, session] of this.sessions){
|
|
164
|
+
if (this.isSessionExpired(session)) {
|
|
165
|
+
expiredSessions.push(sessionId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const sessionId of expiredSessions){
|
|
169
|
+
this.removeSession(sessionId);
|
|
170
|
+
}
|
|
171
|
+
if (expiredSessions.length > 0) {
|
|
172
|
+
this.logger.info('Cleaned up expired sessions', {
|
|
173
|
+
count: expiredSessions.length,
|
|
174
|
+
remainingSessions: this.sessions.size
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
getSessionMetrics() {
|
|
179
|
+
let active = 0;
|
|
180
|
+
let authenticated = 0;
|
|
181
|
+
let expired = 0;
|
|
182
|
+
for (const session of this.sessions.values()){
|
|
183
|
+
if (this.isSessionExpired(session)) {
|
|
184
|
+
expired++;
|
|
185
|
+
} else {
|
|
186
|
+
active++;
|
|
187
|
+
if (session.authenticated) {
|
|
188
|
+
authenticated++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
total: this.sessions.size,
|
|
194
|
+
active,
|
|
195
|
+
authenticated,
|
|
196
|
+
expired
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
destroy() {
|
|
200
|
+
if (this.cleanupInterval) {
|
|
201
|
+
clearInterval(this.cleanupInterval);
|
|
202
|
+
}
|
|
203
|
+
this.sessions.clear();
|
|
204
|
+
}
|
|
205
|
+
generateSessionId() {
|
|
206
|
+
const timestamp = Date.now().toString(36);
|
|
207
|
+
const random = Math.random().toString(36).substr(2, 9);
|
|
208
|
+
return `session_${timestamp}_${random}`;
|
|
209
|
+
}
|
|
210
|
+
isSessionExpired(session) {
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
const sessionAge = now - session.lastActivity.getTime();
|
|
213
|
+
return sessionAge > this.sessionTimeout;
|
|
214
|
+
}
|
|
215
|
+
validateProtocolVersion(version) {
|
|
216
|
+
// Currently supporting MCP version 2024-11-05
|
|
217
|
+
const supportedVersions = [
|
|
218
|
+
{
|
|
219
|
+
major: 2024,
|
|
220
|
+
minor: 11,
|
|
221
|
+
patch: 5
|
|
222
|
+
}
|
|
223
|
+
];
|
|
224
|
+
const isSupported = supportedVersions.some((supported)=>supported.major === version.major && supported.minor === version.minor && supported.patch === version.patch);
|
|
225
|
+
if (!isSupported) {
|
|
226
|
+
throw new MCPError(`Unsupported protocol version: ${version.major}.${version.minor}.${version.patch}`, {
|
|
227
|
+
supportedVersions
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
authenticateToken(credentials) {
|
|
232
|
+
if (!this.authConfig.tokens || this.authConfig.tokens.length === 0) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const token = this.extractToken(credentials);
|
|
236
|
+
if (!token) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
240
|
+
return this.authConfig.tokens.some((validToken)=>{
|
|
241
|
+
const encoder = new TextEncoder();
|
|
242
|
+
const validTokenBytes = encoder.encode(validToken);
|
|
243
|
+
const providedTokenBytes = encoder.encode(token);
|
|
244
|
+
if (validTokenBytes.length !== providedTokenBytes.length) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return timingSafeEqual(validTokenBytes, providedTokenBytes);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
authenticateBasic(credentials) {
|
|
251
|
+
if (!this.authConfig.users || this.authConfig.users.length === 0) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const { username, password } = this.extractBasicAuth(credentials);
|
|
255
|
+
if (!username || !password) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
const user = this.authConfig.users.find((u)=>u.username === username);
|
|
259
|
+
if (!user) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
// Hash the provided password and compare
|
|
263
|
+
const hashedPassword = this.hashPassword(password);
|
|
264
|
+
const expectedHashedPassword = this.hashPassword(user.password);
|
|
265
|
+
const encoder = new TextEncoder();
|
|
266
|
+
const hashedPasswordBytes = encoder.encode(hashedPassword);
|
|
267
|
+
const expectedHashedPasswordBytes = encoder.encode(expectedHashedPassword);
|
|
268
|
+
if (hashedPasswordBytes.length !== expectedHashedPasswordBytes.length) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return timingSafeEqual(hashedPasswordBytes, expectedHashedPasswordBytes);
|
|
272
|
+
}
|
|
273
|
+
authenticateOAuth(credentials) {
|
|
274
|
+
// TODO: Implement OAuth authentication
|
|
275
|
+
// This would typically involve validating JWT tokens
|
|
276
|
+
this.logger.warn('OAuth authentication not yet implemented');
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
extractToken(credentials) {
|
|
280
|
+
if (typeof credentials === 'string') {
|
|
281
|
+
return credentials;
|
|
282
|
+
}
|
|
283
|
+
if (typeof credentials === 'object' && credentials !== null) {
|
|
284
|
+
const creds = credentials;
|
|
285
|
+
if (typeof creds.token === 'string') {
|
|
286
|
+
return creds.token;
|
|
287
|
+
}
|
|
288
|
+
if (typeof creds.authorization === 'string') {
|
|
289
|
+
const match = creds.authorization.match(/^Bearer\s+(.+)$/);
|
|
290
|
+
return match ? match[1] : null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
extractBasicAuth(credentials) {
|
|
296
|
+
if (typeof credentials === 'object' && credentials !== null) {
|
|
297
|
+
const creds = credentials;
|
|
298
|
+
if (typeof creds.username === 'string' && typeof creds.password === 'string') {
|
|
299
|
+
return {
|
|
300
|
+
username: creds.username,
|
|
301
|
+
password: creds.password
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (typeof creds.authorization === 'string') {
|
|
305
|
+
const match = creds.authorization.match(/^Basic\s+(.+)$/);
|
|
306
|
+
if (match) {
|
|
307
|
+
try {
|
|
308
|
+
const decoded = atob(match[1]);
|
|
309
|
+
const [username, password] = decoded.split(':', 2);
|
|
310
|
+
return {
|
|
311
|
+
username,
|
|
312
|
+
password
|
|
313
|
+
};
|
|
314
|
+
} catch {
|
|
315
|
+
return {};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
extractAuthData(credentials) {
|
|
323
|
+
if (typeof credentials === 'object' && credentials !== null) {
|
|
324
|
+
const creds = credentials;
|
|
325
|
+
return {
|
|
326
|
+
token: this.extractToken(credentials),
|
|
327
|
+
user: creds.username || creds.user,
|
|
328
|
+
permissions: creds.permissions || []
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
hashPassword(password) {
|
|
334
|
+
return createHash('sha256').update(password).digest('hex');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
//# sourceMappingURL=session-manager.js.map
|