mcp-proxy-adapter 3.0.0__py3-none-any.whl → 3.0.1__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.
- examples/basic_example/README.md +123 -9
- examples/basic_example/config.json +4 -0
- examples/basic_example/docs/EN/README.md +46 -5
- examples/basic_example/docs/RU/README.md +46 -5
- examples/basic_example/server.py +127 -21
- examples/complete_example/commands/system_command.py +1 -0
- examples/complete_example/server.py +65 -11
- examples/minimal_example/README.md +20 -6
- examples/minimal_example/config.json +7 -14
- examples/minimal_example/main.py +109 -40
- examples/minimal_example/simple_server.py +53 -14
- examples/minimal_example/tests/conftest.py +1 -1
- examples/minimal_example/tests/test_integration.py +8 -10
- examples/simple_server.py +12 -21
- examples/test_server.py +22 -14
- examples/tool_description_example.py +82 -0
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +391 -0
- mcp_proxy_adapter/api/handlers.py +229 -0
- mcp_proxy_adapter/api/middleware/__init__.py +49 -0
- mcp_proxy_adapter/api/middleware/auth.py +146 -0
- mcp_proxy_adapter/api/middleware/base.py +79 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
- mcp_proxy_adapter/api/middleware/logging.py +96 -0
- mcp_proxy_adapter/api/middleware/performance.py +83 -0
- mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
- mcp_proxy_adapter/api/schemas.py +305 -0
- mcp_proxy_adapter/api/tool_integration.py +223 -0
- mcp_proxy_adapter/api/tools.py +198 -0
- mcp_proxy_adapter/commands/__init__.py +19 -0
- mcp_proxy_adapter/commands/base.py +301 -0
- mcp_proxy_adapter/commands/command_registry.py +231 -0
- mcp_proxy_adapter/commands/config_command.py +113 -0
- mcp_proxy_adapter/commands/health_command.py +136 -0
- mcp_proxy_adapter/commands/help_command.py +193 -0
- mcp_proxy_adapter/commands/result.py +215 -0
- mcp_proxy_adapter/config.py +9 -0
- mcp_proxy_adapter/core/__init__.py +0 -0
- mcp_proxy_adapter/core/errors.py +173 -0
- mcp_proxy_adapter/core/logging.py +205 -0
- mcp_proxy_adapter/core/utils.py +138 -0
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +3 -0
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
- mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
- mcp_proxy_adapter/tests/commands/__init__.py +3 -0
- mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
- mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
- mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
- mcp_proxy_adapter/tests/conftest.py +131 -0
- mcp_proxy_adapter/tests/functional/__init__.py +3 -0
- mcp_proxy_adapter/tests/functional/test_api.py +235 -0
- mcp_proxy_adapter/tests/integration/__init__.py +3 -0
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
- mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
- mcp_proxy_adapter/tests/performance/__init__.py +3 -0
- mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
- mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
- mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
- mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
- mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
- mcp_proxy_adapter/tests/test_base_command.py +123 -0
- mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
- mcp_proxy_adapter/tests/test_command_registry.py +245 -0
- mcp_proxy_adapter/tests/test_config.py +127 -0
- mcp_proxy_adapter/tests/test_utils.py +65 -0
- mcp_proxy_adapter/tests/unit/__init__.py +3 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
- mcp_proxy_adapter/tests/unit/test_config.py +217 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-3.0.1.dist-info/RECORD +109 -0
- examples/basic_example/config.yaml +0 -20
- examples/basic_example/main.py +0 -50
- examples/complete_example/main.py +0 -67
- examples/minimal_example/config.yaml +0 -26
- mcp_proxy_adapter/framework.py +0 -109
- mcp_proxy_adapter-3.0.0.dist-info/RECORD +0 -58
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
"""
|
2
|
+
Module for registering and managing commands.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import importlib
|
6
|
+
import inspect
|
7
|
+
import os
|
8
|
+
import pkgutil
|
9
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar, cast
|
10
|
+
|
11
|
+
from mcp_proxy_adapter.commands.base import Command
|
12
|
+
from mcp_proxy_adapter.core.errors import NotFoundError
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
|
15
|
+
T = TypeVar("T", bound=Command)
|
16
|
+
|
17
|
+
|
18
|
+
class CommandRegistry:
|
19
|
+
"""
|
20
|
+
Registry for registering and finding commands.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self):
|
24
|
+
"""
|
25
|
+
Initialize command registry.
|
26
|
+
"""
|
27
|
+
self._commands: Dict[str, Type[Command]] = {}
|
28
|
+
|
29
|
+
def register(self, command_class: Type[Command]) -> None:
|
30
|
+
"""
|
31
|
+
Registers command class in the registry.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
command_class: Command class to register.
|
35
|
+
|
36
|
+
Raises:
|
37
|
+
ValueError: If command with the same name is already registered.
|
38
|
+
"""
|
39
|
+
if not hasattr(command_class, "name") or not command_class.name:
|
40
|
+
# Use class name if name attribute is not set
|
41
|
+
command_name = command_class.__name__.lower()
|
42
|
+
if command_name.endswith("command"):
|
43
|
+
command_name = command_name[:-7] # Remove "command" suffix
|
44
|
+
else:
|
45
|
+
command_name = command_class.name
|
46
|
+
|
47
|
+
if command_name in self._commands:
|
48
|
+
logger.debug(f"Command '{command_name}' is already registered, skipping")
|
49
|
+
raise ValueError(f"Command '{command_name}' is already registered")
|
50
|
+
|
51
|
+
logger.debug(f"Registering command: {command_name}")
|
52
|
+
self._commands[command_name] = command_class
|
53
|
+
|
54
|
+
def unregister(self, command_name: str) -> None:
|
55
|
+
"""
|
56
|
+
Removes command from registry.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
command_name: Command name to remove.
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
NotFoundError: If command is not found.
|
63
|
+
"""
|
64
|
+
if command_name not in self._commands:
|
65
|
+
raise NotFoundError(f"Command '{command_name}' not found")
|
66
|
+
|
67
|
+
logger.debug(f"Unregistering command: {command_name}")
|
68
|
+
del self._commands[command_name]
|
69
|
+
|
70
|
+
def command_exists(self, command_name: str) -> bool:
|
71
|
+
"""
|
72
|
+
Checks if command exists in registry.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
command_name: Command name to check.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
True if command exists, False otherwise.
|
79
|
+
"""
|
80
|
+
return command_name in self._commands
|
81
|
+
|
82
|
+
def get_command(self, command_name: str) -> Type[Command]:
|
83
|
+
"""
|
84
|
+
Gets command class by name.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
command_name: Command name.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
Command class.
|
91
|
+
|
92
|
+
Raises:
|
93
|
+
NotFoundError: If command is not found.
|
94
|
+
"""
|
95
|
+
if command_name not in self._commands:
|
96
|
+
raise NotFoundError(f"Command '{command_name}' not found")
|
97
|
+
|
98
|
+
return self._commands[command_name]
|
99
|
+
|
100
|
+
def get_all_commands(self) -> Dict[str, Type[Command]]:
|
101
|
+
"""
|
102
|
+
Returns all registered commands.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Dictionary with command names and their classes.
|
106
|
+
"""
|
107
|
+
return dict(self._commands)
|
108
|
+
|
109
|
+
def get_command_info(self, command_name: str) -> Dict[str, Any]:
|
110
|
+
"""
|
111
|
+
Gets information about a command.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
command_name: Command name.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Dictionary with command information.
|
118
|
+
|
119
|
+
Raises:
|
120
|
+
NotFoundError: If command is not found.
|
121
|
+
"""
|
122
|
+
command_class = self.get_command(command_name)
|
123
|
+
|
124
|
+
return {
|
125
|
+
"name": command_name,
|
126
|
+
"description": command_class.__doc__ or "",
|
127
|
+
"params": command_class.get_param_info(),
|
128
|
+
"schema": command_class.get_schema(),
|
129
|
+
"result_schema": command_class.get_result_schema()
|
130
|
+
}
|
131
|
+
|
132
|
+
def get_command_metadata(self, command_name: str) -> Dict[str, Any]:
|
133
|
+
"""
|
134
|
+
Get complete metadata for a command.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
command_name: Command name
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
Dict with command metadata
|
141
|
+
|
142
|
+
Raises:
|
143
|
+
NotFoundError: If command is not found
|
144
|
+
"""
|
145
|
+
command_class = self.get_command(command_name)
|
146
|
+
return command_class.get_metadata()
|
147
|
+
|
148
|
+
def get_all_metadata(self) -> Dict[str, Dict[str, Any]]:
|
149
|
+
"""
|
150
|
+
Get metadata for all registered commands.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
Dict with command names as keys and metadata as values
|
154
|
+
"""
|
155
|
+
metadata = {}
|
156
|
+
for name, command_class in self._commands.items():
|
157
|
+
metadata[name] = command_class.get_metadata()
|
158
|
+
return metadata
|
159
|
+
|
160
|
+
def get_all_commands_info(self) -> Dict[str, Dict[str, Any]]:
|
161
|
+
"""
|
162
|
+
Gets information about all registered commands.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
Dictionary with information about all commands.
|
166
|
+
"""
|
167
|
+
commands_info = {}
|
168
|
+
for name in self._commands:
|
169
|
+
commands_info[name] = self.get_command_info(name)
|
170
|
+
return commands_info
|
171
|
+
|
172
|
+
def discover_commands(self, package_path: str = "mcp_proxy_adapter.commands") -> None:
|
173
|
+
"""
|
174
|
+
Automatically discovers and registers commands in the specified package.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
package_path: Path to package with commands.
|
178
|
+
"""
|
179
|
+
logger.info(f"Discovering commands in package: {package_path}")
|
180
|
+
|
181
|
+
try:
|
182
|
+
package = importlib.import_module(package_path)
|
183
|
+
package_dir = os.path.dirname(package.__file__ or "")
|
184
|
+
|
185
|
+
for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]):
|
186
|
+
if is_pkg:
|
187
|
+
# Recursively traverse subpackages
|
188
|
+
self.discover_commands(f"{package_path}.{module_name}")
|
189
|
+
elif module_name.endswith("_command"):
|
190
|
+
# Import only command modules
|
191
|
+
module_path = f"{package_path}.{module_name}"
|
192
|
+
logger.debug(f"Found command module: {module_path}")
|
193
|
+
|
194
|
+
try:
|
195
|
+
module = importlib.import_module(module_path)
|
196
|
+
|
197
|
+
# Find all command classes in the module
|
198
|
+
for name, obj in inspect.getmembers(module):
|
199
|
+
if (inspect.isclass(obj) and
|
200
|
+
issubclass(obj, Command) and
|
201
|
+
obj != Command and
|
202
|
+
not inspect.isabstract(obj)):
|
203
|
+
|
204
|
+
# Get command name before registration
|
205
|
+
command_name = obj.name if hasattr(obj, "name") and obj.name else obj.__name__.lower()
|
206
|
+
if command_name.endswith("command"):
|
207
|
+
command_name = command_name[:-7] # Remove "command" suffix
|
208
|
+
|
209
|
+
# Register the command only if it doesn't exist
|
210
|
+
if not self.command_exists(command_name):
|
211
|
+
self.register(cast(Type[Command], obj))
|
212
|
+
else:
|
213
|
+
logger.debug(f"Command '{command_name}' is already registered, skipping")
|
214
|
+
except ValueError as e:
|
215
|
+
# Skip already registered commands
|
216
|
+
logger.debug(f"Skipping command registration: {str(e)}")
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(f"Error loading command module {module_path}: {e}")
|
219
|
+
except Exception as e:
|
220
|
+
logger.error(f"Error discovering commands: {e}")
|
221
|
+
|
222
|
+
def clear(self) -> None:
|
223
|
+
"""
|
224
|
+
Clears command registry.
|
225
|
+
"""
|
226
|
+
logger.debug("Clearing command registry")
|
227
|
+
self._commands.clear()
|
228
|
+
|
229
|
+
|
230
|
+
# Global command registry instance
|
231
|
+
registry = CommandRegistry()
|
@@ -0,0 +1,113 @@
|
|
1
|
+
"""
|
2
|
+
Config command implementation for managing service configuration.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Optional
|
6
|
+
|
7
|
+
from mcp_proxy_adapter.commands.base import Command
|
8
|
+
from mcp_proxy_adapter.commands.result import SuccessResult
|
9
|
+
from mcp_proxy_adapter.config import config as config_instance
|
10
|
+
|
11
|
+
|
12
|
+
class ConfigResult(SuccessResult):
|
13
|
+
"""
|
14
|
+
Config operation result.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, config: Dict[str, Any], operation: str, message: str = None):
|
18
|
+
"""
|
19
|
+
Initialize config result.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
config: Configuration values
|
23
|
+
operation: Operation performed
|
24
|
+
message: Optional message
|
25
|
+
"""
|
26
|
+
super().__init__(data={
|
27
|
+
"config": config,
|
28
|
+
"operation": operation
|
29
|
+
}, message=message)
|
30
|
+
|
31
|
+
|
32
|
+
class ConfigCommand(Command):
|
33
|
+
"""
|
34
|
+
Command for managing service configuration.
|
35
|
+
"""
|
36
|
+
name = "config"
|
37
|
+
description = "Get or set configuration values"
|
38
|
+
result_class = ConfigResult
|
39
|
+
|
40
|
+
async def execute(self, operation: str = "get", path: str = None, value: Any = None) -> ConfigResult:
|
41
|
+
"""
|
42
|
+
Execute the command.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
operation: Operation to perform (get, set)
|
46
|
+
path: Configuration path (dot notation)
|
47
|
+
value: Value to set (for set operation)
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Config operation result
|
51
|
+
"""
|
52
|
+
message = None
|
53
|
+
result_config = {}
|
54
|
+
|
55
|
+
if operation == "get":
|
56
|
+
if path:
|
57
|
+
# Get specific config value
|
58
|
+
result_config = {path: config_instance.get(path)}
|
59
|
+
else:
|
60
|
+
# Get all config
|
61
|
+
result_config = config_instance.get_all()
|
62
|
+
message = f"Configuration retrieved successfully"
|
63
|
+
|
64
|
+
elif operation == "set":
|
65
|
+
if path and value is not None:
|
66
|
+
# Set config value
|
67
|
+
config_instance.set(path, value)
|
68
|
+
# Save config
|
69
|
+
config_instance.save()
|
70
|
+
result_config = {path: value}
|
71
|
+
message = f"Configuration updated successfully"
|
72
|
+
else:
|
73
|
+
# Error - missing required parameters
|
74
|
+
raise ValueError("Both 'path' and 'value' are required for 'set' operation")
|
75
|
+
|
76
|
+
else:
|
77
|
+
# Invalid operation
|
78
|
+
raise ValueError(f"Invalid operation: {operation}. Valid operations: get, set")
|
79
|
+
|
80
|
+
return ConfigResult(
|
81
|
+
config=result_config,
|
82
|
+
operation=operation,
|
83
|
+
message=message
|
84
|
+
)
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def get_schema(cls) -> Dict[str, Any]:
|
88
|
+
"""
|
89
|
+
Returns JSON schema for command parameters validation.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
Dictionary with JSON schema.
|
93
|
+
"""
|
94
|
+
return {
|
95
|
+
"type": "object",
|
96
|
+
"properties": {
|
97
|
+
"operation": {
|
98
|
+
"type": "string",
|
99
|
+
"enum": ["get", "set"],
|
100
|
+
"default": "get",
|
101
|
+
"description": "Operation to perform (get or set)"
|
102
|
+
},
|
103
|
+
"path": {
|
104
|
+
"type": "string",
|
105
|
+
"description": "Configuration path in dot notation (e.g. 'server.host')"
|
106
|
+
},
|
107
|
+
"value": {
|
108
|
+
"description": "Value to set (required for 'set' operation)"
|
109
|
+
}
|
110
|
+
},
|
111
|
+
"required": ["operation"],
|
112
|
+
"additionalProperties": False
|
113
|
+
}
|
@@ -0,0 +1,136 @@
|
|
1
|
+
"""
|
2
|
+
Module with health command implementation.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import platform
|
7
|
+
import sys
|
8
|
+
import psutil
|
9
|
+
from datetime import datetime
|
10
|
+
from typing import Dict, Any, Optional
|
11
|
+
|
12
|
+
from mcp_proxy_adapter.commands.base import Command
|
13
|
+
from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult
|
14
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
15
|
+
|
16
|
+
|
17
|
+
class HealthResult(SuccessResult):
|
18
|
+
"""
|
19
|
+
Result of the health command execution.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, status: str, version: str, uptime: float, components: Dict[str, Any]):
|
23
|
+
"""
|
24
|
+
Initialize health command result.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
status: Server status ("ok" or "error")
|
28
|
+
version: Server version
|
29
|
+
uptime: Server uptime in seconds
|
30
|
+
components: Dictionary with components status
|
31
|
+
"""
|
32
|
+
super().__init__(
|
33
|
+
data={
|
34
|
+
"status": status,
|
35
|
+
"version": version,
|
36
|
+
"uptime": uptime,
|
37
|
+
"components": components
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def get_schema(cls) -> Dict[str, Any]:
|
43
|
+
"""
|
44
|
+
Get JSON schema for result validation.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Dict[str, Any]: JSON schema
|
48
|
+
"""
|
49
|
+
return {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {
|
52
|
+
"data": {
|
53
|
+
"type": "object",
|
54
|
+
"properties": {
|
55
|
+
"status": {"type": "string"},
|
56
|
+
"version": {"type": "string"},
|
57
|
+
"uptime": {"type": "number"},
|
58
|
+
"components": {
|
59
|
+
"type": "object",
|
60
|
+
"properties": {
|
61
|
+
"system": {"type": "object"},
|
62
|
+
"process": {"type": "object"},
|
63
|
+
"commands": {"type": "object"}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
},
|
67
|
+
"required": ["status", "version", "uptime", "components"]
|
68
|
+
}
|
69
|
+
},
|
70
|
+
"required": ["data"]
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
class HealthCommand(Command):
|
75
|
+
"""
|
76
|
+
Command that returns information about server health and status.
|
77
|
+
"""
|
78
|
+
|
79
|
+
name = "health"
|
80
|
+
result_class = HealthResult
|
81
|
+
|
82
|
+
async def execute(self) -> HealthResult:
|
83
|
+
"""
|
84
|
+
Execute health command.
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
HealthResult: Health command result
|
88
|
+
"""
|
89
|
+
# Get version from package
|
90
|
+
try:
|
91
|
+
from mcp_proxy_adapter.version import __version__ as version
|
92
|
+
except ImportError:
|
93
|
+
version = "unknown"
|
94
|
+
|
95
|
+
# Get process start time
|
96
|
+
process = psutil.Process(os.getpid())
|
97
|
+
start_time = datetime.fromtimestamp(process.create_time())
|
98
|
+
uptime_seconds = (datetime.now() - start_time).total_seconds()
|
99
|
+
|
100
|
+
# Get system information
|
101
|
+
memory_info = process.memory_info()
|
102
|
+
|
103
|
+
return HealthResult(
|
104
|
+
status="ok",
|
105
|
+
version=version,
|
106
|
+
uptime=uptime_seconds,
|
107
|
+
components={
|
108
|
+
"system": {
|
109
|
+
"python_version": sys.version,
|
110
|
+
"platform": platform.platform(),
|
111
|
+
"cpu_count": os.cpu_count()
|
112
|
+
},
|
113
|
+
"process": {
|
114
|
+
"pid": os.getpid(),
|
115
|
+
"memory_usage_mb": memory_info.rss / (1024 * 1024),
|
116
|
+
"start_time": start_time.isoformat()
|
117
|
+
},
|
118
|
+
"commands": {
|
119
|
+
"registered_count": len(registry.get_all_commands())
|
120
|
+
}
|
121
|
+
}
|
122
|
+
)
|
123
|
+
|
124
|
+
@classmethod
|
125
|
+
def get_schema(cls) -> Dict[str, Any]:
|
126
|
+
"""
|
127
|
+
Get JSON schema for command parameters validation.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Dict[str, Any]: JSON schema
|
131
|
+
"""
|
132
|
+
return {
|
133
|
+
"type": "object",
|
134
|
+
"additionalProperties": False,
|
135
|
+
"description": "Command doesn't accept any parameters"
|
136
|
+
}
|
@@ -0,0 +1,193 @@
|
|
1
|
+
"""
|
2
|
+
Module with help command implementation.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Optional
|
6
|
+
|
7
|
+
from mcp_proxy_adapter.commands.base import Command
|
8
|
+
from mcp_proxy_adapter.commands.result import CommandResult
|
9
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
10
|
+
from mcp_proxy_adapter.core.errors import NotFoundError
|
11
|
+
|
12
|
+
|
13
|
+
class HelpResult(CommandResult):
|
14
|
+
"""
|
15
|
+
Result of the help command execution.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(self, commands_info: Optional[Dict[str, Any]] = None, command_info: Optional[Dict[str, Any]] = None):
|
19
|
+
"""
|
20
|
+
Initialize help command result.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
commands_info: Information about all commands (for request without parameters)
|
24
|
+
command_info: Information about a specific command (for request with cmdname parameter)
|
25
|
+
"""
|
26
|
+
self.commands_info = commands_info
|
27
|
+
self.command_info = command_info
|
28
|
+
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
30
|
+
"""
|
31
|
+
Convert result to dictionary.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dict[str, Any]: Result as dictionary
|
35
|
+
"""
|
36
|
+
if self.command_info:
|
37
|
+
return {
|
38
|
+
"cmdname": self.command_info["name"],
|
39
|
+
"info": {
|
40
|
+
"description": self.command_info["description"],
|
41
|
+
"summary": self.command_info["summary"],
|
42
|
+
"params": self.command_info["params"],
|
43
|
+
"examples": self.command_info["examples"]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
# For list of all commands, return as is (already formatted)
|
48
|
+
result = self.commands_info.copy()
|
49
|
+
|
50
|
+
# Add total count and note about usage
|
51
|
+
result["total"] = len(result["commands"])
|
52
|
+
result["note"] = "To get detailed information about a specific command, call help with parameter: POST /cmd {\"command\": \"help\", \"params\": {\"cmdname\": \"<command_name>\"}}. Only 'cmdname' parameter is supported."
|
53
|
+
|
54
|
+
return result
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def get_schema(cls) -> Dict[str, Any]:
|
58
|
+
"""
|
59
|
+
Get JSON schema for result validation.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Dict[str, Any]: JSON schema
|
63
|
+
"""
|
64
|
+
return {
|
65
|
+
"type": "object",
|
66
|
+
"oneOf": [
|
67
|
+
{
|
68
|
+
"properties": {
|
69
|
+
"commands": {
|
70
|
+
"type": "object",
|
71
|
+
"additionalProperties": {
|
72
|
+
"type": "object",
|
73
|
+
"properties": {
|
74
|
+
"description": {"type": "string"}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
},
|
78
|
+
"tool_info": {
|
79
|
+
"type": "object",
|
80
|
+
"properties": {
|
81
|
+
"name": {"type": "string"},
|
82
|
+
"description": {"type": "string"},
|
83
|
+
"version": {"type": "string"}
|
84
|
+
}
|
85
|
+
},
|
86
|
+
"help_usage": {
|
87
|
+
"type": "object"
|
88
|
+
},
|
89
|
+
"total": {"type": "integer"},
|
90
|
+
"note": {"type": "string"}
|
91
|
+
},
|
92
|
+
"required": ["commands"]
|
93
|
+
},
|
94
|
+
{
|
95
|
+
"properties": {
|
96
|
+
"cmdname": {"type": "string"},
|
97
|
+
"info": {
|
98
|
+
"type": "object",
|
99
|
+
"properties": {
|
100
|
+
"description": {"type": "string"},
|
101
|
+
"summary": {"type": "string"},
|
102
|
+
"params": {"type": "object"},
|
103
|
+
"examples": {"type": "array"}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
},
|
107
|
+
"required": ["cmdname", "info"]
|
108
|
+
}
|
109
|
+
]
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
class HelpCommand(Command):
|
114
|
+
"""
|
115
|
+
Command for getting help information about available commands.
|
116
|
+
"""
|
117
|
+
|
118
|
+
name = "help"
|
119
|
+
result_class = HelpResult
|
120
|
+
|
121
|
+
async def execute(self, cmdname: Optional[str] = None) -> HelpResult:
|
122
|
+
"""
|
123
|
+
Execute help command.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
cmdname: Name of the command to get information about (optional)
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
HelpResult: Help command result
|
130
|
+
|
131
|
+
Raises:
|
132
|
+
NotFoundError: If specified command not found
|
133
|
+
"""
|
134
|
+
# If cmdname is provided, return information about specific command
|
135
|
+
if cmdname:
|
136
|
+
try:
|
137
|
+
# Get command metadata from registry
|
138
|
+
command_metadata = registry.get_command_metadata(cmdname)
|
139
|
+
return HelpResult(command_info=command_metadata)
|
140
|
+
except NotFoundError:
|
141
|
+
# If command not found, raise error
|
142
|
+
raise NotFoundError(f"Command '{cmdname}' not found")
|
143
|
+
|
144
|
+
# Otherwise, return information about all available commands
|
145
|
+
# and tool metadata
|
146
|
+
|
147
|
+
# Get metadata for all commands
|
148
|
+
all_metadata = registry.get_all_metadata()
|
149
|
+
|
150
|
+
# Prepare response format with tool metadata
|
151
|
+
result = {
|
152
|
+
"tool_info": {
|
153
|
+
"name": "MCP-Proxy API Service",
|
154
|
+
"description": "JSON-RPC API for microservice command execution",
|
155
|
+
"version": "1.0.0"
|
156
|
+
},
|
157
|
+
"help_usage": {
|
158
|
+
"description": "Get information about commands",
|
159
|
+
"examples": [
|
160
|
+
{"command": "help", "description": "List of all available commands"},
|
161
|
+
{"command": "help", "params": {"cmdname": "command_name"}, "description": "Get detailed information about a specific command"}
|
162
|
+
]
|
163
|
+
},
|
164
|
+
"commands": {}
|
165
|
+
}
|
166
|
+
|
167
|
+
# Add brief information about commands
|
168
|
+
for name, metadata in all_metadata.items():
|
169
|
+
result["commands"][name] = {
|
170
|
+
"summary": metadata["summary"],
|
171
|
+
"params_count": len(metadata["params"])
|
172
|
+
}
|
173
|
+
|
174
|
+
return HelpResult(commands_info=result)
|
175
|
+
|
176
|
+
@classmethod
|
177
|
+
def get_schema(cls) -> Dict[str, Any]:
|
178
|
+
"""
|
179
|
+
Get JSON schema for command parameters validation.
|
180
|
+
|
181
|
+
Returns:
|
182
|
+
Dict[str, Any]: JSON schema
|
183
|
+
"""
|
184
|
+
return {
|
185
|
+
"type": "object",
|
186
|
+
"properties": {
|
187
|
+
"cmdname": {
|
188
|
+
"type": "string",
|
189
|
+
"description": "Name of command to get information about"
|
190
|
+
}
|
191
|
+
},
|
192
|
+
"additionalProperties": False
|
193
|
+
}
|