46
46
PromptPosthookResult ,
47
47
PromptPrehookPayload ,
48
48
PromptPrehookResult ,
49
+ ToolPostInvokePayload ,
50
+ ToolPostInvokeResult ,
51
+ ToolPreInvokePayload ,
52
+ ToolPreInvokeResult ,
49
53
)
50
54
from mcpgateway .plugins .framework .registry import PluginInstanceRegistry
51
- from mcpgateway .plugins .framework .utils import post_prompt_matches , pre_prompt_matches
55
+ from mcpgateway .plugins .framework .utils import post_prompt_matches , post_tool_matches , pre_prompt_matches , pre_tool_matches
52
56
53
57
logger = logging .getLogger (__name__ )
54
58
@@ -303,6 +307,54 @@ async def post_prompt_fetch(plugin: PluginRef, payload: PromptPosthookPayload, c
303
307
return await plugin .plugin .prompt_post_fetch (payload , context )
304
308
305
309
310
+ async def pre_tool_invoke (plugin : PluginRef , payload : ToolPreInvokePayload , context : PluginContext ) -> ToolPreInvokeResult :
311
+ """Call plugin's tool pre-invoke hook.
312
+
313
+ Args:
314
+ plugin: The plugin to execute.
315
+ payload: The tool payload to be analyzed.
316
+ context: Contextual information about the hook call.
317
+
318
+ Returns:
319
+ The result of the plugin execution.
320
+
321
+ Examples:
322
+ >>> from mcpgateway.plugins.framework.base import Plugin, PluginRef
323
+ >>> from mcpgateway.plugins.framework.plugin_types import ToolPreInvokePayload, PluginContext, GlobalContext
324
+ >>> # Assuming you have a plugin instance:
325
+ >>> # plugin_ref = PluginRef(my_plugin)
326
+ >>> payload = ToolPreInvokePayload(name="calculator", args={"operation": "add", "a": 5, "b": 3})
327
+ >>> context = PluginContext(GlobalContext(request_id="123"))
328
+ >>> # In async context:
329
+ >>> # result = await pre_tool_invoke(plugin_ref, payload, context)
330
+ """
331
+ return await plugin .plugin .tool_pre_invoke (payload , context )
332
+
333
+
334
+ async def post_tool_invoke (plugin : PluginRef , payload : ToolPostInvokePayload , context : PluginContext ) -> ToolPostInvokeResult :
335
+ """Call plugin's tool post-invoke hook.
336
+
337
+ Args:
338
+ plugin: The plugin to execute.
339
+ payload: The tool result payload to be analyzed.
340
+ context: Contextual information about the hook call.
341
+
342
+ Returns:
343
+ The result of the plugin execution.
344
+
345
+ Examples:
346
+ >>> from mcpgateway.plugins.framework.base import Plugin, PluginRef
347
+ >>> from mcpgateway.plugins.framework.plugin_types import ToolPostInvokePayload, PluginContext, GlobalContext
348
+ >>> # Assuming you have a plugin instance:
349
+ >>> # plugin_ref = PluginRef(my_plugin)
350
+ >>> payload = ToolPostInvokePayload(name="calculator", result={"result": 8, "status": "success"})
351
+ >>> context = PluginContext(GlobalContext(request_id="123"))
352
+ >>> # In async context:
353
+ >>> # result = await post_tool_invoke(plugin_ref, payload, context)
354
+ """
355
+ return await plugin .plugin .tool_post_invoke (payload , context )
356
+
357
+
306
358
class PluginManager :
307
359
"""Plugin manager for managing the plugin lifecycle.
308
360
@@ -343,6 +395,8 @@ class PluginManager:
343
395
_config : Config | None = None
344
396
_pre_prompt_executor : PluginExecutor [PromptPrehookPayload ] = PluginExecutor [PromptPrehookPayload ]()
345
397
_post_prompt_executor : PluginExecutor [PromptPosthookPayload ] = PluginExecutor [PromptPosthookPayload ]()
398
+ _pre_tool_executor : PluginExecutor [ToolPreInvokePayload ] = PluginExecutor [ToolPreInvokePayload ]()
399
+ _post_tool_executor : PluginExecutor [ToolPostInvokePayload ] = PluginExecutor [ToolPostInvokePayload ]()
346
400
347
401
# Context cleanup tracking
348
402
_context_store : Dict [str , Tuple [PluginContextTable , float ]] = {}
@@ -369,6 +423,8 @@ def __init__(self, config: str = "", timeout: int = DEFAULT_PLUGIN_TIMEOUT):
369
423
# Update executor timeouts
370
424
self ._pre_prompt_executor .timeout = timeout
371
425
self ._post_prompt_executor .timeout = timeout
426
+ self ._pre_tool_executor .timeout = timeout
427
+ self ._post_tool_executor .timeout = timeout
372
428
373
429
# Initialize context tracking if not already done
374
430
if not hasattr (self , "_context_store" ):
@@ -614,3 +670,112 @@ async def prompt_post_fetch(
614
670
del self ._context_store [global_context .request_id ]
615
671
616
672
return result
673
+
674
+ async def tool_pre_invoke (
675
+ self ,
676
+ payload : ToolPreInvokePayload ,
677
+ global_context : GlobalContext ,
678
+ local_contexts : Optional [PluginContextTable ] = None ,
679
+ ) -> tuple [ToolPreInvokeResult , PluginContextTable | None ]:
680
+ """Execute pre-invoke hooks before a tool is invoked.
681
+
682
+ Args:
683
+ payload: The tool payload containing name and arguments.
684
+ global_context: Shared context for all plugins with request metadata.
685
+ local_contexts: Optional existing contexts from previous executions.
686
+
687
+ Returns:
688
+ A tuple containing:
689
+ - ToolPreInvokeResult with processing status and modified payload
690
+ - PluginContextTable with updated contexts for post-invoke hook
691
+
692
+ Raises:
693
+ PayloadSizeError: If payload exceeds size limits.
694
+
695
+ Examples:
696
+ >>> manager = PluginManager("plugins/config.yaml")
697
+ >>> # In async context:
698
+ >>> # await manager.initialize()
699
+ >>>
700
+ >>> from mcpgateway.plugins.framework.plugin_types import ToolPreInvokePayload, GlobalContext
701
+ >>> payload = ToolPreInvokePayload(
702
+ ... name="calculator",
703
+ ... args={"operation": "add", "a": 5, "b": 3}
704
+ ... )
705
+ >>> context = GlobalContext(
706
+ ... request_id="req-123",
707
+
708
+ ... )
709
+ >>>
710
+ >>> # In async context:
711
+ >>> # result, contexts = await manager.tool_pre_invoke(payload, context)
712
+ >>> # if result.continue_processing:
713
+ >>> # # Proceed with tool invocation
714
+ >>> # modified_payload = result.modified_payload or payload
715
+ """
716
+ # Cleanup old contexts periodically
717
+ await self ._cleanup_old_contexts ()
718
+
719
+ # Get plugins configured for this hook
720
+ plugins = self ._registry .get_plugins_for_hook (HookType .TOOL_PRE_INVOKE )
721
+
722
+ # Execute plugins
723
+ result = await self ._pre_tool_executor .execute (plugins , payload , global_context , pre_tool_invoke , pre_tool_matches , local_contexts )
724
+
725
+ # Store contexts for potential reuse
726
+ if result [1 ]:
727
+ self ._context_store [global_context .request_id ] = (result [1 ], time .time ())
728
+
729
+ return result
730
+
731
+ async def tool_post_invoke (
732
+ self , payload : ToolPostInvokePayload , global_context : GlobalContext , local_contexts : Optional [PluginContextTable ] = None
733
+ ) -> tuple [ToolPostInvokeResult , PluginContextTable | None ]:
734
+ """Execute post-invoke hooks after a tool is invoked.
735
+
736
+ Args:
737
+ payload: The tool result payload containing invocation results.
738
+ global_context: Shared context for all plugins with request metadata.
739
+ local_contexts: Optional contexts from pre-invoke hook execution.
740
+
741
+ Returns:
742
+ A tuple containing:
743
+ - ToolPostInvokeResult with processing status and modified result
744
+ - PluginContextTable with final contexts
745
+
746
+ Raises:
747
+ PayloadSizeError: If payload exceeds size limits.
748
+
749
+ Examples:
750
+ >>> # Continuing from tool_pre_invoke example
751
+ >>> from mcpgateway.plugins.framework.plugin_types import ToolPostInvokePayload, GlobalContext
752
+ >>>
753
+ >>> post_payload = ToolPostInvokePayload(
754
+ ... name="calculator",
755
+ ... result={"result": 8, "status": "success"}
756
+ ... )
757
+ >>>
758
+ >>> manager = PluginManager("plugins/config.yaml")
759
+ >>> context = GlobalContext(request_id="req-123")
760
+ >>>
761
+ >>> # In async context:
762
+ >>> # result, _ = await manager.tool_post_invoke(
763
+ >>> # post_payload,
764
+ >>> # context,
765
+ >>> # contexts # From pre_invoke
766
+ >>> # )
767
+ >>> # if result.modified_payload:
768
+ >>> # # Use modified result
769
+ >>> # final_result = result.modified_payload.result
770
+ """
771
+ # Get plugins configured for this hook
772
+ plugins = self ._registry .get_plugins_for_hook (HookType .TOOL_POST_INVOKE )
773
+
774
+ # Execute plugins
775
+ result = await self ._post_tool_executor .execute (plugins , payload , global_context , post_tool_invoke , post_tool_matches , local_contexts )
776
+
777
+ # Clean up stored context after post-invoke
778
+ if global_context .request_id in self ._context_store :
779
+ del self ._context_store [global_context .request_id ]
780
+
781
+ return result
0 commit comments