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.
- docs/README.md +172 -0
- docs/README_ru.md +172 -0
- docs/architecture.md +251 -0
- docs/architecture_ru.md +343 -0
- docs/command_development.md +250 -0
- docs/command_development_ru.md +593 -0
- docs/deployment.md +251 -0
- docs/deployment_ru.md +1298 -0
- docs/examples.md +254 -0
- docs/examples_ru.md +401 -0
- docs/mcp_proxy_adapter.md +251 -0
- docs/mcp_proxy_adapter_ru.md +405 -0
- docs/quickstart.md +251 -0
- docs/quickstart_ru.md +397 -0
- docs/testing.md +255 -0
- docs/testing_ru.md +469 -0
- docs/validation_ru.md +287 -0
- examples/analyze_config.py +141 -0
- examples/basic_integration.py +161 -0
- examples/docstring_and_schema_example.py +60 -0
- examples/extension_example.py +60 -0
- examples/help_best_practices.py +67 -0
- examples/help_usage.py +64 -0
- examples/mcp_proxy_client.py +131 -0
- examples/mcp_proxy_config.json +175 -0
- examples/openapi_server.py +369 -0
- examples/project_structure_example.py +47 -0
- examples/testing_example.py +53 -0
- mcp_proxy_adapter/__init__.py +17 -0
- mcp_proxy_adapter/adapter.py +697 -0
- mcp_proxy_adapter/models.py +47 -0
- mcp_proxy_adapter/registry.py +439 -0
- mcp_proxy_adapter/schema.py +257 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/METADATA +2 -2
- mcp_proxy_adapter-2.1.2.dist-info/RECORD +61 -0
- mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +5 -0
- scripts/code_analyzer/code_analyzer.py +328 -0
- scripts/code_analyzer/register_commands.py +446 -0
- scripts/publish.py +85 -0
- tests/conftest.py +12 -0
- tests/test_adapter.py +529 -0
- tests/test_adapter_coverage.py +274 -0
- tests/test_basic_dispatcher.py +169 -0
- tests/test_command_registry.py +328 -0
- tests/test_examples.py +32 -0
- tests/test_mcp_proxy_adapter.py +568 -0
- tests/test_mcp_proxy_adapter_basic.py +262 -0
- tests/test_part1.py +348 -0
- tests/test_part2.py +524 -0
- tests/test_schema.py +358 -0
- tests/test_simple_adapter.py +251 -0
- adapters/__init__.py +0 -16
- cli/__init__.py +0 -12
- cli/__main__.py +0 -79
- cli/command_runner.py +0 -233
- generators/__init__.py +0 -14
- generators/endpoint_generator.py +0 -172
- generators/openapi_generator.py +0 -254
- generators/rest_api_generator.py +0 -207
- mcp_proxy_adapter-2.1.0.dist-info/RECORD +0 -28
- mcp_proxy_adapter-2.1.0.dist-info/top_level.txt +0 -7
- openapi_schema/__init__.py +0 -38
- openapi_schema/command_registry.py +0 -312
- openapi_schema/rest_schema.py +0 -510
- openapi_schema/rpc_generator.py +0 -307
- openapi_schema/rpc_schema.py +0 -416
- validators/__init__.py +0 -14
- validators/base_validator.py +0 -23
- {analyzers → mcp_proxy_adapter/analyzers}/__init__.py +0 -0
- {analyzers → mcp_proxy_adapter/analyzers}/docstring_analyzer.py +0 -0
- {analyzers → mcp_proxy_adapter/analyzers}/type_analyzer.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/__init__.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/base_dispatcher.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/json_rpc_dispatcher.py +0 -0
- {validators → mcp_proxy_adapter/validators}/docstring_validator.py +0 -0
- {validators → mcp_proxy_adapter/validators}/metadata_validator.py +0 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/licenses/LICENSE +0 -0
tests/test_adapter.py
ADDED
@@ -0,0 +1,529 @@
|
|
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"]
|