Tools

Tools are the actions the LLM can take. Every tool call is executed by Mango and the result is fed back to the LLM.

Built-in tools

Register all built-in MongoDB tools with one call:

from mango.tools import build_mongo_tools
from mango.tools.base import ToolRegistry

tools = ToolRegistry()
for tool in build_mongo_tools(db):
    tools.register(tool)

Available tools

ToolDescription
list_collectionsList all collections. For databases with more than 50 collections, returns a grouped view by name pattern.
search_collectionsSearch collection names by glob pattern (order*, *_log).
describe_collectionReturn schema, indexes and sample documents for a single collection.
inspect_fieldDistinct values and type distribution of one field — for checking exact encodings before filtering.
collection_statsDocument count for every collection, sorted largest first. Optional top_n.
run_mqlExecute a read-only query: find, aggregate, count, distinct. Includes automatic MQL validation.

build_mongo_tools(backend, max_rows=100, validate=True) returns exactly these six. save_text_memory (SaveTextMemoryTool(memory)) is not included — register it manually if you want the LLM to store domain notes.

run_mql in detail

This is the main query tool. The LLM constructs a QueryRequest and passes it as JSON arguments:

# find — filter + projection + sort + limit
run_mql(
    operation="find",
    collection="orders",
    filter={"status": "delivered"},
    projection={"_id": 0, "order_id": 1, "total": 1},
    sort={"created_at": -1},
    limit=20,
)

# aggregate — full pipeline
run_mql(
    operation="aggregate",
    collection="orders",
    pipeline=[
        {"$match": {"created_at": {"$gte": "2026-01-01"}}},
        {"$group": {"_id": "$user_id", "total": {"$sum": "$amount"}}},
        {"$sort": {"total": -1}},
        {"$limit": 10},
    ],
)

# count
run_mql(operation="count", collection="users", filter={"active": True})

# distinct
run_mql(operation="distinct", collection="orders", distinct_field="status")

Allowed operations: find, aggregate, count, distinct. Everything else is rejected.

The result is {"rows": [...], "row_count": N} plus an optional validation_warnings list. Rows are capped at max_rows (default 100) and a terminal $limit is appended to aggregation pipelines. count returns [{"count": N}]; distinct is executed as a bounded $group pipeline and returns [{field: value}, ...]. String literals in filters and $match stages are coerced to datetime / ObjectId when the introspected field type is unambiguous.

MQL Validation

Every call to run_mql is automatically validated before reaching the database. The validator checks:

  • The operation is in the allowlist (find, aggregate, count, distinct)
  • No forbidden operator ($out, $merge, $where, $function, $accumulator, admin stages, …) appears at any depth — filter, pipeline, projection or sort
  • The target collection exists (with a did you mean suggestion otherwise)
  • Required arguments are present (pipeline for aggregate, distinct_field for distinct)
  • Every pipeline stage is in the stage allowlist, including sub-pipelines inside $lookup, $unionWith and $facet
  • Unknown $ operators produce a non-blocking warning

If validation fails, the LLM receives a structured error message with the hint and corrects the query without user intervention. Field names are not checked against the schema; a wrong field simply returns no rows, which the LLM usually detects and fixes.

To disable pre-execution validation (not recommended):

from mango.tools.mongo_tools import RunMQLTool

tool = RunMQLTool(backend, validate=False)

Even with validate=False, the forbidden-operator denylist and the stage allowlist are still enforced inside MongoRunner.execute_query() — the read-only guarantee is never optional.

Error Recovery

When a tool call fails with a query-level error (wrong field path, bad operator, missing $unwind), Mango automatically retries up to max_retries times. The exact error is injected back into the LLM context with correction rules, so the model can fix and resubmit.

agent = MangoAgent(
    ...
    max_retries=2,   # default: 2
)

Errors are classified by exception type: anything raised as QueryError (bad operator, wrong path, execution timeout on a heavy query) is retryable. Only infrastructure failures — BackendError, LLMError, ConnectionFailure, ServerSelectionTimeoutError, AutoReconnect — are not retried and are surfaced immediately as [FATAL].

The ask() result includes a retries_made counter so you can track how often recovery was needed.

Optional tools

These tools are not included in build_mongo_tools(). Register them manually when needed.

ExplainQueryTool

Explains a query step by step, plus execution stats from MongoDB explain() when available. The LLM calls this automatically when the user asks to "walk me through", "explain this query", "show the query plan", or "is this using an index?".

from mango.tools.mongo_tools import ExplainQueryTool

tools.register(ExplainQueryTool(backend, validate=True))

The tool returns structured JSON that the LLM turns into prose:

{
  "collection": "orders",
  "operation": "aggregate",
  "stages": [
    {"step": "$match", "description": "Filter: {\"created_at\": {\"$gte\": \"2026-01-01\"}}"},
    {"step": "$group", "description": "Group by \"$user_id\", compute: total"},
    {"step": "$sort", "description": "Sort: total desc"},
    {"step": "$limit", "description": "Keep first 10 documents"}
  ],
  "execution_stats": {"docs_returned": 10, "docs_examined": 42381, "execution_time_ms": 14, "scan_type": "IXSCAN", "index_used": "{'created_at': 1}"}
}

DeleteLastMemoryEntryTool

Lets the user correct the agent in natural language — "that was wrong, delete it" — without any UI. The tool removes the last auto-saved memory entry from the vector index.

Because it references the agent's internal state, it must be registered after the agent is created:

from mango.tools.memory_tools import DeleteLastMemoryEntryTool

agent = MangoAgent(...)
tools.register(DeleteLastMemoryEntryTool(memory, lambda: agent._last_memory_entry_id))

The LLM calls delete_last_memory_entry when the user says things like "that was wrong", "delete that", "forget the last query", or "don't save that". It does nothing unless the user explicitly asks.

ToolResult

Every tool returns a ToolResult:

@dataclass
class ToolResult:
    success: bool
    data: Any                 # the actual result data
    error: str | None = None  # error message if success=False
    error_kind: str | None = None  # exception class name (e.g. "QueryError"), drives retry classification

    def as_text(self) -> str:
        """Serialize result to text for the LLM."""

ToolRegistry

The registry manages tool lookup and execution:

from mango.tools.base import ToolRegistry

tools = ToolRegistry()

# Register a tool
tools.register(my_tool)

# Get all tool definitions (for the LLM)
definitions = tools.get_definitions()

# Execute a tool by name
result = await tools.execute("run_mql", operation="count", collection="users")

Next step

→ Custom Tools — build your own tools and register them alongside the built-ins.