mcp-proxy-adapter 3.1.5__py3-none-any.whl → 4.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. mcp_proxy_adapter/api/app.py +86 -27
  2. mcp_proxy_adapter/api/handlers.py +1 -1
  3. mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
  4. mcp_proxy_adapter/api/tool_integration.py +5 -2
  5. mcp_proxy_adapter/api/tools.py +3 -3
  6. mcp_proxy_adapter/commands/base.py +19 -1
  7. mcp_proxy_adapter/commands/command_registry.py +258 -6
  8. mcp_proxy_adapter/commands/help_command.py +54 -65
  9. mcp_proxy_adapter/commands/hooks.py +260 -0
  10. mcp_proxy_adapter/commands/reload_command.py +211 -0
  11. mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
  12. mcp_proxy_adapter/commands/settings_command.py +189 -0
  13. mcp_proxy_adapter/config.py +16 -1
  14. mcp_proxy_adapter/core/__init__.py +44 -0
  15. mcp_proxy_adapter/core/logging.py +87 -34
  16. mcp_proxy_adapter/core/settings.py +376 -0
  17. mcp_proxy_adapter/core/utils.py +2 -2
  18. mcp_proxy_adapter/custom_openapi.py +81 -2
  19. mcp_proxy_adapter/examples/README.md +124 -0
  20. mcp_proxy_adapter/examples/__init__.py +7 -0
  21. mcp_proxy_adapter/examples/basic_server/README.md +60 -0
  22. mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
  23. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
  24. mcp_proxy_adapter/examples/basic_server/config.json +35 -0
  25. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
  26. mcp_proxy_adapter/examples/basic_server/server.py +98 -0
  27. mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
  28. mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
  29. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
  30. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
  31. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
  32. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
  33. mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
  34. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
  35. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
  36. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
  37. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
  38. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
  39. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
  40. mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
  41. mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
  42. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
  43. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
  44. mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
  45. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
  46. mcp_proxy_adapter/examples/deployment/README.md +49 -0
  47. mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
  48. mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
  49. {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
  50. mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
  51. mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
  52. mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
  53. mcp_proxy_adapter/examples/deployment/run.sh +43 -0
  54. mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
  55. mcp_proxy_adapter/openapi.py +3 -2
  56. mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
  57. mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
  58. mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
  59. mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
  60. mcp_proxy_adapter/tests/commands/test_help_command.py +8 -5
  61. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +4 -5
  62. mcp_proxy_adapter/tests/test_command_registry.py +37 -1
  63. mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
  64. mcp_proxy_adapter/version.py +1 -1
  65. {mcp_proxy_adapter-3.1.5.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/METADATA +1 -1
  66. mcp_proxy_adapter-4.0.0.dist-info/RECORD +110 -0
  67. {mcp_proxy_adapter-3.1.5.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/WHEEL +1 -1
  68. {mcp_proxy_adapter-3.1.5.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/top_level.txt +0 -1
  69. examples/__init__.py +0 -19
  70. examples/anti_patterns/README.md +0 -51
  71. examples/anti_patterns/__init__.py +0 -9
  72. examples/anti_patterns/bad_design/README.md +0 -72
  73. examples/anti_patterns/bad_design/global_state.py +0 -170
  74. examples/anti_patterns/bad_design/monolithic_command.py +0 -272
  75. examples/basic_example/README.md +0 -245
  76. examples/basic_example/__init__.py +0 -8
  77. examples/basic_example/commands/__init__.py +0 -5
  78. examples/basic_example/commands/echo_command.py +0 -95
  79. examples/basic_example/commands/math_command.py +0 -151
  80. examples/basic_example/commands/time_command.py +0 -152
  81. examples/basic_example/docs/EN/README.md +0 -177
  82. examples/basic_example/docs/RU/README.md +0 -177
  83. examples/basic_example/server.py +0 -151
  84. examples/basic_example/tests/conftest.py +0 -243
  85. examples/check_vstl_schema.py +0 -106
  86. examples/commands/echo_command.py +0 -52
  87. examples/commands/echo_command_di.py +0 -152
  88. examples/commands/echo_result.py +0 -65
  89. examples/commands/get_date_command.py +0 -98
  90. examples/commands/new_uuid4_command.py +0 -91
  91. examples/complete_example/Dockerfile +0 -24
  92. examples/complete_example/README.md +0 -92
  93. examples/complete_example/__init__.py +0 -8
  94. examples/complete_example/commands/__init__.py +0 -5
  95. examples/complete_example/commands/system_command.py +0 -328
  96. examples/complete_example/config.json +0 -41
  97. examples/complete_example/configs/config.dev.yaml +0 -40
  98. examples/complete_example/configs/config.docker.yaml +0 -40
  99. examples/complete_example/docker-compose.yml +0 -35
  100. examples/complete_example/requirements.txt +0 -20
  101. examples/complete_example/server.py +0 -113
  102. examples/di_example/.pytest_cache/README.md +0 -8
  103. examples/di_example/server.py +0 -249
  104. examples/fix_vstl_help.py +0 -123
  105. examples/minimal_example/README.md +0 -65
  106. examples/minimal_example/__init__.py +0 -8
  107. examples/minimal_example/config.json +0 -14
  108. examples/minimal_example/main.py +0 -136
  109. examples/minimal_example/simple_server.py +0 -163
  110. examples/minimal_example/tests/conftest.py +0 -171
  111. examples/minimal_example/tests/test_hello_command.py +0 -111
  112. examples/minimal_example/tests/test_integration.py +0 -181
  113. examples/patch_vstl_service.py +0 -105
  114. examples/patch_vstl_service_mcp.py +0 -108
  115. examples/server.py +0 -69
  116. examples/simple_server.py +0 -128
  117. examples/test_package_3.1.4.py +0 -177
  118. examples/test_server.py +0 -134
  119. examples/tool_description_example.py +0 -82
  120. mcp_proxy_adapter/py.typed +0 -0
  121. mcp_proxy_adapter-3.1.5.dist-info/RECORD +0 -118
  122. {mcp_proxy_adapter-3.1.5.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -18,7 +18,7 @@ from mcp_proxy_adapter.config import config
18
18
  from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
19
19
  from mcp_proxy_adapter.core.logging import logger, RequestLogger
20
20
  from mcp_proxy_adapter.commands.command_registry import registry
21
- from mcp_proxy_adapter.custom_openapi import custom_openapi
21
+ from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
22
22
 
23
23
 
24
24
  @asynccontextmanager
@@ -29,12 +29,16 @@ async def lifespan(app: FastAPI):
29
29
  # Startup events
30
30
  from mcp_proxy_adapter.commands.command_registry import registry
31
31
  from mcp_proxy_adapter.commands.help_command import HelpCommand
32
+ from mcp_proxy_adapter.commands.health_command import HealthCommand
32
33
 
33
- # Register built-in commands (help should be registered first)
34
+ # Register built-in commands if they don't exist (user can override them)
34
35
  if not registry.command_exists("help"):
35
36
  registry.register(HelpCommand)
36
37
 
37
- # Register other commands
38
+ if not registry.command_exists("health"):
39
+ registry.register(HealthCommand)
40
+
41
+ # Discover and register additional commands automatically
38
42
  registry.discover_commands()
39
43
 
40
44
  logger.info(f"Application started with {len(registry.get_all_commands())} commands registered")
@@ -45,18 +49,28 @@ async def lifespan(app: FastAPI):
45
49
  logger.info("Application shutting down")
46
50
 
47
51
 
48
- def create_app() -> FastAPI:
52
+ def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
49
53
  """
50
54
  Creates and configures FastAPI application.
51
55
 
56
+ Args:
57
+ title: Application title (default: "MCP Proxy Adapter")
58
+ description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
59
+ version: Application version (default: "1.0.0")
60
+
52
61
  Returns:
53
62
  Configured FastAPI application.
54
63
  """
64
+ # Use provided parameters or defaults
65
+ app_title = title or "MCP Proxy Adapter"
66
+ app_description = description or "JSON-RPC API for interacting with MCP Proxy"
67
+ app_version = version or "1.0.0"
68
+
55
69
  # Create application
56
70
  app = FastAPI(
57
- title="MCP Proxy Adapter",
58
- description="JSON-RPC API for interacting with MCP Proxy",
59
- version="1.0.0",
71
+ title=app_title,
72
+ description=app_description,
73
+ version=app_version,
60
74
  docs_url="/docs",
61
75
  redoc_url="/redoc",
62
76
  lifespan=lifespan,
@@ -75,7 +89,7 @@ def create_app() -> FastAPI:
75
89
  setup_middleware(app)
76
90
 
77
91
  # Use custom OpenAPI schema
78
- app.openapi = lambda: custom_openapi(app)
92
+ app.openapi = lambda: custom_openapi_with_fallback(app)
79
93
 
80
94
  # Explicit endpoint for OpenAPI schema
81
95
  @app.get("/openapi.json")
@@ -83,8 +97,8 @@ def create_app() -> FastAPI:
83
97
  """
84
98
  Returns optimized OpenAPI schema compatible with MCP-Proxy.
85
99
  """
86
- return custom_openapi(app)
87
-
100
+ return custom_openapi_with_fallback(app)
101
+
88
102
  # JSON-RPC handler
89
103
  @app.post("/api/jsonrpc", response_model=Union[JsonRpcSuccessResponse, JsonRpcErrorResponse, List[Union[JsonRpcSuccessResponse, JsonRpcErrorResponse]]])
90
104
  async def jsonrpc_endpoint(request: Request, request_data: Union[Dict[str, Any], List[Dict[str, Any]]] = Body(...)):
@@ -119,7 +133,7 @@ def create_app() -> FastAPI:
119
133
  else:
120
134
  # Process single request
121
135
  return await handle_json_rpc(request_data, request_id)
122
-
136
+
123
137
  # Command execution endpoint (/cmd)
124
138
  @app.post("/cmd")
125
139
  async def cmd_endpoint(request: Request, command_data: Dict[str, Any] = Body(...)):
@@ -200,6 +214,27 @@ def create_app() -> FastAPI:
200
214
  "error": e.to_dict()
201
215
  }
202
216
  )
217
+ except NotFoundError as e:
218
+ # Специальная обработка для help-команды: возвращаем result с пустым commands и error
219
+ if command_name == "help":
220
+ return {
221
+ "result": {
222
+ "success": False,
223
+ "commands": {},
224
+ "error": str(e),
225
+ "note": "To get detailed information about a specific command, call help with parameter: POST /cmd {\"command\": \"help\", \"params\": {\"cmdname\": \"<command_name>\"}}"
226
+ }
227
+ }
228
+ # Для остальных команд — стандартная ошибка
229
+ return JSONResponse(
230
+ status_code=200,
231
+ content={
232
+ "error": {
233
+ "code": e.code,
234
+ "message": str(e)
235
+ }
236
+ }
237
+ )
203
238
 
204
239
  except json.JSONDecodeError:
205
240
  req_logger.error("JSON decode error")
@@ -224,7 +259,7 @@ def create_app() -> FastAPI:
224
259
  }
225
260
  }
226
261
  )
227
-
262
+
228
263
  # Direct command call
229
264
  @app.post("/api/command/{command_name}")
230
265
  async def command_endpoint(request: Request, command_name: str, params: Dict[str, Any] = Body(default={})):
@@ -238,29 +273,50 @@ def create_app() -> FastAPI:
238
273
  result = await execute_command(command_name, params, request_id)
239
274
  return result
240
275
  except MicroserviceError as e:
276
+ # Convert to proper HTTP status code
277
+ status_code = 400 if e.code < 0 else e.code
241
278
  return JSONResponse(
242
- status_code=e.code,
279
+ status_code=status_code,
243
280
  content=e.to_dict()
244
281
  )
245
-
282
+
246
283
  # Server health check
247
284
  @app.get("/health", operation_id="health_check")
248
285
  async def health_endpoint():
249
286
  """
250
- Проверить работоспособность сервиса.
251
-
252
- Возвращает информацию о состоянии сервиса.
287
+ Health check endpoint.
288
+ Returns server status and basic information.
253
289
  """
254
- # Получаем базовую информацию о здоровье сервера
255
- health_data = await get_server_health()
256
-
257
- # Возвращаем информацию в формате, соответствующем схеме
258
- return JSONResponse(content={
290
+ return {
259
291
  "status": "ok",
260
292
  "model": "mcp-proxy-adapter",
261
- "version": config.get("version", "1.0.0")
262
- })
293
+ "version": "1.0.0"
294
+ }
263
295
 
296
+ # Graceful shutdown endpoint
297
+ @app.post("/shutdown")
298
+ async def shutdown_endpoint():
299
+ """
300
+ Graceful shutdown endpoint.
301
+ Triggers server shutdown after completing current requests.
302
+ """
303
+ import asyncio
304
+
305
+ # Schedule shutdown after a short delay to allow response
306
+ async def delayed_shutdown():
307
+ await asyncio.sleep(1)
308
+ # This will trigger the lifespan shutdown event
309
+ import os
310
+ os._exit(0)
311
+
312
+ # Start shutdown task
313
+ asyncio.create_task(delayed_shutdown())
314
+
315
+ return {
316
+ "status": "shutting_down",
317
+ "message": "Server shutdown initiated. New requests will be rejected."
318
+ }
319
+
264
320
  # List of available commands
265
321
  @app.get("/api/commands", response_model=CommandListResponse)
266
322
  async def commands_list_endpoint():
@@ -269,7 +325,7 @@ def create_app() -> FastAPI:
269
325
  """
270
326
  commands = await get_commands_list()
271
327
  return {"commands": commands}
272
-
328
+
273
329
  # Get command information by name
274
330
  @app.get("/api/commands/{command_name}")
275
331
  async def command_info_endpoint(request: Request, command_name: str):
@@ -296,7 +352,7 @@ def create_app() -> FastAPI:
296
352
  }
297
353
  }
298
354
  )
299
-
355
+
300
356
  # Get API tool description
301
357
  @app.get("/api/tools/{tool_name}")
302
358
  async def tool_description_endpoint(tool_name: str, format: Optional[str] = "json"):
@@ -347,7 +403,7 @@ def create_app() -> FastAPI:
347
403
  }
348
404
  }
349
405
  )
350
-
406
+
351
407
  # Execute API tool
352
408
  @app.post("/api/tools/{tool_name}")
353
409
  async def execute_tool_endpoint(tool_name: str, params: Dict[str, Any] = Body(...)):
@@ -387,5 +443,8 @@ def create_app() -> FastAPI:
387
443
  return app
388
444
 
389
445
 
446
+
447
+
448
+
390
449
  # Create global application instance
391
450
  app = create_app()
@@ -43,7 +43,7 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
43
43
  start_time = time.time()
44
44
 
45
45
  # Use Command.run that handles instances with dependencies properly
46
- command_class = registry.get_command(command_name)
46
+ command_class = registry.get_command_with_priority(command_name)
47
47
  result = await command_class.run(**params)
48
48
 
49
49
  execution_time = time.time() - start_time
@@ -35,7 +35,7 @@ class ErrorHandlingMiddleware(BaseMiddleware):
35
35
  except CommandError as e:
36
36
  # Command error
37
37
  request_id = getattr(request.state, "request_id", "unknown")
38
- logger.error(f"[{request_id}] Command error: {str(e)}")
38
+ logger.debug(f"[{request_id}] Command error: {str(e)}")
39
39
 
40
40
  # Проверяем, является ли запрос JSON-RPC
41
41
  is_jsonrpc = self._is_json_rpc_request(request)
@@ -52,7 +52,7 @@ class ErrorHandlingMiddleware(BaseMiddleware):
52
52
  "error": {
53
53
  "code": e.code,
54
54
  "message": str(e),
55
- "data": e.details if hasattr(e, "details") else None
55
+ "data": e.data if hasattr(e, "data") and e.data else None
56
56
  },
57
57
  "id": request_id_jsonrpc
58
58
  }
@@ -67,7 +67,7 @@ class ErrorHandlingMiddleware(BaseMiddleware):
67
67
  except ValidationError as e:
68
68
  # Validation error
69
69
  request_id = getattr(request.state, "request_id", "unknown")
70
- logger.error(f"[{request_id}] Validation error: {str(e)}")
70
+ logger.debug(f"[{request_id}] Validation error: {str(e)}")
71
71
 
72
72
  # Get JSON-RPC request ID if available
73
73
  request_id_jsonrpc = await self._get_json_rpc_id(request)
@@ -81,7 +81,7 @@ class ErrorHandlingMiddleware(BaseMiddleware):
81
81
  "error": {
82
82
  "code": -32602,
83
83
  "message": "Invalid params",
84
- "data": e.details if hasattr(e, "details") else None
84
+ "data": e.data if hasattr(e, "data") and e.data else None
85
85
  },
86
86
  "id": request_id_jsonrpc
87
87
  }
@@ -96,7 +96,7 @@ class ErrorHandlingMiddleware(BaseMiddleware):
96
96
  except MicroserviceError as e:
97
97
  # Other microservice error
98
98
  request_id = getattr(request.state, "request_id", "unknown")
99
- logger.error(f"[{request_id}] Microservice error: {str(e)}")
99
+ logger.debug(f"[{request_id}] Microservice error: {str(e)}")
100
100
 
101
101
  # Get JSON-RPC request ID if available
102
102
  request_id_jsonrpc = await self._get_json_rpc_id(request)
@@ -104,13 +104,13 @@ class ErrorHandlingMiddleware(BaseMiddleware):
104
104
  # If request was JSON-RPC
105
105
  if self._is_json_rpc_request(request):
106
106
  return JSONResponse(
107
- status_code=e.code,
107
+ status_code=400,
108
108
  content={
109
109
  "jsonrpc": "2.0",
110
110
  "error": {
111
111
  "code": -32000,
112
112
  "message": str(e),
113
- "data": e.details if hasattr(e, "details") else None
113
+ "data": e.data if hasattr(e, "data") and e.data else None
114
114
  },
115
115
  "id": request_id_jsonrpc
116
116
  }
@@ -118,14 +118,14 @@ class ErrorHandlingMiddleware(BaseMiddleware):
118
118
 
119
119
  # Regular API error
120
120
  return JSONResponse(
121
- status_code=e.code,
121
+ status_code=400,
122
122
  content=e.to_dict()
123
123
  )
124
124
 
125
125
  except Exception as e:
126
126
  # Unexpected error
127
127
  request_id = getattr(request.state, "request_id", "unknown")
128
- logger.exception(f"[{request_id}] Unexpected error: {str(e)}")
128
+ logger.debug(f"[{request_id}] Unexpected error: {str(e)}")
129
129
 
130
130
  # Get JSON-RPC request ID if available
131
131
  request_id_jsonrpc = await self._get_json_rpc_id(request)
@@ -165,7 +165,8 @@ class ErrorHandlingMiddleware(BaseMiddleware):
165
165
  Returns:
166
166
  True if request is JSON-RPC, False otherwise.
167
167
  """
168
- return request.url.path.endswith("/jsonrpc")
168
+ # Only requests to /api/jsonrpc are JSON-RPC requests
169
+ return request.url.path == "/api/jsonrpc"
169
170
 
170
171
  async def _get_json_rpc_id(self, request: Request) -> Optional[Any]:
171
172
  """
@@ -126,7 +126,7 @@ class ToolIntegration:
126
126
 
127
127
  logger.info(f"Successfully registered tool: {tool_name}")
128
128
  except Exception as e:
129
- logger.error(f"Error registering tool {tool_name}: {e}")
129
+ logger.debug(f"Error registering tool {tool_name}: {e}")
130
130
  results[tool_name] = {
131
131
  "status": "error",
132
132
  "error": str(e)
@@ -149,7 +149,10 @@ class ToolIntegration:
149
149
 
150
150
  # Формируем словарь типов для всех параметров всех команд
151
151
  for cmd_name, cmd_info in commands.items():
152
- for param_name, param_info in cmd_info.get("params", {}).items():
152
+ params = cmd_info.get("params", {})
153
+ if params is None:
154
+ continue
155
+ for param_name, param_info in params.items():
153
156
  param_type = param_info.get("type", "значение")
154
157
 
155
158
  # Преобразуем русские типы в типы JSON Schema
@@ -50,14 +50,14 @@ class TSTCommandExecutor:
50
50
 
51
51
  try:
52
52
  # Проверяем существование команды
53
- if not registry.command_exists(command):
53
+ if not registry.command_exists_with_priority(command):
54
54
  raise NotFoundError(f"Команда '{command}' не найдена")
55
55
 
56
56
  # Получаем класс команды
57
- command_class = registry.get_command(command)
57
+ command_class = registry.get_command_with_priority(command)
58
58
 
59
59
  # Выполняем команду
60
- result = await command_class.run(**params)
60
+ result = await command_class.execute(**params)
61
61
 
62
62
  # Возвращаем результат
63
63
  return result.to_dict()
@@ -13,6 +13,7 @@ from mcp_proxy_adapter.core.errors import (
13
13
  CommandError, InternalError, InvalidParamsError, NotFoundError, ValidationError
14
14
  )
15
15
  from mcp_proxy_adapter.core.logging import logger
16
+ from .hooks import hooks, HookContext
16
17
 
17
18
 
18
19
  T = TypeVar("T", bound=CommandResult)
@@ -136,6 +137,20 @@ class Command(ABC):
136
137
  # Parameters validation
137
138
  validated_params = cls.validate_params(kwargs)
138
139
 
140
+ # Execute before hooks
141
+ hook_context = hooks.execute_before_hooks(command_name, validated_params)
142
+
143
+ # Check if standard processing should be skipped
144
+ if not hook_context.standard_processing:
145
+ logger.debug(f"Standard processing skipped for command {command_name} due to hook")
146
+ # Return the params as result if standard processing is disabled
147
+ return SuccessResult(data=validated_params)
148
+
149
+ # Get command with priority (custom commands first, then built-in)
150
+ priority_command_class = registry.get_priority_command(command_name)
151
+ if priority_command_class is None:
152
+ raise NotFoundError(f"Command '{command_name}' not found")
153
+
139
154
  # Check if we have a registered instance for this command
140
155
  if registry.has_instance(command_name):
141
156
  # Use existing instance with dependencies
@@ -143,9 +158,12 @@ class Command(ABC):
143
158
  result = await command.execute(**validated_params)
144
159
  else:
145
160
  # Create new instance for commands without dependencies
146
- command = cls()
161
+ command = priority_command_class()
147
162
  result = await command.execute(**validated_params)
148
163
 
164
+ # Execute after hooks
165
+ hooks.execute_after_hooks(command_name, validated_params, result)
166
+
149
167
  logger.debug(f"Command {cls.__name__} executed successfully")
150
168
  return result
151
169
  except ValidationError as e: