mlgym-deploy 2.0.0 → 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 +741 -70
- 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;
|
|
@@ -1212,9 +1774,12 @@ async function createUser(args) {
|
|
|
1212
1774
|
// First generate SSH key pair
|
|
1213
1775
|
console.error('Generating SSH key pair...');
|
|
1214
1776
|
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
1777
|
+
const publicKeyPath = privateKeyPath + '.pub';
|
|
1215
1778
|
console.error(`SSH key generated: ${privateKeyPath}`);
|
|
1216
1779
|
|
|
1217
1780
|
// Create user via backend API with SSH key
|
|
1781
|
+
trackOperation('user_create', true, null);
|
|
1782
|
+
|
|
1218
1783
|
const result = await apiRequest('POST', '/api/v1/users', {
|
|
1219
1784
|
email,
|
|
1220
1785
|
name,
|
|
@@ -1223,6 +1788,7 @@ async function createUser(args) {
|
|
|
1223
1788
|
}, false);
|
|
1224
1789
|
|
|
1225
1790
|
if (!result.success) {
|
|
1791
|
+
trackOperation('user_create', false, new Error(result.error || 'User creation failed'));
|
|
1226
1792
|
return {
|
|
1227
1793
|
content: [{
|
|
1228
1794
|
type: 'text',
|
|
@@ -1238,6 +1804,28 @@ async function createUser(args) {
|
|
|
1238
1804
|
|
|
1239
1805
|
// Format successful response with actual fields from backend
|
|
1240
1806
|
const userData = result.data.user || {};
|
|
1807
|
+
|
|
1808
|
+
// Check SSH key upload status
|
|
1809
|
+
const sshKeyStatus = result.data.ssh_key_status || 'Unknown';
|
|
1810
|
+
const sshKeyUploaded = sshKeyStatus === 'SSH key uploaded successfully';
|
|
1811
|
+
|
|
1812
|
+
// Adjust next steps based on SSH key upload status
|
|
1813
|
+
const nextSteps = [
|
|
1814
|
+
passwordGenerated ? `⚠️ SAVE THIS PASSWORD: ${password}` : 'Using your provided password',
|
|
1815
|
+
`SSH key generated locally at: ${privateKeyPath}`
|
|
1816
|
+
];
|
|
1817
|
+
|
|
1818
|
+
if (sshKeyUploaded) {
|
|
1819
|
+
nextSteps.push(`✅ SSH key uploaded to GitLab (ID: ${result.data.ssh_key_id || 'unknown'})`);
|
|
1820
|
+
nextSteps.push(`Add to SSH agent: ssh-add "${privateKeyPath}"`);
|
|
1821
|
+
nextSteps.push('You can now push code to GitLab repositories');
|
|
1822
|
+
} else {
|
|
1823
|
+
nextSteps.push(`⚠️ SSH key upload failed: ${sshKeyStatus}`);
|
|
1824
|
+
nextSteps.push('You need to manually add the SSH key to GitLab:');
|
|
1825
|
+
nextSteps.push(`cat ${publicKeyPath} # Copy this key`);
|
|
1826
|
+
nextSteps.push('Then add it at: https://git.mlgym.io/-/user_settings/ssh_keys');
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1241
1829
|
const response = {
|
|
1242
1830
|
user_id: userData.user_id || userData.UserID,
|
|
1243
1831
|
email: userData.email || email,
|
|
@@ -1246,14 +1834,11 @@ async function createUser(args) {
|
|
|
1246
1834
|
password: passwordGenerated ? password : '[Your provided password]',
|
|
1247
1835
|
password_generated: passwordGenerated,
|
|
1248
1836
|
ssh_key_path: privateKeyPath,
|
|
1837
|
+
ssh_key_status: sshKeyStatus,
|
|
1838
|
+
ssh_key_uploaded: sshKeyUploaded,
|
|
1249
1839
|
token: result.data.token,
|
|
1250
|
-
message: result.data.message || 'User created successfully
|
|
1251
|
-
next_steps:
|
|
1252
|
-
passwordGenerated ? `⚠️ SAVE THIS PASSWORD: ${password}` : 'Using your provided password',
|
|
1253
|
-
`SSH key created at: ${privateKeyPath}`,
|
|
1254
|
-
`Add this to your SSH agent: ssh-add "${privateKeyPath}"`,
|
|
1255
|
-
'You can now push code to GitLab repositories'
|
|
1256
|
-
]
|
|
1840
|
+
message: result.data.message || 'User created successfully',
|
|
1841
|
+
next_steps: nextSteps
|
|
1257
1842
|
};
|
|
1258
1843
|
|
|
1259
1844
|
return {
|
|
@@ -1333,22 +1918,46 @@ async function detectProjectName(localPath) {
|
|
|
1333
1918
|
|
|
1334
1919
|
// Tool implementation: Initialize Project
|
|
1335
1920
|
async function initProject(args) {
|
|
1336
|
-
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()}`;
|
|
1337
1925
|
|
|
1338
1926
|
// Validate required fields are provided
|
|
1339
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 });
|
|
1340
1938
|
return {
|
|
1341
1939
|
content: [{
|
|
1342
1940
|
type: 'text',
|
|
1343
|
-
text: JSON.stringify(
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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)
|
|
1352
1961
|
}]
|
|
1353
1962
|
};
|
|
1354
1963
|
}
|
|
@@ -1358,10 +1967,12 @@ async function initProject(args) {
|
|
|
1358
1967
|
// Check authentication
|
|
1359
1968
|
const auth = await loadAuth();
|
|
1360
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 });
|
|
1361
1972
|
return {
|
|
1362
1973
|
content: [{
|
|
1363
1974
|
type: 'text',
|
|
1364
|
-
text:
|
|
1975
|
+
text: `Error: ${errorMsg}`
|
|
1365
1976
|
}]
|
|
1366
1977
|
};
|
|
1367
1978
|
}
|
|
@@ -1375,16 +1986,19 @@ async function initProject(args) {
|
|
|
1375
1986
|
description,
|
|
1376
1987
|
visibility: 'private',
|
|
1377
1988
|
enable_deployment: enable_deployment,
|
|
1378
|
-
webhook_secret: webhookSecret
|
|
1989
|
+
webhook_secret: webhookSecret,
|
|
1990
|
+
hostname: hostname || null
|
|
1379
1991
|
};
|
|
1380
1992
|
|
|
1381
1993
|
const result = await apiRequest('POST', '/api/v1/projects', projectData);
|
|
1382
1994
|
|
|
1383
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 });
|
|
1384
1998
|
return {
|
|
1385
1999
|
content: [{
|
|
1386
2000
|
type: 'text',
|
|
1387
|
-
text:
|
|
2001
|
+
text: errorMsg
|
|
1388
2002
|
}]
|
|
1389
2003
|
};
|
|
1390
2004
|
}
|
|
@@ -1423,7 +2037,13 @@ async function initProject(args) {
|
|
|
1423
2037
|
}
|
|
1424
2038
|
|
|
1425
2039
|
// Add git commands for local setup
|
|
2040
|
+
// First ensure git is configured
|
|
1426
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:',
|
|
1427
2047
|
`cd ${local_path}`,
|
|
1428
2048
|
'git init',
|
|
1429
2049
|
`git remote add origin ${project.ssh_url_to_repo}`,
|
|
@@ -1432,6 +2052,11 @@ async function initProject(args) {
|
|
|
1432
2052
|
'git push -u origin main'
|
|
1433
2053
|
];
|
|
1434
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
|
+
|
|
1435
2060
|
return {
|
|
1436
2061
|
content: [{
|
|
1437
2062
|
type: 'text',
|
|
@@ -1454,6 +2079,9 @@ function generateWebhookSecret() {
|
|
|
1454
2079
|
async function recoverUser(args) {
|
|
1455
2080
|
const { email, pin, new_password } = args;
|
|
1456
2081
|
|
|
2082
|
+
// Start tracking this operation
|
|
2083
|
+
const operationId = `recover-user-${Date.now()}`;
|
|
2084
|
+
|
|
1457
2085
|
// Clean up any expired PINs first
|
|
1458
2086
|
await cleanupExpiredPINs();
|
|
1459
2087
|
|
|
@@ -1472,6 +2100,9 @@ async function recoverUser(args) {
|
|
|
1472
2100
|
|
|
1473
2101
|
console.error(`Recovery PIN generated for ${email}: ${generatedPIN}`);
|
|
1474
2102
|
|
|
2103
|
+
// Track PIN generation
|
|
2104
|
+
trackOperation('mlgym_user_recover', operationId, 'success', `Recovery PIN generated for ${email}`, { args: { email }, stage: 'pin_generation' });
|
|
2105
|
+
|
|
1475
2106
|
// In production, this would send an actual email
|
|
1476
2107
|
// For now, we'll include it in the response for testing
|
|
1477
2108
|
return {
|
|
@@ -1493,13 +2124,15 @@ async function recoverUser(args) {
|
|
|
1493
2124
|
const stored = await loadPIN(email);
|
|
1494
2125
|
|
|
1495
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' });
|
|
1496
2132
|
return {
|
|
1497
2133
|
content: [{
|
|
1498
2134
|
type: 'text',
|
|
1499
|
-
text: JSON.stringify(
|
|
1500
|
-
status: 'error',
|
|
1501
|
-
message: 'No recovery request found. Please request a new PIN.'
|
|
1502
|
-
}, null, 2)
|
|
2135
|
+
text: JSON.stringify(error, null, 2)
|
|
1503
2136
|
}]
|
|
1504
2137
|
};
|
|
1505
2138
|
}
|
|
@@ -1507,13 +2140,15 @@ async function recoverUser(args) {
|
|
|
1507
2140
|
// Check if PIN expired (10 minutes)
|
|
1508
2141
|
if (Date.now() - stored.timestamp > 10 * 60 * 1000) {
|
|
1509
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' });
|
|
1510
2148
|
return {
|
|
1511
2149
|
content: [{
|
|
1512
2150
|
type: 'text',
|
|
1513
|
-
text: JSON.stringify(
|
|
1514
|
-
status: 'error',
|
|
1515
|
-
message: 'PIN has expired. Please request a new one.'
|
|
1516
|
-
}, null, 2)
|
|
2151
|
+
text: JSON.stringify(error, null, 2)
|
|
1517
2152
|
}]
|
|
1518
2153
|
};
|
|
1519
2154
|
}
|
|
@@ -1521,13 +2156,15 @@ async function recoverUser(args) {
|
|
|
1521
2156
|
// Check attempts
|
|
1522
2157
|
if (stored.attempts >= 3) {
|
|
1523
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' });
|
|
1524
2164
|
return {
|
|
1525
2165
|
content: [{
|
|
1526
2166
|
type: 'text',
|
|
1527
|
-
text: JSON.stringify(
|
|
1528
|
-
status: 'error',
|
|
1529
|
-
message: 'Too many failed attempts. Please request a new PIN.'
|
|
1530
|
-
}, null, 2)
|
|
2167
|
+
text: JSON.stringify(error, null, 2)
|
|
1531
2168
|
}]
|
|
1532
2169
|
};
|
|
1533
2170
|
}
|
|
@@ -4087,14 +4724,19 @@ async function getDeploymentStatus(args) {
|
|
|
4087
4724
|
async function triggerDeployment(args) {
|
|
4088
4725
|
const { project_id, environment = 'production' } = args;
|
|
4089
4726
|
|
|
4727
|
+
// Start tracking this operation
|
|
4728
|
+
const operationId = `trigger-deployment-${Date.now()}`;
|
|
4729
|
+
|
|
4090
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 });
|
|
4091
4736
|
return {
|
|
4092
4737
|
content: [{
|
|
4093
4738
|
type: 'text',
|
|
4094
|
-
text: JSON.stringify(
|
|
4095
|
-
status: 'error',
|
|
4096
|
-
message: 'project_id is required'
|
|
4097
|
-
}, null, 2)
|
|
4739
|
+
text: JSON.stringify(error, null, 2)
|
|
4098
4740
|
}]
|
|
4099
4741
|
};
|
|
4100
4742
|
}
|
|
@@ -4104,33 +4746,40 @@ async function triggerDeployment(args) {
|
|
|
4104
4746
|
// Check authentication
|
|
4105
4747
|
const auth = await loadAuth();
|
|
4106
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 });
|
|
4107
4754
|
return {
|
|
4108
4755
|
content: [{
|
|
4109
4756
|
type: 'text',
|
|
4110
|
-
text: JSON.stringify(
|
|
4111
|
-
status: 'error',
|
|
4112
|
-
message: 'Not authenticated. Please login first with mlgym_auth_login'
|
|
4113
|
-
}, null, 2)
|
|
4757
|
+
text: JSON.stringify(error, null, 2)
|
|
4114
4758
|
}]
|
|
4115
4759
|
};
|
|
4116
4760
|
}
|
|
4117
4761
|
|
|
4118
4762
|
try {
|
|
4119
4763
|
// Trigger deployment via backend API
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
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);
|
|
4124
4771
|
|
|
4125
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 });
|
|
4126
4779
|
return {
|
|
4127
4780
|
content: [{
|
|
4128
4781
|
type: 'text',
|
|
4129
|
-
text: JSON.stringify(
|
|
4130
|
-
status: 'error',
|
|
4131
|
-
message: result.error || 'Failed to trigger deployment',
|
|
4132
|
-
details: 'Project may not have deployment enabled or webhook configured'
|
|
4133
|
-
}, null, 2)
|
|
4782
|
+
text: JSON.stringify(error, null, 2)
|
|
4134
4783
|
}]
|
|
4135
4784
|
};
|
|
4136
4785
|
}
|
|
@@ -4151,6 +4800,9 @@ async function triggerDeployment(args) {
|
|
|
4151
4800
|
]
|
|
4152
4801
|
};
|
|
4153
4802
|
|
|
4803
|
+
// Track successful operation
|
|
4804
|
+
trackOperation('mlgym_deployments_trigger', operationId, 'success', `Deployment triggered for project ${project_id}`, { args, response });
|
|
4805
|
+
|
|
4154
4806
|
return {
|
|
4155
4807
|
content: [{
|
|
4156
4808
|
type: 'text',
|
|
@@ -4158,14 +4810,16 @@ async function triggerDeployment(args) {
|
|
|
4158
4810
|
}]
|
|
4159
4811
|
};
|
|
4160
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 });
|
|
4161
4819
|
return {
|
|
4162
4820
|
content: [{
|
|
4163
4821
|
type: 'text',
|
|
4164
|
-
text: JSON.stringify(
|
|
4165
|
-
status: 'error',
|
|
4166
|
-
message: 'Failed to trigger deployment',
|
|
4167
|
-
error: error.message
|
|
4168
|
-
}, null, 2)
|
|
4822
|
+
text: JSON.stringify(errorDetails, null, 2)
|
|
4169
4823
|
}]
|
|
4170
4824
|
};
|
|
4171
4825
|
}
|
|
@@ -4247,14 +4901,19 @@ async function listProjects(args) {
|
|
|
4247
4901
|
async function loginUser(args) {
|
|
4248
4902
|
const { email, password } = args;
|
|
4249
4903
|
|
|
4904
|
+
// Start tracking this operation
|
|
4905
|
+
const operationId = `login-user-${Date.now()}`;
|
|
4906
|
+
|
|
4250
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
|
|
4251
4913
|
return {
|
|
4252
4914
|
content: [{
|
|
4253
4915
|
type: 'text',
|
|
4254
|
-
text: JSON.stringify(
|
|
4255
|
-
status: 'error',
|
|
4256
|
-
message: 'Email and password are required'
|
|
4257
|
-
}, null, 2)
|
|
4916
|
+
text: JSON.stringify(error, null, 2)
|
|
4258
4917
|
}]
|
|
4259
4918
|
};
|
|
4260
4919
|
}
|
|
@@ -4269,14 +4928,16 @@ async function loginUser(args) {
|
|
|
4269
4928
|
}, false);
|
|
4270
4929
|
|
|
4271
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 });
|
|
4272
4937
|
return {
|
|
4273
4938
|
content: [{
|
|
4274
4939
|
type: 'text',
|
|
4275
|
-
text: JSON.stringify(
|
|
4276
|
-
status: 'error',
|
|
4277
|
-
message: result.error || 'Login failed',
|
|
4278
|
-
details: 'Invalid email or password'
|
|
4279
|
-
}, null, 2)
|
|
4940
|
+
text: JSON.stringify(error, null, 2)
|
|
4280
4941
|
}]
|
|
4281
4942
|
};
|
|
4282
4943
|
}
|
|
@@ -4306,6 +4967,9 @@ async function loginUser(args) {
|
|
|
4306
4967
|
]
|
|
4307
4968
|
};
|
|
4308
4969
|
|
|
4970
|
+
// Track successful operation
|
|
4971
|
+
trackOperation('mlgym_auth_login', operationId, 'success', `User ${email} logged in successfully`, { args: { email }, response });
|
|
4972
|
+
|
|
4309
4973
|
return {
|
|
4310
4974
|
content: [{
|
|
4311
4975
|
type: 'text',
|
|
@@ -4313,14 +4977,16 @@ async function loginUser(args) {
|
|
|
4313
4977
|
}]
|
|
4314
4978
|
};
|
|
4315
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 });
|
|
4316
4986
|
return {
|
|
4317
4987
|
content: [{
|
|
4318
4988
|
type: 'text',
|
|
4319
|
-
text: JSON.stringify(
|
|
4320
|
-
status: 'error',
|
|
4321
|
-
message: 'Login failed',
|
|
4322
|
-
error: error.message
|
|
4323
|
-
}, null, 2)
|
|
4989
|
+
text: JSON.stringify(errorDetails, null, 2)
|
|
4324
4990
|
}]
|
|
4325
4991
|
};
|
|
4326
4992
|
}
|
|
@@ -5229,7 +5895,12 @@ async function replicateProject(args) {
|
|
|
5229
5895
|
async function main() {
|
|
5230
5896
|
const transport = new StdioServerTransport();
|
|
5231
5897
|
await server.connect(transport);
|
|
5232
|
-
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
|
|
5233
5904
|
}
|
|
5234
5905
|
|
|
5235
5906
|
main().catch((error) => {
|