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_pathis presentchat 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