mlgym-deploy 3.0.0 → 3.0.2
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 +285 -2
- package/package.json +11 -3
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.0.
|
|
20
|
+
const CURRENT_VERSION = '3.0.2'; // Fixed SSH key propagation delay before git push
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Version check state
|
|
@@ -999,6 +999,46 @@ async function initProject(args) {
|
|
|
999
999
|
|
|
1000
1000
|
await execAsync(`git remote add mlgym ${gitUrl}`, { cwd: absolutePath });
|
|
1001
1001
|
|
|
1002
|
+
// Ensure SSH key is ready for git push
|
|
1003
|
+
const authInfo = await loadAuth();
|
|
1004
|
+
if (authInfo.email) {
|
|
1005
|
+
const sanitizedEmail = authInfo.email.replace('@', '_at_').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
1006
|
+
const sshKeyPath = path.join(os.homedir(), '.ssh', `mlgym_${sanitizedEmail}`);
|
|
1007
|
+
|
|
1008
|
+
try {
|
|
1009
|
+
// Check if SSH key exists
|
|
1010
|
+
await fs.access(sshKeyPath);
|
|
1011
|
+
|
|
1012
|
+
// Add SSH key to agent
|
|
1013
|
+
console.error('Adding SSH key to ssh-agent...');
|
|
1014
|
+
try {
|
|
1015
|
+
await execAsync(`ssh-add "${sshKeyPath}" 2>/dev/null`);
|
|
1016
|
+
} catch (agentErr) {
|
|
1017
|
+
// ssh-agent might not be running, which is OK if SSH config is properly set
|
|
1018
|
+
console.error('Note: ssh-agent not available, relying on SSH config');
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Wait for GitLab SSH key propagation (10 seconds)
|
|
1022
|
+
console.error('Waiting for GitLab SSH key propagation (10 seconds)...');
|
|
1023
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
1024
|
+
|
|
1025
|
+
// Test SSH connectivity
|
|
1026
|
+
console.error('Testing SSH connection to GitLab...');
|
|
1027
|
+
try {
|
|
1028
|
+
await execAsync('ssh -T git@git.mlgym.io 2>&1', { timeout: 10000 });
|
|
1029
|
+
} catch (sshTestErr) {
|
|
1030
|
+
// SSH test may fail with "Welcome to GitLab" message which returns exit code 1
|
|
1031
|
+
// This is actually success - we just need to verify the key works
|
|
1032
|
+
const output = sshTestErr.stdout || sshTestErr.stderr || '';
|
|
1033
|
+
if (!output.includes('Welcome') && !output.includes('successfully authenticated')) {
|
|
1034
|
+
console.error('SSH connection test warning:', output);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
} catch (keyErr) {
|
|
1038
|
+
console.error('Warning: SSH key setup issue:', keyErr.message);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1002
1042
|
// Create initial commit and push (like CLI does)
|
|
1003
1043
|
const gitSteps = [];
|
|
1004
1044
|
|
|
@@ -1107,6 +1147,193 @@ async function initProject(args) {
|
|
|
1107
1147
|
};
|
|
1108
1148
|
}
|
|
1109
1149
|
|
|
1150
|
+
// ============================================================================
|
|
1151
|
+
// LEGACY USER MANAGEMENT TOOLS (DISABLED - kept for reference)
|
|
1152
|
+
// ============================================================================
|
|
1153
|
+
|
|
1154
|
+
// Create User (from v2.3.0 - ENABLED)
|
|
1155
|
+
async function createUser(args) {
|
|
1156
|
+
let { email, name, password, accept_terms } = args;
|
|
1157
|
+
|
|
1158
|
+
// Validate all required fields
|
|
1159
|
+
if (!email || !name || !password || accept_terms !== true) {
|
|
1160
|
+
return {
|
|
1161
|
+
content: [{
|
|
1162
|
+
type: 'text',
|
|
1163
|
+
text: JSON.stringify({
|
|
1164
|
+
status: 'error',
|
|
1165
|
+
message: 'All fields are required: email, name, password, and accept_terms must be true',
|
|
1166
|
+
required_fields: {
|
|
1167
|
+
email: email ? '✓ provided' : '✗ missing',
|
|
1168
|
+
name: name ? '✓ provided' : '✗ missing',
|
|
1169
|
+
password: password ? '✓ provided' : '✗ missing',
|
|
1170
|
+
accept_terms: accept_terms === true ? '✓ accepted' : '✗ must explicitly accept terms'
|
|
1171
|
+
}
|
|
1172
|
+
}, null, 2)
|
|
1173
|
+
}]
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
console.error(`Creating user: ${email}`);
|
|
1178
|
+
|
|
1179
|
+
try {
|
|
1180
|
+
// Generate SSH key pair
|
|
1181
|
+
console.error('Generating SSH key pair...');
|
|
1182
|
+
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
1183
|
+
const publicKeyPath = privateKeyPath + '.pub';
|
|
1184
|
+
console.error(`SSH key generated: ${privateKeyPath}`);
|
|
1185
|
+
|
|
1186
|
+
// Create user via backend API with SSH key
|
|
1187
|
+
const result = await apiRequest('POST', '/api/v1/users', {
|
|
1188
|
+
email,
|
|
1189
|
+
name,
|
|
1190
|
+
password,
|
|
1191
|
+
ssh_key: publicKey
|
|
1192
|
+
}, false);
|
|
1193
|
+
|
|
1194
|
+
if (!result.success) {
|
|
1195
|
+
return {
|
|
1196
|
+
content: [{
|
|
1197
|
+
type: 'text',
|
|
1198
|
+
text: `Failed to create user: ${result.error}`
|
|
1199
|
+
}]
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Save authentication token
|
|
1204
|
+
if (result.data.token) {
|
|
1205
|
+
await saveAuth(email, result.data.token);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const userData = result.data.user || {};
|
|
1209
|
+
const sshKeyStatus = result.data.ssh_key_status || 'Unknown';
|
|
1210
|
+
const sshKeyUploaded = sshKeyStatus === 'SSH key uploaded successfully';
|
|
1211
|
+
|
|
1212
|
+
const nextSteps = [
|
|
1213
|
+
'Using your provided password',
|
|
1214
|
+
`SSH key generated locally at: ${privateKeyPath}`
|
|
1215
|
+
];
|
|
1216
|
+
|
|
1217
|
+
if (sshKeyUploaded) {
|
|
1218
|
+
nextSteps.push(`✅ SSH key uploaded to GitLab (ID: ${result.data.ssh_key_id || 'unknown'})`);
|
|
1219
|
+
nextSteps.push(`Add to SSH agent: ssh-add "${privateKeyPath}"`);
|
|
1220
|
+
nextSteps.push('You can now push code to GitLab repositories');
|
|
1221
|
+
} else {
|
|
1222
|
+
nextSteps.push(`⚠️ SSH key upload failed: ${sshKeyStatus}`);
|
|
1223
|
+
nextSteps.push('You need to manually add the SSH key to GitLab:');
|
|
1224
|
+
nextSteps.push(`cat ${publicKeyPath} # Copy this key`);
|
|
1225
|
+
nextSteps.push('Then add it at: https://git.mlgym.io/-/user_settings/ssh_keys');
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const response = {
|
|
1229
|
+
user_id: userData.user_id || userData.UserID,
|
|
1230
|
+
email: userData.email || email,
|
|
1231
|
+
name: userData.name || name,
|
|
1232
|
+
gitlab_username: userData.gitlab_username || userData.GitLabUsername,
|
|
1233
|
+
ssh_key_path: privateKeyPath,
|
|
1234
|
+
ssh_key_status: sshKeyStatus,
|
|
1235
|
+
ssh_key_uploaded: sshKeyUploaded,
|
|
1236
|
+
token: result.data.token,
|
|
1237
|
+
message: result.data.message || 'User created successfully',
|
|
1238
|
+
next_steps: nextSteps
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
return {
|
|
1242
|
+
content: [{
|
|
1243
|
+
type: 'text',
|
|
1244
|
+
text: JSON.stringify(response, null, 2)
|
|
1245
|
+
}]
|
|
1246
|
+
};
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
return {
|
|
1249
|
+
content: [{
|
|
1250
|
+
type: 'text',
|
|
1251
|
+
text: `Failed to create user: ${error.message}`
|
|
1252
|
+
}]
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Login User (from v2.3.0 - ENABLED)
|
|
1258
|
+
async function loginUser(args) {
|
|
1259
|
+
const { email, password } = args;
|
|
1260
|
+
|
|
1261
|
+
if (!email || !password) {
|
|
1262
|
+
return {
|
|
1263
|
+
content: [{
|
|
1264
|
+
type: 'text',
|
|
1265
|
+
text: JSON.stringify({
|
|
1266
|
+
status: 'error',
|
|
1267
|
+
message: 'Email and password are required'
|
|
1268
|
+
}, null, 2)
|
|
1269
|
+
}]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
console.error(`Logging in user: ${email}`);
|
|
1274
|
+
|
|
1275
|
+
try {
|
|
1276
|
+
const result = await apiRequest('POST', '/api/v1/auth/login', {
|
|
1277
|
+
email,
|
|
1278
|
+
password
|
|
1279
|
+
}, false);
|
|
1280
|
+
|
|
1281
|
+
if (!result.success) {
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{
|
|
1284
|
+
type: 'text',
|
|
1285
|
+
text: JSON.stringify({
|
|
1286
|
+
status: 'error',
|
|
1287
|
+
message: result.error || 'Login failed',
|
|
1288
|
+
details: 'Invalid email or password'
|
|
1289
|
+
}, null, 2)
|
|
1290
|
+
}]
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Save authentication token
|
|
1295
|
+
if (result.data.token) {
|
|
1296
|
+
await saveAuth(email, result.data.token);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const userData = result.data.user || {};
|
|
1300
|
+
const response = {
|
|
1301
|
+
status: 'success',
|
|
1302
|
+
token: result.data.token,
|
|
1303
|
+
user: {
|
|
1304
|
+
user_id: userData.user_id || userData.UserID,
|
|
1305
|
+
email: userData.email || email,
|
|
1306
|
+
name: userData.name || userData.Name,
|
|
1307
|
+
gitlab_username: userData.gitlab_username || userData.GitLabUsername
|
|
1308
|
+
},
|
|
1309
|
+
expires_at: result.data.expires_at || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
1310
|
+
message: 'Login successful',
|
|
1311
|
+
next_steps: [
|
|
1312
|
+
'You can now create and manage projects',
|
|
1313
|
+
'Use mlgym_project_init or mlgym_deploy to create a new project',
|
|
1314
|
+
'Token will expire in 24 hours'
|
|
1315
|
+
]
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
return {
|
|
1319
|
+
content: [{
|
|
1320
|
+
type: 'text',
|
|
1321
|
+
text: JSON.stringify(response, null, 2)
|
|
1322
|
+
}]
|
|
1323
|
+
};
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
return {
|
|
1326
|
+
content: [{
|
|
1327
|
+
type: 'text',
|
|
1328
|
+
text: JSON.stringify({
|
|
1329
|
+
status: 'error',
|
|
1330
|
+
message: `Login failed: ${error.message}`
|
|
1331
|
+
}, null, 2)
|
|
1332
|
+
}]
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1110
1337
|
// ============================================================================
|
|
1111
1338
|
// MONOLITHIC DEPLOYMENT WORKFLOW CLASS (v3.0.0)
|
|
1112
1339
|
// ============================================================================
|
|
@@ -1533,6 +1760,56 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1533
1760
|
}
|
|
1534
1761
|
}
|
|
1535
1762
|
}
|
|
1763
|
+
},
|
|
1764
|
+
// ========== LEGACY TOOLS (Re-enabled for specific use cases) ==========
|
|
1765
|
+
{
|
|
1766
|
+
name: 'mlgym_user_create',
|
|
1767
|
+
description: 'Create a new MLGym user account. All fields required: email, name, password, and accept_terms=true.',
|
|
1768
|
+
inputSchema: {
|
|
1769
|
+
type: 'object',
|
|
1770
|
+
properties: {
|
|
1771
|
+
email: {
|
|
1772
|
+
type: 'string',
|
|
1773
|
+
description: 'User email address',
|
|
1774
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1775
|
+
},
|
|
1776
|
+
name: {
|
|
1777
|
+
type: 'string',
|
|
1778
|
+
description: 'User full name',
|
|
1779
|
+
minLength: 2
|
|
1780
|
+
},
|
|
1781
|
+
password: {
|
|
1782
|
+
type: 'string',
|
|
1783
|
+
description: 'User password (minimum 8 characters)',
|
|
1784
|
+
minLength: 8
|
|
1785
|
+
},
|
|
1786
|
+
accept_terms: {
|
|
1787
|
+
type: 'boolean',
|
|
1788
|
+
description: 'User must explicitly accept terms and conditions (must be true)'
|
|
1789
|
+
}
|
|
1790
|
+
},
|
|
1791
|
+
required: ['email', 'name', 'password', 'accept_terms']
|
|
1792
|
+
}
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
name: 'mlgym_auth_login',
|
|
1796
|
+
description: 'Login with existing user credentials to get authentication token.',
|
|
1797
|
+
inputSchema: {
|
|
1798
|
+
type: 'object',
|
|
1799
|
+
properties: {
|
|
1800
|
+
email: {
|
|
1801
|
+
type: 'string',
|
|
1802
|
+
description: 'Email address',
|
|
1803
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1804
|
+
},
|
|
1805
|
+
password: {
|
|
1806
|
+
type: 'string',
|
|
1807
|
+
description: 'Password',
|
|
1808
|
+
minLength: 8
|
|
1809
|
+
}
|
|
1810
|
+
},
|
|
1811
|
+
required: ['email', 'password']
|
|
1812
|
+
}
|
|
1536
1813
|
}
|
|
1537
1814
|
]
|
|
1538
1815
|
};
|
|
@@ -1552,8 +1829,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1552
1829
|
case 'mlgym_status':
|
|
1553
1830
|
return await getStatus(args);
|
|
1554
1831
|
|
|
1832
|
+
case 'mlgym_user_create':
|
|
1833
|
+
return await createUser(args);
|
|
1834
|
+
|
|
1835
|
+
case 'mlgym_auth_login':
|
|
1836
|
+
return await loginUser(args);
|
|
1837
|
+
|
|
1555
1838
|
default:
|
|
1556
|
-
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status`);
|
|
1839
|
+
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_user_create, mlgym_auth_login`);
|
|
1557
1840
|
}
|
|
1558
1841
|
} catch (error) {
|
|
1559
1842
|
console.error(`Tool execution failed:`, error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlgym-deploy",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "MCP server for MLGym -
|
|
3
|
+
"version": "3.0.2",
|
|
4
|
+
"description": "MCP server for MLGym - Fixed SSH key propagation for git push operations",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"mcp": {
|
|
32
32
|
"name": "MLGym Deploy",
|
|
33
|
-
"description": "Deploy applications to MLGym with monolithic
|
|
33
|
+
"description": "Deploy applications to MLGym with monolithic workflows plus legacy user management",
|
|
34
34
|
"tools": [
|
|
35
35
|
{
|
|
36
36
|
"name": "mlgym_deploy",
|
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
{
|
|
40
40
|
"name": "mlgym_status",
|
|
41
41
|
"description": "Check authentication and project configuration status"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "mlgym_user_create",
|
|
45
|
+
"description": "Create new MLGym user account with SSH key setup"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "mlgym_auth_login",
|
|
49
|
+
"description": "Login with existing credentials"
|
|
42
50
|
}
|
|
43
51
|
]
|
|
44
52
|
}
|