# # Copyright 2026 The Dapr Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import logging from typing import Any, Dict, Optional from pydantic import BaseModel, Field from dapr_agents.tool.workflow.tool_context import WorkflowContextInjectedTool logger = logging.getLogger(__name__) def make_mcp_gateway_via_child_workflow_tool( *, target_app_id: str, gateway_workflow_name: str, name: str = "McpGatewayCall", ) -> WorkflowContextInjectedTool: """ Create an AgentTool that calls an MCP "gateway workflow" hosted in another Dapr app, using the *current* workflow context (passed in by the agent) to schedule a multi-app child workflow. This uses Dapr's multi-application child workflow routing via `app_id`. The called workflow is expected to: - Receive input: {"tool": , "arguments": } - Return: a JSON-serializable result (string/dict/etc.) representing the MCP call result. """ class Args(BaseModel): tool: str = Field( ..., description="Name of the MCP tool to call on the remote side." ) arguments: Dict[str, Any] = Field( default_factory=dict, description="Arguments to pass to the remote MCP tool.", ) instance_id: Optional[str] = Field( default=None, description=( "Optional child workflow instance id. Use when you need idempotency / " "dedupe semantics at the child-workflow level." ), ) def _executor( ctx: Any, tool: str, arguments: Dict[str, Any], instance_id: Optional[str] = None, ) -> Any: """ Schedule a child workflow on the target app id, passing the MCP tool call as input. NOTE: This is intentionally a *sync* function that returns a workflow Task. In a Dapr workflow orchestrator, you'd typically do: result = yield tool_obj(ctx=ctx, tool="X", arguments={...}) """ payload = {"tool": tool, "arguments": arguments} logger.debug( "Scheduling child workflow '%s' on app_id='%s' for MCP tool='%s' args=%s instance_id=%s", gateway_workflow_name, target_app_id, tool, arguments, instance_id, ) # Dapr multi-app child workflow call: route execution to target_app_id. # API: yield ctx.call_child_workflow(workflow='Workflow2', input='my-input', app_id='App2') if instance_id: return ctx.call_child_workflow( workflow=gateway_workflow_name, input=payload, instance_id=instance_id, app_id=target_app_id, ) return ctx.call_child_workflow( workflow=gateway_workflow_name, input=payload, app_id=target_app_id, ) return WorkflowContextInjectedTool( name=name, description=( f"Gateway tool that calls MCP via child workflow '{gateway_workflow_name}' " f"hosted by Dapr app '{target_app_id}'." ), func=_executor, args_model=Args, )