mlgym-deploy 3.2.2 → 3.3.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 +968 -7
- package/package.json +34 -2
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import crypto from 'crypto';
|
|
|
17
17
|
const execAsync = promisify(exec);
|
|
18
18
|
|
|
19
19
|
// Current version of this MCP server - INCREMENT FOR WORKFLOW FIXES
|
|
20
|
-
const CURRENT_VERSION = '3.
|
|
20
|
+
const CURRENT_VERSION = '3.3.0'; // Added 6 new tools: health check, domain, deployment commands, manual deploy, options, rollback
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Debug logging configuration - ENABLED BY DEFAULT
|
|
@@ -1072,17 +1072,17 @@ function validateAndFixDockerCompose(content) {
|
|
|
1072
1072
|
const [external, internal] = portEntry.split(':').map(p => p.trim());
|
|
1073
1073
|
const webPorts = ['80', '8080', '3000', '4000', '5000', '8000', '9000'];
|
|
1074
1074
|
|
|
1075
|
-
if (webPorts.includes(internal)) {
|
|
1075
|
+
if (webPorts.includes(internal) && external !== '80') {
|
|
1076
1076
|
issues.push({
|
|
1077
1077
|
line: i + 1,
|
|
1078
1078
|
service: currentService,
|
|
1079
1079
|
issue: `Port mapping "${external}:${internal}" not allowed`,
|
|
1080
|
-
fix:
|
|
1080
|
+
fix: `Use "80:${internal}" instead`
|
|
1081
1081
|
});
|
|
1082
1082
|
|
|
1083
|
-
// Fix the line
|
|
1084
|
-
fixed[i] = line.replace(trimmed,
|
|
1085
|
-
fixes.push(`Fixed ${currentService}: ${external}:${internal} → 80`);
|
|
1083
|
+
// Fix the line - preserve internal port
|
|
1084
|
+
fixed[i] = line.replace(trimmed, `- "80:${internal}"`);
|
|
1085
|
+
fixes.push(`Fixed ${currentService}: ${external}:${internal} → 80:${internal}`);
|
|
1086
1086
|
}
|
|
1087
1087
|
}
|
|
1088
1088
|
} else if (inPortsSection && !trimmed.startsWith('-')) {
|
|
@@ -2311,6 +2311,702 @@ async function getDeploymentLogs(args) {
|
|
|
2311
2311
|
}
|
|
2312
2312
|
}
|
|
2313
2313
|
|
|
2314
|
+
// Set environment variables for an application
|
|
2315
|
+
async function setEnvironmentVariables(args) {
|
|
2316
|
+
try {
|
|
2317
|
+
const {project_name, variables, local_path = '.'} = args;
|
|
2318
|
+
|
|
2319
|
+
log.info(`MCP >>> [setEnvironmentVariables] Setting environment variables for ${project_name || 'project'}`);
|
|
2320
|
+
log.debug(`Variables to set:`, JSON.stringify(Object.keys(variables), null, 2));
|
|
2321
|
+
|
|
2322
|
+
// Ensure authentication
|
|
2323
|
+
let auth = await loadAuth();
|
|
2324
|
+
if (!auth || !auth.jwt_token) {
|
|
2325
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
log.debug(`Using JWT token: ${auth.jwt_token.substring(0, 20)}...`);
|
|
2329
|
+
|
|
2330
|
+
// Determine project name
|
|
2331
|
+
let finalProjectName = project_name;
|
|
2332
|
+
if (!finalProjectName) {
|
|
2333
|
+
// Try to get project name from git remote
|
|
2334
|
+
try {
|
|
2335
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2336
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2337
|
+
if (match) {
|
|
2338
|
+
finalProjectName = match[1];
|
|
2339
|
+
log.debug(`Detected project name from git remote: ${finalProjectName}`);
|
|
2340
|
+
}
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
throw new Error('project_name is required when not in a git repository with remote configured');
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
log.info(`Setting environment variables for project: ${finalProjectName}`);
|
|
2347
|
+
|
|
2348
|
+
// Get application UUID from backend
|
|
2349
|
+
const backendUrl = `${CONFIG.backend_url}/api/v1/projects/${finalProjectName}`;
|
|
2350
|
+
log.debug(`Fetching project details from: ${backendUrl}`);
|
|
2351
|
+
|
|
2352
|
+
const projectResponse = await axios.get(backendUrl, {
|
|
2353
|
+
headers: {
|
|
2354
|
+
'Authorization': `Bearer ${auth.jwt_token}`,
|
|
2355
|
+
'Content-Type': 'application/json'
|
|
2356
|
+
}
|
|
2357
|
+
});
|
|
2358
|
+
|
|
2359
|
+
if (!projectResponse.data || !projectResponse.data.application_uuid) {
|
|
2360
|
+
throw new Error(`Project ${finalProjectName} not found or has no application`);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
const appUUID = projectResponse.data.application_uuid;
|
|
2364
|
+
log.debug(`Application UUID: ${appUUID}`);
|
|
2365
|
+
|
|
2366
|
+
// Call User Agent to set environment variables
|
|
2367
|
+
const userAgentUrl = 'http://coolify.eu.ezb.net:9000/applications/' + appUUID + '/environment-variables';
|
|
2368
|
+
log.debug(`Calling User Agent: ${userAgentUrl}`);
|
|
2369
|
+
|
|
2370
|
+
const envResponse = await axios.post(userAgentUrl, {
|
|
2371
|
+
variables: variables
|
|
2372
|
+
}, {
|
|
2373
|
+
headers: {
|
|
2374
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2375
|
+
'Content-Type': 'application/json'
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
|
|
2379
|
+
log.success(`Environment variables set successfully`);
|
|
2380
|
+
log.debug(`Response:`, JSON.stringify(envResponse.data, null, 2));
|
|
2381
|
+
|
|
2382
|
+
return {
|
|
2383
|
+
content: [{
|
|
2384
|
+
type: 'text',
|
|
2385
|
+
text: JSON.stringify({
|
|
2386
|
+
status: 'success',
|
|
2387
|
+
project_name: finalProjectName,
|
|
2388
|
+
application_uuid: appUUID,
|
|
2389
|
+
variables_set: envResponse.data.variables_set || Object.keys(variables),
|
|
2390
|
+
count: envResponse.data.count || Object.keys(variables).length,
|
|
2391
|
+
message: `Successfully set ${Object.keys(variables).length} environment variable(s) for ${finalProjectName}`,
|
|
2392
|
+
next_steps: [
|
|
2393
|
+
'Environment variables are now set for both build-time and runtime',
|
|
2394
|
+
'If you need to trigger a rebuild with new variables, use mlgym_deploy',
|
|
2395
|
+
'Variables will be available in your application as process.env.VARIABLE_NAME'
|
|
2396
|
+
]
|
|
2397
|
+
}, null, 2)
|
|
2398
|
+
}]
|
|
2399
|
+
};
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
log.error(`MCP >>> [setEnvironmentVariables] Error: ${error.message}`);
|
|
2402
|
+
if (error.response) {
|
|
2403
|
+
log.error(`Response status: ${error.response.status}`);
|
|
2404
|
+
log.error(`Response data:`, JSON.stringify(error.response.data, null, 2));
|
|
2405
|
+
}
|
|
2406
|
+
return {
|
|
2407
|
+
content: [{
|
|
2408
|
+
type: 'text',
|
|
2409
|
+
text: JSON.stringify({
|
|
2410
|
+
status: 'error',
|
|
2411
|
+
error: error.message,
|
|
2412
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2413
|
+
}, null, 2)
|
|
2414
|
+
}]
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// Helper function to get application UUID from project name
|
|
2420
|
+
async function getApplicationUUID(projectName, auth) {
|
|
2421
|
+
const backendUrl = `${CONFIG.backend_url}/api/v1/projects/${projectName}`;
|
|
2422
|
+
log.debug(`Fetching project details from: ${backendUrl}`);
|
|
2423
|
+
|
|
2424
|
+
const projectResponse = await axios.get(backendUrl, {
|
|
2425
|
+
headers: {
|
|
2426
|
+
'Authorization': `Bearer ${auth.jwt_token}`,
|
|
2427
|
+
'Content-Type': 'application/json'
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
if (!projectResponse.data || !projectResponse.data.application_uuid) {
|
|
2432
|
+
throw new Error(`Project ${projectName} not found or has no application`);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
return projectResponse.data.application_uuid;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// Set health check configuration
|
|
2439
|
+
async function setHealthCheck(args) {
|
|
2440
|
+
try {
|
|
2441
|
+
const {project_name, local_path = '.', ...healthConfig} = args;
|
|
2442
|
+
|
|
2443
|
+
log.info(`MCP >>> [setHealthCheck] Configuring health check for ${project_name || 'project'}`);
|
|
2444
|
+
|
|
2445
|
+
// Ensure authentication
|
|
2446
|
+
let auth = await loadAuth();
|
|
2447
|
+
if (!auth || !auth.jwt_token) {
|
|
2448
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Determine project name
|
|
2452
|
+
let finalProjectName = project_name;
|
|
2453
|
+
if (!finalProjectName) {
|
|
2454
|
+
try {
|
|
2455
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2456
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2457
|
+
if (match) finalProjectName = match[1];
|
|
2458
|
+
} catch (error) {
|
|
2459
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2464
|
+
log.debug(`Application UUID: ${appUUID}`);
|
|
2465
|
+
|
|
2466
|
+
// Call User Agent to set health check
|
|
2467
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/health-check`;
|
|
2468
|
+
const response = await axios.post(userAgentUrl, healthConfig, {
|
|
2469
|
+
headers: {
|
|
2470
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2471
|
+
'Content-Type': 'application/json'
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
log.success(`Health check configured successfully`);
|
|
2476
|
+
|
|
2477
|
+
return {
|
|
2478
|
+
content: [{
|
|
2479
|
+
type: 'text',
|
|
2480
|
+
text: JSON.stringify({
|
|
2481
|
+
status: 'success',
|
|
2482
|
+
project_name: finalProjectName,
|
|
2483
|
+
application_uuid: appUUID,
|
|
2484
|
+
health_check: healthConfig,
|
|
2485
|
+
message: `Health check ${healthConfig.enabled ? 'enabled' : 'disabled'} for ${finalProjectName}`
|
|
2486
|
+
}, null, 2)
|
|
2487
|
+
}]
|
|
2488
|
+
};
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
log.error(`MCP >>> [setHealthCheck] Error: ${error.message}`);
|
|
2491
|
+
return {
|
|
2492
|
+
content: [{
|
|
2493
|
+
type: 'text',
|
|
2494
|
+
text: JSON.stringify({
|
|
2495
|
+
status: 'error',
|
|
2496
|
+
error: error.message,
|
|
2497
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2498
|
+
}, null, 2)
|
|
2499
|
+
}]
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// Set custom domain
|
|
2505
|
+
async function setDomain(args) {
|
|
2506
|
+
try {
|
|
2507
|
+
const {project_name, domain, local_path = '.'} = args;
|
|
2508
|
+
|
|
2509
|
+
log.info(`MCP >>> [setDomain] Setting domain for ${project_name || 'project'}`);
|
|
2510
|
+
|
|
2511
|
+
let auth = await loadAuth();
|
|
2512
|
+
if (!auth || !auth.jwt_token) {
|
|
2513
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
let finalProjectName = project_name;
|
|
2517
|
+
if (!finalProjectName) {
|
|
2518
|
+
try {
|
|
2519
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2520
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2521
|
+
if (match) finalProjectName = match[1];
|
|
2522
|
+
} catch (error) {
|
|
2523
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2528
|
+
|
|
2529
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/domain`;
|
|
2530
|
+
const response = await axios.post(userAgentUrl, {domain}, {
|
|
2531
|
+
headers: {
|
|
2532
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2533
|
+
'Content-Type': 'application/json'
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
log.success(`Domain set successfully: ${domain}`);
|
|
2538
|
+
|
|
2539
|
+
return {
|
|
2540
|
+
content: [{
|
|
2541
|
+
type: 'text',
|
|
2542
|
+
text: JSON.stringify({
|
|
2543
|
+
status: 'success',
|
|
2544
|
+
project_name: finalProjectName,
|
|
2545
|
+
application_uuid: appUUID,
|
|
2546
|
+
domain: response.data.domain,
|
|
2547
|
+
message: `Domain updated to ${response.data.domain}`
|
|
2548
|
+
}, null, 2)
|
|
2549
|
+
}]
|
|
2550
|
+
};
|
|
2551
|
+
} catch (error) {
|
|
2552
|
+
log.error(`MCP >>> [setDomain] Error: ${error.message}`);
|
|
2553
|
+
return {
|
|
2554
|
+
content: [{
|
|
2555
|
+
type: 'text',
|
|
2556
|
+
text: JSON.stringify({
|
|
2557
|
+
status: 'error',
|
|
2558
|
+
error: error.message,
|
|
2559
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2560
|
+
}, null, 2)
|
|
2561
|
+
}]
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// Set deployment commands
|
|
2567
|
+
async function setDeploymentCommands(args) {
|
|
2568
|
+
try {
|
|
2569
|
+
const {project_name, local_path = '.', ...commands} = args;
|
|
2570
|
+
|
|
2571
|
+
log.info(`MCP >>> [setDeploymentCommands] Setting deployment commands for ${project_name || 'project'}`);
|
|
2572
|
+
|
|
2573
|
+
let auth = await loadAuth();
|
|
2574
|
+
if (!auth || !auth.jwt_token) {
|
|
2575
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
let finalProjectName = project_name;
|
|
2579
|
+
if (!finalProjectName) {
|
|
2580
|
+
try {
|
|
2581
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2582
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2583
|
+
if (match) finalProjectName = match[1];
|
|
2584
|
+
} catch (error) {
|
|
2585
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2590
|
+
|
|
2591
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/deployment-commands`;
|
|
2592
|
+
const response = await axios.post(userAgentUrl, commands, {
|
|
2593
|
+
headers: {
|
|
2594
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2595
|
+
'Content-Type': 'application/json'
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
log.success(`Deployment commands set successfully`);
|
|
2600
|
+
|
|
2601
|
+
return {
|
|
2602
|
+
content: [{
|
|
2603
|
+
type: 'text',
|
|
2604
|
+
text: JSON.stringify({
|
|
2605
|
+
status: 'success',
|
|
2606
|
+
project_name: finalProjectName,
|
|
2607
|
+
application_uuid: appUUID,
|
|
2608
|
+
commands: commands,
|
|
2609
|
+
message: `Deployment commands updated for ${finalProjectName}`
|
|
2610
|
+
}, null, 2)
|
|
2611
|
+
}]
|
|
2612
|
+
};
|
|
2613
|
+
} catch (error) {
|
|
2614
|
+
log.error(`MCP >>> [setDeploymentCommands] Error: ${error.message}`);
|
|
2615
|
+
return {
|
|
2616
|
+
content: [{
|
|
2617
|
+
type: 'text',
|
|
2618
|
+
text: JSON.stringify({
|
|
2619
|
+
status: 'error',
|
|
2620
|
+
error: error.message,
|
|
2621
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2622
|
+
}, null, 2)
|
|
2623
|
+
}]
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// Manually trigger deployment
|
|
2629
|
+
async function manualDeploy(args) {
|
|
2630
|
+
try {
|
|
2631
|
+
const {project_name, local_path = '.'} = args;
|
|
2632
|
+
|
|
2633
|
+
log.info(`MCP >>> [manualDeploy] Triggering manual deployment for ${project_name || 'project'}`);
|
|
2634
|
+
|
|
2635
|
+
let auth = await loadAuth();
|
|
2636
|
+
if (!auth || !auth.jwt_token) {
|
|
2637
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
let finalProjectName = project_name;
|
|
2641
|
+
if (!finalProjectName) {
|
|
2642
|
+
try {
|
|
2643
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2644
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2645
|
+
if (match) finalProjectName = match[1];
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2652
|
+
|
|
2653
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/deploy-manual`;
|
|
2654
|
+
const response = await axios.post(userAgentUrl, {}, {
|
|
2655
|
+
headers: {
|
|
2656
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2657
|
+
'Content-Type': 'application/json'
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
log.success(`Manual deployment triggered successfully`);
|
|
2662
|
+
|
|
2663
|
+
return {
|
|
2664
|
+
content: [{
|
|
2665
|
+
type: 'text',
|
|
2666
|
+
text: JSON.stringify({
|
|
2667
|
+
status: 'success',
|
|
2668
|
+
project_name: finalProjectName,
|
|
2669
|
+
application_uuid: appUUID,
|
|
2670
|
+
deployment_uuid: response.data.deployment_uuid,
|
|
2671
|
+
deployment_status: response.data.status,
|
|
2672
|
+
message: `Manual deployment triggered for ${finalProjectName}. Use mlgym_deploy_logs to monitor progress.`,
|
|
2673
|
+
next_steps: [
|
|
2674
|
+
'Deployment is queued and will start shortly',
|
|
2675
|
+
'Use mlgym_deploy_logs to view deployment progress',
|
|
2676
|
+
'Check application URL once deployment completes'
|
|
2677
|
+
]
|
|
2678
|
+
}, null, 2)
|
|
2679
|
+
}]
|
|
2680
|
+
};
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
log.error(`MCP >>> [manualDeploy] Error: ${error.message}`);
|
|
2683
|
+
return {
|
|
2684
|
+
content: [{
|
|
2685
|
+
type: 'text',
|
|
2686
|
+
text: JSON.stringify({
|
|
2687
|
+
status: 'error',
|
|
2688
|
+
error: error.message,
|
|
2689
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2690
|
+
}, null, 2)
|
|
2691
|
+
}]
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// Set application options
|
|
2697
|
+
async function setOptions(args) {
|
|
2698
|
+
try {
|
|
2699
|
+
const {project_name, local_path = '.', ...options} = args;
|
|
2700
|
+
|
|
2701
|
+
log.info(`MCP >>> [setOptions] Setting options for ${project_name || 'project'}`);
|
|
2702
|
+
|
|
2703
|
+
let auth = await loadAuth();
|
|
2704
|
+
if (!auth || !auth.jwt_token) {
|
|
2705
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
let finalProjectName = project_name;
|
|
2709
|
+
if (!finalProjectName) {
|
|
2710
|
+
try {
|
|
2711
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2712
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2713
|
+
if (match) finalProjectName = match[1];
|
|
2714
|
+
} catch (error) {
|
|
2715
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2720
|
+
|
|
2721
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/options`;
|
|
2722
|
+
const response = await axios.post(userAgentUrl, options, {
|
|
2723
|
+
headers: {
|
|
2724
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2725
|
+
'Content-Type': 'application/json'
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
log.success(`Options set successfully`);
|
|
2730
|
+
|
|
2731
|
+
return {
|
|
2732
|
+
content: [{
|
|
2733
|
+
type: 'text',
|
|
2734
|
+
text: JSON.stringify({
|
|
2735
|
+
status: 'success',
|
|
2736
|
+
project_name: finalProjectName,
|
|
2737
|
+
application_uuid: appUUID,
|
|
2738
|
+
options: options,
|
|
2739
|
+
message: `Application options updated for ${finalProjectName}`
|
|
2740
|
+
}, null, 2)
|
|
2741
|
+
}]
|
|
2742
|
+
};
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
log.error(`MCP >>> [setOptions] Error: ${error.message}`);
|
|
2745
|
+
return {
|
|
2746
|
+
content: [{
|
|
2747
|
+
type: 'text',
|
|
2748
|
+
text: JSON.stringify({
|
|
2749
|
+
status: 'error',
|
|
2750
|
+
error: error.message,
|
|
2751
|
+
hint: error.response?.data?.error || 'Make sure the project exists and you are authenticated'
|
|
2752
|
+
}, null, 2)
|
|
2753
|
+
}]
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
// Rollback to previous deployment
|
|
2759
|
+
async function rollback(args) {
|
|
2760
|
+
try {
|
|
2761
|
+
const {project_name, deployment_id, local_path = '.'} = args;
|
|
2762
|
+
|
|
2763
|
+
log.info(`MCP >>> [rollback] Rolling back ${project_name || 'project'}`);
|
|
2764
|
+
|
|
2765
|
+
let auth = await loadAuth();
|
|
2766
|
+
if (!auth || !auth.jwt_token) {
|
|
2767
|
+
throw new Error('Not authenticated. Please run mlgym_deploy or mlgym_auth_login first');
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
let finalProjectName = project_name;
|
|
2771
|
+
if (!finalProjectName) {
|
|
2772
|
+
try {
|
|
2773
|
+
const {stdout} = await execAsync('git remote get-url origin', {cwd: local_path});
|
|
2774
|
+
const match = stdout.match(/\/([^\/]+)\.git/);
|
|
2775
|
+
if (match) finalProjectName = match[1];
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
throw new Error('project_name is required when not in a git repository');
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
const appUUID = await getApplicationUUID(finalProjectName, auth);
|
|
2782
|
+
|
|
2783
|
+
const userAgentUrl = `http://coolify.eu.ezb.net:9000/applications/${appUUID}/rollback`;
|
|
2784
|
+
const requestBody = deployment_id ? {deployment_id} : {};
|
|
2785
|
+
const response = await axios.post(userAgentUrl, requestBody, {
|
|
2786
|
+
headers: {
|
|
2787
|
+
'Authorization': 'Bearer 1|Jkztb5qPptwRKgtocfbT2TLjp8WGJG1SkPE4DzLt4c5d600f',
|
|
2788
|
+
'Content-Type': 'application/json'
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2792
|
+
log.success(`Rollback triggered successfully`);
|
|
2793
|
+
|
|
2794
|
+
return {
|
|
2795
|
+
content: [{
|
|
2796
|
+
type: 'text',
|
|
2797
|
+
text: JSON.stringify({
|
|
2798
|
+
status: 'success',
|
|
2799
|
+
project_name: finalProjectName,
|
|
2800
|
+
application_uuid: appUUID,
|
|
2801
|
+
rollback_to_deployment: response.data.rollback_to_deployment,
|
|
2802
|
+
commit: response.data.commit,
|
|
2803
|
+
deployment_uuid: response.data.deployment_uuid,
|
|
2804
|
+
message: `Rollback deployment queued for ${finalProjectName}. Use mlgym_deploy_logs to monitor progress.`,
|
|
2805
|
+
next_steps: [
|
|
2806
|
+
'Rollback deployment is queued',
|
|
2807
|
+
'Use mlgym_deploy_logs to view deployment progress',
|
|
2808
|
+
'Application will revert to previous version once deployed'
|
|
2809
|
+
]
|
|
2810
|
+
}, null, 2)
|
|
2811
|
+
}]
|
|
2812
|
+
};
|
|
2813
|
+
} catch (error) {
|
|
2814
|
+
log.error(`MCP >>> [rollback] Error: ${error.message}`);
|
|
2815
|
+
return {
|
|
2816
|
+
content: [{
|
|
2817
|
+
type: 'text',
|
|
2818
|
+
text: JSON.stringify({
|
|
2819
|
+
status: 'error',
|
|
2820
|
+
error: error.message,
|
|
2821
|
+
hint: error.response?.data?.error || 'Make sure the project exists and has previous deployments'
|
|
2822
|
+
}, null, 2)
|
|
2823
|
+
}]
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// Display help with all available tools
|
|
2829
|
+
async function showHelp(args) {
|
|
2830
|
+
const helpText = `
|
|
2831
|
+
╔═══════════════════════════════════════════════════════════════════════╗
|
|
2832
|
+
║ MLGym MCP Tools v${CURRENT_VERSION} ║
|
|
2833
|
+
║ Complete Deployment Platform for GitLab + Coolify ║
|
|
2834
|
+
╚═══════════════════════════════════════════════════════════════════════╝
|
|
2835
|
+
|
|
2836
|
+
📦 DEPLOYMENT TOOLS
|
|
2837
|
+
───────────────────────────────────────────────────────────────────────
|
|
2838
|
+
|
|
2839
|
+
🚀 mlgym_deploy
|
|
2840
|
+
Complete deployment workflow - auth, analyze, create repo, and deploy in one call.
|
|
2841
|
+
|
|
2842
|
+
Usage:
|
|
2843
|
+
mlgym_deploy({
|
|
2844
|
+
project_name: "my-app",
|
|
2845
|
+
project_description: "My application",
|
|
2846
|
+
email: "user@example.com", // optional if already authenticated
|
|
2847
|
+
password: "password123" // optional if already authenticated
|
|
2848
|
+
})
|
|
2849
|
+
|
|
2850
|
+
📊 mlgym_status
|
|
2851
|
+
Check authentication status and project configuration.
|
|
2852
|
+
|
|
2853
|
+
Usage:
|
|
2854
|
+
mlgym_status({local_path: "."})
|
|
2855
|
+
|
|
2856
|
+
📝 mlgym_deploy_logs
|
|
2857
|
+
View deployment history and build logs.
|
|
2858
|
+
|
|
2859
|
+
Usage:
|
|
2860
|
+
mlgym_deploy_logs({
|
|
2861
|
+
project_name: "my-app",
|
|
2862
|
+
depth: 3 // number of deployments to retrieve
|
|
2863
|
+
})
|
|
2864
|
+
|
|
2865
|
+
⚙️ CONFIGURATION TOOLS
|
|
2866
|
+
───────────────────────────────────────────────────────────────────────
|
|
2867
|
+
|
|
2868
|
+
🔧 mlgym_set_env_vars
|
|
2869
|
+
Set environment variables (DATABASE_URL, API keys, etc.)
|
|
2870
|
+
|
|
2871
|
+
Usage:
|
|
2872
|
+
mlgym_set_env_vars({
|
|
2873
|
+
project_name: "my-app",
|
|
2874
|
+
variables: {
|
|
2875
|
+
DATABASE_URL: "postgresql://...",
|
|
2876
|
+
API_KEY: "secret123"
|
|
2877
|
+
}
|
|
2878
|
+
})
|
|
2879
|
+
|
|
2880
|
+
❤️ mlgym_set_health_check
|
|
2881
|
+
Configure health checks to monitor application availability.
|
|
2882
|
+
|
|
2883
|
+
Usage:
|
|
2884
|
+
mlgym_set_health_check({
|
|
2885
|
+
project_name: "my-app",
|
|
2886
|
+
enabled: true,
|
|
2887
|
+
path: "/health",
|
|
2888
|
+
method: "GET",
|
|
2889
|
+
return_code: 200,
|
|
2890
|
+
interval: 5
|
|
2891
|
+
})
|
|
2892
|
+
|
|
2893
|
+
🌐 mlgym_set_domain
|
|
2894
|
+
Set custom domain/FQDN for your application.
|
|
2895
|
+
|
|
2896
|
+
Usage:
|
|
2897
|
+
mlgym_set_domain({
|
|
2898
|
+
project_name: "my-app",
|
|
2899
|
+
domain: "myapp.example.com"
|
|
2900
|
+
})
|
|
2901
|
+
|
|
2902
|
+
📜 mlgym_set_deployment_commands
|
|
2903
|
+
Configure pre-deployment and post-deployment commands.
|
|
2904
|
+
|
|
2905
|
+
Usage:
|
|
2906
|
+
mlgym_set_deployment_commands({
|
|
2907
|
+
project_name: "my-app",
|
|
2908
|
+
pre_command: "npm run migrate",
|
|
2909
|
+
post_command: "npm run seed"
|
|
2910
|
+
})
|
|
2911
|
+
|
|
2912
|
+
⚙️ mlgym_set_options
|
|
2913
|
+
Configure application options (build cache, etc.)
|
|
2914
|
+
|
|
2915
|
+
Usage:
|
|
2916
|
+
mlgym_set_options({
|
|
2917
|
+
project_name: "my-app",
|
|
2918
|
+
disable_build_cache: true
|
|
2919
|
+
})
|
|
2920
|
+
|
|
2921
|
+
🎬 DEPLOYMENT MANAGEMENT
|
|
2922
|
+
───────────────────────────────────────────────────────────────────────
|
|
2923
|
+
|
|
2924
|
+
▶️ mlgym_deploy_manual
|
|
2925
|
+
Manually trigger a new deployment.
|
|
2926
|
+
|
|
2927
|
+
Usage:
|
|
2928
|
+
mlgym_deploy_manual({project_name: "my-app"})
|
|
2929
|
+
|
|
2930
|
+
⏪ mlgym_rollback
|
|
2931
|
+
Rollback to previous deployment.
|
|
2932
|
+
|
|
2933
|
+
Usage:
|
|
2934
|
+
mlgym_rollback({
|
|
2935
|
+
project_name: "my-app",
|
|
2936
|
+
deployment_id: 123 // optional, uses previous if not specified
|
|
2937
|
+
})
|
|
2938
|
+
|
|
2939
|
+
👤 USER MANAGEMENT
|
|
2940
|
+
───────────────────────────────────────────────────────────────────────
|
|
2941
|
+
|
|
2942
|
+
➕ mlgym_user_create
|
|
2943
|
+
Create a new MLGym user account.
|
|
2944
|
+
|
|
2945
|
+
Usage:
|
|
2946
|
+
mlgym_user_create({
|
|
2947
|
+
email: "user@example.com",
|
|
2948
|
+
name: "John Doe",
|
|
2949
|
+
password: "SecurePass123!",
|
|
2950
|
+
accept_terms: true
|
|
2951
|
+
})
|
|
2952
|
+
|
|
2953
|
+
🔐 mlgym_auth_login
|
|
2954
|
+
Login with existing credentials.
|
|
2955
|
+
|
|
2956
|
+
Usage:
|
|
2957
|
+
mlgym_auth_login({
|
|
2958
|
+
email: "user@example.com",
|
|
2959
|
+
password: "SecurePass123!"
|
|
2960
|
+
})
|
|
2961
|
+
|
|
2962
|
+
❓ HELP
|
|
2963
|
+
───────────────────────────────────────────────────────────────────────
|
|
2964
|
+
|
|
2965
|
+
📖 mlgym_help
|
|
2966
|
+
Display this help message.
|
|
2967
|
+
|
|
2968
|
+
Usage:
|
|
2969
|
+
mlgym_help()
|
|
2970
|
+
|
|
2971
|
+
╔═══════════════════════════════════════════════════════════════════════╗
|
|
2972
|
+
║ WORKFLOW EXAMPLES ║
|
|
2973
|
+
╚═══════════════════════════════════════════════════════════════════════╝
|
|
2974
|
+
|
|
2975
|
+
1️⃣ Complete New Deployment:
|
|
2976
|
+
mlgym_deploy({
|
|
2977
|
+
project_name: "my-app",
|
|
2978
|
+
project_description: "My new app",
|
|
2979
|
+
email: "me@example.com",
|
|
2980
|
+
password: "pass123"
|
|
2981
|
+
})
|
|
2982
|
+
|
|
2983
|
+
2️⃣ Set Environment Variables:
|
|
2984
|
+
mlgym_set_env_vars({
|
|
2985
|
+
project_name: "my-app",
|
|
2986
|
+
variables: {DATABASE_URL: "..."}
|
|
2987
|
+
})
|
|
2988
|
+
|
|
2989
|
+
3️⃣ Configure & Deploy:
|
|
2990
|
+
mlgym_set_health_check({project_name: "my-app", enabled: true})
|
|
2991
|
+
mlgym_set_domain({project_name: "my-app", domain: "app.example.com"})
|
|
2992
|
+
mlgym_deploy_manual({project_name: "my-app"})
|
|
2993
|
+
|
|
2994
|
+
4️⃣ Monitor & Rollback if needed:
|
|
2995
|
+
mlgym_deploy_logs({project_name: "my-app", depth: 5})
|
|
2996
|
+
mlgym_rollback({project_name: "my-app"})
|
|
2997
|
+
|
|
2998
|
+
📚 More info: https://backend.eu.ezb.net
|
|
2999
|
+
🐛 Issues: Report via user dashboard
|
|
3000
|
+
`;
|
|
3001
|
+
|
|
3002
|
+
return {
|
|
3003
|
+
content: [{
|
|
3004
|
+
type: 'text',
|
|
3005
|
+
text: helpText
|
|
3006
|
+
}]
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
|
|
2314
3010
|
// Create the MCP server
|
|
2315
3011
|
const server = new Server(
|
|
2316
3012
|
{
|
|
@@ -2429,6 +3125,231 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2429
3125
|
}
|
|
2430
3126
|
}
|
|
2431
3127
|
},
|
|
3128
|
+
{
|
|
3129
|
+
name: 'mlgym_set_env_vars',
|
|
3130
|
+
description: 'Set environment variables for a Coolify application. Environment variables are available during both build time and runtime. Useful for setting DATABASE_URL, API keys, and other configuration.',
|
|
3131
|
+
inputSchema: {
|
|
3132
|
+
type: 'object',
|
|
3133
|
+
properties: {
|
|
3134
|
+
project_name: {
|
|
3135
|
+
type: 'string',
|
|
3136
|
+
description: 'Project/application name',
|
|
3137
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3138
|
+
minLength: 3
|
|
3139
|
+
},
|
|
3140
|
+
variables: {
|
|
3141
|
+
type: 'object',
|
|
3142
|
+
description: 'Environment variables as key-value pairs (e.g., {"DATABASE_URL": "postgresql://...", "STRIPE_SECRET_KEY": "sk_..."})',
|
|
3143
|
+
additionalProperties: {
|
|
3144
|
+
type: 'string'
|
|
3145
|
+
}
|
|
3146
|
+
},
|
|
3147
|
+
local_path: {
|
|
3148
|
+
type: 'string',
|
|
3149
|
+
description: 'Local project directory path (optional, defaults to current directory)',
|
|
3150
|
+
default: '.'
|
|
3151
|
+
}
|
|
3152
|
+
},
|
|
3153
|
+
required: ['variables']
|
|
3154
|
+
}
|
|
3155
|
+
},
|
|
3156
|
+
{
|
|
3157
|
+
name: 'mlgym_set_health_check',
|
|
3158
|
+
description: 'Configure health check for application. Health checks monitor application availability and restart containers if unhealthy.',
|
|
3159
|
+
inputSchema: {
|
|
3160
|
+
type: 'object',
|
|
3161
|
+
properties: {
|
|
3162
|
+
project_name: {
|
|
3163
|
+
type: 'string',
|
|
3164
|
+
description: 'Project/application name',
|
|
3165
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3166
|
+
minLength: 3
|
|
3167
|
+
},
|
|
3168
|
+
enabled: {
|
|
3169
|
+
type: 'boolean',
|
|
3170
|
+
description: 'Enable or disable health checks'
|
|
3171
|
+
},
|
|
3172
|
+
path: {
|
|
3173
|
+
type: 'string',
|
|
3174
|
+
description: 'Health check endpoint path (e.g., "/health", "/api/health")',
|
|
3175
|
+
default: '/'
|
|
3176
|
+
},
|
|
3177
|
+
port: {
|
|
3178
|
+
type: 'string',
|
|
3179
|
+
description: 'Port to check (optional, uses exposed port if not specified)'
|
|
3180
|
+
},
|
|
3181
|
+
method: {
|
|
3182
|
+
type: 'string',
|
|
3183
|
+
description: 'HTTP method for health check',
|
|
3184
|
+
enum: ['GET', 'POST', 'HEAD'],
|
|
3185
|
+
default: 'GET'
|
|
3186
|
+
},
|
|
3187
|
+
return_code: {
|
|
3188
|
+
type: 'number',
|
|
3189
|
+
description: 'Expected HTTP return code for healthy status',
|
|
3190
|
+
default: 200
|
|
3191
|
+
},
|
|
3192
|
+
interval: {
|
|
3193
|
+
type: 'number',
|
|
3194
|
+
description: 'Seconds between health checks',
|
|
3195
|
+
default: 5
|
|
3196
|
+
},
|
|
3197
|
+
timeout: {
|
|
3198
|
+
type: 'number',
|
|
3199
|
+
description: 'Seconds before health check times out',
|
|
3200
|
+
default: 5
|
|
3201
|
+
},
|
|
3202
|
+
retries: {
|
|
3203
|
+
type: 'number',
|
|
3204
|
+
description: 'Number of retries before marking unhealthy',
|
|
3205
|
+
default: 10
|
|
3206
|
+
},
|
|
3207
|
+
local_path: {
|
|
3208
|
+
type: 'string',
|
|
3209
|
+
description: 'Local project directory path (optional)',
|
|
3210
|
+
default: '.'
|
|
3211
|
+
}
|
|
3212
|
+
},
|
|
3213
|
+
required: ['enabled']
|
|
3214
|
+
}
|
|
3215
|
+
},
|
|
3216
|
+
{
|
|
3217
|
+
name: 'mlgym_set_domain',
|
|
3218
|
+
description: 'Set custom domain/FQDN for application. Updates the application URL where it will be accessible.',
|
|
3219
|
+
inputSchema: {
|
|
3220
|
+
type: 'object',
|
|
3221
|
+
properties: {
|
|
3222
|
+
project_name: {
|
|
3223
|
+
type: 'string',
|
|
3224
|
+
description: 'Project/application name',
|
|
3225
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3226
|
+
minLength: 3
|
|
3227
|
+
},
|
|
3228
|
+
domain: {
|
|
3229
|
+
type: 'string',
|
|
3230
|
+
description: 'Domain/FQDN (e.g., "myapp.example.com" or "https://myapp.example.com")'
|
|
3231
|
+
},
|
|
3232
|
+
local_path: {
|
|
3233
|
+
type: 'string',
|
|
3234
|
+
description: 'Local project directory path (optional)',
|
|
3235
|
+
default: '.'
|
|
3236
|
+
}
|
|
3237
|
+
},
|
|
3238
|
+
required: ['domain']
|
|
3239
|
+
}
|
|
3240
|
+
},
|
|
3241
|
+
{
|
|
3242
|
+
name: 'mlgym_set_deployment_commands',
|
|
3243
|
+
description: 'Configure pre-deployment and post-deployment commands. Pre-deployment runs before build, post-deployment runs after deployment.',
|
|
3244
|
+
inputSchema: {
|
|
3245
|
+
type: 'object',
|
|
3246
|
+
properties: {
|
|
3247
|
+
project_name: {
|
|
3248
|
+
type: 'string',
|
|
3249
|
+
description: 'Project/application name',
|
|
3250
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3251
|
+
minLength: 3
|
|
3252
|
+
},
|
|
3253
|
+
pre_command: {
|
|
3254
|
+
type: 'string',
|
|
3255
|
+
description: 'Command to run before deployment (e.g., "npm run migrate")'
|
|
3256
|
+
},
|
|
3257
|
+
pre_command_container: {
|
|
3258
|
+
type: 'string',
|
|
3259
|
+
description: 'Container to run pre-command in (for docker-compose apps)'
|
|
3260
|
+
},
|
|
3261
|
+
post_command: {
|
|
3262
|
+
type: 'string',
|
|
3263
|
+
description: 'Command to run after deployment (e.g., "npm run seed")'
|
|
3264
|
+
},
|
|
3265
|
+
post_command_container: {
|
|
3266
|
+
type: 'string',
|
|
3267
|
+
description: 'Container to run post-command in (for docker-compose apps)'
|
|
3268
|
+
},
|
|
3269
|
+
local_path: {
|
|
3270
|
+
type: 'string',
|
|
3271
|
+
description: 'Local project directory path (optional)',
|
|
3272
|
+
default: '.'
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
name: 'mlgym_deploy_manual',
|
|
3279
|
+
description: 'Manually trigger a new deployment. Forces rebuild and deploys current code from GitLab repository.',
|
|
3280
|
+
inputSchema: {
|
|
3281
|
+
type: 'object',
|
|
3282
|
+
properties: {
|
|
3283
|
+
project_name: {
|
|
3284
|
+
type: 'string',
|
|
3285
|
+
description: 'Project/application name',
|
|
3286
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3287
|
+
minLength: 3
|
|
3288
|
+
},
|
|
3289
|
+
local_path: {
|
|
3290
|
+
type: 'string',
|
|
3291
|
+
description: 'Local project directory path (optional)',
|
|
3292
|
+
default: '.'
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
},
|
|
3297
|
+
{
|
|
3298
|
+
name: 'mlgym_set_options',
|
|
3299
|
+
description: 'Configure application options like build cache. More options may be added in future versions.',
|
|
3300
|
+
inputSchema: {
|
|
3301
|
+
type: 'object',
|
|
3302
|
+
properties: {
|
|
3303
|
+
project_name: {
|
|
3304
|
+
type: 'string',
|
|
3305
|
+
description: 'Project/application name',
|
|
3306
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3307
|
+
minLength: 3
|
|
3308
|
+
},
|
|
3309
|
+
disable_build_cache: {
|
|
3310
|
+
type: 'boolean',
|
|
3311
|
+
description: 'Disable Docker build cache (forces clean builds)'
|
|
3312
|
+
},
|
|
3313
|
+
local_path: {
|
|
3314
|
+
type: 'string',
|
|
3315
|
+
description: 'Local project directory path (optional)',
|
|
3316
|
+
default: '.'
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
},
|
|
3321
|
+
{
|
|
3322
|
+
name: 'mlgym_rollback',
|
|
3323
|
+
description: 'Rollback to a previous deployment. If deployment_id not specified, rolls back to the last successful deployment.',
|
|
3324
|
+
inputSchema: {
|
|
3325
|
+
type: 'object',
|
|
3326
|
+
properties: {
|
|
3327
|
+
project_name: {
|
|
3328
|
+
type: 'string',
|
|
3329
|
+
description: 'Project/application name',
|
|
3330
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
3331
|
+
minLength: 3
|
|
3332
|
+
},
|
|
3333
|
+
deployment_id: {
|
|
3334
|
+
type: 'number',
|
|
3335
|
+
description: 'Specific deployment ID to rollback to (optional, uses previous if not specified)'
|
|
3336
|
+
},
|
|
3337
|
+
local_path: {
|
|
3338
|
+
type: 'string',
|
|
3339
|
+
description: 'Local project directory path (optional)',
|
|
3340
|
+
default: '.'
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
{
|
|
3346
|
+
name: 'mlgym_help',
|
|
3347
|
+
description: 'Display all available MLGym tools with descriptions and usage examples. Use this to discover what operations you can perform.',
|
|
3348
|
+
inputSchema: {
|
|
3349
|
+
type: 'object',
|
|
3350
|
+
properties: {}
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
2432
3353
|
// ========== LEGACY TOOLS (Re-enabled for specific use cases) ==========
|
|
2433
3354
|
{
|
|
2434
3355
|
name: 'mlgym_user_create',
|
|
@@ -2521,8 +3442,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2521
3442
|
result = await loginUser(args);
|
|
2522
3443
|
break;
|
|
2523
3444
|
|
|
3445
|
+
case 'mlgym_set_env_vars':
|
|
3446
|
+
log.info(`Setting environment variables...`);
|
|
3447
|
+
result = await setEnvironmentVariables(args);
|
|
3448
|
+
break;
|
|
3449
|
+
|
|
3450
|
+
case 'mlgym_set_health_check':
|
|
3451
|
+
log.info(`Configuring health check...`);
|
|
3452
|
+
result = await setHealthCheck(args);
|
|
3453
|
+
break;
|
|
3454
|
+
|
|
3455
|
+
case 'mlgym_set_domain':
|
|
3456
|
+
log.info(`Setting custom domain...`);
|
|
3457
|
+
result = await setDomain(args);
|
|
3458
|
+
break;
|
|
3459
|
+
|
|
3460
|
+
case 'mlgym_set_deployment_commands':
|
|
3461
|
+
log.info(`Setting deployment commands...`);
|
|
3462
|
+
result = await setDeploymentCommands(args);
|
|
3463
|
+
break;
|
|
3464
|
+
|
|
3465
|
+
case 'mlgym_deploy_manual':
|
|
3466
|
+
log.info(`Triggering manual deployment...`);
|
|
3467
|
+
result = await manualDeploy(args);
|
|
3468
|
+
break;
|
|
3469
|
+
|
|
3470
|
+
case 'mlgym_set_options':
|
|
3471
|
+
log.info(`Setting application options...`);
|
|
3472
|
+
result = await setOptions(args);
|
|
3473
|
+
break;
|
|
3474
|
+
|
|
3475
|
+
case 'mlgym_rollback':
|
|
3476
|
+
log.info(`Rolling back deployment...`);
|
|
3477
|
+
result = await rollback(args);
|
|
3478
|
+
break;
|
|
3479
|
+
|
|
3480
|
+
case 'mlgym_help':
|
|
3481
|
+
log.info(`Showing help...`);
|
|
3482
|
+
result = await showHelp(args);
|
|
3483
|
+
break;
|
|
3484
|
+
|
|
2524
3485
|
default:
|
|
2525
|
-
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_deploy_logs, mlgym_user_create, mlgym_auth_login`);
|
|
3486
|
+
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_deploy_logs, mlgym_user_create, mlgym_auth_login, mlgym_set_env_vars, mlgym_set_health_check, mlgym_set_domain, mlgym_set_deployment_commands, mlgym_deploy_manual, mlgym_set_options, mlgym_rollback`);
|
|
2526
3487
|
}
|
|
2527
3488
|
|
|
2528
3489
|
const duration = Date.now() - startTime;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlgym-deploy",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "MCP server for MLGym -
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "MCP server for MLGym - Complete deployment management: deploy, configure, monitor, and rollback applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -47,6 +47,38 @@
|
|
|
47
47
|
{
|
|
48
48
|
"name": "mlgym_auth_login",
|
|
49
49
|
"description": "Login with existing credentials"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "mlgym_set_env_vars",
|
|
53
|
+
"description": "Set environment variables for an application (DATABASE_URL, API keys, etc.)"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "mlgym_set_health_check",
|
|
57
|
+
"description": "Configure health checks to monitor application availability"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "mlgym_set_domain",
|
|
61
|
+
"description": "Set custom domain/FQDN for application"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "mlgym_set_deployment_commands",
|
|
65
|
+
"description": "Configure pre-deployment and post-deployment commands"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "mlgym_deploy_manual",
|
|
69
|
+
"description": "Manually trigger a new deployment"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "mlgym_set_options",
|
|
73
|
+
"description": "Configure application options (build cache, etc.)"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "mlgym_rollback",
|
|
77
|
+
"description": "Rollback to previous deployment"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "mlgym_help",
|
|
81
|
+
"description": "Display all available tools with descriptions and usage examples"
|
|
50
82
|
}
|
|
51
83
|
]
|
|
52
84
|
}
|