@perfai/mcp 1.0.24

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +352 -0
  3. package/dist/auth/authManager.d.ts +83 -0
  4. package/dist/auth/authManager.d.ts.map +1 -0
  5. package/dist/auth/authManager.js +555 -0
  6. package/dist/auth/sessionCache.d.ts +5 -0
  7. package/dist/auth/sessionCache.d.ts.map +1 -0
  8. package/dist/auth/sessionCache.js +29 -0
  9. package/dist/auth/sessionStorage.d.ts +53 -0
  10. package/dist/auth/sessionStorage.d.ts.map +1 -0
  11. package/dist/auth/sessionStorage.js +234 -0
  12. package/dist/auth/types.d.ts +28 -0
  13. package/dist/auth/types.d.ts.map +1 -0
  14. package/dist/auth/types.js +1 -0
  15. package/dist/config.d.ts +65 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +74 -0
  18. package/dist/server.d.ts +3 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.js +144 -0
  21. package/dist/setup-config.d.ts +4 -0
  22. package/dist/setup-config.d.ts.map +1 -0
  23. package/dist/setup-config.js +69 -0
  24. package/dist/tools/index.d.ts +361 -0
  25. package/dist/tools/index.d.ts.map +1 -0
  26. package/dist/tools/index.js +275 -0
  27. package/dist/tools/protected/aiFixDesignIssue.d.ts +17 -0
  28. package/dist/tools/protected/aiFixDesignIssue.d.ts.map +1 -0
  29. package/dist/tools/protected/aiFixDesignIssue.js +205 -0
  30. package/dist/tools/protected/aiFixQualityIssue.d.ts +17 -0
  31. package/dist/tools/protected/aiFixQualityIssue.d.ts.map +1 -0
  32. package/dist/tools/protected/aiFixQualityIssue.js +188 -0
  33. package/dist/tools/protected/aiFixSecurityIssue.d.ts +17 -0
  34. package/dist/tools/protected/aiFixSecurityIssue.d.ts.map +1 -0
  35. package/dist/tools/protected/aiFixSecurityIssue.js +205 -0
  36. package/dist/tools/protected/checkDesignFixes.d.ts +17 -0
  37. package/dist/tools/protected/checkDesignFixes.d.ts.map +1 -0
  38. package/dist/tools/protected/checkDesignFixes.js +199 -0
  39. package/dist/tools/protected/checkQualityFixes.d.ts +17 -0
  40. package/dist/tools/protected/checkQualityFixes.d.ts.map +1 -0
  41. package/dist/tools/protected/checkQualityFixes.js +199 -0
  42. package/dist/tools/protected/checkSecurityFixes.d.ts +17 -0
  43. package/dist/tools/protected/checkSecurityFixes.d.ts.map +1 -0
  44. package/dist/tools/protected/checkSecurityFixes.js +177 -0
  45. package/dist/tools/protected/listApis.d.ts +28 -0
  46. package/dist/tools/protected/listApis.d.ts.map +1 -0
  47. package/dist/tools/protected/listApis.js +102 -0
  48. package/dist/tools/protected/logout.d.ts +11 -0
  49. package/dist/tools/protected/logout.d.ts.map +1 -0
  50. package/dist/tools/protected/logout.js +22 -0
  51. package/dist/tools/protected/manageOrganizations.d.ts +26 -0
  52. package/dist/tools/protected/manageOrganizations.d.ts.map +1 -0
  53. package/dist/tools/protected/manageOrganizations.js +147 -0
  54. package/dist/tools/protected/runDesignTest.d.ts +21 -0
  55. package/dist/tools/protected/runDesignTest.d.ts.map +1 -0
  56. package/dist/tools/protected/runDesignTest.js +132 -0
  57. package/dist/tools/protected/runQualityTest.d.ts +21 -0
  58. package/dist/tools/protected/runQualityTest.d.ts.map +1 -0
  59. package/dist/tools/protected/runQualityTest.js +150 -0
  60. package/dist/tools/protected/runSecurityTest.d.ts +21 -0
  61. package/dist/tools/protected/runSecurityTest.d.ts.map +1 -0
  62. package/dist/tools/protected/runSecurityTest.js +107 -0
  63. package/dist/tools/protected/selectApi.d.ts +24 -0
  64. package/dist/tools/protected/selectApi.d.ts.map +1 -0
  65. package/dist/tools/protected/selectApi.js +172 -0
  66. package/dist/tools/protected/setup.d.ts +11 -0
  67. package/dist/tools/protected/setup.d.ts.map +1 -0
  68. package/dist/tools/protected/setup.js +151 -0
  69. package/dist/tools/protected/showDesignIssues.d.ts +38 -0
  70. package/dist/tools/protected/showDesignIssues.d.ts.map +1 -0
  71. package/dist/tools/protected/showDesignIssues.js +201 -0
  72. package/dist/tools/protected/showFixedIssues.d.ts +11 -0
  73. package/dist/tools/protected/showFixedIssues.d.ts.map +1 -0
  74. package/dist/tools/protected/showFixedIssues.js +36 -0
  75. package/dist/tools/protected/showQualityIssues.d.ts +33 -0
  76. package/dist/tools/protected/showQualityIssues.d.ts.map +1 -0
  77. package/dist/tools/protected/showQualityIssues.js +225 -0
  78. package/dist/tools/protected/showSecurityIssues.d.ts +47 -0
  79. package/dist/tools/protected/showSecurityIssues.d.ts.map +1 -0
  80. package/dist/tools/protected/showSecurityIssues.js +212 -0
  81. package/dist/tools/protected/summarizeIssues.d.ts +11 -0
  82. package/dist/tools/protected/summarizeIssues.d.ts.map +1 -0
  83. package/dist/tools/protected/summarizeIssues.js +161 -0
  84. package/dist/tools/protected/userInfo.d.ts +11 -0
  85. package/dist/tools/protected/userInfo.d.ts.map +1 -0
  86. package/dist/tools/protected/userInfo.js +21 -0
  87. package/dist/tools/protected/visionAiAppLearning.d.ts +37 -0
  88. package/dist/tools/protected/visionAiAppLearning.d.ts.map +1 -0
  89. package/dist/tools/protected/visionAiAppLearning.js +122 -0
  90. package/dist/tools/public/authStatus.d.ts +11 -0
  91. package/dist/tools/public/authStatus.d.ts.map +1 -0
  92. package/dist/tools/public/authStatus.js +78 -0
  93. package/dist/tools/public/login.d.ts +12 -0
  94. package/dist/tools/public/login.d.ts.map +1 -0
  95. package/dist/tools/public/login.js +230 -0
  96. package/dist/types/api.d.ts +12 -0
  97. package/dist/types/api.d.ts.map +1 -0
  98. package/dist/types/api.js +1 -0
  99. package/dist/utils/dockerRunner.d.ts +44 -0
  100. package/dist/utils/dockerRunner.d.ts.map +1 -0
  101. package/dist/utils/dockerRunner.js +300 -0
  102. package/dist/utils/formatters.d.ts +14 -0
  103. package/dist/utils/formatters.d.ts.map +1 -0
  104. package/dist/utils/formatters.js +510 -0
  105. package/dist/utils/promptBuilder.d.ts +4 -0
  106. package/dist/utils/promptBuilder.d.ts.map +1 -0
  107. package/dist/utils/promptBuilder.js +132 -0
  108. package/package.json +67 -0
@@ -0,0 +1,555 @@
1
+ import axios from "axios";
2
+ import open from "open";
3
+ import { createHash, randomBytes } from "crypto";
4
+ import * as http from "http";
5
+ import { URL } from "url";
6
+ import { AUTH0_DOMAIN, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, API_ENDPOINTS } from '../config.js';
7
+ import { SessionStorage } from './sessionStorage.js';
8
+ export class AuthenticationManager {
9
+ session = null;
10
+ codeVerifier = "";
11
+ authServer = null;
12
+ sessionStorage;
13
+ isInitialized = false;
14
+ initializationPromise;
15
+ constructor() {
16
+ this.sessionStorage = new SessionStorage();
17
+ this.initializationPromise = this.initializeSession();
18
+ }
19
+ /**
20
+ * Wait for session initialization to complete
21
+ */
22
+ async waitForInitialization() {
23
+ if (!this.isInitialized) {
24
+ await this.initializationPromise;
25
+ }
26
+ }
27
+ /**
28
+ * Initialize session from persistent storage
29
+ */
30
+ async initializeSession() {
31
+ if (this.isInitialized)
32
+ return;
33
+ try {
34
+ console.error('🔍 Checking for saved session...');
35
+ const savedSession = await this.sessionStorage.loadSession();
36
+ if (savedSession) {
37
+ this.session = savedSession;
38
+ const userEmail = savedSession.userInfo?.email || 'Unknown';
39
+ const timeLeft = Math.round((savedSession.expiresAt - Date.now()) / (1000 * 60 * 60));
40
+ console.error(`✅ Session restored for ${userEmail}, expires in ${timeLeft} hours`);
41
+ // Auto-save session updates
42
+ this.scheduleSessionSave();
43
+ }
44
+ else {
45
+ console.error('📝 No valid saved session found');
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.error('⚠️ Failed to initialize session:', error);
50
+ }
51
+ finally {
52
+ this.isInitialized = true;
53
+ }
54
+ }
55
+ /**
56
+ * Schedule automatic session saving when session data changes
57
+ */
58
+ scheduleSessionSave() {
59
+ if (this.session) {
60
+ // Debounced save to avoid excessive I/O
61
+ setTimeout(() => {
62
+ if (this.session) {
63
+ this.sessionStorage.saveSession(this.session);
64
+ }
65
+ }, 1000);
66
+ }
67
+ }
68
+ async isAuthenticated() {
69
+ // Ensure session is initialized
70
+ if (!this.isInitialized) {
71
+ await this.initializationPromise;
72
+ }
73
+ if (!this.session)
74
+ return false;
75
+ const isValid = Date.now() < this.session.expiresAt;
76
+ // If session is expired, clear it
77
+ if (!isValid && this.session) {
78
+ console.error('⏰ Session expired, clearing storage');
79
+ this.clearSession();
80
+ }
81
+ return isValid;
82
+ }
83
+ /**
84
+ * Synchronous version for backward compatibility
85
+ * Note: This will return false if initialization is still in progress
86
+ */
87
+ isAuthenticatedSync() {
88
+ if (!this.isInitialized || !this.session)
89
+ return false;
90
+ const isValid = Date.now() < this.session.expiresAt;
91
+ // If session is expired, clear it
92
+ if (!isValid && this.session) {
93
+ console.error('⏰ Session expired, clearing storage');
94
+ this.clearSession();
95
+ }
96
+ return isValid;
97
+ }
98
+ getUserInfo() {
99
+ return this.session?.userInfo || null;
100
+ }
101
+ getAccessToken() {
102
+ return this.session?.accessToken || null;
103
+ }
104
+ getTokenType() {
105
+ return this.session?.tokenType || 'Bearer';
106
+ }
107
+ getSelectedOrgId() {
108
+ return this.session?.selectedOrgId || null;
109
+ }
110
+ getOrganizations() {
111
+ return this.session?.organizations || [];
112
+ }
113
+ setSelectedOrgId(orgId) {
114
+ if (this.session) {
115
+ this.session.selectedOrgId = orgId;
116
+ this.scheduleSessionSave();
117
+ }
118
+ }
119
+ // APP management methods
120
+ getSelectedApiId() {
121
+ return this.session?.selectedApiId || null;
122
+ }
123
+ getSelectedApiData() {
124
+ return this.session?.selectedApiData || null;
125
+ }
126
+ getSelectedApiIdentifier() {
127
+ return this.session?.selectedApiIdentifier || null;
128
+ }
129
+ setSelectedApi(apiId, apiData, apiIdentifier) {
130
+ if (this.session) {
131
+ this.session.selectedApiId = apiId;
132
+ this.session.selectedApiData = apiData;
133
+ this.session.selectedApiIdentifier = apiIdentifier;
134
+ this.scheduleSessionSave();
135
+ }
136
+ }
137
+ // Security app ID management methods
138
+ getSelectedSecurityAppId() {
139
+ return this.session?.selectedSecurityAppId || null;
140
+ }
141
+ setSelectedSecurityAppId(securityAppId) {
142
+ if (this.session) {
143
+ this.session.selectedSecurityAppId = securityAppId;
144
+ this.scheduleSessionSave();
145
+ }
146
+ }
147
+ // Design app ID management methods
148
+ getSelectedDesignAppId() {
149
+ return this.session?.selectedDesignAppId || null;
150
+ }
151
+ setSelectedDesignAppId(designAppId) {
152
+ if (this.session) {
153
+ this.session.selectedDesignAppId = designAppId;
154
+ this.scheduleSessionSave();
155
+ }
156
+ }
157
+ // Quality app ID management methods
158
+ getSelectedQualityAppId() {
159
+ return this.session?.selectedQualityAppId || null;
160
+ }
161
+ setSelectedQualityAppId(qualityAppId) {
162
+ if (this.session) {
163
+ this.session.selectedQualityAppId = qualityAppId;
164
+ this.scheduleSessionSave();
165
+ }
166
+ }
167
+ // App sequence mapping methods
168
+ setAppSequenceMap(sequenceMap) {
169
+ if (this.session) {
170
+ this.session.appSequenceMap = sequenceMap;
171
+ this.scheduleSessionSave();
172
+ }
173
+ }
174
+ getAppSequenceMap() {
175
+ return this.session?.appSequenceMap || null;
176
+ }
177
+ getAppIdBySequence(sequenceNumber) {
178
+ const sequenceMap = this.getAppSequenceMap();
179
+ return sequenceMap?.get(sequenceNumber) || null;
180
+ }
181
+ clearAppSequenceMap() {
182
+ if (this.session) {
183
+ this.session.appSequenceMap = undefined;
184
+ this.scheduleSessionSave();
185
+ }
186
+ }
187
+ // Fixed issues management methods
188
+ getFixedIssues() {
189
+ return this.session?.fixedIssues || [];
190
+ }
191
+ markIssueAsFixed(issueId, issue, prompt) {
192
+ if (!this.session) {
193
+ throw new Error('No active session');
194
+ }
195
+ const fixedIssue = {
196
+ issueId,
197
+ issue: {
198
+ ...issue,
199
+ isAIFixed: true,
200
+ aiFixedAt: new Date(),
201
+ aiFixPrompt: prompt,
202
+ },
203
+ fixedAt: new Date(),
204
+ prompt,
205
+ status: "ai-fixed",
206
+ };
207
+ // Initialize fixedIssues array if it doesn't exist
208
+ if (!this.session.fixedIssues) {
209
+ this.session.fixedIssues = [];
210
+ }
211
+ // Add or update fixed issue
212
+ const existingIndex = this.session.fixedIssues.findIndex((fi) => fi.issueId === issueId);
213
+ if (existingIndex !== -1) {
214
+ this.session.fixedIssues[existingIndex] = fixedIssue;
215
+ }
216
+ else {
217
+ this.session.fixedIssues.push(fixedIssue);
218
+ }
219
+ this.scheduleSessionSave();
220
+ return fixedIssue;
221
+ }
222
+ isIssueFixed(issueId) {
223
+ return this.session?.fixedIssues?.some(fi => fi.issueId === issueId) || false;
224
+ }
225
+ clearFixedIssues() {
226
+ if (this.session) {
227
+ this.session.fixedIssues = [];
228
+ this.scheduleSessionSave();
229
+ console.error('🗑️ Fixed issues cleared from session');
230
+ }
231
+ }
232
+ // Design fixed issues management methods
233
+ getFixedDesignIssues() {
234
+ return this.session?.fixedDesignIssues || [];
235
+ }
236
+ markDesignIssueAsFixed(issueId, issue, prompt) {
237
+ if (!this.session) {
238
+ throw new Error('No active session');
239
+ }
240
+ const fixedIssue = {
241
+ issueId,
242
+ issue: {
243
+ ...issue,
244
+ isAIFixed: true,
245
+ aiFixedAt: new Date(),
246
+ aiFixPrompt: prompt
247
+ },
248
+ fixedAt: new Date(),
249
+ prompt,
250
+ status: 'ai-fixed'
251
+ };
252
+ // Initialize fixedDesignIssues array if it doesn't exist
253
+ if (!this.session.fixedDesignIssues) {
254
+ this.session.fixedDesignIssues = [];
255
+ }
256
+ // Check if issue is already fixed and update or add
257
+ const existingIndex = this.session.fixedDesignIssues.findIndex(fi => fi.issueId === issueId);
258
+ if (existingIndex !== -1) {
259
+ this.session.fixedDesignIssues[existingIndex] = fixedIssue;
260
+ console.error(`🔄 Updated existing design fix for issue: ${issueId}`);
261
+ }
262
+ else {
263
+ this.session.fixedDesignIssues.push(fixedIssue);
264
+ console.error(`✅ Design issue marked as fixed: ${issueId}`);
265
+ }
266
+ this.scheduleSessionSave();
267
+ return fixedIssue;
268
+ }
269
+ isDesignIssueFixed(issueId) {
270
+ return this.session?.fixedDesignIssues?.some(fi => fi.issueId === issueId) || false;
271
+ }
272
+ clearFixedDesignIssues() {
273
+ if (this.session) {
274
+ this.session.fixedDesignIssues = [];
275
+ this.scheduleSessionSave();
276
+ console.error('🗑️ Fixed design issues cleared from session');
277
+ }
278
+ }
279
+ // Quality fixed issues management methods
280
+ getFixedQualityIssues() {
281
+ return this.session?.fixedQualityIssues || [];
282
+ }
283
+ addFixedQualityIssue(fixedIssue) {
284
+ if (!this.session) {
285
+ throw new Error('No active session');
286
+ }
287
+ // Initialize fixedQualityIssues array if it doesn't exist
288
+ if (!this.session.fixedQualityIssues) {
289
+ this.session.fixedQualityIssues = [];
290
+ }
291
+ // Add or update fixed issue
292
+ const existingIndex = this.session.fixedQualityIssues.findIndex(fi => fi.issueId === fixedIssue.issueId);
293
+ if (existingIndex !== -1) {
294
+ this.session.fixedQualityIssues[existingIndex] = fixedIssue;
295
+ console.error(`🔄 Updated existing quality fix for issue: ${fixedIssue.issueId}`);
296
+ }
297
+ else {
298
+ this.session.fixedQualityIssues.push(fixedIssue);
299
+ console.error(`✅ Quality issue marked as fixed: ${fixedIssue.issueId}`);
300
+ }
301
+ this.scheduleSessionSave();
302
+ }
303
+ markQualityIssueAsFixed(issueId, issue, prompt) {
304
+ if (!this.session) {
305
+ throw new Error('No active session');
306
+ }
307
+ const fixedIssue = {
308
+ issueId,
309
+ issue: {
310
+ ...issue,
311
+ isAIFixed: true,
312
+ aiFixedAt: new Date(),
313
+ aiFixPrompt: prompt,
314
+ },
315
+ fixedAt: new Date(),
316
+ prompt,
317
+ status: "ai-fixed",
318
+ };
319
+ this.addFixedQualityIssue(fixedIssue);
320
+ return fixedIssue;
321
+ }
322
+ isQualityIssueFixed(issueId) {
323
+ return this.session?.fixedQualityIssues?.some(fi => fi.issueId === issueId) || false;
324
+ }
325
+ clearFixedQualityIssues() {
326
+ if (this.session) {
327
+ this.session.fixedQualityIssues = [];
328
+ this.scheduleSessionSave();
329
+ console.error('🗑️ Fixed quality issues cleared from session');
330
+ }
331
+ }
332
+ clearSession() {
333
+ this.session = null;
334
+ this.sessionStorage.clearSession();
335
+ console.error('🗑️ Session cleared from memory and storage');
336
+ }
337
+ /**
338
+ * Get session storage location for debugging
339
+ */
340
+ getStorageLocation() {
341
+ return this.sessionStorage.getStorageLocation();
342
+ }
343
+ /**
344
+ * Check if there's a valid session in storage without loading it
345
+ */
346
+ hasValidStoredSession() {
347
+ return this.sessionStorage.hasValidSession();
348
+ }
349
+ /**
350
+ * Save current session to persistent storage
351
+ */
352
+ async saveCurrentSession() {
353
+ if (this.session) {
354
+ await this.sessionStorage.saveSession(this.session);
355
+ }
356
+ else {
357
+ throw new Error('No active session to save');
358
+ }
359
+ }
360
+ generateCodeChallenge() {
361
+ this.codeVerifier = randomBytes(32).toString('base64url');
362
+ const codeChallenge = createHash('sha256')
363
+ .update(this.codeVerifier)
364
+ .digest('base64url');
365
+ return { codeVerifier: this.codeVerifier, codeChallenge };
366
+ }
367
+ buildAuthUrl() {
368
+ const { codeChallenge } = this.generateCodeChallenge();
369
+ const params = new URLSearchParams({
370
+ response_type: "code",
371
+ client_id: CLIENT_ID,
372
+ redirect_uri: REDIRECT_URI,
373
+ scope: "openid profile email offline_access",
374
+ //code_challenge: codeChallenge,
375
+ //code_challenge_method: "S256",
376
+ });
377
+ return `https://${AUTH0_DOMAIN}/authorize?${params.toString()}`;
378
+ }
379
+ async manualCodeFlow() {
380
+ const authUrl = this.buildAuthUrl();
381
+ // Open browser for user to authenticate
382
+ await open(authUrl);
383
+ return authUrl;
384
+ }
385
+ async startAuthServer() {
386
+ return new Promise((resolve, reject) => {
387
+ this.authServer = http.createServer(async (req, res) => {
388
+ if (req.url?.startsWith('/') || req.url?.startsWith('/auth-callback')) {
389
+ try {
390
+ const url = new URL(req.url, `http://localhost:5173`);
391
+ const code = url.searchParams.get('code');
392
+ const error = url.searchParams.get('error');
393
+ if (error) {
394
+ throw new Error(`Auth error: ${error}`);
395
+ }
396
+ if (!code) {
397
+ throw new Error('No authorization code received');
398
+ }
399
+ // Exchange code for tokens
400
+ await this.exchangeCodeForTokens(code);
401
+ // Send success response
402
+ res.writeHead(200, { 'Content-Type': 'text/html' });
403
+ res.end(`
404
+ <html>
405
+ <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
406
+ <h1 style="color: #28a745;">✅ Authentication Successful!</h1>
407
+ <p>You have successfully logged in to PerfAI MCP Server.</p>
408
+ <p>You can now close this window and return to your MCP client.</p>
409
+ <script>setTimeout(() => window.close(), 3000);</script>
410
+ </body>
411
+ </html>
412
+ `);
413
+ // Close server after successful auth
414
+ setTimeout(() => {
415
+ this.authServer?.close();
416
+ this.authServer = null;
417
+ }, 1000);
418
+ resolve('Authentication successful');
419
+ }
420
+ catch (error) {
421
+ res.writeHead(400, { 'Content-Type': 'text/html' });
422
+ res.end(`
423
+ <html>
424
+ <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
425
+ <h1 style="color: #dc3545;">❌ Authentication Failed</h1>
426
+ <p>Error: ${error instanceof Error ? error.message : 'Unknown error'}</p>
427
+ <p>Please try again.</p>
428
+ </body>
429
+ </html>
430
+ `);
431
+ reject(error);
432
+ }
433
+ }
434
+ else {
435
+ res.writeHead(404);
436
+ res.end('Not found');
437
+ }
438
+ });
439
+ // Try to bind to port 5173 first (matching Auth0 config), fallback to 5174
440
+ const tryPort = (port) => {
441
+ this.authServer.listen(port, () => {
442
+ console.error(`🚀 Auth server listening on port ${port} for OAuth callback`);
443
+ if (port === 5173) {
444
+ console.error('✅ Using port 5173 - matches your Auth0 configuration perfectly!');
445
+ }
446
+ else {
447
+ console.error('⚠️ Using port 5174 - Auth0 should redirect to localhost:5173/auth-callback');
448
+ console.error(' (The server will handle the callback regardless of port)');
449
+ }
450
+ resolve('Authentication server started');
451
+ });
452
+ this.authServer.on('error', (error) => {
453
+ if (error.code === 'EADDRINUSE' && port === 5173) {
454
+ console.error('⚠️ Port 5173 is busy (MCP Inspector running), trying port 5174...');
455
+ // Clear the error handler before trying the next port
456
+ this.authServer.removeAllListeners('error');
457
+ tryPort(5174);
458
+ }
459
+ else {
460
+ reject(error);
461
+ }
462
+ });
463
+ };
464
+ tryPort(5173);
465
+ });
466
+ }
467
+ async exchangeCodeForTokens(code) {
468
+ console.error('🔄 Starting token exchange process...');
469
+ const tokenData = {
470
+ grant_type: 'authorization_code',
471
+ client_id: CLIENT_ID,
472
+ client_secret: CLIENT_SECRET,
473
+ code: code,
474
+ redirect_uri: REDIRECT_URI,
475
+ };
476
+ try {
477
+ console.error('📡 Sending token request to Auth0...');
478
+ const response = await axios.post(API_ENDPOINTS.TOKEN, tokenData, {
479
+ headers: { 'Content-Type': 'application/json' }
480
+ });
481
+ console.error('✅ Token response received');
482
+ const { access_token, id_token, expires_in, token_type } = response.data;
483
+ console.error('🎫 Tokens extracted - access_token:', !!access_token, 'id_token:', !!id_token, 'expires_in:', expires_in, 'token_type:', token_type);
484
+ // Get user info
485
+ console.error('👤 Fetching user information...');
486
+ const userInfoResponse = await axios.get(API_ENDPOINTS.USERINFO, {
487
+ headers: { Authorization: `Bearer ${access_token}` }
488
+ });
489
+ console.error('✅ User info received:', userInfoResponse.data.email);
490
+ // Fetch user organizations
491
+ let organizations = [];
492
+ let selectedOrgId = undefined;
493
+ try {
494
+ console.error('🏢 Attempting to fetch organizations...');
495
+ const orgResponse = await axios.get(API_ENDPOINTS.ORGANIZATIONS, {
496
+ headers: { Authorization: `Bearer ${access_token}` }
497
+ });
498
+ console.error('🏢 Organization response status:', orgResponse.status);
499
+ console.error('🏢 Organization response data:', JSON.stringify(orgResponse.data, null, 2));
500
+ organizations = orgResponse.data.data?.userOrgs || [];
501
+ const currentOrgId = orgResponse.data.data?.currentOrg;
502
+ // Prefer current org from APP; fallback to first available org_id
503
+ selectedOrgId = currentOrgId || organizations[0]?.org_id;
504
+ console.error(`📊 Found ${organizations.length} organizations, current: ${currentOrgId}`);
505
+ if (organizations.length === 0) {
506
+ console.error('⚠️ No organizations found in response - this might cause issues with APP calls');
507
+ }
508
+ else {
509
+ console.error(`🔍 First org structure:`, JSON.stringify(organizations[0], null, 2));
510
+ }
511
+ }
512
+ catch (orgError) {
513
+ console.error('⚠️ Failed to fetch organizations:');
514
+ console.error(' Error:', orgError instanceof Error ? orgError.message : String(orgError));
515
+ if (axios.isAxiosError(orgError)) {
516
+ console.error(' Status:', orgError.response?.status);
517
+ console.error(' Response:', JSON.stringify(orgError.response?.data, null, 2));
518
+ }
519
+ // Continue without organizations - user can still authenticate but won't be able to access org-specific data
520
+ }
521
+ // Store session
522
+ console.error('💾 Creating session object...');
523
+ this.session = {
524
+ // Align with extension behavior: prefer id_token for APP calls
525
+ accessToken: id_token || access_token,
526
+ idToken: id_token,
527
+ tokenType: token_type || 'Bearer',
528
+ userInfo: userInfoResponse.data,
529
+ expiresAt: Date.now() + (expires_in * 1000),
530
+ organizations,
531
+ selectedOrgId
532
+ };
533
+ console.error('✅ Session object created in memory');
534
+ // Save session to persistent storage - COMMENTED OUT FOR SECURITY
535
+ // console.error('💾 Attempting to save session to persistent storage...');
536
+ // try {
537
+ // await this.sessionStorage.saveSession(this.session);
538
+ // console.error('✅ Session saved successfully to persistent storage');
539
+ // } catch (saveError) {
540
+ // console.error('❌ Failed to save session:', saveError);
541
+ // throw saveError;
542
+ // }
543
+ console.error('⚠️ Session storage disabled - credentials not persisted to disk');
544
+ const expirationDate = new Date(this.session.expiresAt).toLocaleString();
545
+ console.error('✅ Authentication successful for user:', userInfoResponse.data.email);
546
+ console.error('💾 Session saved and expires at:', expirationDate);
547
+ }
548
+ catch (error) {
549
+ const errorMessage = error instanceof Error ? error.message : String(error);
550
+ const responseData = error?.response?.data;
551
+ console.error('❌ Token exchange failed:', responseData || errorMessage);
552
+ throw new Error('Failed to exchange authorization code for tokens');
553
+ }
554
+ }
555
+ }
@@ -0,0 +1,5 @@
1
+ import { AuthenticationManager } from "./authManager.js";
2
+ export declare function getOrCreateSession(username: string, password: string): Promise<AuthenticationManager>;
3
+ export declare function invalidateSession(username: string): void;
4
+ export declare function invalidateSessionByToken(token: string): void;
5
+ //# sourceMappingURL=sessionCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionCache.d.ts","sourceRoot":"","sources":["../../src/auth/sessionCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAKzD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAY3G;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGxD;AAGD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAQ5D"}
@@ -0,0 +1,29 @@
1
+ import { AuthenticationManager } from "./authManager.js";
2
+ // In-memory token cache keyed by username
3
+ const cache = new Map();
4
+ export async function getOrCreateSession(username, password) {
5
+ const cached = cache.get(username);
6
+ if (cached && cached.isAuthenticated()) {
7
+ return cached;
8
+ }
9
+ // First request or token expired — login fresh
10
+ console.log(`Logging in: ${username}`);
11
+ const manager = await AuthenticationManager.login(username, password);
12
+ cache.set(username, manager);
13
+ console.log(`Session created for: ${username}`);
14
+ return manager;
15
+ }
16
+ export function invalidateSession(username) {
17
+ cache.delete(username);
18
+ console.log(`Session invalidated for: ${username}`);
19
+ }
20
+ // Called by tool handlers when an API call gets a 401 — token rejected by PerfAI
21
+ export function invalidateSessionByToken(token) {
22
+ for (const [username, manager] of cache.entries()) {
23
+ if (manager.getAccessToken() === token) {
24
+ cache.delete(username);
25
+ console.log(`Session evicted (token rejected) for: ${username}`);
26
+ return;
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,53 @@
1
+ import { UserSession } from './types.js';
2
+ /**
3
+ * Secure session storage for MCP PerfAI server
4
+ * Handles persistent storage of authentication sessions with encryption
5
+ */
6
+ export declare class SessionStorage {
7
+ private readonly storageDir;
8
+ private readonly sessionFile;
9
+ private readonly keyFile;
10
+ private encryptionKey;
11
+ constructor(appName?: string);
12
+ /**
13
+ * Get OS-specific configuration directory
14
+ */
15
+ private getConfigDirectory;
16
+ /**
17
+ * Ensure storage directory exists
18
+ */
19
+ private ensureStorageDirectory;
20
+ /**
21
+ * Initialize or load encryption key
22
+ */
23
+ private initializeEncryptionKey;
24
+ /**
25
+ * Encrypt data using AES-256-CBC
26
+ */
27
+ private encrypt;
28
+ /**
29
+ * Decrypt data using AES-256-CBC
30
+ */
31
+ private decrypt;
32
+ /**
33
+ * Save session to persistent storage
34
+ */
35
+ saveSession(session: UserSession): Promise<void>;
36
+ /**
37
+ * Load session from persistent storage
38
+ */
39
+ loadSession(): Promise<UserSession | null>;
40
+ /**
41
+ * Clear stored session
42
+ */
43
+ clearSession(): void;
44
+ /**
45
+ * Check if session exists and is valid
46
+ */
47
+ hasValidSession(): boolean;
48
+ /**
49
+ * Get session storage location (for debugging)
50
+ */
51
+ getStorageLocation(): string;
52
+ }
53
+ //# sourceMappingURL=sessionStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionStorage.d.ts","sourceRoot":"","sources":["../../src/auth/sessionStorage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,aAAa,CAAuB;gBAEhC,OAAO,GAAE,MAAqB;IAU1C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;OAEG;IACH,OAAO,CAAC,OAAO;IAqBf;;OAEG;IACH,OAAO,CAAC,OAAO;IA4Bf;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BtD;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA+ChD;;OAEG;IACH,YAAY,IAAI,IAAI;IAWpB;;OAEG;IACH,eAAe,IAAI,OAAO;IAoB1B;;OAEG;IACH,kBAAkB,IAAI,MAAM;CAG7B"}