mcp-proxy-adapter 6.4.48__py3-none-any.whl → 6.6.1__py3-none-any.whl

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.
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration Checker CLI for MCP Proxy Adapter
4
+ Validates configuration files and checks for common issues.
5
+
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+
10
+ import argparse
11
+ import json
12
+ import sys
13
+ import os
14
+ from pathlib import Path
15
+ from typing import Dict, Any, List, Optional
16
+
17
+ # Add the project root to the path
18
+ sys.path.insert(0, str(Path(__file__).parent))
19
+
20
+ from mcp_proxy_adapter.config import Config
21
+ from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, Protocol, AuthMethod
22
+
23
+
24
+ class ConfigChecker:
25
+ """Configuration checker with validation and analysis."""
26
+
27
+ def __init__(self):
28
+ self.errors: List[str] = []
29
+ self.warnings: List[str] = []
30
+ self.info: List[str] = []
31
+
32
+ def check_config_file(self, config_path: str) -> bool:
33
+ """
34
+ Check a configuration file for issues.
35
+
36
+ Args:
37
+ config_path: Path to configuration file
38
+
39
+ Returns:
40
+ True if config is valid, False otherwise
41
+ """
42
+ self.errors.clear()
43
+ self.warnings.clear()
44
+ self.info.clear()
45
+
46
+ print(f"🔍 Checking configuration: {config_path}")
47
+ print("=" * 60)
48
+
49
+ # Check if file exists
50
+ if not os.path.exists(config_path):
51
+ self.errors.append(f"Configuration file not found: {config_path}")
52
+ return False
53
+
54
+ # Check if file is readable
55
+ if not os.access(config_path, os.R_OK):
56
+ self.errors.append(f"Configuration file is not readable: {config_path}")
57
+ return False
58
+
59
+ try:
60
+ # Load and parse JSON
61
+ with open(config_path, 'r', encoding='utf-8') as f:
62
+ config_data = json.load(f)
63
+ except json.JSONDecodeError as e:
64
+ self.errors.append(f"Invalid JSON format: {e}")
65
+ return False
66
+ except Exception as e:
67
+ self.errors.append(f"Error reading configuration file: {e}")
68
+ return False
69
+
70
+ # Load with Config class
71
+ try:
72
+ config = Config(config_path)
73
+ except Exception as e:
74
+ self.errors.append(f"Error loading configuration with Config class: {e}")
75
+ return False
76
+
77
+ # Perform checks
78
+ self._check_basic_structure(config_data)
79
+ self._check_server_config(config)
80
+ self._check_security_config(config)
81
+ self._check_ssl_config(config)
82
+ self._check_logging_config(config)
83
+ self._check_file_paths(config_path, config)
84
+ self._check_port_availability(config)
85
+
86
+ # Print results
87
+ self._print_results()
88
+
89
+ return len(self.errors) == 0
90
+
91
+ def _check_basic_structure(self, config_data: Dict[str, Any]) -> None:
92
+ """Check basic configuration structure."""
93
+ required_sections = ["server", "security", "logging", "transport"]
94
+
95
+ for section in required_sections:
96
+ if section not in config_data:
97
+ self.errors.append(f"Missing required section: {section}")
98
+ else:
99
+ self.info.append(f"✅ Section '{section}' present")
100
+
101
+ def _check_server_config(self, config: Config) -> None:
102
+ """Check server configuration."""
103
+ protocol = config.get("server.protocol")
104
+ host = config.get("server.host")
105
+ port = config.get("server.port")
106
+
107
+ if not protocol:
108
+ self.errors.append("Server protocol not specified")
109
+ elif protocol not in ["http", "https", "mtls"]:
110
+ self.errors.append(f"Invalid server protocol: {protocol}")
111
+ else:
112
+ self.info.append(f"✅ Server protocol: {protocol}")
113
+
114
+ if not host:
115
+ self.warnings.append("Server host not specified, using default")
116
+ else:
117
+ self.info.append(f"✅ Server host: {host}")
118
+
119
+ if not port:
120
+ self.errors.append("Server port not specified")
121
+ elif not isinstance(port, int) or port < 1 or port > 65535:
122
+ self.errors.append(f"Invalid server port: {port}")
123
+ else:
124
+ self.info.append(f"✅ Server port: {port}")
125
+
126
+ def _check_security_config(self, config: Config) -> None:
127
+ """Check security configuration."""
128
+ security_enabled = config.get("security.enabled", False)
129
+ tokens = config.get("security.tokens", {})
130
+ roles = config.get("security.roles", {})
131
+ roles_file = config.get("security.roles_file")
132
+
133
+ if security_enabled:
134
+ self.info.append("✅ Security enabled")
135
+
136
+ if not tokens and not roles and not roles_file:
137
+ self.warnings.append("Security enabled but no authentication methods configured")
138
+ else:
139
+ if tokens:
140
+ self.info.append(f"✅ Tokens configured: {len(tokens)} tokens")
141
+ if roles:
142
+ self.info.append(f"✅ Roles configured: {len(roles)} roles")
143
+ if roles_file:
144
+ self.info.append(f"✅ Roles file: {roles_file}")
145
+ else:
146
+ self.info.append("ℹ️ Security disabled")
147
+
148
+ def _check_ssl_config(self, config: Config) -> None:
149
+ """Check SSL configuration."""
150
+ protocol = config.get("server.protocol")
151
+ chk_hostname = config.get("transport.ssl.chk_hostname")
152
+
153
+ if protocol in ["https", "mtls"]:
154
+ self.info.append(f"✅ SSL protocol: {protocol}")
155
+
156
+ if chk_hostname is None:
157
+ self.warnings.append("chk_hostname not specified for SSL protocol")
158
+ elif chk_hostname:
159
+ self.info.append("✅ Hostname checking enabled")
160
+ else:
161
+ self.warnings.append("Hostname checking disabled for SSL protocol")
162
+ else:
163
+ if chk_hostname:
164
+ self.warnings.append("Hostname checking enabled for non-SSL protocol")
165
+ else:
166
+ self.info.append("✅ Hostname checking disabled for HTTP")
167
+
168
+ def _check_logging_config(self, config: Config) -> None:
169
+ """Check logging configuration."""
170
+ log_level = config.get("logging.level")
171
+ log_file = config.get("logging.file")
172
+ log_dir = config.get("logging.log_dir")
173
+
174
+ if log_level and log_level.upper() in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
175
+ self.info.append(f"✅ Log level: {log_level}")
176
+ else:
177
+ self.warnings.append(f"Invalid or missing log level: {log_level}")
178
+
179
+ if log_file:
180
+ self.info.append(f"✅ Log file: {log_file}")
181
+
182
+ if log_dir:
183
+ self.info.append(f"✅ Log directory: {log_dir}")
184
+
185
+ def _check_file_paths(self, config_path: str, config: Config) -> None:
186
+ """Check file paths in configuration."""
187
+ config_dir = Path(config_path).parent
188
+
189
+ # Check roles file
190
+ roles_file = config.get("security.roles_file")
191
+ if roles_file:
192
+ roles_path = config_dir / roles_file
193
+ if not roles_path.exists():
194
+ self.warnings.append(f"Roles file not found: {roles_path}")
195
+ else:
196
+ self.info.append(f"✅ Roles file exists: {roles_path}")
197
+
198
+ # Check log directory
199
+ log_dir = config.get("logging.log_dir")
200
+ if log_dir:
201
+ log_path = config_dir / log_dir
202
+ if not log_path.exists():
203
+ self.warnings.append(f"Log directory not found: {log_path}")
204
+ else:
205
+ self.info.append(f"✅ Log directory exists: {log_path}")
206
+
207
+ def _check_port_availability(self, config: Config) -> None:
208
+ """Check if the configured port is available."""
209
+ import socket
210
+
211
+ port = config.get("server.port")
212
+ if not port:
213
+ return
214
+
215
+ try:
216
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
217
+ s.bind(('localhost', port))
218
+ self.info.append(f"✅ Port {port} is available")
219
+ except OSError:
220
+ self.warnings.append(f"Port {port} is already in use")
221
+
222
+ def _print_results(self) -> None:
223
+ """Print check results."""
224
+ if self.info:
225
+ print("\n📋 Information:")
226
+ for info in self.info:
227
+ print(f" {info}")
228
+
229
+ if self.warnings:
230
+ print("\n⚠️ Warnings:")
231
+ for warning in self.warnings:
232
+ print(f" {warning}")
233
+
234
+ if self.errors:
235
+ print("\n❌ Errors:")
236
+ for error in self.errors:
237
+ print(f" {error}")
238
+
239
+ print(f"\n📊 Summary:")
240
+ print(f" Information: {len(self.info)}")
241
+ print(f" Warnings: {len(self.warnings)}")
242
+ print(f" Errors: {len(self.errors)}")
243
+
244
+ if self.errors:
245
+ print(f" Status: ❌ FAILED")
246
+ elif self.warnings:
247
+ print(f" Status: ⚠️ WARNINGS")
248
+ else:
249
+ print(f" Status: ✅ PASSED")
250
+
251
+
252
+ def check_all_configs(config_dir: str = "./configs") -> None:
253
+ """Check all configuration files in a directory."""
254
+ config_dir_path = Path(config_dir)
255
+
256
+ if not config_dir_path.exists():
257
+ print(f"❌ Configuration directory not found: {config_dir}")
258
+ return
259
+
260
+ config_files = list(config_dir_path.glob("*.json"))
261
+
262
+ if not config_files:
263
+ print(f"❌ No JSON configuration files found in: {config_dir}")
264
+ return
265
+
266
+ print(f"🔍 Checking {len(config_files)} configuration files in {config_dir}")
267
+ print("=" * 80)
268
+
269
+ checker = ConfigChecker()
270
+ total_checked = 0
271
+ total_passed = 0
272
+
273
+ for config_file in sorted(config_files):
274
+ if checker.check_config_file(str(config_file)):
275
+ total_passed += 1
276
+ total_checked += 1
277
+ print("\n" + "-" * 80 + "\n")
278
+
279
+ print(f"📊 Final Summary:")
280
+ print(f" Total files checked: {total_checked}")
281
+ print(f" Passed: {total_passed}")
282
+ print(f" Failed: {total_checked - total_passed}")
283
+ print(f" Success rate: {(total_passed / total_checked * 100):.1f}%")
284
+
285
+
286
+ def validate_config_syntax(config_path: str) -> bool:
287
+ """Validate configuration syntax only."""
288
+ try:
289
+ with open(config_path, 'r', encoding='utf-8') as f:
290
+ json.load(f)
291
+ print(f"✅ Configuration syntax is valid: {config_path}")
292
+ return True
293
+ except json.JSONDecodeError as e:
294
+ print(f"❌ Invalid JSON syntax: {e}")
295
+ return False
296
+ except Exception as e:
297
+ print(f"❌ Error reading file: {e}")
298
+ return False
299
+
300
+
301
+ def compare_configs(config1_path: str, config2_path: str) -> None:
302
+ """Compare two configuration files."""
303
+ print(f"🔍 Comparing configurations:")
304
+ print(f" Config 1: {config1_path}")
305
+ print(f" Config 2: {config2_path}")
306
+ print("=" * 60)
307
+
308
+ try:
309
+ with open(config1_path, 'r') as f:
310
+ config1 = json.load(f)
311
+ with open(config2_path, 'r') as f:
312
+ config2 = json.load(f)
313
+ except Exception as e:
314
+ print(f"❌ Error loading configurations: {e}")
315
+ return
316
+
317
+ # Compare key sections
318
+ sections_to_compare = ["server", "security", "logging", "transport"]
319
+
320
+ for section in sections_to_compare:
321
+ print(f"\n📋 Section: {section}")
322
+
323
+ if section not in config1 and section not in config2:
324
+ print(" Both configs missing this section")
325
+ continue
326
+ elif section not in config1:
327
+ print(f" ❌ Missing in {config1_path}")
328
+ continue
329
+ elif section not in config2:
330
+ print(f" ❌ Missing in {config2_path}")
331
+ continue
332
+
333
+ if config1[section] == config2[section]:
334
+ print(" ✅ Identical")
335
+ else:
336
+ print(" ⚠️ Different")
337
+ # Show differences for key fields
338
+ if section == "server":
339
+ for key in ["protocol", "host", "port"]:
340
+ val1 = config1[section].get(key)
341
+ val2 = config2[section].get(key)
342
+ if val1 != val2:
343
+ print(f" {key}: {val1} vs {val2}")
344
+
345
+
346
+ def main():
347
+ """Main CLI function."""
348
+ parser = argparse.ArgumentParser(
349
+ description="MCP Proxy Adapter Configuration Checker",
350
+ formatter_class=argparse.RawDescriptionHelpFormatter,
351
+ epilog="""
352
+ Examples:
353
+ # Check a single configuration file
354
+ python check_config.py configs/http.json
355
+
356
+ # Check all configurations in a directory
357
+ python check_config.py --all
358
+
359
+ # Validate JSON syntax only
360
+ python check_config.py --syntax configs/http.json
361
+
362
+ # Compare two configurations
363
+ python check_config.py --compare configs/http.json configs/https.json
364
+ """
365
+ )
366
+
367
+ parser.add_argument(
368
+ "config_file",
369
+ nargs="?",
370
+ help="Configuration file to check"
371
+ )
372
+
373
+ parser.add_argument(
374
+ "--all",
375
+ action="store_true",
376
+ help="Check all configuration files in ./configs directory"
377
+ )
378
+
379
+ parser.add_argument(
380
+ "--syntax",
381
+ action="store_true",
382
+ help="Validate JSON syntax only"
383
+ )
384
+
385
+ parser.add_argument(
386
+ "--compare",
387
+ nargs=2,
388
+ metavar=("CONFIG1", "CONFIG2"),
389
+ help="Compare two configuration files"
390
+ )
391
+
392
+ parser.add_argument(
393
+ "--config-dir",
394
+ default="./configs",
395
+ help="Configuration directory (default: ./configs)"
396
+ )
397
+
398
+ args = parser.parse_args()
399
+
400
+ if args.compare:
401
+ compare_configs(args.compare[0], args.compare[1])
402
+ elif args.all:
403
+ check_all_configs(args.config_dir)
404
+ elif args.config_file:
405
+ if args.syntax:
406
+ validate_config_syntax(args.config_file)
407
+ else:
408
+ checker = ConfigChecker()
409
+ checker.check_config_file(args.config_file)
410
+ else:
411
+ parser.print_help()
412
+
413
+
414
+ if __name__ == "__main__":
415
+ main()