mlgym-deploy 2.0.1 → 2.1.0
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/index.js +714 -63
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,6 +16,65 @@ import crypto from 'crypto';
|
|
|
16
16
|
|
|
17
17
|
const execAsync = promisify(exec);
|
|
18
18
|
|
|
19
|
+
// Current version of this MCP server
|
|
20
|
+
const CURRENT_VERSION = '2.1.0';
|
|
21
|
+
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
|
+
|
|
23
|
+
// Version check state
|
|
24
|
+
let versionCheckResult = null;
|
|
25
|
+
let lastVersionCheck = 0;
|
|
26
|
+
const VERSION_CHECK_INTERVAL = 3600000; // Check once per hour
|
|
27
|
+
|
|
28
|
+
// Helper to check for updates
|
|
29
|
+
async function checkForUpdates() {
|
|
30
|
+
try {
|
|
31
|
+
// Check if we've recently checked
|
|
32
|
+
if (Date.now() - lastVersionCheck < VERSION_CHECK_INTERVAL && versionCheckResult) {
|
|
33
|
+
return versionCheckResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get latest version from npm
|
|
37
|
+
const { stdout } = await execAsync(`npm view ${PACKAGE_NAME} version`);
|
|
38
|
+
const latestVersion = stdout.trim();
|
|
39
|
+
|
|
40
|
+
// Compare versions
|
|
41
|
+
const isUpdateAvailable = latestVersion !== CURRENT_VERSION;
|
|
42
|
+
|
|
43
|
+
versionCheckResult = {
|
|
44
|
+
current: CURRENT_VERSION,
|
|
45
|
+
latest: latestVersion,
|
|
46
|
+
updateAvailable: isUpdateAvailable,
|
|
47
|
+
lastChecked: new Date().toISOString()
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
lastVersionCheck = Date.now();
|
|
51
|
+
|
|
52
|
+
// Log to stderr if update available (visible in Cursor logs)
|
|
53
|
+
if (isUpdateAvailable) {
|
|
54
|
+
console.error(`\n╔════════════════════════════════════════════════════════════════╗`);
|
|
55
|
+
console.error(`║ MLGym MCP Server Update Available! ║`);
|
|
56
|
+
console.error(`║ ║`);
|
|
57
|
+
console.error(`║ Current version: ${CURRENT_VERSION.padEnd(49)} ║`);
|
|
58
|
+
console.error(`║ Latest version: ${latestVersion.padEnd(49)} ║`);
|
|
59
|
+
console.error(`║ ║`);
|
|
60
|
+
console.error(`║ To update, run: npm install -g ${PACKAGE_NAME}@latest ║`);
|
|
61
|
+
console.error(`╚════════════════════════════════════════════════════════════════╝\n`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return versionCheckResult;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// Silent fail - don't disrupt normal operation
|
|
67
|
+
console.error('Version check failed (network may be offline):', error.message);
|
|
68
|
+
return {
|
|
69
|
+
current: CURRENT_VERSION,
|
|
70
|
+
latest: 'unknown',
|
|
71
|
+
updateAvailable: false,
|
|
72
|
+
error: error.message,
|
|
73
|
+
lastChecked: new Date().toISOString()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
19
78
|
// Helper to generate random password
|
|
20
79
|
function generateRandomPassword() {
|
|
21
80
|
const chars = 'ABCDEFGHJKLMNPQRSTWXYZabcdefghjkmnpqrstwxyz23456789!@#$%^&*';
|
|
@@ -195,6 +254,35 @@ async function apiRequest(method, endpoint, data = null, useAuth = true) {
|
|
|
195
254
|
}
|
|
196
255
|
}
|
|
197
256
|
|
|
257
|
+
// Global error and status tracking
|
|
258
|
+
let lastError = null;
|
|
259
|
+
let lastOperation = null;
|
|
260
|
+
let operationHistory = [];
|
|
261
|
+
const MAX_HISTORY = 20;
|
|
262
|
+
|
|
263
|
+
// Helper to track operations
|
|
264
|
+
function trackOperation(operation, success = true, error = null) {
|
|
265
|
+
const entry = {
|
|
266
|
+
operation,
|
|
267
|
+
success,
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
error: error ? error.message || error : null
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
lastOperation = entry;
|
|
273
|
+
if (!success && error) {
|
|
274
|
+
lastError = {
|
|
275
|
+
...entry,
|
|
276
|
+
stack: error.stack || null
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
operationHistory.unshift(entry);
|
|
281
|
+
if (operationHistory.length > MAX_HISTORY) {
|
|
282
|
+
operationHistory = operationHistory.slice(0, MAX_HISTORY);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
198
286
|
// Create MCP server
|
|
199
287
|
const server = new Server(
|
|
200
288
|
{
|
|
@@ -210,6 +298,119 @@ const server = new Server(
|
|
|
210
298
|
|
|
211
299
|
// Tool definitions
|
|
212
300
|
const TOOLS = [
|
|
301
|
+
{
|
|
302
|
+
name: 'mlgym_git_configure',
|
|
303
|
+
description: 'Check and configure git user settings (required before making commits)',
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: {
|
|
307
|
+
email: {
|
|
308
|
+
type: 'string',
|
|
309
|
+
description: 'Email to use for git commits (defaults to MLGym user email if logged in)',
|
|
310
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
311
|
+
},
|
|
312
|
+
name: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
description: 'Name to use for git commits (defaults to email username if not provided)'
|
|
315
|
+
},
|
|
316
|
+
check_only: {
|
|
317
|
+
type: 'boolean',
|
|
318
|
+
description: 'If true, only check current configuration without changing it',
|
|
319
|
+
default: false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'mlgym_debug_last_error',
|
|
326
|
+
description: 'Get detailed error information from the last failed operation',
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: 'object',
|
|
329
|
+
properties: {
|
|
330
|
+
include_stack: {
|
|
331
|
+
type: 'boolean',
|
|
332
|
+
description: 'Include stack trace if available',
|
|
333
|
+
default: true
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: 'mlgym_debug_status',
|
|
340
|
+
description: 'Get current system status and recent operations',
|
|
341
|
+
inputSchema: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: {
|
|
344
|
+
verbose: {
|
|
345
|
+
type: 'boolean',
|
|
346
|
+
description: 'Include detailed status information',
|
|
347
|
+
default: false
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'mlgym_debug_logs',
|
|
354
|
+
description: 'View recent logs from backend, User Agent, or Coolify',
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: {
|
|
358
|
+
source: {
|
|
359
|
+
type: 'string',
|
|
360
|
+
enum: ['backend', 'user-agent', 'coolify', 'all'],
|
|
361
|
+
description: 'Which service logs to retrieve',
|
|
362
|
+
default: 'all'
|
|
363
|
+
},
|
|
364
|
+
lines: {
|
|
365
|
+
type: 'integer',
|
|
366
|
+
description: 'Number of log lines to retrieve',
|
|
367
|
+
default: 50,
|
|
368
|
+
minimum: 10,
|
|
369
|
+
maximum: 500
|
|
370
|
+
},
|
|
371
|
+
filter: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
description: 'Filter logs by keyword (e.g., "error", "deployment", "user")'
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'mlgym_debug_deployment',
|
|
380
|
+
description: 'Check detailed deployment status and troubleshoot deployment issues',
|
|
381
|
+
inputSchema: {
|
|
382
|
+
type: 'object',
|
|
383
|
+
properties: {
|
|
384
|
+
project_id: {
|
|
385
|
+
type: 'integer',
|
|
386
|
+
description: 'Project ID to check deployment status for'
|
|
387
|
+
},
|
|
388
|
+
deployment_uuid: {
|
|
389
|
+
type: 'string',
|
|
390
|
+
description: 'Specific deployment UUID to investigate'
|
|
391
|
+
},
|
|
392
|
+
check_queue: {
|
|
393
|
+
type: 'boolean',
|
|
394
|
+
description: 'Check Coolify deployment queue status',
|
|
395
|
+
default: true
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'mlgym_version_check',
|
|
402
|
+
description: 'Check for MCP server updates and get version information',
|
|
403
|
+
inputSchema: {
|
|
404
|
+
type: 'object',
|
|
405
|
+
properties: {
|
|
406
|
+
check_now: {
|
|
407
|
+
type: 'boolean',
|
|
408
|
+
description: 'Force immediate version check (bypasses cache)',
|
|
409
|
+
default: false
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
213
414
|
{
|
|
214
415
|
name: 'mlgym_user_create',
|
|
215
416
|
description: 'Create a new user with GitLab, Coolify, and SSH key setup. IMPORTANT: You must explicitly ask the user for their email, full name, password, and terms acceptance.',
|
|
@@ -241,7 +442,7 @@ const TOOLS = [
|
|
|
241
442
|
},
|
|
242
443
|
{
|
|
243
444
|
name: 'mlgym_project_init',
|
|
244
|
-
description: 'Initialize and deploy a project with GitLab repository and Coolify deployment. IMPORTANT: You must explicitly ask the user for project name and
|
|
445
|
+
description: 'Initialize and deploy a project with GitLab repository and Coolify deployment. IMPORTANT: You must explicitly ask the user for project name, description, and deployment URL if deployment is enabled.',
|
|
245
446
|
inputSchema: {
|
|
246
447
|
type: 'object',
|
|
247
448
|
properties: {
|
|
@@ -261,6 +462,13 @@ const TOOLS = [
|
|
|
261
462
|
description: 'Enable Coolify deployment with webhooks (ask user yes/no)',
|
|
262
463
|
default: true
|
|
263
464
|
},
|
|
465
|
+
hostname: {
|
|
466
|
+
type: 'string',
|
|
467
|
+
description: 'Custom hostname for deployment (REQUIRED if enable_deployment is true: ask user for their preferred hostname, will be used as subdomain)',
|
|
468
|
+
pattern: '^[a-z][a-z0-9-]*[a-z0-9]$',
|
|
469
|
+
minLength: 3,
|
|
470
|
+
maxLength: 63
|
|
471
|
+
},
|
|
264
472
|
local_path: {
|
|
265
473
|
type: 'string',
|
|
266
474
|
description: 'Local directory path to initialize as git repository',
|
|
@@ -955,6 +1163,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
955
1163
|
const { name, arguments: args } = request.params;
|
|
956
1164
|
|
|
957
1165
|
switch (name) {
|
|
1166
|
+
case 'mlgym_git_configure':
|
|
1167
|
+
return await configureGit(args);
|
|
1168
|
+
|
|
1169
|
+
case 'mlgym_debug_last_error':
|
|
1170
|
+
return await debugLastError(args);
|
|
1171
|
+
|
|
1172
|
+
case 'mlgym_debug_status':
|
|
1173
|
+
return await debugStatus(args);
|
|
1174
|
+
|
|
1175
|
+
case 'mlgym_debug_logs':
|
|
1176
|
+
return await debugLogs(args);
|
|
1177
|
+
|
|
1178
|
+
case 'mlgym_debug_deployment':
|
|
1179
|
+
return await debugDeployment(args);
|
|
1180
|
+
|
|
1181
|
+
case 'mlgym_version_check':
|
|
1182
|
+
return await versionCheck(args);
|
|
1183
|
+
|
|
958
1184
|
case 'mlgym_user_create':
|
|
959
1185
|
return await createUser(args);
|
|
960
1186
|
|
|
@@ -1138,6 +1364,342 @@ async function generateSSHKeyPair(email) {
|
|
|
1138
1364
|
}
|
|
1139
1365
|
}
|
|
1140
1366
|
|
|
1367
|
+
// Tool implementation: Debug Last Error
|
|
1368
|
+
async function debugLastError(args) {
|
|
1369
|
+
const { include_stack = true } = args;
|
|
1370
|
+
|
|
1371
|
+
if (!lastError) {
|
|
1372
|
+
return {
|
|
1373
|
+
content: [{
|
|
1374
|
+
type: 'text',
|
|
1375
|
+
text: JSON.stringify({
|
|
1376
|
+
status: 'info',
|
|
1377
|
+
message: 'No errors recorded',
|
|
1378
|
+
last_operation: lastOperation || 'None'
|
|
1379
|
+
}, null, 2)
|
|
1380
|
+
}]
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const response = {
|
|
1385
|
+
status: 'error',
|
|
1386
|
+
error: {
|
|
1387
|
+
operation: lastError.operation,
|
|
1388
|
+
message: lastError.error,
|
|
1389
|
+
timestamp: lastError.timestamp
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
if (include_stack && lastError.stack) {
|
|
1394
|
+
response.error.stack = lastError.stack;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
content: [{
|
|
1399
|
+
type: 'text',
|
|
1400
|
+
text: JSON.stringify(response, null, 2)
|
|
1401
|
+
}]
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Tool implementation: Debug Status
|
|
1406
|
+
async function debugStatus(args) {
|
|
1407
|
+
const { verbose = false } = args;
|
|
1408
|
+
|
|
1409
|
+
// Check authentication status
|
|
1410
|
+
const auth = await loadAuth();
|
|
1411
|
+
const isAuthenticated = !!auth.token;
|
|
1412
|
+
|
|
1413
|
+
const response = {
|
|
1414
|
+
status: 'info',
|
|
1415
|
+
authentication: {
|
|
1416
|
+
logged_in: isAuthenticated,
|
|
1417
|
+
user: isAuthenticated ? auth.email : null
|
|
1418
|
+
},
|
|
1419
|
+
last_operation: lastOperation || { message: 'No operations recorded' },
|
|
1420
|
+
recent_operations: operationHistory.slice(0, verbose ? 10 : 5).map(op => ({
|
|
1421
|
+
operation: op.operation,
|
|
1422
|
+
success: op.success,
|
|
1423
|
+
timestamp: op.timestamp,
|
|
1424
|
+
error: op.error
|
|
1425
|
+
}))
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
if (verbose) {
|
|
1429
|
+
response.backend_url = process.env.GITLAB_BACKEND_URL || 'https://backend.eu.ezb.net';
|
|
1430
|
+
response.gitlab_url = process.env.GITLAB_URL || 'https://git.mlgym.io';
|
|
1431
|
+
response.coolify_url = process.env.COOLIFY_URL || 'https://coolify.eu.ezb.net';
|
|
1432
|
+
response.error_count = operationHistory.filter(op => !op.success).length;
|
|
1433
|
+
response.success_count = operationHistory.filter(op => op.success).length;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
return {
|
|
1437
|
+
content: [{
|
|
1438
|
+
type: 'text',
|
|
1439
|
+
text: JSON.stringify(response, null, 2)
|
|
1440
|
+
}]
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Tool implementation: Debug Logs
|
|
1445
|
+
async function debugLogs(args) {
|
|
1446
|
+
const { source = 'all', lines = 50, filter } = args;
|
|
1447
|
+
|
|
1448
|
+
// Since we're in an MCP server, we can't directly access remote logs
|
|
1449
|
+
// But we can provide instructions on how to check them
|
|
1450
|
+
const instructions = {
|
|
1451
|
+
status: 'info',
|
|
1452
|
+
message: 'Log viewing instructions',
|
|
1453
|
+
sources: {}
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
if (source === 'all' || source === 'backend') {
|
|
1457
|
+
instructions.sources.backend = {
|
|
1458
|
+
description: 'GitLab Backend Server logs',
|
|
1459
|
+
commands: [
|
|
1460
|
+
'ssh -i ~/.ssh/id_rsa_chka05 root@backend.eu.ezb.net',
|
|
1461
|
+
'docker logs gitlab-admin-server --tail ' + lines,
|
|
1462
|
+
filter ? `docker logs gitlab-admin-server 2>&1 | grep -i "${filter}" | tail -${lines}` : null
|
|
1463
|
+
].filter(Boolean)
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (source === 'all' || source === 'user-agent') {
|
|
1468
|
+
instructions.sources.user_agent = {
|
|
1469
|
+
description: 'Coolify User Agent logs',
|
|
1470
|
+
commands: [
|
|
1471
|
+
'ssh -i ~/.ssh/id_rsa_chka05 root@coolify.eu.ezb.net',
|
|
1472
|
+
'docker logs coolify-user-agent --tail ' + lines,
|
|
1473
|
+
filter ? `docker logs coolify-user-agent 2>&1 | grep -i "${filter}" | tail -${lines}` : null
|
|
1474
|
+
].filter(Boolean)
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
if (source === 'all' || source === 'coolify') {
|
|
1479
|
+
instructions.sources.coolify = {
|
|
1480
|
+
description: 'Coolify platform logs',
|
|
1481
|
+
commands: [
|
|
1482
|
+
'ssh -i ~/.ssh/id_rsa_chka05 root@coolify.eu.ezb.net',
|
|
1483
|
+
'docker logs coolify --tail ' + lines,
|
|
1484
|
+
filter ? `docker logs coolify 2>&1 | grep -i "${filter}" | tail -${lines}` : null
|
|
1485
|
+
].filter(Boolean)
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
instructions.note = 'The AI assistant should execute these commands to retrieve logs for debugging';
|
|
1490
|
+
|
|
1491
|
+
return {
|
|
1492
|
+
content: [{
|
|
1493
|
+
type: 'text',
|
|
1494
|
+
text: JSON.stringify(instructions, null, 2)
|
|
1495
|
+
}]
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Tool implementation: Debug Deployment
|
|
1500
|
+
async function debugDeployment(args) {
|
|
1501
|
+
const { project_id, deployment_uuid, check_queue = true } = args;
|
|
1502
|
+
|
|
1503
|
+
const response = {
|
|
1504
|
+
status: 'info',
|
|
1505
|
+
deployment_debugging: {}
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1508
|
+
// Get authentication
|
|
1509
|
+
const auth = await loadAuth();
|
|
1510
|
+
if (!auth.token) {
|
|
1511
|
+
return {
|
|
1512
|
+
content: [{
|
|
1513
|
+
type: 'text',
|
|
1514
|
+
text: JSON.stringify({
|
|
1515
|
+
status: 'error',
|
|
1516
|
+
message: 'Not authenticated. Login required to check deployments.'
|
|
1517
|
+
}, null, 2)
|
|
1518
|
+
}]
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (project_id) {
|
|
1523
|
+
// Get project deployment status
|
|
1524
|
+
try {
|
|
1525
|
+
const result = await apiRequest('GET', `/api/v1/projects/${project_id}/deployments?limit=5`);
|
|
1526
|
+
response.deployment_debugging.project_deployments = result.success ? result.data : { error: result.error };
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
response.deployment_debugging.project_deployments = { error: error.message };
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if (deployment_uuid) {
|
|
1533
|
+
response.deployment_debugging.specific_deployment = {
|
|
1534
|
+
uuid: deployment_uuid,
|
|
1535
|
+
check_commands: [
|
|
1536
|
+
'Check deployment status in Coolify database:',
|
|
1537
|
+
`ssh -i ~/.ssh/id_rsa_chka05 root@coolify.eu.ezb.net`,
|
|
1538
|
+
`docker exec coolify-db psql -U coolify -d coolify -c "SELECT * FROM application_deployment_queues WHERE deployment_uuid = '${deployment_uuid}';"`,
|
|
1539
|
+
'',
|
|
1540
|
+
'Check if deployment is stuck in queue:',
|
|
1541
|
+
`docker exec coolify sh -c "cd /var/www/html && php artisan check:deployment-queue"`
|
|
1542
|
+
]
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (check_queue) {
|
|
1547
|
+
response.deployment_debugging.queue_status = {
|
|
1548
|
+
description: 'Commands to check Coolify deployment queue',
|
|
1549
|
+
commands: [
|
|
1550
|
+
'Check recent deployments:',
|
|
1551
|
+
`ssh -i ~/.ssh/id_rsa_chka05 root@coolify.eu.ezb.net`,
|
|
1552
|
+
`docker exec coolify-db psql -U coolify -d coolify -c "SELECT id, application_id, deployment_uuid, status, created_at FROM application_deployment_queues ORDER BY created_at DESC LIMIT 10;"`,
|
|
1553
|
+
'',
|
|
1554
|
+
'Check if Horizon queue processor is running:',
|
|
1555
|
+
`docker exec coolify sh -c "cd /var/www/html && php artisan horizon:status"`,
|
|
1556
|
+
'',
|
|
1557
|
+
'Try to manually process queue:',
|
|
1558
|
+
`docker exec coolify sh -c "cd /var/www/html && php artisan queue:work --once"`
|
|
1559
|
+
]
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
response.common_issues = [
|
|
1564
|
+
'Deployment stuck in "queued" status: Coolify queue processor not picking up jobs',
|
|
1565
|
+
'Authentication errors: SSH keys not properly configured',
|
|
1566
|
+
'Build failures: Check Dockerfile or build configuration',
|
|
1567
|
+
'Network issues: Verify GitLab repository is accessible'
|
|
1568
|
+
];
|
|
1569
|
+
|
|
1570
|
+
return {
|
|
1571
|
+
content: [{
|
|
1572
|
+
type: 'text',
|
|
1573
|
+
text: JSON.stringify(response, null, 2)
|
|
1574
|
+
}]
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// Tool implementation: Version Check
|
|
1579
|
+
async function versionCheck(args) {
|
|
1580
|
+
const { check_now = false } = args;
|
|
1581
|
+
|
|
1582
|
+
// Force check if requested
|
|
1583
|
+
if (check_now) {
|
|
1584
|
+
lastVersionCheck = 0;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Check for updates
|
|
1588
|
+
const versionInfo = await checkForUpdates();
|
|
1589
|
+
|
|
1590
|
+
const response = {
|
|
1591
|
+
status: 'success',
|
|
1592
|
+
version: {
|
|
1593
|
+
current: versionInfo.current,
|
|
1594
|
+
latest: versionInfo.latest,
|
|
1595
|
+
update_available: versionInfo.updateAvailable,
|
|
1596
|
+
last_checked: versionInfo.lastChecked
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
if (versionInfo.updateAvailable) {
|
|
1601
|
+
response.update_instructions = {
|
|
1602
|
+
message: '🔄 A new version of MLGym MCP Server is available!',
|
|
1603
|
+
current_version: versionInfo.current,
|
|
1604
|
+
latest_version: versionInfo.latest,
|
|
1605
|
+
commands: [
|
|
1606
|
+
'To update globally:',
|
|
1607
|
+
`npm install -g ${PACKAGE_NAME}@latest`,
|
|
1608
|
+
'',
|
|
1609
|
+
'Or update to a specific version:',
|
|
1610
|
+
`npm install -g ${PACKAGE_NAME}@${versionInfo.latest}`,
|
|
1611
|
+
'',
|
|
1612
|
+
'After updating, restart Cursor IDE to load the new version.'
|
|
1613
|
+
],
|
|
1614
|
+
changelog_url: `https://www.npmjs.com/package/${PACKAGE_NAME}?activeTab=versions`
|
|
1615
|
+
};
|
|
1616
|
+
} else {
|
|
1617
|
+
response.message = '✅ You are running the latest version of MLGym MCP Server';
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (versionInfo.error) {
|
|
1621
|
+
response.note = `Version check had issues: ${versionInfo.error}`;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
return {
|
|
1625
|
+
content: [{
|
|
1626
|
+
type: 'text',
|
|
1627
|
+
text: JSON.stringify(response, null, 2)
|
|
1628
|
+
}]
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Tool implementation: Configure Git
|
|
1633
|
+
async function configureGit(args) {
|
|
1634
|
+
const { email, name, check_only = false } = args;
|
|
1635
|
+
|
|
1636
|
+
// Try to get email from auth if not provided
|
|
1637
|
+
let gitEmail = email;
|
|
1638
|
+
let gitName = name;
|
|
1639
|
+
|
|
1640
|
+
if (!gitEmail && !check_only) {
|
|
1641
|
+
// Try to get from authenticated user
|
|
1642
|
+
const auth = await loadAuth();
|
|
1643
|
+
if (auth.token && auth.email) {
|
|
1644
|
+
gitEmail = auth.email;
|
|
1645
|
+
gitName = gitName || auth.email.split('@')[0];
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// If just checking, return current configuration
|
|
1650
|
+
if (check_only) {
|
|
1651
|
+
return {
|
|
1652
|
+
content: [{
|
|
1653
|
+
type: 'text',
|
|
1654
|
+
text: JSON.stringify({
|
|
1655
|
+
status: 'info',
|
|
1656
|
+
message: 'Git configuration check',
|
|
1657
|
+
instructions: [
|
|
1658
|
+
'Run: git config --global user.email',
|
|
1659
|
+
'Run: git config --global user.name',
|
|
1660
|
+
'If not set, configure with:',
|
|
1661
|
+
`git config --global user.email "${gitEmail || 'your-email@example.com'}"`,
|
|
1662
|
+
`git config --global user.name "${gitName || 'Your Name'}"`,
|
|
1663
|
+
'',
|
|
1664
|
+
'IMPORTANT: Git must be configured before making any commits.'
|
|
1665
|
+
]
|
|
1666
|
+
}, null, 2)
|
|
1667
|
+
}]
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// Return configuration instructions
|
|
1672
|
+
if (!gitEmail) {
|
|
1673
|
+
return {
|
|
1674
|
+
content: [{
|
|
1675
|
+
type: 'text',
|
|
1676
|
+
text: JSON.stringify({
|
|
1677
|
+
status: 'error',
|
|
1678
|
+
message: 'Email is required for git configuration',
|
|
1679
|
+
solution: 'Either provide email parameter or login with mlgym_auth_login first'
|
|
1680
|
+
}, null, 2)
|
|
1681
|
+
}]
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
gitName = gitName || gitEmail.split('@')[0];
|
|
1686
|
+
|
|
1687
|
+
return {
|
|
1688
|
+
content: [{
|
|
1689
|
+
type: 'text',
|
|
1690
|
+
text: JSON.stringify({
|
|
1691
|
+
status: 'success',
|
|
1692
|
+
message: 'Git configuration instructions',
|
|
1693
|
+
commands: [
|
|
1694
|
+
`git config --global user.email "${gitEmail}"`,
|
|
1695
|
+
`git config --global user.name "${gitName}"`
|
|
1696
|
+
],
|
|
1697
|
+
note: 'The AI assistant should execute these commands to configure git before making any commits'
|
|
1698
|
+
}, null, 2)
|
|
1699
|
+
}]
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1141
1703
|
// Tool implementation: Create User
|
|
1142
1704
|
async function createUser(args) {
|
|
1143
1705
|
let { email, name, password, accept_terms } = args;
|
|
@@ -1216,6 +1778,8 @@ async function createUser(args) {
|
|
|
1216
1778
|
console.error(`SSH key generated: ${privateKeyPath}`);
|
|
1217
1779
|
|
|
1218
1780
|
// Create user via backend API with SSH key
|
|
1781
|
+
trackOperation('user_create', true, null);
|
|
1782
|
+
|
|
1219
1783
|
const result = await apiRequest('POST', '/api/v1/users', {
|
|
1220
1784
|
email,
|
|
1221
1785
|
name,
|
|
@@ -1224,6 +1788,7 @@ async function createUser(args) {
|
|
|
1224
1788
|
}, false);
|
|
1225
1789
|
|
|
1226
1790
|
if (!result.success) {
|
|
1791
|
+
trackOperation('user_create', false, new Error(result.error || 'User creation failed'));
|
|
1227
1792
|
return {
|
|
1228
1793
|
content: [{
|
|
1229
1794
|
type: 'text',
|
|
@@ -1353,22 +1918,46 @@ async function detectProjectName(localPath) {
|
|
|
1353
1918
|
|
|
1354
1919
|
// Tool implementation: Initialize Project
|
|
1355
1920
|
async function initProject(args) {
|
|
1356
|
-
let { name, description, enable_deployment = true, local_path = '.' } = args;
|
|
1921
|
+
let { name, description, enable_deployment = true, hostname, local_path = '.' } = args;
|
|
1922
|
+
|
|
1923
|
+
// Start tracking this operation
|
|
1924
|
+
const operationId = `init-project-${Date.now()}`;
|
|
1357
1925
|
|
|
1358
1926
|
// Validate required fields are provided
|
|
1359
1927
|
if (!name || !description) {
|
|
1928
|
+
const error = {
|
|
1929
|
+
status: 'error',
|
|
1930
|
+
message: 'Project name and description are required and must be provided by user',
|
|
1931
|
+
required_fields: {
|
|
1932
|
+
name: name ? '✓ provided' : '✗ missing - must be provided by user',
|
|
1933
|
+
description: description ? '✓ provided' : '✗ missing - must be provided by user'
|
|
1934
|
+
},
|
|
1935
|
+
note: 'Auto-detection is disabled. User must explicitly provide project details.'
|
|
1936
|
+
};
|
|
1937
|
+
trackOperation('mlgym_project_init', operationId, 'failed', error.message, { args, error });
|
|
1360
1938
|
return {
|
|
1361
1939
|
content: [{
|
|
1362
1940
|
type: 'text',
|
|
1363
|
-
text: JSON.stringify(
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1941
|
+
text: JSON.stringify(error, null, 2)
|
|
1942
|
+
}]
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// Validate hostname if deployment is enabled
|
|
1947
|
+
if (enable_deployment && !hostname) {
|
|
1948
|
+
const error = {
|
|
1949
|
+
status: 'error',
|
|
1950
|
+
message: 'Hostname is required when deployment is enabled',
|
|
1951
|
+
required_fields: {
|
|
1952
|
+
hostname: '✗ missing - must be provided by user (will be used as subdomain)'
|
|
1953
|
+
},
|
|
1954
|
+
note: 'Please provide a unique hostname for your deployment (e.g., "myapp" for myapp.ezb.net)'
|
|
1955
|
+
};
|
|
1956
|
+
trackOperation('mlgym_project_init', operationId, 'failed', error.message, { args, error });
|
|
1957
|
+
return {
|
|
1958
|
+
content: [{
|
|
1959
|
+
type: 'text',
|
|
1960
|
+
text: JSON.stringify(error, null, 2)
|
|
1372
1961
|
}]
|
|
1373
1962
|
};
|
|
1374
1963
|
}
|
|
@@ -1378,10 +1967,12 @@ async function initProject(args) {
|
|
|
1378
1967
|
// Check authentication
|
|
1379
1968
|
const auth = await loadAuth();
|
|
1380
1969
|
if (!auth.token) {
|
|
1970
|
+
const errorMsg = 'Not authenticated. Please create a user first with mlgym_user_create';
|
|
1971
|
+
trackOperation('mlgym_project_init', operationId, 'failed', errorMsg, { args });
|
|
1381
1972
|
return {
|
|
1382
1973
|
content: [{
|
|
1383
1974
|
type: 'text',
|
|
1384
|
-
text:
|
|
1975
|
+
text: `Error: ${errorMsg}`
|
|
1385
1976
|
}]
|
|
1386
1977
|
};
|
|
1387
1978
|
}
|
|
@@ -1395,16 +1986,19 @@ async function initProject(args) {
|
|
|
1395
1986
|
description,
|
|
1396
1987
|
visibility: 'private',
|
|
1397
1988
|
enable_deployment: enable_deployment,
|
|
1398
|
-
webhook_secret: webhookSecret
|
|
1989
|
+
webhook_secret: webhookSecret,
|
|
1990
|
+
hostname: hostname || null
|
|
1399
1991
|
};
|
|
1400
1992
|
|
|
1401
1993
|
const result = await apiRequest('POST', '/api/v1/projects', projectData);
|
|
1402
1994
|
|
|
1403
1995
|
if (!result.success) {
|
|
1996
|
+
const errorMsg = `Failed to create project: ${result.error}`;
|
|
1997
|
+
trackOperation('mlgym_project_init', operationId, 'failed', errorMsg, { args, projectData, response: result });
|
|
1404
1998
|
return {
|
|
1405
1999
|
content: [{
|
|
1406
2000
|
type: 'text',
|
|
1407
|
-
text:
|
|
2001
|
+
text: errorMsg
|
|
1408
2002
|
}]
|
|
1409
2003
|
};
|
|
1410
2004
|
}
|
|
@@ -1443,7 +2037,13 @@ async function initProject(args) {
|
|
|
1443
2037
|
}
|
|
1444
2038
|
|
|
1445
2039
|
// Add git commands for local setup
|
|
2040
|
+
// First ensure git is configured
|
|
1446
2041
|
response.next_steps = [
|
|
2042
|
+
'# First, ensure git is configured (check with: git config --list | grep user)',
|
|
2043
|
+
'# If not configured, run:',
|
|
2044
|
+
`git config --global user.email "${auth.email}"`,
|
|
2045
|
+
`git config --global user.name "${auth.email.split('@')[0]}"`,
|
|
2046
|
+
'# Then initialize and push the repository:',
|
|
1447
2047
|
`cd ${local_path}`,
|
|
1448
2048
|
'git init',
|
|
1449
2049
|
`git remote add origin ${project.ssh_url_to_repo}`,
|
|
@@ -1452,6 +2052,11 @@ async function initProject(args) {
|
|
|
1452
2052
|
'git push -u origin main'
|
|
1453
2053
|
];
|
|
1454
2054
|
|
|
2055
|
+
response.important_note = 'IMPORTANT: Ensure git is configured with user.email and user.name before committing. The AI should check this first and configure if needed.';
|
|
2056
|
+
|
|
2057
|
+
// Track successful operation
|
|
2058
|
+
trackOperation('mlgym_project_init', operationId, 'success', `Project ${name} created successfully`, { args, response });
|
|
2059
|
+
|
|
1455
2060
|
return {
|
|
1456
2061
|
content: [{
|
|
1457
2062
|
type: 'text',
|
|
@@ -1474,6 +2079,9 @@ function generateWebhookSecret() {
|
|
|
1474
2079
|
async function recoverUser(args) {
|
|
1475
2080
|
const { email, pin, new_password } = args;
|
|
1476
2081
|
|
|
2082
|
+
// Start tracking this operation
|
|
2083
|
+
const operationId = `recover-user-${Date.now()}`;
|
|
2084
|
+
|
|
1477
2085
|
// Clean up any expired PINs first
|
|
1478
2086
|
await cleanupExpiredPINs();
|
|
1479
2087
|
|
|
@@ -1492,6 +2100,9 @@ async function recoverUser(args) {
|
|
|
1492
2100
|
|
|
1493
2101
|
console.error(`Recovery PIN generated for ${email}: ${generatedPIN}`);
|
|
1494
2102
|
|
|
2103
|
+
// Track PIN generation
|
|
2104
|
+
trackOperation('mlgym_user_recover', operationId, 'success', `Recovery PIN generated for ${email}`, { args: { email }, stage: 'pin_generation' });
|
|
2105
|
+
|
|
1495
2106
|
// In production, this would send an actual email
|
|
1496
2107
|
// For now, we'll include it in the response for testing
|
|
1497
2108
|
return {
|
|
@@ -1513,13 +2124,15 @@ async function recoverUser(args) {
|
|
|
1513
2124
|
const stored = await loadPIN(email);
|
|
1514
2125
|
|
|
1515
2126
|
if (!stored) {
|
|
2127
|
+
const error = {
|
|
2128
|
+
status: 'error',
|
|
2129
|
+
message: 'No recovery request found. Please request a new PIN.'
|
|
2130
|
+
};
|
|
2131
|
+
trackOperation('mlgym_user_recover', operationId, 'failed', error.message, { args: { email }, stage: 'pin_verification' });
|
|
1516
2132
|
return {
|
|
1517
2133
|
content: [{
|
|
1518
2134
|
type: 'text',
|
|
1519
|
-
text: JSON.stringify(
|
|
1520
|
-
status: 'error',
|
|
1521
|
-
message: 'No recovery request found. Please request a new PIN.'
|
|
1522
|
-
}, null, 2)
|
|
2135
|
+
text: JSON.stringify(error, null, 2)
|
|
1523
2136
|
}]
|
|
1524
2137
|
};
|
|
1525
2138
|
}
|
|
@@ -1527,13 +2140,15 @@ async function recoverUser(args) {
|
|
|
1527
2140
|
// Check if PIN expired (10 minutes)
|
|
1528
2141
|
if (Date.now() - stored.timestamp > 10 * 60 * 1000) {
|
|
1529
2142
|
await deletePIN(email);
|
|
2143
|
+
const error = {
|
|
2144
|
+
status: 'error',
|
|
2145
|
+
message: 'PIN has expired. Please request a new one.'
|
|
2146
|
+
};
|
|
2147
|
+
trackOperation('mlgym_user_recover', operationId, 'failed', error.message, { args: { email }, stage: 'pin_verification' });
|
|
1530
2148
|
return {
|
|
1531
2149
|
content: [{
|
|
1532
2150
|
type: 'text',
|
|
1533
|
-
text: JSON.stringify(
|
|
1534
|
-
status: 'error',
|
|
1535
|
-
message: 'PIN has expired. Please request a new one.'
|
|
1536
|
-
}, null, 2)
|
|
2151
|
+
text: JSON.stringify(error, null, 2)
|
|
1537
2152
|
}]
|
|
1538
2153
|
};
|
|
1539
2154
|
}
|
|
@@ -1541,13 +2156,15 @@ async function recoverUser(args) {
|
|
|
1541
2156
|
// Check attempts
|
|
1542
2157
|
if (stored.attempts >= 3) {
|
|
1543
2158
|
await deletePIN(email);
|
|
2159
|
+
const error = {
|
|
2160
|
+
status: 'error',
|
|
2161
|
+
message: 'Too many failed attempts. Please request a new PIN.'
|
|
2162
|
+
};
|
|
2163
|
+
trackOperation('mlgym_user_recover', operationId, 'failed', error.message, { args: { email }, stage: 'pin_verification' });
|
|
1544
2164
|
return {
|
|
1545
2165
|
content: [{
|
|
1546
2166
|
type: 'text',
|
|
1547
|
-
text: JSON.stringify(
|
|
1548
|
-
status: 'error',
|
|
1549
|
-
message: 'Too many failed attempts. Please request a new PIN.'
|
|
1550
|
-
}, null, 2)
|
|
2167
|
+
text: JSON.stringify(error, null, 2)
|
|
1551
2168
|
}]
|
|
1552
2169
|
};
|
|
1553
2170
|
}
|
|
@@ -4107,14 +4724,19 @@ async function getDeploymentStatus(args) {
|
|
|
4107
4724
|
async function triggerDeployment(args) {
|
|
4108
4725
|
const { project_id, environment = 'production' } = args;
|
|
4109
4726
|
|
|
4727
|
+
// Start tracking this operation
|
|
4728
|
+
const operationId = `trigger-deployment-${Date.now()}`;
|
|
4729
|
+
|
|
4110
4730
|
if (!project_id) {
|
|
4731
|
+
const error = {
|
|
4732
|
+
status: 'error',
|
|
4733
|
+
message: 'project_id is required'
|
|
4734
|
+
};
|
|
4735
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'failed', error.message, { args });
|
|
4111
4736
|
return {
|
|
4112
4737
|
content: [{
|
|
4113
4738
|
type: 'text',
|
|
4114
|
-
text: JSON.stringify(
|
|
4115
|
-
status: 'error',
|
|
4116
|
-
message: 'project_id is required'
|
|
4117
|
-
}, null, 2)
|
|
4739
|
+
text: JSON.stringify(error, null, 2)
|
|
4118
4740
|
}]
|
|
4119
4741
|
};
|
|
4120
4742
|
}
|
|
@@ -4124,33 +4746,40 @@ async function triggerDeployment(args) {
|
|
|
4124
4746
|
// Check authentication
|
|
4125
4747
|
const auth = await loadAuth();
|
|
4126
4748
|
if (!auth.token) {
|
|
4749
|
+
const error = {
|
|
4750
|
+
status: 'error',
|
|
4751
|
+
message: 'Not authenticated. Please login first with mlgym_auth_login'
|
|
4752
|
+
};
|
|
4753
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'failed', error.message, { args });
|
|
4127
4754
|
return {
|
|
4128
4755
|
content: [{
|
|
4129
4756
|
type: 'text',
|
|
4130
|
-
text: JSON.stringify(
|
|
4131
|
-
status: 'error',
|
|
4132
|
-
message: 'Not authenticated. Please login first with mlgym_auth_login'
|
|
4133
|
-
}, null, 2)
|
|
4757
|
+
text: JSON.stringify(error, null, 2)
|
|
4134
4758
|
}]
|
|
4135
4759
|
};
|
|
4136
4760
|
}
|
|
4137
4761
|
|
|
4138
4762
|
try {
|
|
4139
4763
|
// Trigger deployment via backend API
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4764
|
+
// Backend expects project_id (integer) and optional trigger field
|
|
4765
|
+
const deploymentData = {
|
|
4766
|
+
project_id: parseInt(project_id), // Ensure it's an integer
|
|
4767
|
+
trigger: 'initial' // Use 'initial' like the CLI does, not 'environment'
|
|
4768
|
+
};
|
|
4769
|
+
|
|
4770
|
+
const result = await apiRequest('POST', '/api/v1/deployments/trigger', deploymentData);
|
|
4144
4771
|
|
|
4145
4772
|
if (!result.success) {
|
|
4773
|
+
const error = {
|
|
4774
|
+
status: 'error',
|
|
4775
|
+
message: result.error || 'Failed to trigger deployment',
|
|
4776
|
+
details: 'Project may not have deployment enabled or webhook configured'
|
|
4777
|
+
};
|
|
4778
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'failed', error.message, { args, deploymentData, response: result });
|
|
4146
4779
|
return {
|
|
4147
4780
|
content: [{
|
|
4148
4781
|
type: 'text',
|
|
4149
|
-
text: JSON.stringify(
|
|
4150
|
-
status: 'error',
|
|
4151
|
-
message: result.error || 'Failed to trigger deployment',
|
|
4152
|
-
details: 'Project may not have deployment enabled or webhook configured'
|
|
4153
|
-
}, null, 2)
|
|
4782
|
+
text: JSON.stringify(error, null, 2)
|
|
4154
4783
|
}]
|
|
4155
4784
|
};
|
|
4156
4785
|
}
|
|
@@ -4171,6 +4800,9 @@ async function triggerDeployment(args) {
|
|
|
4171
4800
|
]
|
|
4172
4801
|
};
|
|
4173
4802
|
|
|
4803
|
+
// Track successful operation
|
|
4804
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'success', `Deployment triggered for project ${project_id}`, { args, response });
|
|
4805
|
+
|
|
4174
4806
|
return {
|
|
4175
4807
|
content: [{
|
|
4176
4808
|
type: 'text',
|
|
@@ -4178,14 +4810,16 @@ async function triggerDeployment(args) {
|
|
|
4178
4810
|
}]
|
|
4179
4811
|
};
|
|
4180
4812
|
} catch (error) {
|
|
4813
|
+
const errorDetails = {
|
|
4814
|
+
status: 'error',
|
|
4815
|
+
message: 'Failed to trigger deployment',
|
|
4816
|
+
error: error.message
|
|
4817
|
+
};
|
|
4818
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'failed', error.message, { args, error: errorDetails });
|
|
4181
4819
|
return {
|
|
4182
4820
|
content: [{
|
|
4183
4821
|
type: 'text',
|
|
4184
|
-
text: JSON.stringify(
|
|
4185
|
-
status: 'error',
|
|
4186
|
-
message: 'Failed to trigger deployment',
|
|
4187
|
-
error: error.message
|
|
4188
|
-
}, null, 2)
|
|
4822
|
+
text: JSON.stringify(errorDetails, null, 2)
|
|
4189
4823
|
}]
|
|
4190
4824
|
};
|
|
4191
4825
|
}
|
|
@@ -4267,14 +4901,19 @@ async function listProjects(args) {
|
|
|
4267
4901
|
async function loginUser(args) {
|
|
4268
4902
|
const { email, password } = args;
|
|
4269
4903
|
|
|
4904
|
+
// Start tracking this operation
|
|
4905
|
+
const operationId = `login-user-${Date.now()}`;
|
|
4906
|
+
|
|
4270
4907
|
if (!email || !password) {
|
|
4908
|
+
const error = {
|
|
4909
|
+
status: 'error',
|
|
4910
|
+
message: 'Email and password are required'
|
|
4911
|
+
};
|
|
4912
|
+
trackOperation('mlgym_auth_login', operationId, 'failed', error.message, { args: { email } }); // Don't log password
|
|
4271
4913
|
return {
|
|
4272
4914
|
content: [{
|
|
4273
4915
|
type: 'text',
|
|
4274
|
-
text: JSON.stringify(
|
|
4275
|
-
status: 'error',
|
|
4276
|
-
message: 'Email and password are required'
|
|
4277
|
-
}, null, 2)
|
|
4916
|
+
text: JSON.stringify(error, null, 2)
|
|
4278
4917
|
}]
|
|
4279
4918
|
};
|
|
4280
4919
|
}
|
|
@@ -4289,14 +4928,16 @@ async function loginUser(args) {
|
|
|
4289
4928
|
}, false);
|
|
4290
4929
|
|
|
4291
4930
|
if (!result.success) {
|
|
4931
|
+
const error = {
|
|
4932
|
+
status: 'error',
|
|
4933
|
+
message: result.error || 'Login failed',
|
|
4934
|
+
details: 'Invalid email or password'
|
|
4935
|
+
};
|
|
4936
|
+
trackOperation('mlgym_auth_login', operationId, 'failed', error.message, { args: { email }, response: result });
|
|
4292
4937
|
return {
|
|
4293
4938
|
content: [{
|
|
4294
4939
|
type: 'text',
|
|
4295
|
-
text: JSON.stringify(
|
|
4296
|
-
status: 'error',
|
|
4297
|
-
message: result.error || 'Login failed',
|
|
4298
|
-
details: 'Invalid email or password'
|
|
4299
|
-
}, null, 2)
|
|
4940
|
+
text: JSON.stringify(error, null, 2)
|
|
4300
4941
|
}]
|
|
4301
4942
|
};
|
|
4302
4943
|
}
|
|
@@ -4326,6 +4967,9 @@ async function loginUser(args) {
|
|
|
4326
4967
|
]
|
|
4327
4968
|
};
|
|
4328
4969
|
|
|
4970
|
+
// Track successful operation
|
|
4971
|
+
trackOperation('mlgym_auth_login', operationId, 'success', `User ${email} logged in successfully`, { args: { email }, response });
|
|
4972
|
+
|
|
4329
4973
|
return {
|
|
4330
4974
|
content: [{
|
|
4331
4975
|
type: 'text',
|
|
@@ -4333,14 +4977,16 @@ async function loginUser(args) {
|
|
|
4333
4977
|
}]
|
|
4334
4978
|
};
|
|
4335
4979
|
} catch (error) {
|
|
4980
|
+
const errorDetails = {
|
|
4981
|
+
status: 'error',
|
|
4982
|
+
message: 'Login failed',
|
|
4983
|
+
error: error.message
|
|
4984
|
+
};
|
|
4985
|
+
trackOperation('mlgym_auth_login', operationId, 'failed', error.message, { args: { email }, error: errorDetails });
|
|
4336
4986
|
return {
|
|
4337
4987
|
content: [{
|
|
4338
4988
|
type: 'text',
|
|
4339
|
-
text: JSON.stringify(
|
|
4340
|
-
status: 'error',
|
|
4341
|
-
message: 'Login failed',
|
|
4342
|
-
error: error.message
|
|
4343
|
-
}, null, 2)
|
|
4989
|
+
text: JSON.stringify(errorDetails, null, 2)
|
|
4344
4990
|
}]
|
|
4345
4991
|
};
|
|
4346
4992
|
}
|
|
@@ -5249,7 +5895,12 @@ async function replicateProject(args) {
|
|
|
5249
5895
|
async function main() {
|
|
5250
5896
|
const transport = new StdioServerTransport();
|
|
5251
5897
|
await server.connect(transport);
|
|
5252
|
-
console.error(
|
|
5898
|
+
console.error(`GitLab Backend MCP Server v${CURRENT_VERSION} started`);
|
|
5899
|
+
|
|
5900
|
+
// Check for updates in the background (don't block startup)
|
|
5901
|
+
setTimeout(async () => {
|
|
5902
|
+
await checkForUpdates();
|
|
5903
|
+
}, 2000); // Check after 2 seconds
|
|
5253
5904
|
}
|
|
5254
5905
|
|
|
5255
5906
|
main().catch((error) => {
|