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.
- mcp_proxy_adapter/api/middleware/unified_security.py +8 -12
- mcp_proxy_adapter/config.py +76 -117
- mcp_proxy_adapter/core/protocol_manager.py +25 -42
- mcp_proxy_adapter/core/security_integration.py +60 -97
- mcp_proxy_adapter/core/server_adapter.py +4 -0
- mcp_proxy_adapter/examples/check_config.py +415 -0
- mcp_proxy_adapter/examples/config_builder.py +142 -428
- mcp_proxy_adapter/examples/config_builder_simple.py +271 -0
- mcp_proxy_adapter/examples/generate_config.py +343 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +186 -23
- mcp_proxy_adapter/examples/security_test_client.py +21 -7
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +214 -0
- mcp_proxy_adapter/examples/test_config_builder.py +40 -0
- mcp_proxy_adapter/main.py +54 -27
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/RECORD +20 -16
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/top_level.txt +0 -0
@@ -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()
|