@rishibhushan/jenkins-mcp-server 1.1.16 → 1.1.18
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/README.md +104 -20
- package/bin/jenkins-mcp.js +35 -13
- package/package.json +2 -1
- package/src/jenkins_mcp_server/__init__.py +17 -16
- package/src/jenkins_mcp_server/config.py +10 -12
- package/src/jenkins_mcp_server/server.py +62 -15
- package/src/jenkins_mcp_server/verbose.py +19 -0
- package/src/jenkins_mcp_server/version.py +33 -0
package/README.md
CHANGED
|
@@ -7,9 +7,15 @@ Designed to work seamlessly with automation clients such as:
|
|
|
7
7
|
- 🖥️ **Claude Desktop** - AI-powered Jenkins automation
|
|
8
8
|
- 🔌 **Any MCP-compatible client** - Universal compatibility
|
|
9
9
|
|
|
10
|
-
## ✨ What's New in v1.1.
|
|
10
|
+
## ✨ What's New in v1.1.16
|
|
11
11
|
|
|
12
|
-
### 🚀
|
|
12
|
+
### 🚀 Latest Improvements (v1.1.16)
|
|
13
|
+
- ⚡ **Instant startup** - Server initialization in <1 second (previously 30-60s)
|
|
14
|
+
- 🔧 **Simplified architecture** - Streamlined Node.js wrapper for better reliability
|
|
15
|
+
- 🌐 **Universal compatibility** - Works seamlessly with VSCode, Claude Desktop, and all MCP clients
|
|
16
|
+
- 🐛 **Critical fixes** - Resolved initialization timeouts and network blocking issues
|
|
17
|
+
|
|
18
|
+
### 🚀 Performance Enhancements (v1.1.0)
|
|
13
19
|
- ⚡ **10x faster** - Client connection caching for repeated operations
|
|
14
20
|
- 📊 **Smart caching** - Job list caching with 30-60s TTL (5-10x improvement)
|
|
15
21
|
- 🎯 **Optimized queries** - Reduced API calls by 33-83%
|
|
@@ -252,6 +258,15 @@ npx @rishibhushan/jenkins-mcp-server --env-file .env
|
|
|
252
258
|
# OR if .env file is located in /path/to/.env
|
|
253
259
|
npx @rishibhushan/jenkins-mcp-server --env-file /path/to/.env
|
|
254
260
|
```
|
|
261
|
+
**NPX Usage:**
|
|
262
|
+
- Always use `--yes` flag with npx for automatic package installation
|
|
263
|
+
- First run may take 10-20 seconds to set up Python environment
|
|
264
|
+
- Subsequent runs are instant (environment is cached)
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
````bash
|
|
268
|
+
npx --yes @rishibhushan/jenkins-mcp-server --env-file .env
|
|
269
|
+
````
|
|
255
270
|
|
|
256
271
|
### Option 2: Global Installation
|
|
257
272
|
```bash
|
|
@@ -267,12 +282,12 @@ jenkins-mcp-server --env-file .env
|
|
|
267
282
|
npx github:rishibhushan/jenkins_mcp_server --env-file .env
|
|
268
283
|
```
|
|
269
284
|
|
|
270
|
-
---
|
|
271
|
-
|
|
272
285
|
This automatically:
|
|
273
286
|
- ✅ Installs all dependencies
|
|
274
287
|
- ✅ Starts the Jenkins MCP server
|
|
275
288
|
|
|
289
|
+
---
|
|
290
|
+
|
|
276
291
|
### Method 2: Direct Python Execution
|
|
277
292
|
|
|
278
293
|
```bash
|
|
@@ -307,11 +322,13 @@ Options:
|
|
|
307
322
|
|
|
308
323
|
---
|
|
309
324
|
|
|
310
|
-
## 🔌 Integration
|
|
325
|
+
## 🔌 Integration with VSCode
|
|
326
|
+
|
|
327
|
+
1. **Install VSCode MCP Extension** (if not already installed)
|
|
311
328
|
|
|
312
|
-
|
|
329
|
+
2. **Configure the server** in your workspace or user settings:
|
|
313
330
|
|
|
314
|
-
|
|
331
|
+
**Option A: Using `mcp.json` (Recommended)**
|
|
315
332
|
|
|
316
333
|
```json
|
|
317
334
|
{
|
|
@@ -327,7 +344,9 @@ Add to your VS Code `mcp.json`:
|
|
|
327
344
|
}
|
|
328
345
|
```
|
|
329
346
|
|
|
330
|
-
|
|
347
|
+
**Option B: Using settings.json**
|
|
348
|
+
|
|
349
|
+
Add to your VSCode `settings.json`. With `.env` file, `--verbose`, and proxy settings:
|
|
331
350
|
```json
|
|
332
351
|
{
|
|
333
352
|
"mcp": {
|
|
@@ -339,7 +358,7 @@ Or `setting.json` with `.env` file and proxy settings:
|
|
|
339
358
|
"@rishibhushan/jenkins-mcp-server",
|
|
340
359
|
"--verbose",
|
|
341
360
|
"--env-file",
|
|
342
|
-
"/path/to/.env"
|
|
361
|
+
"/absolute/path/to/your/.env"
|
|
343
362
|
],
|
|
344
363
|
"env": {
|
|
345
364
|
"HTTP_PROXY": "http://proxy.example.com:8080",
|
|
@@ -351,11 +370,36 @@ Or `setting.json` with `.env` file and proxy settings:
|
|
|
351
370
|
}
|
|
352
371
|
}
|
|
353
372
|
```
|
|
373
|
+
**Important:**
|
|
374
|
+
- Always use **absolute paths** for the `--env-file` parameter
|
|
375
|
+
- Restart VSCode completely after configuration changes
|
|
376
|
+
- Check Output → Model Context Protocol for connection status
|
|
354
377
|
|
|
355
|
-
|
|
378
|
+
3. **Verify connection:**
|
|
379
|
+
- Open Command Palette (Cmd/Ctrl+Shift+P)
|
|
380
|
+
- Type "MCP" to see available commands
|
|
381
|
+
- Should see "Discovered 26 tools" in Output panel
|
|
356
382
|
|
|
357
|
-
|
|
383
|
+
#### Troubleshooting VSCode Connection
|
|
384
|
+
|
|
385
|
+
If VSCode shows "Waiting for initialize response":
|
|
386
|
+
1. Ensure `.env` file path is **absolute**, not relative
|
|
387
|
+
2. Verify `.env` file has correct Jenkins credentials
|
|
388
|
+
3. Restart VSCode completely (quit and reopen)
|
|
389
|
+
4. Check Output → Model Context Protocol for error messages
|
|
390
|
+
5. Try running manually first: `npx --yes @rishibhushan/jenkins-mcp-server --env-file /path/to/.env`
|
|
391
|
+
|
|
392
|
+
## 🤖 Integration with Claude Desktop
|
|
358
393
|
|
|
394
|
+
1. **Locate Claude Desktop config:**
|
|
395
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
396
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
397
|
+
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
2. **Add server configuration:**
|
|
401
|
+
|
|
402
|
+
Add to `claude_desktop_config.json`:
|
|
359
403
|
```json
|
|
360
404
|
{
|
|
361
405
|
"mcpServers": {
|
|
@@ -364,20 +408,28 @@ Add to `claude_desktop_config.json`:
|
|
|
364
408
|
"args": [
|
|
365
409
|
"@rishibhushan/jenkins-mcp-server",
|
|
366
410
|
"--env-file",
|
|
367
|
-
"/path/to/.env"
|
|
411
|
+
"/absolute/path/to/your/.env"
|
|
368
412
|
]
|
|
369
413
|
}
|
|
370
414
|
}
|
|
371
415
|
}
|
|
372
416
|
```
|
|
373
417
|
|
|
374
|
-
**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
-
|
|
418
|
+
3. **Restart Claude Desktop** completely
|
|
419
|
+
|
|
420
|
+
4. **Verify:**
|
|
421
|
+
- You should see a 🔌 icon in Claude's input area
|
|
422
|
+
- Click it to see "jenkins" server with 26 tools
|
|
423
|
+
|
|
424
|
+
#### Troubleshooting Claude Desktop
|
|
425
|
+
Common issues:
|
|
426
|
+
- **Server disconnects after 60s:** Make sure you're on VPN if Jenkins requires it
|
|
427
|
+
- **No tools appear:** Check the config file path and restart Claude Desktop
|
|
428
|
+
- **Connection timeout:** Verify Jenkins URL is accessible from your network
|
|
378
429
|
|
|
379
430
|
---
|
|
380
431
|
|
|
432
|
+
|
|
381
433
|
## 🏢 Corporate Network / Proxy Setup
|
|
382
434
|
|
|
383
435
|
If you're behind a corporate proxy or firewall:
|
|
@@ -501,6 +553,36 @@ if result['build_number']:
|
|
|
501
553
|
|
|
502
554
|
---
|
|
503
555
|
|
|
556
|
+
## 🔍 Quick Troubleshooting
|
|
557
|
+
|
|
558
|
+
### Server won't start
|
|
559
|
+
```bash
|
|
560
|
+
# Test if server works manually
|
|
561
|
+
npx --yes @rishibhushan/jenkins-mcp-server --env-file /path/to/.env
|
|
562
|
+
|
|
563
|
+
# Should start and wait for input (Ctrl+C to exit)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### VSCode/Claude Desktop can't connect
|
|
567
|
+
- ✅ Use **absolute paths** for `--env-file`
|
|
568
|
+
- ✅ Include `--yes` flag with npx
|
|
569
|
+
- ✅ Restart application completely after config changes
|
|
570
|
+
- ✅ Check if Jenkins is accessible (try in browser)
|
|
571
|
+
- ✅ Verify `.env` file has correct credentials
|
|
572
|
+
|
|
573
|
+
### Server connects but times out
|
|
574
|
+
- ✅ Connect to VPN if Jenkins is on private network
|
|
575
|
+
- ✅ Check firewall settings
|
|
576
|
+
- ✅ Verify Jenkins URL is correct
|
|
577
|
+
- ✅ Test with: `curl -I http://your-jenkins-url:8080`
|
|
578
|
+
|
|
579
|
+
### "Module not found" errors
|
|
580
|
+
- ✅ Delete `.venv` folder and let it reinstall
|
|
581
|
+
- ✅ Clear npm cache: `npm cache clean --force`
|
|
582
|
+
- ✅ Try with latest version: `npx --yes @rishibhushan/jenkins-mcp-server@latest`
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
504
586
|
## 🔧 Troubleshooting
|
|
505
587
|
|
|
506
588
|
### Common Issues
|
|
@@ -1133,17 +1215,19 @@ jenkins-mcp-server --verbose
|
|
|
1133
1215
|
```
|
|
1134
1216
|
jenkins_mcp_server/
|
|
1135
1217
|
├── bin/
|
|
1136
|
-
│ └── jenkins-mcp.js # Node.js wrapper
|
|
1218
|
+
│ └── jenkins-mcp.js # Simplified Node.js wrapper (v1.1.16+)
|
|
1137
1219
|
├── src/
|
|
1138
1220
|
│ └── jenkins_mcp_server/
|
|
1139
1221
|
│ ├── __init__.py # Package initialization & main()
|
|
1140
1222
|
│ ├── __main__.py # Entry point for python -m
|
|
1141
1223
|
│ ├── config.py # Configuration management
|
|
1142
|
-
│ ├── jenkins_client.py # Jenkins API client
|
|
1143
|
-
│
|
|
1224
|
+
│ ├── jenkins_client.py # Jenkins API client (with caching)
|
|
1225
|
+
│ ├── cache.py # Smart caching layer
|
|
1226
|
+
│ ├── metrics.py # Performance telemetry
|
|
1227
|
+
│ └── server.py # MCP server implementation (26 tools)
|
|
1144
1228
|
├── tests/ # Test suite
|
|
1145
1229
|
├── requirements.txt # Python dependencies
|
|
1146
|
-
├── package.json # Node.js configuration
|
|
1230
|
+
├── package.json # Node.js configuration (ES modules)
|
|
1147
1231
|
└── README.md # This file
|
|
1148
1232
|
```
|
|
1149
1233
|
|
package/bin/jenkins-mcp.js
CHANGED
|
@@ -13,12 +13,14 @@ const IS_WINDOWS = process.platform === "win32";
|
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
16
|
+
const isVerbose = process.argv.includes('--verbose');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Debug (stderr only – safe for MCP)
|
|
19
20
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
if (isVerbose) {
|
|
22
|
+
console.error("[jenkins-mcp] package root:", PACKAGE_ROOT);
|
|
23
|
+
}
|
|
22
24
|
/**
|
|
23
25
|
* Resolve Python executable inside packaged venv
|
|
24
26
|
*/
|
|
@@ -30,15 +32,21 @@ function run(cmd, args, cwd) {
|
|
|
30
32
|
return new Promise((resolve, reject) => {
|
|
31
33
|
const p = spawn(cmd, args, {
|
|
32
34
|
cwd,
|
|
33
|
-
stdio: ["ignore", "pipe", "pipe"], //
|
|
35
|
+
stdio: ["ignore", "pipe", "pipe"], // Always pipe stdout/stderr
|
|
34
36
|
});
|
|
35
37
|
|
|
38
|
+
// Handle stdout
|
|
36
39
|
p.stdout.on("data", (d) => {
|
|
37
|
-
|
|
40
|
+
if (isVerbose) {
|
|
41
|
+
console.error(d.toString()); // Only show if verbose
|
|
42
|
+
}
|
|
38
43
|
});
|
|
39
44
|
|
|
45
|
+
// Handle stderr
|
|
40
46
|
p.stderr.on("data", (d) => {
|
|
41
|
-
|
|
47
|
+
if (isVerbose) {
|
|
48
|
+
console.error(d.toString()); // Only show if verbose
|
|
49
|
+
}
|
|
42
50
|
});
|
|
43
51
|
|
|
44
52
|
p.on("exit", (code) =>
|
|
@@ -49,11 +57,15 @@ function run(cmd, args, cwd) {
|
|
|
49
57
|
|
|
50
58
|
async function ensureVenv() {
|
|
51
59
|
if (!fs.existsSync(pythonPath)) {
|
|
52
|
-
|
|
60
|
+
if (isVerbose) {
|
|
61
|
+
console.error("[jenkins-mcp] Python venv not found, creating...");
|
|
62
|
+
}
|
|
53
63
|
const venvPython = IS_WINDOWS ? "python" : "python3";
|
|
54
64
|
await run(venvPython, ["-m", "venv", ".venv"], PACKAGE_ROOT);
|
|
55
65
|
} else {
|
|
56
|
-
|
|
66
|
+
if (isVerbose) {
|
|
67
|
+
console.error("[jenkins-mcp] Python venv exists, ensuring dependencies...");
|
|
68
|
+
}
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
const pipArgs = ["-m", "pip", "install", "-r", "requirements.txt"];
|
|
@@ -73,14 +85,18 @@ async function ensureVenv() {
|
|
|
73
85
|
|
|
74
86
|
await run(pythonPath, pipArgs, PACKAGE_ROOT);
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
if (isVerbose) {
|
|
89
|
+
console.error("[jenkins-mcp] Python environment ready");
|
|
90
|
+
}
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
(async () => {
|
|
80
94
|
try {
|
|
81
95
|
await ensureVenv();
|
|
82
96
|
} catch (err) {
|
|
83
|
-
|
|
97
|
+
if (isVerbose) {
|
|
98
|
+
console.error("[jenkins-mcp] Bootstrap failed:", err);
|
|
99
|
+
}
|
|
84
100
|
process.exit(1);
|
|
85
101
|
}
|
|
86
102
|
|
|
@@ -99,8 +115,10 @@ async function ensureVenv() {
|
|
|
99
115
|
...args,
|
|
100
116
|
];
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
if (isVerbose) {
|
|
119
|
+
console.error("[jenkins-mcp] Python:", pythonPath);
|
|
120
|
+
console.error("[jenkins-mcp] Args:", pythonArgs.join(" "));
|
|
121
|
+
}
|
|
104
122
|
|
|
105
123
|
/**
|
|
106
124
|
* Spawn Python MCP server
|
|
@@ -119,14 +137,18 @@ async function ensureVenv() {
|
|
|
119
137
|
*/
|
|
120
138
|
child.on("exit", (code, signal) => {
|
|
121
139
|
if (signal) {
|
|
122
|
-
|
|
140
|
+
if (isVerbose) {
|
|
141
|
+
console.error("[jenkins-mcp] exited due to signal:", signal);
|
|
142
|
+
}
|
|
123
143
|
process.exit(1);
|
|
124
144
|
}
|
|
125
145
|
process.exit(code ?? 0);
|
|
126
146
|
});
|
|
127
147
|
|
|
128
148
|
child.on("error", (err) => {
|
|
129
|
-
|
|
149
|
+
if (isVerbose) {
|
|
150
|
+
console.error("[jenkins-mcp] failed to start:", err);
|
|
151
|
+
}
|
|
130
152
|
process.exit(1);
|
|
131
153
|
});
|
|
132
154
|
})();
|
package/package.json
CHANGED
|
@@ -10,6 +10,8 @@ import logging
|
|
|
10
10
|
import sys
|
|
11
11
|
|
|
12
12
|
from .config import get_settings, get_default_settings
|
|
13
|
+
from .verbose import set_verbose, vprint
|
|
14
|
+
from .version import __version__
|
|
13
15
|
from . import server
|
|
14
16
|
|
|
15
17
|
|
|
@@ -43,7 +45,6 @@ def main():
|
|
|
43
45
|
Parses command-line arguments, configures settings, and starts the server.
|
|
44
46
|
"""
|
|
45
47
|
|
|
46
|
-
print("=== Jenkins MCP Server: Entry point called ===", file=sys.stderr, flush=True)
|
|
47
48
|
parser = argparse.ArgumentParser(
|
|
48
49
|
description='Jenkins MCP Server - AI-enabled Jenkins automation',
|
|
49
50
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -83,7 +84,7 @@ Configuration Priority:
|
|
|
83
84
|
parser.add_argument(
|
|
84
85
|
'--version',
|
|
85
86
|
action='version',
|
|
86
|
-
version='jenkins-mcp-server
|
|
87
|
+
version=f'jenkins-mcp-server {__version__}'
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
parser.add_argument(
|
|
@@ -92,26 +93,27 @@ Configuration Priority:
|
|
|
92
93
|
help='Skip loading settings from VS Code (use only .env/environment)'
|
|
93
94
|
)
|
|
94
95
|
|
|
95
|
-
print("=== Parsing arguments ===", file=sys.stderr, flush=True)
|
|
96
96
|
args = parser.parse_args()
|
|
97
|
-
|
|
97
|
+
set_verbose(args.verbose)
|
|
98
|
+
|
|
99
|
+
vprint(f"=== Args parsed: env_file={args.env_file}, verbose={args.verbose} ===")
|
|
98
100
|
|
|
99
101
|
# Setup logging first
|
|
100
|
-
|
|
102
|
+
vprint("=== Setting up logging ===")
|
|
101
103
|
setup_logging(args.verbose)
|
|
102
104
|
logger = logging.getLogger(__name__)
|
|
103
|
-
|
|
105
|
+
vprint("=== Logging configured ===")
|
|
104
106
|
|
|
105
107
|
try:
|
|
106
108
|
# Load settings based on arguments
|
|
107
|
-
|
|
109
|
+
vprint("=== Loading Jenkins configuration ===")
|
|
108
110
|
logger.info("Loading Jenkins configuration...")
|
|
109
111
|
|
|
110
112
|
settings = get_settings(
|
|
111
113
|
env_file=args.env_file,
|
|
112
114
|
load_vscode=not args.no_vscode
|
|
113
115
|
)
|
|
114
|
-
|
|
116
|
+
vprint(f"=== Settings loaded: url={settings.url}, configured={settings.is_configured} ===")
|
|
115
117
|
|
|
116
118
|
# Validate configuration
|
|
117
119
|
if not settings.is_configured:
|
|
@@ -125,27 +127,27 @@ Configuration Priority:
|
|
|
125
127
|
sys.exit(1)
|
|
126
128
|
|
|
127
129
|
# Log configuration summary
|
|
128
|
-
|
|
130
|
+
vprint("=== Configuration validated ===")
|
|
129
131
|
logger.info(f"Jenkins server: {settings.url}")
|
|
130
132
|
logger.info(f"Username: {settings.username}")
|
|
131
133
|
logger.info(f"Authentication: {settings.auth_method}")
|
|
132
134
|
|
|
133
135
|
# Pass settings to server module
|
|
134
|
-
|
|
136
|
+
vprint("=== Setting server settings ===")
|
|
135
137
|
server.set_jenkins_settings(settings)
|
|
136
|
-
|
|
138
|
+
vprint("=== Server settings configured ===")
|
|
137
139
|
|
|
138
140
|
# Run the server
|
|
139
|
-
|
|
141
|
+
vprint("=== Starting asyncio server ===")
|
|
140
142
|
logger.info("Starting Jenkins MCP Server...")
|
|
141
143
|
asyncio.run(server.main())
|
|
142
144
|
|
|
143
145
|
except KeyboardInterrupt:
|
|
144
|
-
|
|
146
|
+
vprint("=== Server stopped by user ===")
|
|
145
147
|
logger.info("Server stopped by user")
|
|
146
148
|
sys.exit(0)
|
|
147
149
|
except Exception as e:
|
|
148
|
-
|
|
150
|
+
vprint(f"=== EXCEPTION: {e} ===")
|
|
149
151
|
logger.error(f"Failed to start server: {e}", exc_info=args.verbose)
|
|
150
152
|
import traceback
|
|
151
153
|
traceback.print_exc(file=sys.stderr)
|
|
@@ -153,5 +155,4 @@ Configuration Priority:
|
|
|
153
155
|
|
|
154
156
|
|
|
155
157
|
# Package metadata
|
|
156
|
-
|
|
157
|
-
__all__ = ['main', 'server']
|
|
158
|
+
__all__ = ['main', 'server', '__version__']
|
|
@@ -18,10 +18,11 @@ from typing import Any, Dict, Optional
|
|
|
18
18
|
from pydantic import Field, field_validator
|
|
19
19
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
20
20
|
|
|
21
|
+
from .verbose import vprint
|
|
22
|
+
|
|
21
23
|
# Configure logging
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
24
|
-
|
|
25
26
|
class JenkinsSettings(BaseSettings):
|
|
26
27
|
"""
|
|
27
28
|
Jenkins connection settings with support for multiple configuration sources.
|
|
@@ -275,25 +276,23 @@ def load_settings(
|
|
|
275
276
|
Returns:
|
|
276
277
|
JenkinsSettings instance with merged configuration
|
|
277
278
|
"""
|
|
278
|
-
|
|
279
|
-
print(f"=== config.load_settings called: env_file={env_file}, load_vscode={load_vscode} ===", file=sys.stderr,
|
|
280
|
-
flush=True)
|
|
279
|
+
vprint(f"=== config.load_settings called: env_file={env_file}, load_vscode={load_vscode} ===")
|
|
281
280
|
|
|
282
281
|
# Start with environment variables and .env file
|
|
283
282
|
if env_file:
|
|
284
|
-
|
|
283
|
+
vprint(f"=== Using custom env file: {env_file} ===")
|
|
285
284
|
settings = JenkinsSettings(_env_file=env_file)
|
|
286
285
|
else:
|
|
287
|
-
|
|
286
|
+
vprint("=== Using default .env ===")
|
|
288
287
|
settings = JenkinsSettings()
|
|
289
288
|
|
|
290
|
-
|
|
289
|
+
vprint(f"=== Initial settings: url={settings.url} ===")
|
|
291
290
|
|
|
292
291
|
# Override with VS Code settings if requested
|
|
293
292
|
if load_vscode:
|
|
294
|
-
|
|
293
|
+
vprint("=== Loading VS Code settings ===")
|
|
295
294
|
vscode_settings = VSCodeSettingsLoader.load()
|
|
296
|
-
|
|
295
|
+
vprint(f"=== VS Code settings loaded: {vscode_settings is not None} ===")
|
|
297
296
|
if vscode_settings:
|
|
298
297
|
# Merge VS Code settings into our settings object
|
|
299
298
|
for key in ['url', 'username', 'password', 'token', 'timeout', 'connect_timeout',
|
|
@@ -303,14 +302,13 @@ def load_settings(
|
|
|
303
302
|
setattr(settings, key, vscode_value)
|
|
304
303
|
|
|
305
304
|
# Apply direct overrides (highest priority)
|
|
306
|
-
|
|
305
|
+
vprint("=== Applying overrides ===")
|
|
307
306
|
for key, value in override_values.items():
|
|
308
307
|
if value is not None and hasattr(settings, key):
|
|
309
308
|
setattr(settings, key, value)
|
|
310
309
|
|
|
311
310
|
# Log final configuration
|
|
312
|
-
|
|
313
|
-
flush=True)
|
|
311
|
+
vprint(f"=== Final settings: url={settings.url}, configured={settings.is_configured} ===")
|
|
314
312
|
settings.log_config()
|
|
315
313
|
|
|
316
314
|
return settings
|
|
@@ -6,12 +6,14 @@ Provides MCP protocol handlers for Jenkins operations including:
|
|
|
6
6
|
- Prompts (analysis templates)
|
|
7
7
|
- Tools (Jenkins operations)
|
|
8
8
|
"""
|
|
9
|
+
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import asyncio
|
|
11
12
|
import json
|
|
12
13
|
import logging
|
|
13
14
|
import sys
|
|
14
15
|
import time
|
|
16
|
+
from datetime import datetime
|
|
15
17
|
from typing import Optional
|
|
16
18
|
|
|
17
19
|
import mcp.server.stdio
|
|
@@ -24,6 +26,8 @@ from .cache import get_cache_manager
|
|
|
24
26
|
from .config import JenkinsSettings, get_default_settings
|
|
25
27
|
from .jenkins_client import get_jenkins_client
|
|
26
28
|
from .metrics import get_metrics_collector, record_tool_execution
|
|
29
|
+
from .verbose import vprint, _VERBOSE
|
|
30
|
+
from .version import __version__
|
|
27
31
|
|
|
28
32
|
# Configure logging
|
|
29
33
|
logger = logging.getLogger(__name__)
|
|
@@ -458,7 +462,7 @@ async def _tool_trigger_multiple_builds(client, args):
|
|
|
458
462
|
@server.list_tools()
|
|
459
463
|
async def handle_list_tools() -> list[types.Tool]:
|
|
460
464
|
"""List available tools for interacting with Jenkins"""
|
|
461
|
-
|
|
465
|
+
vprint("=== list_tools CALLED ===")
|
|
462
466
|
tools = [
|
|
463
467
|
# Build Operations
|
|
464
468
|
types.Tool(
|
|
@@ -1902,42 +1906,85 @@ async def main():
|
|
|
1902
1906
|
"""Run the Jenkins MCP server"""
|
|
1903
1907
|
try:
|
|
1904
1908
|
# Add explicit stderr debug
|
|
1905
|
-
|
|
1909
|
+
vprint("=== main() entered ===")
|
|
1906
1910
|
|
|
1907
1911
|
# Verify settings are configured
|
|
1908
1912
|
settings = get_settings()
|
|
1909
|
-
|
|
1913
|
+
vprint(f"=== Settings verified: {settings.is_configured} ===")
|
|
1910
1914
|
|
|
1911
1915
|
if not settings.is_configured:
|
|
1912
1916
|
logger.error("Jenkins settings not configured!")
|
|
1913
1917
|
sys.exit(1)
|
|
1914
1918
|
|
|
1915
|
-
|
|
1916
|
-
logger.info(f"Starting Jenkins MCP Server
|
|
1919
|
+
vprint(f"=== About to log startup message ===")
|
|
1920
|
+
logger.info(f"Starting Jenkins MCP Server v{__version__}")
|
|
1917
1921
|
logger.info(f"Connected to: {settings.url}")
|
|
1918
|
-
|
|
1922
|
+
vprint(f"=== Startup messages logged ===")
|
|
1919
1923
|
|
|
1920
1924
|
# Run the server using stdin/stdout streams
|
|
1921
|
-
|
|
1925
|
+
vprint("=== About to create stdio_server ===")
|
|
1922
1926
|
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
1923
|
-
|
|
1924
|
-
|
|
1927
|
+
vprint("=== stdio_server created ===")
|
|
1928
|
+
vprint("=== About to call server.run() ===")
|
|
1929
|
+
|
|
1930
|
+
now_local = datetime.now().astimezone()
|
|
1931
|
+
formatted_date = now_local.strftime("%a %b %d %H:%M:%S %Z %Y")
|
|
1932
|
+
|
|
1933
|
+
print(f"\n------ JENKINS MCP SERVER STARTUP ------")
|
|
1934
|
+
print(f"MCP Server started successfully on {formatted_date}")
|
|
1935
|
+
print(f"Press Ctrl+C to stop the server")
|
|
1936
|
+
print(f"----------------------------------------")
|
|
1937
|
+
if _VERBOSE:
|
|
1938
|
+
vprint(f"Jenkins MCP Server v{__version__}")
|
|
1939
|
+
vprint(f"Connected to: {settings.url}")
|
|
1925
1940
|
|
|
1926
1941
|
await server.run(
|
|
1927
1942
|
read_stream,
|
|
1928
1943
|
write_stream,
|
|
1929
1944
|
InitializationOptions(
|
|
1930
1945
|
server_name="jenkins-mcp-server",
|
|
1931
|
-
server_version=
|
|
1946
|
+
server_version=__version__,
|
|
1932
1947
|
capabilities=server.get_capabilities(
|
|
1933
1948
|
notification_options=NotificationOptions(),
|
|
1934
1949
|
experimental_capabilities={},
|
|
1935
1950
|
),
|
|
1936
1951
|
),
|
|
1937
1952
|
)
|
|
1938
|
-
|
|
1953
|
+
vprint("=== server.run() completed ===")
|
|
1939
1954
|
except KeyboardInterrupt:
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1955
|
+
vprint("=== Received interrupt signal ===")
|
|
1956
|
+
if not _VERBOSE:
|
|
1957
|
+
logger.info("Server stopped")
|
|
1958
|
+
|
|
1959
|
+
except BaseException as e:
|
|
1960
|
+
# Catch BaseExceptionGroup (Python 3.11+) and regular exceptions
|
|
1961
|
+
if type(e).__name__ == 'ExceptionGroup' or type(e).__name__ == 'BaseExceptionGroup':
|
|
1962
|
+
# Handle exception group
|
|
1963
|
+
vprint("=== Handling exception group ===")
|
|
1964
|
+
exceptions = getattr(e, 'exceptions', [e])
|
|
1965
|
+
|
|
1966
|
+
# Check if all exceptions are OSError with errno 5 (expected on Ctrl+C)
|
|
1967
|
+
all_io_errors = all(
|
|
1968
|
+
isinstance(exc, OSError) and getattr(exc, 'errno', None) == 5
|
|
1969
|
+
for exc in exceptions
|
|
1970
|
+
)
|
|
1971
|
+
|
|
1972
|
+
if all_io_errors:
|
|
1973
|
+
vprint("=== I/O error (stdin closed) ===")
|
|
1974
|
+
if not _VERBOSE:
|
|
1975
|
+
logger.info("Server stopped")
|
|
1976
|
+
# Clean exit - don't re-raise
|
|
1977
|
+
else:
|
|
1978
|
+
# Some other error
|
|
1979
|
+
for exc in exceptions:
|
|
1980
|
+
logger.error(f"Server error: {exc}", exc_info=True)
|
|
1981
|
+
sys.exit(1)
|
|
1982
|
+
elif isinstance(e, (OSError, IOError)) and getattr(e, 'errno', None) == 5:
|
|
1983
|
+
# Regular OSError with errno 5
|
|
1984
|
+
vprint("=== I/O error (stdin closed) ===")
|
|
1985
|
+
if not _VERBOSE:
|
|
1986
|
+
logger.info("Server stopped")
|
|
1987
|
+
else:
|
|
1988
|
+
# Some other error - re-raise
|
|
1989
|
+
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1990
|
+
raise
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shared verbose logging utility"""
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# Global verbose flag
|
|
5
|
+
_VERBOSE = False
|
|
6
|
+
|
|
7
|
+
def set_verbose(verbose: bool):
|
|
8
|
+
"""Set the global verbose flag"""
|
|
9
|
+
global _VERBOSE
|
|
10
|
+
_VERBOSE = verbose
|
|
11
|
+
|
|
12
|
+
def vprint(msg: str):
|
|
13
|
+
"""Print message only if verbose mode is enabled"""
|
|
14
|
+
if _VERBOSE:
|
|
15
|
+
print(msg, file=sys.stderr, flush=True)
|
|
16
|
+
|
|
17
|
+
def is_verbose() -> bool:
|
|
18
|
+
"""Check if verbose mode is enabled"""
|
|
19
|
+
return _VERBOSE
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Version management - reads from package.json"""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_version() -> str:
|
|
8
|
+
"""
|
|
9
|
+
Read version from package.json.
|
|
10
|
+
Falls back to a default if package.json is not found.
|
|
11
|
+
"""
|
|
12
|
+
try:
|
|
13
|
+
# Get the project root (3 levels up from this file)
|
|
14
|
+
# src/jenkins_mcp_server/version.py -> project root
|
|
15
|
+
current_file = Path(__file__)
|
|
16
|
+
project_root = current_file.parent.parent.parent
|
|
17
|
+
package_json_path = project_root / "package.json"
|
|
18
|
+
|
|
19
|
+
if package_json_path.exists():
|
|
20
|
+
with open(package_json_path, 'r') as f:
|
|
21
|
+
package_data = json.load(f)
|
|
22
|
+
return package_data.get('version', '0.0.0')
|
|
23
|
+
else:
|
|
24
|
+
# Fallback if package.json not found
|
|
25
|
+
return '0.0.0'
|
|
26
|
+
|
|
27
|
+
except Exception:
|
|
28
|
+
# If anything goes wrong, return a safe default
|
|
29
|
+
return '0.0.0'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Module-level constant
|
|
33
|
+
__version__ = get_version()
|