Source code for aidputils.agents.tools.base_tool
from abc import ABC, abstractmethod
from typing import final
from aidputils.agents.toolkit import chat_context
import oci
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
[docs]
class BaseTool(ABC):
tool_class_registry = {}
tool_function_registry = {}
[docs]
@classmethod
@final
def register(cls, subclass):
"""
Decorator for automatic tool registration.
Usage:
@BaseTool.register
class HttpTool(BaseTool):
...
This will automatically add HttpTool to the global tool registry,
allowing it to be discovered and invoked without explicit manual registration.
Implementors should use this decorator on any new tool class they want
to make available for agent usage.
"""
cls.tool_class_registry[subclass.__name__] = subclass
return subclass
[docs]
@classmethod
def logger(cls, tool_name=None):
"""
Returns a logger for the given tool name (or class).
Args:
tool_name (str): Optional. If provided, used as logger name and for caching.
If not provided, uses the class name.
"""
# Ensure _loggers cache exists
if not hasattr(cls, "_loggers"):
cls._loggers = {}
name = tool_name if tool_name else cls.__name__
# If a logger for this name doesn't exist, create and cache it.
if name not in cls._loggers:
new_logger = logging.getLogger(name)
cls._loggers[name] = new_logger
return cls._loggers[name]
[docs]
@classmethod
@final
def invoke(cls, conf, runtime_params, **context_vars) -> dict:
token = chat_context.tool_context_var.set(cls.__name__)
try:
response = cls._invoke_tool(conf, runtime_params, **context_vars)
mcp_format_response = cls._format_output_as_mcp(response)
return mcp_format_response
finally:
if token:
chat_context.tool_context_var.reset(token)
[docs]
@classmethod
def make_security_token_signer(cls, oci_config):
pk = oci.signer.load_private_key_from_file(oci_config.get("key_file"), None)
with open(oci_config.get("security_token_file")) as f:
st_string = f.read()
return oci.auth.signers.SecurityTokenSigner(st_string, pk)
@classmethod
@abstractmethod
def _invoke_tool(cls, conf, runtime_params, **context_vars) -> dict:
raise NotImplementedError("Each tool must implement its own _invoke_tool method.")
@classmethod
@abstractmethod
def _format_output_as_mcp(cls, response):
raise NotImplementedError("Each tool must implement its own format_output_as_mcp method.")
# ========== MCP reference methods (for future MCP compliance) ==========
[docs]
@classmethod
@final
def register_tool_function(cls, conf):
"""
Reference-only: For future MCP compliance, not for immediate agent or tool use.
"""
cls.tool_function_registry[conf["name"]] = conf
[docs]
@classmethod
@final
def list_tools(cls):
"""
Reference-only: Returns all MCP tool schemas in the local registry.
"""
tools_schema = [cls._generate_mcp_schema(conf) for tool_name, conf in cls.tool_class_registry.items()]
return {"tools": tools_schema}
@classmethod
@final
def _tool_schema(cls, tool_name):
"""
Reference-only: Returns MCP tool schema for a given registered tool.
"""
return cls._generate_mcp_schema(cls.tool_class_registry[tool_name])
@staticmethod
def _generate_mcp_schema(conf):
"""
Reference-only: Returns an MCP-compliant schema using the param specs found in conf["params"].
Expects conf = {..., "params": {param_name: {"name": str, "description": str, "type": str}}}
"""
params = conf.get("params", {})
schema_properties = {}
required = []
for param_key, param_info in params.items():
name = param_info.get("name", param_key)
schema_properties[name] = {
"type": param_info.get("type", "string"),
"description": param_info.get("description", "")
}
required.append(name)
input_schema = {
"type": "object",
"properties": schema_properties,
"required": required
}
return {
"description": conf.get("description", ""),
"name": conf.get("name", ""),
"inputSchema": input_schema
}
# ========== End MCP reference methods ==========