Execute tool and return ToolCallResult. When middleware is enabled (default), CTP handles: - Retry with exponential backoff for transient errors - Circuit breaker pattern for failing servers - Rate limiting (if configured) OAuth handling: - If a tool
(
self,
tool_name: str,
arguments: dict[str, Any],
namespace: str | None = None,
timeout: float | None = None,
_oauth_retry: bool = False,
)
| 707 | return False |
| 708 | |
| 709 | async def execute_tool( |
| 710 | self, |
| 711 | tool_name: str, |
| 712 | arguments: dict[str, Any], |
| 713 | namespace: str | None = None, |
| 714 | timeout: float | None = None, |
| 715 | _oauth_retry: bool = False, |
| 716 | ) -> ToolCallResult: |
| 717 | """Execute tool and return ToolCallResult. |
| 718 | |
| 719 | When middleware is enabled (default), CTP handles: |
| 720 | - Retry with exponential backoff for transient errors |
| 721 | - Circuit breaker pattern for failing servers |
| 722 | - Rate limiting (if configured) |
| 723 | |
| 724 | OAuth handling: |
| 725 | - If a tool fails with OAuth authorization error, automatically |
| 726 | triggers the OAuth flow and retries the tool call once. |
| 727 | """ |
| 728 | # Check if this is a dynamic tool |
| 729 | if self.dynamic_tool_provider.is_dynamic_tool(tool_name): |
| 730 | logger.info(f"Executing dynamic tool: {tool_name}") |
| 731 | try: |
| 732 | result = await self.dynamic_tool_provider.execute_dynamic_tool( |
| 733 | tool_name, arguments |
| 734 | ) |
| 735 | return ToolCallResult(tool_name=tool_name, success=True, result=result) |
| 736 | except Exception as e: |
| 737 | error_msg = str(e) |
| 738 | logger.error(f"Dynamic tool execution failed: {error_msg}") |
| 739 | return ToolCallResult( |
| 740 | tool_name=tool_name, success=False, error=error_msg |
| 741 | ) |
| 742 | |
| 743 | # Regular MCP tool execution (middleware handles retries if enabled) |
| 744 | if not self.stream_manager: |
| 745 | return ToolCallResult( |
| 746 | tool_name=tool_name, success=False, error="ToolManager not initialized" |
| 747 | ) |
| 748 | |
| 749 | try: |
| 750 | result = await self.stream_manager.call_tool( |
| 751 | tool_name=tool_name, |
| 752 | arguments=arguments, |
| 753 | server_name=namespace, |
| 754 | timeout=timeout or self._get_server_timeout(namespace), |
| 755 | ) |
| 756 | |
| 757 | # Check if result contains an OAuth error (some servers return errors in content) |
| 758 | # Only check results that are flagged as errors — scanning successful |
| 759 | # payloads causes false positives (e.g. the number "401" in data). |
| 760 | result_is_error = ( |
| 761 | getattr(result, "isError", False) |
| 762 | or (isinstance(result, dict) and result.get("isError", False)) |
| 763 | or (isinstance(result, dict) and "error" in result) |
| 764 | ) |
| 765 | result_str = str(result) if result_is_error else "" |
| 766 | if result_str and _is_oauth_error(result_str) and not _oauth_retry: |