mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.0__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 (259) hide show
  1. mcp_proxy_adapter/api/app.py +174 -80
  2. mcp_proxy_adapter/api/handlers.py +16 -5
  3. mcp_proxy_adapter/api/middleware/__init__.py +7 -2
  4. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  5. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  6. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  7. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  8. mcp_proxy_adapter/commands/__init__.py +7 -1
  9. mcp_proxy_adapter/commands/base.py +7 -4
  10. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  11. mcp_proxy_adapter/commands/command_registry.py +8 -0
  12. mcp_proxy_adapter/commands/echo_command.py +81 -0
  13. mcp_proxy_adapter/commands/help_command.py +21 -14
  14. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  15. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  16. mcp_proxy_adapter/commands/security_command.py +488 -0
  17. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  18. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  19. mcp_proxy_adapter/config.py +81 -21
  20. mcp_proxy_adapter/core/app_factory.py +326 -0
  21. mcp_proxy_adapter/core/client_security.py +384 -0
  22. mcp_proxy_adapter/core/logging.py +8 -3
  23. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  24. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  25. mcp_proxy_adapter/core/protocol_manager.py +9 -0
  26. mcp_proxy_adapter/core/proxy_client.py +602 -0
  27. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  28. mcp_proxy_adapter/core/security_adapter.py +12 -15
  29. mcp_proxy_adapter/core/security_integration.py +277 -0
  30. mcp_proxy_adapter/core/server_adapter.py +345 -0
  31. mcp_proxy_adapter/core/server_engine.py +364 -0
  32. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  33. mcp_proxy_adapter/examples/README.md +230 -97
  34. mcp_proxy_adapter/examples/README_EN.md +258 -0
  35. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  36. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  37. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  38. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  44. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  45. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  46. mcp_proxy_adapter/examples/cert_config.json +9 -0
  47. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  48. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  49. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  50. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  51. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  52. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  53. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  54. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  55. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  56. mcp_proxy_adapter/examples/certs/client.key +52 -0
  57. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  58. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  59. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  61. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  62. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  63. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  64. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  65. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  66. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  67. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  68. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/server.key +52 -0
  72. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  73. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  74. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  75. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  76. mcp_proxy_adapter/examples/certs/user.key +52 -0
  77. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  78. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  79. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  80. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  81. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  82. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  83. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  84. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  85. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  86. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  87. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  88. mcp_proxy_adapter/examples/demo_client.py +341 -0
  89. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  90. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  91. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  92. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  93. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  94. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  95. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  96. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  99. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  100. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  102. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  103. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  104. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  105. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  106. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  107. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  108. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  109. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  124. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  125. mcp_proxy_adapter/examples/roles.json +38 -0
  126. mcp_proxy_adapter/examples/run_example.py +81 -0
  127. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  128. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  129. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  130. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  131. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  132. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  133. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  134. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  135. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  136. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  137. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  138. mcp_proxy_adapter/examples/test_examples.py +344 -0
  139. mcp_proxy_adapter/examples/universal_client.py +628 -0
  140. mcp_proxy_adapter/main.py +21 -10
  141. mcp_proxy_adapter/utils/config_generator.py +639 -0
  142. mcp_proxy_adapter/version.py +2 -1
  143. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  144. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  145. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  146. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  147. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  148. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  149. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  150. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  151. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  152. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  153. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  154. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  155. mcp_proxy_adapter/api/middleware/security.py +0 -376
  156. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  157. mcp_proxy_adapter/examples/__init__.py +0 -7
  158. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  159. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  160. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  161. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  162. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  163. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  164. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  165. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  166. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  167. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  168. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  169. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  170. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  171. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  172. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  173. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  174. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  175. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  176. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  177. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  178. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  179. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  180. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  181. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  182. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  183. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  184. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  185. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  186. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  187. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  188. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  189. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  190. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  191. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  192. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  193. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  194. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  195. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  196. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  197. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  198. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  199. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  200. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  201. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  202. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  203. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  204. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  205. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  206. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  207. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  208. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  209. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  210. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  211. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  212. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  213. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  214. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  215. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  216. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  217. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  218. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  219. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  220. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  221. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  222. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  223. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  224. mcp_proxy_adapter/tests/__init__.py +0 -0
  225. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  226. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  227. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  228. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  229. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  230. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  231. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  232. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  233. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  234. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  235. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  236. mcp_proxy_adapter/tests/conftest.py +0 -131
  237. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  239. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  240. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  241. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  242. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  244. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  245. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  246. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  247. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  248. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  249. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  250. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  251. mcp_proxy_adapter/tests/test_config.py +0 -127
  252. mcp_proxy_adapter/tests/test_utils.py +0 -65
  253. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  254. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  255. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  256. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  257. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  258. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  259. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -1,522 +0,0 @@
1
- """
2
- Tests for API handlers module.
3
-
4
- This module contains comprehensive tests for the handlers module
5
- to ensure 90%+ code coverage.
6
- """
7
-
8
- import pytest
9
- import json
10
- from unittest.mock import Mock, patch, MagicMock, AsyncMock
11
- from typing import Dict, Any, List
12
-
13
- from fastapi import Request
14
-
15
- from mcp_proxy_adapter.api.handlers import (
16
- execute_command, handle_batch_json_rpc, handle_json_rpc,
17
- _create_error_response, get_server_health, get_commands_list
18
- )
19
- from mcp_proxy_adapter.core.errors import (
20
- NotFoundError, MethodNotFoundError, InvalidRequestError,
21
- InternalError, MicroserviceError, InvalidParamsError
22
- )
23
- from mcp_proxy_adapter.commands.command_registry import registry
24
-
25
-
26
- class TestExecuteCommand:
27
- """Test cases for execute_command function."""
28
-
29
- @pytest.fixture
30
- def mock_registry(self):
31
- """Create a mock registry for testing."""
32
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_reg:
33
- yield mock_reg
34
-
35
- @pytest.fixture
36
- def mock_command_class(self):
37
- """Create a mock command class for testing."""
38
- class MockCommand:
39
- name = "test_command"
40
-
41
- @classmethod
42
- def get_schema(cls):
43
- return {"type": "object", "properties": {}}
44
-
45
- @classmethod
46
- async def run(cls, **kwargs):
47
- return MockResult()
48
-
49
- async def execute(self, **kwargs):
50
- return {"result": "test"}
51
-
52
- class MockResult:
53
- def to_dict(self):
54
- return {"result": "test"}
55
-
56
- return MockCommand
57
-
58
- @pytest.mark.asyncio
59
- async def test_execute_command_success(self, mock_registry, mock_command_class):
60
- """Test successful command execution."""
61
- mock_registry.get_command.return_value = mock_command_class
62
-
63
- result = await execute_command("test_command", {"param": "value"})
64
-
65
- assert result == {"result": "test"}
66
- mock_registry.get_command.assert_called_once_with("test_command")
67
-
68
- @pytest.mark.asyncio
69
- async def test_execute_command_with_request_id(self, mock_registry, mock_command_class):
70
- """Test command execution with request ID."""
71
- mock_registry.get_command.return_value = mock_command_class
72
-
73
- result = await execute_command("test_command", {"param": "value"}, "req_123")
74
-
75
- assert result == {"result": "test"}
76
-
77
- @pytest.mark.asyncio
78
- async def test_execute_command_not_found(self, mock_registry):
79
- """Test command execution when command not found."""
80
- mock_registry.get_command.side_effect = NotFoundError("Command not found")
81
-
82
- with pytest.raises(MethodNotFoundError):
83
- await execute_command("nonexistent_command", {})
84
-
85
- @pytest.mark.asyncio
86
- async def test_execute_command_microservice_error(self, mock_registry):
87
- """Test command execution with microservice error."""
88
- mock_registry.get_command.side_effect = InvalidRequestError("Invalid parameters")
89
-
90
- with pytest.raises(InvalidRequestError):
91
- await execute_command("test_command", {})
92
-
93
- @pytest.mark.asyncio
94
- async def test_execute_command_general_exception(self, mock_registry):
95
- """Test command execution with general exception."""
96
- mock_registry.get_command.side_effect = Exception("Unexpected error")
97
-
98
- with pytest.raises(InternalError):
99
- await execute_command("test_command", {})
100
-
101
- @pytest.mark.asyncio
102
- async def test_execute_command_execution_time_logging(self, mock_registry, mock_command_class):
103
- """Test that execution time is logged."""
104
- mock_registry.get_command.return_value = mock_command_class
105
-
106
- with patch('mcp_proxy_adapter.api.handlers.logger') as mock_logger:
107
- await execute_command("test_command", {})
108
-
109
- # Check that timing was logged
110
- mock_logger.info.assert_called()
111
- call_args = mock_logger.info.call_args[0][0]
112
- assert "executed in" in call_args
113
-
114
-
115
- class TestHandleBatchJsonRpc:
116
- """Test cases for handle_batch_json_rpc function."""
117
-
118
- @pytest.fixture
119
- def mock_request(self):
120
- """Create a mock request object."""
121
- request = Mock(spec=Request)
122
- request.state.request_id = "batch_123"
123
- return request
124
-
125
- @pytest.mark.asyncio
126
- async def test_handle_batch_json_rpc_success(self, mock_request):
127
- """Test successful batch JSON-RPC handling."""
128
- batch_requests = [
129
- {"jsonrpc": "2.0", "method": "help", "id": 1},
130
- {"jsonrpc": "2.0", "method": "config", "id": 2}
131
- ]
132
-
133
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
134
- mock_handle.side_effect = [
135
- {"jsonrpc": "2.0", "result": "help_result", "id": 1},
136
- {"jsonrpc": "2.0", "result": "config_result", "id": 2}
137
- ]
138
-
139
- responses = await handle_batch_json_rpc(batch_requests, mock_request)
140
-
141
- assert len(responses) == 2
142
- assert responses[0]["id"] == 1
143
- assert responses[1]["id"] == 2
144
-
145
- @pytest.mark.asyncio
146
- async def test_handle_batch_json_rpc_without_request(self):
147
- """Test batch JSON-RPC handling without request object."""
148
- batch_requests = [
149
- {"jsonrpc": "2.0", "method": "help", "id": 1}
150
- ]
151
-
152
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
153
- mock_handle.return_value = {"jsonrpc": "2.0", "result": "help_result", "id": 1}
154
-
155
- responses = await handle_batch_json_rpc(batch_requests)
156
-
157
- assert len(responses) == 1
158
-
159
- @pytest.mark.asyncio
160
- async def test_handle_batch_json_rpc_empty_batch(self):
161
- """Test batch JSON-RPC handling with empty batch."""
162
- responses = await handle_batch_json_rpc([])
163
-
164
- assert responses == []
165
-
166
-
167
- class TestHandleJsonRpc:
168
- """Test cases for handle_json_rpc function."""
169
-
170
- @pytest.mark.asyncio
171
- async def test_handle_json_rpc_success(self):
172
- """Test successful JSON-RPC handling."""
173
- request_data = {
174
- "jsonrpc": "2.0",
175
- "method": "help",
176
- "id": 1
177
- }
178
-
179
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
180
- mock_execute.return_value = {"result": "help_result"}
181
-
182
- response = await handle_json_rpc(request_data)
183
-
184
- assert response["jsonrpc"] == "2.0"
185
- assert response["result"] == {"result": "help_result"}
186
- assert response["id"] == 1
187
-
188
- @pytest.mark.asyncio
189
- async def test_handle_json_rpc_with_request_id(self):
190
- """Test JSON-RPC handling with request ID."""
191
- request_data = {
192
- "jsonrpc": "2.0",
193
- "method": "help",
194
- "id": 1
195
- }
196
-
197
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
198
- mock_execute.return_value = {"result": "help_result"}
199
-
200
- response = await handle_json_rpc(request_data, "req_123")
201
-
202
- assert response["jsonrpc"] == "2.0"
203
- assert response["result"] == {"result": "help_result"}
204
-
205
- @pytest.mark.asyncio
206
- async def test_handle_json_rpc_invalid_version(self):
207
- """Test JSON-RPC handling with invalid version."""
208
- request_data = {
209
- "jsonrpc": "1.0", # Invalid version
210
- "method": "help",
211
- "id": 1
212
- }
213
-
214
- response = await handle_json_rpc(request_data)
215
-
216
- assert response["jsonrpc"] == "2.0"
217
- assert "error" in response
218
- assert response["error"]["code"] == -32600 # Invalid Request
219
-
220
- @pytest.mark.asyncio
221
- async def test_handle_json_rpc_missing_method(self):
222
- """Test JSON-RPC handling with missing method."""
223
- request_data = {
224
- "jsonrpc": "2.0",
225
- "id": 1
226
- # Missing method
227
- }
228
-
229
- response = await handle_json_rpc(request_data)
230
-
231
- assert response["jsonrpc"] == "2.0"
232
- assert "error" in response
233
- assert response["error"]["code"] == -32600 # Invalid Request
234
-
235
- @pytest.mark.asyncio
236
- async def test_handle_json_rpc_microservice_error(self):
237
- """Test JSON-RPC handling with microservice error."""
238
- request_data = {
239
- "jsonrpc": "2.0",
240
- "method": "help",
241
- "id": 1
242
- }
243
-
244
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
245
- mock_execute.side_effect = InvalidRequestError("Invalid parameters")
246
-
247
- response = await handle_json_rpc(request_data)
248
-
249
- assert response["jsonrpc"] == "2.0"
250
- assert "error" in response
251
- assert response["error"]["code"] == -32600 # Invalid Request
252
-
253
- @pytest.mark.asyncio
254
- async def test_handle_json_rpc_unhandled_exception(self):
255
- """Test JSON-RPC handling with unhandled exception."""
256
- request_data = {
257
- "jsonrpc": "2.0",
258
- "method": "help",
259
- "id": 1
260
- }
261
-
262
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
263
- mock_execute.side_effect = Exception("Unexpected error")
264
-
265
- response = await handle_json_rpc(request_data)
266
-
267
- assert response["jsonrpc"] == "2.0"
268
- assert "error" in response
269
- assert response["error"]["code"] == -32603 # Internal error
270
-
271
- @pytest.mark.asyncio
272
- async def test_handle_json_rpc_without_params(self):
273
- """Test JSON-RPC handling without parameters."""
274
- request_data = {
275
- "jsonrpc": "2.0",
276
- "method": "help",
277
- "id": 1
278
- # No params
279
- }
280
-
281
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
282
- mock_execute.return_value = {"result": "help_result"}
283
-
284
- response = await handle_json_rpc(request_data)
285
-
286
- assert response["jsonrpc"] == "2.0"
287
- assert response["result"] == {"result": "help_result"}
288
-
289
-
290
- class TestCreateErrorResponse:
291
- """Test cases for _create_error_response function."""
292
-
293
- def test_create_error_response(self):
294
- """Test creating error response."""
295
- error = InvalidRequestError("Test error")
296
- request_id = 123
297
-
298
- response = _create_error_response(error, request_id)
299
-
300
- assert response["jsonrpc"] == "2.0"
301
- assert response["error"]["code"] == -32600
302
- assert response["id"] == 123
303
-
304
- def test_create_error_response_with_none_id(self):
305
- """Test creating error response with None ID."""
306
- error = InvalidRequestError("Test error")
307
-
308
- response = _create_error_response(error, None)
309
-
310
- assert response["jsonrpc"] == "2.0"
311
- assert response["error"]["code"] == -32600
312
- assert response["id"] is None
313
-
314
-
315
- class TestGetServerHealth:
316
- """Test cases for get_server_health function."""
317
-
318
- @pytest.mark.asyncio
319
- async def test_get_server_health(self):
320
- """Test getting server health information."""
321
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
322
- mock_registry.get_all_commands.return_value = {"help": Mock(), "config": Mock()}
323
-
324
- health = await get_server_health()
325
-
326
- assert health["status"] == "ok"
327
- assert health["version"] == "1.0.0"
328
- assert "uptime" in health
329
- assert "components" in health
330
- assert health["components"]["commands"]["registered_count"] == 2
331
-
332
- @pytest.mark.asyncio
333
- async def test_get_server_health_with_empty_registry(self):
334
- """Test getting server health with empty registry."""
335
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
336
- mock_registry.get_all_commands.return_value = {}
337
-
338
- health = await get_server_health()
339
-
340
- assert health["status"] == "ok"
341
- assert health["components"]["commands"]["registered_count"] == 0
342
-
343
- @pytest.mark.asyncio
344
- async def test_get_server_health_system_info(self):
345
- """Test that system information is included."""
346
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
347
- mock_registry.get_all_commands.return_value = {}
348
-
349
- health = await get_server_health()
350
-
351
- assert "system" in health["components"]
352
- assert "process" in health["components"]
353
- assert "python_version" in health["components"]["system"]
354
- assert "platform" in health["components"]["system"]
355
- assert "cpu_count" in health["components"]["system"]
356
-
357
-
358
- class TestGetCommandsList:
359
- """Test cases for get_commands_list function."""
360
-
361
- @pytest.mark.asyncio
362
- async def test_get_commands_list(self):
363
- """Test getting commands list."""
364
- mock_command1 = Mock()
365
- mock_command1.get_schema.return_value = {
366
- "description": "Test command 1",
367
- "properties": {}
368
- }
369
-
370
- mock_command2 = Mock()
371
- mock_command2.get_schema.return_value = {
372
- "description": "Test command 2",
373
- "properties": {}
374
- }
375
-
376
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
377
- mock_registry.get_all_commands.return_value = {
378
- "help": mock_command1,
379
- "config": mock_command2
380
- }
381
-
382
- commands = await get_commands_list()
383
-
384
- assert len(commands) == 2
385
- assert "help" in commands
386
- assert "config" in commands
387
- assert commands["help"]["name"] == "help"
388
- assert commands["help"]["description"] == "Test command 1"
389
-
390
- @pytest.mark.asyncio
391
- async def test_get_commands_list_empty_registry(self):
392
- """Test getting commands list with empty registry."""
393
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
394
- mock_registry.get_all_commands.return_value = {}
395
-
396
- commands = await get_commands_list()
397
-
398
- assert commands == {}
399
-
400
- @pytest.mark.asyncio
401
- async def test_get_commands_list_command_without_description(self):
402
- """Test getting commands list with command without description."""
403
- mock_command = Mock()
404
- mock_command.get_schema.return_value = {
405
- "properties": {}
406
- # No description
407
- }
408
-
409
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
410
- mock_registry.get_all_commands.return_value = {"test": mock_command}
411
-
412
- commands = await get_commands_list()
413
-
414
- assert "test" in commands
415
- assert commands["test"]["description"] == ""
416
-
417
-
418
- class TestHandlersIntegration:
419
- """Integration tests for handlers."""
420
-
421
- @pytest.mark.asyncio
422
- async def test_full_json_rpc_workflow(self):
423
- """Test complete JSON-RPC workflow."""
424
- request_data = {
425
- "jsonrpc": "2.0",
426
- "method": "help",
427
- "id": 1
428
- }
429
-
430
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
431
- mock_execute.return_value = {"result": "help_result"}
432
-
433
- response = await handle_json_rpc(request_data)
434
-
435
- assert response["jsonrpc"] == "2.0"
436
- assert response["result"] == {"result": "help_result"}
437
- assert response["id"] == 1
438
-
439
- @pytest.mark.asyncio
440
- async def test_batch_json_rpc_workflow(self):
441
- """Test complete batch JSON-RPC workflow."""
442
- batch_requests = [
443
- {"jsonrpc": "2.0", "method": "help", "id": 1},
444
- {"jsonrpc": "2.0", "method": "config", "id": 2}
445
- ]
446
-
447
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
448
- mock_handle.side_effect = [
449
- {"jsonrpc": "2.0", "result": "help_result", "id": 1},
450
- {"jsonrpc": "2.0", "result": "config_result", "id": 2}
451
- ]
452
-
453
- responses = await handle_batch_json_rpc(batch_requests)
454
-
455
- assert len(responses) == 2
456
- assert responses[0]["id"] == 1
457
- assert responses[1]["id"] == 2
458
-
459
-
460
- class TestHandlersEdgeCases:
461
- """Test edge cases for handlers."""
462
-
463
- @pytest.mark.asyncio
464
- async def test_execute_command_with_complex_params(self):
465
- """Test command execution with complex parameters."""
466
- class MockCommandClass:
467
- @classmethod
468
- async def run(cls, **kwargs):
469
- return MockResult()
470
-
471
- class MockResult:
472
- def to_dict(self):
473
- return {"result": "complex"}
474
-
475
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
476
- mock_registry.get_command.return_value = MockCommandClass
477
-
478
- complex_params = {
479
- "nested": {"key": "value"},
480
- "list": [1, 2, 3],
481
- "boolean": True
482
- }
483
-
484
- result = await execute_command("test_command", complex_params)
485
-
486
- assert result == {"result": "complex"}
487
-
488
- @pytest.mark.asyncio
489
- async def test_json_rpc_with_null_params(self):
490
- """Test JSON-RPC handling with null parameters."""
491
- request_data = {
492
- "jsonrpc": "2.0",
493
- "method": "help",
494
- "params": None,
495
- "id": 1
496
- }
497
-
498
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
499
- mock_execute.return_value = {"result": "help_result"}
500
-
501
- response = await handle_json_rpc(request_data)
502
-
503
- assert response["jsonrpc"] == "2.0"
504
- assert response["result"] == {"result": "help_result"}
505
-
506
- @pytest.mark.asyncio
507
- async def test_json_rpc_without_id(self):
508
- """Test JSON-RPC handling without ID."""
509
- request_data = {
510
- "jsonrpc": "2.0",
511
- "method": "help"
512
- # No id
513
- }
514
-
515
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
516
- mock_execute.return_value = {"result": "help_result"}
517
-
518
- response = await handle_json_rpc(request_data)
519
-
520
- assert response["jsonrpc"] == "2.0"
521
- assert response["result"] == {"result": "help_result"}
522
- assert response["id"] is None