@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 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.0
10
+ ## ✨ What's New in v1.1.16
11
11
 
12
- ### 🚀 Performance Enhancements
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 Examples
325
+ ## 🔌 Integration with VSCode
326
+
327
+ 1. **Install VSCode MCP Extension** (if not already installed)
311
328
 
312
- ### VS Code MCP Client
329
+ 2. **Configure the server** in your workspace or user settings:
313
330
 
314
- Add to your VS Code `mcp.json`:
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
- Or `setting.json` with `.env` file and proxy settings:
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
- ### Claude Desktop
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
- Add to `claude_desktop_config.json`:
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
- **Where to find claude_desktop_config.json:**
375
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
376
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
377
- - **Linux**: `~/.config/Claude/claude_desktop_config.json`
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 script
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
- └── server.py # MCP server implementation
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
 
@@ -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
- console.error("[jenkins-mcp] package root:", PACKAGE_ROOT);
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"], // no stdout leakage
35
+ stdio: ["ignore", "pipe", "pipe"], // Always pipe stdout/stderr
34
36
  });
35
37
 
38
+ // Handle stdout
36
39
  p.stdout.on("data", (d) => {
37
- console.error(d.toString()); // redirect to stderr
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
- console.error(d.toString());
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
- console.error("[jenkins-mcp] Python venv not found, creating...");
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
- console.error("[jenkins-mcp] Python venv exists, ensuring dependencies...");
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
- console.error("[jenkins-mcp] Python environment ready");
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
- console.error("[jenkins-mcp] Bootstrap failed:", err);
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
- console.error("[jenkins-mcp] Python:", pythonPath);
103
- console.error("[jenkins-mcp] Args:", pythonArgs.join(" "));
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
- console.error("[jenkins-mcp] exited due to signal:", signal);
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
- console.error("[jenkins-mcp] failed to start:", err);
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
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@rishibhushan/jenkins-mcp-server",
3
- "version": "1.1.16",
3
+ "type": "module",
4
+ "version": "1.1.18",
4
5
  "description": "AI-enabled Jenkins automation via Model Context Protocol (MCP)",
5
6
  "main": "bin/jenkins-mcp.js",
6
7
  "bin": {
@@ -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 1.0.0'
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
- print(f"=== Args parsed: env_file={args.env_file}, verbose={args.verbose} ===", file=sys.stderr, flush=True)
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
- print("=== Setting up logging ===", file=sys.stderr, flush=True)
102
+ vprint("=== Setting up logging ===")
101
103
  setup_logging(args.verbose)
102
104
  logger = logging.getLogger(__name__)
103
- print("=== Logging configured ===", file=sys.stderr, flush=True)
105
+ vprint("=== Logging configured ===")
104
106
 
105
107
  try:
106
108
  # Load settings based on arguments
107
- print("=== Loading Jenkins configuration ===", file=sys.stderr, flush=True)
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
- print(f"=== Settings loaded: url={settings.url}, configured={settings.is_configured} ===", file=sys.stderr, flush=True)
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
- print("=== Configuration validated ===", file=sys.stderr, flush=True)
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
- print("=== Setting server settings ===", file=sys.stderr, flush=True)
136
+ vprint("=== Setting server settings ===")
135
137
  server.set_jenkins_settings(settings)
136
- print("=== Server settings configured ===", file=sys.stderr, flush=True)
138
+ vprint("=== Server settings configured ===")
137
139
 
138
140
  # Run the server
139
- print("=== Starting asyncio server ===", file=sys.stderr, flush=True)
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
- print("=== Server stopped by user ===", file=sys.stderr, flush=True)
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
- print(f"=== EXCEPTION: {e} ===", file=sys.stderr, flush=True)
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
- __version__ = "1.0.0"
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
- import sys
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
- print(f"=== Using custom env file: {env_file} ===", file=sys.stderr, flush=True)
283
+ vprint(f"=== Using custom env file: {env_file} ===")
285
284
  settings = JenkinsSettings(_env_file=env_file)
286
285
  else:
287
- print("=== Using default .env ===", file=sys.stderr, flush=True)
286
+ vprint("=== Using default .env ===")
288
287
  settings = JenkinsSettings()
289
288
 
290
- print(f"=== Initial settings: url={settings.url} ===", file=sys.stderr, flush=True)
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
- print("=== Loading VS Code settings ===", file=sys.stderr, flush=True)
293
+ vprint("=== Loading VS Code settings ===")
295
294
  vscode_settings = VSCodeSettingsLoader.load()
296
- print(f"=== VS Code settings loaded: {vscode_settings is not None} ===", file=sys.stderr, flush=True)
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
- print("=== Applying overrides ===", file=sys.stderr, flush=True)
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
- print(f"=== Final settings: url={settings.url}, configured={settings.is_configured} ===", file=sys.stderr,
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
- print("=== list_tools CALLED ===", file=sys.stderr, flush=True)
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
- print("=== main() entered ===", file=sys.stderr, flush=True)
1909
+ vprint("=== main() entered ===")
1906
1910
 
1907
1911
  # Verify settings are configured
1908
1912
  settings = get_settings()
1909
- print(f"=== Settings verified: {settings.is_configured} ===", file=sys.stderr, flush=True)
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
- print(f"=== About to log startup message ===", file=sys.stderr, flush=True)
1916
- logger.info(f"Starting Jenkins MCP Server v1.0.0")
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
- print(f"=== Startup messages logged ===", file=sys.stderr, flush=True)
1922
+ vprint(f"=== Startup messages logged ===")
1919
1923
 
1920
1924
  # Run the server using stdin/stdout streams
1921
- print("=== About to create stdio_server ===", file=sys.stderr, flush=True)
1925
+ vprint("=== About to create stdio_server ===")
1922
1926
  async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
1923
- print("=== stdio_server created ===", file=sys.stderr, flush=True)
1924
- print("=== About to call server.run() ===", file=sys.stderr, flush=True)
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="1.0.0",
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
- print("=== server.run() completed ===", file=sys.stderr, flush=True)
1953
+ vprint("=== server.run() completed ===")
1939
1954
  except KeyboardInterrupt:
1940
- logger.info("Server stopped by user")
1941
- except Exception as e:
1942
- logger.error(f"Server error: {e}", exc_info=True)
1943
- sys.exit(1)
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()