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