mcp-proxy-adapter 6.6.0__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()
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration Generator CLI for MCP Proxy Adapter
4
+ Generates configurations based on command line flags.
5
+
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Dict, Any, Optional
14
+
15
+ # Add the examples directory to the path to import config_builder
16
+ sys.path.insert(0, str(Path(__file__).parent / "mcp_proxy_adapter" / "examples"))
17
+
18
+ from config_builder import ConfigBuilder, ConfigFactory, Protocol, AuthMethod
19
+
20
+
21
+ def create_config_from_flags(
22
+ protocol: str,
23
+ token: bool = False,
24
+ roles: bool = False,
25
+ host: str = "127.0.0.1",
26
+ port: int = 8000,
27
+ cert_dir: str = "./certs",
28
+ key_dir: str = "./keys",
29
+ output_dir: str = "./configs"
30
+ ) -> Dict[str, Any]:
31
+ """
32
+ Create configuration based on command line flags.
33
+
34
+ Args:
35
+ protocol: Protocol type (http, https, mtls)
36
+ token: Enable token authentication
37
+ roles: Enable role-based access control
38
+ host: Server host
39
+ port: Server port
40
+ cert_dir: Certificate directory
41
+ key_dir: Key directory
42
+ output_dir: Output directory for configs
43
+
44
+ Returns:
45
+ Configuration dictionary
46
+ """
47
+ # Use the simplified config builder
48
+ from config_builder import create_config_from_flags as simple_create_config
49
+
50
+ return simple_create_config(protocol, token, roles, port)
51
+
52
+
53
+ def save_config(config: Dict[str, Any], filename: str, output_dir: str) -> Path:
54
+ """Save configuration to file."""
55
+ output_path = Path(output_dir)
56
+ output_path.mkdir(parents=True, exist_ok=True)
57
+
58
+ config_file = output_path / f"{filename}.json"
59
+ with open(config_file, 'w', encoding='utf-8') as f:
60
+ json.dump(config, f, indent=2, ensure_ascii=False)
61
+
62
+ return config_file
63
+
64
+
65
+ def create_full_config_with_all_options(host: str = "127.0.0.1", port: int = 20000) -> Dict[str, Any]:
66
+ """
67
+ Create a full configuration with all options enabled but set to HTTP base.
68
+ This allows testing all features by enabling different sections.
69
+
70
+ Args:
71
+ host: Server host
72
+ port: Server port
73
+
74
+ Returns:
75
+ Full configuration dictionary with all options
76
+ """
77
+ builder = ConfigBuilder()
78
+
79
+ # Set server configuration
80
+ builder.set_server(host=host, port=port)
81
+
82
+ # Set to HTTP base (no SSL by default)
83
+ builder.set_protocol(Protocol.HTTP)
84
+
85
+ # Enable all authentication options but keep them disabled by default
86
+ api_keys = {
87
+ "admin": "admin-secret-key",
88
+ "user": "user-secret-key",
89
+ "readonly": "readonly-secret-key"
90
+ }
91
+ roles = {
92
+ "admin": ["read", "write", "delete", "admin"],
93
+ "user": ["read", "write"],
94
+ "readonly": ["read"]
95
+ }
96
+
97
+ # Set up all authentication methods but keep security disabled
98
+ builder.set_auth(AuthMethod.TOKEN_ROLES, api_keys=api_keys, roles=roles)
99
+
100
+ # Disable security by default (will be enabled in tests)
101
+ config = builder.build()
102
+ config["security"]["enabled"] = False
103
+
104
+ # Add protocol variants for easy switching
105
+ config["protocol_variants"] = {
106
+ "http": {"server": {"protocol": "http"}},
107
+ "https": {"server": {"protocol": "https"}},
108
+ "mtls": {"server": {"protocol": "mtls"}}
109
+ }
110
+
111
+ # Add authentication configurations for easy switching
112
+ config["auth_variants"] = {
113
+ "none": {"security": {"enabled": False}},
114
+ "token": {
115
+ "security": {
116
+ "enabled": True,
117
+ "tokens": api_keys,
118
+ "roles": roles,
119
+ "roles_file": None
120
+ }
121
+ },
122
+ "token_roles": {
123
+ "security": {
124
+ "enabled": True,
125
+ "tokens": api_keys,
126
+ "roles": roles,
127
+ "roles_file": "configs/roles.json"
128
+ }
129
+ }
130
+ }
131
+
132
+ return config
133
+
134
+
135
+ def generate_all_configs(output_dir: str = "./configs", host: str = "127.0.0.1") -> None:
136
+ """Generate all standard configurations."""
137
+ configs = [
138
+ # HTTP configurations
139
+ ("http", False, False, 20000),
140
+ ("http", True, True, 20001), # token=True always includes roles
141
+ ("http", True, True, 20002), # token_roles is same as token now
142
+
143
+ # HTTPS configurations
144
+ ("https", False, False, 20003),
145
+ ("https", True, True, 20004), # token=True always includes roles
146
+ ("https", True, True, 20005), # token_roles is same as token now
147
+
148
+ # mTLS configurations
149
+ ("mtls", False, False, 20006),
150
+ ("mtls", True, True, 20007), # token=True always includes roles
151
+ ("mtls", True, True, 20008), # token_roles is same as token now
152
+ ]
153
+
154
+ print("🔧 Generating MCP Proxy Adapter configurations...")
155
+ print("=" * 60)
156
+
157
+ generated_files = []
158
+
159
+ for protocol, token, roles, port in configs:
160
+ # Create configuration name
161
+ name_parts = [protocol]
162
+ if token:
163
+ name_parts.append("token")
164
+ if roles:
165
+ name_parts.append("roles")
166
+
167
+ config_name = "_".join(name_parts)
168
+
169
+ # Generate configuration
170
+ config = create_config_from_flags(
171
+ protocol=protocol,
172
+ token=token,
173
+ roles=roles,
174
+ host=host,
175
+ port=port,
176
+ output_dir=output_dir
177
+ )
178
+
179
+ # Save configuration
180
+ config_file = save_config(config, config_name, output_dir)
181
+ generated_files.append(config_file)
182
+
183
+ print(f"✅ Created {config_name}.json (port {port})")
184
+
185
+ # Create roles.json file if any role-based configs were generated
186
+ roles_config = {
187
+ "enabled": True,
188
+ "default_policy": {
189
+ "deny_by_default": False,
190
+ "require_role_match": False,
191
+ "case_sensitive": False,
192
+ "allow_wildcard": False
193
+ },
194
+ "roles": {
195
+ "admin": ["read", "write", "delete", "admin"],
196
+ "user": ["read", "write"],
197
+ "readonly": ["read"],
198
+ "guest": ["read"],
199
+ "proxy": ["read", "write"]
200
+ },
201
+ "permissions": {
202
+ "read": ["GET"],
203
+ "write": ["POST", "PUT", "PATCH"],
204
+ "delete": ["DELETE"],
205
+ "admin": ["*"]
206
+ }
207
+ }
208
+
209
+ roles_file = Path(output_dir) / "roles.json"
210
+ with open(roles_file, 'w', encoding='utf-8') as f:
211
+ json.dump(roles_config, f, indent=2, ensure_ascii=False)
212
+ print(f"✅ Created roles.json")
213
+
214
+ print(f"\n🎉 Generated {len(generated_files)} configurations in {output_dir}/")
215
+ print("\n📋 Generated configurations:")
216
+ for config_file in generated_files:
217
+ print(f" - {config_file.name}")
218
+
219
+
220
+ def main():
221
+ """Main CLI function."""
222
+ parser = argparse.ArgumentParser(
223
+ description="MCP Proxy Adapter Configuration Generator",
224
+ formatter_class=argparse.RawDescriptionHelpFormatter,
225
+ epilog="""
226
+ Examples:
227
+ # Generate all standard configurations
228
+ python generate_config.py --all
229
+
230
+ # Generate full config with all options (HTTP base)
231
+ python generate_config.py --full-config
232
+
233
+ # Generate specific configuration
234
+ python generate_config.py --protocol https --token --roles --port 8080
235
+
236
+ # Generate HTTP configuration with token auth
237
+ python generate_config.py --protocol http --token
238
+
239
+ # Generate mTLS configuration with roles
240
+ python generate_config.py --protocol mtls --roles
241
+ """
242
+ )
243
+
244
+ # Configuration options
245
+ parser.add_argument("--protocol", choices=["http", "https", "mtls"],
246
+ help="Protocol type (http, https, mtls)")
247
+ parser.add_argument("--token", action="store_true",
248
+ help="Enable token authentication")
249
+ parser.add_argument("--roles", action="store_true",
250
+ help="Enable role-based access control")
251
+ parser.add_argument("--all", action="store_true",
252
+ help="Generate all standard configurations")
253
+ parser.add_argument("--full-config", action="store_true",
254
+ help="Generate full config with all options (HTTP base)")
255
+
256
+ # Server configuration
257
+ parser.add_argument("--host", default="127.0.0.1",
258
+ help="Server host (default: 127.0.0.1)")
259
+ parser.add_argument("--port", type=int, default=8000,
260
+ help="Server port (default: 8000)")
261
+
262
+ # Paths
263
+ parser.add_argument("--cert-dir", default="./certs",
264
+ help="Certificate directory (default: ./certs)")
265
+ parser.add_argument("--key-dir", default="./keys",
266
+ help="Key directory (default: ./keys)")
267
+ parser.add_argument("--output-dir", default="./configs",
268
+ help="Output directory (default: ./configs)")
269
+
270
+ # Output options
271
+ parser.add_argument("--output", "-o",
272
+ help="Output filename (without extension)")
273
+ parser.add_argument("--stdout", action="store_true",
274
+ help="Output to stdout instead of file")
275
+
276
+ args = parser.parse_args()
277
+
278
+ try:
279
+ if args.all:
280
+ # Generate all configurations
281
+ generate_all_configs(
282
+ output_dir=args.output_dir,
283
+ host=args.host
284
+ )
285
+ elif args.full_config:
286
+ # Generate full config with all options
287
+ config = create_full_config_with_all_options(
288
+ host=args.host,
289
+ port=args.port
290
+ )
291
+
292
+ if args.stdout:
293
+ # Output to stdout
294
+ print(json.dumps(config, indent=2, ensure_ascii=False))
295
+ else:
296
+ # Save to file
297
+ filename = args.output or "full_config"
298
+ config_file = save_config(config, filename, args.output_dir)
299
+ print(f"✅ Full configuration saved to: {config_file}")
300
+ elif args.protocol:
301
+ # Generate specific configuration
302
+ config = create_config_from_flags(
303
+ protocol=args.protocol,
304
+ token=args.token,
305
+ roles=args.roles,
306
+ host=args.host,
307
+ port=args.port,
308
+ cert_dir=args.cert_dir,
309
+ key_dir=args.key_dir,
310
+ output_dir=args.output_dir
311
+ )
312
+
313
+ if args.stdout:
314
+ # Output to stdout
315
+ print(json.dumps(config, indent=2, ensure_ascii=False))
316
+ else:
317
+ # Save to file
318
+ if args.output:
319
+ filename = args.output
320
+ else:
321
+ # Generate filename from flags
322
+ name_parts = [args.protocol]
323
+ if args.token:
324
+ name_parts.append("token")
325
+ if args.roles:
326
+ name_parts.append("roles")
327
+ filename = "_".join(name_parts)
328
+
329
+ config_file = save_config(config, filename, args.output_dir)
330
+ print(f"✅ Configuration saved to: {config_file}")
331
+ else:
332
+ parser.print_help()
333
+ return 1
334
+
335
+ return 0
336
+
337
+ except Exception as e:
338
+ print(f"❌ Error: {e}", file=sys.stderr)
339
+ return 1
340
+
341
+
342
+ if __name__ == "__main__":
343
+ sys.exit(main())
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Automated tests for chk_hostname functionality in all SSL modes.
4
+
5
+ Author: Vasiliy Zdanovskiy
6
+ email: vasilyvz@gmail.com
7
+ """
8
+
9
+ import json
10
+ import tempfile
11
+ import os
12
+ from pathlib import Path
13
+ import sys
14
+
15
+ # Add the project root to the path
16
+ sys.path.insert(0, str(Path(__file__).parent))
17
+
18
+ from mcp_proxy_adapter.config import Config
19
+ from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, Protocol, AuthMethod
20
+
21
+
22
+ def test_chk_hostname_default_config():
23
+ """Test that default config has chk_hostname=False for HTTP."""
24
+ print("🧪 Testing default config chk_hostname...")
25
+
26
+ config = Config()
27
+
28
+ # Default should be HTTP with chk_hostname=False
29
+ assert config.get("server.protocol") == "http"
30
+ assert config.get("transport.ssl.chk_hostname") is False
31
+
32
+ print("✅ Default config: chk_hostname=False for HTTP")
33
+
34
+
35
+ def test_chk_hostname_http_config():
36
+ """Test that HTTP config has chk_hostname=False."""
37
+ print("🧪 Testing HTTP config chk_hostname...")
38
+
39
+ # Create HTTP config
40
+ http_config = ConfigBuilder().set_protocol(Protocol.HTTP).build()
41
+
42
+ # Save to temporary file and load with Config
43
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
44
+ json.dump(http_config, f)
45
+ temp_config_path = f.name
46
+
47
+ try:
48
+ config = Config(temp_config_path)
49
+
50
+ # HTTP should have chk_hostname=False
51
+ assert config.get("server.protocol") == "http"
52
+ assert config.get("transport.ssl.chk_hostname") is False
53
+
54
+ print("✅ HTTP config: chk_hostname=False")
55
+ finally:
56
+ os.unlink(temp_config_path)
57
+
58
+
59
+ def test_chk_hostname_https_config():
60
+ """Test that HTTPS config has chk_hostname=True."""
61
+ print("🧪 Testing HTTPS config chk_hostname...")
62
+
63
+ # Create HTTPS config
64
+ https_config = ConfigBuilder().set_protocol(Protocol.HTTPS).build()
65
+
66
+ # Save to temporary file and load with Config
67
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
68
+ json.dump(https_config, f)
69
+ temp_config_path = f.name
70
+
71
+ try:
72
+ config = Config(temp_config_path)
73
+
74
+
75
+ # HTTPS should have chk_hostname=True
76
+ assert config.get("server.protocol") == "https"
77
+ assert config.get("transport.ssl.chk_hostname") is True
78
+
79
+ print("✅ HTTPS config: chk_hostname=True")
80
+ finally:
81
+ os.unlink(temp_config_path)
82
+
83
+
84
+ def test_chk_hostname_mtls_config():
85
+ """Test that mTLS config has chk_hostname=True."""
86
+ print("🧪 Testing mTLS config chk_hostname...")
87
+
88
+ # Create mTLS config
89
+ mtls_config = ConfigBuilder().set_protocol(Protocol.MTLS).build()
90
+
91
+ # Save to temporary file and load with Config
92
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
93
+ json.dump(mtls_config, f)
94
+ temp_config_path = f.name
95
+
96
+ try:
97
+ config = Config(temp_config_path)
98
+
99
+ # mTLS should have chk_hostname=True
100
+ assert config.get("server.protocol") == "mtls"
101
+ assert config.get("transport.ssl.chk_hostname") is True
102
+
103
+ print("✅ mTLS config: chk_hostname=True")
104
+ finally:
105
+ os.unlink(temp_config_path)
106
+
107
+
108
+ def test_chk_hostname_override():
109
+ """Test that chk_hostname can be overridden in config."""
110
+ print("🧪 Testing chk_hostname override...")
111
+
112
+ # Create HTTPS config with chk_hostname=False override
113
+ https_config = ConfigBuilder().set_protocol(Protocol.HTTPS).build()
114
+ # Add transport section if it doesn't exist
115
+ if "transport" not in https_config:
116
+ https_config["transport"] = {}
117
+ if "ssl" not in https_config["transport"]:
118
+ https_config["transport"]["ssl"] = {}
119
+ https_config["transport"]["ssl"]["chk_hostname"] = False
120
+ https_config["transport"]["ssl"]["_chk_hostname_user_set"] = True
121
+
122
+ # Save to temporary file and load with Config
123
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
124
+ json.dump(https_config, f)
125
+ temp_config_path = f.name
126
+
127
+ try:
128
+ config = Config(temp_config_path)
129
+
130
+
131
+ # Should respect the override
132
+ assert config.get("server.protocol") == "https"
133
+ assert config.get("transport.ssl.chk_hostname") is False
134
+
135
+ print("✅ HTTPS config with chk_hostname=False override works")
136
+ finally:
137
+ os.unlink(temp_config_path)
138
+
139
+
140
+ def test_chk_hostname_all_combinations():
141
+ """Test chk_hostname for all protocol and auth combinations."""
142
+ print("🧪 Testing chk_hostname for all combinations...")
143
+
144
+ protocols = [Protocol.HTTP, Protocol.HTTPS, Protocol.MTLS]
145
+ auth_methods = [AuthMethod.NONE, AuthMethod.TOKEN, AuthMethod.TOKEN_ROLES]
146
+
147
+ for protocol in protocols:
148
+ for auth_method in auth_methods:
149
+ # Create config
150
+ config_data = (ConfigBuilder()
151
+ .set_protocol(protocol)
152
+ .set_auth(auth_method)
153
+ .build())
154
+
155
+ # Save to temporary file and load with Config
156
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
157
+ json.dump(config_data, f)
158
+ temp_config_path = f.name
159
+
160
+ try:
161
+ config = Config(temp_config_path)
162
+
163
+ protocol_name = protocol.value
164
+ auth_name = auth_method.value
165
+
166
+ # Check chk_hostname based on protocol
167
+ if protocol_name == "http":
168
+ expected_chk_hostname = False
169
+ else: # https or mtls
170
+ expected_chk_hostname = True
171
+
172
+ actual_chk_hostname = config.get("transport.ssl.chk_hostname")
173
+
174
+ assert actual_chk_hostname == expected_chk_hostname, (
175
+ f"Protocol {protocol_name} with auth {auth_name}: "
176
+ f"expected chk_hostname={expected_chk_hostname}, "
177
+ f"got {actual_chk_hostname}"
178
+ )
179
+
180
+ print(f"✅ {protocol_name}+{auth_name}: chk_hostname={actual_chk_hostname}")
181
+
182
+ finally:
183
+ os.unlink(temp_config_path)
184
+
185
+ print("✅ All protocol+auth combinations have correct chk_hostname values")
186
+
187
+
188
+ def main():
189
+ """Run all chk_hostname tests."""
190
+ print("🧪 Running Automated chk_hostname Tests")
191
+ print("=" * 50)
192
+
193
+ try:
194
+ test_chk_hostname_default_config()
195
+ test_chk_hostname_http_config()
196
+ test_chk_hostname_https_config()
197
+ test_chk_hostname_mtls_config()
198
+ test_chk_hostname_override()
199
+ test_chk_hostname_all_combinations()
200
+
201
+ print("=" * 50)
202
+ print("🎉 All chk_hostname tests passed!")
203
+ return True
204
+
205
+ except Exception as e:
206
+ print(f"❌ Test failed: {e}")
207
+ import traceback
208
+ traceback.print_exc()
209
+ return False
210
+
211
+
212
+ if __name__ == "__main__":
213
+ success = main()
214
+ sys.exit(0 if success else 1)
@@ -2,4 +2,4 @@
2
2
  Version information for MCP Proxy Adapter.
3
3
  """
4
4
 
5
- __version__ = "6.6.0"
5
+ __version__ = "6.6.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 6.6.0
3
+ Version: 6.6.1
4
4
  Summary: Powerful JSON-RPC microservices framework with built-in security, authentication, and proxy registration
5
5
  Home-page: https://github.com/maverikod/mcp-proxy-adapter
6
6
  Author: Vasiliy Zdanovskiy
@@ -4,7 +4,7 @@ mcp_proxy_adapter/config.py,sha256=NmwYJPfnAFWmGsOlyYkMDfuqA-SeduuvTSjZKJbzbKc,2
4
4
  mcp_proxy_adapter/custom_openapi.py,sha256=XRviX-C-ZkSKdBhORhDTdeN_1FWyEfXZADiASft3t9I,28149
5
5
  mcp_proxy_adapter/main.py,sha256=I9fYgMD8_RNYuSHn-paeTSr9coAuVlEkfLaWeYbZors,5724
6
6
  mcp_proxy_adapter/openapi.py,sha256=2UZOI09ZDRJuBYBjKbMyb2U4uASszoCMD5o_4ktRpvg,13480
7
- mcp_proxy_adapter/version.py,sha256=3wn4ixEY89A5pJaACrT6Pg-UKLFQTHKOi6F0jL-TyDo,74
7
+ mcp_proxy_adapter/version.py,sha256=Mh_TTmFK-EgbYu8AwRu8Ax1MTsScWluXVsIKkST5imU,74
8
8
  mcp_proxy_adapter/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  mcp_proxy_adapter/api/app.py,sha256=cxjavhNTtaYg2ea-UeHSDnKh8edKVNQ2NbXUDYbufFU,34183
10
10
  mcp_proxy_adapter/api/handlers.py,sha256=X-hcMNVeTAu4yVkKJEChEsj2bFptUS6sLNN-Wysjkow,10011
@@ -85,6 +85,7 @@ mcp_proxy_adapter/core/utils.py,sha256=wBdDYBDWQ6zbwrnl9tykHjo0FjJVsLT_x8Bjk1lZX
85
85
  mcp_proxy_adapter/examples/__init__.py,sha256=k1F-EotAFbJ3JvK_rNgiH4bUztmxIWtYn0AfbAZ1ZGs,450
86
86
  mcp_proxy_adapter/examples/bugfix_certificate_config.py,sha256=YGBE_SI6wYZUJLWl7-fP1OWXiSH4mHJAZHApgQWvG7s,10529
87
87
  mcp_proxy_adapter/examples/cert_manager_bugfix.py,sha256=UWUwItjqHqSnOMHocsz40_3deoZE8-vdROLW9y2fEns,7259
88
+ mcp_proxy_adapter/examples/check_config.py,sha256=W6gS6stP-WfE71YmIemvbmolf04FiR7mvJYSJqDFdzQ,14090
88
89
  mcp_proxy_adapter/examples/config_builder.py,sha256=kd3-donlGuDgZwJYsy_rjzrydBg_A1GaaJuJ8jj4VDc,9696
89
90
  mcp_proxy_adapter/examples/config_builder_simple.py,sha256=qTG3tIIu8eUW8gb06snaWHOTp0BFRRim38VqKrP2Ezo,9109
90
91
  mcp_proxy_adapter/examples/config_cli.py,sha256=ZhVG6XEpTFe5-MzELByVsUh0AD4bHPBZeoXnGWbqifs,11059
@@ -98,6 +99,7 @@ mcp_proxy_adapter/examples/generate_certificates_cli.py,sha256=jb5bdNhoODL6qTWBd
98
99
  mcp_proxy_adapter/examples/generate_certificates_fixed.py,sha256=6zJj9xdEuwj-ZO2clMoB7pNj5hW4IgwK-qsLPzuq8VQ,12566
99
100
  mcp_proxy_adapter/examples/generate_certificates_framework.py,sha256=TGLyy4AJNI0w-Dd2FUQyX2sOXt05o_q4zkjoapzG1Wc,15017
100
101
  mcp_proxy_adapter/examples/generate_certificates_openssl.py,sha256=5V4B8Ys_-FZsDh-CH7BNf1XXkMtpidkm4iecY0XCFuE,16891
102
+ mcp_proxy_adapter/examples/generate_config.py,sha256=4WcyCMuQKqr5qtGekoqy-jrQ-71F-ZYdDYftwLrjJNg,11461
101
103
  mcp_proxy_adapter/examples/proxy_registration_example.py,sha256=vemRhftnjbiOBCJkmtDGqlWQ8syTG0a8755GCOnaQsg,12503
102
104
  mcp_proxy_adapter/examples/required_certificates.py,sha256=YW9-V78oFiZ-FmHlGP-8FQFS569VdDVyq9hfvCv31pk,7133
103
105
  mcp_proxy_adapter/examples/run_example.py,sha256=yp-a6HIrSk3ddQmbn0KkuKwErId0aNfj028TE6U-zmY,2626
@@ -107,6 +109,7 @@ mcp_proxy_adapter/examples/run_security_tests_fixed.py,sha256=aIf57LcAlNUEoroRue
107
109
  mcp_proxy_adapter/examples/security_test_client.py,sha256=HL0AhmmHZ9SHlUx6aJ32jTHJonjKmGil74ifLbsGkZA,48660
108
110
  mcp_proxy_adapter/examples/setup_test_environment.py,sha256=JkMqLpH5ZmkNKE7-WT52_kYMxEKLFOyQWbtip29TeiU,51629
109
111
  mcp_proxy_adapter/examples/simple_protocol_test.py,sha256=BzFUZvK9Fih3aG4IFLQTZPyPe_s6YjpZfB6uZmQ76rw,3969
112
+ mcp_proxy_adapter/examples/test_chk_hostname_automated.py,sha256=9n3V2rk3WdC9yPj40o_3MyRlVRM6jGBBgpyJ5PL2Fd8,7178
110
113
  mcp_proxy_adapter/examples/test_config.py,sha256=ekEoUZe9q484vU_0IxOVhQdNMVJXG3IpmQpP--VmuDI,6491
111
114
  mcp_proxy_adapter/examples/test_config_builder.py,sha256=SAcWIC54vzO0mrb1L9xZKK2IhNkZuSx7_cMEkC1Lm60,21607
112
115
  mcp_proxy_adapter/examples/test_examples.py,sha256=CYlVatdHUVC_rwv4NsvxFG3GXiKIyxPDUH43BOJHjrU,12330
@@ -131,8 +134,8 @@ mcp_proxy_adapter/schemas/base_schema.json,sha256=v9G9cGMd4dRhCZsOQ_FMqOi5VFyVbI
131
134
  mcp_proxy_adapter/schemas/openapi_schema.json,sha256=C3yLkwmDsvnLW9B5gnKKdBGl4zxkeU-rEmjTrNVsQU0,8405
132
135
  mcp_proxy_adapter/schemas/roles.json,sha256=pgf_ZyqKyXbfGUxvobpiLiSJz9zzxrMuoVWEkEpz3N8,764
133
136
  mcp_proxy_adapter/schemas/roles_schema.json,sha256=deHgI7L6GwfBXacOlNtDgDJelDThppClC3Ti4Eh8rJY,5659
134
- mcp_proxy_adapter-6.6.0.dist-info/METADATA,sha256=WSvtaCv_mkqE20kqYWmmJSt_UkCUqmKi4w7vVSLweeo,8510
135
- mcp_proxy_adapter-6.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
136
- mcp_proxy_adapter-6.6.0.dist-info/entry_points.txt,sha256=Bf-O5Aq80n22Ayu9fI9BgidzWqwzIVaqextAddTuHZw,563
137
- mcp_proxy_adapter-6.6.0.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
138
- mcp_proxy_adapter-6.6.0.dist-info/RECORD,,
137
+ mcp_proxy_adapter-6.6.1.dist-info/METADATA,sha256=b7eTkQKOdUxqilzA8AMTgJRr3f6SrnBwrhgRmAzML04,8510
138
+ mcp_proxy_adapter-6.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
139
+ mcp_proxy_adapter-6.6.1.dist-info/entry_points.txt,sha256=Bf-O5Aq80n22Ayu9fI9BgidzWqwzIVaqextAddTuHZw,563
140
+ mcp_proxy_adapter-6.6.1.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
141
+ mcp_proxy_adapter-6.6.1.dist-info/RECORD,,