mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.29__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/core/errors.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Module for defining errors and exceptions for the microservice.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Any, Dict, List, Optional, Union
|
|
6
5
|
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MicroserviceError(Exception):
|
|
@@ -33,20 +33,17 @@ class MicroserviceError(Exception):
|
|
|
33
33
|
super().__init__(message)
|
|
34
34
|
|
|
35
35
|
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"""
|
|
42
|
-
result = {"code": self.code, "message": self.message}
|
|
43
|
-
|
|
36
|
+
"""Convert error to dictionary format."""
|
|
37
|
+
result = {
|
|
38
|
+
"code": self.code,
|
|
39
|
+
"message": self.message
|
|
40
|
+
}
|
|
44
41
|
if self.data:
|
|
45
42
|
result["data"] = self.data
|
|
46
|
-
|
|
47
43
|
return result
|
|
48
44
|
|
|
49
45
|
|
|
46
|
+
|
|
50
47
|
class ParseError(MicroserviceError):
|
|
51
48
|
"""
|
|
52
49
|
Error while parsing JSON request.
|
|
@@ -199,23 +196,6 @@ class TimeoutError(MicroserviceError):
|
|
|
199
196
|
super().__init__(message, code=-32003, data=data)
|
|
200
197
|
|
|
201
198
|
|
|
202
|
-
def format_validation_errors(errors: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
203
|
-
"""
|
|
204
|
-
Formats validation errors into a standard format.
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
errors: List of validation errors.
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
Formatted validation errors.
|
|
211
|
-
"""
|
|
212
|
-
formatted_errors = {}
|
|
213
|
-
for error in errors:
|
|
214
|
-
loc = error.get("loc", [])
|
|
215
|
-
field = ".".join(str(item) for item in loc)
|
|
216
|
-
msg = error.get("msg", "Validation error")
|
|
217
|
-
formatted_errors[field] = msg
|
|
218
|
-
return formatted_errors
|
|
219
199
|
|
|
220
200
|
|
|
221
201
|
@dataclass
|
|
@@ -242,21 +222,6 @@ class ConfigError(MicroserviceError):
|
|
|
242
222
|
super().__init__(message, code=-32001, data={"type": "configuration_error"})
|
|
243
223
|
self.validation_results = validation_results or []
|
|
244
224
|
|
|
245
|
-
def get_error_summary(self) -> str:
|
|
246
|
-
"""Get summary of all validation errors."""
|
|
247
|
-
if not self.validation_results:
|
|
248
|
-
return self.message
|
|
249
|
-
|
|
250
|
-
error_messages = []
|
|
251
|
-
for result in self.validation_results:
|
|
252
|
-
if result.level == "error":
|
|
253
|
-
location = f"{result.section}.{result.key}" if result.key else result.section
|
|
254
|
-
error_msg = f"[{location}] {result.message}"
|
|
255
|
-
if result.suggestion:
|
|
256
|
-
error_msg += f" (Suggestion: {result.suggestion})"
|
|
257
|
-
error_messages.append(error_msg)
|
|
258
|
-
|
|
259
|
-
return "\n".join(error_messages)
|
|
260
225
|
|
|
261
226
|
|
|
262
227
|
class MissingConfigKeyError(ConfigError):
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Simple in-memory job manager for long-running demo commands.
|
|
6
|
+
Not for production use.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Dict, Optional, Awaitable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JobRecord:
|
|
15
|
+
def __init__(self, task: asyncio.Task):
|
|
16
|
+
self.task = task
|
|
17
|
+
self.status = "running"
|
|
18
|
+
self.result: Optional[Any] = None
|
|
19
|
+
self.error: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_jobs: Dict[str, JobRecord] = {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def enqueue_coroutine(coro: Awaitable[Any]) -> str:
|
|
26
|
+
job_id = str(uuid.uuid4())
|
|
27
|
+
task = asyncio.create_task(_run_job(job_id, coro))
|
|
28
|
+
_jobs[job_id] = JobRecord(task)
|
|
29
|
+
return job_id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _run_job(job_id: str, coro):
|
|
33
|
+
rec = _jobs[job_id]
|
|
34
|
+
try:
|
|
35
|
+
rec.result = await coro
|
|
36
|
+
rec.status = "completed"
|
|
37
|
+
except Exception as exc: # noqa: BLE001
|
|
38
|
+
rec.error = str(exc)
|
|
39
|
+
rec.status = "failed"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_job_status(job_id: str) -> Dict[str, Any]:
|
|
43
|
+
rec = _jobs.get(job_id)
|
|
44
|
+
if not rec:
|
|
45
|
+
return {"exists": False}
|
|
46
|
+
return {
|
|
47
|
+
"exists": True,
|
|
48
|
+
"status": rec.status,
|
|
49
|
+
"done": rec.task.done(),
|
|
50
|
+
"result": rec.result,
|
|
51
|
+
"error": rec.error,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
@@ -6,10 +6,8 @@ import logging
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import uuid
|
|
9
|
-
from logging.handlers import RotatingFileHandler
|
|
10
|
-
from typing import
|
|
11
|
-
|
|
12
|
-
from mcp_proxy_adapter.config import get_config
|
|
9
|
+
from logging.handlers import RotatingFileHandler
|
|
10
|
+
from typing import Optional
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class CustomFormatter(logging.Formatter):
|
|
@@ -47,11 +45,6 @@ class RequestContextFilter(logging.Filter):
|
|
|
47
45
|
super().__init__()
|
|
48
46
|
self.request_id = request_id
|
|
49
47
|
|
|
50
|
-
def filter(self, record):
|
|
51
|
-
# Add request_id attribute to the record
|
|
52
|
-
record.request_id = self.request_id or "no-request-id"
|
|
53
|
-
return True
|
|
54
|
-
|
|
55
48
|
|
|
56
49
|
class RequestLogger:
|
|
57
50
|
"""
|
|
@@ -245,19 +238,6 @@ def _parse_file_size(size_str) -> int:
|
|
|
245
238
|
return int(size_str)
|
|
246
239
|
|
|
247
240
|
|
|
248
|
-
def get_logger(name: str) -> logging.Logger:
|
|
249
|
-
"""
|
|
250
|
-
Get a get_global_logger() with the specified name.
|
|
251
|
-
|
|
252
|
-
Args:
|
|
253
|
-
name: Logger name.
|
|
254
|
-
|
|
255
|
-
Returns:
|
|
256
|
-
Configured get_global_logger() instance.
|
|
257
|
-
"""
|
|
258
|
-
return logging.getLogger(name)
|
|
259
|
-
|
|
260
|
-
|
|
261
241
|
# Global get_global_logger() for use throughout the application
|
|
262
242
|
# Initialize lazily to avoid import-time errors
|
|
263
243
|
logger = None
|
|
@@ -8,9 +8,6 @@ client certificates in mTLS connections.
|
|
|
8
8
|
import ssl
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Dict, Any, Optional
|
|
11
|
-
from starlette.applications import Starlette
|
|
12
|
-
from starlette.requests import Request
|
|
13
|
-
from starlette.responses import Response
|
|
14
11
|
from starlette.types import ASGIApp, Receive, Send, Scope
|
|
15
12
|
|
|
16
13
|
logger = logging.getLogger(__name__)
|
|
@@ -141,20 +138,3 @@ class MTLSASGIApp:
|
|
|
141
138
|
await send({"type": "http.response.body", "body": body})
|
|
142
139
|
|
|
143
140
|
|
|
144
|
-
def create_mtls_asgi_app(app: ASGIApp, ssl_config: Dict[str, Any]) -> ASGIApp:
|
|
145
|
-
"""
|
|
146
|
-
Create MTLS-enabled ASGI application.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
app: The underlying ASGI application (FastAPI)
|
|
150
|
-
ssl_config: SSL configuration for mTLS
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
MTLS-enabled ASGI application
|
|
154
|
-
"""
|
|
155
|
-
if ssl_config.get("mode") == "mtls" or ssl_config.get("verify_client", False):
|
|
156
|
-
get_global_logger().info("Creating MTLS-enabled ASGI application")
|
|
157
|
-
return MTLSASGIApp(app, ssl_config)
|
|
158
|
-
else:
|
|
159
|
-
get_global_logger().info("Creating standard ASGI application (no mTLS)")
|
|
160
|
-
return app
|
|
@@ -185,15 +185,3 @@ class MTLSASGIApp:
|
|
|
185
185
|
await send({"type": "http.response.body", "body": body})
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
def create_mtls_asgi_app(app, ssl_config: Dict[str, Any]):
|
|
189
|
-
"""
|
|
190
|
-
Create MTLS ASGI application wrapper.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
app: The underlying ASGI application
|
|
194
|
-
ssl_config: SSL configuration dictionary
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
MTLS ASGI app wrapper
|
|
198
|
-
"""
|
|
199
|
-
return MTLSASGIApp(app, ssl_config)
|
|
@@ -12,7 +12,6 @@ import asyncio
|
|
|
12
12
|
import ssl
|
|
13
13
|
import logging
|
|
14
14
|
from typing import Optional, Dict, Any
|
|
15
|
-
from pathlib import Path
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
@@ -79,39 +78,7 @@ class MTLSProxy:
|
|
|
79
78
|
get_global_logger().error(f"❌ Failed to start mTLS proxy: {e}")
|
|
80
79
|
raise
|
|
81
80
|
|
|
82
|
-
async def stop(self):
|
|
83
|
-
"""Stop the mTLS proxy server."""
|
|
84
|
-
if self.server:
|
|
85
|
-
self.server.close()
|
|
86
|
-
await self.server.wait_closed()
|
|
87
|
-
get_global_logger().info("🔐 mTLS Proxy stopped")
|
|
88
81
|
|
|
89
|
-
async def _handle_client(self, reader, writer):
|
|
90
|
-
"""Handle client connection."""
|
|
91
|
-
try:
|
|
92
|
-
# Get client address
|
|
93
|
-
client_addr = writer.get_extra_info('peername')
|
|
94
|
-
get_global_logger().info(f"🔐 mTLS connection from {client_addr}")
|
|
95
|
-
|
|
96
|
-
# Connect to internal server
|
|
97
|
-
internal_reader, internal_writer = await asyncio.open_connection(
|
|
98
|
-
self.internal_host, self.internal_port
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# Create bidirectional proxy
|
|
102
|
-
await asyncio.gather(
|
|
103
|
-
self._proxy_data(reader, internal_writer, "client->server"),
|
|
104
|
-
self._proxy_data(internal_reader, writer, "server->client")
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
except Exception as e:
|
|
108
|
-
get_global_logger().error(f"❌ Error handling client connection: {e}")
|
|
109
|
-
finally:
|
|
110
|
-
try:
|
|
111
|
-
writer.close()
|
|
112
|
-
await writer.wait_closed()
|
|
113
|
-
except:
|
|
114
|
-
pass
|
|
115
82
|
|
|
116
83
|
async def _proxy_data(self, reader, writer, direction):
|
|
117
84
|
"""Proxy data between reader and writer."""
|
|
@@ -132,50 +99,3 @@ class MTLSProxy:
|
|
|
132
99
|
pass
|
|
133
100
|
|
|
134
101
|
|
|
135
|
-
async def start_mtls_proxy(config: Dict[str, Any]) -> Optional[MTLSProxy]:
|
|
136
|
-
"""
|
|
137
|
-
Start mTLS proxy based on configuration.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
config: Application configuration
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
MTLSProxy instance if started, None otherwise
|
|
144
|
-
"""
|
|
145
|
-
# Check if mTLS is enabled
|
|
146
|
-
protocol = config.get("server", {}).get("protocol", "http")
|
|
147
|
-
verify_client = config.get("transport", {}).get("verify_client", False)
|
|
148
|
-
|
|
149
|
-
# Only start mTLS proxy if mTLS is explicitly enabled
|
|
150
|
-
if protocol != "mtls" and not verify_client:
|
|
151
|
-
get_global_logger().info("🌐 Regular mode: no mTLS proxy needed")
|
|
152
|
-
return None
|
|
153
|
-
|
|
154
|
-
# Get configuration
|
|
155
|
-
server_config = config.get("server", {})
|
|
156
|
-
transport_config = config.get("transport", {})
|
|
157
|
-
|
|
158
|
-
external_host = server_config.get("host", "0.0.0.0")
|
|
159
|
-
external_port = server_config.get("port", 8000)
|
|
160
|
-
internal_port = external_port + 1000 # Internal port
|
|
161
|
-
|
|
162
|
-
cert_file = transport_config.get("cert_file")
|
|
163
|
-
key_file = transport_config.get("key_file")
|
|
164
|
-
ca_cert = transport_config.get("ca_cert")
|
|
165
|
-
|
|
166
|
-
if not cert_file or not key_file:
|
|
167
|
-
get_global_logger().warning("⚠️ mTLS enabled but certificates not configured")
|
|
168
|
-
return None
|
|
169
|
-
|
|
170
|
-
# Create and start proxy
|
|
171
|
-
proxy = MTLSProxy(
|
|
172
|
-
external_host=external_host,
|
|
173
|
-
external_port=external_port,
|
|
174
|
-
internal_port=internal_port,
|
|
175
|
-
cert_file=cert_file,
|
|
176
|
-
key_file=key_file,
|
|
177
|
-
ca_cert=ca_cert
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
await proxy.start()
|
|
181
|
-
return proxy
|
|
@@ -24,84 +24,8 @@ class mTLSHandler(BaseHTTPRequestHandler):
|
|
|
24
24
|
self.main_app = main_app
|
|
25
25
|
super().__init__(*args, **kwargs)
|
|
26
26
|
|
|
27
|
-
def log_message(self, format, *args):
|
|
28
|
-
"""Override to use our get_global_logger()."""
|
|
29
|
-
get_global_logger().info(f"mTLS Server: {format % args}")
|
|
30
27
|
|
|
31
|
-
def do_GET(self):
|
|
32
|
-
"""Handle GET requests."""
|
|
33
|
-
try:
|
|
34
|
-
# Get client certificate
|
|
35
|
-
client_cert = None
|
|
36
|
-
if hasattr(self.connection, "getpeercert"):
|
|
37
|
-
client_cert = self.connection.getpeercert()
|
|
38
|
-
|
|
39
|
-
# Process request through main app if available
|
|
40
|
-
if self.main_app:
|
|
41
|
-
# Forward to main FastAPI app
|
|
42
|
-
response_data = self._forward_to_main_app(
|
|
43
|
-
"GET", self.path, client_cert
|
|
44
|
-
)
|
|
45
|
-
else:
|
|
46
|
-
# Simple response
|
|
47
|
-
response_data = {
|
|
48
|
-
"status": "ok",
|
|
49
|
-
"message": "mTLS connection successful",
|
|
50
|
-
"client_cert": client_cert,
|
|
51
|
-
"path": self.path,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Send response
|
|
55
|
-
self.send_response(200)
|
|
56
|
-
self.send_header("Content-type", "application/json")
|
|
57
|
-
self.end_headers()
|
|
58
|
-
self.wfile.write(json.dumps(response_data).encode())
|
|
59
28
|
|
|
60
|
-
except Exception as e:
|
|
61
|
-
get_global_logger().error(f"Error in mTLS GET handler: {e}")
|
|
62
|
-
self.send_error(500, str(e))
|
|
63
|
-
|
|
64
|
-
def do_POST(self):
|
|
65
|
-
"""Handle POST requests."""
|
|
66
|
-
try:
|
|
67
|
-
# Get content length
|
|
68
|
-
content_length = int(self.headers.get("Content-Length", 0))
|
|
69
|
-
|
|
70
|
-
# Read request body
|
|
71
|
-
post_data = None
|
|
72
|
-
if content_length > 0:
|
|
73
|
-
post_data = self.rfile.read(content_length)
|
|
74
|
-
|
|
75
|
-
# Get client certificate
|
|
76
|
-
client_cert = None
|
|
77
|
-
if hasattr(self.connection, "getpeercert"):
|
|
78
|
-
client_cert = self.connection.getpeercert()
|
|
79
|
-
|
|
80
|
-
# Process request through main app if available
|
|
81
|
-
if self.main_app:
|
|
82
|
-
# Forward to main FastAPI app
|
|
83
|
-
response_data = self._forward_to_main_app(
|
|
84
|
-
"POST", self.path, client_cert, post_data
|
|
85
|
-
)
|
|
86
|
-
else:
|
|
87
|
-
# Simple response
|
|
88
|
-
response_data = {
|
|
89
|
-
"status": "ok",
|
|
90
|
-
"message": "mTLS POST successful",
|
|
91
|
-
"client_cert": client_cert,
|
|
92
|
-
"path": self.path,
|
|
93
|
-
"data_received": len(post_data) if post_data else 0,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# Send response
|
|
97
|
-
self.send_response(200)
|
|
98
|
-
self.send_header("Content-type", "application/json")
|
|
99
|
-
self.end_headers()
|
|
100
|
-
self.wfile.write(json.dumps(response_data).encode())
|
|
101
|
-
|
|
102
|
-
except Exception as e:
|
|
103
|
-
get_global_logger().error(f"Error in mTLS POST handler: {e}")
|
|
104
|
-
self.send_error(500, str(e))
|
|
105
29
|
|
|
106
30
|
def _forward_to_main_app(
|
|
107
31
|
self,
|
|
@@ -172,8 +96,6 @@ class mTLSServer:
|
|
|
172
96
|
def _create_handler(self):
|
|
173
97
|
"""Create handler with main app reference."""
|
|
174
98
|
|
|
175
|
-
def handler(*args, **kwargs):
|
|
176
|
-
return mTLSHandler(*args, main_app=self.main_app, **kwargs)
|
|
177
99
|
|
|
178
100
|
return handler
|
|
179
101
|
|
|
@@ -226,99 +148,7 @@ class mTLSServer:
|
|
|
226
148
|
get_global_logger().error(f"Failed to start mTLS server: {e}")
|
|
227
149
|
return False
|
|
228
150
|
|
|
229
|
-
def _run_server(self):
|
|
230
|
-
"""Run the server (blocking)."""
|
|
231
|
-
try:
|
|
232
|
-
get_global_logger().info(
|
|
233
|
-
f"mTLS Server listening on https://{self.host}:{self.port}"
|
|
234
|
-
)
|
|
235
|
-
self.server.serve_forever()
|
|
236
|
-
except Exception as e:
|
|
237
|
-
get_global_logger().error(f"mTLS Server error: {e}")
|
|
238
|
-
finally:
|
|
239
|
-
self.running = False
|
|
240
|
-
|
|
241
|
-
def stop(self):
|
|
242
|
-
"""Stop mTLS server."""
|
|
243
|
-
if self.server and self.running:
|
|
244
|
-
try:
|
|
245
|
-
self.server.shutdown()
|
|
246
|
-
self.server.server_close()
|
|
247
|
-
self.running = False
|
|
248
|
-
get_global_logger().info("✅ mTLS Server stopped")
|
|
249
|
-
except Exception as e:
|
|
250
|
-
get_global_logger().error(f"Error stopping mTLS server: {e}")
|
|
251
|
-
|
|
252
|
-
def is_running(self) -> bool:
|
|
253
|
-
"""Check if server is running."""
|
|
254
|
-
return (
|
|
255
|
-
self.running
|
|
256
|
-
and self.server_thread
|
|
257
|
-
and self.server_thread.is_alive()
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def start_mtls_server_thread(
|
|
262
|
-
config: Dict[str, Any], main_app=None
|
|
263
|
-
) -> Optional[mTLSServer]:
|
|
264
|
-
"""
|
|
265
|
-
Start mTLS server in separate thread.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
config: Configuration dictionary
|
|
269
|
-
main_app: Main FastAPI application
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
mTLSServer instance or None if failed
|
|
273
|
-
"""
|
|
274
|
-
try:
|
|
275
|
-
# Extract SSL configuration
|
|
276
|
-
ssl_config = config.get("ssl", {})
|
|
277
|
-
|
|
278
|
-
# Check if mTLS is enabled
|
|
279
|
-
verify_client = ssl_config.get("verify_client", False)
|
|
280
|
-
if not verify_client:
|
|
281
|
-
get_global_logger().info(
|
|
282
|
-
"mTLS not enabled (verify_client=False), skipping mTLS server"
|
|
283
|
-
)
|
|
284
|
-
return None
|
|
285
|
-
|
|
286
|
-
# Get server configuration
|
|
287
|
-
server_config = config.get("server", {})
|
|
288
|
-
host = server_config.get("host", "127.0.0.1")
|
|
289
|
-
preferred_port = ssl_config.get("mtls_port", 8443) # Different port for mTLS
|
|
290
|
-
|
|
291
|
-
# For internal servers (mTLS), find available port if preferred is occupied
|
|
292
|
-
from mcp_proxy_adapter.core.utils import find_port_for_internal_server
|
|
293
|
-
port = find_port_for_internal_server(host, preferred_port)
|
|
294
|
-
|
|
295
|
-
# Get certificate paths - all required for mTLS
|
|
296
|
-
cert_file = ssl_config.get("cert_file")
|
|
297
|
-
key_file = ssl_config.get("key_file")
|
|
298
|
-
ca_cert_file = ssl_config.get("ca_cert")
|
|
299
|
-
|
|
300
|
-
if not cert_file or not key_file:
|
|
301
|
-
raise ValueError(
|
|
302
|
-
"mTLS server requires SSL certificate configuration. "
|
|
303
|
-
"Please configure 'ssl.cert_file' and 'ssl.key_file' in your configuration file."
|
|
304
|
-
)
|
|
305
151
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
port=port,
|
|
310
|
-
cert_file=cert_file,
|
|
311
|
-
key_file=key_file,
|
|
312
|
-
ca_cert_file=ca_cert_file,
|
|
313
|
-
main_app=main_app,
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
if mtls_server.start():
|
|
317
|
-
return mtls_server
|
|
318
|
-
else:
|
|
319
|
-
get_global_logger().error("Failed to start mTLS server")
|
|
320
|
-
return None
|
|
321
|
-
|
|
322
|
-
except Exception as e:
|
|
323
|
-
get_global_logger().error(f"Error starting mTLS server thread: {e}")
|
|
324
|
-
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|