mcp-proxy-adapter 2.1.1__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.1.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.1.dist-info/RECORD +0 -61
  33. mcp_proxy_adapter-2.1.1.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.1.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/WHEEL +0 -0
  50. {mcp_proxy_adapter-2.1.1.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,568 +0,0 @@
1
- """
2
- Тесты для MCPProxyAdapter с акцентом на 100% покрытие кода,
3
- включая обработку ошибок, пограничные случаи и неполное заполнение сигнатур.
4
- """
5
- import json
6
- import logging
7
- import sys
8
- import os
9
- import pytest
10
- import tempfile
11
- from unittest.mock import MagicMock, patch
12
- import types
13
-
14
- # Добавляем путь к исходникам
15
- project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
16
- if project_root not in sys.path:
17
- sys.path.insert(0, project_root)
18
-
19
- from fastapi import FastAPI
20
- from fastapi.testclient import TestClient
21
-
22
- # Импортируем напрямую из src
23
- from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
24
- from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig, MCPProxyTool
25
-
26
- # Тестовые функции-команды
27
- def success_command(value: int = 1) -> dict:
28
- """Тестовая команда, завершающаяся успешно."""
29
- return {"result": value * 2}
30
-
31
- def error_command() -> None:
32
- """Тестовая команда, генерирующая ошибку."""
33
- raise ValueError("Тестовая ошибка")
34
-
35
- def param_command(required_param: str, optional_param: int = 0) -> dict:
36
- """Тестовая команда с обязательным и необязательным параметрами."""
37
- return {"required": required_param, "optional": optional_param}
38
-
39
- def complex_param_command(array_param: list, object_param: dict, bool_param: bool = True) -> dict:
40
- """Тестовая команда со сложными типами параметров."""
41
- return {
42
- "array_length": len(array_param),
43
- "object_keys": list(object_param.keys()),
44
- "bool_value": bool_param
45
- }
46
-
47
- def type_error_command(param: int) -> dict:
48
- """Команда, которая вызовет TypeError при неправильном типе параметра."""
49
- return {"param": param + 1} # Требуется int
50
-
51
- # Мок для диспетчера команд
52
- class MockDispatcher:
53
- """Mock for command dispatcher in tests."""
54
-
55
- def __init__(self):
56
- self.commands = {
57
- "success": success_command,
58
- "error": error_command,
59
- "param": param_command,
60
- "execute": self.execute_from_params
61
- }
62
- self.commands_info = {
63
- "success": {
64
- "description": "Successful command",
65
- "params": {
66
- "value": {
67
- "type": "integer",
68
- "description": "Input value",
69
- "required": False,
70
- "default": 1
71
- }
72
- }
73
- },
74
- "error": {
75
- "description": "Command with error",
76
- "params": {}
77
- },
78
- "param": {
79
- "description": "Command with parameters",
80
- "params": {
81
- "required_param": {
82
- "type": "string",
83
- "description": "Required parameter",
84
- "required": True
85
- },
86
- "optional_param": {
87
- "type": "integer",
88
- "description": "Optional parameter",
89
- "required": False,
90
- "default": 0
91
- }
92
- }
93
- },
94
- "execute": {
95
- "description": "Universal command for executing other commands",
96
- "params": {
97
- "query": {
98
- "type": "string",
99
- "description": "Command or query to execute",
100
- "required": False
101
- }
102
- }
103
- },
104
- "complex_param": {
105
- "description": "Command with complex parameters",
106
- "params": {
107
- "array_param": {
108
- "type": "array",
109
- "description": "Array of values",
110
- "required": True
111
- },
112
- "object_param": {
113
- "type": "object",
114
- "description": "Object",
115
- "required": True
116
- },
117
- "bool_param": {
118
- "type": "boolean",
119
- "description": "Boolean value",
120
- "required": False,
121
- "default": True
122
- }
123
- }
124
- },
125
- "type_error": {
126
- "description": "Command that will raise TypeError",
127
- "params": {
128
- "param": {
129
- "type": "integer",
130
- "description": "Integer parameter",
131
- "required": True
132
- }
133
- }
134
- }
135
- }
136
-
137
- def execute_from_params(self, **params):
138
- """Executes command based on parameters."""
139
- if "query" in params and params["query"] in self.commands:
140
- command = params.pop("query")
141
- return self.execute(command, **params)
142
- return {
143
- "available_commands": self.get_valid_commands(),
144
- "received_params": params
145
- }
146
-
147
- def execute(self, command, **params):
148
- """Executes command with specified parameters."""
149
- if command not in self.commands:
150
- raise KeyError(f"Unknown command: {command}")
151
- return self.commands[command](**params)
152
-
153
- def get_valid_commands(self):
154
- """Returns list of available commands."""
155
- return list(self.commands.keys())
156
-
157
- def get_command_info(self, command):
158
- """Returns information about command."""
159
- return self.commands_info.get(command)
160
-
161
- def get_commands_info(self):
162
- """Returns information about all commands."""
163
- return self.commands_info
164
-
165
- # Мок для CommandRegistry
166
- class MockRegistry:
167
- """Мок для CommandRegistry в тестах."""
168
-
169
- def __init__(self, use_openapi_generator=False):
170
- self.dispatcher = MockDispatcher()
171
- self.generators = []
172
- self.use_openapi_generator = use_openapi_generator
173
-
174
- def get_commands_info(self):
175
- """Возвращает информацию о командах из диспетчера."""
176
- return self.dispatcher.get_commands_info()
177
-
178
- def add_generator(self, generator):
179
- """Добавляет генератор API."""
180
- self.generators.append(generator)
181
- if hasattr(generator, 'set_dispatcher'):
182
- generator.set_dispatcher(self.dispatcher)
183
-
184
- # Мок для OpenApiGenerator
185
- class MockOpenApiGenerator:
186
- """Мок для OpenApiGenerator в тестах."""
187
-
188
- def __init__(self, **kwargs):
189
- self.dispatcher = None
190
- self.kwargs = kwargs
191
-
192
- def set_dispatcher(self, dispatcher):
193
- """Устанавливает диспетчер команд."""
194
- self.dispatcher = dispatcher
195
-
196
- def generate_schema(self):
197
- """Генерирует схему OpenAPI."""
198
- return {
199
- "openapi": "3.0.0",
200
- "info": {
201
- "title": self.kwargs.get("title", "API"),
202
- "version": self.kwargs.get("version", "1.0.0"),
203
- "description": self.kwargs.get("description", "API description")
204
- },
205
- "paths": {
206
- "/test": {
207
- "get": {
208
- "summary": "Test endpoint",
209
- "responses": {
210
- "200": {
211
- "description": "Successful response"
212
- }
213
- }
214
- }
215
- }
216
- }
217
- }
218
-
219
- # Фикстуры для тестов
220
- @pytest.fixture
221
- def registry():
222
- """Возвращает мок реестра команд."""
223
- return MockRegistry()
224
-
225
- @pytest.fixture
226
- def registry_with_openapi():
227
- """Возвращает мок реестра команд с поддержкой OpenAPI."""
228
- registry = MockRegistry(use_openapi_generator=True)
229
- return registry
230
-
231
- @pytest.fixture
232
- def adapter(registry):
233
- """Возвращает настроенный адаптер для тестов."""
234
- return MCPProxyAdapter(registry)
235
-
236
- @pytest.fixture
237
- def adapter_with_openapi(registry):
238
- """Возвращает адаптер с поддержкой OpenAPI для тестов."""
239
- with patch('mcp_proxy_adapter.adapter.OpenApiGenerator', MockOpenApiGenerator):
240
- return MCPProxyAdapter(registry)
241
-
242
- @pytest.fixture
243
- def test_app(adapter):
244
- """Создает тестовое приложение FastAPI с настроенным адаптером."""
245
- app = FastAPI()
246
- adapter.register_endpoints(app)
247
- return TestClient(app)
248
-
249
- @pytest.fixture
250
- def custom_endpoint_adapter(registry):
251
- """Возвращает адаптер с кастомным эндпоинтом."""
252
- return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
253
-
254
- @pytest.fixture
255
- def custom_endpoint_app(custom_endpoint_adapter):
256
- """Создает тестовое приложение с адаптером, имеющим кастомный эндпоинт."""
257
- app = FastAPI()
258
- custom_endpoint_adapter.register_endpoints(app)
259
- return TestClient(app)
260
-
261
- @pytest.fixture
262
- def no_schema_adapter(registry):
263
- """Возвращает адаптер без включения схемы OpenAPI."""
264
- return MCPProxyAdapter(registry, include_schema=False)
265
-
266
- @pytest.fixture
267
- def no_schema_app(no_schema_adapter):
268
- """Создает тестовое приложение без эндпоинта схемы OpenAPI."""
269
- app = FastAPI()
270
- no_schema_adapter.register_endpoints(app)
271
- return TestClient(app)
272
-
273
- @pytest.fixture
274
- def no_optimize_adapter(registry):
275
- """Возвращает адаптер без оптимизации схемы."""
276
- return MCPProxyAdapter(registry, optimize_schema=False)
277
-
278
- @pytest.fixture
279
- def custom_prefix_adapter(registry):
280
- """Возвращает адаптер с пользовательским префиксом инструментов."""
281
- return MCPProxyAdapter(registry, tool_name_prefix="custom_")
282
-
283
- @pytest.fixture
284
- def custom_logger():
285
- """Создает настраиваемый логгер для тестов."""
286
- logger = logging.getLogger("test_logger")
287
- logger.setLevel(logging.DEBUG)
288
-
289
- # Очищаем обработчики, если они были добавлены в предыдущих тестах
290
- if logger.handlers:
291
- logger.handlers = []
292
-
293
- # Добавляем обработчик, который будет записывать сообщения в список
294
- log_records = []
295
-
296
- class ListHandler(logging.Handler):
297
- def emit(self, record):
298
- log_records.append(self.format(record))
299
-
300
- handler = ListHandler()
301
- formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
302
- handler.setFormatter(formatter)
303
- logger.addHandler(handler)
304
-
305
- return logger, log_records
306
-
307
- @pytest.fixture
308
- def help_project_command():
309
- """Фикстура: help реализован в проекте (как команда)."""
310
- def help_handler(**params):
311
- # Симулируем поведение help-команды проекта
312
- if not params or not params.get('command'):
313
- return {"source": "project", "commands": ["success", "error"]}
314
- if params["command"] == "success":
315
- return {"source": "project", "command": "success", "info": {"description": "Success command"}}
316
- return {"error": f"Command '{params['command']}' not found", "available_commands": ["success", "error"]}
317
- return help_handler
318
-
319
- @pytest.fixture
320
- def help_adapter_command():
321
- """Фикстура: help-адаптер (стандартный)."""
322
- def help_handler(**params):
323
- # Симулируем поведение help-адаптера
324
- if not params or not params.get('command'):
325
- return {"source": "adapter", "commands": ["success", "error"]}
326
- if params["command"] == "success":
327
- return {"source": "adapter", "command": "success", "info": {"description": "Success command (adapter)"}}
328
- return {"source": "adapter", "error": f"Command '{params['command']}' not found (adapter)", "available_commands": ["success", "error"]}
329
- return help_handler
330
-
331
- class HelpDispatcher(MockDispatcher):
332
- def __init__(self, project_help=None, adapter_help=None):
333
- super().__init__()
334
- self.project_help = project_help
335
- self.adapter_help = adapter_help
336
- # Добавляем help в список команд, если задан project_help
337
- if project_help:
338
- self.commands["help"] = self.help_command
339
- self.commands_info["help"] = {"description": "Project help command", "params": {"command": {"type": "string", "required": False}}}
340
- def help_command(self, **params):
341
- return self.project_help(**params)
342
- def adapter_help_command(self, **params):
343
- return self.adapter_help(**params)
344
-
345
- class HelpRegistry(MockRegistry):
346
- def __init__(self, project_help=None, adapter_help=None):
347
- self.dispatcher = HelpDispatcher(project_help, adapter_help)
348
- self.generators = []
349
- self.use_openapi_generator = False
350
- def get_commands_info(self):
351
- return self.dispatcher.get_commands_info()
352
- def add_generator(self, generator):
353
- self.generators.append(generator)
354
- if hasattr(generator, 'set_dispatcher'):
355
- generator.set_dispatcher(self.dispatcher)
356
-
357
- # Тесты для основных сценариев
358
- def test_successful_command_execution(test_app):
359
- """Тест успешного выполнения команды."""
360
- response = test_app.post("/cmd", json={
361
- "jsonrpc": "2.0",
362
- "method": "success",
363
- "params": {"value": 5},
364
- "id": 1
365
- })
366
- assert response.status_code == 200
367
- data = response.json()
368
- assert data["result"] == {"result": 10}
369
-
370
- # === HELP WRAPPER TESTS ===
371
- def test_help_project_no_param(monkeypatch, help_project_command, help_adapter_command):
372
- """help реализован в проекте, вызов без параметров: должен вызываться help проекта."""
373
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
374
- adapter = MCPProxyAdapter(registry)
375
- # monkeypatch: simulate help-wrapper logic
376
- result = registry.dispatcher.help_command()
377
- assert result["source"] == "project"
378
- assert "commands" in result
379
-
380
- def test_help_project_with_param(monkeypatch, help_project_command, help_adapter_command):
381
- """help реализован в проекте, вызов с существующим параметром: должен вызываться help проекта."""
382
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
383
- adapter = MCPProxyAdapter(registry)
384
- result = registry.dispatcher.help_command(command="success")
385
- assert result["source"] == "project"
386
- assert result["command"] == "success"
387
- assert "info" in result
388
-
389
- def test_help_project_with_wrong_param(monkeypatch, help_project_command, help_adapter_command):
390
- """help реализован в проекте, вызов с несуществующим параметром: help проекта возвращает ошибку, вызывается help-адаптер."""
391
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
392
- adapter = MCPProxyAdapter(registry)
393
- # Симулируем: если help проекта вернул ошибку, вызываем help-адаптер
394
- result = registry.dispatcher.help_command(command="unknown")
395
- if "error" in result:
396
- adapter_result = registry.dispatcher.adapter_help_command(command="unknown")
397
- assert adapter_result["source"] == "adapter"
398
- assert "error" in adapter_result
399
- else:
400
- assert False, "Project help should return error for unknown command"
401
-
402
- def test_help_adapter_no_project(monkeypatch, help_adapter_command):
403
- """help не реализован в проекте, вызов без параметров: должен вызываться help-адаптер."""
404
- registry = HelpRegistry(project_help=None, adapter_help=help_adapter_command)
405
- adapter = MCPProxyAdapter(registry)
406
- # Симулируем: help-адаптер вызывается напрямую
407
- result = registry.dispatcher.adapter_help_command()
408
- assert result["source"] == "adapter"
409
- assert "commands" in result
410
-
411
- def test_help_adapter_with_param_no_project(monkeypatch, help_adapter_command):
412
- """help не реализован в проекте, вызов с параметром: должен вызываться help-адаптер."""
413
- registry = HelpRegistry(project_help=None, adapter_help=help_adapter_command)
414
- adapter = MCPProxyAdapter(registry)
415
- result = registry.dispatcher.adapter_help_command(command="success")
416
- assert result["source"] == "adapter"
417
- assert result["command"] == "success"
418
- assert "info" in result
419
-
420
- # === COVERAGE BOOST TESTS ===
421
- def test_dispatcher_keyerror():
422
- """Test KeyError for unknown command in MockDispatcher."""
423
- dispatcher = MockDispatcher()
424
- with pytest.raises(KeyError):
425
- dispatcher.execute("unknown")
426
-
427
- def test_dispatcher_type_error():
428
- """Test TypeError for wrong param type in type_error_command."""
429
- dispatcher = MockDispatcher()
430
- # Явно добавляем type_error команду, если вдруг отсутствует
431
- dispatcher.commands["type_error"] = type_error_command
432
- with pytest.raises(TypeError):
433
- dispatcher.execute("type_error", param="not_an_int")
434
-
435
- def test_execute_from_params_edge():
436
- """Test execute_from_params with unknown query and empty params."""
437
- dispatcher = MockDispatcher()
438
- result = dispatcher.execute_from_params(query="unknown")
439
- assert "available_commands" in result
440
- assert "received_params" in result
441
- result2 = dispatcher.execute_from_params()
442
- assert isinstance(result2, dict)
443
-
444
- def test_registry_fixtures(registry, registry_with_openapi):
445
- """Test registry and registry_with_openapi fixtures."""
446
- assert isinstance(registry, MockRegistry)
447
- assert isinstance(registry_with_openapi, MockRegistry)
448
- assert registry_with_openapi.use_openapi_generator
449
-
450
- def test_adapter_fixtures(adapter, adapter_with_openapi, no_schema_adapter, no_optimize_adapter, custom_prefix_adapter):
451
- """Test all adapter fixtures."""
452
- assert isinstance(adapter, MCPProxyAdapter)
453
- assert isinstance(adapter_with_openapi, MCPProxyAdapter)
454
- assert isinstance(no_schema_adapter, MCPProxyAdapter)
455
- assert isinstance(no_optimize_adapter, MCPProxyAdapter)
456
- assert isinstance(custom_prefix_adapter, MCPProxyAdapter)
457
-
458
- def test_custom_logger_fixture(custom_logger):
459
- """Test custom_logger fixture."""
460
- logger, log_records = custom_logger
461
- logger.info("test message")
462
- assert any("test message" in rec for rec in log_records)
463
-
464
- def test_custom_endpoint_app(custom_endpoint_app):
465
- """Test custom endpoint app fixture."""
466
- response = custom_endpoint_app.post("/api/execute", json={"jsonrpc": "2.0", "method": "success", "params": {"value": 2}, "id": 1})
467
- assert response.status_code == 200
468
- assert response.json()["result"] == {"result": 4}
469
-
470
- def test_no_schema_app(no_schema_app):
471
- """Test no_schema_app fixture."""
472
- response = no_schema_app.post("/cmd", json={"jsonrpc": "2.0", "method": "success", "params": {"value": 3}, "id": 1})
473
- assert response.status_code == 200
474
- assert response.json()["result"] == {"result": 6}
475
-
476
- def test_help_project_empty_param(help_project_command):
477
- """Test help_project_command with empty param dict."""
478
- result = help_project_command()
479
- assert result["source"] == "project"
480
-
481
- def test_help_adapter_empty_param(help_adapter_command):
482
- """Test help_adapter_command with empty param dict."""
483
- result = help_adapter_command()
484
- assert result["source"] == "adapter"
485
-
486
- def test_help_dispatcher_and_registry():
487
- """Test HelpDispatcher and HelpRegistry edge cases."""
488
- dispatcher = HelpDispatcher()
489
- registry = HelpRegistry()
490
- # help not registered
491
- assert "help" not in dispatcher.commands
492
- # add generator
493
- class DummyGen: pass
494
- registry.add_generator(DummyGen())
495
- assert registry.generators
496
-
497
- # === DETAILED HELP-COMMAND TESTS ===
498
- def test_project_help_priority(monkeypatch, help_project_command, help_adapter_command):
499
- """If project help exists, it must always be called first (no param)."""
500
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
501
- adapter = MCPProxyAdapter(registry)
502
- # Симулируем вызов help без параметров
503
- result = registry.dispatcher.help_command()
504
- assert result["source"] == "project"
505
- assert "commands" in result
506
- # Адаптер не должен вызываться
507
- assert not ("adapter" in result.get("source", ""))
508
-
509
- def test_project_help_with_param_success(monkeypatch, help_project_command, help_adapter_command):
510
- """Project help with valid param: must return project info, not adapter."""
511
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
512
- adapter = MCPProxyAdapter(registry)
513
- result = registry.dispatcher.help_command(command="success")
514
- assert result["source"] == "project"
515
- assert result["command"] == "success"
516
- assert "info" in result
517
- # Адаптер не должен вызываться
518
- assert not ("adapter" in result.get("source", ""))
519
-
520
- def test_project_help_with_param_not_found(monkeypatch, help_project_command, help_adapter_command):
521
- """Project help with unknown param: must call adapter help after project help error."""
522
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
523
- adapter = MCPProxyAdapter(registry)
524
- result = registry.dispatcher.help_command(command="unknown")
525
- # Проектный help возвращает ошибку
526
- assert "error" in result
527
- # После ошибки вызывается help-адаптер
528
- adapter_result = registry.dispatcher.adapter_help_command(command="unknown")
529
- assert adapter_result["source"] == "adapter"
530
- assert "error" in adapter_result
531
-
532
- def test_project_help_with_param_exception(monkeypatch, help_adapter_command):
533
- """Project help raises exception: adapter help must be called as fallback."""
534
- def broken_help(**params):
535
- raise RuntimeError("project help failed")
536
- registry = HelpRegistry(project_help=broken_help, adapter_help=help_adapter_command)
537
- adapter = MCPProxyAdapter(registry)
538
- # Симулируем: если проектный help падает, вызываем help-адаптер
539
- try:
540
- registry.dispatcher.help_command(command="any")
541
- except Exception as e:
542
- adapter_result = registry.dispatcher.adapter_help_command(command="any")
543
- assert adapter_result["source"] == "adapter"
544
- assert "commands" in adapter_result or "error" in adapter_result
545
-
546
- def test_project_help_with_param_none(monkeypatch, help_project_command, help_adapter_command):
547
- """Project help with param=None: must return project help info."""
548
- registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
549
- adapter = MCPProxyAdapter(registry)
550
- result = registry.dispatcher.help_command(command=None)
551
- assert result["source"] == "project"
552
- assert "commands" in result
553
-
554
- def test_project_help_returns_unexpected_type(monkeypatch, help_adapter_command):
555
- """Project help returns unexpected type: adapter help must be called as fallback."""
556
- def weird_help(**params):
557
- return "not a dict"
558
- registry = HelpRegistry(project_help=weird_help, adapter_help=help_adapter_command)
559
- adapter = MCPProxyAdapter(registry)
560
- result = registry.dispatcher.help_command(command="any")
561
- # Если результат не dict, вызываем help-адаптер
562
- if not isinstance(result, dict):
563
- adapter_result = registry.dispatcher.adapter_help_command(command="any")
564
- assert adapter_result["source"] == "adapter"
565
- assert "commands" in adapter_result or "error" in adapter_result
566
-
567
- if __name__ == "__main__":
568
- pytest.main(["-v", __file__])