Tools#

BaseTool#

EAA tools are stateful Python objects derived from BaseTool. Tool methods are exposed by decorating them with @tool(name=...).

A typical tool looks like this:

from eaa_core.tool.base import BaseTool, tool


class ExampleTool(BaseTool):
    @tool(name="add")
    def add(self, a: float, b: float) -> float:
        return a + b

Tool execution normalizes every result into a JSON object. Scalar returns are wrapped as {"result": ...}, while image-producing tools should surface the path through {"img_path": "..."}.

When a BaseTool instance is created, it discovers decorated methods and builds exposed_tools metadata that the task manager can register with the model-facing tool executor.

Thread safety and serial execution#

EAA intentionally executes tools serially through SerialToolExecutor.

Why this matters:

  • many experiment tools are stateful

  • tool calls often mutate instrument state

  • parallel tool calls would make ordering and rollback ambiguous

The executor therefore runs assistant-requested tool calls one at a time and records a normalized tool message for each result. This is the default safety model for the current codebase.

Approval gates#

Each tool instance can require approval by setting require_approval=True. When a tool call needs approval, the task manager routes the decision through its normal input path, including the WebUI path when use_webui=True.

Serving built-in tools as MCP servers#

EAA can expose any BaseTool instance as an MCP server by wrapping it with the helpers in eaa_core.tool.mcp_server.

from eaa_core.tool.mcp_server import run_mcp_server_from_tools
from eaa_core.tool.example_calculator import CalculatorTool

run_mcp_server_from_tools(
    tools=CalculatorTool(),
    server_name="Calculator MCP Server",
)

The MCP wrapper preserves the normalized EAA JSON result contract by publishing an object output schema.

Using external MCP servers#

EAA can also consume remote MCP tools through MCPTool in eaa_core.tool.mcp_client. The wrapper connects to one or more MCP servers using a FastMCP-compatible configuration and exposes the remote tools through the normal BaseTool interface.

To create MCP tool servers that contro instruments at experiment endstations, we strongly recommend following the async-safe server pattern to avoid issues related to thread-safety and async even loop conflicts. Find more details in Creating Async-Safe MCP Servers.

from eaa_core.tool.mcp_client import MCPTool

mcp_tool = MCPTool(
    {
        "mcpServers": {
            "image_acquisition": {
                "command": "python",
                "args": ["./image_acquisition_mcp_server.py"],
            }
        }
    }
)

task_manager.register_tools(mcp_tool)

To connect to an MCP server over HTTP from a different machine, configure the client with a named entry under mcpServers and point it at the server’s MCP endpoint:

from eaa_core.tool.mcp_client import MCPTool

mcp_tool = MCPTool(
    {
        "mcpServers": {
            "calculator": {
                "url": "http://SERVER_IP:8050/mcp",
                "transport": "http",
            }
        }
    }
)

The server side should be started with HTTP transport enabled, for example:

from eaa_core.tool.mcp_server import run_mcp_server_from_tools
from eaa_core.tool.example_calculator import CalculatorTool

run_mcp_server_from_tools(
    tools=CalculatorTool(),
    server_name="Calculator MCP Server",
    transport="http",
    host="0.0.0.0",
    port=8050,
    path="/mcp",
)

Do not pass only {"url": ..., "transport": "http"} to MCPTool. The FastMCP client expects a full config object with one or more named servers inside mcpServers.

Notes:

  • EAA normalizes tool results to JSON for tools served through the EAA MCP server helper

  • arbitrary third-party MCP servers may still return non-EAA payloads, so the agent loop only treats results as image-bearing when an img_path is present

  • chat interactions and agent-selected tool calls do not require external MCP servers to use EAA-specific tool names; only logic-driven task managers that call tool methods directly need the adapter contracts documented in Creating Async-Safe MCP Servers