mcp-proxy-adapter 6.6.0__py3-none-any.whl → 6.6.3__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.
Files changed (25) hide show
  1. mcp_proxy_adapter/api/app.py +28 -26
  2. mcp_proxy_adapter/config.py +2 -9
  3. mcp_proxy_adapter/core/server_adapter.py +1 -1
  4. mcp_proxy_adapter/examples/check_config.py +415 -0
  5. mcp_proxy_adapter/examples/config_builder.py +11 -17
  6. mcp_proxy_adapter/examples/{generate_certificates_bugfix.py → generate_certificates.py} +11 -0
  7. mcp_proxy_adapter/examples/generate_config.py +343 -0
  8. mcp_proxy_adapter/examples/run_full_test_suite.py +3 -3
  9. mcp_proxy_adapter/examples/security_test_client.py +6 -5
  10. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  11. mcp_proxy_adapter/examples/test_framework_complete.py +269 -0
  12. mcp_proxy_adapter/examples/test_mcp_server.py +188 -0
  13. mcp_proxy_adapter/main.py +11 -18
  14. mcp_proxy_adapter/version.py +1 -1
  15. {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/METADATA +1 -1
  16. {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/RECORD +19 -20
  17. mcp_proxy_adapter/examples/config_builder_simple.py +0 -271
  18. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -487
  19. mcp_proxy_adapter/examples/generate_certificates_cli.py +0 -406
  20. mcp_proxy_adapter/examples/generate_certificates_fixed.py +0 -313
  21. mcp_proxy_adapter/examples/generate_certificates_framework.py +0 -366
  22. mcp_proxy_adapter/examples/generate_certificates_openssl.py +0 -391
  23. {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/WHEEL +0 -0
  24. {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/entry_points.txt +0 -0
  25. {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/top_level.txt +0 -0
@@ -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 project root to the path to import config_builder
16
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
17
+
18
+ from mcp_proxy_adapter.examples.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())
@@ -113,7 +113,7 @@ class FullTestSuiteRunner:
113
113
 
114
114
  try:
115
115
  # Check if certificate generation script exists
116
- cert_script = self.working_dir / "generate_certificates_bugfix.py"
116
+ cert_script = self.working_dir / "mcp_proxy_adapter" / "examples" / "generate_certificates.py"
117
117
  if not cert_script.exists():
118
118
  self.print_error(
119
119
  f"Certificate generation script not found: {cert_script}"
@@ -167,7 +167,7 @@ class FullTestSuiteRunner:
167
167
 
168
168
  try:
169
169
  # Check if create_test_configs.py exists
170
- config_script = self.working_dir / "create_test_configs.py"
170
+ config_script = self.working_dir / "mcp_proxy_adapter" / "examples" / "create_test_configs.py"
171
171
  if not config_script.exists():
172
172
  self.print_error(f"Configuration generator not found: {config_script}")
173
173
  return False
@@ -194,7 +194,7 @@ class FullTestSuiteRunner:
194
194
  # Run the configuration generator
195
195
  cmd = [
196
196
  sys.executable,
197
- "create_test_configs.py",
197
+ "mcp_proxy_adapter/examples/create_test_configs.py",
198
198
  "--comprehensive-config",
199
199
  "comprehensive_config.json",
200
200
  ]
@@ -124,11 +124,12 @@ class SecurityTestClient:
124
124
  def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
125
125
  """Create SSL context for mTLS connections."""
126
126
  # For mTLS, we need client certificates - check if they exist
127
- # Paths are relative to project root (../../ from examples directory)
128
- # Use admin certificates to match server configuration
129
- cert_file = "../../certs/admin.crt"
130
- key_file = "../../certs/client_admin.key"
131
- ca_cert_file = "../../certs/localhost_server.crt"
127
+ # Use absolute paths to avoid issues with working directory
128
+ # Use newly generated admin_client_client.crt which is signed by the same CA as server
129
+ project_root = Path(__file__).parent.parent.parent
130
+ cert_file = str(project_root / "certs" / "admin_client_client.crt")
131
+ key_file = str(project_root / "keys" / "admin-client_client.key")
132
+ ca_cert_file = str(project_root / "certs" / "localhost_server.crt")
132
133
 
133
134
  # CRITICAL: For mTLS, certificates are REQUIRED
134
135
  if not os.path.exists(cert_file):
@@ -0,0 +1,211 @@
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.parent.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.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.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.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.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
+ https_config["transport"]["chk_hostname"] = False
118
+
119
+ # Save to temporary file and load with Config
120
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
121
+ json.dump(https_config, f)
122
+ temp_config_path = f.name
123
+
124
+ try:
125
+ config = Config(temp_config_path)
126
+
127
+
128
+ # Should respect the override
129
+ assert config.get("server.protocol") == "https"
130
+ assert config.get("transport.chk_hostname") is False
131
+
132
+ print("✅ HTTPS config with chk_hostname=False override works")
133
+ finally:
134
+ os.unlink(temp_config_path)
135
+
136
+
137
+ def test_chk_hostname_all_combinations():
138
+ """Test chk_hostname for all protocol and auth combinations."""
139
+ print("🧪 Testing chk_hostname for all combinations...")
140
+
141
+ protocols = [Protocol.HTTP, Protocol.HTTPS, Protocol.MTLS]
142
+ auth_methods = [AuthMethod.NONE, AuthMethod.TOKEN, AuthMethod.TOKEN_ROLES]
143
+
144
+ for protocol in protocols:
145
+ for auth_method in auth_methods:
146
+ # Create config
147
+ config_data = (ConfigBuilder()
148
+ .set_protocol(protocol)
149
+ .set_auth(auth_method)
150
+ .build())
151
+
152
+ # Save to temporary file and load with Config
153
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
154
+ json.dump(config_data, f)
155
+ temp_config_path = f.name
156
+
157
+ try:
158
+ config = Config(temp_config_path)
159
+
160
+ protocol_name = protocol.value
161
+ auth_name = auth_method.value
162
+
163
+ # Check chk_hostname based on protocol
164
+ if protocol_name == "http":
165
+ expected_chk_hostname = False
166
+ else: # https or mtls
167
+ expected_chk_hostname = True
168
+
169
+ actual_chk_hostname = config.get("transport.ssl.chk_hostname")
170
+
171
+ assert actual_chk_hostname == expected_chk_hostname, (
172
+ f"Protocol {protocol_name} with auth {auth_name}: "
173
+ f"expected chk_hostname={expected_chk_hostname}, "
174
+ f"got {actual_chk_hostname}"
175
+ )
176
+
177
+ print(f"✅ {protocol_name}+{auth_name}: chk_hostname={actual_chk_hostname}")
178
+
179
+ finally:
180
+ os.unlink(temp_config_path)
181
+
182
+ print("✅ All protocol+auth combinations have correct chk_hostname values")
183
+
184
+
185
+ def main():
186
+ """Run all chk_hostname tests."""
187
+ print("🧪 Running Automated chk_hostname Tests")
188
+ print("=" * 50)
189
+
190
+ try:
191
+ test_chk_hostname_default_config()
192
+ test_chk_hostname_http_config()
193
+ test_chk_hostname_https_config()
194
+ test_chk_hostname_mtls_config()
195
+ test_chk_hostname_override()
196
+ test_chk_hostname_all_combinations()
197
+
198
+ print("=" * 50)
199
+ print("🎉 All chk_hostname tests passed!")
200
+ return True
201
+
202
+ except Exception as e:
203
+ print(f"❌ Test failed: {e}")
204
+ import traceback
205
+ traceback.print_exc()
206
+ return False
207
+
208
+
209
+ if __name__ == "__main__":
210
+ success = main()
211
+ sys.exit(0 if success else 1)