4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
588 lines
21 KiB
Python
588 lines
21 KiB
Python
"""OpenClaw MCP-backed bridge tools."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from core.tool_framework.telemetry import report_run_error
|
|
from core.tool_framework.tool_decorator import tool
|
|
from core.tool_framework.utils.mcp_params import first_list, first_string
|
|
from core.tool_framework.utils.mcp_tool_listing import build_mcp_tool_listing
|
|
from integrations.openclaw import (
|
|
OpenClawConfig,
|
|
OpenClawToolCallResult,
|
|
build_openclaw_config,
|
|
describe_openclaw_error,
|
|
openclaw_config_from_env,
|
|
openclaw_runtime_unavailable_reason,
|
|
)
|
|
from integrations.openclaw import (
|
|
call_openclaw_tool as invoke_openclaw_mcp_tool,
|
|
)
|
|
from integrations.openclaw import (
|
|
list_openclaw_tools as list_openclaw_mcp_tools,
|
|
)
|
|
|
|
OpenClawParams = dict[str, object]
|
|
OpenClawBridgeResponse = dict[str, object]
|
|
OpenClawConversationRow = dict[str, object]
|
|
|
|
|
|
def _openclaw_unavailable_response(
|
|
error: str,
|
|
*,
|
|
tool_name: str | None = None,
|
|
arguments: OpenClawParams | None = None,
|
|
) -> OpenClawBridgeResponse:
|
|
payload: OpenClawBridgeResponse = {
|
|
"source": "openclaw",
|
|
"available": False,
|
|
"error": error,
|
|
}
|
|
if tool_name:
|
|
payload["tool"] = tool_name
|
|
if arguments is not None:
|
|
payload["arguments"] = arguments
|
|
return payload
|
|
|
|
|
|
def _resolve_config(
|
|
openclaw_url: str | None,
|
|
openclaw_mode: str | None,
|
|
openclaw_token: str | None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
) -> OpenClawConfig | None:
|
|
env_config = openclaw_config_from_env()
|
|
if any((openclaw_url, openclaw_mode, openclaw_token, openclaw_command, openclaw_args)):
|
|
inferred_mode = (
|
|
openclaw_mode
|
|
or ("stdio" if openclaw_command else "")
|
|
or ("streamable-http" if openclaw_url else "")
|
|
or (env_config.mode if env_config else "")
|
|
)
|
|
raw_config: OpenClawParams = {
|
|
"url": openclaw_url or (env_config.url if env_config else ""),
|
|
"mode": inferred_mode,
|
|
"auth_token": openclaw_token or (env_config.auth_token if env_config else ""),
|
|
"command": openclaw_command or (env_config.command if env_config else ""),
|
|
"args": openclaw_args or (list(env_config.args) if env_config else []),
|
|
"headers": env_config.headers if env_config else {},
|
|
}
|
|
return build_openclaw_config(raw_config)
|
|
return env_config
|
|
|
|
|
|
def _openclaw_available(sources: dict[str, dict]) -> bool:
|
|
return bool(sources.get("openclaw", {}).get("connection_verified"))
|
|
|
|
|
|
def _openclaw_extract_params(sources: dict[str, dict]) -> OpenClawParams:
|
|
openclaw = sources.get("openclaw", {})
|
|
if not openclaw:
|
|
return {}
|
|
return {
|
|
"openclaw_url": first_string(openclaw, "openclaw_url", "url"),
|
|
"openclaw_mode": first_string(openclaw, "openclaw_mode", "mode"),
|
|
"openclaw_token": first_string(openclaw, "openclaw_token", "auth_token"),
|
|
"openclaw_command": first_string(openclaw, "openclaw_command", "command"),
|
|
"openclaw_args": first_list(openclaw, "openclaw_args", "args"),
|
|
}
|
|
|
|
|
|
def _openclaw_conversation_id(sources: dict[str, dict]) -> str:
|
|
openclaw = sources.get("openclaw", {})
|
|
return str(
|
|
openclaw.get("openclaw_conversation_id") or openclaw.get("conversation_id") or ""
|
|
).strip()
|
|
|
|
|
|
def _openclaw_conversation_params(sources: dict[str, dict]) -> OpenClawParams:
|
|
params = _openclaw_extract_params(sources)
|
|
openclaw = sources.get("openclaw", {})
|
|
params["search"] = (
|
|
openclaw.get("openclaw_search_query")
|
|
or openclaw.get("search_query")
|
|
or openclaw.get("search")
|
|
or ""
|
|
)
|
|
params["limit"] = 10
|
|
return params
|
|
|
|
|
|
def _openclaw_conversation_detail_params(sources: dict[str, dict]) -> OpenClawParams:
|
|
params = _openclaw_extract_params(sources)
|
|
conversation_id = _openclaw_conversation_id(sources)
|
|
if conversation_id:
|
|
params["conversation_id"] = conversation_id
|
|
return params
|
|
|
|
|
|
def _normalize_tool_result(result: OpenClawToolCallResult) -> OpenClawBridgeResponse:
|
|
if result.get("is_error"):
|
|
return _openclaw_unavailable_response(
|
|
str(result.get("text") or "OpenClaw MCP tool call failed."),
|
|
tool_name=str(result.get("tool", "")).strip() or None,
|
|
arguments=result.get("arguments", {}),
|
|
)
|
|
return {
|
|
"source": "openclaw",
|
|
"available": True,
|
|
"tool": result.get("tool"),
|
|
"arguments": result.get("arguments", {}),
|
|
"text": result.get("text", ""),
|
|
"structured_content": result.get("structured_content"),
|
|
"content": result.get("content", []),
|
|
}
|
|
|
|
|
|
def _conversation_rows_from_result(result: OpenClawToolCallResult) -> list[OpenClawConversationRow]:
|
|
structured = result.get("structured_content")
|
|
if isinstance(structured, list):
|
|
return [item for item in structured if isinstance(item, dict)]
|
|
if isinstance(structured, dict):
|
|
conversations = structured.get("conversations")
|
|
if isinstance(conversations, list):
|
|
return [item for item in conversations if isinstance(item, dict)]
|
|
return [structured]
|
|
return []
|
|
|
|
|
|
def _normalize_named_bridge_call(
|
|
config: OpenClawConfig,
|
|
*,
|
|
tool_name: str,
|
|
arguments: OpenClawParams,
|
|
surface_tool_name: str,
|
|
) -> OpenClawBridgeResponse:
|
|
"""Invoke a named MCP tool and normalise its result.
|
|
|
|
``tool_name`` is the MCP-side tool identifier (e.g. ``conversations_get``);
|
|
``surface_tool_name`` is the OpenSRE registered tool name that this call
|
|
is running on behalf of (e.g. ``get_openclaw_conversation``) so the Sentry
|
|
``tool_name`` tag matches the tool's declared metadata.
|
|
"""
|
|
try:
|
|
result = invoke_openclaw_mcp_tool(config, tool_name, arguments)
|
|
except Exception as err:
|
|
report_run_error(
|
|
err,
|
|
tool_name=surface_tool_name,
|
|
source="openclaw",
|
|
component="integrations.openclaw.tools.openclaw_mcp_tool",
|
|
method=f"invoke_openclaw_mcp_tool('{tool_name}')",
|
|
extras={"mcp_tool": tool_name, "transport": config.mode},
|
|
)
|
|
return _openclaw_unavailable_response(
|
|
describe_openclaw_error(err, config),
|
|
tool_name=tool_name,
|
|
arguments=arguments,
|
|
)
|
|
|
|
payload = _normalize_tool_result(result)
|
|
if payload.get("available") is False:
|
|
payload.setdefault("tool", tool_name)
|
|
payload.setdefault("arguments", arguments)
|
|
return payload
|
|
|
|
|
|
@tool(
|
|
name="list_openclaw_tools",
|
|
source="openclaw",
|
|
description=(
|
|
"List tools exposed by the configured OpenClaw MCP bridge. Returns a "
|
|
"compact, bounded listing (names + short descriptions, no schemas) so it "
|
|
"never overflows the agent's context budget. Pass name_filter (e.g. "
|
|
"'conversation event permission') to narrow the list, and include_schema=true "
|
|
"on a narrowed list to fetch the input schema of the tool you intend to call."
|
|
),
|
|
use_cases=[
|
|
"Inspecting which OpenClaw bridge tools are available before making a call",
|
|
"Finding the right tool by passing a name_filter (e.g. 'conversation event permission')",
|
|
"Fetching the input schema of a specific tool with include_schema before calling it",
|
|
],
|
|
surfaces=("investigation", "chat"),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"name_filter": {
|
|
"type": "string",
|
|
"description": (
|
|
"Optional space- or comma-separated terms; tools whose name or "
|
|
"description contains any term are returned (e.g. 'conversation event')."
|
|
),
|
|
},
|
|
"include_schema": {
|
|
"type": "boolean",
|
|
"description": (
|
|
"Include each tool's full input_schema. Only honored when the "
|
|
"(filtered) result set is small; narrow with name_filter first."
|
|
),
|
|
},
|
|
"openclaw_url": {"type": "string"},
|
|
"openclaw_mode": {"type": "string"},
|
|
"openclaw_token": {"type": "string"},
|
|
"openclaw_command": {"type": "string"},
|
|
"openclaw_args": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": [],
|
|
},
|
|
is_available=_openclaw_available,
|
|
extract_params=_openclaw_extract_params,
|
|
)
|
|
def list_openclaw_bridge_tools(
|
|
name_filter: str | None = None,
|
|
include_schema: bool = False,
|
|
openclaw_url: str | None = None,
|
|
openclaw_mode: str | None = None,
|
|
openclaw_token: str | None = None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
**_kwargs: object,
|
|
) -> OpenClawBridgeResponse:
|
|
"""List tools available from the configured OpenClaw MCP bridge.
|
|
|
|
Returns a compact, bounded view by default so the listing never overflows the
|
|
agent's context budget.
|
|
"""
|
|
config = _resolve_config(
|
|
openclaw_url,
|
|
openclaw_mode,
|
|
openclaw_token,
|
|
openclaw_command,
|
|
openclaw_args,
|
|
)
|
|
if config is None:
|
|
payload = _openclaw_unavailable_response("OpenClaw MCP integration is not configured.")
|
|
payload["tools"] = []
|
|
return payload
|
|
|
|
runtime_error = openclaw_runtime_unavailable_reason(config)
|
|
if runtime_error is not None:
|
|
payload = _openclaw_unavailable_response(runtime_error)
|
|
payload["tools"] = []
|
|
return payload
|
|
|
|
try:
|
|
tools = list_openclaw_mcp_tools(config)
|
|
except Exception as err:
|
|
report_run_error(
|
|
err,
|
|
tool_name="list_openclaw_tools",
|
|
source="openclaw",
|
|
component="integrations.openclaw.tools.openclaw_mcp_tool",
|
|
method="list_openclaw_mcp_tools",
|
|
extras={"transport": config.mode},
|
|
)
|
|
payload = _openclaw_unavailable_response(describe_openclaw_error(err, config))
|
|
payload["tools"] = []
|
|
return payload
|
|
|
|
listing = build_mcp_tool_listing(
|
|
[dict(descriptor) for descriptor in tools],
|
|
name_filter=(name_filter or "").strip() or None,
|
|
include_schema=bool(include_schema),
|
|
filter_example="conversation event permission",
|
|
)
|
|
return {
|
|
"source": "openclaw",
|
|
"available": True,
|
|
"transport": config.mode,
|
|
"endpoint": config.command if config.mode == "stdio" else config.url,
|
|
**listing,
|
|
}
|
|
|
|
|
|
@tool(
|
|
name="search_openclaw_conversations",
|
|
source="openclaw",
|
|
description="Search recent OpenClaw conversations through the configured MCP bridge.",
|
|
use_cases=[
|
|
"Checking whether an engineer already discussed the failing service in OpenClaw",
|
|
"Pulling recent OpenClaw context before querying external systems",
|
|
],
|
|
surfaces=("investigation", "chat"),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"search": {"type": "string"},
|
|
"limit": {"type": "integer"},
|
|
"openclaw_url": {"type": "string"},
|
|
"openclaw_mode": {"type": "string"},
|
|
"openclaw_token": {"type": "string"},
|
|
"openclaw_command": {"type": "string"},
|
|
"openclaw_args": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": [],
|
|
},
|
|
is_available=_openclaw_available,
|
|
extract_params=_openclaw_conversation_params,
|
|
)
|
|
def search_openclaw_conversations(
|
|
search: str = "",
|
|
limit: int = 10,
|
|
openclaw_url: str | None = None,
|
|
openclaw_mode: str | None = None,
|
|
openclaw_token: str | None = None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
**_kwargs: object,
|
|
) -> OpenClawBridgeResponse:
|
|
"""Search recent OpenClaw conversations through the MCP bridge."""
|
|
config = _resolve_config(
|
|
openclaw_url,
|
|
openclaw_mode,
|
|
openclaw_token,
|
|
openclaw_command,
|
|
openclaw_args,
|
|
)
|
|
if config is None:
|
|
payload = _openclaw_unavailable_response("OpenClaw MCP integration is not configured.")
|
|
payload["conversations"] = []
|
|
return payload
|
|
|
|
runtime_error = openclaw_runtime_unavailable_reason(config)
|
|
if runtime_error is not None:
|
|
payload = _openclaw_unavailable_response(runtime_error)
|
|
payload["conversations"] = []
|
|
return payload
|
|
|
|
arguments: OpenClawParams = {
|
|
"limit": max(1, min(limit, 25)),
|
|
"includeDerivedTitles": True,
|
|
"includeLastMessage": True,
|
|
}
|
|
if search.strip():
|
|
arguments["search"] = search.strip()
|
|
|
|
try:
|
|
result = invoke_openclaw_mcp_tool(config, "conversations_list", arguments)
|
|
except Exception as err:
|
|
report_run_error(
|
|
err,
|
|
tool_name="search_openclaw_conversations",
|
|
source="openclaw",
|
|
component="integrations.openclaw.tools.openclaw_mcp_tool",
|
|
method="invoke_openclaw_mcp_tool('conversations_list')",
|
|
extras={"transport": config.mode},
|
|
)
|
|
payload = _openclaw_unavailable_response(describe_openclaw_error(err, config))
|
|
payload["conversations"] = []
|
|
return payload
|
|
|
|
payload = _normalize_tool_result(result)
|
|
payload["search"] = search.strip()
|
|
payload["conversations"] = _conversation_rows_from_result(result)
|
|
return payload
|
|
|
|
|
|
@tool(
|
|
name="get_openclaw_conversation",
|
|
source="openclaw",
|
|
description="Fetch one OpenClaw conversation by id through the configured MCP bridge.",
|
|
use_cases=[
|
|
"Reading the full context of an OpenClaw conversation that may explain the active alert",
|
|
"Pulling the latest assistant and engineer messages before continuing an investigation",
|
|
],
|
|
requires=["conversation_id"],
|
|
surfaces=("investigation", "chat"),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"conversation_id": {"type": "string"},
|
|
"openclaw_url": {"type": "string"},
|
|
"openclaw_mode": {"type": "string"},
|
|
"openclaw_token": {"type": "string"},
|
|
"openclaw_command": {"type": "string"},
|
|
"openclaw_args": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": ["conversation_id"],
|
|
},
|
|
is_available=_openclaw_available,
|
|
extract_params=_openclaw_conversation_detail_params,
|
|
)
|
|
def get_openclaw_conversation(
|
|
conversation_id: str | None = None,
|
|
openclaw_url: str | None = None,
|
|
openclaw_mode: str | None = None,
|
|
openclaw_token: str | None = None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
**_kwargs: object,
|
|
) -> OpenClawBridgeResponse:
|
|
"""Fetch a specific OpenClaw conversation."""
|
|
normalized_conversation_id = (conversation_id or "").strip()
|
|
if not normalized_conversation_id:
|
|
return _openclaw_unavailable_response("conversation_id is required.")
|
|
|
|
config = _resolve_config(
|
|
openclaw_url,
|
|
openclaw_mode,
|
|
openclaw_token,
|
|
openclaw_command,
|
|
openclaw_args,
|
|
)
|
|
if config is None:
|
|
return _openclaw_unavailable_response("OpenClaw MCP integration is not configured.")
|
|
|
|
runtime_error = openclaw_runtime_unavailable_reason(config)
|
|
if runtime_error is not None:
|
|
return _openclaw_unavailable_response(runtime_error)
|
|
|
|
return _normalize_named_bridge_call(
|
|
config,
|
|
tool_name="conversations_get",
|
|
arguments={"conversationId": normalized_conversation_id},
|
|
surface_tool_name="get_openclaw_conversation",
|
|
)
|
|
|
|
|
|
@tool(
|
|
name="send_openclaw_message",
|
|
source="openclaw",
|
|
description="Send a message into an existing OpenClaw conversation.",
|
|
use_cases=[
|
|
"Writing investigation findings back into a conversation an engineer is already using",
|
|
"Appending a short remediation note or next-step summary to an OpenClaw thread",
|
|
],
|
|
requires=["conversation_id"],
|
|
surfaces=("investigation", "chat"),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"conversation_id": {"type": "string"},
|
|
"content": {"type": "string"},
|
|
"openclaw_url": {"type": "string"},
|
|
"openclaw_mode": {"type": "string"},
|
|
"openclaw_token": {"type": "string"},
|
|
"openclaw_command": {"type": "string"},
|
|
"openclaw_args": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": ["conversation_id", "content"],
|
|
},
|
|
is_available=_openclaw_available,
|
|
extract_params=_openclaw_conversation_detail_params,
|
|
)
|
|
def send_openclaw_message(
|
|
conversation_id: str | None = None,
|
|
content: str | None = None,
|
|
openclaw_url: str | None = None,
|
|
openclaw_mode: str | None = None,
|
|
openclaw_token: str | None = None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
**_kwargs: object,
|
|
) -> OpenClawBridgeResponse:
|
|
"""Send a message into an OpenClaw conversation."""
|
|
normalized_conversation_id = (conversation_id or "").strip()
|
|
normalized_content = (content or "").strip()
|
|
if not normalized_conversation_id:
|
|
return _openclaw_unavailable_response("conversation_id is required.")
|
|
if not normalized_content:
|
|
return _openclaw_unavailable_response("content is required.")
|
|
|
|
config = _resolve_config(
|
|
openclaw_url,
|
|
openclaw_mode,
|
|
openclaw_token,
|
|
openclaw_command,
|
|
openclaw_args,
|
|
)
|
|
if config is None:
|
|
return _openclaw_unavailable_response("OpenClaw MCP integration is not configured.")
|
|
|
|
runtime_error = openclaw_runtime_unavailable_reason(config)
|
|
if runtime_error is not None:
|
|
return _openclaw_unavailable_response(runtime_error)
|
|
|
|
return _normalize_named_bridge_call(
|
|
config,
|
|
tool_name="message_send",
|
|
arguments={"conversationId": normalized_conversation_id, "content": normalized_content},
|
|
surface_tool_name="send_openclaw_message",
|
|
)
|
|
|
|
|
|
@tool(
|
|
name="call_openclaw_tool",
|
|
source="openclaw",
|
|
description="Call a named tool exposed by the configured OpenClaw MCP bridge.",
|
|
use_cases=[
|
|
"Reading OpenClaw conversations and recent transcript history",
|
|
"Polling OpenClaw event queues or responding through an existing route",
|
|
],
|
|
requires=["tool_name"],
|
|
surfaces=("investigation", "chat"),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"tool_name": {"type": "string"},
|
|
"arguments": {"type": "object"},
|
|
"openclaw_url": {"type": "string"},
|
|
"openclaw_mode": {"type": "string"},
|
|
"openclaw_token": {"type": "string"},
|
|
"openclaw_command": {"type": "string"},
|
|
"openclaw_args": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": ["tool_name"],
|
|
},
|
|
is_available=_openclaw_available,
|
|
extract_params=_openclaw_extract_params,
|
|
)
|
|
def call_openclaw_bridge_tool(
|
|
tool_name: str | None = None,
|
|
arguments: OpenClawParams | None = None,
|
|
openclaw_url: str | None = None,
|
|
openclaw_mode: str | None = None,
|
|
openclaw_token: str | None = None,
|
|
openclaw_command: str | None = None,
|
|
openclaw_args: list[str] | None = None,
|
|
**_kwargs: object,
|
|
) -> OpenClawBridgeResponse:
|
|
"""Call a specific OpenClaw MCP bridge tool."""
|
|
normalized_tool_name = (tool_name or "").strip()
|
|
if not normalized_tool_name:
|
|
return _openclaw_unavailable_response(
|
|
"tool_name is required to call an OpenClaw MCP tool.",
|
|
arguments=arguments or {},
|
|
)
|
|
|
|
config = _resolve_config(
|
|
openclaw_url,
|
|
openclaw_mode,
|
|
openclaw_token,
|
|
openclaw_command,
|
|
openclaw_args,
|
|
)
|
|
if config is None:
|
|
return _openclaw_unavailable_response(
|
|
"OpenClaw MCP integration is not configured.",
|
|
tool_name=normalized_tool_name or None,
|
|
arguments=arguments or {},
|
|
)
|
|
|
|
runtime_error = openclaw_runtime_unavailable_reason(config)
|
|
if runtime_error is not None:
|
|
return _openclaw_unavailable_response(
|
|
runtime_error,
|
|
tool_name=normalized_tool_name,
|
|
arguments=arguments or {},
|
|
)
|
|
|
|
try:
|
|
result = invoke_openclaw_mcp_tool(config, normalized_tool_name, arguments or {})
|
|
except Exception as err:
|
|
report_run_error(
|
|
err,
|
|
tool_name="call_openclaw_tool",
|
|
source="openclaw",
|
|
component="integrations.openclaw.tools.openclaw_mcp_tool",
|
|
method="invoke_openclaw_mcp_tool",
|
|
extras={"mcp_tool": normalized_tool_name, "transport": config.mode},
|
|
)
|
|
return _openclaw_unavailable_response(
|
|
describe_openclaw_error(err, config),
|
|
tool_name=normalized_tool_name,
|
|
arguments=arguments or {},
|
|
)
|
|
|
|
return _normalize_tool_result(result)
|