mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.4__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. {examples → mcp_proxy_adapter/examples}/openapi_server.py +35 -7
  2. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/METADATA +98 -2
  3. mcp_proxy_adapter-2.1.4.dist-info/RECORD +28 -0
  4. mcp_proxy_adapter-2.1.4.dist-info/top_level.txt +1 -0
  5. docs/README.md +0 -172
  6. docs/README_ru.md +0 -172
  7. docs/architecture.md +0 -251
  8. docs/architecture_ru.md +0 -343
  9. docs/command_development.md +0 -250
  10. docs/command_development_ru.md +0 -593
  11. docs/deployment.md +0 -251
  12. docs/deployment_ru.md +0 -1298
  13. docs/examples.md +0 -254
  14. docs/examples_ru.md +0 -401
  15. docs/mcp_proxy_adapter.md +0 -251
  16. docs/mcp_proxy_adapter_ru.md +0 -405
  17. docs/quickstart.md +0 -251
  18. docs/quickstart_ru.md +0 -397
  19. docs/testing.md +0 -255
  20. docs/testing_ru.md +0 -469
  21. docs/validation_ru.md +0 -287
  22. examples/mcp_proxy_config.json +0 -175
  23. mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
  24. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
  25. scripts/code_analyzer/code_analyzer.py +0 -328
  26. scripts/code_analyzer/register_commands.py +0 -446
  27. scripts/publish.py +0 -85
  28. tests/conftest.py +0 -12
  29. tests/test_adapter.py +0 -529
  30. tests/test_adapter_coverage.py +0 -274
  31. tests/test_basic_dispatcher.py +0 -169
  32. tests/test_command_registry.py +0 -328
  33. tests/test_examples.py +0 -32
  34. tests/test_mcp_proxy_adapter.py +0 -568
  35. tests/test_mcp_proxy_adapter_basic.py +0 -262
  36. tests/test_part1.py +0 -348
  37. tests/test_part2.py +0 -524
  38. tests/test_schema.py +0 -358
  39. tests/test_simple_adapter.py +0 -251
  40. {examples → mcp_proxy_adapter/examples}/analyze_config.py +0 -0
  41. {examples → mcp_proxy_adapter/examples}/basic_integration.py +0 -0
  42. {examples → mcp_proxy_adapter/examples}/docstring_and_schema_example.py +0 -0
  43. {examples → mcp_proxy_adapter/examples}/extension_example.py +0 -0
  44. {examples → mcp_proxy_adapter/examples}/help_best_practices.py +0 -0
  45. {examples → mcp_proxy_adapter/examples}/help_usage.py +0 -0
  46. {examples → mcp_proxy_adapter/examples}/mcp_proxy_client.py +0 -0
  47. {examples → mcp_proxy_adapter/examples}/project_structure_example.py +0 -0
  48. {examples → mcp_proxy_adapter/examples}/testing_example.py +0 -0
  49. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/WHEEL +0 -0
  50. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/licenses/LICENSE +0 -0
tests/test_part2.py DELETED
@@ -1,524 +0,0 @@
1
- """
2
- Tests for MCPProxyAdapter - Extended parameter validation and error handling.
3
- """
4
- import json
5
- import logging
6
- import sys
7
- import os
8
- import pytest
9
- import tempfile
10
- from unittest.mock import MagicMock, patch
11
-
12
- # Add path to source files
13
- project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
14
- if project_root not in sys.path:
15
- sys.path.insert(0, project_root)
16
-
17
- from fastapi import FastAPI, APIRouter
18
- from fastapi.testclient import TestClient
19
-
20
- # Import directly from src
21
- from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger, SchemaOptimizer
22
- from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig, MCPProxyTool
23
-
24
- # Import common test components
25
- from tests.test_mcp_proxy_adapter import (
26
- MockDispatcher,
27
- MockRegistry,
28
- MockOpenApiGenerator,
29
- success_command,
30
- error_command,
31
- param_command,
32
- complex_param_command,
33
- type_error_command
34
- )
35
-
36
- # Переопределяем MockDispatcher для этого файла, чтобы добавить complex_param и type_error
37
- class PatchedMockDispatcher(MockDispatcher):
38
- def __init__(self):
39
- super().__init__()
40
- self.commands["complex_param"] = complex_param_command
41
- self.commands["type_error"] = type_error_command
42
- self.commands_info["complex_param"] = {
43
- "description": "Command with complex parameters",
44
- "params": {
45
- "array_param": {
46
- "type": "array",
47
- "description": "Array of values",
48
- "required": True
49
- },
50
- "object_param": {
51
- "type": "object",
52
- "description": "Object",
53
- "required": True
54
- },
55
- "bool_param": {
56
- "type": "boolean",
57
- "description": "Boolean value",
58
- "required": False,
59
- "default": True
60
- }
61
- }
62
- }
63
- self.commands_info["type_error"] = {
64
- "description": "Command that will raise TypeError",
65
- "params": {
66
- "param": {
67
- "type": "integer",
68
- "description": "Integer parameter",
69
- "required": True
70
- }
71
- }
72
- }
73
-
74
- class PatchedMockRegistry(MockRegistry):
75
- def __init__(self):
76
- super().__init__()
77
- self.dispatcher = PatchedMockDispatcher()
78
-
79
- @pytest.fixture
80
- def registry():
81
- return PatchedMockRegistry()
82
-
83
- @pytest.fixture
84
- def adapter(registry):
85
- """Returns a configured adapter for tests."""
86
- return MCPProxyAdapter(registry)
87
-
88
- @pytest.fixture
89
- def test_app(adapter):
90
- """Creates a FastAPI test application with a configured adapter."""
91
- app = FastAPI()
92
- adapter.register_endpoints(app)
93
- return TestClient(app)
94
-
95
- @pytest.fixture
96
- def custom_endpoint_adapter(registry):
97
- """Returns an adapter with a custom endpoint."""
98
- return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
99
-
100
- @pytest.fixture
101
- def custom_endpoint_app(custom_endpoint_adapter):
102
- """Creates a test application with an adapter having a custom endpoint."""
103
- app = FastAPI()
104
- custom_endpoint_adapter.register_endpoints(app)
105
- return TestClient(app)
106
-
107
- @pytest.fixture
108
- def no_schema_adapter(registry):
109
- """Returns an adapter without including OpenAPI schema."""
110
- return MCPProxyAdapter(registry, include_schema=False)
111
-
112
- @pytest.fixture
113
- def adapter_with_openapi(registry):
114
- """Returns an adapter with OpenAPI support for tests."""
115
- with patch('mcp_proxy_adapter.adapter.OpenApiGenerator', MockOpenApiGenerator):
116
- return MCPProxyAdapter(registry)
117
-
118
- @pytest.fixture
119
- def custom_prefix_adapter(registry):
120
- """Returns an adapter with a custom tool prefix."""
121
- return MCPProxyAdapter(registry, tool_name_prefix="custom_")
122
-
123
- @pytest.fixture
124
- def custom_logger():
125
- """Creates a custom logger for tests."""
126
- logger = logging.getLogger("test_logger")
127
- logger.setLevel(logging.DEBUG)
128
-
129
- # Clear handlers if they were added in previous tests
130
- if logger.handlers:
131
- logger.handlers = []
132
-
133
- # Add handler that will write messages to list
134
- log_records = []
135
-
136
- class ListHandler(logging.Handler):
137
- def emit(self, record):
138
- log_records.append(self.format(record))
139
-
140
- handler = ListHandler()
141
- formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
142
- handler.setFormatter(formatter)
143
- logger.addHandler(handler)
144
-
145
- return logger, log_records
146
-
147
- def test_error_command_execution(test_app):
148
- """Test error handling when executing a command."""
149
- response = test_app.post("/cmd", json={
150
- "jsonrpc": "2.0",
151
- "method": "error",
152
- "id": 1
153
- })
154
- assert response.status_code == 200 # JSON-RPC always returns 200
155
- data = response.json()
156
- assert "error" in data
157
- assert "Тестовая ошибка" in data["error"]["message"] or "Test error" in data["error"]["message"]
158
-
159
- def test_unknown_command(test_app):
160
- """Test handling of unknown command."""
161
- response = test_app.post("/cmd", json={
162
- "jsonrpc": "2.0",
163
- "method": "unknown_command",
164
- "id": 1
165
- })
166
- assert response.status_code == 200 # JSON-RPC always returns 200
167
- data = response.json()
168
- assert "error" in data
169
- assert "Unknown command" in data["error"]["message"]
170
-
171
- def test_missing_required_parameter(test_app):
172
- """Test handling of missing required parameter."""
173
- response = test_app.post("/cmd", json={
174
- "jsonrpc": "2.0",
175
- "method": "param",
176
- "params": {}, # Missing required parameter required_param
177
- "id": 1
178
- })
179
- assert response.status_code == 200 # JSON-RPC always returns 200
180
- data = response.json()
181
- assert "error" in data
182
- assert "required_param" in data["error"]["message"].lower()
183
-
184
- def test_custom_endpoint(custom_endpoint_app):
185
- """Test adapter working with custom endpoint."""
186
- # Проверяем, что стандартный эндпоинт доступен
187
- response = custom_endpoint_app.post("/cmd", json={
188
- "jsonrpc": "2.0",
189
- "method": "success",
190
- "params": {"value": 5},
191
- "id": 1
192
- })
193
- assert response.status_code == 200
194
- data = response.json()
195
- assert data["result"] == {"result": 10}
196
-
197
- # Проверяем, что кастомный эндпоинт работает
198
- response = custom_endpoint_app.post("/api/execute", json={
199
- "jsonrpc": "2.0",
200
- "method": "success",
201
- "params": {"value": 5},
202
- "id": 1
203
- })
204
- assert response.status_code == 200
205
- data = response.json()
206
- assert data["result"] == {"result": 10}
207
-
208
- # Test parameter type validation
209
- def test_string_parameter_validation(test_app):
210
- """Test string parameter validation."""
211
- response = test_app.post("/cmd", json={
212
- "jsonrpc": "2.0",
213
- "method": "param",
214
- "params": {
215
- "required_param": 123, # Must be a string
216
- "optional_param": 0
217
- },
218
- "id": 1
219
- })
220
- assert response.status_code == 200
221
- data = response.json()
222
- assert "error" in data
223
- assert "required_param" in data["error"]["message"]
224
- assert "must be a string" in data["error"]["message"]
225
-
226
- def test_integer_parameter_validation(test_app):
227
- """Test integer parameter validation."""
228
- response = test_app.post("/cmd", json={
229
- "jsonrpc": "2.0",
230
- "method": "param",
231
- "params": {
232
- "required_param": "test",
233
- "optional_param": "not_integer" # Must be an integer
234
- },
235
- "id": 1
236
- })
237
- assert response.status_code == 200
238
- data = response.json()
239
- assert "error" in data
240
- assert "optional_param" in data["error"]["message"]
241
- assert "must be an integer" in data["error"]["message"]
242
-
243
- def test_array_parameter_validation(test_app):
244
- """Test array parameter validation."""
245
- response = test_app.post("/cmd", json={
246
- "jsonrpc": "2.0",
247
- "method": "complex_param",
248
- "params": {
249
- "array_param": "not_array", # Must be an array
250
- "object_param": {}
251
- },
252
- "id": 1
253
- })
254
- assert response.status_code == 200
255
- data = response.json()
256
- assert "error" in data
257
- assert "array_param" in data["error"]["message"]
258
- assert "must be an array" in data["error"]["message"]
259
-
260
- def test_object_parameter_validation(test_app):
261
- """Test object parameter validation."""
262
- response = test_app.post("/cmd", json={
263
- "jsonrpc": "2.0",
264
- "method": "complex_param",
265
- "params": {
266
- "array_param": [1, 2, 3],
267
- "object_param": "not_object" # Must be an object
268
- },
269
- "id": 1
270
- })
271
- assert response.status_code == 200
272
- data = response.json()
273
- assert "error" in data
274
- assert "object_param" in data["error"]["message"]
275
- assert "must be an object" in data["error"]["message"]
276
-
277
- def test_boolean_parameter_validation(test_app):
278
- """Test boolean parameter validation."""
279
- response = test_app.post("/cmd", json={
280
- "jsonrpc": "2.0",
281
- "method": "complex_param",
282
- "params": {
283
- "array_param": [1, 2, 3],
284
- "object_param": {"key": "value"},
285
- "bool_param": "not_boolean" # Must be a boolean value
286
- },
287
- "id": 1
288
- })
289
- assert response.status_code == 200
290
- data = response.json()
291
- assert "error" in data
292
- assert "bool_param" in data["error"]["message"]
293
- assert "must be a boolean" in data["error"]["message"]
294
-
295
- # Test error handling
296
- def test_type_error_command(test_app):
297
- """Test TypeError handling when executing a command."""
298
- response = test_app.post("/cmd", json={
299
- "jsonrpc": "2.0",
300
- "method": "type_error",
301
- "params": {
302
- "param": "not_int" # Will cause TypeError inside function
303
- },
304
- "id": 1
305
- })
306
- assert response.status_code == 200
307
- data = response.json()
308
- assert "error" in data
309
- assert data["error"]["code"] in [400, -32602, 404] # Acceptable codes
310
- assert "param" in data["error"]["message"]
311
- assert "must be an integer" in data["error"]["message"]
312
-
313
- def test_unexpected_error_handling(test_app):
314
- """Test handling of unexpected error."""
315
- with patch.object(MockDispatcher, 'execute', side_effect=Exception("Unexpected error")):
316
- response = test_app.post("/cmd", json={
317
- "jsonrpc": "2.0",
318
- "method": "success",
319
- "params": {"value": 5},
320
- "id": 1
321
- })
322
- assert response.status_code == 200
323
- data = response.json()
324
- assert "error" in data
325
- assert data["error"]["code"] in [500, -32603]
326
- assert "Unexpected error" in data["error"]["message"]
327
-
328
- # Test configuration and schema generation
329
- def test_no_schema_endpoint(no_schema_adapter):
330
- """Test absence of schema endpoint if include_schema=False."""
331
- # Check that adapter is configured correctly
332
- assert not no_schema_adapter.include_schema
333
-
334
- # Check that there is no OpenAPI schema endpoint in routes
335
- routes = [route.path for route in no_schema_adapter.router.routes]
336
- assert "/openapi.json" not in routes
337
-
338
- def test_schema_endpoint(test_app):
339
- """Test presence of schema endpoint if include_schema=True."""
340
- response = test_app.get("/openapi.json")
341
- assert response.status_code == 200
342
- data = response.json()
343
- assert "openapi" in data
344
-
345
- def test_openapi_generator_integration(adapter_with_openapi):
346
- """Test OpenApiGenerator integration."""
347
- schema = adapter_with_openapi._generate_basic_schema()
348
- assert "openapi" in schema
349
- assert "info" in schema
350
- assert "paths" in schema
351
-
352
- def test_schema_without_openapi_generator(adapter):
353
- """Test schema generation without OpenApiGenerator."""
354
- schema = adapter._generate_basic_schema()
355
- assert "openapi" in schema
356
- assert "info" in schema
357
- assert "paths" in schema
358
-
359
- def test_config_generation(adapter):
360
- """Test configuration generation for MCPProxy."""
361
- config = adapter.generate_mcp_proxy_config()
362
- # Check configuration structure (устойчивая проверка)
363
- assert type(config).__name__ == "MCPProxyConfig"
364
- assert hasattr(config, "tools") and isinstance(config.tools, list)
365
- assert hasattr(config, "routes") and isinstance(config.routes, list)
366
- assert hasattr(config, "version") and config.version == "1.0"
367
- # Check presence of tools
368
- assert len(config.tools) > 0
369
- # Check presence of routes
370
- assert len(config.routes) > 0
371
- # Check tools and routes correspondence
372
- tool_names = [tool.name for tool in config.tools]
373
- route_tools = [route["tool_name"] for route in config.routes]
374
- assert all(name in tool_names for name in route_tools)
375
-
376
- def test_config_with_custom_prefix(custom_prefix_adapter):
377
- """Test configuration generation with custom prefix."""
378
- config = custom_prefix_adapter.generate_mcp_proxy_config()
379
- tool_names = [tool.name for tool in config.tools]
380
- for name in tool_names:
381
- assert name.startswith("custom_")
382
-
383
- def test_save_config_to_file():
384
- """Test saving configuration to file."""
385
- registry = MockRegistry()
386
- adapter = MCPProxyAdapter(registry)
387
-
388
- with tempfile.NamedTemporaryFile(suffix='.json') as temp_file:
389
- adapter.save_config_to_file(temp_file.name)
390
-
391
- # Check that file is not empty
392
- assert os.path.getsize(temp_file.name) > 0
393
-
394
- # Load and check content
395
- with open(temp_file.name, 'r') as f:
396
- config_data = json.load(f)
397
- assert "routes" in config_data
398
- assert "tools" in config_data
399
-
400
- def test_from_registry_classmethod():
401
- """Test creating adapter through from_registry class method."""
402
- registry = MockRegistry()
403
- adapter = MCPProxyAdapter.from_registry(registry, cmd_endpoint="/custom", tool_name_prefix="test_")
404
-
405
- assert adapter.cmd_endpoint == "/custom"
406
- assert adapter.tool_name_prefix == "test_"
407
-
408
- def test_logger_configuration():
409
- """Test logger configuration."""
410
- # Test with creating new logger
411
- logger1 = configure_logger()
412
- assert logger1.name == "mcp_proxy_adapter"
413
-
414
- # Test with parent logger
415
- parent_logger = logging.getLogger("parent")
416
- logger2 = configure_logger(parent_logger)
417
- assert logger2.name == "parent.mcp_proxy_adapter"
418
-
419
- def test_custom_logger_integration(custom_logger):
420
- """Test integration with custom logger."""
421
- logger, log_records = custom_logger
422
-
423
- # Configure adapter with custom logger
424
- with patch('mcp_proxy_adapter.adapter.logger', logger):
425
- registry = MockRegistry()
426
- adapter = MCPProxyAdapter(registry)
427
-
428
- # Create test application
429
- app = FastAPI()
430
- adapter.register_endpoints(app)
431
- client = TestClient(app)
432
-
433
- # Call unknown command to trigger logging
434
- client.post("/cmd", json={
435
- "jsonrpc": "2.0",
436
- "method": "unknown_command",
437
- "id": 1
438
- })
439
-
440
- # Check that message was logged
441
- assert any("unknown_command" in record for record in log_records)
442
-
443
- # Test edge cases
444
- def test_command_with_empty_info(test_app):
445
- """Test command with empty parameter information."""
446
- # Patch get_command_info to return empty dictionary
447
- with patch.object(MockDispatcher, 'get_command_info', return_value={}):
448
- response = test_app.post("/cmd", json={
449
- "jsonrpc": "2.0",
450
- "method": "success",
451
- "params": {"value": 5},
452
- "id": 1
453
- })
454
- assert response.status_code == 200
455
- data = response.json()
456
- assert "result" in data
457
-
458
- def test_command_with_none_info(test_app):
459
- """Test command with missing information."""
460
- # Patch get_command_info to return None
461
- with patch.object(MockDispatcher, 'get_command_info', return_value=None):
462
- response = test_app.post("/cmd", json={
463
- "jsonrpc": "2.0",
464
- "method": "success",
465
- "params": {"value": 5},
466
- "id": 1
467
- })
468
- assert response.status_code == 200
469
- data = response.json()
470
- assert "result" in data
471
-
472
- def test_command_info_without_params(test_app):
473
- """Test command with information, not containing parameters."""
474
- # Patch get_command_info to return dictionary without params
475
- with patch.object(MockDispatcher, 'get_command_info', return_value={"description": "Test"}):
476
- response = test_app.post("/cmd", json={
477
- "jsonrpc": "2.0",
478
- "method": "success",
479
- "params": {"value": 5},
480
- "id": 1
481
- })
482
- assert response.status_code == 200
483
- data = response.json()
484
- assert "result" in data
485
-
486
- def test_command_params_without_type(test_app):
487
- """Test command parameters without type indication."""
488
- # Create command information with parameter without type
489
- command_info = {
490
- "description": "Test",
491
- "params": {
492
- "value": {
493
- "description": "Value without type",
494
- "required": True
495
- }
496
- }
497
- }
498
-
499
- # Patch get_command_info
500
- with patch.object(MockDispatcher, 'get_command_info', return_value=command_info):
501
- response = test_app.post("/cmd", json={
502
- "jsonrpc": "2.0",
503
- "method": "success",
504
- "params": {"value": "test"},
505
- "id": 1
506
- })
507
- assert response.status_code == 200
508
- data = response.json()
509
- assert "result" in data
510
-
511
- def test_api_commands_endpoint(test_app):
512
- """Test endpoint for getting information about all commands."""
513
- response = test_app.get("/api/commands")
514
- assert response.status_code == 200
515
- # In real adapter commands may be in different structure,
516
- # let's check more general case - presence of answer in JSON format
517
- data = response.json()
518
- assert isinstance(data, dict)
519
- # Check that returned data about commands (without specific structure)
520
- assert len(data) > 0
521
- assert "success" in str(data) # Command name must be present somewhere in answer
522
-
523
- if __name__ == "__main__":
524
- pytest.main(["-v", __file__])