Skip to content

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 control instruments at experiment endstations, we strongly recommend following the async-safe server pattern to avoid issues related to thread-safety and async event 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