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_adapter.py DELETED
@@ -1,529 +0,0 @@
1
- """
2
- Tests for MCPProxyAdapter, checking integration with CommandRegistry
3
- and correct handling of different types of errors.
4
- """
5
- import json
6
- import logging
7
- import sys
8
- import os
9
- import pytest
10
- from unittest.mock import MagicMock, patch
11
- from typing import Dict, Any, List, Optional, Callable, Type
12
- import inspect
13
- from fastapi import FastAPI
14
- from fastapi.testclient import TestClient
15
-
16
- # Add parent directory to import path
17
- current_dir = os.path.dirname(os.path.abspath(__file__))
18
- parent_dir = os.path.dirname(current_dir)
19
- if parent_dir not in sys.path:
20
- sys.path.insert(0, parent_dir)
21
-
22
- # Import tested modules
23
- try:
24
- from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig
25
- from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
26
- from mcp_proxy_adapter.registry import CommandRegistry
27
- except ImportError:
28
- from src.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig
29
- from src.adapter import MCPProxyAdapter, configure_logger
30
- from src.registry import CommandRegistry
31
-
32
- # Test command functions
33
- def success_command(value: int = 1) -> dict:
34
- """Test command that completes successfully."""
35
- return {"result": value * 2}
36
-
37
- def error_command() -> None:
38
- """Test command that generates an error."""
39
- raise ValueError("Test error")
40
-
41
- def param_command(required_param: str, optional_param: int = 0) -> dict:
42
- """Test command with required and optional parameters."""
43
- return {"required": required_param, "optional": optional_param}
44
-
45
- # Base class for dispatcher, define it here for tests
46
- class BaseDispatcher:
47
- """Base class for command dispatcher."""
48
-
49
- def execute(self, command, **params):
50
- """Executes a command with specified parameters."""
51
- raise NotImplementedError("Method must be overridden in subclass")
52
-
53
- def get_valid_commands(self):
54
- """Returns a list of available commands."""
55
- raise NotImplementedError("Method must be overridden in subclass")
56
-
57
- def get_command_info(self, command):
58
- """Returns information about a command."""
59
- raise NotImplementedError("Method must be overridden in subclass")
60
-
61
- def get_commands_info(self):
62
- """Returns information about all commands."""
63
- raise NotImplementedError("Method must be overridden in subclass")
64
-
65
- # Mock for command dispatcher
66
- class MockDispatcher(BaseDispatcher):
67
- """Mock for command dispatcher in tests."""
68
-
69
- def __init__(self):
70
- self.commands = {
71
- "success": success_command,
72
- "error": error_command,
73
- "param": param_command,
74
- "execute": self.execute_from_params
75
- }
76
- self.commands_info = {
77
- "success": {
78
- "description": "Successful command",
79
- "params": {
80
- "value": {
81
- "type": "integer",
82
- "description": "Input value",
83
- "required": False,
84
- "default": 1
85
- }
86
- }
87
- },
88
- "error": {
89
- "description": "Command with error",
90
- "params": {}
91
- },
92
- "param": {
93
- "description": "Command with parameters",
94
- "params": {
95
- "required_param": {
96
- "type": "string",
97
- "description": "Required parameter",
98
- "required": True
99
- },
100
- "optional_param": {
101
- "type": "integer",
102
- "description": "Optional parameter",
103
- "required": False,
104
- "default": 0
105
- }
106
- }
107
- },
108
- "execute": {
109
- "description": "Universal command for executing other commands",
110
- "params": {
111
- "query": {
112
- "type": "string",
113
- "description": "Command or query to execute",
114
- "required": False
115
- }
116
- }
117
- }
118
- }
119
-
120
- def execute_from_params(self, **params):
121
- """Executes command based on parameters."""
122
- if "query" in params and params["query"] in self.commands:
123
- command = params.pop("query")
124
- return self.execute(command, **params)
125
- return {
126
- "available_commands": self.get_valid_commands(),
127
- "received_params": params
128
- }
129
-
130
- def execute(self, command, **params):
131
- if command not in self.commands:
132
- raise KeyError(f"Unknown command: {command}")
133
- return self.commands[command](**params)
134
-
135
- def get_valid_commands(self):
136
- return list(self.commands.keys())
137
-
138
- def get_command_info(self, command):
139
- return self.commands_info.get(command)
140
-
141
- def get_commands_info(self):
142
- return self.commands_info
143
-
144
- # Mock for CommandRegistry
145
- class MockRegistry:
146
- """Mock for CommandRegistry in tests."""
147
-
148
- def __init__(self):
149
- self.dispatcher = MockDispatcher()
150
- self.generators = []
151
-
152
- def get_commands_info(self):
153
- """Returns command information from the dispatcher."""
154
- return self.dispatcher.get_commands_info()
155
-
156
- def add_generator(self, generator):
157
- """Adds an API generator."""
158
- self.generators.append(generator)
159
- if hasattr(generator, 'set_dispatcher'):
160
- generator.set_dispatcher(self.dispatcher)
161
-
162
- # Fixtures for tests
163
- @pytest.fixture
164
- def registry():
165
- """Returns a mock command registry."""
166
- return MockRegistry()
167
-
168
- @pytest.fixture
169
- def adapter(registry):
170
- """Returns a configured adapter for tests."""
171
- return MCPProxyAdapter(registry)
172
-
173
- @pytest.fixture
174
- def test_app(adapter):
175
- """Creates a test FastAPI application with configured adapter."""
176
- app = FastAPI()
177
- adapter.register_endpoints(app)
178
- return TestClient(app)
179
-
180
- @pytest.fixture
181
- def custom_endpoint_adapter(registry):
182
- """Returns an adapter with custom endpoint."""
183
- return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
184
-
185
- @pytest.fixture
186
- def custom_endpoint_app(custom_endpoint_adapter):
187
- """Creates a test application with an adapter having a custom endpoint."""
188
- app = FastAPI()
189
- custom_endpoint_adapter.register_endpoints(app)
190
- return TestClient(app)
191
-
192
- @pytest.fixture
193
- def custom_logger():
194
- """Creates a custom logger for tests."""
195
- logger = logging.getLogger("mcp_proxy_adapter")
196
- logger.setLevel(logging.DEBUG)
197
-
198
- # Clear handlers if they were added in previous tests
199
- if logger.handlers:
200
- logger.handlers = []
201
-
202
- # Add handler that will record messages to list
203
- log_records = []
204
-
205
- class ListHandler(logging.Handler):
206
- def emit(self, record):
207
- log_records.append(self.format(record))
208
-
209
- handler = ListHandler()
210
- formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
211
- handler.setFormatter(formatter)
212
- logger.addHandler(handler)
213
-
214
- return logger, log_records
215
-
216
- # Tests for adapter
217
- def test_successful_command_execution(test_app):
218
- """Test successful command execution."""
219
- response = test_app.post("/cmd", json={
220
- "jsonrpc": "2.0",
221
- "method": "success",
222
- "params": {"value": 5},
223
- "id": 1
224
- })
225
- assert response.status_code == 200
226
- data = response.json()
227
- assert data["result"] == {"result": 10}
228
-
229
- def test_error_command_execution(test_app):
230
- """Test error handling when executing a command."""
231
- response = test_app.post("/cmd", json={
232
- "jsonrpc": "2.0",
233
- "method": "error",
234
- "id": 1
235
- })
236
- assert response.status_code == 200 # JSON-RPC always returns 200
237
- data = response.json()
238
- assert "error" in data
239
- assert "Test error" in data["error"]["message"]
240
-
241
- def test_unknown_command(test_app):
242
- """Test handling of unknown command."""
243
- response = test_app.post("/cmd", json={
244
- "jsonrpc": "2.0",
245
- "method": "unknown_command",
246
- "id": 1
247
- })
248
- assert response.status_code == 200 # JSON-RPC always returns 200
249
- data = response.json()
250
- assert "error" in data
251
- assert "Unknown command" in data["error"]["message"]
252
-
253
- def test_missing_required_parameter(test_app):
254
- """Test handling of missing required parameter."""
255
- response = test_app.post("/cmd", json={
256
- "jsonrpc": "2.0",
257
- "method": "param",
258
- "params": {}, # Missing required parameter required_param
259
- "id": 1
260
- })
261
- assert response.status_code == 200 # JSON-RPC always returns 200
262
- data = response.json()
263
- assert "error" in data
264
- assert "required_param" in data["error"]["message"].lower()
265
-
266
- def test_custom_endpoint(custom_endpoint_app):
267
- """Test adapter working with custom endpoint."""
268
- # Check that custom endpoint works
269
- response = custom_endpoint_app.post("/api/execute", json={
270
- "jsonrpc": "2.0",
271
- "method": "success",
272
- "params": {"value": 5},
273
- "id": 1
274
- })
275
- assert response.status_code == 200
276
- data = response.json()
277
- assert "result" in data
278
- assert data["result"] == {"result": 10}
279
-
280
- # Also test standard endpoint, which is now also available
281
- # with our changes in register_endpoints
282
- response = custom_endpoint_app.post("/cmd", json={
283
- "jsonrpc": "2.0",
284
- "method": "success",
285
- "params": {"value": 3},
286
- "id": 2
287
- })
288
- assert response.status_code == 200
289
- data = response.json()
290
- assert "result" in data
291
- assert data["result"] == {"result": 6}
292
-
293
- def test_adapter_config_with_custom_endpoint(custom_endpoint_adapter):
294
- """Test adapter configuration with custom endpoint."""
295
- config = custom_endpoint_adapter.generate_mcp_proxy_config()
296
-
297
- # Check that custom endpoint is used in configuration
298
- assert len(config.routes) > 0
299
- for route in config.routes:
300
- assert route["endpoint"] == "/api/execute"
301
-
302
- def test_custom_logger_integration(registry, custom_logger):
303
- """Test integration with external logger."""
304
- logger, log_records = custom_logger
305
-
306
- # Patch logger in adapter module with correct path to module
307
- with patch('mcp_proxy_adapter.adapter.logger', logger):
308
- adapter = MCPProxyAdapter(registry)
309
-
310
- # Create application for tests
311
- app = FastAPI()
312
- adapter.register_endpoints(app)
313
- client = TestClient(app)
314
-
315
- # Execute command with error to trigger logging
316
- client.post("/cmd", json={
317
- "jsonrpc": "2.0",
318
- "method": "error",
319
- "id": 1
320
- })
321
-
322
- # DEBUG: print log_records if test fails
323
- if not any("error" in record.lower() for record in log_records):
324
- print("LOG RECORDS:", log_records)
325
- assert any("error" in record.lower() for record in log_records), f"No error in log_records: {log_records}"
326
-
327
- def test_schema_generation(adapter):
328
- """Test schema generation."""
329
- # Create mock for schema generator
330
- adapter.openapi_generator = MagicMock()
331
- adapter.openapi_generator.generate_schema.return_value = {
332
- "openapi": "3.0.0",
333
- "info": {"title": "Test API", "version": "1.0.0"},
334
- "paths": {}
335
- }
336
-
337
- # Patch schema_optimizer
338
- original_optimize = adapter.schema_optimizer.optimize
339
- adapter.schema_optimizer.optimize = MagicMock(side_effect=lambda schema, *args, **kwargs: schema)
340
-
341
- # Create application and client for tests
342
- app = FastAPI(title="Test API")
343
- adapter.register_endpoints(app)
344
- client = TestClient(app)
345
-
346
- # Request schema
347
- response = client.get("/openapi.json")
348
- assert response.status_code == 200
349
- assert "openapi" in response.json()
350
-
351
- # Restore original method
352
- adapter.schema_optimizer.optimize = original_optimize
353
-
354
- def test_schema_generation_without_generator(registry):
355
- """Test schema generation without generator."""
356
- # Create adapter without schema generator
357
- adapter = MCPProxyAdapter(registry)
358
- adapter.openapi_generator = None
359
-
360
- # Patch _generate_basic_schema
361
- original_method = adapter._generate_basic_schema
362
- mock_schema = {
363
- "openapi": "3.0.0",
364
- "info": {"title": "Basic API", "version": "1.0.0"},
365
- "paths": {}
366
- }
367
- adapter._generate_basic_schema = MagicMock(return_value=mock_schema)
368
-
369
- # Patch schema_optimizer
370
- original_optimize = adapter.schema_optimizer.optimize
371
- adapter.schema_optimizer.optimize = MagicMock(side_effect=lambda schema, *args, **kwargs: schema)
372
-
373
- # Create application and client for tests
374
- app = FastAPI(title="Basic API")
375
- adapter.register_endpoints(app)
376
- client = TestClient(app)
377
-
378
- # Request schema
379
- response = client.get("/openapi.json")
380
- assert response.status_code == 200
381
- assert "openapi" in response.json()
382
-
383
- # Restore original methods
384
- adapter._generate_basic_schema = original_method
385
- adapter.schema_optimizer.optimize = original_optimize
386
-
387
- def test_config_generation(adapter):
388
- """Test configuration generation for MCP Proxy."""
389
- # Generate configuration
390
- config = adapter.generate_mcp_proxy_config()
391
- # Check configuration structure (устойчивая проверка)
392
- assert type(config).__name__ == "MCPProxyConfig"
393
- assert hasattr(config, "tools") and isinstance(config.tools, list)
394
- assert hasattr(config, "routes") and isinstance(config.routes, list)
395
- assert hasattr(config, "version") and config.version == "1.0"
396
- # Check presence of tools
397
- assert len(config.tools) > 0
398
- # Check presence of routes
399
- assert len(config.routes) > 0
400
- # Check tools and routes correspondence
401
- tool_names = [tool.name for tool in config.tools]
402
- route_tools = [route["tool_name"] for route in config.routes]
403
- assert all(name in tool_names for name in route_tools)
404
-
405
- def test_save_config_to_file(adapter, tmp_path):
406
- """Test saving configuration to file."""
407
- # Create temporary file for test
408
- config_file = tmp_path / "config.json"
409
-
410
- # Save configuration to file
411
- adapter.save_config_to_file(str(config_file))
412
-
413
- # Check that file was created
414
- assert config_file.exists()
415
-
416
- # Load configuration from file and check
417
- with open(config_file, "r", encoding="utf-8") as f:
418
- config = json.load(f)
419
-
420
- assert "version" in config
421
- assert "tools" in config
422
- assert len(config["tools"]) == 4 # Corrected: now we have 4 commands
423
- assert "routes" in config
424
- assert len(config["routes"]) == 4 # Corrected: now we have 4 routes, corresponding to commands
425
-
426
- def test_parameter_type_validation(test_app):
427
- """Test parameter type validation."""
428
- # Test with incorrect parameter type
429
- response = test_app.post("/cmd", json={
430
- "jsonrpc": "2.0",
431
- "method": "param",
432
- "params": {
433
- "required_param": 123, # Should be string, but passing number
434
- "optional_param": 1
435
- },
436
- "id": 1
437
- })
438
-
439
- assert response.status_code == 200 # JSON-RPC always returns 200
440
- data = response.json()
441
- assert "error" in data
442
- assert "Invalid parameter types" in data["error"]["message"]
443
-
444
- def test_type_error_handling(test_app):
445
- """Test type error handling when executing a command."""
446
- # Create request with incorrect types for checking TypeError handling
447
- response = test_app.post("/cmd", json={
448
- "jsonrpc": "2.0",
449
- "method": "success",
450
- "params": {"value": "not a number"}, # success_command expects int
451
- "id": 1
452
- })
453
-
454
- assert response.status_code == 200 # JSON-RPC always returns 200
455
- data = response.json()
456
- assert "error" in data
457
- assert "Invalid parameter types" in data["error"]["message"]
458
-
459
- def test_unexpected_error_handling(test_app):
460
- """Test handling of unexpected error when executing a request."""
461
- # Here we pass invalid JSON, which will cause an error when parsing
462
- response = test_app.post("/cmd", content="invalid json")
463
-
464
- assert response.status_code == 200 # Now we return 200 with error information
465
- data = response.json()
466
- assert "error" in data
467
- assert "Invalid JSON format" in data["error"]["message"]
468
-
469
- def test_from_registry_classmethod(registry):
470
- """Test creating adapter through from_registry class method."""
471
- # Create adapter with custom parameters through from_registry
472
- adapter = MCPProxyAdapter.from_registry(
473
- registry,
474
- cmd_endpoint="/custom",
475
- tool_name_prefix="test_"
476
- )
477
-
478
- # Check that parameters were passed correctly
479
- assert adapter.cmd_endpoint == "/custom"
480
- assert adapter.tool_name_prefix == "test_"
481
- assert adapter.registry == registry
482
-
483
- def test_configure_logger():
484
- """Test configure_logger function."""
485
- # Create parent logger
486
- parent_logger = logging.getLogger("parent")
487
-
488
- # Configure child logger
489
- child_logger = configure_logger(parent_logger)
490
-
491
- # Check that logger was configured as child
492
- assert child_logger.name == "parent.mcp_proxy_adapter"
493
-
494
- # Check configuration without parent logger
495
- default_logger = configure_logger()
496
- assert default_logger.name == "mcp_proxy_adapter"
497
-
498
- def test_params_only_format(test_app):
499
- """Test request format with only params."""
500
- # Test request with only params field
501
- response = test_app.post("/cmd", json={
502
- "params": {"query": "success", "value": 5}
503
- })
504
-
505
- assert response.status_code == 200
506
- data = response.json()
507
- assert "result" in data
508
- assert data["result"] == {"result": 10}
509
-
510
- # Test request with command in params
511
- response = test_app.post("/cmd", json={
512
- "params": {"command": "success", "value": 7}
513
- })
514
-
515
- assert response.status_code == 200
516
- data = response.json()
517
- assert "result" in data
518
- assert data["result"] == {"result": 14}
519
-
520
- # Test request without explicitly specifying command
521
- response = test_app.post("/cmd", json={
522
- "params": {"unknown_param": "value"}
523
- })
524
-
525
- assert response.status_code == 200
526
- data = response.json()
527
- assert "result" in data
528
- assert "available_commands" in data["result"]
529
- assert "received_params" in data["result"]