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,189 +0,0 @@
1
- """
2
- Performance tests for the API.
3
- """
4
-
5
- import asyncio
6
- import time
7
- from typing import Dict, Any
8
-
9
- import pytest
10
- import pytest_asyncio
11
- from fastapi.testclient import TestClient
12
- from httpx import AsyncClient, ASGITransport
13
-
14
- from mcp_proxy_adapter.api.app import create_app
15
- from mcp_proxy_adapter.commands.command_registry import registry
16
- from mcp_proxy_adapter.tests.stubs.echo_command import EchoCommand
17
-
18
-
19
- @pytest_asyncio.fixture
20
- async def async_client(test_config):
21
- """
22
- Fixture for async HTTP client.
23
-
24
- Args:
25
- test_config: Test configuration instance.
26
-
27
- Returns:
28
- AsyncClient instance for making async requests.
29
- """
30
- app = create_app()
31
- transport = ASGITransport(app=app)
32
- async with AsyncClient(transport=transport, base_url="http://test") as client:
33
- yield client
34
-
35
-
36
- @pytest.fixture
37
- def register_echo_command(clean_registry):
38
- """
39
- Fixture to register the Echo command for testing.
40
-
41
- Args:
42
- clean_registry: Fixture to clean registry before and after test.
43
- """
44
- registry.register(EchoCommand)
45
- yield
46
- registry.clear()
47
-
48
-
49
- @pytest.mark.performance
50
- @pytest.mark.asyncio
51
- async def test_sequential_requests(async_client: AsyncClient, json_rpc_request: Dict[str, Any],
52
- register_echo_command):
53
- """
54
- Test sequential API requests performance.
55
-
56
- Args:
57
- async_client: Async HTTP client.
58
- json_rpc_request: Base JSON-RPC request.
59
- register_echo_command: Fixture to register test command.
60
- """
61
- num_requests = 50
62
-
63
- # Create JSON-RPC request
64
- request_data = json_rpc_request.copy()
65
- request_data["method"] = "echo"
66
- request_data["params"] = {"test": "value"}
67
-
68
- # Measure execution time
69
- start_time = time.time()
70
-
71
- for i in range(num_requests):
72
- response = await async_client.post("/api/jsonrpc", json=request_data)
73
- assert response.status_code == 200
74
-
75
- end_time = time.time()
76
- total_time = end_time - start_time
77
-
78
- # Calculate requests per second
79
- rps = num_requests / total_time
80
-
81
- print(f"Sequential requests: {num_requests} requests in {total_time:.2f}s ({rps:.2f} req/s)")
82
-
83
- # Check that performance is within expected range
84
- # This threshold should be adjusted based on actual performance measurements
85
- assert rps > 30, f"Performance too low: {rps:.2f} req/s"
86
-
87
-
88
- @pytest.mark.performance
89
- @pytest.mark.asyncio
90
- async def test_concurrent_requests(async_client: AsyncClient, json_rpc_request: Dict[str, Any],
91
- register_echo_command):
92
- """
93
- Test concurrent API requests performance.
94
-
95
- Args:
96
- async_client: Async HTTP client.
97
- json_rpc_request: Base JSON-RPC request.
98
- register_echo_command: Fixture to register test command.
99
- """
100
- num_requests = 50
101
-
102
- # Create JSON-RPC request
103
- request_data = json_rpc_request.copy()
104
- request_data["method"] = "echo"
105
- request_data["params"] = {"test": "value"}
106
-
107
- # Create task list
108
- tasks = []
109
- for i in range(num_requests):
110
- task = asyncio.create_task(async_client.post("/api/jsonrpc", json=request_data))
111
- tasks.append(task)
112
-
113
- # Measure execution time
114
- start_time = time.time()
115
-
116
- # Wait for all tasks to complete
117
- responses = await asyncio.gather(*tasks)
118
-
119
- end_time = time.time()
120
- total_time = end_time - start_time
121
-
122
- # Check that all responses are successful
123
- for response in responses:
124
- assert response.status_code == 200
125
-
126
- # Calculate requests per second
127
- rps = num_requests / total_time
128
-
129
- print(f"Concurrent requests: {num_requests} requests in {total_time:.2f}s ({rps:.2f} req/s)")
130
-
131
- # Check that performance is within expected range
132
- # This threshold should be adjusted based on actual performance measurements
133
- assert rps > 100, f"Concurrent performance too low: {rps:.2f} req/s"
134
-
135
-
136
- @pytest.mark.performance
137
- @pytest.mark.asyncio
138
- async def test_batch_requests_performance(async_client: AsyncClient, json_rpc_request: Dict[str, Any],
139
- register_echo_command):
140
- """
141
- Test batch requests performance.
142
-
143
- Args:
144
- async_client: Async HTTP client.
145
- json_rpc_request: Base JSON-RPC request.
146
- register_echo_command: Fixture to register test command.
147
- """
148
- num_batches = 10
149
- batch_size = 5
150
-
151
- # Create base request
152
- base_request = json_rpc_request.copy()
153
- base_request["method"] = "echo"
154
- base_request["params"] = {"test": "value"}
155
-
156
- # Prepare batch requests
157
- batch_requests = []
158
- for i in range(num_batches):
159
- batch = []
160
- for j in range(batch_size):
161
- request = base_request.copy()
162
- request["id"] = f"batch-{i}-{j}"
163
- batch.append(request)
164
- batch_requests.append(batch)
165
-
166
- # Measure execution time
167
- start_time = time.time()
168
-
169
- # Execute batch requests
170
- for batch in batch_requests:
171
- response = await async_client.post("/api/jsonrpc", json=batch)
172
- assert response.status_code == 200
173
-
174
- # Check response structure
175
- data = response.json()
176
- assert isinstance(data, list)
177
- assert len(data) == batch_size
178
-
179
- end_time = time.time()
180
- total_time = end_time - start_time
181
-
182
- # Calculate total requests and requests per second
183
- total_requests = num_batches * batch_size
184
- rps = total_requests / total_time
185
-
186
- print(f"Batch requests: {total_requests} requests in {total_time:.2f}s ({rps:.2f} req/s)")
187
-
188
- # Check that performance is within expected range
189
- assert rps > 50, f"Batch performance too low: {rps:.2f} req/s"
@@ -1,10 +0,0 @@
1
- """
2
- Test stub modules for framework tests.
3
- """
4
-
5
- from mcp_proxy_adapter.tests.stubs.echo_command import EchoCommand, EchoResult
6
-
7
- __all__ = [
8
- "EchoCommand",
9
- "EchoResult"
10
- ]
@@ -1,104 +0,0 @@
1
- """
2
- Stub module for echo command tests.
3
- """
4
-
5
- from typing import Any, Dict, Optional, ClassVar, Type
6
-
7
- from mcp_proxy_adapter.commands.base import Command
8
- from mcp_proxy_adapter.core.logging import logger
9
-
10
-
11
- class EchoResult:
12
- """
13
- Result of the echo command execution (stub for tests).
14
- """
15
-
16
- def __init__(self, params: Dict[str, Any] = None):
17
- """
18
- Initialize echo result.
19
-
20
- Args:
21
- params: Parameters to echo back.
22
- """
23
- self.params = params or {}
24
-
25
- def to_dict(self) -> Dict[str, Any]:
26
- """
27
- Convert result to dictionary.
28
-
29
- Returns:
30
- Dict[str, Any]: Result as dictionary
31
- """
32
- return {"params": self.params}
33
-
34
- @classmethod
35
- def get_schema(cls) -> Dict[str, Any]:
36
- """
37
- Get JSON schema for result validation.
38
-
39
- Returns:
40
- Dict[str, Any]: JSON schema
41
- """
42
- return {
43
- "type": "object",
44
- "properties": {
45
- "params": {
46
- "type": "object",
47
- "additionalProperties": True
48
- }
49
- },
50
- "required": ["params"]
51
- }
52
-
53
- @classmethod
54
- def from_dict(cls, data: Dict[str, Any]) -> "EchoResult":
55
- """
56
- Creates result instance from dictionary.
57
-
58
- Args:
59
- data: Dictionary with result data.
60
-
61
- Returns:
62
- EchoResult instance.
63
- """
64
- return cls(
65
- params=data.get("params", {})
66
- )
67
-
68
-
69
- class EchoCommand(Command):
70
- """
71
- Test stub for echo command.
72
- """
73
-
74
- name: ClassVar[str] = "echo"
75
- result_class: ClassVar[Type[EchoResult]] = EchoResult
76
-
77
- @classmethod
78
- def get_schema(cls) -> Dict[str, Any]:
79
- """
80
- Returns JSON schema for command parameters validation.
81
-
82
- Returns:
83
- Dictionary with JSON schema.
84
- """
85
- return {
86
- "type": "object",
87
- "additionalProperties": True,
88
- "description": "Any parameters will be echoed back in the response"
89
- }
90
-
91
- async def execute(self, **kwargs) -> EchoResult:
92
- """
93
- Executes echo command and returns the parameters back.
94
-
95
- Args:
96
- **kwargs: Any parameters to echo back.
97
-
98
- Returns:
99
- EchoResult: Command execution result with the parameters.
100
- """
101
- logger.debug(f"Echo command received parameters: {kwargs}")
102
-
103
- # Simply return the parameters that were passed
104
- return EchoResult(params=kwargs)
@@ -1,271 +0,0 @@
1
- """
2
- Tests for API endpoints.
3
- """
4
-
5
- import pytest
6
- from typing import Dict, Any
7
- from unittest.mock import patch, MagicMock, AsyncMock
8
- from fastapi.testclient import TestClient
9
- import asyncio
10
-
11
- from mcp_proxy_adapter.api.app import create_app
12
- from mcp_proxy_adapter.commands.result import SuccessResult
13
- from mcp_proxy_adapter.core.errors import NotFoundError
14
-
15
-
16
- @pytest.fixture
17
- def client():
18
- """Fixture for test client."""
19
- # Создаем приложение с очищенным реестром команд для тестов
20
- with patch("mcp_proxy_adapter.commands.registry.discover_commands"): # Предотвращаем автообнаружение команд
21
- app = create_app()
22
- return TestClient(app)
23
-
24
-
25
- @pytest.fixture
26
- def success_result():
27
- """Fixture for test success result."""
28
- result = SuccessResult(data={"key": "value"}, message="Success")
29
- return result
30
-
31
-
32
- class TestCommandsEndpoint:
33
- """Tests for the /api/commands endpoint."""
34
-
35
- @patch("mcp_proxy_adapter.api.app.get_commands_list")
36
- def test_commands_list_endpoint(self, mock_get_commands_list, client):
37
- """Test getting list of available commands."""
38
- # Create mock commands info
39
- mock_commands_info = {
40
- "command1": {
41
- "name": "command1",
42
- "description": "Test command 1",
43
- "params": {},
44
- "schema": {"type": "object"},
45
- "result_schema": {"type": "object"}
46
- },
47
- "command2": {
48
- "name": "command2",
49
- "description": "Test command 2",
50
- "params": {},
51
- "schema": {"type": "object"},
52
- "result_schema": {"type": "object"}
53
- }
54
- }
55
-
56
- # Setup mock для асинхронного метода
57
- mock_get_commands_list.return_value = mock_commands_info
58
-
59
- # Get commands list
60
- response = client.get("/api/commands")
61
-
62
- # Assertions
63
- assert response.status_code == 200
64
- assert response.json() == {"commands": mock_commands_info}
65
- mock_get_commands_list.assert_called_once()
66
-
67
-
68
- class TestHealthEndpoint:
69
- """Tests for the /health endpoint."""
70
-
71
- @patch("mcp_proxy_adapter.api.app.get_server_health")
72
- def test_health_endpoint(self, mock_get_server_health, client):
73
- """Test getting server health information."""
74
- # Create mock health info
75
- mock_health_info = {
76
- "status": "ok",
77
- "model": "mcp-proxy-adapter",
78
- "version": "1.0.0"
79
- }
80
-
81
- # Setup mock для асинхронного метода
82
- mock_get_server_health.return_value = mock_health_info
83
-
84
- # Get health info
85
- response = client.get("/health")
86
-
87
- # Assertions
88
- assert response.status_code == 200
89
- response_data = response.json()
90
- assert "status" in response_data
91
- assert response_data["status"] == "ok"
92
- assert "version" in response_data
93
-
94
-
95
- class TestJsonRpcEndpoint:
96
- """Tests for JSON-RPC endpoint."""
97
-
98
- @pytest.mark.asyncio
99
- async def test_jsonrpc_endpoint_empty_batch(self, client):
100
- """Test JSON-RPC endpoint handles empty batch requests."""
101
- # Make request with empty array
102
- response = client.post("/api/jsonrpc", json=[])
103
-
104
- # Assertions
105
- assert response.status_code == 400
106
- data = response.json()
107
- assert data["jsonrpc"] == "2.0"
108
- assert "error" in data
109
- assert data["error"]["code"] == -32600
110
- assert "Empty batch request" in data["error"]["message"]
111
-
112
- @pytest.mark.asyncio
113
- @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command")
114
- async def test_handle_json_rpc_success(self, mock_get_command):
115
- """Test handle_json_rpc function with success result."""
116
- from mcp_proxy_adapter.commands.result import SuccessResult
117
-
118
- # Create mock command class
119
- mock_command = MagicMock()
120
- mock_command.run = AsyncMock(return_value=SuccessResult(
121
- data={"status": "success"},
122
- message="Success message"
123
- ))
124
- mock_get_command.return_value = mock_command
125
-
126
- # Create JSON-RPC request
127
- request_data = {
128
- "jsonrpc": "2.0",
129
- "method": "test_command",
130
- "params": {"param": "value"},
131
- "id": "1"
132
- }
133
-
134
- # Call handler directly
135
- from mcp_proxy_adapter.api.handlers import handle_json_rpc
136
- response = await handle_json_rpc(request_data)
137
-
138
- # Assertions
139
- assert response["jsonrpc"] == "2.0"
140
- assert "result" in response
141
- assert response["result"]["data"]["status"] == "success"
142
- assert response["id"] == "1"
143
- mock_get_command.assert_called_once_with("test_command")
144
- mock_command.run.assert_called_once_with(param="value")
145
-
146
- @pytest.mark.asyncio
147
- async def test_handle_batch_json_rpc(self):
148
- """Test handle_batch_json_rpc function."""
149
- from mcp_proxy_adapter.api.handlers import handle_batch_json_rpc
150
-
151
- # Create mock for handle_json_rpc
152
- with patch("mcp_proxy_adapter.api.handlers.handle_json_rpc") as mock_handle_json_rpc:
153
- # Setup mock responses
154
- mock_handle_json_rpc.side_effect = [
155
- {"jsonrpc": "2.0", "result": {"status": "success1"}, "id": "1"},
156
- {"jsonrpc": "2.0", "result": {"status": "success2"}, "id": "2"}
157
- ]
158
-
159
- # Create batch request
160
- batch_request = [
161
- {"jsonrpc": "2.0", "method": "method1", "params": {}, "id": "1"},
162
- {"jsonrpc": "2.0", "method": "method2", "params": {}, "id": "2"}
163
- ]
164
-
165
- # Create mock request
166
- mock_request = MagicMock()
167
- mock_request.state.request_id = "test-request-id"
168
-
169
- # Call batch handler
170
- responses = await handle_batch_json_rpc(batch_request, mock_request)
171
-
172
- # Assertions
173
- assert len(responses) == 2
174
- assert responses[0]["jsonrpc"] == "2.0"
175
- assert responses[0]["result"]["status"] == "success1"
176
- assert responses[0]["id"] == "1"
177
-
178
- assert responses[1]["jsonrpc"] == "2.0"
179
- assert responses[1]["result"]["status"] == "success2"
180
- assert responses[1]["id"] == "2"
181
-
182
- # Check mock calls
183
- assert mock_handle_json_rpc.call_count == 2
184
- mock_handle_json_rpc.assert_any_call(batch_request[0], "test-request-id")
185
- mock_handle_json_rpc.assert_any_call(batch_request[1], "test-request-id")
186
-
187
-
188
- class TestCommandEndpoint:
189
- """Tests for the /api/command/{command_name} endpoint."""
190
-
191
- @patch("mcp_proxy_adapter.api.app.execute_command")
192
- def test_command_endpoint_success(self, mock_execute_command, client, success_result):
193
- """Test direct command execution with success."""
194
- # Create mock params and result
195
- mock_params = {"param": "value"}
196
- mock_result = success_result.to_dict()
197
-
198
- # Setup mock
199
- mock_execute_command.return_value = mock_result
200
-
201
- # Execute command
202
- response = client.post("/api/command/test_command", json=mock_params)
203
-
204
- # Assertions
205
- assert response.status_code == 200
206
- assert response.json() == mock_result
207
- mock_execute_command.assert_called_once()
208
-
209
- @patch("mcp_proxy_adapter.api.app.execute_command")
210
- def test_command_endpoint_error(self, mock_execute_command, client):
211
- """Test direct command execution with error."""
212
- # Create mock error
213
- from mcp_proxy_adapter.core.errors import MicroserviceError
214
- mock_error = MicroserviceError("Test error", code=400)
215
- mock_error_dict = {"error": {"code": 400, "message": "Test error"}}
216
- mock_error.to_dict = MagicMock(return_value=mock_error_dict)
217
-
218
- # Setup mock
219
- mock_execute_command.side_effect = mock_error
220
-
221
- # Execute command
222
- response = client.post("/api/command/test_command", json={})
223
-
224
- # Assertions
225
- assert response.status_code == 400
226
- assert response.json() == mock_error_dict
227
- mock_execute_command.assert_called_once()
228
-
229
-
230
- class TestCommandInfoEndpoint:
231
- """Tests for command info endpoint."""
232
-
233
- @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command_info")
234
- def test_command_info_endpoint_success(self, mock_get_command_info, client):
235
- """Test command info endpoint returns command information."""
236
- # Setup mock
237
- mock_get_command_info.return_value = {
238
- "name": "test_command",
239
- "description": "Test command description",
240
- "params": {"param1": {"type": "string"}},
241
- "schema": {"properties": {"param1": {"type": "string"}}},
242
- "result_schema": {"properties": {"key": {"type": "string"}}}
243
- }
244
-
245
- # Make request
246
- response = client.get("/api/commands/test_command")
247
-
248
- # Assertions
249
- assert response.status_code == 200
250
- data = response.json()
251
- assert data["name"] == "test_command"
252
- assert data["description"] == "Test command description"
253
- assert "params" in data
254
- assert "schema" in data
255
- assert "result_schema" in data
256
-
257
- @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command_info")
258
- def test_command_info_endpoint_not_found(self, mock_get_command_info, client):
259
- """Test command info endpoint returns 404 for non-existent command."""
260
- # Setup mock to raise NotFoundError
261
- mock_get_command_info.side_effect = NotFoundError("Command not found")
262
-
263
- # Make request
264
- response = client.get("/api/commands/non_existent")
265
-
266
- # Assertions
267
- assert response.status_code == 404
268
- data = response.json()
269
- assert "error" in data
270
- assert data["error"]["code"] == 404
271
- assert "Command 'non_existent' not found" in data["error"]["message"]