mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.3__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 (50) hide show
  1. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/METADATA +1 -1
  2. mcp_proxy_adapter-2.1.3.dist-info/RECORD +18 -0
  3. mcp_proxy_adapter-2.1.3.dist-info/top_level.txt +1 -0
  4. docs/README.md +0 -172
  5. docs/README_ru.md +0 -172
  6. docs/architecture.md +0 -251
  7. docs/architecture_ru.md +0 -343
  8. docs/command_development.md +0 -250
  9. docs/command_development_ru.md +0 -593
  10. docs/deployment.md +0 -251
  11. docs/deployment_ru.md +0 -1298
  12. docs/examples.md +0 -254
  13. docs/examples_ru.md +0 -401
  14. docs/mcp_proxy_adapter.md +0 -251
  15. docs/mcp_proxy_adapter_ru.md +0 -405
  16. docs/quickstart.md +0 -251
  17. docs/quickstart_ru.md +0 -397
  18. docs/testing.md +0 -255
  19. docs/testing_ru.md +0 -469
  20. docs/validation_ru.md +0 -287
  21. examples/analyze_config.py +0 -141
  22. examples/basic_integration.py +0 -161
  23. examples/docstring_and_schema_example.py +0 -60
  24. examples/extension_example.py +0 -60
  25. examples/help_best_practices.py +0 -67
  26. examples/help_usage.py +0 -64
  27. examples/mcp_proxy_client.py +0 -131
  28. examples/mcp_proxy_config.json +0 -175
  29. examples/openapi_server.py +0 -369
  30. examples/project_structure_example.py +0 -47
  31. examples/testing_example.py +0 -53
  32. mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
  33. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
  34. scripts/code_analyzer/code_analyzer.py +0 -328
  35. scripts/code_analyzer/register_commands.py +0 -446
  36. scripts/publish.py +0 -85
  37. tests/conftest.py +0 -12
  38. tests/test_adapter.py +0 -529
  39. tests/test_adapter_coverage.py +0 -274
  40. tests/test_basic_dispatcher.py +0 -169
  41. tests/test_command_registry.py +0 -328
  42. tests/test_examples.py +0 -32
  43. tests/test_mcp_proxy_adapter.py +0 -568
  44. tests/test_mcp_proxy_adapter_basic.py +0 -262
  45. tests/test_part1.py +0 -348
  46. tests/test_part2.py +0 -524
  47. tests/test_schema.py +0 -358
  48. tests/test_simple_adapter.py +0 -251
  49. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/WHEEL +0 -0
  50. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/licenses/LICENSE +0 -0
tests/test_schema.py DELETED
@@ -1,358 +0,0 @@
1
- """
2
- Tests for SchemaOptimizer, checking OpenAPI schema optimization for MCP Proxy.
3
- """
4
- import json
5
- import sys
6
- import os
7
- import pytest
8
- from typing import Dict, Any
9
-
10
- # Add path to source files
11
- project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
12
- sys.path.insert(0, os.path.join(project_root, 'src'))
13
-
14
- from mcp_proxy_adapter.schema import SchemaOptimizer
15
-
16
- # Test fixtures
17
- @pytest.fixture
18
- def base_schema() -> Dict[str, Any]:
19
- """Returns base OpenAPI schema for tests."""
20
- return {
21
- "openapi": "3.0.2",
22
- "info": {
23
- "title": "Test API",
24
- "description": "Test API for SchemaOptimizer",
25
- "version": "1.0.0"
26
- },
27
- "paths": {
28
- "/test": {
29
- "get": {
30
- "summary": "Test endpoint",
31
- "description": "Test endpoint for SchemaOptimizer",
32
- "responses": {
33
- "200": {
34
- "description": "Successful response"
35
- }
36
- }
37
- }
38
- }
39
- }
40
- }
41
-
42
- @pytest.fixture
43
- def commands_info() -> Dict[str, Dict[str, Any]]:
44
- """Returns command information for tests."""
45
- return {
46
- "test_command": {
47
- "description": "Test command",
48
- "params": {
49
- "param1": {
50
- "type": "string",
51
- "description": "String parameter",
52
- "required": True
53
- },
54
- "param2": {
55
- "type": "integer",
56
- "description": "Numeric parameter",
57
- "required": False,
58
- "default": 42
59
- },
60
- "param3": {
61
- "type": "boolean",
62
- "description": "Boolean parameter",
63
- "required": False,
64
- "default": False
65
- }
66
- }
67
- },
68
- "no_params_command": {
69
- "description": "Command without parameters",
70
- "params": {}
71
- },
72
- "enum_params_command": {
73
- "description": "Command with enumerations",
74
- "params": {
75
- "enum_param": {
76
- "type": "string",
77
- "description": "Enumeration parameter",
78
- "required": True,
79
- "enum": ["value1", "value2", "value3"]
80
- }
81
- }
82
- },
83
- "example_command": {
84
- "description": "Command with example",
85
- "params": {
86
- "example_param": {
87
- "type": "string",
88
- "description": "Parameter with example",
89
- "required": True,
90
- "example": "example_value"
91
- }
92
- }
93
- }
94
- }
95
-
96
- # Tests for SchemaOptimizer
97
- def test_optimize_basic(base_schema, commands_info):
98
- """Test basic schema optimization."""
99
- optimizer = SchemaOptimizer()
100
- cmd_endpoint = "/cmd"
101
-
102
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
103
-
104
- # Check basic structure
105
- assert "openapi" in optimized
106
- assert "info" in optimized
107
- assert "paths" in optimized
108
- assert cmd_endpoint in optimized["paths"]
109
- assert "post" in optimized["paths"][cmd_endpoint]
110
-
111
- # Check presence of components
112
- assert "components" in optimized
113
- assert "schemas" in optimized["components"]
114
- assert "CommandRequest" in optimized["components"]["schemas"]
115
- assert "CommandResponse" in optimized["components"]["schemas"]
116
-
117
- # Check presence of parameter schemas for each command
118
- for cmd_name in commands_info.keys():
119
- param_schema_name = f"{cmd_name.capitalize()}Params"
120
- assert param_schema_name in optimized["components"]["schemas"]
121
-
122
- def test_optimize_with_examples(base_schema, commands_info):
123
- """Test optimization with examples."""
124
- optimizer = SchemaOptimizer()
125
- cmd_endpoint = "/cmd"
126
-
127
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
128
-
129
- # Check presence of examples
130
- assert "examples" in optimized["components"]
131
-
132
- for cmd_name in commands_info.keys():
133
- example_id = f"{cmd_name}_example"
134
- assert example_id in optimized["components"]["examples"]
135
-
136
- # Check example correctness
137
- example = optimized["components"]["examples"][example_id]
138
- assert "value" in example
139
- assert "command" in example["value"]
140
- assert example["value"]["command"] == cmd_name
141
-
142
- # For commands with parameters check that params is present
143
- if commands_info[cmd_name]["params"]:
144
- assert "params" in example["value"]
145
- # For commands without parameters check that params is absent
146
- else:
147
- assert "params" not in example["value"]
148
-
149
- def test_optimize_command_request_schema(base_schema, commands_info):
150
- """Test CommandRequest schema."""
151
- optimizer = SchemaOptimizer()
152
- cmd_endpoint = "/cmd"
153
-
154
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
155
-
156
- command_request = optimized["components"]["schemas"]["CommandRequest"]
157
-
158
- # Check basic structure
159
- assert "properties" in command_request
160
- assert "command" in command_request["properties"]
161
- assert "params" in command_request["properties"]
162
-
163
- # Check enum in command
164
- assert "enum" in command_request["properties"]["command"]
165
- for cmd_name in commands_info.keys():
166
- assert cmd_name in command_request["properties"]["command"]["enum"]
167
-
168
- # Check oneOf in params
169
- assert "oneOf" in command_request["properties"]["params"]
170
-
171
- # Last element in oneOf should be null for commands without parameters
172
- last_oneof = command_request["properties"]["params"]["oneOf"][-1]
173
- assert ("type" in last_oneof and last_oneof["type"] == "null") or last_oneof.get("nullable") is True or "$ref" in last_oneof
174
-
175
- # Check references to parameter schemas
176
- param_refs = [ref["$ref"].split("/")[-1] for ref in command_request["properties"]["params"]["oneOf"][:-1] if "$ref" in ref]
177
- for cmd_name in commands_info.keys():
178
- param_schema_name = f"{cmd_name.capitalize()}Params"
179
- if commands_info[cmd_name]["params"]: # Проверяем только если есть параметры
180
- assert param_schema_name in param_refs
181
-
182
- def test_optimize_parameter_schemas(base_schema, commands_info):
183
- """Test parameter schemas."""
184
- optimizer = SchemaOptimizer()
185
- cmd_endpoint = "/cmd"
186
-
187
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
188
-
189
- # Check parameter schemas for each command
190
- for cmd_name, cmd_info in commands_info.items():
191
- param_schema_name = f"{cmd_name.capitalize()}Params"
192
- param_schema = optimized["components"]["schemas"][param_schema_name]
193
-
194
- assert "properties" in param_schema
195
-
196
- # Check parameters
197
- for param_name, param_info in cmd_info.get("params", {}).items():
198
- assert param_name in param_schema["properties"]
199
- assert "type" in param_schema["properties"][param_name]
200
- assert param_schema["properties"][param_name]["type"] == param_info["type"]
201
-
202
- # Check description
203
- assert "description" in param_schema["properties"][param_name]
204
- assert param_schema["properties"][param_name]["description"] == param_info["description"]
205
-
206
- # Check default if present
207
- if "default" in param_info:
208
- assert "default" in param_schema["properties"][param_name]
209
- assert param_schema["properties"][param_name]["default"] == param_info["default"]
210
-
211
- # Check enum if present
212
- if "enum" in param_info:
213
- assert "enum" in param_schema["properties"][param_name]
214
- assert param_schema["properties"][param_name]["enum"] == param_info["enum"]
215
-
216
- # Check required if there are required parameters
217
- required_params = [
218
- param_name for param_name, param_info in cmd_info.get("params", {}).items()
219
- if param_info.get("required", False)
220
- ]
221
-
222
- if required_params:
223
- assert "required" in param_schema
224
- assert set(param_schema["required"]) == set(required_params)
225
-
226
- def test_add_tool_descriptions(base_schema, commands_info):
227
- """Test adding tool descriptions."""
228
- optimizer = SchemaOptimizer()
229
- cmd_endpoint = "/cmd"
230
-
231
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
232
-
233
- # Check presence of x-mcp-tools
234
- assert "x-mcp-tools" in optimized
235
- assert isinstance(optimized["x-mcp-tools"], list)
236
-
237
- # Check tool descriptions
238
- for cmd_name, cmd_info in commands_info.items():
239
- # Find corresponding tool
240
- tool = next((t for t in optimized["x-mcp-tools"] if t["name"] == f"mcp_{cmd_name}"), None)
241
-
242
- assert tool is not None
243
- assert "description" in tool
244
- assert "parameters" in tool
245
- assert "type" in tool["parameters"]
246
- assert tool["parameters"]["type"] == "object"
247
- assert "properties" in tool["parameters"]
248
-
249
- # Check parameters
250
- for param_name, param_info in cmd_info.get("params", {}).items():
251
- assert param_name in tool["parameters"]["properties"]
252
-
253
- # Check type and description
254
- assert "type" in tool["parameters"]["properties"][param_name]
255
- assert tool["parameters"]["properties"][param_name]["type"] == param_info["type"]
256
- assert "description" in tool["parameters"]["properties"][param_name]
257
-
258
- # Check enum if present
259
- if "enum" in param_info:
260
- assert "enum" in tool["parameters"]["properties"][param_name]
261
- assert tool["parameters"]["properties"][param_name]["enum"] == param_info["enum"]
262
-
263
- # Check default if present
264
- if "default" in param_info:
265
- assert "default" in tool["parameters"]["properties"][param_name]
266
- assert tool["parameters"]["properties"][param_name]["default"] == param_info["default"]
267
-
268
- # Check required
269
- required_params = [
270
- param_name for param_name, param_info in cmd_info.get("params", {}).items()
271
- if param_info.get("required", False)
272
- ]
273
-
274
- if required_params:
275
- assert "required" in tool["parameters"]
276
- assert set(tool["parameters"]["required"]) == set(required_params)
277
-
278
- def test_optimize_with_empty_schema():
279
- """Test optimization with empty schema."""
280
- optimizer = SchemaOptimizer()
281
- empty_schema = {}
282
- cmd_endpoint = "/cmd"
283
- commands_info = {
284
- "test_command": {
285
- "description": "Test command",
286
- "params": {}
287
- }
288
- }
289
-
290
- optimized = optimizer.optimize(empty_schema, cmd_endpoint, commands_info)
291
-
292
- # Ensure that optimizer created a valid schema even from empty
293
- assert "openapi" in optimized
294
- assert "info" in optimized
295
- assert "paths" in optimized
296
- assert cmd_endpoint in optimized["paths"]
297
- assert "components" in optimized
298
- assert "schemas" in optimized["components"]
299
- assert "CommandRequest" in optimized["components"]["schemas"]
300
- assert "CommandResponse" in optimized["components"]["schemas"]
301
-
302
- def test_optimize_command_with_all_param_types(base_schema):
303
- """Test optimization of command with all parameter types."""
304
- optimizer = SchemaOptimizer()
305
- cmd_endpoint = "/cmd"
306
- commands_info = {
307
- "all_types": {
308
- "description": "Command with all parameter types",
309
- "params": {
310
- "string_param": {
311
- "type": "string",
312
- "description": "String parameter",
313
- "required": True
314
- },
315
- "integer_param": {
316
- "type": "integer",
317
- "description": "Integer parameter",
318
- "required": False,
319
- "default": 0
320
- },
321
- "number_param": {
322
- "type": "number",
323
- "description": "Numeric parameter",
324
- "required": False,
325
- "default": 0.0
326
- },
327
- "boolean_param": {
328
- "type": "boolean",
329
- "description": "Boolean parameter",
330
- "required": False,
331
- "default": False
332
- },
333
- "array_param": {
334
- "type": "array",
335
- "description": "Array",
336
- "required": False
337
- },
338
- "object_param": {
339
- "type": "object",
340
- "description": "Object",
341
- "required": False
342
- }
343
- }
344
- }
345
- }
346
-
347
- optimized = optimizer.optimize(base_schema, cmd_endpoint, commands_info)
348
-
349
- # Check parameter schema
350
- param_schema = optimized["components"]["schemas"]["All_typesParams"]
351
-
352
- # Check parameter types
353
- assert param_schema["properties"]["string_param"]["type"] == "string"
354
- assert param_schema["properties"]["integer_param"]["type"] == "integer"
355
- assert param_schema["properties"]["number_param"]["type"] == "number"
356
- assert param_schema["properties"]["boolean_param"]["type"] == "boolean"
357
- assert param_schema["properties"]["array_param"]["type"] == "array"
358
- assert param_schema["properties"]["object_param"]["type"] == "object"
@@ -1,251 +0,0 @@
1
- """
2
- Simplified test for MCPProxyAdapter.
3
- """
4
- import json
5
- import pytest
6
- from unittest.mock import MagicMock
7
- from fastapi import FastAPI
8
- from fastapi.testclient import TestClient
9
-
10
- # Create mock classes for testing
11
- class MockDispatcher:
12
- def __init__(self):
13
- self.commands = {
14
- "test_command": lambda value=1: {"result": value * 2}
15
- }
16
- self.commands_info = {
17
- "test_command": {
18
- "description": "Test command",
19
- "params": {
20
- "value": {
21
- "type": "integer",
22
- "required": False,
23
- "default": 1
24
- }
25
- }
26
- }
27
- }
28
-
29
- def execute(self, command, **params):
30
- return self.commands[command](**params)
31
-
32
- def get_valid_commands(self):
33
- return list(self.commands.keys())
34
-
35
- def get_command_info(self, command):
36
- return self.commands_info.get(command)
37
-
38
- def get_commands_info(self):
39
- return self.commands_info
40
-
41
- class MockRegistry:
42
- def __init__(self):
43
- self.dispatcher = MockDispatcher()
44
- self.generators = []
45
-
46
- def get_commands_info(self):
47
- return self.dispatcher.get_commands_info()
48
-
49
- def add_generator(self, generator):
50
- self.generators.append(generator)
51
-
52
- # Создаем минимальную версию JsonRpcRequest и JsonRpcResponse
53
- class JsonRpcRequest:
54
- def __init__(self, method, params=None, id=None):
55
- self.jsonrpc = "2.0"
56
- self.method = method
57
- self.params = params or {}
58
- self.id = id
59
-
60
- class JsonRpcResponse:
61
- def __init__(self, result=None, error=None, id=None, jsonrpc="2.0"):
62
- self.jsonrpc = jsonrpc
63
- self.result = result
64
- self.error = error
65
- self.id = id
66
-
67
- def dict(self):
68
- response = {"jsonrpc": self.jsonrpc}
69
- if self.result is not None:
70
- response["result"] = self.result
71
- if self.error is not None:
72
- response["error"] = self.error
73
- if self.id is not None:
74
- response["id"] = self.id
75
- return response
76
-
77
- # Мок для SchemaOptimizer
78
- class MockSchemaOptimizer:
79
- def optimize(self, schema, cmd_endpoint, commands_info):
80
- return schema
81
-
82
- # Определяем класс MCPProxyAdapter для тестов
83
- class MCPProxyAdapter:
84
- def __init__(self, registry, cmd_endpoint="/cmd", include_schema=True, optimize_schema=True, tool_name_prefix="mcp_"):
85
- self.registry = registry
86
- self.cmd_endpoint = cmd_endpoint
87
- self.include_schema = include_schema
88
- self.optimize_schema = optimize_schema
89
- self.tool_name_prefix = tool_name_prefix
90
- self.router = MagicMock()
91
- self.schema_optimizer = MockSchemaOptimizer()
92
- self.openapi_generator = None
93
-
94
- def register_endpoints(self, app):
95
- # Регистрируем эндпоинт для выполнения команд
96
- @app.post(self.cmd_endpoint)
97
- async def execute_command(request_data: dict):
98
- request = JsonRpcRequest(
99
- method=request_data.get("method", ""),
100
- params=request_data.get("params", {}),
101
- id=request_data.get("id")
102
- )
103
-
104
- # Проверяем существование команды
105
- if request.method not in self.registry.dispatcher.get_valid_commands():
106
- return {
107
- "jsonrpc": "2.0",
108
- "error": {
109
- "code": -32601,
110
- "message": f"Command '{request.method}' not found"
111
- },
112
- "id": request.id
113
- }
114
-
115
- # Выполняем команду
116
- try:
117
- result = self.registry.dispatcher.execute(
118
- request.method,
119
- **request.params
120
- )
121
-
122
- # Возвращаем результат
123
- return {
124
- "jsonrpc": "2.0",
125
- "result": result,
126
- "id": request.id
127
- }
128
- except Exception as e:
129
- return {
130
- "jsonrpc": "2.0",
131
- "error": {
132
- "code": -32603,
133
- "message": str(e)
134
- },
135
- "id": request.id
136
- }
137
-
138
- def generate_mcp_proxy_config(self):
139
- return {
140
- "version": "1.0",
141
- "tools": [
142
- {
143
- "name": f"{self.tool_name_prefix}{cmd_name}",
144
- "description": cmd_info.get("description", ""),
145
- "parameters": {
146
- "type": "object",
147
- "properties": {param_name: {"type": param_info.get("type", "string")}
148
- for param_name, param_info in cmd_info.get("params", {}).items()},
149
- "required": [param_name for param_name, param_info in cmd_info.get("params", {}).items()
150
- if param_info.get("required", False)]
151
- }
152
- }
153
- for cmd_name, cmd_info in self.registry.get_commands_info().items()
154
- ],
155
- "routes": [
156
- {
157
- "tool_name": f"{self.tool_name_prefix}{cmd_name}",
158
- "endpoint": self.cmd_endpoint,
159
- "method": "post",
160
- "json_rpc": {"method": cmd_name}
161
- }
162
- for cmd_name in self.registry.get_commands_info()
163
- ]
164
- }
165
-
166
- # Тесты
167
- @pytest.fixture
168
- def registry():
169
- return MockRegistry()
170
-
171
- @pytest.fixture
172
- def adapter(registry):
173
- return MCPProxyAdapter(registry)
174
-
175
- @pytest.fixture
176
- def custom_endpoint_adapter(registry):
177
- return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
178
-
179
- @pytest.fixture
180
- def test_app(adapter):
181
- app = FastAPI()
182
- adapter.register_endpoints(app)
183
- return TestClient(app)
184
-
185
- @pytest.fixture
186
- def custom_endpoint_app(custom_endpoint_adapter):
187
- app = FastAPI()
188
- custom_endpoint_adapter.register_endpoints(app)
189
- return TestClient(app)
190
-
191
- def test_successful_command_execution(test_app):
192
- """Тест успешного выполнения команды."""
193
- response = test_app.post("/cmd", json={
194
- "jsonrpc": "2.0",
195
- "method": "test_command",
196
- "params": {"value": 5},
197
- "id": 1
198
- })
199
- assert response.status_code == 200
200
- data = response.json()
201
- assert data["result"] == {"result": 10}
202
-
203
- def test_unknown_command(test_app):
204
- """Тест обработки неизвестной команды."""
205
- response = test_app.post("/cmd", json={
206
- "jsonrpc": "2.0",
207
- "method": "unknown_command",
208
- "id": 1
209
- })
210
- assert response.status_code == 200
211
- data = response.json()
212
- assert "error" in data
213
- assert "not found" in data["error"]["message"]
214
-
215
- def test_custom_endpoint(custom_endpoint_app):
216
- """Тест работы адаптера с кастомным эндпоинтом."""
217
- # Проверяем, что стандартный эндпоинт недоступен
218
- response = custom_endpoint_app.post("/cmd", json={
219
- "jsonrpc": "2.0",
220
- "method": "test_command",
221
- "params": {"value": 5},
222
- "id": 1
223
- })
224
- assert response.status_code == 404
225
-
226
- # Проверяем, что кастомный эндпоинт работает
227
- response = custom_endpoint_app.post("/api/execute", json={
228
- "jsonrpc": "2.0",
229
- "method": "test_command",
230
- "params": {"value": 5},
231
- "id": 1
232
- })
233
- assert response.status_code == 200
234
- data = response.json()
235
- assert data["result"] == {"result": 10}
236
-
237
- def test_mcp_proxy_config(adapter):
238
- """Test MCP Proxy configuration generation."""
239
- config = adapter.generate_mcp_proxy_config()
240
-
241
- assert "version" in config
242
- assert "tools" in config
243
- assert len(config["tools"]) == 1
244
-
245
- tool = config["tools"][0]
246
- assert tool["name"] == "mcp_test_command"
247
- assert "parameters" in tool
248
-
249
- assert "routes" in config
250
- assert len(config["routes"]) == 1
251
- assert config["routes"][0]["endpoint"] == "/cmd"