@rishibhushan/jenkins-mcp-server 1.1.0 → 1.1.3
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/bin/jenkins-mcp.js +357 -17
- package/package.json +6 -4
- package/pyproject.toml +190 -0
- package/src/jenkins_mcp_server/jenkins_client.py +14 -12
- package/src/jenkins_mcp_server/server.py +19 -3
- package/src/jenkins_mcp_server/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/jenkins_mcp_server/__pycache__/__main__.cpython-313.pyc +0 -0
- package/src/jenkins_mcp_server/__pycache__/config.cpython-313.pyc +0 -0
- package/src/jenkins_mcp_server/__pycache__/jenkins_client.cpython-313.pyc +0 -0
- package/src/jenkins_mcp_server/__pycache__/server.cpython-313.pyc +0 -0
- package/src/jenkins_mcp_server.egg-info/PKG-INFO +0 -624
- package/src/jenkins_mcp_server.egg-info/SOURCES.txt +0 -13
- package/src/jenkins_mcp_server.egg-info/dependency_links.txt +0 -1
- package/src/jenkins_mcp_server.egg-info/entry_points.txt +0 -2
- package/src/jenkins_mcp_server.egg-info/requires.txt +0 -20
- package/src/jenkins_mcp_server.egg-info/top_level.txt +0 -1
package/bin/jenkins-mcp.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* This wrapper handles:
|
|
6
6
|
* - Python version detection (cross-platform)
|
|
7
7
|
* - Virtual environment creation
|
|
8
|
-
* -
|
|
8
|
+
* - Smart proxy detection and handling (auto-detects corporate vs public networks)
|
|
9
|
+
* - Dependency installation with retry logic
|
|
9
10
|
* - Server execution
|
|
10
11
|
*
|
|
11
12
|
* Supports: Windows, macOS, Linux
|
|
@@ -26,7 +27,9 @@ const colors = {
|
|
|
26
27
|
green: '\x1b[32m',
|
|
27
28
|
yellow: '\x1b[33m',
|
|
28
29
|
red: '\x1b[31m',
|
|
29
|
-
blue: '\x1b[34m'
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
bold: '\x1b[1m'
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
function log(message, color = 'reset') {
|
|
@@ -40,6 +43,149 @@ function error(message) {
|
|
|
40
43
|
console.error(`${colors.red}ERROR: ${message}${colors.reset}`);
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
function warning(message) {
|
|
47
|
+
console.error(`${colors.yellow}WARNING: ${message}${colors.reset}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function info(message) {
|
|
51
|
+
console.error(`${colors.cyan}ℹ ${message}${colors.reset}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect and display proxy configuration
|
|
56
|
+
* @returns {Object} Proxy configuration details
|
|
57
|
+
*/
|
|
58
|
+
function detectProxyConfig() {
|
|
59
|
+
const proxyVars = [
|
|
60
|
+
'HTTP_PROXY', 'http_proxy',
|
|
61
|
+
'HTTPS_PROXY', 'https_proxy',
|
|
62
|
+
'ALL_PROXY', 'all_proxy',
|
|
63
|
+
'NO_PROXY', 'no_proxy'
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const activeProxies = {};
|
|
67
|
+
for (const varName of proxyVars) {
|
|
68
|
+
if (process.env[varName]) {
|
|
69
|
+
activeProxies[varName] = process.env[varName];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return activeProxies;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check npm config for proxy settings
|
|
78
|
+
* @returns {Object} npm proxy configuration
|
|
79
|
+
*/
|
|
80
|
+
function checkNpmProxy() {
|
|
81
|
+
try {
|
|
82
|
+
const result = spawnSync('npm', ['config', 'list'], {
|
|
83
|
+
stdio: 'pipe',
|
|
84
|
+
encoding: 'utf-8'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (result.status === 0) {
|
|
88
|
+
const output = result.stdout || '';
|
|
89
|
+
const proxyLines = output.split('\n').filter(line =>
|
|
90
|
+
line.toLowerCase().includes('proxy') && !line.includes('; ///')
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (proxyLines.length > 0) {
|
|
94
|
+
return {
|
|
95
|
+
found: true,
|
|
96
|
+
config: proxyLines.join('\n')
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// npm not available or error
|
|
102
|
+
}
|
|
103
|
+
return { found: false };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check pip config for proxy settings
|
|
108
|
+
* @returns {Object} pip proxy configuration
|
|
109
|
+
*/
|
|
110
|
+
function checkPipProxy() {
|
|
111
|
+
try {
|
|
112
|
+
const result = spawnSync('pip3', ['config', 'list'], {
|
|
113
|
+
stdio: 'pipe',
|
|
114
|
+
encoding: 'utf-8'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (result.status === 0) {
|
|
118
|
+
const output = result.stdout || '';
|
|
119
|
+
if (output.toLowerCase().includes('proxy')) {
|
|
120
|
+
return {
|
|
121
|
+
found: true,
|
|
122
|
+
config: output
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// pip not available or error
|
|
128
|
+
}
|
|
129
|
+
return { found: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Test if proxy is reachable
|
|
134
|
+
* @param {string} proxyUrl - Proxy URL to test
|
|
135
|
+
* @returns {boolean} True if proxy is reachable
|
|
136
|
+
*/
|
|
137
|
+
function testProxyConnectivity(proxyUrl) {
|
|
138
|
+
try {
|
|
139
|
+
// Try to parse proxy URL
|
|
140
|
+
const url = new URL(proxyUrl);
|
|
141
|
+
|
|
142
|
+
// Use curl to test proxy connectivity (cross-platform)
|
|
143
|
+
const testCmd = isWindows
|
|
144
|
+
? `curl -s -o NUL -w "%{http_code}" --proxy ${proxyUrl} --max-time 5 https://pypi.org/simple/`
|
|
145
|
+
: `curl -s -o /dev/null -w "%{http_code}" --proxy ${proxyUrl} --max-time 5 https://pypi.org/simple/`;
|
|
146
|
+
|
|
147
|
+
const result = spawnSync(isWindows ? 'cmd' : 'sh',
|
|
148
|
+
isWindows ? ['/c', testCmd] : ['-c', testCmd],
|
|
149
|
+
{
|
|
150
|
+
stdio: 'pipe',
|
|
151
|
+
encoding: 'utf-8',
|
|
152
|
+
timeout: 6000
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const httpCode = result.stdout?.trim();
|
|
157
|
+
return httpCode === '200' || httpCode === '301' || httpCode === '302';
|
|
158
|
+
} catch (e) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Test if PyPI is directly accessible (without proxy)
|
|
165
|
+
* @returns {boolean} True if PyPI is accessible
|
|
166
|
+
*/
|
|
167
|
+
function testDirectPyPIAccess() {
|
|
168
|
+
try {
|
|
169
|
+
const testCmd = isWindows
|
|
170
|
+
? 'curl -s -o NUL -w "%{http_code}" --max-time 5 https://pypi.org/simple/'
|
|
171
|
+
: 'curl -s -o /dev/null -w "%{http_code}" --max-time 5 https://pypi.org/simple/';
|
|
172
|
+
|
|
173
|
+
const result = spawnSync(isWindows ? 'cmd' : 'sh',
|
|
174
|
+
isWindows ? ['/c', testCmd] : ['-c', testCmd],
|
|
175
|
+
{
|
|
176
|
+
stdio: 'pipe',
|
|
177
|
+
encoding: 'utf-8',
|
|
178
|
+
timeout: 6000
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const httpCode = result.stdout?.trim();
|
|
183
|
+
return httpCode === '200' || httpCode === '301' || httpCode === '302';
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
43
189
|
/**
|
|
44
190
|
* Check for Python 3 installation
|
|
45
191
|
* @returns {string} Python command name
|
|
@@ -157,7 +303,115 @@ function dependenciesInstalled(pipPath) {
|
|
|
157
303
|
}
|
|
158
304
|
|
|
159
305
|
/**
|
|
160
|
-
*
|
|
306
|
+
* Show helpful guidance for proxy-related installation failures
|
|
307
|
+
*/
|
|
308
|
+
function showProxyTroubleshooting(activeProxies, canAccessDirectly, proxyWorks) {
|
|
309
|
+
console.error('\n' + '='.repeat(70));
|
|
310
|
+
console.error(colors.bold + colors.yellow + 'INSTALLATION FAILED - NETWORK CONFIGURATION ISSUE' + colors.reset);
|
|
311
|
+
console.error('='.repeat(70));
|
|
312
|
+
|
|
313
|
+
// Check npm and pip config
|
|
314
|
+
const npmProxy = checkNpmProxy();
|
|
315
|
+
const pipProxy = checkPipProxy();
|
|
316
|
+
|
|
317
|
+
if (npmProxy.found) {
|
|
318
|
+
console.error('\n' + colors.red + '⚠️ FOUND PROXY IN NPM CONFIG!' + colors.reset);
|
|
319
|
+
console.error(colors.cyan + npmProxy.config + colors.reset);
|
|
320
|
+
console.error('\n' + colors.yellow + 'This is likely the cause of your issue!' + colors.reset);
|
|
321
|
+
console.error('\n' + colors.bold + 'FIX (run these commands):' + colors.reset);
|
|
322
|
+
console.error(colors.cyan + ' npm config delete proxy' + colors.reset);
|
|
323
|
+
console.error(colors.cyan + ' npm config delete https-proxy' + colors.reset);
|
|
324
|
+
console.error(colors.cyan + ' npm config delete http-proxy' + colors.reset);
|
|
325
|
+
console.error(colors.cyan + ' npm config --global delete proxy' + colors.reset);
|
|
326
|
+
console.error(colors.cyan + ' npm config --global delete https-proxy' + colors.reset);
|
|
327
|
+
console.error('\nThen run your command again.\n');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (pipProxy.found) {
|
|
331
|
+
console.error('\n' + colors.red + '⚠️ FOUND PROXY IN PIP CONFIG!' + colors.reset);
|
|
332
|
+
console.error(colors.cyan + pipProxy.config + colors.reset);
|
|
333
|
+
console.error('\n' + colors.bold + 'FIX (run these commands):' + colors.reset);
|
|
334
|
+
console.error(colors.cyan + ' pip3 config unset global.proxy' + colors.reset);
|
|
335
|
+
console.error(colors.cyan + ' pip3 config unset user.proxy' + colors.reset);
|
|
336
|
+
console.error('\nOr edit/delete: ~/.config/pip/pip.conf\n');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (Object.keys(activeProxies).length > 0) {
|
|
340
|
+
console.error('\n📡 Active proxy environment variables found:');
|
|
341
|
+
for (const [key, value] of Object.entries(activeProxies)) {
|
|
342
|
+
console.error(` ${colors.cyan}${key}${colors.reset} = ${value}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!proxyWorks && canAccessDirectly) {
|
|
346
|
+
// Proxy is set but doesn't work, and direct access works
|
|
347
|
+
console.error('\n' + colors.red + '❌ Proxy is NOT reachable' + colors.reset);
|
|
348
|
+
console.error(colors.green + '✓ Direct internet access IS available' + colors.reset);
|
|
349
|
+
console.error('\n' + colors.yellow + '⚠️ You\'re on a PUBLIC network but have proxy settings from a corporate/VPN network!' + colors.reset);
|
|
350
|
+
console.error('\n💡 SOLUTION:\n');
|
|
351
|
+
console.error(colors.bold + 'Remove the proxy settings:' + colors.reset);
|
|
352
|
+
console.error(colors.cyan + ' unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy ALL_PROXY all_proxy' + colors.reset);
|
|
353
|
+
console.error(' Then run the command again.\n');
|
|
354
|
+
|
|
355
|
+
} else if (proxyWorks && !canAccessDirectly) {
|
|
356
|
+
// Proxy works, direct access doesn't
|
|
357
|
+
console.error('\n' + colors.green + '✓ Proxy IS reachable' + colors.reset);
|
|
358
|
+
console.error(colors.red + '❌ Direct internet access is NOT available' + colors.reset);
|
|
359
|
+
console.error('\n' + colors.blue + 'You\'re on a CORPORATE network - proxy is required.' + colors.reset);
|
|
360
|
+
console.error('\n💡 SOLUTION:\n');
|
|
361
|
+
console.error('The proxy should work. The error may be due to:');
|
|
362
|
+
console.error('1. SSL certificate issues - contact your IT department');
|
|
363
|
+
console.error('2. Authentication required - check if proxy needs username/password');
|
|
364
|
+
console.error('3. Specific packages blocked - contact your IT department\n');
|
|
365
|
+
|
|
366
|
+
} else if (!proxyWorks && !canAccessDirectly) {
|
|
367
|
+
// Neither works
|
|
368
|
+
console.error('\n' + colors.red + '❌ Proxy is NOT reachable' + colors.reset);
|
|
369
|
+
console.error(colors.red + '❌ Direct internet access is NOT available' + colors.reset);
|
|
370
|
+
console.error('\n💡 SOLUTIONS:\n');
|
|
371
|
+
console.error('1. Check your internet connection');
|
|
372
|
+
console.error('2. If on corporate network, verify proxy settings with IT');
|
|
373
|
+
console.error('3. Try a different network (mobile hotspot, home WiFi)');
|
|
374
|
+
console.error('4. Check firewall/antivirus settings\n');
|
|
375
|
+
|
|
376
|
+
} else {
|
|
377
|
+
// Both work (unusual case)
|
|
378
|
+
console.error('\n' + colors.green + '✓ Proxy IS reachable' + colors.reset);
|
|
379
|
+
console.error(colors.green + '✓ Direct internet access IS available' + colors.reset);
|
|
380
|
+
console.error('\nThe issue may be:');
|
|
381
|
+
console.error('1. SSL certificate problems');
|
|
382
|
+
console.error('2. Intermittent connectivity');
|
|
383
|
+
console.error('3. Package-specific blocking\n');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.error(colors.bold + 'Additional options:' + colors.reset);
|
|
387
|
+
console.error('• Check system proxy settings:');
|
|
388
|
+
if (!isWindows) {
|
|
389
|
+
console.error(' macOS: System Settings → Network → Advanced → Proxies');
|
|
390
|
+
} else {
|
|
391
|
+
console.error(' Windows: Settings → Network & Internet → Proxy');
|
|
392
|
+
}
|
|
393
|
+
console.error('• Try manual installation (see TROUBLESHOOTING.md)');
|
|
394
|
+
|
|
395
|
+
} else {
|
|
396
|
+
console.error('\n💡 No proxy environment variables detected.\n');
|
|
397
|
+
console.error('POSSIBLE CAUSES:');
|
|
398
|
+
console.error('• Network connectivity issues');
|
|
399
|
+
console.error('• Firewall blocking PyPI access');
|
|
400
|
+
console.error('• DNS resolution problems');
|
|
401
|
+
console.error('• System-level proxy (not in environment variables)\n');
|
|
402
|
+
|
|
403
|
+
console.error('SOLUTIONS TO TRY:');
|
|
404
|
+
console.error('1. Check your internet connection');
|
|
405
|
+
console.error('2. Try: curl https://pypi.org/simple/');
|
|
406
|
+
console.error('3. Check firewall/antivirus settings');
|
|
407
|
+
console.error('4. Check system proxy settings (not environment variables)\n');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.error('='.repeat(70) + '\n');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Install Python dependencies with smart proxy detection
|
|
161
415
|
* @param {string} venvPath - Path to virtual environment
|
|
162
416
|
*/
|
|
163
417
|
function installDependencies(venvPath) {
|
|
@@ -171,11 +425,10 @@ function installDependencies(venvPath) {
|
|
|
171
425
|
if (fs.existsSync(wheelsPath)) {
|
|
172
426
|
console.error('Using pre-packaged wheels (no internet required)...');
|
|
173
427
|
|
|
174
|
-
// Install from local wheels (fast, no network needed)
|
|
175
428
|
const installReqs = spawnSync(pip, [
|
|
176
429
|
'install',
|
|
177
|
-
'--no-index',
|
|
178
|
-
'--find-links', wheelsPath,
|
|
430
|
+
'--no-index',
|
|
431
|
+
'--find-links', wheelsPath,
|
|
179
432
|
'-r', requirementsPath
|
|
180
433
|
], {
|
|
181
434
|
cwd: projectRoot,
|
|
@@ -186,38 +439,111 @@ function installDependencies(venvPath) {
|
|
|
186
439
|
error('Failed to install from wheels');
|
|
187
440
|
process.exit(1);
|
|
188
441
|
}
|
|
442
|
+
|
|
443
|
+
log('✓ Requirements installed', 'green');
|
|
189
444
|
} else {
|
|
190
|
-
//
|
|
445
|
+
// Need network - detect proxy configuration intelligently
|
|
191
446
|
console.error('Downloading from PyPI...');
|
|
447
|
+
|
|
448
|
+
const activeProxies = detectProxyConfig();
|
|
449
|
+
const hasProxyVars = Object.keys(activeProxies).length > 0;
|
|
450
|
+
|
|
451
|
+
let proxyToUse = null;
|
|
452
|
+
let useNoProxy = false;
|
|
453
|
+
|
|
454
|
+
if (hasProxyVars) {
|
|
455
|
+
const proxyUrl = process.env.HTTP_PROXY || process.env.http_proxy ||
|
|
456
|
+
process.env.HTTPS_PROXY || process.env.https_proxy;
|
|
457
|
+
|
|
458
|
+
info('Proxy environment variables detected. Testing connectivity...');
|
|
459
|
+
|
|
460
|
+
// Test proxy connectivity
|
|
461
|
+
const proxyWorks = proxyUrl && proxyUrl.startsWith('http') && testProxyConnectivity(proxyUrl);
|
|
462
|
+
const directWorks = testDirectPyPIAccess();
|
|
463
|
+
|
|
464
|
+
if (proxyWorks && !directWorks) {
|
|
465
|
+
// Corporate network - use proxy
|
|
466
|
+
info('Corporate network detected. Using proxy.');
|
|
467
|
+
proxyToUse = proxyUrl;
|
|
468
|
+
} else if (!proxyWorks && directWorks) {
|
|
469
|
+
// Public network with stale proxy vars - ignore proxy
|
|
470
|
+
warning('Proxy unreachable but direct access available. Ignoring proxy settings.');
|
|
471
|
+
useNoProxy = true;
|
|
472
|
+
} else if (proxyWorks && directWorks) {
|
|
473
|
+
// Both work - prefer direct
|
|
474
|
+
info('Both proxy and direct access available. Using direct connection.');
|
|
475
|
+
useNoProxy = true;
|
|
476
|
+
} else {
|
|
477
|
+
// Neither works - will fail but try direct
|
|
478
|
+
warning('Neither proxy nor direct access working. Attempting direct connection...');
|
|
479
|
+
useNoProxy = true;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Build pip arguments
|
|
192
484
|
const pipArgs = ['install', '-r', requirementsPath];
|
|
193
485
|
|
|
194
|
-
|
|
486
|
+
// Trusted hosts for SSL-friendly installation
|
|
487
|
+
const trustedHostArgs = [
|
|
195
488
|
'--trusted-host', 'pypi.org',
|
|
196
489
|
'--trusted-host', 'pypi.python.org',
|
|
197
490
|
'--trusted-host', 'files.pythonhosted.org'
|
|
198
491
|
];
|
|
492
|
+
pipArgs.push(...trustedHostArgs);
|
|
493
|
+
|
|
494
|
+
// Add proxy if needed
|
|
495
|
+
if (proxyToUse && !useNoProxy) {
|
|
496
|
+
info(`Using proxy: ${proxyToUse}`);
|
|
497
|
+
pipArgs.push('--proxy', proxyToUse);
|
|
498
|
+
}
|
|
199
499
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
500
|
+
// Set up environment (potentially removing proxy vars)
|
|
501
|
+
const pipEnv = { ...process.env };
|
|
502
|
+
if (useNoProxy) {
|
|
503
|
+
// Remove proxy environment variables for this pip call
|
|
504
|
+
delete pipEnv.HTTP_PROXY;
|
|
505
|
+
delete pipEnv.HTTPS_PROXY;
|
|
506
|
+
delete pipEnv.http_proxy;
|
|
507
|
+
delete pipEnv.https_proxy;
|
|
508
|
+
delete pipEnv.ALL_PROXY;
|
|
509
|
+
delete pipEnv.all_proxy;
|
|
203
510
|
}
|
|
204
|
-
pipArgs.push(...proxyFriendlyArgs);
|
|
205
511
|
|
|
206
512
|
const installReqs = spawnSync(pip, pipArgs, {
|
|
207
513
|
cwd: projectRoot,
|
|
208
|
-
stdio: 'inherit'
|
|
514
|
+
stdio: 'inherit',
|
|
515
|
+
env: pipEnv
|
|
209
516
|
});
|
|
210
517
|
|
|
211
518
|
if (installReqs.status !== 0) {
|
|
212
|
-
error('Failed to install dependencies');
|
|
519
|
+
error('Failed to install dependencies from PyPI');
|
|
520
|
+
|
|
521
|
+
// Show intelligent troubleshooting
|
|
522
|
+
const proxyUrl = process.env.HTTP_PROXY || process.env.http_proxy;
|
|
523
|
+
const proxyWorks = proxyUrl ? testProxyConnectivity(proxyUrl) : false;
|
|
524
|
+
const directWorks = testDirectPyPIAccess();
|
|
525
|
+
|
|
526
|
+
showProxyTroubleshooting(activeProxies, directWorks, proxyWorks);
|
|
213
527
|
process.exit(1);
|
|
214
528
|
}
|
|
215
|
-
}
|
|
216
529
|
|
|
217
|
-
|
|
530
|
+
log('✓ Requirements installed', 'green');
|
|
531
|
+
}
|
|
218
532
|
|
|
219
533
|
// Install package itself
|
|
220
534
|
console.error('Installing jenkins-mcp-server package...');
|
|
535
|
+
|
|
536
|
+
// Verify pyproject.toml exists
|
|
537
|
+
const pyprojectPath = path.join(projectRoot, 'pyproject.toml');
|
|
538
|
+
if (!fs.existsSync(pyprojectPath)) {
|
|
539
|
+
error('pyproject.toml not found in project root');
|
|
540
|
+
console.error('\nProject root:', projectRoot);
|
|
541
|
+
console.error('Expected file:', pyprojectPath);
|
|
542
|
+
console.error('\nThis may be due to npx cache issues with special characters.');
|
|
543
|
+
console.error('Try clearing npx cache: npx clear-npx-cache');
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
|
|
221
547
|
const packageArgs = ['install', '-e', '.'];
|
|
222
548
|
|
|
223
549
|
const installPkg = spawnSync(pip, packageArgs, {
|
|
@@ -227,10 +553,24 @@ function installDependencies(venvPath) {
|
|
|
227
553
|
|
|
228
554
|
if (installPkg.status !== 0) {
|
|
229
555
|
error('Failed to install package');
|
|
556
|
+
console.error('\nProject root:', projectRoot);
|
|
557
|
+
console.error('Files in project root:');
|
|
558
|
+
try {
|
|
559
|
+
const files = fs.readdirSync(projectRoot);
|
|
560
|
+
console.error(files.join(', '));
|
|
561
|
+
} catch (e) {
|
|
562
|
+
console.error('Could not list files');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.error('\n💡 TROUBLESHOOTING:');
|
|
566
|
+
console.error('1. The npx cache may have issues with the @ symbol in package name');
|
|
567
|
+
console.error('2. Try: npx clear-npx-cache && rm -rf ~/.npm/_npx');
|
|
568
|
+
console.error('3. Or install globally: npm install -g @rishibhushan/jenkins-mcp-server');
|
|
569
|
+
console.error(' Then run: jenkins-mcp-server --env-file /path/to/.env');
|
|
230
570
|
process.exit(1);
|
|
231
571
|
}
|
|
232
572
|
|
|
233
|
-
|
|
573
|
+
log('✓ Package installed successfully', 'green');
|
|
234
574
|
}
|
|
235
575
|
|
|
236
576
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rishibhushan/jenkins-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "AI-enabled Jenkins automation via Model Context Protocol (MCP)",
|
|
5
5
|
"main": "bin/jenkins-mcp.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/jenkins-mcp.js",
|
|
11
11
|
"test": "python -m pytest tests/",
|
|
12
|
-
"build": "python -m build"
|
|
12
|
+
"build": "python -m build",
|
|
13
|
+
"prepack": "npm run clean",
|
|
14
|
+
"clean": "find . -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true; find . -type d -name '*.egg-info' -exec rm -rf {} + 2>/dev/null || true"
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
15
17
|
"bin/",
|
|
16
|
-
"src
|
|
18
|
+
"src/**/*.py",
|
|
19
|
+
"pyproject.toml",
|
|
17
20
|
"requirements.txt",
|
|
18
|
-
"wheels/",
|
|
19
21
|
"README.md",
|
|
20
22
|
"LICENSE"
|
|
21
23
|
],
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jenkins-mcp-server"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "AI-enabled Jenkins automation via Model Context Protocol (MCP)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Rishi Bhushan", email = "rishibharat2007@gmail.com"}
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{name = "Rishi Bhushan", email = "rishibharat2007@example.com"}
|
|
17
|
+
]
|
|
18
|
+
keywords = [
|
|
19
|
+
"mcp",
|
|
20
|
+
"jenkins",
|
|
21
|
+
"ai",
|
|
22
|
+
"automation",
|
|
23
|
+
"ci-cd",
|
|
24
|
+
"devops",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"llm"
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Intended Audience :: System Administrators",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.8",
|
|
36
|
+
"Programming Language :: Python :: 3.9",
|
|
37
|
+
"Programming Language :: Python :: 3.10",
|
|
38
|
+
"Programming Language :: Python :: 3.11",
|
|
39
|
+
"Programming Language :: Python :: 3.12",
|
|
40
|
+
"Topic :: Software Development :: Build Tools",
|
|
41
|
+
"Topic :: System :: Systems Administration",
|
|
42
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
dependencies = [
|
|
46
|
+
"mcp>=1.0.0",
|
|
47
|
+
"python-jenkins>=1.8.0",
|
|
48
|
+
"requests>=2.28.0",
|
|
49
|
+
"pydantic>=2.0.0",
|
|
50
|
+
"pydantic-settings>=2.0.0",
|
|
51
|
+
"python-dotenv>=1.0.0",
|
|
52
|
+
"urllib3>=2.0.0"
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.optional-dependencies]
|
|
56
|
+
dev = [
|
|
57
|
+
"pytest>=7.0.0",
|
|
58
|
+
"pytest-asyncio>=0.21.0",
|
|
59
|
+
"pytest-cov>=4.0.0",
|
|
60
|
+
"black>=23.0.0",
|
|
61
|
+
"ruff>=0.1.0",
|
|
62
|
+
"mypy>=1.0.0"
|
|
63
|
+
]
|
|
64
|
+
test = [
|
|
65
|
+
"pytest>=7.0.0",
|
|
66
|
+
"pytest-asyncio>=0.21.0",
|
|
67
|
+
"pytest-cov>=4.0.0"
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[project.urls]
|
|
71
|
+
Homepage = "https://github.com/rishibhushan/jenkins_mcp_server"
|
|
72
|
+
Documentation = "https://github.com/rishibhushan/jenkins_mcp_server#readme"
|
|
73
|
+
Repository = "https://github.com/rishibhushan/jenkins_mcp_server.git"
|
|
74
|
+
"Bug Tracker" = "https://github.com/rishibhushan/jenkins_mcp_server/issues"
|
|
75
|
+
Changelog = "https://github.com/rishibhushan/jenkins_mcp_server/releases"
|
|
76
|
+
|
|
77
|
+
[project.scripts]
|
|
78
|
+
jenkins-mcp-server = "jenkins_mcp_server:main"
|
|
79
|
+
|
|
80
|
+
[tool.setuptools]
|
|
81
|
+
package-dir = {"" = "src"}
|
|
82
|
+
|
|
83
|
+
[tool.setuptools.packages.find]
|
|
84
|
+
where = ["src"]
|
|
85
|
+
include = ["jenkins_mcp_server*"]
|
|
86
|
+
exclude = ["tests*"]
|
|
87
|
+
|
|
88
|
+
[tool.setuptools.package-data]
|
|
89
|
+
jenkins_mcp_server = ["py.typed"]
|
|
90
|
+
|
|
91
|
+
# Black configuration
|
|
92
|
+
[tool.black]
|
|
93
|
+
line-length = 100
|
|
94
|
+
target-version = ['py38', 'py39', 'py310', 'py311']
|
|
95
|
+
include = '\.pyi?$'
|
|
96
|
+
extend-exclude = '''
|
|
97
|
+
/(
|
|
98
|
+
# directories
|
|
99
|
+
\.eggs
|
|
100
|
+
| \.git
|
|
101
|
+
| \.hg
|
|
102
|
+
| \.mypy_cache
|
|
103
|
+
| \.tox
|
|
104
|
+
| \.venv
|
|
105
|
+
| build
|
|
106
|
+
| dist
|
|
107
|
+
)/
|
|
108
|
+
'''
|
|
109
|
+
|
|
110
|
+
# Ruff configuration (fast linter)
|
|
111
|
+
[tool.ruff]
|
|
112
|
+
line-length = 100
|
|
113
|
+
target-version = "py38"
|
|
114
|
+
select = [
|
|
115
|
+
"E", # pycodestyle errors
|
|
116
|
+
"W", # pycodestyle warnings
|
|
117
|
+
"F", # pyflakes
|
|
118
|
+
"I", # isort
|
|
119
|
+
"B", # flake8-bugbear
|
|
120
|
+
"C4", # flake8-comprehensions
|
|
121
|
+
"UP", # pyupgrade
|
|
122
|
+
]
|
|
123
|
+
ignore = [
|
|
124
|
+
"E501", # line too long (handled by black)
|
|
125
|
+
"B008", # do not perform function calls in argument defaults
|
|
126
|
+
"C901", # too complex
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[tool.ruff.per-file-ignores]
|
|
130
|
+
"__init__.py" = ["F401"] # unused imports in __init__.py
|
|
131
|
+
|
|
132
|
+
# MyPy configuration
|
|
133
|
+
[tool.mypy]
|
|
134
|
+
python_version = "3.8"
|
|
135
|
+
warn_return_any = true
|
|
136
|
+
warn_unused_configs = true
|
|
137
|
+
disallow_untyped_defs = false
|
|
138
|
+
disallow_incomplete_defs = false
|
|
139
|
+
check_untyped_defs = true
|
|
140
|
+
no_implicit_optional = true
|
|
141
|
+
warn_redundant_casts = true
|
|
142
|
+
warn_unused_ignores = true
|
|
143
|
+
warn_no_return = true
|
|
144
|
+
strict_equality = true
|
|
145
|
+
|
|
146
|
+
[[tool.mypy.overrides]]
|
|
147
|
+
module = [
|
|
148
|
+
"jenkins",
|
|
149
|
+
"mcp.*"
|
|
150
|
+
]
|
|
151
|
+
ignore_missing_imports = true
|
|
152
|
+
|
|
153
|
+
# Pytest configuration
|
|
154
|
+
[tool.pytest.ini_options]
|
|
155
|
+
minversion = "7.0"
|
|
156
|
+
addopts = [
|
|
157
|
+
"-ra",
|
|
158
|
+
"--strict-markers",
|
|
159
|
+
"--strict-config",
|
|
160
|
+
"--showlocals",
|
|
161
|
+
"--tb=short"
|
|
162
|
+
]
|
|
163
|
+
testpaths = ["tests"]
|
|
164
|
+
pythonpath = ["src"]
|
|
165
|
+
asyncio_mode = "auto"
|
|
166
|
+
|
|
167
|
+
# Coverage configuration
|
|
168
|
+
[tool.coverage.run]
|
|
169
|
+
source = ["src"]
|
|
170
|
+
branch = true
|
|
171
|
+
omit = [
|
|
172
|
+
"*/tests/*",
|
|
173
|
+
"*/__pycache__/*",
|
|
174
|
+
"*/.venv/*"
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
[tool.coverage.report]
|
|
178
|
+
precision = 2
|
|
179
|
+
show_missing = true
|
|
180
|
+
skip_covered = false
|
|
181
|
+
exclude_lines = [
|
|
182
|
+
"pragma: no cover",
|
|
183
|
+
"def __repr__",
|
|
184
|
+
"raise AssertionError",
|
|
185
|
+
"raise NotImplementedError",
|
|
186
|
+
"if __name__ == .__main__.:",
|
|
187
|
+
"if TYPE_CHECKING:",
|
|
188
|
+
"class .*\\bProtocol\\):",
|
|
189
|
+
"@(abc\\.)?abstractmethod"
|
|
190
|
+
]
|