mcp-proxy-adapter 2.1.7__tar.gz → 2.1.9__tar.gz

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.7/mcp_proxy_adapter.egg-info → mcp_proxy_adapter-2.1.9}/PKG-INFO +1 -1
  2. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/adapter.py +13 -3
  3. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +11 -17
  4. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/openapi_server.py +1 -1
  5. mcp_proxy_adapter-2.1.9/mcp_proxy_adapter/testing_utils.py +112 -0
  6. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9/mcp_proxy_adapter.egg-info}/PKG-INFO +1 -1
  7. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter.egg-info/SOURCES.txt +1 -0
  8. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/pyproject.toml +1 -1
  9. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/setup.py +2 -2
  10. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_adapter.py +102 -6
  11. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/LICENSE +0 -0
  12. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/MANIFEST.in +0 -0
  13. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/README.md +0 -0
  14. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/code_index.yaml +0 -0
  15. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/__init__.py +0 -0
  16. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/analyzers/__init__.py +0 -0
  17. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -0
  18. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/analyzers/type_analyzer.py +0 -0
  19. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/dispatchers/__init__.py +0 -0
  20. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -0
  21. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/analyze_config.py +0 -0
  22. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/basic_integration.py +0 -0
  23. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -0
  24. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/extension_example.py +0 -0
  25. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/help_best_practices.py +0 -0
  26. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/help_usage.py +0 -0
  27. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -0
  28. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/project_structure_example.py +0 -0
  29. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/examples/testing_example.py +0 -0
  30. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/models.py +0 -0
  31. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/registry.py +0 -0
  32. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/schema.py +0 -0
  33. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/validators/docstring_validator.py +0 -0
  34. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter/validators/metadata_validator.py +0 -0
  35. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter.egg-info/dependency_links.txt +0 -0
  36. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter.egg-info/requires.txt +0 -0
  37. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/mcp_proxy_adapter.egg-info/top_level.txt +0 -0
  38. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/requirements.txt +0 -0
  39. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/setup.cfg +0 -0
  40. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/conftest.py +0 -0
  41. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_adapter_coverage.py +0 -0
  42. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_basic_dispatcher.py +0 -0
  43. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_command_registry.py +0 -0
  44. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_examples.py +0 -0
  45. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_mcp_proxy_adapter.py +0 -0
  46. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_mcp_proxy_adapter_basic.py +0 -0
  47. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_part1.py +0 -0
  48. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_part2.py +0 -0
  49. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_schema.py +0 -0
  50. {mcp_proxy_adapter-2.1.7 → mcp_proxy_adapter-2.1.9}/tests/test_simple_adapter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Summary: Adapter for exposing Command Registry commands as tools for AI models via MCP Proxy.
5
5
  Home-page: https://github.com/vasilyvz/mcp-proxy-adapter
6
6
  Author: Vasiliy VZ
@@ -493,6 +493,13 @@ class MCPProxyAdapter:
493
493
  "details": "Request requires 'command', 'method' or 'params' field"
494
494
  }
495
495
  }
496
+ # Подмена help -> __help
497
+ if command == "help":
498
+ command = "__help"
499
+ # Переименовываем параметр внутри params
500
+ if "command" in params:
501
+ params["cmdname"] = params.pop("command")
502
+ logger.info(f"[DEBUG] MCP CMD: command={command}, params={params}")
496
503
 
497
504
  # Check if command exists
498
505
  if command not in self.registry.dispatcher.get_valid_commands():
@@ -501,12 +508,14 @@ class MCPProxyAdapter:
501
508
  "error": {
502
509
  "code": 404,
503
510
  "message": f"Unknown command: {command}",
504
- "details": f"Command '{command}' not found in registry. Available commands: {', '.join(self.registry.dispatcher.get_valid_commands())}"
511
+ "details": f"Unknown command: {command}. Available commands: {', '.join(self.registry.dispatcher.get_valid_commands())}"
505
512
  }
506
513
  }
507
514
 
515
+ logger.info(f"[DEBUG] MCP CMD: command={command}, params={params}")
508
516
  # Check for required parameters
509
517
  command_info = self.registry.dispatcher.get_command_info(command)
518
+ logger.info(f"[DEBUG] MCP CMD: command_info={command_info}")
510
519
  if command_info and "params" in command_info:
511
520
  missing_params = []
512
521
  for param_name, param_info in command_info["params"].items():
@@ -522,7 +531,7 @@ class MCPProxyAdapter:
522
531
  "details": f"Command '{command}' requires following parameters: {', '.join(missing_params)}"
523
532
  }
524
533
  }
525
-
534
+ logger.info(f"[DEBUG] MCP CMD: command={command}, params={params}")
526
535
  # Check parameter types
527
536
  type_errors = self._validate_param_types(command, params)
528
537
  if type_errors:
@@ -534,9 +543,10 @@ class MCPProxyAdapter:
534
543
  "details": "Check parameter types and try again"
535
544
  }
536
545
  }
537
-
546
+ logger.info(f"[DEBUG] MCP CMD: command={command}, params={params}")
538
547
  # Execute the command
539
548
  try:
549
+ logger.info(f"[DEBUG] MCP CMD: executing command={command}, params={params}")
540
550
  result = self.registry.dispatcher.execute(command, **params)
541
551
 
542
552
  # Return result in MCP Proxy format
@@ -41,7 +41,7 @@ class JsonRpcDispatcher(BaseDispatcher):
41
41
  description="Returns information about available commands",
42
42
  summary="Command help",
43
43
  params={
44
- "command": {
44
+ "cmdname": {
45
45
  "type": "string",
46
46
  "description": "Command name for detailed information",
47
47
  "required": False
@@ -157,31 +157,26 @@ class JsonRpcDispatcher(BaseDispatcher):
157
157
  def _help_command(self, params: Dict[str, Any] = None) -> Dict[str, Any]:
158
158
  """
159
159
  Built-in help command for getting command information.
160
-
161
160
  Args:
162
- params: Command parameters
163
- command: Command name for detailed information
164
-
161
+ params: Command parameters (dict)
162
+ cmdname: Command name for detailed information
165
163
  Returns:
166
164
  Dict[str, Any]: Command help information
167
165
  """
168
- if not params:
166
+ if params is None:
169
167
  params = {}
170
-
171
168
  # If specific command is specified, return information only about it
172
- if "command" in params and params["command"]:
173
- command = params["command"]
174
- if command not in self._metadata:
169
+ cmdname = params.get("cmdname")
170
+ if cmdname:
171
+ if cmdname not in self._metadata:
175
172
  return {
176
- "error": f"Command '{command}' not found",
173
+ "error": f"Command '{cmdname}' not found",
177
174
  "available_commands": list(self._metadata.keys())
178
175
  }
179
-
180
176
  return {
181
- "command": command,
182
- "info": self._metadata[command]
177
+ "cmdname": cmdname,
178
+ "info": self._metadata[cmdname]
183
179
  }
184
-
185
180
  # Otherwise return brief information about all commands
186
181
  commands_info = {}
187
182
  for cmd, info in self._metadata.items():
@@ -190,9 +185,8 @@ class JsonRpcDispatcher(BaseDispatcher):
190
185
  "description": info["description"],
191
186
  "params_count": len(info["params"])
192
187
  }
193
-
194
188
  return {
195
189
  "commands": commands_info,
196
190
  "total": len(commands_info),
197
- "note": "Use the 'command' parameter to get detailed information about a specific command"
191
+ "note": "Use the 'cmdname' parameter to get detailed information about a specific command"
198
192
  }
@@ -26,7 +26,7 @@ from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse
26
26
 
27
27
  # Import MockRegistry from tests for example
28
28
  # (in a real project, CommandRegistry would be used)
29
- from tests.test_mcp_proxy_adapter import MockRegistry
29
+ from mcp_proxy_adapter.testing_utils import MockRegistry
30
30
 
31
31
  # Configure logging
32
32
  logging.basicConfig(level=logging.DEBUG)
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Test utilities for MCP Proxy Adapter: mock dispatcher, registry, and OpenAPI generator.
4
+ Can be used in examples and tests.
5
+ """
6
+
7
+ def success_command(value: int = 1) -> dict:
8
+ return {"result": value * 2}
9
+
10
+ def error_command() -> None:
11
+ raise ValueError("Test error")
12
+
13
+ def param_command(required_param: str, optional_param: int = 0) -> dict:
14
+ return {"required": required_param, "optional": optional_param}
15
+
16
+ def complex_param_command(array_param: list, object_param: dict, bool_param: bool = True) -> dict:
17
+ return {
18
+ "array_length": len(array_param),
19
+ "object_keys": list(object_param.keys()),
20
+ "bool_value": bool_param
21
+ }
22
+
23
+ def type_error_command(param: int) -> dict:
24
+ return {"param": param + 1}
25
+
26
+ class MockDispatcher:
27
+ def __init__(self):
28
+ self.commands = {
29
+ "success": success_command,
30
+ "error": error_command,
31
+ "param": param_command,
32
+ "execute": self.execute_from_params
33
+ }
34
+ self.commands_info = {
35
+ "success": {
36
+ "description": "Successful command",
37
+ "params": {"value": {"type": "integer", "description": "Input value", "required": False, "default": 1}}
38
+ },
39
+ "error": {"description": "Command with error", "params": {}},
40
+ "param": {
41
+ "description": "Command with parameters",
42
+ "params": {
43
+ "required_param": {"type": "string", "description": "Required parameter", "required": True},
44
+ "optional_param": {"type": "integer", "description": "Optional parameter", "required": False, "default": 0}
45
+ }
46
+ },
47
+ "execute": {
48
+ "description": "Universal command for executing other commands",
49
+ "params": {"query": {"type": "string", "description": "Command or query to execute", "required": False}}
50
+ },
51
+ "complex_param": {
52
+ "description": "Command with complex parameters",
53
+ "params": {
54
+ "array_param": {"type": "array", "description": "Array of values", "required": True},
55
+ "object_param": {"type": "object", "description": "Object", "required": True},
56
+ "bool_param": {"type": "boolean", "description": "Boolean value", "required": False, "default": True}
57
+ }
58
+ },
59
+ "type_error": {
60
+ "description": "Command that will raise TypeError",
61
+ "params": {"param": {"type": "integer", "description": "Integer parameter", "required": True}}
62
+ }
63
+ }
64
+
65
+ def execute_from_params(self, **params):
66
+ if "query" in params and params["query"] in self.commands:
67
+ command = params.pop("query")
68
+ return self.execute(command, **params)
69
+ return {"available_commands": self.get_valid_commands(), "received_params": params}
70
+
71
+ def execute(self, command, **params):
72
+ if command not in self.commands:
73
+ raise KeyError(f"Unknown command: {command}")
74
+ return self.commands[command](**params)
75
+
76
+ def get_valid_commands(self):
77
+ return list(self.commands.keys())
78
+
79
+ def get_command_info(self, command):
80
+ return self.commands_info.get(command)
81
+
82
+ def get_commands_info(self):
83
+ return self.commands_info
84
+
85
+ class MockRegistry:
86
+ def __init__(self, use_openapi_generator=False):
87
+ self.dispatcher = MockDispatcher()
88
+ self.generators = []
89
+ self.use_openapi_generator = use_openapi_generator
90
+
91
+ def get_commands_info(self):
92
+ return self.dispatcher.get_commands_info()
93
+
94
+ def add_generator(self, generator):
95
+ self.generators.append(generator)
96
+ if hasattr(generator, 'set_dispatcher'):
97
+ generator.set_dispatcher(self.dispatcher)
98
+
99
+ class MockOpenApiGenerator:
100
+ def __init__(self, **kwargs):
101
+ self.dispatcher = None
102
+ self.kwargs = kwargs
103
+
104
+ def set_dispatcher(self, dispatcher):
105
+ self.dispatcher = dispatcher
106
+
107
+ def generate_schema(self):
108
+ return {
109
+ "openapi": "3.0.0",
110
+ "info": {"title": "Mock API", "version": "1.0.0"},
111
+ "paths": {}
112
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Summary: Adapter for exposing Command Registry commands as tools for AI models via MCP Proxy.
5
5
  Home-page: https://github.com/vasilyvz/mcp-proxy-adapter
6
6
  Author: Vasiliy VZ
@@ -10,6 +10,7 @@ mcp_proxy_adapter/adapter.py
10
10
  mcp_proxy_adapter/models.py
11
11
  mcp_proxy_adapter/registry.py
12
12
  mcp_proxy_adapter/schema.py
13
+ mcp_proxy_adapter/testing_utils.py
13
14
  mcp_proxy_adapter.egg-info/PKG-INFO
14
15
  mcp_proxy_adapter.egg-info/SOURCES.txt
15
16
  mcp_proxy_adapter.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mcp-proxy-adapter"
7
- version = "2.1.7"
7
+ version = "2.1.9"
8
8
  description = "Adapter for exposing Command Registry commands as tools for AI models via MCP Proxy."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -14,7 +14,7 @@ with open(os.path.join(here, 'requirements.txt'), encoding='utf-8') as f:
14
14
 
15
15
  setup(
16
16
  name="mcp-proxy-adapter",
17
- version="2.1.7",
17
+ version="2.1.9",
18
18
  description="Adapter for exposing Command Registry commands as tools for AI models via MCP Proxy.",
19
19
  long_description=long_description,
20
20
  long_description_content_type="text/markdown",
@@ -42,6 +42,6 @@ setup(
42
42
  },
43
43
  include_package_data=True,
44
44
  package_data={
45
- 'mcp_proxy_adapter': ['examples/*.py', 'examples/*.json', 'docs/*.md', '../README.md'],
45
+ 'mcp_proxy_adapter': ['examples/*.py', 'examples/*.json', 'docs/*.md', '../README.md', 'testing_utils.py'],
46
46
  },
47
47
  )
@@ -29,6 +29,8 @@ except ImportError:
29
29
  from src.adapter import MCPProxyAdapter, configure_logger
30
30
  from src.registry import CommandRegistry
31
31
 
32
+ print('LOADED test_adapter.py')
33
+
32
34
  # Test command functions
33
35
  def success_command(value: int = 1) -> dict:
34
36
  """Test command that completes successfully."""
@@ -63,7 +65,7 @@ class BaseDispatcher:
63
65
  raise NotImplementedError("Method must be overridden in subclass")
64
66
 
65
67
  # Mock for command dispatcher
66
- class MockDispatcher(BaseDispatcher):
68
+ class MockDispatcherXXX(BaseDispatcher):
67
69
  """Mock for command dispatcher in tests."""
68
70
 
69
71
  def __init__(self):
@@ -71,7 +73,8 @@ class MockDispatcher(BaseDispatcher):
71
73
  "success": success_command,
72
74
  "error": error_command,
73
75
  "param": param_command,
74
- "execute": self.execute_from_params
76
+ "execute": self.execute_from_params,
77
+ "__help": self.help_command
75
78
  }
76
79
  self.commands_info = {
77
80
  "success": {
@@ -114,6 +117,16 @@ class MockDispatcher(BaseDispatcher):
114
117
  "required": False
115
118
  }
116
119
  }
120
+ },
121
+ "__help": {
122
+ "description": "Show help for commands",
123
+ "params": {
124
+ "cmdname": {
125
+ "type": "string",
126
+ "description": "Command name for detailed info",
127
+ "required": False
128
+ }
129
+ }
117
130
  }
118
131
  }
119
132
 
@@ -121,6 +134,8 @@ class MockDispatcher(BaseDispatcher):
121
134
  """Executes command based on parameters."""
122
135
  if "query" in params and params["query"] in self.commands:
123
136
  command = params.pop("query")
137
+ if command == "help":
138
+ command = "__help"
124
139
  return self.execute(command, **params)
125
140
  return {
126
141
  "available_commands": self.get_valid_commands(),
@@ -128,25 +143,48 @@ class MockDispatcher(BaseDispatcher):
128
143
  }
129
144
 
130
145
  def execute(self, command, **params):
146
+ print(f"[DEBUG] execute called: command={command}, params={params}")
147
+ if command == "help":
148
+ command = "__help"
131
149
  if command not in self.commands:
132
150
  raise KeyError(f"Unknown command: {command}")
151
+ if command == "__help":
152
+ return self.commands[command](params=params)
133
153
  return self.commands[command](**params)
134
154
 
135
155
  def get_valid_commands(self):
136
156
  return list(self.commands.keys())
137
157
 
138
158
  def get_command_info(self, command):
159
+ if command == "help":
160
+ command = "__help"
139
161
  return self.commands_info.get(command)
140
162
 
141
163
  def get_commands_info(self):
142
164
  return self.commands_info
143
165
 
166
+ def help_command(self, params=None):
167
+ import traceback; print(f"[DEBUG] help_command called with params: {params}"); print(f"[DEBUG] commands_info: {self.commands_info}"); traceback.print_stack()
168
+ if params is None:
169
+ params = {}
170
+ cmdname = params.get("cmdname")
171
+ if cmdname:
172
+ if cmdname in self.commands_info:
173
+ return {"cmdname": cmdname, "info": self.commands_info[cmdname]}
174
+ else:
175
+ return {"error": f"Command '{cmdname}' not found", "available_commands": list(self.commands_info.keys())}
176
+ return {
177
+ "commands": {cmd: {"description": info["description"], "params": info["params"]} for cmd, info in self.commands_info.items()},
178
+ "total": len(self.commands_info),
179
+ "note": "Use the 'cmdname' parameter to get detailed information about a specific command"
180
+ }
181
+
144
182
  # Mock for CommandRegistry
145
183
  class MockRegistry:
146
184
  """Mock for CommandRegistry in tests."""
147
185
 
148
186
  def __init__(self):
149
- self.dispatcher = MockDispatcher()
187
+ self.dispatcher = MockDispatcherXXX()
150
188
  self.generators = []
151
189
 
152
190
  def get_commands_info(self):
@@ -419,9 +457,9 @@ def test_save_config_to_file(adapter, tmp_path):
419
457
 
420
458
  assert "version" in config
421
459
  assert "tools" in config
422
- assert len(config["tools"]) == 4 # Corrected: now we have 4 commands
460
+ assert len(config["tools"]) == 5 # Теперь 5 команд (включая help)
423
461
  assert "routes" in config
424
- assert len(config["routes"]) == 4 # Corrected: now we have 4 routes, corresponding to commands
462
+ assert len(config["routes"]) == 5 # Теперь 5 маршрутов
425
463
 
426
464
  def test_parameter_type_validation(test_app):
427
465
  """Test parameter type validation."""
@@ -526,4 +564,62 @@ def test_params_only_format(test_app):
526
564
  data = response.json()
527
565
  assert "result" in data
528
566
  assert "available_commands" in data["result"]
529
- assert "received_params" in data["result"]
567
+ assert "received_params" in data["result"]
568
+
569
+ def test_jsonrpc_proxy_and_help_conflict(test_app):
570
+ """Test JSON-RPC proxying and help/command param conflict."""
571
+ # Обычный JSON-RPC вызов существующей команды
572
+ response = test_app.post("/cmd", json={
573
+ "jsonrpc": "2.0",
574
+ "method": "success",
575
+ "params": {"value": 3},
576
+ "id": 42
577
+ })
578
+ assert response.status_code == 200
579
+ data = response.json()
580
+ assert "result" in data
581
+ assert data["result"] == {"result": 6}
582
+
583
+ # Вызов help по конкретной команде (не должно быть multiple values for argument 'command')
584
+ response = test_app.post("/cmd", json={
585
+ "jsonrpc": "2.0",
586
+ "method": "help",
587
+ "params": {"cmdname": "success"},
588
+ "id": 43
589
+ })
590
+ assert response.status_code == 200
591
+ data = response.json()
592
+ assert "result" in data
593
+ assert data["result"]["cmdname"] == "success"
594
+ assert "info" in data["result"]
595
+ assert "description" in data["result"]["info"]
596
+
597
+ # Вызов help по несуществующей команде
598
+ response = test_app.post("/cmd", json={
599
+ "jsonrpc": "2.0",
600
+ "method": "help",
601
+ "params": {"cmdname": "not_a_command"},
602
+ "id": 44
603
+ })
604
+ assert response.status_code == 200
605
+ data = response.json()
606
+ assert "result" in data
607
+ # Ошибка может быть либо в data['result']['error'], либо в data['error']['message']
608
+ found = False
609
+ if "error" in data["result"] and "not found" in data["result"]["error"]:
610
+ found = True
611
+ if "error" in data and "message" in data["error"] and "not found" in data["error"]["message"]:
612
+ found = True
613
+ assert found, f"No 'not found' in error: {data}"
614
+
615
+ # Вызов несуществующей команды
616
+ response = test_app.post("/cmd", json={
617
+ "jsonrpc": "2.0",
618
+ "method": "not_a_command",
619
+ "params": {},
620
+ "id": 45
621
+ })
622
+ assert response.status_code == 200
623
+ data = response.json()
624
+ assert "error" in data
625
+ assert "Unknown command" in data["error"]["message"]