MongoDB
MongoRunner is the built-in NoSQLRunner implementation for MongoDB. It uses pymongo and exposes only read-only operations.
Connect
from mango.integrations.mongodb import MongoRunner
db = MongoRunner()
db.connect("mongodb://localhost:27017/mydb")
The database name must be part of the URI. Use a MongoDB user limited to the read role: on connect, Mango checks the user's roles and logs a warning if it can write (or is unauthenticated).
Constructor
MongoRunner(
max_time_ms=30_000, # server-side time budget per query (maxTimeMS); 0 disables
introspect_ttl_s=300.0, # cache for describe_collection / schema inference; 0 disables
allowed_collections=None, # if set, only these are visible and queryable
denied_collections=None, # never visible; wins over allowed
)
system.* collections are always hidden. The collection policy is enforced on listing, introspection, field profiling and execution — including collections referenced by $lookup, $graphLookup and $unionWith. Per-user restrictions on top of it come from the CollectionAccess middleware.
Connection string formats
# Local
db.connect("mongodb://localhost:27017/mydb")
# With auth
db.connect("mongodb://reader:password@localhost:27017/mydb")
# MongoDB Atlas
db.connect("mongodb+srv://reader:password@cluster.mongodb.net/mydb")
# With options — extra kwargs go straight to MongoClient
db.connect("mongodb://localhost:27017/mydb", serverSelectionTimeoutMS=5000, connectTimeoutMS=10000)
Allowed operations
| Operation | Notes |
|---|---|
find | filter, projection, sort, limit |
aggregate | pipeline; a terminal $limit is appended to enforce the row cap |
count | count_documents() with optional filter |
distinct | emulated with $group + $sort + $limit (no 16 MB array) |
Anything else raises ValidationError before reaching the server. Independently of the validator, execute_query() rejects forbidden operators at any depth of filter, pipeline, projection and sort ($out, $merge, $where, $function, $accumulator, $changeStream, admin stages), unknown pipeline stages, and inaccessible collections. See Security Model.
Literal coercion
The LLM writes JSON, MongoDB stores BSON. run_mql converts literals only when the target field's inferred type says so: ISO-8601 strings on date fields become datetime (offsets normalised to UTC), 24-hex strings on ObjectId fields become ObjectId, and Extended JSON {"$date": …} / {"$oid": …} are honoured. Fields of mixed type are left untouched.
Methods
# Execute a query → list of JSON-safe row dicts (absent fields stay absent, ints stay ints)
rows: list[dict] = db.execute_query(QueryRequest(operation="count", collection="users", filter={"active": True}))
# → [{"count": 1247}]
collections: list[str] = db.list_collections() # filtered by the collection policy
schema: dict[str, SchemaInfo] = db.introspect_schema() # samples 100 docs per collection, cached
docs: list[dict] = db.get_sample_documents("orders", n=5)
indexes: list[dict] = db.get_indexes("orders")
profile: dict = db.profile_field("orders", "status", top_k=20) # top values, cardinality, type breakdown
types: dict[str, set[str]] = db.field_types("orders") # {dotted_path: {bson types}}
db.check_collection_access("orders") # raises ValidationError if hidden
BSON values in rows are made JSON-safe: datetime → ISO string, ObjectId / Decimal128 / Binary → string.
Error handling
| Exception | When raised |
|---|---|
ValidationError | forbidden operator or stage, inaccessible collection, disallowed operation, missing required argument |
QueryError | MongoDB rejected or timed out the query — the agent lets the LLM correct and retry |
BackendError | connection failure, server unreachable — fatal for the turn, no retry |