mcp-proxy-adapter 6.2.23__py3-none-any.whl → 6.2.25__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 (33) hide show
  1. mcp_proxy_adapter/api/app.py +0 -3
  2. mcp_proxy_adapter/api/middleware/protocol_middleware.py +10 -10
  3. mcp_proxy_adapter/commands/health_command.py +1 -1
  4. mcp_proxy_adapter/config.py +16 -4
  5. mcp_proxy_adapter/core/protocol_manager.py +9 -9
  6. mcp_proxy_adapter/examples/create_certificates_simple.py +7 -17
  7. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  8. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  9. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  10. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  11. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  12. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  13. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  14. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  15. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  16. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  17. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  18. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  19. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  20. mcp_proxy_adapter/examples/generate_test_configs.py +70 -33
  21. mcp_proxy_adapter/examples/run_full_test_suite.py +302 -109
  22. mcp_proxy_adapter/examples/run_security_tests.py +14 -5
  23. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  24. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  25. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  26. mcp_proxy_adapter/main.py +0 -2
  27. mcp_proxy_adapter/version.py +1 -1
  28. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/METADATA +1 -1
  29. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/RECORD +33 -17
  30. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/WHEEL +0 -0
  31. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/entry_points.txt +0 -0
  32. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/licenses/LICENSE +0 -0
  33. {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Full Application Example
4
+ This is a complete application that demonstrates all features of MCP Proxy Adapter framework:
5
+ - Built-in commands
6
+ - Custom commands
7
+ - Dynamically loaded commands
8
+ - Built-in command hooks
9
+ - Application hooks
10
+ Author: Vasiliy Zdanovskiy
11
+ email: vasilyvz@gmail.com
12
+ """
13
+ import sys
14
+ import argparse
15
+ import logging
16
+ from pathlib import Path
17
+ # Add the framework to the path
18
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19
+ from mcp_proxy_adapter.core.app_factory import create_and_run_server
20
+ from mcp_proxy_adapter.api.app import create_app
21
+ from mcp_proxy_adapter.config import Config
22
+ from mcp_proxy_adapter.commands.command_registry import CommandRegistry
23
+ class FullApplication:
24
+ """Full application example with all framework features."""
25
+ def __init__(self, config_path: str):
26
+ self.config_path = config_path
27
+ self.config = Config(config_path)
28
+ self.app = None
29
+ self.command_registry = None
30
+ # Setup logging
31
+ logging.basicConfig(level=logging.INFO)
32
+ self.logger = logging.getLogger(__name__)
33
+ def setup_hooks(self):
34
+ """Setup application hooks."""
35
+ try:
36
+ # Import hooks
37
+ from hooks.application_hooks import ApplicationHooks
38
+ from hooks.builtin_command_hooks import BuiltinCommandHooks
39
+ # Register application hooks
40
+ self.logger.info("🔧 Setting up application hooks...")
41
+ # Register built-in command hooks
42
+ self.logger.info("🔧 Setting up built-in command hooks...")
43
+ # Note: In a real implementation, these hooks would be registered
44
+ # with the framework's hook system
45
+ self.logger.info("✅ Hooks setup completed")
46
+ except ImportError as e:
47
+ self.logger.warning(f"⚠️ Could not import hooks: {e}")
48
+ def setup_custom_commands(self):
49
+ """Setup custom commands."""
50
+ try:
51
+ self.logger.info("🔧 Setting up custom commands...")
52
+ # Import custom commands
53
+ from commands.custom_echo_command import CustomEchoCommand
54
+ from commands.dynamic_calculator_command import DynamicCalculatorCommand
55
+ # Register custom commands
56
+ # Note: In a real implementation, these would be registered
57
+ # with the framework's command registry
58
+ self.logger.info("✅ Custom commands setup completed")
59
+ except ImportError as e:
60
+ self.logger.warning(f"⚠️ Could not import custom commands: {e}")
61
+ def setup_proxy_endpoints(self):
62
+ """Setup proxy registration endpoints."""
63
+ try:
64
+ self.logger.info("🔧 Setting up proxy endpoints...")
65
+ # Import proxy endpoints
66
+ from proxy_endpoints import router as proxy_router
67
+ # Add proxy router to the application
68
+ self.app.include_router(proxy_router)
69
+ self.logger.info("✅ Proxy endpoints setup completed")
70
+ except ImportError as e:
71
+ self.logger.warning(f"⚠️ Could not import proxy endpoints: {e}")
72
+ def create_application(self):
73
+ """Create the FastAPI application."""
74
+ self.logger.info("🔧 Creating application...")
75
+ # Setup hooks and commands before creating app
76
+ self.setup_hooks()
77
+ self.setup_custom_commands()
78
+ # Create application with configuration
79
+ self.app = create_app(app_config=self.config)
80
+ # Setup proxy endpoints after app creation
81
+ self.setup_proxy_endpoints()
82
+ self.logger.info("✅ Application created successfully")
83
+ def run(self, host: str = None, port: int = None, debug: bool = False):
84
+ """Run the application using the factory method."""
85
+ # Override configuration if specified
86
+ config_overrides = {}
87
+ if host:
88
+ config_overrides["host"] = host
89
+ if port:
90
+ config_overrides["port"] = port
91
+ if debug:
92
+ config_overrides["debug"] = True
93
+ print(f"🚀 Starting Full Application Example")
94
+ print(f"📋 Configuration: {self.config_path}")
95
+ print(f"🔧 Features: Built-in commands, Custom commands, Dynamic commands, Hooks, Proxy endpoints")
96
+ print("=" * 60)
97
+ # Create application with configuration
98
+ self.create_application()
99
+ # Get server configuration
100
+ server_host = self.config.get("server.host", "0.0.0.0")
101
+ server_port = self.config.get("server.port", 8000)
102
+ server_debug = self.config.get("server.debug", False)
103
+ # Get SSL configuration
104
+ ssl_enabled = self.config.get("ssl.enabled", False)
105
+ ssl_cert_file = self.config.get("ssl.cert_file")
106
+ ssl_key_file = self.config.get("ssl.key_file")
107
+ ssl_ca_cert = self.config.get("ssl.ca_cert")
108
+ verify_client = self.config.get("ssl.verify_client", False)
109
+ print(f"🌐 Server: {server_host}:{server_port}")
110
+ print(f"🔧 Debug: {server_debug}")
111
+ if ssl_enabled:
112
+ print(f"🔐 SSL: Enabled")
113
+ print(f" Certificate: {ssl_cert_file}")
114
+ print(f" Key: {ssl_key_file}")
115
+ if ssl_ca_cert:
116
+ print(f" CA: {ssl_ca_cert}")
117
+ print(f" Client verification: {verify_client}")
118
+ print("=" * 60)
119
+ # Use hypercorn directly to run the application with proxy endpoints
120
+ try:
121
+ import hypercorn.asyncio
122
+ import hypercorn.config
123
+ import asyncio
124
+ # Configure hypercorn
125
+ config_hypercorn = hypercorn.config.Config()
126
+ config_hypercorn.bind = [f"{server_host}:{server_port}"]
127
+ config_hypercorn.loglevel = "debug" if server_debug else "info"
128
+ if ssl_enabled and ssl_cert_file and ssl_key_file:
129
+ config_hypercorn.certfile = ssl_cert_file
130
+ config_hypercorn.keyfile = ssl_key_file
131
+ if ssl_ca_cert:
132
+ config_hypercorn.ca_certs = ssl_ca_cert
133
+ if verify_client:
134
+ import ssl
135
+ config_hypercorn.verify_mode = ssl.CERT_REQUIRED
136
+ print(f"🔐 Starting HTTPS server with hypercorn...")
137
+ else:
138
+ print(f"🌐 Starting HTTP server with hypercorn...")
139
+ # Run the server
140
+ asyncio.run(hypercorn.asyncio.serve(self.app, config_hypercorn))
141
+ except ImportError:
142
+ print("❌ hypercorn not installed. Installing...")
143
+ import subprocess
144
+ subprocess.run([sys.executable, "-m", "pip", "install", "hypercorn"])
145
+ print("✅ hypercorn installed. Please restart the application.")
146
+ return
147
+ def main():
148
+ """Main entry point for the full application example."""
149
+ parser = argparse.ArgumentParser(description="Full Application Example")
150
+ parser.add_argument("--config", "-c", required=True, help="Path to configuration file")
151
+ parser.add_argument("--host", help="Server host")
152
+ parser.add_argument("--port", type=int, help="Server port")
153
+ parser.add_argument("--debug", action="store_true", help="Enable debug mode")
154
+ args = parser.parse_args()
155
+ # Create and run application
156
+ app = FullApplication(args.config)
157
+ app.run(host=args.host, port=args.port, debug=args.debug)
158
+ # Create global app instance for import
159
+ app = None
160
+
161
+ def get_app():
162
+ """Get the FastAPI application instance."""
163
+ global app
164
+ if app is None:
165
+ # Create a default configuration for import
166
+ config = Config("configs/mtls_with_roles.json") # Default config
167
+ app_instance = FullApplication("configs/mtls_with_roles.json")
168
+ app_instance.create_application()
169
+ app = app_instance.app
170
+ return app
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -0,0 +1,154 @@
1
+ """
2
+ Proxy Registration Endpoints
3
+ This module provides proxy registration endpoints for testing.
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+ from fastapi import APIRouter, HTTPException
8
+ from pydantic import BaseModel
9
+ from typing import Dict, List, Optional
10
+ import time
11
+ import uuid
12
+ # In-memory registry for testing
13
+ _registry: Dict[str, Dict] = {}
14
+ router = APIRouter(prefix="/proxy", tags=["proxy"])
15
+ class ServerRegistration(BaseModel):
16
+ """Server registration request model."""
17
+ server_id: str
18
+ server_url: str
19
+ server_name: str
20
+ description: Optional[str] = None
21
+ version: Optional[str] = "1.0.0"
22
+ capabilities: Optional[List[str]] = None
23
+ endpoints: Optional[Dict[str, str]] = None
24
+ auth_method: Optional[str] = "none"
25
+ security_enabled: Optional[bool] = False
26
+ class ServerUnregistration(BaseModel):
27
+ """Server unregistration request model."""
28
+ server_key: str # Use server_key directly
29
+ class HeartbeatData(BaseModel):
30
+ """Heartbeat data model."""
31
+ server_id: str
32
+ server_key: str
33
+ timestamp: Optional[int] = None
34
+ status: Optional[str] = "healthy"
35
+ class RegistrationResponse(BaseModel):
36
+ """Registration response model."""
37
+ success: bool
38
+ server_key: str
39
+ message: str
40
+ copy_number: int
41
+ class DiscoveryResponse(BaseModel):
42
+ """Discovery response model."""
43
+ success: bool
44
+ servers: List[Dict]
45
+ total: int
46
+ active: int
47
+ @router.post("/register", response_model=RegistrationResponse)
48
+ async def register_server(registration: ServerRegistration):
49
+ """Register a server with the proxy."""
50
+ try:
51
+ # Generate unique server key
52
+ server_key = f"{registration.server_id}_{uuid.uuid4().hex[:8]}"
53
+ copy_number = 1
54
+ # Store server information
55
+ _registry[server_key] = {
56
+ "server_id": registration.server_id,
57
+ "server_url": registration.server_url,
58
+ "server_name": registration.server_name,
59
+ "description": registration.description,
60
+ "version": registration.version,
61
+ "capabilities": registration.capabilities or [],
62
+ "endpoints": registration.endpoints or {},
63
+ "auth_method": registration.auth_method,
64
+ "security_enabled": registration.security_enabled,
65
+ "registered_at": int(time.time()),
66
+ "last_heartbeat": int(time.time()),
67
+ "status": "active"
68
+ }
69
+ return RegistrationResponse(
70
+ success=True,
71
+ server_key=server_key,
72
+ message=f"Server {registration.server_name} registered successfully",
73
+ copy_number=copy_number
74
+ )
75
+ except Exception as e:
76
+ raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}")
77
+ @router.post("/unregister")
78
+ async def unregister_server(unregistration: ServerUnregistration):
79
+ """Unregister a server from the proxy."""
80
+ try:
81
+ # Check if server exists in registry
82
+ if unregistration.server_key not in _registry:
83
+ raise HTTPException(status_code=404, detail="Server not found")
84
+ # Remove from registry
85
+ del _registry[unregistration.server_key]
86
+ return {"success": True, "message": "Server unregistered successfully"}
87
+ except HTTPException:
88
+ raise
89
+ except Exception as e:
90
+ raise HTTPException(status_code=500, detail=f"Unregistration failed: {str(e)}")
91
+ @router.post("/heartbeat")
92
+ async def send_heartbeat(heartbeat: HeartbeatData):
93
+ """Send heartbeat for a registered server."""
94
+ try:
95
+ if heartbeat.server_key not in _registry:
96
+ raise HTTPException(status_code=404, detail="Server not found")
97
+ # Update heartbeat information
98
+ _registry[heartbeat.server_key]["last_heartbeat"] = heartbeat.timestamp or int(time.time())
99
+ _registry[heartbeat.server_key]["status"] = heartbeat.status
100
+ return {"success": True, "message": "Heartbeat received"}
101
+ except HTTPException:
102
+ raise
103
+ except Exception as e:
104
+ raise HTTPException(status_code=500, detail=f"Heartbeat failed: {str(e)}")
105
+ @router.get("/discover", response_model=DiscoveryResponse)
106
+ async def discover_servers():
107
+ """Discover active servers."""
108
+ try:
109
+ current_time = int(time.time())
110
+ active_servers = []
111
+ for server_key, server in _registry.items():
112
+ # Consider server active if heartbeat was within last 5 minutes
113
+ if current_time - server["last_heartbeat"] < 300:
114
+ active_servers.append({
115
+ "server_key": server_key,
116
+ "server_id": server["server_id"],
117
+ "server_name": server["server_name"],
118
+ "server_url": server["server_url"],
119
+ "status": server["status"],
120
+ "last_heartbeat": server["last_heartbeat"]
121
+ })
122
+ return DiscoveryResponse(
123
+ success=True,
124
+ servers=active_servers,
125
+ total=len(_registry),
126
+ active=len(active_servers)
127
+ )
128
+ except Exception as e:
129
+ raise HTTPException(status_code=500, detail=f"Discovery failed: {str(e)}")
130
+ @router.get("/status")
131
+ async def get_proxy_status():
132
+ """Get proxy status."""
133
+ try:
134
+ current_time = int(time.time())
135
+ active_count = sum(
136
+ 1 for server in _registry.values()
137
+ if current_time - server["last_heartbeat"] < 300
138
+ )
139
+ return {
140
+ "success": True,
141
+ "total_registered": len(_registry),
142
+ "active_servers": active_count,
143
+ "inactive_servers": len(_registry) - active_count
144
+ }
145
+ except Exception as e:
146
+ raise HTTPException(status_code=500, detail=f"Status check failed: {str(e)}")
147
+ @router.delete("/clear")
148
+ async def clear_registry():
149
+ """Clear the registry (for testing)."""
150
+ try:
151
+ _registry.clear()
152
+ return {"success": True, "message": "Registry cleared"}
153
+ except Exception as e:
154
+ raise HTTPException(status_code=500, detail=f"Clear failed: {str(e)}")
@@ -9,7 +9,7 @@ import json
9
9
  import os
10
10
  import argparse
11
11
  from typing import Dict, Any
12
- def generate_http_simple_config(port: int = 20000, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
12
+ def generate_http_simple_config(port: int = 20000, certs_dir: str = "certs", keys_dir: str = "keys") -> Dict[str, Any]:
13
13
  """Generate HTTP configuration without authorization."""
14
14
  return {
15
15
  "server": {"host": "127.0.0.1", "port": port},
@@ -29,7 +29,7 @@ def generate_http_simple_config(port: int = 20000, certs_dir: str = "./certs", k
29
29
  },
30
30
  "protocols": {"enabled": True, "allowed_protocols": ["http"]}
31
31
  }
32
- def generate_http_token_config(port: int = 20001, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
32
+ def generate_http_token_config(port: int = 20001, certs_dir: str = "certs", keys_dir: str = "keys", roles_file: str = "configs/roles.json") -> Dict[str, Any]:
33
33
  """Generate HTTP configuration with token authorization."""
34
34
  return {
35
35
  "server": {"host": "127.0.0.1", "port": port},
@@ -48,7 +48,7 @@ def generate_http_token_config(port: int = 20001, certs_dir: str = "./certs", ke
48
48
  "proxy-token-123": "proxy"
49
49
  }
50
50
  },
51
- "permissions": {"enabled": True, "roles_file": "./roles.json"}
51
+ "permissions": {"enabled": True, "roles_file": roles_file}
52
52
  },
53
53
  "registration": {
54
54
  "enabled": True,
@@ -61,14 +61,14 @@ def generate_http_token_config(port: int = 20001, certs_dir: str = "./certs", ke
61
61
  },
62
62
  "protocols": {"enabled": True, "allowed_protocols": ["http"]}
63
63
  }
64
- def generate_https_simple_config(port: int = 20002, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
64
+ def generate_https_simple_config(port: int = 20002, certs_dir: str = "certs", keys_dir: str = "keys") -> Dict[str, Any]:
65
65
  """Generate HTTPS configuration without client certificate verification and authorization."""
66
66
  return {
67
67
  "server": {"host": "127.0.0.1", "port": port},
68
68
  "ssl": {
69
69
  "enabled": True,
70
- "cert_file": "./certs/localhost_server.crt",
71
- "key_file": "./keys/localhost_server.key"
70
+ "cert_file": f"{certs_dir}/localhost_server.crt",
71
+ "key_file": f"{keys_dir}/localhost_server.key"
72
72
  },
73
73
  "security": {"enabled": False},
74
74
  "registration": {
@@ -82,14 +82,14 @@ def generate_https_simple_config(port: int = 20002, certs_dir: str = "./certs",
82
82
  },
83
83
  "protocols": {"enabled": True, "allowed_protocols": ["http", "https"]}
84
84
  }
85
- def generate_https_token_config(port: int = 20003, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
85
+ def generate_https_token_config(port: int = 20003, certs_dir: str = "certs", keys_dir: str = "keys") -> Dict[str, Any]:
86
86
  """Generate HTTPS configuration without client certificate verification with token authorization."""
87
87
  return {
88
88
  "server": {"host": "127.0.0.1", "port": port},
89
89
  "ssl": {
90
90
  "enabled": True,
91
- "cert_file": "./certs/localhost_server.crt",
92
- "key_file": "./keys/localhost_server.key"
91
+ "cert_file": f"{certs_dir}/localhost_server.crt",
92
+ "key_file": f"{keys_dir}/localhost_server.key"
93
93
  },
94
94
  "security": {
95
95
  "enabled": True,
@@ -104,7 +104,7 @@ def generate_https_token_config(port: int = 20003, certs_dir: str = "./certs", k
104
104
  "proxy-token-123": "proxy"
105
105
  }
106
106
  },
107
- "permissions": {"enabled": True, "roles_file": "./roles.json"}
107
+ "permissions": {"enabled": True, "roles_file": "./configs/roles.json"}
108
108
  },
109
109
  "registration": {
110
110
  "enabled": True,
@@ -117,33 +117,33 @@ def generate_https_token_config(port: int = 20003, certs_dir: str = "./certs", k
117
117
  },
118
118
  "protocols": {"enabled": True, "allowed_protocols": ["http", "https"]}
119
119
  }
120
- def generate_mtls_no_roles_config(port: int = 20004, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
120
+ def generate_mtls_no_roles_config(port: int = 20004, certs_dir: str = "certs", keys_dir: str = "keys") -> Dict[str, Any]:
121
121
  """Generate mTLS configuration without roles."""
122
122
  return {
123
123
  "server": {"host": "127.0.0.1", "port": port},
124
124
  "ssl": {
125
125
  "enabled": True,
126
- "cert_file": "./certs/localhost_server.crt",
127
- "key_file": "./keys/localhost_server.key",
128
- "ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
126
+ "cert_file": f"{certs_dir}/localhost_server.crt",
127
+ "key_file": f"{keys_dir}/localhost_server.key",
128
+ "ca_cert": f"{certs_dir}/mcp_proxy_adapter_ca_ca.crt",
129
129
  "verify_client": True
130
130
  },
131
131
  "security": {
132
132
  "enabled": True,
133
133
  "auth": {"enabled": True, "methods": ["certificate"]},
134
- "permissions": {"enabled": True, "roles_file": "./roles.json"}
134
+ "permissions": {"enabled": False}
135
135
  },
136
136
  "protocols": {"enabled": True, "allowed_protocols": ["https", "mtls"]}
137
137
  }
138
- def generate_mtls_with_roles_config(port: int = 20005, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
138
+ def generate_mtls_with_roles_config(port: int = 20005, certs_dir: str = "certs", keys_dir: str = "keys", roles_file: str = "configs/roles.json") -> Dict[str, Any]:
139
139
  """Generate mTLS configuration with roles."""
140
140
  return {
141
141
  "server": {"host": "127.0.0.1", "port": port},
142
142
  "ssl": {
143
143
  "enabled": True,
144
- "cert_file": "./certs/localhost_server.crt",
145
- "key_file": "./keys/localhost_server.key",
146
- "ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
144
+ "cert_file": f"{certs_dir}/localhost_server.crt",
145
+ "key_file": f"{keys_dir}/localhost_server.key",
146
+ "ca_cert": f"{certs_dir}/mcp_proxy_adapter_ca_ca.crt",
147
147
  "verify_client": True
148
148
  },
149
149
  "registration": {
@@ -161,7 +161,7 @@ def generate_mtls_with_roles_config(port: int = 20005, certs_dir: str = "./certs
161
161
  "security": {
162
162
  "enabled": True,
163
163
  "auth": {"enabled": True, "methods": ["certificate"]},
164
- "permissions": {"enabled": True, "roles_file": "./roles.json"}
164
+ "permissions": {"enabled": True, "roles_file": roles_file}
165
165
  },
166
166
  "protocols": {"enabled": True, "allowed_protocols": ["https", "mtls"]}
167
167
  }
@@ -222,15 +222,18 @@ def generate_roles_config() -> Dict[str, Any]:
222
222
  "tokens": ["proxy-token-123"]
223
223
  }
224
224
  }
225
- def generate_all_configs(output_dir: str, certs_dir: str = "./certs", keys_dir: str = "./keys") -> None:
225
+ def generate_all_configs(output_dir: str, certs_dir: str = "certs", keys_dir: str = "keys", roles_file: str = "configs/roles.json") -> None:
226
226
  """Generate all 6 configuration types and save them to files."""
227
+ # Ensure output directory exists first
228
+ os.makedirs(output_dir, exist_ok=True)
229
+
227
230
  configs = {
228
231
  "http_simple": generate_http_simple_config(20000, certs_dir, keys_dir),
229
- "http_token": generate_http_token_config(20001, certs_dir, keys_dir),
232
+ "http_token": generate_http_token_config(20001, certs_dir, keys_dir, roles_file),
230
233
  "https_simple": generate_https_simple_config(20002, certs_dir, keys_dir),
231
234
  "https_token": generate_https_token_config(20003, certs_dir, keys_dir),
232
235
  "mtls_no_roles": generate_mtls_no_roles_config(20004, certs_dir, keys_dir),
233
- "mtls_with_roles": generate_mtls_with_roles_config(20005, certs_dir, keys_dir)
236
+ "mtls_with_roles": generate_mtls_with_roles_config(20005, certs_dir, keys_dir, roles_file)
234
237
  }
235
238
  # Ensure output directory exists
236
239
  os.makedirs(output_dir, exist_ok=True)
@@ -242,17 +245,36 @@ def generate_all_configs(output_dir: str, certs_dir: str = "./certs", keys_dir:
242
245
  print(f"Generated: {filename}")
243
246
  # Generate roles configuration
244
247
  roles_config = generate_roles_config()
248
+
249
+ # Create roles.json in the root directory (test environment root) for compatibility
250
+ # When running as module, we need to create roles.json in the current working directory
251
+ # This is the directory where the user is running the command from
252
+ try:
253
+ # Get the current working directory where the user is running the command
254
+ current_dir = os.getcwd()
255
+ root_roles_filename = os.path.join(current_dir, "roles.json")
256
+
257
+ # Create roles.json in the current working directory
258
+ with open(root_roles_filename, 'w', encoding='utf-8') as f:
259
+ json.dump(roles_config, f, indent=2, ensure_ascii=False)
260
+ print(f"Generated: {root_roles_filename}")
261
+
262
+ # Also create a copy in the output directory for reference
263
+ backup_roles_filename = os.path.join(output_dir, "roles_backup.json")
264
+ with open(backup_roles_filename, 'w', encoding='utf-8') as f:
265
+ json.dump(roles_config, f, indent=2, ensure_ascii=False)
266
+ print(f"Generated backup: {backup_roles_filename}")
267
+
268
+ except Exception as e:
269
+ print(f"Warning: Could not create roles.json in current directory: {e}")
270
+ print(f"Current working directory: {os.getcwd()}")
271
+ print(f"Script directory: {os.path.dirname(os.path.abspath(__file__))}")
272
+
273
+ # Also create roles.json in configs directory for reference
245
274
  roles_filename = os.path.join(output_dir, "roles.json")
246
275
  with open(roles_filename, 'w', encoding='utf-8') as f:
247
276
  json.dump(roles_config, f, indent=2, ensure_ascii=False)
248
277
  print(f"Generated: {roles_filename}")
249
- # Also create roles.json in certs directory for compatibility
250
- certs_dir = os.path.join(os.path.dirname(output_dir), "certs")
251
- if os.path.exists(certs_dir):
252
- certs_roles_filename = os.path.join(certs_dir, "roles.json")
253
- with open(certs_roles_filename, 'w', encoding='utf-8') as f:
254
- json.dump(roles_config, f, indent=2, ensure_ascii=False)
255
- print(f"Generated: {certs_roles_filename}")
256
278
  print(f"\nGenerated {len(configs)} configuration files and roles.json in {output_dir}")
257
279
 
258
280
  print("\n" + "=" * 60)
@@ -273,12 +295,27 @@ def main():
273
295
  )
274
296
  parser.add_argument(
275
297
  "--output-dir",
276
- default="./configs",
277
- help="Output directory for configuration files (default: ./configs)"
298
+ default="configs",
299
+ help="Output directory for configuration files (default: configs)"
300
+ )
301
+ parser.add_argument(
302
+ "--certs-dir",
303
+ default="certs",
304
+ help="Certificates directory (default: certs)"
305
+ )
306
+ parser.add_argument(
307
+ "--keys-dir",
308
+ default="keys",
309
+ help="Keys directory (default: keys)"
310
+ )
311
+ parser.add_argument(
312
+ "--roles-file",
313
+ default="configs/roles.json",
314
+ help="Roles file path (default: configs/roles.json)"
278
315
  )
279
316
  args = parser.parse_args()
280
317
  try:
281
- generate_all_configs(args.output_dir)
318
+ generate_all_configs(args.output_dir, args.certs_dir, args.keys_dir, args.roles_file)
282
319
  print("Configuration generation completed successfully!")
283
320
  except Exception as e:
284
321
  print(f"\n❌ CONFIGURATION GENERATION FAILED: {e}")