mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.0.0__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 (118) hide show
  1. mcp_proxy_adapter/api/app.py +65 -27
  2. mcp_proxy_adapter/api/handlers.py +1 -1
  3. mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
  4. mcp_proxy_adapter/api/tool_integration.py +5 -2
  5. mcp_proxy_adapter/api/tools.py +3 -3
  6. mcp_proxy_adapter/commands/base.py +19 -1
  7. mcp_proxy_adapter/commands/command_registry.py +243 -6
  8. mcp_proxy_adapter/commands/hooks.py +260 -0
  9. mcp_proxy_adapter/commands/reload_command.py +211 -0
  10. mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
  11. mcp_proxy_adapter/commands/settings_command.py +189 -0
  12. mcp_proxy_adapter/config.py +16 -1
  13. mcp_proxy_adapter/core/__init__.py +44 -0
  14. mcp_proxy_adapter/core/logging.py +87 -34
  15. mcp_proxy_adapter/core/settings.py +376 -0
  16. mcp_proxy_adapter/core/utils.py +2 -2
  17. mcp_proxy_adapter/custom_openapi.py +81 -2
  18. mcp_proxy_adapter/examples/README.md +124 -0
  19. mcp_proxy_adapter/examples/__init__.py +7 -0
  20. mcp_proxy_adapter/examples/basic_server/README.md +60 -0
  21. mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
  22. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
  23. mcp_proxy_adapter/examples/basic_server/config.json +35 -0
  24. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
  25. mcp_proxy_adapter/examples/basic_server/server.py +98 -0
  26. mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
  27. mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
  28. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
  29. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
  30. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
  31. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
  32. mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
  33. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
  34. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
  35. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
  36. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
  37. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
  38. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
  39. mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
  40. mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
  41. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
  42. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
  43. mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
  44. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
  45. mcp_proxy_adapter/examples/deployment/README.md +49 -0
  46. mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
  47. mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
  48. {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
  49. mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
  50. mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
  51. mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
  52. mcp_proxy_adapter/examples/deployment/run.sh +43 -0
  53. mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
  54. mcp_proxy_adapter/openapi.py +3 -2
  55. mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
  56. mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
  57. mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
  58. mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
  59. mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
  60. mcp_proxy_adapter/version.py +1 -1
  61. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/METADATA +1 -1
  62. mcp_proxy_adapter-4.0.0.dist-info/RECORD +110 -0
  63. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/WHEEL +1 -1
  64. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/top_level.txt +0 -1
  65. examples/__init__.py +0 -19
  66. examples/anti_patterns/README.md +0 -51
  67. examples/anti_patterns/__init__.py +0 -9
  68. examples/anti_patterns/bad_design/README.md +0 -72
  69. examples/anti_patterns/bad_design/global_state.py +0 -170
  70. examples/anti_patterns/bad_design/monolithic_command.py +0 -272
  71. examples/basic_example/README.md +0 -245
  72. examples/basic_example/__init__.py +0 -8
  73. examples/basic_example/commands/__init__.py +0 -5
  74. examples/basic_example/commands/echo_command.py +0 -95
  75. examples/basic_example/commands/math_command.py +0 -151
  76. examples/basic_example/commands/time_command.py +0 -152
  77. examples/basic_example/docs/EN/README.md +0 -177
  78. examples/basic_example/docs/RU/README.md +0 -177
  79. examples/basic_example/server.py +0 -151
  80. examples/basic_example/tests/conftest.py +0 -243
  81. examples/check_vstl_schema.py +0 -106
  82. examples/commands/echo_command.py +0 -52
  83. examples/commands/echo_command_di.py +0 -152
  84. examples/commands/echo_result.py +0 -65
  85. examples/commands/get_date_command.py +0 -98
  86. examples/commands/new_uuid4_command.py +0 -91
  87. examples/complete_example/Dockerfile +0 -24
  88. examples/complete_example/README.md +0 -92
  89. examples/complete_example/__init__.py +0 -8
  90. examples/complete_example/commands/__init__.py +0 -5
  91. examples/complete_example/commands/system_command.py +0 -328
  92. examples/complete_example/config.json +0 -41
  93. examples/complete_example/configs/config.dev.yaml +0 -40
  94. examples/complete_example/configs/config.docker.yaml +0 -40
  95. examples/complete_example/docker-compose.yml +0 -35
  96. examples/complete_example/requirements.txt +0 -20
  97. examples/complete_example/server.py +0 -113
  98. examples/di_example/.pytest_cache/README.md +0 -8
  99. examples/di_example/server.py +0 -249
  100. examples/fix_vstl_help.py +0 -123
  101. examples/minimal_example/README.md +0 -65
  102. examples/minimal_example/__init__.py +0 -8
  103. examples/minimal_example/config.json +0 -14
  104. examples/minimal_example/main.py +0 -136
  105. examples/minimal_example/simple_server.py +0 -163
  106. examples/minimal_example/tests/conftest.py +0 -171
  107. examples/minimal_example/tests/test_hello_command.py +0 -111
  108. examples/minimal_example/tests/test_integration.py +0 -181
  109. examples/patch_vstl_service.py +0 -105
  110. examples/patch_vstl_service_mcp.py +0 -108
  111. examples/server.py +0 -69
  112. examples/simple_server.py +0 -128
  113. examples/test_package_3.1.4.py +0 -177
  114. examples/test_server.py +0 -134
  115. examples/tool_description_example.py +0 -82
  116. mcp_proxy_adapter/py.typed +0 -0
  117. mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
  118. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,531 @@
1
+ """
2
+ Tests for tool_integration module.
3
+
4
+ This module contains comprehensive tests for the ToolIntegration class
5
+ and related functions to ensure 90%+ code coverage.
6
+ """
7
+
8
+ import pytest
9
+ import json
10
+ from unittest.mock import Mock, patch, MagicMock
11
+ from typing import Dict, Any
12
+
13
+ from mcp_proxy_adapter.api.tool_integration import ToolIntegration, generate_tool_help
14
+ from mcp_proxy_adapter.commands.command_registry import CommandRegistry
15
+
16
+
17
+ class TestToolIntegration:
18
+ """Test cases for ToolIntegration class."""
19
+
20
+ @pytest.fixture
21
+ def mock_registry(self):
22
+ """Create a mock command registry for testing."""
23
+ registry = Mock(spec=CommandRegistry)
24
+
25
+ # Mock metadata for commands
26
+ registry.get_all_metadata.return_value = {
27
+ "test_command": {
28
+ "summary": "Test command description",
29
+ "params": {
30
+ "param1": {
31
+ "type": "строка",
32
+ "description": "Test parameter",
33
+ "required": True
34
+ },
35
+ "param2": {
36
+ "type": "целое число",
37
+ "description": "Another parameter",
38
+ "required": False
39
+ }
40
+ },
41
+ "examples": [
42
+ {
43
+ "command": "test_command",
44
+ "params": {"param1": "value1"}
45
+ }
46
+ ]
47
+ },
48
+ "another_command": {
49
+ "summary": "Another test command",
50
+ "params": {
51
+ "param3": {
52
+ "type": "логическое значение",
53
+ "description": "Boolean parameter",
54
+ "required": True
55
+ }
56
+ },
57
+ "examples": [
58
+ {
59
+ "command": "another_command",
60
+ "params": {"param3": True}
61
+ }
62
+ ]
63
+ }
64
+ }
65
+
66
+ return registry
67
+
68
+ @pytest.fixture
69
+ def mock_api_tool_description(self):
70
+ """Mock APIToolDescription class."""
71
+ with patch('mcp_proxy_adapter.api.tool_integration.APIToolDescription') as mock:
72
+ mock.generate_tool_description.return_value = {
73
+ "description": "Test tool description",
74
+ "supported_commands": {
75
+ "test_command": {
76
+ "params": {
77
+ "param1": {
78
+ "type": "строка",
79
+ "description": "Test parameter"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ mock.generate_tool_description_text.return_value = "# Test Tool\n\nTest description"
86
+ yield mock
87
+
88
+ def test_generate_tool_schema_success(self, mock_registry, mock_api_tool_description):
89
+ """Test successful tool schema generation."""
90
+ tool_name = "test_tool"
91
+ description = "Custom tool description"
92
+
93
+ schema = ToolIntegration.generate_tool_schema(tool_name, mock_registry, description)
94
+
95
+ # Verify schema structure
96
+ assert schema["name"] == tool_name
97
+ assert schema["description"] == description
98
+ assert "parameters" in schema
99
+ assert "properties" in schema["parameters"]
100
+ assert "command" in schema["parameters"]["properties"]
101
+ assert "params" in schema["parameters"]["properties"]
102
+
103
+ # Verify command enum
104
+ command_enum = schema["parameters"]["properties"]["command"]["enum"]
105
+ assert "test_command" in command_enum
106
+
107
+ # Verify parameter types
108
+ param_types = schema["parameters"]["properties"]["params"]["properties"]
109
+ assert "param1" in param_types
110
+ assert param_types["param1"]["type"] == "string"
111
+
112
+ def test_generate_tool_schema_without_description(self, mock_registry, mock_api_tool_description):
113
+ """Test tool schema generation without custom description."""
114
+ tool_name = "test_tool"
115
+
116
+ schema = ToolIntegration.generate_tool_schema(tool_name, mock_registry)
117
+
118
+ assert schema["description"] == "Test tool description"
119
+
120
+ def test_generate_tool_schema_parameter_type_conversion(self, mock_registry):
121
+ """Test parameter type conversion from Russian to JSON Schema types."""
122
+ with patch('mcp_proxy_adapter.api.tool_integration.APIToolDescription') as mock:
123
+ mock.generate_tool_description.return_value = {
124
+ "description": "Test tool",
125
+ "supported_commands": {
126
+ "test_command": {
127
+ "params": {
128
+ "string_param": {"type": "строка", "description": "String"},
129
+ "int_param": {"type": "целое число", "description": "Integer"},
130
+ "float_param": {"type": "число", "description": "Float"},
131
+ "bool_param": {"type": "логическое значение", "description": "Boolean"},
132
+ "array_param": {"type": "список", "description": "Array"},
133
+ "object_param": {"type": "объект", "description": "Object"},
134
+ "unknown_param": {"type": "неизвестный", "description": "Unknown"}
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ schema = ToolIntegration.generate_tool_schema("test_tool", mock_registry)
141
+ param_types = schema["parameters"]["properties"]["params"]["properties"]
142
+
143
+ assert param_types["string_param"]["type"] == "string"
144
+ assert param_types["int_param"]["type"] == "integer"
145
+ assert param_types["float_param"]["type"] == "number"
146
+ assert param_types["bool_param"]["type"] == "boolean"
147
+ assert param_types["array_param"]["type"] == "array"
148
+ assert param_types["object_param"]["type"] == "object"
149
+ assert param_types["unknown_param"]["type"] == "string" # Default fallback
150
+
151
+ def test_generate_tool_documentation_markdown(self, mock_registry, mock_api_tool_description):
152
+ """Test markdown documentation generation."""
153
+ tool_name = "test_tool"
154
+
155
+ doc = ToolIntegration.generate_tool_documentation(tool_name, mock_registry, "markdown")
156
+
157
+ assert doc == "# Test Tool\n\nTest description"
158
+ mock_api_tool_description.generate_tool_description_text.assert_called_once_with(tool_name, mock_registry)
159
+
160
+ def test_generate_tool_documentation_html(self, mock_registry, mock_api_tool_description):
161
+ """Test HTML documentation generation."""
162
+ tool_name = "test_tool"
163
+
164
+ doc = ToolIntegration.generate_tool_documentation(tool_name, mock_registry, "html")
165
+
166
+ assert "<html>" in doc
167
+ assert "<body>" in doc
168
+ assert "Test Tool" in doc
169
+
170
+ def test_generate_tool_documentation_default_format(self, mock_registry, mock_api_tool_description):
171
+ """Test documentation generation with default format."""
172
+ tool_name = "test_tool"
173
+
174
+ doc = ToolIntegration.generate_tool_documentation(tool_name, mock_registry, "unknown")
175
+
176
+ assert doc == "# Test Tool\n\nTest description"
177
+
178
+ def test_register_external_tools_success(self, mock_registry, mock_api_tool_description):
179
+ """Test successful external tool registration."""
180
+ tool_names = ["tool1", "tool2"]
181
+
182
+ results = ToolIntegration.register_external_tools(mock_registry, tool_names)
183
+
184
+ assert len(results) == 2
185
+ assert results["tool1"]["status"] == "success"
186
+ assert results["tool2"]["status"] == "success"
187
+ assert "schema" in results["tool1"]
188
+ assert "schema" in results["tool2"]
189
+
190
+ def test_register_external_tools_with_error(self, mock_registry):
191
+ """Test external tool registration with error."""
192
+ with patch('mcp_proxy_adapter.api.tool_integration.APIToolDescription') as mock:
193
+ mock.generate_tool_description.side_effect = Exception("Test error")
194
+
195
+ tool_names = ["error_tool"]
196
+ results = ToolIntegration.register_external_tools(mock_registry, tool_names)
197
+
198
+ assert results["error_tool"]["status"] == "error"
199
+ assert "Test error" in results["error_tool"]["error"]
200
+
201
+ def test_register_external_tools_empty_list(self, mock_registry):
202
+ """Test external tool registration with empty list."""
203
+ results = ToolIntegration.register_external_tools(mock_registry, [])
204
+
205
+ assert results == {}
206
+
207
+ def test_extract_parameter_types(self):
208
+ """Test parameter type extraction."""
209
+ commands = {
210
+ "cmd1": {
211
+ "params": {
212
+ "param1": {"type": "строка", "description": "String param"},
213
+ "param2": {"type": "целое число", "description": "Integer param"}
214
+ }
215
+ },
216
+ "cmd2": {
217
+ "params": {
218
+ "param3": {"type": "логическое значение", "description": "Boolean param"}
219
+ }
220
+ }
221
+ }
222
+
223
+ parameter_types = ToolIntegration._extract_parameter_types(commands)
224
+
225
+ assert parameter_types["param1"]["type"] == "string"
226
+ assert parameter_types["param2"]["type"] == "integer"
227
+ assert parameter_types["param3"]["type"] == "boolean"
228
+ assert parameter_types["param1"]["description"] == "String param"
229
+
230
+ def test_extract_parameter_types_empty_commands(self):
231
+ """Test parameter type extraction with empty commands."""
232
+ parameter_types = ToolIntegration._extract_parameter_types({})
233
+
234
+ assert parameter_types == {}
235
+
236
+ def test_extract_parameter_types_commands_without_params(self):
237
+ """Test parameter type extraction for commands without parameters."""
238
+ commands = {
239
+ "cmd1": {"params": {}},
240
+ "cmd2": {"params": None}
241
+ }
242
+
243
+ parameter_types = ToolIntegration._extract_parameter_types(commands)
244
+
245
+ assert parameter_types == {}
246
+
247
+ def test_extract_parameter_types_missing_type(self):
248
+ """Test parameter type extraction with missing type information."""
249
+ commands = {
250
+ "cmd1": {
251
+ "params": {
252
+ "param1": {"description": "Param without type"}
253
+ }
254
+ }
255
+ }
256
+
257
+ parameter_types = ToolIntegration._extract_parameter_types(commands)
258
+
259
+ assert parameter_types["param1"]["type"] == "string" # Default fallback
260
+
261
+
262
+ class TestGenerateToolHelp:
263
+ """Test cases for generate_tool_help function."""
264
+
265
+ @pytest.fixture
266
+ def mock_registry(self):
267
+ """Create a mock command registry for testing."""
268
+ registry = Mock(spec=CommandRegistry)
269
+
270
+ registry.get_all_metadata.return_value = {
271
+ "help": {
272
+ "summary": "Show help information",
273
+ "params": {
274
+ "command": {
275
+ "type": "строка",
276
+ "description": "Command name",
277
+ "required": False
278
+ }
279
+ },
280
+ "examples": [
281
+ {
282
+ "command": "help",
283
+ "params": {"command": "test"}
284
+ }
285
+ ]
286
+ },
287
+ "config": {
288
+ "summary": "Get configuration",
289
+ "params": {
290
+ "section": {
291
+ "type": "строка",
292
+ "description": "Configuration section",
293
+ "required": True
294
+ }
295
+ },
296
+ "examples": [
297
+ {
298
+ "command": "config",
299
+ "params": {"section": "database"}
300
+ }
301
+ ]
302
+ }
303
+ }
304
+
305
+ return registry
306
+
307
+ def test_generate_tool_help_success(self, mock_registry):
308
+ """Test successful tool help generation."""
309
+ tool_name = "test_tool"
310
+
311
+ help_text = generate_tool_help(tool_name, mock_registry)
312
+
313
+ # Verify basic structure
314
+ assert f"# Инструмент {tool_name}" in help_text
315
+ assert "Позволяет выполнять команды через JSON-RPC протокол" in help_text
316
+ assert "## Доступные команды:" in help_text
317
+
318
+ # Verify command information
319
+ assert "### help" in help_text
320
+ assert "Show help information" in help_text
321
+ assert "### config" in help_text
322
+ assert "Get configuration" in help_text
323
+
324
+ # Verify parameter information
325
+ assert "Параметры:" in help_text
326
+ assert "command: опциональный" in help_text
327
+ assert "section: обязательный" in help_text
328
+
329
+ # Verify JSON examples
330
+ assert "```json" in help_text
331
+ assert '"command": "help"' in help_text
332
+ assert '"command": "test"' in help_text
333
+
334
+ def test_generate_tool_help_without_params(self, mock_registry):
335
+ """Test tool help generation for commands without parameters."""
336
+ mock_registry.get_all_metadata.return_value = {
337
+ "simple_command": {
338
+ "summary": "Simple command without params",
339
+ "params": {},
340
+ "examples": [
341
+ {
342
+ "command": "simple_command",
343
+ "params": {}
344
+ }
345
+ ]
346
+ }
347
+ }
348
+
349
+ help_text = generate_tool_help("test_tool", mock_registry)
350
+
351
+ assert "### simple_command" in help_text
352
+ assert "Simple command without params" in help_text
353
+ # Should not contain "Параметры:" section for commands without params
354
+
355
+ def test_generate_tool_help_without_examples(self, mock_registry):
356
+ """Test tool help generation for commands without examples."""
357
+ mock_registry.get_all_metadata.return_value = {
358
+ "no_example_command": {
359
+ "summary": "Command without examples",
360
+ "params": {
361
+ "param1": {
362
+ "type": "строка",
363
+ "description": "Test parameter",
364
+ "required": True
365
+ }
366
+ },
367
+ "examples": []
368
+ }
369
+ }
370
+
371
+ help_text = generate_tool_help("test_tool", mock_registry)
372
+
373
+ assert "### no_example_command" in help_text
374
+ assert "Command without examples" in help_text
375
+ # Should not contain JSON example section
376
+
377
+ def test_generate_tool_help_empty_registry(self, mock_registry):
378
+ """Test tool help generation with empty command registry."""
379
+ mock_registry.get_all_metadata.return_value = {}
380
+
381
+ help_text = generate_tool_help("test_tool", mock_registry)
382
+
383
+ assert "## Доступные команды:" in help_text
384
+ # Should not contain any command sections
385
+
386
+ def test_generate_tool_help_with_none_params(self, mock_registry):
387
+ """Test tool help generation with None params."""
388
+ mock_registry.get_all_metadata.return_value = {
389
+ "command": {
390
+ "summary": "Test command",
391
+ "params": None,
392
+ "examples": []
393
+ }
394
+ }
395
+
396
+ help_text = generate_tool_help("test_tool", mock_registry)
397
+
398
+ assert "### command" in help_text
399
+ # Should handle None params gracefully
400
+
401
+ def test_generate_tool_help_with_missing_examples(self, mock_registry):
402
+ """Test tool help generation with missing examples key."""
403
+ mock_registry.get_all_metadata.return_value = {
404
+ "command": {
405
+ "summary": "Test command",
406
+ "params": {}
407
+ }
408
+ }
409
+
410
+ help_text = generate_tool_help("test_tool", mock_registry)
411
+
412
+ assert "### command" in help_text
413
+ # Should handle missing examples key gracefully
414
+
415
+
416
+ class TestToolIntegrationEdgeCases:
417
+ """Test edge cases and error conditions."""
418
+
419
+ def test_generate_tool_schema_with_none_registry(self):
420
+ """Test tool schema generation with None registry."""
421
+ with pytest.raises(AttributeError):
422
+ ToolIntegration.generate_tool_schema("test_tool", None)
423
+
424
+ def test_generate_tool_documentation_with_none_registry(self):
425
+ """Test documentation generation with None registry."""
426
+ with pytest.raises(AttributeError):
427
+ ToolIntegration.generate_tool_documentation("test_tool", None)
428
+
429
+ def test_register_external_tools_with_none_registry(self):
430
+ """Test external tool registration with None registry."""
431
+ # This should handle None gracefully and return error status
432
+ results = ToolIntegration.register_external_tools(None, ["tool1"])
433
+ assert "tool1" in results
434
+ assert results["tool1"]["status"] == "error"
435
+ assert "get_all_metadata" in results["tool1"]["error"]
436
+
437
+ def test_generate_tool_help_with_none_registry(self):
438
+ """Test tool help generation with None registry."""
439
+ with pytest.raises(AttributeError):
440
+ generate_tool_help("test_tool", None)
441
+
442
+ @patch('mcp_proxy_adapter.api.tool_integration.logger')
443
+ def test_register_external_tools_logging(self, mock_logger):
444
+ """Test that logging is called during tool registration."""
445
+ mock_registry = Mock(spec=CommandRegistry)
446
+ with patch('mcp_proxy_adapter.api.tool_integration.APIToolDescription') as mock:
447
+ mock.generate_tool_description.return_value = {
448
+ "description": "Test tool",
449
+ "supported_commands": {}
450
+ }
451
+
452
+ ToolIntegration.register_external_tools(mock_registry, ["test_tool"])
453
+
454
+ # Verify info log for successful registration
455
+ mock_logger.info.assert_called_with("Successfully registered tool: test_tool")
456
+
457
+ @patch('mcp_proxy_adapter.api.tool_integration.logger')
458
+ def test_register_external_tools_error_logging(self, mock_logger):
459
+ """Test that error logging is called during failed tool registration."""
460
+ mock_registry = Mock(spec=CommandRegistry)
461
+ with patch('mcp_proxy_adapter.api.tool_integration.APIToolDescription') as mock:
462
+ mock.generate_tool_description.side_effect = Exception("Test error")
463
+
464
+ ToolIntegration.register_external_tools(mock_registry, ["error_tool"])
465
+
466
+ # Verify debug log for error
467
+ mock_logger.debug.assert_called_with("Error registering tool error_tool: Test error")
468
+
469
+
470
+ class TestToolIntegrationIntegration:
471
+ """Integration tests for ToolIntegration class."""
472
+
473
+ @pytest.fixture
474
+ def real_registry(self):
475
+ """Create a real command registry for integration testing."""
476
+ from mcp_proxy_adapter.commands.command_registry import CommandRegistry
477
+ from mcp_proxy_adapter.commands.base import Command
478
+
479
+ registry = CommandRegistry()
480
+
481
+ # Create a proper mock that inherits from Command
482
+ class MockCommand(Command):
483
+ name = "integration_test"
484
+
485
+ @classmethod
486
+ def get_metadata(cls):
487
+ return {
488
+ "summary": "Integration test command",
489
+ "description": "Integration test command description",
490
+ "params": {
491
+ "test_param": {
492
+ "type": "строка",
493
+ "description": "Test parameter",
494
+ "required": True
495
+ }
496
+ },
497
+ "examples": [
498
+ {
499
+ "command": "integration_test",
500
+ "params": {"test_param": "value"}
501
+ }
502
+ ]
503
+ }
504
+
505
+ async def execute(self, **kwargs):
506
+ return {"result": "test"}
507
+
508
+ registry.register(MockCommand())
509
+
510
+ return registry
511
+
512
+ def test_integration_generate_tool_schema(self, real_registry):
513
+ """Integration test for tool schema generation with real registry."""
514
+ schema = ToolIntegration.generate_tool_schema("integration_tool", real_registry)
515
+
516
+ assert schema["name"] == "integration_tool"
517
+ assert "parameters" in schema
518
+ assert "properties" in schema["parameters"]
519
+
520
+ def test_integration_generate_tool_documentation(self, real_registry):
521
+ """Integration test for tool documentation generation with real registry."""
522
+ doc = ToolIntegration.generate_tool_documentation("integration_tool", real_registry)
523
+
524
+ assert "integration_tool" in doc.lower()
525
+
526
+ def test_integration_generate_tool_help(self, real_registry):
527
+ """Integration test for tool help generation with real registry."""
528
+ help_text = generate_tool_help("integration_tool", real_registry)
529
+
530
+ assert "Инструмент integration_tool" in help_text
531
+ assert "integration_test" in help_text