Skip to content

Tools

Tools gives you full CRUD access to the tools and toolsets defined in a CXAS app. It understands both simple Python function tools and more complex OpenAPI toolsets — so whether you're creating a lightweight helper function or wiring up an external REST API, this class has you covered.

Beyond management, Tools can also execute a tool directly via execute_tool() — without needing to run a full agent session. This is incredibly useful for unit testing tools in isolation or debugging unexpected responses.

Quick Example

from cxas_scrapi import Tools

app_name = "projects/my-project/locations/us/apps/my-app-id"
tools = Tools(app_name=app_name)

# List everything
all_tools = tools.list_tools()
for t in all_tools:
    print(t.display_name, t.name)

# Get a name-to-display-name map
tools_map = tools.get_tools_map()

# Execute a tool directly (great for testing)
result = tools.execute_tool(
    tool_display_name="lookup_account",
    args={"customer_id": "C-1234"},
)
print(result)

# Create a new Python function tool
tools.create_tool(
    tool_id="greet_user",
    display_name="greet_user",
    payload={
        "python_code": "def greet_user(name: str) -> dict:\n    return {'greeting': f'Hello, {name}!'}",
    },
    tool_type="python_function",
)

Reference

Tools

Tools(app_name, creds_path=None, creds_dict=None, creds=None, scope=None, **kwargs)

Bases: Apps

Core Class for managing Tool and Toolset Resources.

Initializes the Tools client.

Source code in src/cxas_scrapi/core/tools.py
def __init__(
    self,
    app_name: str,
    creds_path: str = None,
    creds_dict: Dict[str, str] = None,
    creds: Any = None,
    scope: List[str] = None,
    **kwargs,
):
    """Initializes the Tools client."""
    project_id = app_name.split("/")[1]
    location = app_name.split("/")[3]

    super().__init__(
        project_id=project_id,
        location=location,
        creds_path=creds_path,
        creds_dict=creds_dict,
        creds=creds,
        scope=scope,
        **kwargs,
    )
    self.app_name = app_name
    self.app_id = app_name.rsplit("/", maxsplit=1)[-1]
    self.resource_type = "tools"
    self.client = AgentServiceClient(
        transport=self.get_grpc_transport(AgentServiceClient),
        client_info=self.client_info,
    )
    self.tool_client = ToolServiceClient(
        transport=self.get_grpc_transport(ToolServiceClient),
        client_info=self.client_info,
    )
    self.var_client = Variables(
        app_name=app_name,
        creds_path=creds_path,
        creds_dict=creds_dict,
        creds=creds,
        scope=scope,
    )
    self.tools_map: Dict[str, str] = {}

get_tools_map

get_tools_map(reverse=False)

Creates a map of Tool and Toolset full names to display names.

Parameters:

Name Type Description Default
reverse bool

If True, map display_name -> name.

False
Source code in src/cxas_scrapi/core/tools.py
def get_tools_map(self, reverse: bool = False) -> Dict[str, str]:
    """Creates a map of Tool and Toolset full names to display names.

    Args:
        reverse: If True, map display_name -> name.
    """
    tools = self.list_tools()
    tools_dict: Dict[str, str] = {}

    for tool in tools:
        if self._is_toolset(tool.name):
            # Try to parse OpenAPI toolsets locally to avoid excessive
            # API calls
            if getattr(tool, "open_api_toolset", None):
                schema_str = getattr(
                    tool.open_api_toolset, "open_api_schema", None
                )
                if schema_str:
                    openapi_tools = self._parse_openapi_schema(
                        schema_str, tool.display_name, tool.name, reverse
                    )
                    tools_dict.update(openapi_tools)
            else:
                toolset_tools = self.retrieve_tools(
                    tool.name.split("/")[-1]
                )
                for toolset_tool in toolset_tools.tools:
                    if reverse:
                        tools_dict[toolset_tool.display_name] = (
                            toolset_tool.name
                        )
                    else:
                        tools_dict[toolset_tool.name] = (
                            toolset_tool.display_name
                        )
        elif reverse:
            tools_dict[tool.display_name] = tool.name
        else:
            tools_dict[tool.name] = tool.display_name

    return tools_dict

list_tools

list_tools()

Lists both tools and toolsets within a specific app.

Source code in src/cxas_scrapi/core/tools.py
def list_tools(self) -> List[Any]:
    """Lists both tools and toolsets within a specific app."""
    tools_request = types.ListToolsRequest(parent=self.app_name)
    tools_response = self.client.list_tools(request=tools_request)

    toolsets_request = types.ListToolsetsRequest(parent=self.app_name)
    toolsets_response = self.client.list_toolsets(request=toolsets_request)

    return list(tools_response) + list(toolsets_response)

get_tool

get_tool(tool_name)

Gets a specific tool or toolset by full resource name.

Source code in src/cxas_scrapi/core/tools.py
def get_tool(self, tool_name: str) -> Any:
    """Gets a specific tool or toolset by full resource name."""
    if self._is_toolset(tool_name):
        request = types.GetToolsetRequest(name=tool_name)
        return self.client.get_toolset(request=request)
    else:
        request = types.GetToolRequest(name=tool_name)
        return self.client.get_tool(request=request)

create_tool

create_tool(tool_id, display_name, payload, tool_type='python_function', description='')

Creates a new tool or toolset.

If tool_type implies a toolset, it creates a Toolset wrapper (e.g. open_api_toolset). Otherwise it creates a standard Tool wrapper (e.g. python_function).

Source code in src/cxas_scrapi/core/tools.py
def create_tool(
    self,
    tool_id: str,
    display_name: str,
    payload: Dict[str, Any],
    tool_type: str = "python_function",
    description: str = "",
) -> Any:
    """Creates a new tool or toolset.

    If tool_type implies a toolset, it creates a Toolset wrapper
    (e.g. open_api_toolset). Otherwise it creates a standard Tool wrapper
    (e.g. python_function).
    """
    is_toolset = tool_type in [
        "open_api_toolset",
        "connector_toolset",
        "mcp_toolset",
    ]

    payload_copy = payload.copy()
    payload_copy.pop("display_name", None)

    if is_toolset:
        desc = payload_copy.pop("description", description)
        kwargs = {
            "display_name": display_name,
            "description": desc,
            tool_type: payload_copy,
        }
        toolset = types.Toolset(**kwargs)
        request = types.CreateToolsetRequest(
            parent=self.app_name, toolset_id=tool_id, toolset=toolset
        )
        return self.client.create_toolset(request=request)
    else:
        if description and "description" not in payload_copy:
            payload_copy["description"] = description

        kwargs = {"display_name": display_name, tool_type: payload_copy}
        tool = types.Tool(**kwargs)
        request = types.CreateToolRequest(
            parent=self.app_name, tool_id=tool_id, tool=tool
        )
        return self.client.create_tool(request=request)

update_tool

update_tool(tool_name, **kwargs)

Updates specific fields of an existing Tool or Toolset.

Source code in src/cxas_scrapi/core/tools.py
def update_tool(self, tool_name: str, **kwargs) -> Any:
    """Updates specific fields of an existing Tool or Toolset."""
    mask_paths = list(kwargs.keys())

    if self._is_toolset(tool_name):
        toolset = types.Toolset(name=tool_name)
        for key, value in kwargs.items():
            setattr(toolset, key, value)

        request = types.UpdateToolsetRequest(
            toolset=toolset,
            update_mask=field_mask_pb2.FieldMask(paths=mask_paths),
        )
        return self.client.update_toolset(request=request)
    else:
        tool = types.Tool(name=tool_name)
        for key, value in kwargs.items():
            setattr(tool, key, value)

        request = types.UpdateToolRequest(
            tool=tool,
            update_mask=field_mask_pb2.FieldMask(paths=mask_paths),
        )
        return self.client.update_tool(request=request)

delete_tool

delete_tool(tool_name)

Deletes a specific tool or toolset.

Source code in src/cxas_scrapi/core/tools.py
def delete_tool(self, tool_name: str) -> None:
    """Deletes a specific tool or toolset."""
    if self._is_toolset(tool_name):
        request = types.DeleteToolsetRequest(name=tool_name)
        self.client.delete_toolset(request=request)
    else:
        request = types.DeleteToolRequest(name=tool_name)
        self.client.delete_tool(request=request)

execute_tool

execute_tool(tool_display_name, args=None, variables=None, context=None)

Executes a tool directly via the CES API.

Parameters:

Name Type Description Default
tool_display_name str

The display name of the tool (or toolset key).

required
args Optional[Dict[str, Any]]

Dictionary of arguments for the tool.

None
variables Optional[Any]

Can be: - None: Fetches and passes ALL variables from the app. - List[str]: Fetches variables from the app and filters by this list of names. - Dict[str, Any]: Uses the provided dictionary directly (e.g. from Evals).

None
context Optional[Dict[str, Any]]

ToolContext object available to the Python Function tool. If context is provided, variables will be ignored.

None

Returns:

Type Description
Any

The tool execution response (JSON or Object).

Source code in src/cxas_scrapi/core/tools.py
def execute_tool(
    self,
    tool_display_name: str,
    args: Optional[Dict[str, Any]] = None,
    variables: Optional[Any] = None,  # Accepts Dict, List[str], or None
    context: Optional[Dict[str, Any]] = None,
) -> Any:
    """Executes a tool directly via the CES API.

    Args:
        tool_display_name: The display name of the tool (or toolset key).
        args: Dictionary of arguments for the tool.
        variables: Can be:
            - None: Fetches and passes ALL variables from the app.
            - List[str]: Fetches variables from the app and filters by
              this list of names.
            - Dict[str, Any]: Uses the provided dictionary directly
              (e.g. from Evals).
        context: ToolContext object available to the Python Function tool.
                 If context is provided, variables will be ignored.

    Returns:
        The tool execution response (JSON or Object).
    """
    # Use HTTP REST request instead of SDK because the current SDK version
    # is missing the 'variables' field in ExecuteToolRequest proto
    url = f"https://ces.googleapis.com/v1beta/{self.app_name}:executeTool"

    headers = {
        "Authorization": f"Bearer {self.creds.token}",
        "Content-Type": "application/json",
        "x-goog-user-project": self.project_id,
        "User-Agent": self.user_agent,
    }

    payload = {}

    tools_map = self._get_or_load_tools_map()
    tool_name = tools_map.get(tool_display_name)

    if not tool_name:
        raise ValueError(
            f"Tool '{tool_display_name}' not found in App "
            f"'{self.app_name}'. "
        )

    if "toolsets/" in tool_name and "/tools/" in tool_name:
        toolset_name, tool_id = tool_name.split("/tools/")
        payload["toolsetTool"] = {
            "toolset": toolset_name,
            "toolId": tool_id,
        }
    else:
        payload["tool"] = tool_name

    payload["args"] = args or {}

    final_variables = self._get_final_variables(variables, context)

    # Use context if provided, otherwise use variables.
    if context:
        context_copy = context.copy()
        if "state" in context_copy:
            context_copy["state"] = final_variables
        payload["context"] = context_copy
    else:
        payload["variables"] = final_variables

    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()

    return response.json()

retrieve_tools

retrieve_tools(toolset_id)

Retrieves all tools in a toolset.

Source code in src/cxas_scrapi/core/tools.py
def retrieve_tools(self, toolset_id: str) -> Any:
    """Retrieves all tools in a toolset."""
    request = types.RetrieveToolsRequest(
        toolset=f"{self.app_name}/toolsets/{toolset_id}"
    )
    return self.tool_client.retrieve_tools(request=request)

retrieve_tool_schema

retrieve_tool_schema(tool_name)

Retrieves all tools in a toolset.

Source code in src/cxas_scrapi/core/tools.py
def retrieve_tool_schema(self, tool_name: str) -> Any:
    """Retrieves all tools in a toolset."""
    if "/toolsets/" not in tool_name:
        request = types.RetrieveToolSchemaRequest(
            parent=self.app_name, tool=tool_name
        )
        return self.tool_client.retrieve_tool_schema(request=request)
    toolset_name, tool_id = tool_name.split("/tools/")
    request = types.RetrieveToolSchemaRequest(
        parent=self.app_name,
        toolset_tool=types.ToolsetTool(
            toolset=toolset_name, tool_id=tool_id
        ),
    )
    return self.tool_client.retrieve_tool_schema(request=request)