mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.5__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.
- mcp_proxy_adapter/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +108 -88
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -36,6 +36,7 @@ from mcp_proxy_adapter.core.logging import logger
|
|
36
36
|
|
37
37
|
try:
|
38
38
|
import requests
|
39
|
+
|
39
40
|
REQUESTS_AVAILABLE = True
|
40
41
|
except ImportError:
|
41
42
|
REQUESTS_AVAILABLE = False
|
@@ -48,16 +49,22 @@ class CommandRegistry:
|
|
48
49
|
"""
|
49
50
|
Registry for registering and finding commands.
|
50
51
|
"""
|
51
|
-
|
52
|
+
|
52
53
|
def __init__(self):
|
53
54
|
"""
|
54
55
|
Initialize command registry.
|
55
56
|
"""
|
56
|
-
self._builtin_commands: Dict[str, Type[Command]] =
|
57
|
-
|
58
|
-
|
57
|
+
self._builtin_commands: Dict[str, Type[Command]] = (
|
58
|
+
{}
|
59
|
+
) # Built-in framework commands
|
60
|
+
self._custom_commands: Dict[str, Type[Command]] = (
|
61
|
+
{}
|
62
|
+
) # Custom commands (highest priority)
|
63
|
+
self._loaded_commands: Dict[str, Type[Command]] = (
|
64
|
+
{}
|
65
|
+
) # Commands loaded from directory
|
59
66
|
self._instances: Dict[str, Command] = {} # Command instances
|
60
|
-
|
67
|
+
|
61
68
|
def register_builtin(self, command: Union[Type[Command], Command]) -> None:
|
62
69
|
"""
|
63
70
|
Register a built-in framework command.
|
@@ -69,20 +76,22 @@ class CommandRegistry:
|
|
69
76
|
ValueError: If command with the same name is already registered.
|
70
77
|
"""
|
71
78
|
command_name = self._get_command_name(command)
|
72
|
-
|
79
|
+
|
73
80
|
# Check for conflicts with other built-in commands
|
74
81
|
if command_name in self._builtin_commands:
|
75
|
-
logger.error(
|
82
|
+
logger.error(
|
83
|
+
f"Built-in command '{command_name}' is already registered, skipping"
|
84
|
+
)
|
76
85
|
raise ValueError(f"Built-in command '{command_name}' is already registered")
|
77
|
-
|
86
|
+
|
78
87
|
# Built-in commands can override loaded commands
|
79
88
|
# Remove any existing loaded commands with the same name
|
80
89
|
if command_name in self._loaded_commands:
|
81
90
|
logger.info(f"Built-in command '{command_name}' overrides loaded command")
|
82
91
|
del self._loaded_commands[command_name]
|
83
|
-
|
92
|
+
|
84
93
|
self._register_command(command, self._builtin_commands, "built-in")
|
85
|
-
|
94
|
+
|
86
95
|
def register_custom(self, command: Union[Type[Command], Command]) -> None:
|
87
96
|
"""
|
88
97
|
Register a custom command with highest priority.
|
@@ -94,24 +103,26 @@ class CommandRegistry:
|
|
94
103
|
ValueError: If command with the same name is already registered.
|
95
104
|
"""
|
96
105
|
command_name = self._get_command_name(command)
|
97
|
-
|
106
|
+
|
98
107
|
# Check for conflicts with other custom commands
|
99
108
|
if command_name in self._custom_commands:
|
100
|
-
logger.error(
|
109
|
+
logger.error(
|
110
|
+
f"Custom command '{command_name}' is already registered, skipping"
|
111
|
+
)
|
101
112
|
raise ValueError(f"Custom command '{command_name}' is already registered")
|
102
|
-
|
113
|
+
|
103
114
|
# Custom commands can override built-in and loaded commands
|
104
115
|
# Remove any existing commands with the same name from other types
|
105
116
|
if command_name in self._builtin_commands:
|
106
117
|
logger.info(f"Custom command '{command_name}' overrides built-in command")
|
107
118
|
del self._builtin_commands[command_name]
|
108
|
-
|
119
|
+
|
109
120
|
if command_name in self._loaded_commands:
|
110
121
|
logger.info(f"Custom command '{command_name}' overrides loaded command")
|
111
122
|
del self._loaded_commands[command_name]
|
112
|
-
|
123
|
+
|
113
124
|
self._register_command(command, self._custom_commands, "custom")
|
114
|
-
|
125
|
+
|
115
126
|
def register_loaded(self, command: Union[Type[Command], Command]) -> None:
|
116
127
|
"""
|
117
128
|
Register a command loaded from directory.
|
@@ -123,30 +134,39 @@ class CommandRegistry:
|
|
123
134
|
bool: True if registered, False if skipped due to conflict.
|
124
135
|
"""
|
125
136
|
command_name = self._get_command_name(command)
|
126
|
-
|
137
|
+
|
127
138
|
# Check for conflicts with custom and built-in commands
|
128
139
|
if command_name in self._custom_commands:
|
129
|
-
logger.warning(
|
140
|
+
logger.warning(
|
141
|
+
f"Loaded command '{command_name}' conflicts with custom command, skipping"
|
142
|
+
)
|
130
143
|
return False
|
131
|
-
|
144
|
+
|
132
145
|
if command_name in self._builtin_commands:
|
133
|
-
logger.warning(
|
146
|
+
logger.warning(
|
147
|
+
f"Loaded command '{command_name}' conflicts with built-in command, skipping"
|
148
|
+
)
|
134
149
|
return False
|
135
|
-
|
150
|
+
|
136
151
|
# Check for conflicts within loaded commands
|
137
152
|
if command_name in self._loaded_commands:
|
138
|
-
logger.warning(
|
153
|
+
logger.warning(
|
154
|
+
f"Loaded command '{command_name}' already exists, skipping duplicate"
|
155
|
+
)
|
139
156
|
return False
|
140
|
-
|
157
|
+
|
141
158
|
try:
|
142
159
|
self._register_command(command, self._loaded_commands, "loaded")
|
143
160
|
return True
|
144
161
|
except ValueError:
|
145
162
|
return False
|
146
|
-
|
147
|
-
def _register_command(
|
148
|
-
|
149
|
-
|
163
|
+
|
164
|
+
def _register_command(
|
165
|
+
self,
|
166
|
+
command: Union[Type[Command], Command],
|
167
|
+
target_dict: Dict[str, Type[Command]],
|
168
|
+
command_type: str,
|
169
|
+
) -> None:
|
150
170
|
"""
|
151
171
|
Internal method to register a command in the specified dictionary.
|
152
172
|
|
@@ -166,21 +186,25 @@ class CommandRegistry:
|
|
166
186
|
command_class = command.__class__
|
167
187
|
command_instance = command
|
168
188
|
else:
|
169
|
-
raise ValueError(
|
170
|
-
|
189
|
+
raise ValueError(
|
190
|
+
f"Invalid command type: {type(command)}. Expected Command class or instance."
|
191
|
+
)
|
192
|
+
|
171
193
|
command_name = self._get_command_name(command_class)
|
172
|
-
|
194
|
+
|
173
195
|
if command_name in target_dict:
|
174
|
-
raise ValueError(
|
175
|
-
|
196
|
+
raise ValueError(
|
197
|
+
f"{command_type.capitalize()} command '{command_name}' is already registered"
|
198
|
+
)
|
199
|
+
|
176
200
|
logger.debug(f"Registering {command_type} command: {command_name}")
|
177
201
|
target_dict[command_name] = command_class
|
178
|
-
|
202
|
+
|
179
203
|
# Store instance if provided
|
180
204
|
if command_instance:
|
181
205
|
logger.debug(f"Storing {command_type} instance for command: {command_name}")
|
182
206
|
self._instances[command_name] = command_instance
|
183
|
-
|
207
|
+
|
184
208
|
def _get_command_name(self, command_class: Type[Command]) -> str:
|
185
209
|
"""
|
186
210
|
Get command name from command class.
|
@@ -198,90 +222,91 @@ class CommandRegistry:
|
|
198
222
|
command_name = command_name[:-7] # Remove "command" suffix
|
199
223
|
else:
|
200
224
|
command_name = command_class.name
|
201
|
-
|
202
|
-
return command_name
|
203
|
-
|
204
225
|
|
205
|
-
|
226
|
+
return command_name
|
206
227
|
|
207
|
-
|
208
228
|
def load_command_from_source(self, source: str) -> Dict[str, Any]:
|
209
229
|
"""
|
210
230
|
Universal command loader - handles local files, URLs, and remote registry.
|
211
|
-
|
231
|
+
|
212
232
|
Args:
|
213
233
|
source: Source string - local path, URL, or command name from registry
|
214
|
-
|
234
|
+
|
215
235
|
Returns:
|
216
236
|
Dictionary with loading result information
|
217
237
|
"""
|
218
238
|
logger.info(f"Loading command from source: {source}")
|
219
|
-
|
239
|
+
|
220
240
|
# Parse source to determine type
|
221
241
|
parsed_url = urllib.parse.urlparse(source)
|
222
|
-
is_url = parsed_url.scheme in (
|
223
|
-
|
242
|
+
is_url = parsed_url.scheme in ("http", "https")
|
243
|
+
|
224
244
|
if is_url:
|
225
245
|
# URL - always download and load
|
226
246
|
return self._load_command_from_url(source)
|
227
247
|
else:
|
228
248
|
# Local path or command name - check remote registry first
|
229
249
|
return self._load_command_with_registry_check(source)
|
230
|
-
|
250
|
+
|
231
251
|
def _load_command_with_registry_check(self, source: str) -> Dict[str, Any]:
|
232
252
|
"""
|
233
253
|
Load command with remote registry check.
|
234
|
-
|
254
|
+
|
235
255
|
Args:
|
236
256
|
source: Local path or command name
|
237
|
-
|
257
|
+
|
238
258
|
Returns:
|
239
259
|
Dictionary with loading result information
|
240
260
|
"""
|
241
261
|
try:
|
242
262
|
from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
|
243
|
-
|
263
|
+
|
244
264
|
# Get remote registry
|
245
265
|
plugin_servers = config.get("commands.plugin_servers", [])
|
246
266
|
catalog_dir = "./catalog"
|
247
|
-
|
267
|
+
|
248
268
|
if plugin_servers:
|
249
269
|
# Initialize catalog manager
|
250
270
|
catalog_manager = CatalogManager(catalog_dir)
|
251
|
-
|
271
|
+
|
252
272
|
# Check if source is a command name in registry
|
253
|
-
if not os.path.exists(source) and not source.endswith(
|
273
|
+
if not os.path.exists(source) and not source.endswith("_command.py"):
|
254
274
|
# Try to find in remote registry
|
255
275
|
for server_url in plugin_servers:
|
256
276
|
try:
|
257
|
-
server_catalog = catalog_manager.get_catalog_from_server(
|
277
|
+
server_catalog = catalog_manager.get_catalog_from_server(
|
278
|
+
server_url
|
279
|
+
)
|
258
280
|
if source in server_catalog:
|
259
281
|
server_cmd = server_catalog[source]
|
260
282
|
# Download from registry
|
261
|
-
if catalog_manager._download_command(
|
262
|
-
source
|
283
|
+
if catalog_manager._download_command(
|
284
|
+
source, server_cmd
|
285
|
+
):
|
286
|
+
source = str(
|
287
|
+
catalog_manager.commands_dir
|
288
|
+
/ f"{source}_command.py"
|
289
|
+
)
|
263
290
|
break
|
264
291
|
except Exception as e:
|
265
|
-
logger.warning(
|
266
|
-
|
292
|
+
logger.warning(
|
293
|
+
f"Failed to check registry {server_url}: {e}"
|
294
|
+
)
|
295
|
+
|
267
296
|
# Load from local file
|
268
297
|
return self._load_command_from_file(source)
|
269
|
-
|
298
|
+
|
270
299
|
except Exception as e:
|
271
300
|
logger.error(f"Failed to load command with registry check: {e}")
|
272
|
-
return {
|
273
|
-
|
274
|
-
"commands_loaded": 0,
|
275
|
-
"error": str(e)
|
276
|
-
}
|
277
|
-
|
301
|
+
return {"success": False, "commands_loaded": 0, "error": str(e)}
|
302
|
+
|
278
303
|
def _load_command_from_url(self, url: str) -> Dict[str, Any]:
|
279
304
|
"""
|
280
305
|
Load command from HTTP/HTTPS URL.
|
281
|
-
|
306
|
+
|
282
307
|
Args:
|
283
308
|
url: URL to load command from
|
284
|
-
|
309
|
+
|
285
310
|
Returns:
|
286
311
|
Dictionary with loading result information
|
287
312
|
"""
|
@@ -292,24 +317,26 @@ class CommandRegistry:
|
|
292
317
|
"success": False,
|
293
318
|
"error": error_msg,
|
294
319
|
"commands_loaded": 0,
|
295
|
-
"source": url
|
320
|
+
"source": url,
|
296
321
|
}
|
297
|
-
|
322
|
+
|
298
323
|
try:
|
299
324
|
logger.debug(f"Downloading command from URL: {url}")
|
300
325
|
response = requests.get(url, timeout=30)
|
301
326
|
response.raise_for_status()
|
302
|
-
|
327
|
+
|
303
328
|
# Get filename from URL or use default
|
304
329
|
filename = os.path.basename(urllib.parse.urlparse(url).path)
|
305
|
-
if not filename or not filename.endswith(
|
306
|
-
filename =
|
307
|
-
|
330
|
+
if not filename or not filename.endswith(".py"):
|
331
|
+
filename = "remote_command.py"
|
332
|
+
|
308
333
|
# Create temporary file
|
309
|
-
with tempfile.NamedTemporaryFile(
|
334
|
+
with tempfile.NamedTemporaryFile(
|
335
|
+
mode="w", suffix=".py", delete=False
|
336
|
+
) as temp_file:
|
310
337
|
temp_file.write(response.text)
|
311
338
|
temp_file_path = temp_file.name
|
312
|
-
|
339
|
+
|
313
340
|
try:
|
314
341
|
# Load command from temporary file
|
315
342
|
result = self._load_command_from_file(temp_file_path, is_temporary=True)
|
@@ -320,8 +347,10 @@ class CommandRegistry:
|
|
320
347
|
try:
|
321
348
|
os.unlink(temp_file_path)
|
322
349
|
except Exception as e:
|
323
|
-
logger.warning(
|
324
|
-
|
350
|
+
logger.warning(
|
351
|
+
f"Failed to clean up temporary file {temp_file_path}: {e}"
|
352
|
+
)
|
353
|
+
|
325
354
|
except Exception as e:
|
326
355
|
error_msg = f"Failed to load command from URL {url}: {e}"
|
327
356
|
logger.error(error_msg)
|
@@ -329,17 +358,19 @@ class CommandRegistry:
|
|
329
358
|
"success": False,
|
330
359
|
"error": error_msg,
|
331
360
|
"commands_loaded": 0,
|
332
|
-
"source": url
|
361
|
+
"source": url,
|
333
362
|
}
|
334
|
-
|
335
|
-
def _load_command_from_file(
|
363
|
+
|
364
|
+
def _load_command_from_file(
|
365
|
+
self, file_path: str, is_temporary: bool = False
|
366
|
+
) -> Dict[str, Any]:
|
336
367
|
"""
|
337
368
|
Load command from local file.
|
338
|
-
|
369
|
+
|
339
370
|
Args:
|
340
371
|
file_path: Path to command file
|
341
372
|
is_temporary: Whether this is a temporary file (for cleanup)
|
342
|
-
|
373
|
+
|
343
374
|
Returns:
|
344
375
|
Dictionary with loading result information
|
345
376
|
"""
|
@@ -350,41 +381,43 @@ class CommandRegistry:
|
|
350
381
|
"success": False,
|
351
382
|
"error": error_msg,
|
352
383
|
"commands_loaded": 0,
|
353
|
-
"source": file_path
|
384
|
+
"source": file_path,
|
354
385
|
}
|
355
|
-
|
386
|
+
|
356
387
|
# For temporary files (downloaded from URL), we don't enforce the _command.py naming
|
357
388
|
# since the original filename is preserved in the URL
|
358
|
-
if not is_temporary and not file_path.endswith(
|
389
|
+
if not is_temporary and not file_path.endswith("_command.py"):
|
359
390
|
error_msg = f"Command file must end with '_command.py': {file_path}"
|
360
391
|
logger.error(error_msg)
|
361
392
|
return {
|
362
393
|
"success": False,
|
363
394
|
"error": error_msg,
|
364
395
|
"commands_loaded": 0,
|
365
|
-
"source": file_path
|
396
|
+
"source": file_path,
|
366
397
|
}
|
367
|
-
|
398
|
+
|
368
399
|
try:
|
369
400
|
module_name = os.path.basename(file_path)[:-3] # Remove .py extension
|
370
401
|
logger.debug(f"Loading command from file: {file_path}")
|
371
|
-
|
402
|
+
|
372
403
|
# Load module from file
|
373
404
|
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
374
405
|
if spec and spec.loader:
|
375
406
|
module = importlib.util.module_from_spec(spec)
|
376
407
|
spec.loader.exec_module(module)
|
377
|
-
|
408
|
+
|
378
409
|
commands_loaded = 0
|
379
410
|
loaded_commands = []
|
380
|
-
|
411
|
+
|
381
412
|
# Find command classes in the module
|
382
413
|
for name, obj in inspect.getmembers(module):
|
383
|
-
if (
|
384
|
-
|
385
|
-
obj
|
386
|
-
|
387
|
-
|
414
|
+
if (
|
415
|
+
inspect.isclass(obj)
|
416
|
+
and issubclass(obj, Command)
|
417
|
+
and obj != Command
|
418
|
+
and not inspect.isabstract(obj)
|
419
|
+
):
|
420
|
+
|
388
421
|
command_name = self._get_command_name(obj)
|
389
422
|
if self.register_loaded(cast(Type[Command], obj)):
|
390
423
|
commands_loaded += 1
|
@@ -392,12 +425,12 @@ class CommandRegistry:
|
|
392
425
|
logger.debug(f"Loaded command: {command_name}")
|
393
426
|
else:
|
394
427
|
logger.debug(f"Skipped command: {command_name}")
|
395
|
-
|
428
|
+
|
396
429
|
return {
|
397
430
|
"success": True,
|
398
431
|
"commands_loaded": commands_loaded,
|
399
432
|
"loaded_commands": loaded_commands,
|
400
|
-
"source": file_path
|
433
|
+
"source": file_path,
|
401
434
|
}
|
402
435
|
else:
|
403
436
|
error_msg = f"Failed to create module spec for: {file_path}"
|
@@ -406,9 +439,9 @@ class CommandRegistry:
|
|
406
439
|
"success": False,
|
407
440
|
"error": error_msg,
|
408
441
|
"commands_loaded": 0,
|
409
|
-
"source": file_path
|
442
|
+
"source": file_path,
|
410
443
|
}
|
411
|
-
|
444
|
+
|
412
445
|
except Exception as e:
|
413
446
|
error_msg = f"Error loading command from file {file_path}: {e}"
|
414
447
|
logger.error(error_msg)
|
@@ -416,55 +449,49 @@ class CommandRegistry:
|
|
416
449
|
"success": False,
|
417
450
|
"error": error_msg,
|
418
451
|
"commands_loaded": 0,
|
419
|
-
"source": file_path
|
452
|
+
"source": file_path,
|
420
453
|
}
|
421
|
-
|
454
|
+
|
422
455
|
def unload_command(self, command_name: str) -> Dict[str, Any]:
|
423
456
|
"""
|
424
457
|
Unload a loaded command from registry.
|
425
|
-
|
458
|
+
|
426
459
|
Args:
|
427
460
|
command_name: Name of the command to unload
|
428
|
-
|
461
|
+
|
429
462
|
Returns:
|
430
463
|
Dictionary with unloading result information
|
431
464
|
"""
|
432
465
|
logger.info(f"Unloading command: {command_name}")
|
433
|
-
|
466
|
+
|
434
467
|
# Check if command exists in loaded commands
|
435
468
|
if command_name not in self._loaded_commands:
|
436
|
-
error_msg =
|
469
|
+
error_msg = (
|
470
|
+
f"Command '{command_name}' is not a loaded command or does not exist"
|
471
|
+
)
|
437
472
|
logger.warning(error_msg)
|
438
|
-
return {
|
439
|
-
|
440
|
-
"error": error_msg,
|
441
|
-
"command_name": command_name
|
442
|
-
}
|
443
|
-
|
473
|
+
return {"success": False, "error": error_msg, "command_name": command_name}
|
474
|
+
|
444
475
|
try:
|
445
476
|
# Remove from loaded commands
|
446
477
|
del self._loaded_commands[command_name]
|
447
|
-
|
478
|
+
|
448
479
|
# Remove instance if exists
|
449
480
|
if command_name in self._instances:
|
450
481
|
del self._instances[command_name]
|
451
|
-
|
482
|
+
|
452
483
|
logger.info(f"Successfully unloaded command: {command_name}")
|
453
484
|
return {
|
454
485
|
"success": True,
|
455
486
|
"command_name": command_name,
|
456
|
-
"message": f"Command '{command_name}' unloaded successfully"
|
487
|
+
"message": f"Command '{command_name}' unloaded successfully",
|
457
488
|
}
|
458
|
-
|
489
|
+
|
459
490
|
except Exception as e:
|
460
491
|
error_msg = f"Failed to unload command '{command_name}': {e}"
|
461
492
|
logger.error(error_msg)
|
462
|
-
return {
|
463
|
-
|
464
|
-
"error": error_msg,
|
465
|
-
"command_name": command_name
|
466
|
-
}
|
467
|
-
|
493
|
+
return {"success": False, "error": error_msg, "command_name": command_name}
|
494
|
+
|
468
495
|
def command_exists(self, command_name: str) -> bool:
|
469
496
|
"""
|
470
497
|
Check if command exists with priority order.
|
@@ -475,10 +502,12 @@ class CommandRegistry:
|
|
475
502
|
Returns:
|
476
503
|
True if command exists, False otherwise.
|
477
504
|
"""
|
478
|
-
return (
|
479
|
-
|
480
|
-
|
481
|
-
|
505
|
+
return (
|
506
|
+
command_name in self._custom_commands
|
507
|
+
or command_name in self._builtin_commands
|
508
|
+
or command_name in self._loaded_commands
|
509
|
+
)
|
510
|
+
|
482
511
|
def get_command(self, command_name: str) -> Type[Command]:
|
483
512
|
"""
|
484
513
|
Get command class with priority order.
|
@@ -501,47 +530,49 @@ class CommandRegistry:
|
|
501
530
|
return self._loaded_commands[command_name]
|
502
531
|
else:
|
503
532
|
raise NotFoundError(f"Command '{command_name}' not found")
|
504
|
-
|
533
|
+
|
505
534
|
def get_command_instance(self, command_name: str) -> Command:
|
506
535
|
"""
|
507
536
|
Get command instance by name. If instance doesn't exist, creates new one.
|
508
|
-
|
537
|
+
|
509
538
|
Args:
|
510
539
|
command_name: Command name
|
511
|
-
|
540
|
+
|
512
541
|
Returns:
|
513
542
|
Command instance
|
514
|
-
|
543
|
+
|
515
544
|
Raises:
|
516
545
|
NotFoundError: If command is not found
|
517
546
|
"""
|
518
547
|
if not self.command_exists(command_name):
|
519
548
|
raise NotFoundError(f"Command '{command_name}' not found")
|
520
|
-
|
549
|
+
|
521
550
|
# Return existing instance if available
|
522
551
|
if command_name in self._instances:
|
523
552
|
return self._instances[command_name]
|
524
|
-
|
553
|
+
|
525
554
|
# Otherwise create new instance
|
526
555
|
try:
|
527
556
|
command_class = self.get_command(command_name)
|
528
557
|
return command_class()
|
529
558
|
except Exception as e:
|
530
559
|
logger.error(f"Failed to create instance of '{command_name}': {e}")
|
531
|
-
raise ValueError(
|
532
|
-
|
560
|
+
raise ValueError(
|
561
|
+
f"Command '{command_name}' requires dependencies but was registered as class. Register an instance instead."
|
562
|
+
) from e
|
563
|
+
|
533
564
|
def has_instance(self, command_name: str) -> bool:
|
534
565
|
"""
|
535
566
|
Check if command has a registered instance.
|
536
|
-
|
567
|
+
|
537
568
|
Args:
|
538
569
|
command_name: Command name
|
539
|
-
|
570
|
+
|
540
571
|
Returns:
|
541
572
|
True if command has instance, False otherwise
|
542
573
|
"""
|
543
574
|
return command_name in self._instances
|
544
|
-
|
575
|
+
|
545
576
|
def get_all_commands(self) -> Dict[str, Type[Command]]:
|
546
577
|
"""
|
547
578
|
Get all registered commands with priority order.
|
@@ -550,23 +581,23 @@ class CommandRegistry:
|
|
550
581
|
Dictionary with command names and their classes.
|
551
582
|
"""
|
552
583
|
all_commands = {}
|
553
|
-
|
584
|
+
|
554
585
|
# Add commands in priority order: custom -> built-in -> loaded
|
555
586
|
# Custom commands override built-in and loaded
|
556
587
|
all_commands.update(self._custom_commands)
|
557
|
-
|
588
|
+
|
558
589
|
# Built-in commands (only if not overridden by custom)
|
559
590
|
for name, command_class in self._builtin_commands.items():
|
560
591
|
if name not in all_commands:
|
561
592
|
all_commands[name] = command_class
|
562
|
-
|
593
|
+
|
563
594
|
# Loaded commands (only if not overridden by custom or built-in)
|
564
595
|
for name, command_class in self._loaded_commands.items():
|
565
596
|
if name not in all_commands:
|
566
597
|
all_commands[name] = command_class
|
567
|
-
|
598
|
+
|
568
599
|
return all_commands
|
569
|
-
|
600
|
+
|
570
601
|
def get_commands_by_type(self) -> Dict[str, Dict[str, Type[Command]]]:
|
571
602
|
"""
|
572
603
|
Get commands grouped by type.
|
@@ -577,25 +608,25 @@ class CommandRegistry:
|
|
577
608
|
return {
|
578
609
|
"custom": self._custom_commands,
|
579
610
|
"builtin": self._builtin_commands,
|
580
|
-
"loaded": self._loaded_commands
|
611
|
+
"loaded": self._loaded_commands,
|
581
612
|
}
|
582
|
-
|
613
|
+
|
583
614
|
def get_all_metadata(self) -> Dict[str, Dict[str, Any]]:
|
584
615
|
"""
|
585
616
|
Get metadata for all registered commands.
|
586
|
-
|
617
|
+
|
587
618
|
Returns:
|
588
619
|
Dictionary with command names as keys and metadata as values.
|
589
620
|
"""
|
590
621
|
metadata = {}
|
591
|
-
|
622
|
+
|
592
623
|
# Get all commands with priority order
|
593
624
|
all_commands = self.get_all_commands()
|
594
|
-
|
625
|
+
|
595
626
|
for command_name, command_class in all_commands.items():
|
596
627
|
try:
|
597
628
|
# Get command metadata
|
598
|
-
if hasattr(command_class,
|
629
|
+
if hasattr(command_class, "get_metadata"):
|
599
630
|
metadata[command_name] = command_class.get_metadata()
|
600
631
|
else:
|
601
632
|
# Fallback metadata
|
@@ -603,17 +634,21 @@ class CommandRegistry:
|
|
603
634
|
"name": command_name,
|
604
635
|
"class": command_class.__name__,
|
605
636
|
"module": command_class.__module__,
|
606
|
-
"description": getattr(
|
637
|
+
"description": getattr(
|
638
|
+
command_class, "__doc__", "No description available"
|
639
|
+
),
|
607
640
|
}
|
608
641
|
except Exception as e:
|
609
|
-
logger.warning(
|
642
|
+
logger.warning(
|
643
|
+
f"Failed to get metadata for command '{command_name}': {e}"
|
644
|
+
)
|
610
645
|
metadata[command_name] = {
|
611
646
|
"name": command_name,
|
612
|
-
"error": f"Failed to get metadata: {str(e)}"
|
647
|
+
"error": f"Failed to get metadata: {str(e)}",
|
613
648
|
}
|
614
|
-
|
649
|
+
|
615
650
|
return metadata
|
616
|
-
|
651
|
+
|
617
652
|
def clear(self) -> None:
|
618
653
|
"""
|
619
654
|
Clear all registered commands.
|
@@ -623,21 +658,22 @@ class CommandRegistry:
|
|
623
658
|
self._custom_commands.clear()
|
624
659
|
self._loaded_commands.clear()
|
625
660
|
self._instances.clear()
|
626
|
-
|
627
|
-
|
661
|
+
|
628
662
|
async def reload_system(self, config_path: Optional[str] = None) -> Dict[str, Any]:
|
629
663
|
"""
|
630
664
|
Universal method for system initialization and reload.
|
631
665
|
This method should be used both at startup and during reload.
|
632
|
-
|
666
|
+
|
633
667
|
Args:
|
634
668
|
config_path: Path to configuration file. If None, uses default or existing path.
|
635
|
-
|
669
|
+
|
636
670
|
Returns:
|
637
671
|
Dictionary with initialization information.
|
638
672
|
"""
|
639
|
-
logger.info(
|
640
|
-
|
673
|
+
logger.info(
|
674
|
+
f"🔄 Starting system reload with config: {config_path or 'default'}"
|
675
|
+
)
|
676
|
+
|
641
677
|
# Step 1: Load configuration
|
642
678
|
try:
|
643
679
|
if config_path:
|
@@ -646,52 +682,57 @@ class CommandRegistry:
|
|
646
682
|
else:
|
647
683
|
config.load_config()
|
648
684
|
logger.info("✅ Configuration loaded from default path")
|
649
|
-
|
685
|
+
|
650
686
|
config_reloaded = True
|
651
687
|
except Exception as e:
|
652
688
|
logger.error(f"❌ Failed to load configuration: {e}")
|
653
689
|
config_reloaded = False
|
654
|
-
|
690
|
+
|
655
691
|
# Step 2: Initialize logging with configuration
|
656
692
|
try:
|
657
693
|
from mcp_proxy_adapter.core.logging import setup_logging
|
694
|
+
|
658
695
|
setup_logging()
|
659
696
|
logger.info("✅ Logging initialized with configuration")
|
660
697
|
except Exception as e:
|
661
698
|
logger.error(f"❌ Failed to initialize logging: {e}")
|
662
|
-
|
699
|
+
|
663
700
|
# Step 2.5: Reload protocol manager configuration
|
664
701
|
try:
|
665
702
|
from mcp_proxy_adapter.core.protocol_manager import protocol_manager
|
703
|
+
|
666
704
|
protocol_manager.reload_config()
|
667
705
|
logger.info("✅ Protocol manager configuration reloaded")
|
668
706
|
except Exception as e:
|
669
707
|
logger.error(f"❌ Failed to reload protocol manager: {e}")
|
670
|
-
|
708
|
+
|
671
709
|
# Step 3: Clear all commands (always clear for consistency)
|
672
710
|
self.clear()
|
673
|
-
|
711
|
+
|
674
712
|
# Step 4: Execute before init hooks
|
675
713
|
try:
|
676
714
|
hooks.execute_before_init_hooks()
|
677
715
|
except Exception as e:
|
678
716
|
logger.error(f"❌ Failed to execute before init hooks: {e}")
|
679
|
-
|
717
|
+
|
680
718
|
# Step 5: Register built-in commands
|
681
719
|
try:
|
682
|
-
from mcp_proxy_adapter.commands.builtin_commands import
|
720
|
+
from mcp_proxy_adapter.commands.builtin_commands import (
|
721
|
+
register_builtin_commands,
|
722
|
+
)
|
723
|
+
|
683
724
|
builtin_commands_count = register_builtin_commands()
|
684
725
|
except Exception as e:
|
685
726
|
logger.error(f"❌ Failed to register built-in commands: {e}")
|
686
727
|
builtin_commands_count = 0
|
687
|
-
|
728
|
+
|
688
729
|
# Step 6: Execute custom commands hooks
|
689
730
|
try:
|
690
731
|
custom_commands_count = hooks.execute_custom_commands_hooks(self)
|
691
732
|
except Exception as e:
|
692
733
|
logger.error(f"❌ Failed to execute custom commands hooks: {e}")
|
693
734
|
custom_commands_count = 0
|
694
|
-
|
735
|
+
|
695
736
|
# Step 7: Load all commands (built-in, custom, loadable)
|
696
737
|
try:
|
697
738
|
load_result = self._load_all_commands()
|
@@ -701,52 +742,59 @@ class CommandRegistry:
|
|
701
742
|
logger.error(f"❌ Failed to load commands: {e}")
|
702
743
|
remote_commands_count = 0
|
703
744
|
loaded_commands_count = 0
|
704
|
-
|
745
|
+
|
705
746
|
# Step 8: Execute after init hooks
|
706
747
|
try:
|
707
748
|
hooks.execute_after_init_hooks()
|
708
749
|
except Exception as e:
|
709
750
|
logger.error(f"❌ Failed to execute after init hooks: {e}")
|
710
|
-
|
751
|
+
|
711
752
|
# Step 9: Register with proxy if enabled
|
712
753
|
proxy_registration_success = False
|
713
754
|
try:
|
714
|
-
from mcp_proxy_adapter.core.proxy_registration import
|
715
|
-
|
755
|
+
from mcp_proxy_adapter.core.proxy_registration import (
|
756
|
+
register_with_proxy,
|
757
|
+
initialize_proxy_registration,
|
758
|
+
)
|
759
|
+
|
716
760
|
# Initialize proxy registration manager with current config
|
717
761
|
initialize_proxy_registration(config.get_all())
|
718
|
-
|
762
|
+
|
719
763
|
# Get server configuration
|
720
764
|
server_config = config.get("server", {})
|
721
765
|
server_host = server_config.get("host", "0.0.0.0")
|
722
766
|
server_port = server_config.get("port", 8000)
|
723
|
-
|
767
|
+
|
724
768
|
# Determine server URL based on SSL configuration
|
725
769
|
ssl_config = config.get("ssl", {})
|
726
770
|
if ssl_config.get("enabled", False):
|
727
771
|
protocol = "https"
|
728
772
|
else:
|
729
773
|
protocol = "http"
|
730
|
-
|
774
|
+
|
731
775
|
# Use localhost for external access if host is 0.0.0.0
|
732
776
|
if server_host == "0.0.0.0":
|
733
777
|
server_host = "localhost"
|
734
|
-
|
778
|
+
|
735
779
|
server_url = f"{protocol}://{server_host}:{server_port}"
|
736
|
-
|
780
|
+
|
737
781
|
# Attempt proxy registration
|
738
782
|
proxy_registration_success = await register_with_proxy(server_url)
|
739
783
|
if proxy_registration_success:
|
740
|
-
logger.info(
|
784
|
+
logger.info(
|
785
|
+
"✅ Proxy registration completed successfully during system reload"
|
786
|
+
)
|
741
787
|
else:
|
742
|
-
logger.info(
|
743
|
-
|
788
|
+
logger.info(
|
789
|
+
"ℹ️ Proxy registration is disabled or failed during system reload"
|
790
|
+
)
|
791
|
+
|
744
792
|
except Exception as e:
|
745
793
|
logger.error(f"❌ Failed to register with proxy during system reload: {e}")
|
746
|
-
|
794
|
+
|
747
795
|
# Get final counts
|
748
796
|
total_commands = len(self.get_all_commands())
|
749
|
-
|
797
|
+
|
750
798
|
result = {
|
751
799
|
"config_reloaded": config_reloaded,
|
752
800
|
"builtin_commands": builtin_commands_count,
|
@@ -754,23 +802,23 @@ class CommandRegistry:
|
|
754
802
|
"loaded_commands": loaded_commands_count,
|
755
803
|
"remote_commands": remote_commands_count,
|
756
804
|
"total_commands": total_commands,
|
757
|
-
"proxy_registration_success": proxy_registration_success
|
805
|
+
"proxy_registration_success": proxy_registration_success,
|
758
806
|
}
|
759
|
-
|
807
|
+
|
760
808
|
logger.info(f"✅ System reload completed: {result}")
|
761
809
|
return result
|
762
|
-
|
810
|
+
|
763
811
|
def _load_all_commands(self) -> Dict[str, Any]:
|
764
812
|
"""
|
765
813
|
Universal command loader - handles all command types.
|
766
|
-
|
814
|
+
|
767
815
|
Returns:
|
768
816
|
Dictionary with loading results
|
769
817
|
"""
|
770
818
|
try:
|
771
819
|
remote_commands = 0
|
772
820
|
loaded_commands = 0
|
773
|
-
|
821
|
+
|
774
822
|
# 1. Load commands from directory (if configured)
|
775
823
|
commands_directory = config.get("commands.commands_directory")
|
776
824
|
if commands_directory and os.path.exists(commands_directory):
|
@@ -782,18 +830,25 @@ class CommandRegistry:
|
|
782
830
|
loaded_commands += result.get("commands_loaded", 0)
|
783
831
|
except Exception as e:
|
784
832
|
logger.error(f"Failed to load command from {file_path}: {e}")
|
785
|
-
|
833
|
+
|
786
834
|
# 2. Load commands from plugin servers (if configured)
|
787
835
|
plugin_servers = config.get("commands.plugin_servers", [])
|
788
836
|
if plugin_servers:
|
789
|
-
logger.info(
|
837
|
+
logger.info(
|
838
|
+
f"Loading commands from {len(plugin_servers)} plugin servers"
|
839
|
+
)
|
790
840
|
for server_url in plugin_servers:
|
791
841
|
try:
|
792
842
|
# Load catalog from server
|
793
|
-
from mcp_proxy_adapter.commands.catalog_manager import
|
843
|
+
from mcp_proxy_adapter.commands.catalog_manager import (
|
844
|
+
CatalogManager,
|
845
|
+
)
|
846
|
+
|
794
847
|
catalog_manager = CatalogManager("./catalog")
|
795
|
-
server_catalog = catalog_manager.get_catalog_from_server(
|
796
|
-
|
848
|
+
server_catalog = catalog_manager.get_catalog_from_server(
|
849
|
+
server_url
|
850
|
+
)
|
851
|
+
|
797
852
|
# Load each command from catalog
|
798
853
|
for command_name, server_cmd in server_catalog.items():
|
799
854
|
try:
|
@@ -801,72 +856,66 @@ class CommandRegistry:
|
|
801
856
|
if result.get("success"):
|
802
857
|
remote_commands += result.get("commands_loaded", 0)
|
803
858
|
except Exception as e:
|
804
|
-
logger.error(
|
805
|
-
|
859
|
+
logger.error(
|
860
|
+
f"Failed to load command {command_name}: {e}"
|
861
|
+
)
|
862
|
+
|
806
863
|
except Exception as e:
|
807
864
|
logger.error(f"Failed to load from server {server_url}: {e}")
|
808
|
-
|
865
|
+
|
809
866
|
return {
|
810
867
|
"remote_commands": remote_commands,
|
811
|
-
"loaded_commands": loaded_commands
|
868
|
+
"loaded_commands": loaded_commands,
|
812
869
|
}
|
813
|
-
|
870
|
+
|
814
871
|
except Exception as e:
|
815
872
|
logger.error(f"Failed to load all commands: {e}")
|
816
|
-
return {
|
817
|
-
"remote_commands": 0,
|
818
|
-
"loaded_commands": 0,
|
819
|
-
"error": str(e)
|
820
|
-
}
|
821
|
-
|
873
|
+
return {"remote_commands": 0, "loaded_commands": 0, "error": str(e)}
|
822
874
|
|
823
875
|
def get_all_commands_info(self) -> Dict[str, Any]:
|
824
876
|
"""
|
825
877
|
Get information about all registered commands.
|
826
|
-
|
878
|
+
|
827
879
|
Returns:
|
828
880
|
Dictionary with command information
|
829
881
|
"""
|
830
882
|
commands_info = {}
|
831
|
-
|
883
|
+
|
832
884
|
# Get all commands
|
833
885
|
all_commands = self.get_all_commands()
|
834
|
-
|
886
|
+
|
835
887
|
for command_name, command_class in all_commands.items():
|
836
888
|
try:
|
837
889
|
# Get command metadata
|
838
890
|
metadata = command_class.get_metadata()
|
839
|
-
|
891
|
+
|
840
892
|
# Get command schema
|
841
893
|
schema = command_class.get_schema()
|
842
|
-
|
894
|
+
|
843
895
|
commands_info[command_name] = {
|
844
896
|
"name": command_name,
|
845
897
|
"metadata": metadata,
|
846
898
|
"schema": schema,
|
847
|
-
"type": self._get_command_type(command_name)
|
899
|
+
"type": self._get_command_type(command_name),
|
848
900
|
}
|
849
|
-
|
901
|
+
|
850
902
|
except Exception as e:
|
851
903
|
logger.warning(f"Failed to get info for command {command_name}: {e}")
|
852
904
|
commands_info[command_name] = {
|
853
905
|
"name": command_name,
|
854
906
|
"error": str(e),
|
855
|
-
"type": self._get_command_type(command_name)
|
907
|
+
"type": self._get_command_type(command_name),
|
856
908
|
}
|
857
|
-
|
858
|
-
return {
|
859
|
-
|
860
|
-
"total": len(commands_info)
|
861
|
-
}
|
862
|
-
|
909
|
+
|
910
|
+
return {"commands": commands_info, "total": len(commands_info)}
|
911
|
+
|
863
912
|
def get_command_info(self, command_name: str) -> Optional[Dict[str, Any]]:
|
864
913
|
"""
|
865
914
|
Get information about a specific command.
|
866
|
-
|
915
|
+
|
867
916
|
Args:
|
868
917
|
command_name: Name of the command
|
869
|
-
|
918
|
+
|
870
919
|
Returns:
|
871
920
|
Dictionary with command information or None if not found
|
872
921
|
"""
|
@@ -874,38 +923,38 @@ class CommandRegistry:
|
|
874
923
|
# Check if command exists
|
875
924
|
if not self.command_exists(command_name):
|
876
925
|
return None
|
877
|
-
|
926
|
+
|
878
927
|
# Get command class
|
879
928
|
command_class = self.get_command(command_name)
|
880
|
-
|
929
|
+
|
881
930
|
# Get command metadata
|
882
931
|
metadata = command_class.get_metadata()
|
883
|
-
|
932
|
+
|
884
933
|
# Get command schema
|
885
934
|
schema = command_class.get_schema()
|
886
|
-
|
935
|
+
|
887
936
|
return {
|
888
937
|
"name": command_name,
|
889
938
|
"metadata": metadata,
|
890
939
|
"schema": schema,
|
891
|
-
"type": self._get_command_type(command_name)
|
940
|
+
"type": self._get_command_type(command_name),
|
892
941
|
}
|
893
|
-
|
942
|
+
|
894
943
|
except Exception as e:
|
895
944
|
logger.warning(f"Failed to get info for command {command_name}: {e}")
|
896
945
|
return {
|
897
946
|
"name": command_name,
|
898
947
|
"error": str(e),
|
899
|
-
"type": self._get_command_type(command_name)
|
948
|
+
"type": self._get_command_type(command_name),
|
900
949
|
}
|
901
|
-
|
950
|
+
|
902
951
|
def _get_command_type(self, command_name: str) -> str:
|
903
952
|
"""
|
904
953
|
Get the type of a command (built-in, custom, or loaded).
|
905
|
-
|
954
|
+
|
906
955
|
Args:
|
907
956
|
command_name: Name of the command
|
908
|
-
|
957
|
+
|
909
958
|
Returns:
|
910
959
|
Command type string
|
911
960
|
"""
|