Skip to main content

A guide for coding agents to create agents that can invoke tools

Enhance Autonomy agents with tools that perform specific actions, access external data, and integrate with external services using Python functions or MCP servers.
Key Terms: See the definitions section of the main guide.
Keywords: tools, python tools, mcp tools, web search This guide shows how to create agents with both Python tools and MCP tools for external service integration.

Built-in Tools

Autonomy automatically provides built-in tools to agents:

Always Available

These tools are automatically available to all agents without any configuration:
  • get_current_time_utc - Get the current time in UTC timezone
  • get_current_time - Get the current time in a specific timezone (e.g., “America/New_York”, “Europe/London”, “Asia/Tokyo”)

Available When Enabled

  • ask_user_for_input - Request input from the user (human-in-the-loop)
    • Requires: enable_ask_for_user_input=True when calling Agent.start()
    • Default: Not enabled (defaults to False)

Subagent Tools

When an agent has subagents configured, these tools are automatically added:
  • start_subagent - Start a configured subagent on-demand
  • delegate_to_subagent - Delegate a task to a specific subagent and wait for response
  • delegate_to_subagents_parallel - Delegate multiple tasks to parallel subagent instances
  • list_subagents - List all configured and running subagents with their status
  • stop_subagent - Stop a running subagent

Creating Agents with Tools

Autonomy agents can use two types of custom tools:
  1. Python Tools - Functions defined directly in your code
  2. MCP Tools - Tools from Model Context Protocol servers

Complete Example: Agent with Both Tool Types

This example shows an agent with a Python tool (time) and MCP tool (web search):

Project Structure

your-app/
├── autonomy.yaml
├── secrets.yaml
└── images/main/
    ├── Dockerfile
    └── main.py

main.py

from datetime import datetime, UTC
from autonomy import Agent, Model, Node, McpTool, McpClient, Tool


def current_iso8601_utc_time():
    """
    Returns the current UTC time in ISO 8601 format.
    """
    return datetime.now(UTC).isoformat() + "Z"


async def main(node):
    await Agent.start(
        node=node,
        name="henry",
        instructions="""
            You are Henry, an expert legal assistant.

            When answering questions, use web search if it improves your response.
        """,
        model=Model("claude-sonnet-4-v1"),
        tools=[
            McpTool("brave_search", "brave_web_search"),
            Tool(current_iso8601_utc_time),
        ],
    )


Node.start(
    main,
    mcp_clients=[
        McpClient(name="brave_search", address="http://localhost:8001/sse"),
    ],
)

autonomy.yaml

name: example006
pods:
  - name: main-pod
    public: true
    containers:
      - name: main
        image: main

      - name: brave
        image: ghcr.io/build-trust/mcp-proxy
        env:
          - BRAVE_API_KEY: secrets.BRAVE_API_KEY
        args:
          ["--sse-port", "8001", "--pass-environment", "--",
           "npx", "-y", "@modelcontextprotocol/server-brave-search"]

secrets.yaml

BRAVE_API_KEY: "YOUR_BRAVE_API_KEY_HERE"
Get a Brave API key by signing up at https://brave.com/search/api/

Dockerfile

FROM ghcr.io/build-trust/autonomy-python:latest
COPY . .
ENTRYPOINT ["python", "main.py"]

Deploy and Test

Deploy your zone:
autonomy zone deploy
Test the agent:
timeout 15s curl --silent --request POST \
--header "Content-Type: application/json" \
--data '{ "message": "Which companies were acquired today?" }' \
"https://${CLUSTER}-${ZONE}.cluster.autonomy.computer/agents/henry"

How It Works

Python Tools: Define functions and wrap them with Tool(). The agent calls these functions directly in your code. MCP Tools: Configure MCP servers in autonomy.yaml as containers using ghcr.io/build-trust/mcp-proxy. Register them with McpClient() and reference with McpTool(). The MCP proxy runs alongside your agent in the same pod. Tool Selection: The agent analyzes the user’s message and your instructions to decide which tools to use and when.

MCP Servers in Multi-Pod Deployments

IMPORTANT: MCP servers are accessed over localhost. When your zone has nodes distributed across multiple pods, each pod that needs MCP tools must have its own MCP server container.

Why This Matters

Containers within the same pod share a network namespace and communicate via localhost. When you distribute nodes across multiple pods (using clones or separate pod definitions), each pod runs on a different machine. An agent running in one pod cannot access MCP servers in another pod over localhost.

Example: Distributed Workers with MCP Tools

If you have workers across multiple pods that need MCP tools, configure the MCP server in each pod:

autonomy.yaml

name: distributed
pods:
  - name: main-pod
    public: true
    containers:
      - name: main
        image: main
      - name: brave
        image: ghcr.io/build-trust/mcp-proxy
        env:
          - BRAVE_API_KEY: secrets.BRAVE_API_KEY
        args:
          ["--sse-port", "8001", "--pass-environment", "--",
           "npx", "-y", "@modelcontextprotocol/server-brave-search"]

  - name: runner-pod
    clones: 3
    containers:
      - name: runner
        image: runner
      - name: brave
        image: ghcr.io/build-trust/mcp-proxy
        env:
          - BRAVE_API_KEY: secrets.BRAVE_API_KEY
        args:
          ["--sse-port", "8001", "--pass-environment", "--",
           "npx", "-y", "@modelcontextprotocol/server-brave-search"]

images/runner/main.py

from autonomy import Agent, Model, Node, McpTool, McpClient


async def main(node):
    await Agent.start(
        node=node,
        name="searcher",
        instructions="You search the web for information.",
        model=Model("nova-micro-v1"),
        tools=[
            McpTool("brave_search", "brave_web_search"),
        ],
    )


Node.start(
    main,
    mcp_clients=[
        McpClient(name="brave_search", address="http://localhost:8001/sse"),
    ],
)
Key Points:
  • Both main-pod and runner-pod have their own brave container - Each pod needs its own MCP server
  • Each clone of runner-pod gets its own MCP server - The 3 clones each run independently with their own brave container
  • All MCP clients use localhost - Each node connects to the MCP server in its own pod
  • Environment variables are duplicated - Each pod needs the same secrets configuration

Decision Guide

ScenarioMCP Server Configuration
Single pod with one nodeOne MCP server container in that pod
Single pod with multiple agentsOne MCP server container shared by all agents
Multiple pods with clonesMCP server container in each pod definition
Only main-pod needs MCP toolsMCP server only in main-pod
All pods need MCP toolsMCP server in every pod definition

Using Built-in Time Tools

Agents can check the current time without any configuration. The built-in time tools are automatically available:

Example: Agent with Time Awareness

from autonomy import Agent, Model, Node


async def main(node):
    await Agent.start(
        node=node,
        name="henry",
        instructions="""
            You are Henry, a helpful assistant.
            
            When users ask about time, use the built-in time tools:
            - Use get_current_time_utc() for UTC time
            - Use get_current_time(timezone="...") for specific timezones
        """,
        model=Model("claude-sonnet-4-v1"),
    )


Node.start(main)

Test Time Tools

# Test UTC time
timeout 15s curl --silent --request POST \
--header "Content-Type: application/json" \
--data '{ "message": "What is the current UTC time?" }' \
"https://${CLUSTER}-${ZONE}.cluster.autonomy.computer/agents/henry"

# Test timezone-specific time
timeout 15s curl --silent --request POST \
--header "Content-Type: application/json" \
--data '{ "message": "What time is it in Tokyo?" }' \
"https://${CLUSTER}-${ZONE}.cluster.autonomy.computer/agents/henry"
The agent will automatically use:
  • get_current_time_utc() to get UTC time
  • get_current_time(timezone="Asia/Tokyo") to get Tokyo time

Using the Ask User for Input Tool

The ask_user_for_input tool enables human-in-the-loop interactions where the agent can pause and request input from the user.

Example: Agent with User Input Capability

from autonomy import Agent, Model, Node


async def main(node):
    await Agent.start(
        node=node,
        name="henry",
        instructions="""
            You are Henry, an expert legal assistant.
            
            When you need information from the user, use ask_user_for_input.
        """,
        model=Model("claude-sonnet-4-v1"),
        enable_ask_for_user_input=True,  # Enable the tool
    )


Node.start(main)
Key Points:
  • Set enable_ask_for_user_input=True when calling Agent.start()
  • The agent can call this tool to pause and wait for user response
  • The conversation resumes after the user provides input
  • Useful for gathering missing information, confirmations, or clarifications