You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have looked for existing issues (including closed) about this
Feature Request
Allow per-call MCP _meta to be derived from the tool call the model chose.
The _meta channel added in #1954 and kept through #2141 and #2398 is read from the run's ToolContext at dispatch time: context.get::<rmcp::model::Meta>().cloned() at rig-agent/src/tool/rmcp.rs:487 on 0.42.0, and crates/rig-rmcp/src/native.rs:478 on main, then request.meta = meta. That context is set once by the caller through PromptRequest::tool_context before the model has picked a tool or its arguments. Metadata that has to be computed from the call itself has no way onto the request.
The pre-tool hook sees the call but cannot contribute to it. on_tool_call(&self, &HookContext, ToolCall<'_>) receives tool_name, tool_call_id, internal_call_id, and args, and returns ToolCallAction::{Run, Rewrite(Value), Skip(String), Stop(String)}. No hook method receives a mutable ToolContext; the only context exposed to hooks is the immutable tool_context on the result event.
Motivation
Some metadata is inherently per call because it binds to the arguments:
Request signing. A signature or proof of possession over (tool, arguments, time) lets the MCP server verify that the caller authorized this call, and a captured signature cannot be replayed with different arguments. This is my case (Tenuo warrants); it is the same shape as any HMAC-signed request.
Idempotency keys derived from the argument set, so retries of one logical call coalesce downstream.
Trace or budget context that depends on which tool was chosen.
A run-level Meta covers auth tokens, session ids, and A2A context_id/task_id, which is what #1536 asked for and what #1954 delivered. It cannot cover a value that does not exist until the model decides.
Current workaround: implement the MCP tool by hand (impl Tool), compute the metadata inside call() from the deserialized arguments, and drive the rmcp client directly, setting CallToolRequestParams::meta. That works, but it gives up the registration path (rmcp_tools() on 0.42; PortableDynamicTool on main), tools/list_changed reconciliation through McpClientHandler, and the result preservation in preserve_mcp_result, and it has to be repeated for every tool.
feat(agent): hand-drive a configured Agent — AgentDriver + DriveStep/TurnTools #2278 (open since 2026-08-10): AgentDriver's DriveStep::ExecuteTools { calls, tools } hands the host the pending calls before dispatch and the host builds the ToolContext. That is exactly the insertion point, but only on the sans-IO path, which by design runs no hooks, memory, retrieval, or telemetry.
Elsewhere:
LangChain's @wrap_tool_call middleware receives a ToolCallRequest and a handler, runs after the model selects a tool and before it executes, and can inspect or modify the call. Tools also read runtime.tool_call_id from ToolRuntime.
pydantic-ai passes a per-call RunContext carrying tool_call_id and tool_name (no arguments), and tools have a prepare(RunContext, ToolDefinition) -> ToolDefinition | None step.
Describe the solution you'd like
Smallest first. Any one closes the gap.
A. Per-call context from the hook. Let on_tool_call contribute values merged into that call's dispatch snapshot. Sketch, not a proposal for exact naming:
asyncfnon_tool_call(&self,ctx:&HookContext,event:ToolCall<'_>) -> ToolCallAction{let meta = self.sign(event.tool_name, event.args);// computed from the chosen callToolCallAction::Run.with_context(|c:&mutToolContext| { c.insert(meta);})}
This generalizes beyond MCP, since sub-agents and native tools read the same context, and leaves the Meta forwarding in rig-rmcp unchanged.
B. A per-call meta provider on the MCP registration. Narrower, MCP only:
Falls back to the context Meta on None, so it is additive.
C. Land #2278 and document ExecuteTools as the supported path. Works for hand-driven loops today; the cost is the hook lifecycle.
I'd suggest A, with B as a reasonable narrow alternative. Happy to open a PR for either once there is agreement on the shape.
Reproduction
Register any MCP tool via the portable path and try to set a Meta whose value depends on arguments. The only place to set it is before prompt() runs. An end-to-end example of the hand-written workaround (a Rig agent, a guarded MCP tool, and an rmcp server verifying per-call _meta) is at https://github.com/tenuo-ai/tenuo-rig-demo — see src/tools/incident_mcp.rs for the tool and src/bin/incident_mcp_server.rs for the server.
Feature Request
Allow per-call MCP
_metato be derived from the tool call the model chose.The
_metachannel added in #1954 and kept through #2141 and #2398 is read from the run'sToolContextat dispatch time:context.get::<rmcp::model::Meta>().cloned()atrig-agent/src/tool/rmcp.rs:487on 0.42.0, andcrates/rig-rmcp/src/native.rs:478onmain, thenrequest.meta = meta. That context is set once by the caller throughPromptRequest::tool_contextbefore the model has picked a tool or its arguments. Metadata that has to be computed from the call itself has no way onto the request.The pre-tool hook sees the call but cannot contribute to it.
on_tool_call(&self, &HookContext, ToolCall<'_>)receivestool_name,tool_call_id,internal_call_id, andargs, and returnsToolCallAction::{Run, Rewrite(Value), Skip(String), Stop(String)}. No hook method receives a mutableToolContext; the only context exposed to hooks is the immutabletool_contexton the result event.Motivation
Some metadata is inherently per call because it binds to the arguments:
(tool, arguments, time)lets the MCP server verify that the caller authorized this call, and a captured signature cannot be replayed with different arguments. This is my case (Tenuo warrants); it is the same shape as any HMAC-signed request.A run-level
Metacovers auth tokens, session ids, and A2Acontext_id/task_id, which is what #1536 asked for and what #1954 delivered. It cannot cover a value that does not exist until the model decides.Current workaround: implement the MCP tool by hand (
impl Tool), compute the metadata insidecall()from the deserialized arguments, and drive thermcpclient directly, settingCallToolRequestParams::meta. That works, but it gives up the registration path (rmcp_tools()on 0.42;PortableDynamicToolonmain),tools/list_changedreconciliation throughMcpClientHandler, and the result preservation inpreserve_mcp_result, and it has to be repeated for every tool.Prior art
In this repo:
_meta(SEP-1319). The value is supplied by the caller before the run.on_tool_callhook to modify tool arguments before execution #1680 asked for argument rewriting from the hook; the requester closed it. Rewriting exists today asToolCallAction::Rewrite. Nobody has asked for hooks to add context.ToolContextmoves torig-core,PortableDynamicTool::new_with_contextgives dynamic tools the per-call context,_metaforwarding unchanged.Agent—AgentDriver+DriveStep/TurnTools#2278 (open since 2026-08-10):AgentDriver'sDriveStep::ExecuteTools { calls, tools }hands the host the pending calls before dispatch and the host builds theToolContext. That is exactly the insertion point, but only on the sans-IO path, which by design runs no hooks, memory, retrieval, or telemetry.Elsewhere:
@wrap_tool_callmiddleware receives aToolCallRequestand a handler, runs after the model selects a tool and before it executes, and can inspect or modify the call. Tools also readruntime.tool_call_idfromToolRuntime.RunContextcarryingtool_call_idandtool_name(no arguments), and tools have aprepare(RunContext, ToolDefinition) -> ToolDefinition | Nonestep.Describe the solution you'd like
Smallest first. Any one closes the gap.
A. Per-call context from the hook. Let
on_tool_callcontribute values merged into that call's dispatch snapshot. Sketch, not a proposal for exact naming:This generalizes beyond MCP, since sub-agents and native tools read the same context, and leaves the
Metaforwarding inrig-rmcpunchanged.B. A per-call meta provider on the MCP registration. Narrower, MCP only:
Falls back to the context
MetaonNone, so it is additive.C. Land #2278 and document
ExecuteToolsas the supported path. Works for hand-driven loops today; the cost is the hook lifecycle.I'd suggest A, with B as a reasonable narrow alternative. Happy to open a PR for either once there is agreement on the shape.
Reproduction
Register any MCP tool via the portable path and try to set a
Metawhose value depends onarguments. The only place to set it is beforeprompt()runs. An end-to-end example of the hand-written workaround (a Rig agent, a guarded MCP tool, and an rmcp server verifying per-call_meta) is at https://github.com/tenuo-ai/tenuo-rig-demo — seesrc/tools/incident_mcp.rsfor the tool andsrc/bin/incident_mcp_server.rsfor the server.