mcp-proxy-adapter 2.1.0__py3-none-any.whl → 2.1.2__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 (78) hide show
  1. docs/README.md +172 -0
  2. docs/README_ru.md +172 -0
  3. docs/architecture.md +251 -0
  4. docs/architecture_ru.md +343 -0
  5. docs/command_development.md +250 -0
  6. docs/command_development_ru.md +593 -0
  7. docs/deployment.md +251 -0
  8. docs/deployment_ru.md +1298 -0
  9. docs/examples.md +254 -0
  10. docs/examples_ru.md +401 -0
  11. docs/mcp_proxy_adapter.md +251 -0
  12. docs/mcp_proxy_adapter_ru.md +405 -0
  13. docs/quickstart.md +251 -0
  14. docs/quickstart_ru.md +397 -0
  15. docs/testing.md +255 -0
  16. docs/testing_ru.md +469 -0
  17. docs/validation_ru.md +287 -0
  18. examples/analyze_config.py +141 -0
  19. examples/basic_integration.py +161 -0
  20. examples/docstring_and_schema_example.py +60 -0
  21. examples/extension_example.py +60 -0
  22. examples/help_best_practices.py +67 -0
  23. examples/help_usage.py +64 -0
  24. examples/mcp_proxy_client.py +131 -0
  25. examples/mcp_proxy_config.json +175 -0
  26. examples/openapi_server.py +369 -0
  27. examples/project_structure_example.py +47 -0
  28. examples/testing_example.py +53 -0
  29. mcp_proxy_adapter/__init__.py +17 -0
  30. mcp_proxy_adapter/adapter.py +697 -0
  31. mcp_proxy_adapter/models.py +47 -0
  32. mcp_proxy_adapter/registry.py +439 -0
  33. mcp_proxy_adapter/schema.py +257 -0
  34. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/METADATA +2 -2
  35. mcp_proxy_adapter-2.1.2.dist-info/RECORD +61 -0
  36. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +5 -0
  37. scripts/code_analyzer/code_analyzer.py +328 -0
  38. scripts/code_analyzer/register_commands.py +446 -0
  39. scripts/publish.py +85 -0
  40. tests/conftest.py +12 -0
  41. tests/test_adapter.py +529 -0
  42. tests/test_adapter_coverage.py +274 -0
  43. tests/test_basic_dispatcher.py +169 -0
  44. tests/test_command_registry.py +328 -0
  45. tests/test_examples.py +32 -0
  46. tests/test_mcp_proxy_adapter.py +568 -0
  47. tests/test_mcp_proxy_adapter_basic.py +262 -0
  48. tests/test_part1.py +348 -0
  49. tests/test_part2.py +524 -0
  50. tests/test_schema.py +358 -0
  51. tests/test_simple_adapter.py +251 -0
  52. adapters/__init__.py +0 -16
  53. cli/__init__.py +0 -12
  54. cli/__main__.py +0 -79
  55. cli/command_runner.py +0 -233
  56. generators/__init__.py +0 -14
  57. generators/endpoint_generator.py +0 -172
  58. generators/openapi_generator.py +0 -254
  59. generators/rest_api_generator.py +0 -207
  60. mcp_proxy_adapter-2.1.0.dist-info/RECORD +0 -28
  61. mcp_proxy_adapter-2.1.0.dist-info/top_level.txt +0 -7
  62. openapi_schema/__init__.py +0 -38
  63. openapi_schema/command_registry.py +0 -312
  64. openapi_schema/rest_schema.py +0 -510
  65. openapi_schema/rpc_generator.py +0 -307
  66. openapi_schema/rpc_schema.py +0 -416
  67. validators/__init__.py +0 -14
  68. validators/base_validator.py +0 -23
  69. {analyzers → mcp_proxy_adapter/analyzers}/__init__.py +0 -0
  70. {analyzers → mcp_proxy_adapter/analyzers}/docstring_analyzer.py +0 -0
  71. {analyzers → mcp_proxy_adapter/analyzers}/type_analyzer.py +0 -0
  72. {dispatchers → mcp_proxy_adapter/dispatchers}/__init__.py +0 -0
  73. {dispatchers → mcp_proxy_adapter/dispatchers}/base_dispatcher.py +0 -0
  74. {dispatchers → mcp_proxy_adapter/dispatchers}/json_rpc_dispatcher.py +0 -0
  75. {validators → mcp_proxy_adapter/validators}/docstring_validator.py +0 -0
  76. {validators → mcp_proxy_adapter/validators}/metadata_validator.py +0 -0
  77. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/WHEEL +0 -0
  78. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,274 @@
1
+ """
2
+ Tests for MCPProxyAdapter code coverage.
3
+ """
4
+ import sys
5
+ import os
6
+ import pytest
7
+ import json
8
+ import logging
9
+ from unittest.mock import MagicMock, patch
10
+ from typing import Dict, Any, List, Optional
11
+
12
+ # Add parent directory to path for imports
13
+ current_dir = os.path.dirname(os.path.abspath(__file__))
14
+ parent_dir = os.path.dirname(current_dir)
15
+ if parent_dir not in sys.path:
16
+ sys.path.insert(0, parent_dir)
17
+
18
+ # Import tested modules
19
+ try:
20
+ from mcp_proxy_adapter.adapter import MCPProxyAdapter, CommandRegistry
21
+ from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig
22
+ except ImportError:
23
+ from src.adapter import MCPProxyAdapter, CommandRegistry
24
+ from src.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig
25
+
26
+ from fastapi import FastAPI
27
+ from fastapi.testclient import TestClient
28
+
29
+ # Mock for command dispatcher
30
+ class MockDispatcher:
31
+ """Mock for command dispatcher in tests."""
32
+
33
+ def __init__(self):
34
+ self.commands = {
35
+ "success": lambda value=1: {"result": value * 2},
36
+ "error": lambda: exec('raise ValueError("Test error")'),
37
+ "execute": self.execute_from_params
38
+ }
39
+ self.commands_info = {
40
+ "success": {
41
+ "description": "Successful command",
42
+ "params": {
43
+ "value": {
44
+ "type": "integer",
45
+ "description": "Input value",
46
+ "required": False,
47
+ "default": 1
48
+ }
49
+ }
50
+ },
51
+ "error": {
52
+ "description": "Command with error",
53
+ "params": {}
54
+ },
55
+ "execute": {
56
+ "description": "Universal command for executing other commands",
57
+ "params": {
58
+ "query": {
59
+ "type": "string",
60
+ "description": "Command or query to execute",
61
+ "required": False
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ def execute_from_params(self, **params):
68
+ """Executes command based on parameters."""
69
+ if "query" in params and params["query"] in self.commands:
70
+ command = params.pop("query")
71
+ return self.execute(command, **params)
72
+ return {
73
+ "available_commands": self.get_valid_commands(),
74
+ "received_params": params
75
+ }
76
+
77
+ def execute(self, command, **params):
78
+ """Executes command with specified parameters."""
79
+ if command not in self.commands:
80
+ raise KeyError(f"Unknown command: {command}")
81
+ return self.commands[command](**params)
82
+
83
+ def get_valid_commands(self):
84
+ """Returns list of available commands."""
85
+ return list(self.commands.keys())
86
+
87
+ def get_command_info(self, command):
88
+ """Returns information about command."""
89
+ return self.commands_info.get(command)
90
+
91
+ def get_commands_info(self):
92
+ """Returns information about all commands."""
93
+ return self.commands_info
94
+
95
+ # Mock for CommandRegistry
96
+ class MockRegistry:
97
+ """Mock for CommandRegistry in tests."""
98
+
99
+ def __init__(self):
100
+ self.dispatcher = MockDispatcher()
101
+ self.generators = []
102
+
103
+ def get_commands_info(self):
104
+ """Returns command information from dispatcher."""
105
+ return self.dispatcher.get_commands_info()
106
+
107
+ def add_generator(self, generator):
108
+ """Adds API generator."""
109
+ self.generators.append(generator)
110
+
111
+ # Tests targeting uncovered code sections in adapter.py
112
+ def test_openapi_generator_import_error():
113
+ """Test for simulating OpenApiGenerator import error situation."""
114
+ # Use more specific patch for OpenApiGenerator import
115
+ with patch('mcp_proxy_adapter.adapter.OpenApiGenerator', side_effect=ImportError("Mocked import error")):
116
+ registry = MockRegistry()
117
+ adapter = MCPProxyAdapter(registry)
118
+
119
+ # Check that adapter was created and works even without OpenApiGenerator
120
+ assert adapter.openapi_generator is None
121
+
122
+ # Create application and check functionality
123
+ app = FastAPI()
124
+ adapter.register_endpoints(app)
125
+ client = TestClient(app)
126
+
127
+ # Check that API commands work
128
+ response = client.get("/api/commands")
129
+ assert response.status_code == 200
130
+ assert "commands" in response.json()
131
+
132
+ def test_protocol_errors():
133
+ """Test for working with disabled schema."""
134
+ registry = MockRegistry()
135
+
136
+ # Test without schema inclusion
137
+ adapter = MCPProxyAdapter(registry, include_schema=False, optimize_schema=False)
138
+
139
+ # Check that adapter has no OpenAPI schema endpoint
140
+ # For this we look at router routes
141
+ routes = [route for route in adapter.router.routes if hasattr(route, 'path')]
142
+ openapi_routes = [route for route in routes if route.path == "/openapi.json"]
143
+
144
+ # Make sure OpenAPI route is absent
145
+ assert len(openapi_routes) == 0
146
+
147
+ def test_api_endpoints():
148
+ """Test for checking API endpoints."""
149
+ registry = MockRegistry()
150
+ adapter = MCPProxyAdapter(registry)
151
+
152
+ app = FastAPI()
153
+ adapter.register_endpoints(app)
154
+ client = TestClient(app)
155
+
156
+ # Check /api/commands endpoint
157
+ response = client.get("/api/commands")
158
+ assert response.status_code == 200
159
+ data = response.json()
160
+ assert "commands" in data
161
+
162
+ def test_exception_handling():
163
+ """Test for handling exceptions during command execution."""
164
+ registry = MockRegistry()
165
+ adapter = MCPProxyAdapter(registry)
166
+
167
+ app = FastAPI()
168
+ adapter.register_endpoints(app)
169
+ client = TestClient(app)
170
+
171
+ # Test error during JSON parsing
172
+ response = client.post("/cmd", content="invalid json")
173
+ assert response.status_code == 200
174
+ data = response.json()
175
+ assert "error" in data
176
+ assert "Invalid JSON format" in data["error"]["message"]
177
+
178
+ def test_mcp_proxy_format():
179
+ """Test for checking MCP Proxy format."""
180
+ registry = MockRegistry()
181
+ adapter = MCPProxyAdapter(registry)
182
+
183
+ app = FastAPI()
184
+ adapter.register_endpoints(app)
185
+ client = TestClient(app)
186
+
187
+ # Test MCP Proxy format
188
+ response = client.post("/cmd", json={
189
+ "command": "success",
190
+ "params": {"value": 10}
191
+ })
192
+
193
+ assert response.status_code == 200
194
+ data = response.json()
195
+ assert "result" in data
196
+ assert data["result"] == {"result": 20}
197
+
198
+ def test_query_subcommand():
199
+ """Test for extracting command from query with / separator."""
200
+ registry = MockRegistry()
201
+ adapter = MCPProxyAdapter(registry)
202
+
203
+ # Patch execute method for test
204
+ registry.dispatcher.execute = MagicMock(return_value={"subcommand_executed": True})
205
+
206
+ app = FastAPI()
207
+ adapter.register_endpoints(app)
208
+ client = TestClient(app)
209
+
210
+ # Test command extraction from query with / separator
211
+ response = client.post("/cmd", json={
212
+ "params": {"query": "success/subcommand"}
213
+ })
214
+
215
+ assert response.status_code == 200
216
+ registry.dispatcher.execute.assert_called_once()
217
+
218
+ def test_only_params_with_command():
219
+ """Test for request with only params containing command."""
220
+ registry = MockRegistry()
221
+ adapter = MCPProxyAdapter(registry)
222
+
223
+ app = FastAPI()
224
+ adapter.register_endpoints(app)
225
+ client = TestClient(app)
226
+
227
+ # Test request with command in params
228
+ response = client.post("/cmd", json={
229
+ "params": {"command": "success", "value": 15}
230
+ })
231
+
232
+ assert response.status_code == 200
233
+ data = response.json()
234
+ assert "result" in data
235
+ assert data["result"] == {"result": 30}
236
+
237
+ def test_error_responses():
238
+ """Test for checking response formats for different errors."""
239
+ registry = MockRegistry()
240
+ adapter = MCPProxyAdapter(registry)
241
+
242
+ app = FastAPI()
243
+ adapter.register_endpoints(app)
244
+ client = TestClient(app)
245
+
246
+ # Test unknown command
247
+ response = client.post("/cmd", json={
248
+ "command": "unknown",
249
+ "params": {}
250
+ })
251
+
252
+ assert response.status_code == 200
253
+ data = response.json()
254
+ assert "error" in data
255
+ assert "Unknown command" in data["error"]["message"]
256
+
257
+ def test_register_endpoints_order():
258
+ """Test for checking order of endpoint registration."""
259
+ registry = MockRegistry()
260
+ adapter = MCPProxyAdapter(registry)
261
+
262
+ # Create mock for router and include_router
263
+ app = FastAPI()
264
+ original_include_router = app.include_router
265
+ app.include_router = MagicMock()
266
+
267
+ # Register endpoints
268
+ adapter.register_endpoints(app)
269
+
270
+ # Check that include_router was called
271
+ app.include_router.assert_called_once()
272
+
273
+ # Restore original method
274
+ app.include_router = original_include_router
@@ -0,0 +1,169 @@
1
+ """
2
+ Tests for basic command dispatcher functionality.
3
+ """
4
+ import sys
5
+ import os
6
+ import pytest
7
+ from typing import Dict, Any, List
8
+
9
+ # Add parent directory to path for imports
10
+ current_dir = os.path.dirname(os.path.abspath(__file__))
11
+ parent_dir = os.path.dirname(current_dir)
12
+ if parent_dir not in sys.path:
13
+ sys.path.insert(0, parent_dir)
14
+
15
+ # Import classes for testing
16
+ from mcp_proxy_adapter.dispatchers.json_rpc_dispatcher import JsonRpcDispatcher, CommandNotFoundError, CommandExecutionError
17
+
18
+ # Fixture for creating dispatcher with test commands
19
+ @pytest.fixture
20
+ def test_dispatcher():
21
+ """Creates dispatcher with test commands"""
22
+ dispatcher = JsonRpcDispatcher()
23
+
24
+ # Test command with parameters
25
+ def test_command(a: int, b: int = 1) -> int:
26
+ """
27
+ Test command for adding two numbers.
28
+
29
+ Args:
30
+ a: First number
31
+ b: Second number (default 1)
32
+
33
+ Returns:
34
+ int: Sum of numbers
35
+ """
36
+ return a + b
37
+
38
+ # Command for error testing
39
+ def error_command(message: str = "Test error") -> None:
40
+ """
41
+ Command for testing error handling.
42
+
43
+ Args:
44
+ message: Error message
45
+
46
+ Raises:
47
+ ValueError: Always raises error
48
+ """
49
+ raise ValueError(message)
50
+
51
+ # Command accepting params dictionary
52
+ def params_command(params: Dict[str, Any]) -> Dict[str, Any]:
53
+ """
54
+ Command for testing passing all parameters in dictionary.
55
+
56
+ Args:
57
+ params: Parameters dictionary
58
+
59
+ Returns:
60
+ Dict[str, Any]: Same parameters dictionary
61
+ """
62
+ return params
63
+
64
+ # Register commands
65
+ dispatcher.register_handler(
66
+ command="test",
67
+ handler=test_command,
68
+ description="Test command for adding numbers",
69
+ params={
70
+ "a": {"type": "integer", "description": "First number", "required": True},
71
+ "b": {"type": "integer", "description": "Second number", "required": False, "default": 1}
72
+ }
73
+ )
74
+
75
+ dispatcher.register_handler(
76
+ command="error",
77
+ handler=error_command,
78
+ description="Command for testing errors",
79
+ params={
80
+ "message": {"type": "string", "description": "Error message", "required": False}
81
+ }
82
+ )
83
+
84
+ dispatcher.register_handler(
85
+ command="params",
86
+ handler=params_command,
87
+ description="Command for testing params passing",
88
+ params={}
89
+ )
90
+
91
+ return dispatcher
92
+
93
+ def test_dispatcher_initialization():
94
+ """Test dispatcher initialization"""
95
+ dispatcher = JsonRpcDispatcher()
96
+
97
+ # Check that dispatcher contains built-in help command
98
+ assert "help" in dispatcher.get_valid_commands()
99
+
100
+ # Check that help command metadata is correct
101
+ help_info = dispatcher.get_command_info("help")
102
+ assert help_info is not None
103
+ assert "description" in help_info
104
+ assert "summary" in help_info
105
+ assert "params" in help_info
106
+
107
+ def test_register_and_execute_command(test_dispatcher):
108
+ """Test command registration and execution"""
109
+ # Check that command is registered
110
+ assert "test" in test_dispatcher.get_valid_commands()
111
+
112
+ # Execute command with required parameter
113
+ result = test_dispatcher.execute("test", a=5)
114
+ assert result == 6 # 5 + 1 (default value)
115
+
116
+ # Execute command with all parameters
117
+ result = test_dispatcher.execute("test", a=5, b=3)
118
+ assert result == 8 # 5 + 3
119
+
120
+ def test_command_not_found(test_dispatcher):
121
+ """Test handling of non-existent command"""
122
+ with pytest.raises(CommandNotFoundError):
123
+ test_dispatcher.execute("non_existent_command")
124
+
125
+ def test_command_execution_error(test_dispatcher):
126
+ """Test handling of error during command execution"""
127
+ with pytest.raises(CommandExecutionError):
128
+ test_dispatcher.execute("error")
129
+
130
+ def test_get_commands_info(test_dispatcher):
131
+ """Test getting information about commands"""
132
+ commands_info = test_dispatcher.get_commands_info()
133
+
134
+ # Check presence of all registered commands
135
+ assert "test" in commands_info
136
+ assert "error" in commands_info
137
+ assert "params" in commands_info
138
+ assert "help" in commands_info
139
+
140
+ # Check presence of all fields in command information
141
+ test_info = commands_info["test"]
142
+ assert "description" in test_info
143
+ assert "summary" in test_info
144
+ assert "params" in test_info
145
+
146
+ def test_params_command(test_dispatcher):
147
+ """Test command accepting params dictionary"""
148
+ test_params = {"key1": "value1", "key2": 123}
149
+ result = test_dispatcher.execute("params", **test_params)
150
+ assert result == test_params
151
+
152
+ def test_help_command(test_dispatcher):
153
+ """Test built-in help command"""
154
+ # Request list of all commands
155
+ result = test_dispatcher.execute("help")
156
+ assert "commands" in result
157
+ assert "total" in result
158
+ assert result["total"] == 4 # help, test, error, params
159
+
160
+ # Run second check through different approach - compare command list
161
+ assert "help" in result["commands"]
162
+ assert "test" in result["commands"]
163
+ assert "error" in result["commands"]
164
+ assert "params" in result["commands"]
165
+
166
+ # Check that command data contains required fields
167
+ assert "summary" in result["commands"]["test"]
168
+ assert "description" in result["commands"]["test"]
169
+ assert "params_count" in result["commands"]["test"]