Backend Integration
This page is the end-to-end recipe for putting Mango behind your application: your users log in to your app, ask questions, and get answers scoped to what they are allowed to see.
The one rule: Mango never authenticates. It receives a user object you already trust and enforces the permissions you attach to it. See Access Control for the model.
1. Build the agent once
The agent is a long-lived object: build it at application startup, not per request.
# mango_setup.py
import os
from mango import MangoAgent
from mango.tools import ToolRegistry, build_mongo_tools, SaveTextMemoryTool
from mango.integrations.anthropic import AnthropicLlmService
from mango.integrations.mongodb import MongoRunner
from mango.integrations.chromadb import ChromaAgentMemory
llm = AnthropicLlmService(model="claude-sonnet-4-6", api_key=os.environ["ANTHROPIC_API_KEY"])
db = MongoRunner(max_time_ms=30_000) # server-side time budget per query
db.connect(os.environ["MONGODB_URI"]) # use a MongoDB user with the `read` role
memory = ChromaAgentMemory(persist_dir="/var/lib/mango/memory")
tools = ToolRegistry()
for tool in build_mongo_tools(db):
tools.register(tool)
tools.register(SaveTextMemoryTool(memory))
agent = MangoAgent(
llm_service=llm,
tool_registry=tools,
db=db,
agent_memory=memory,
introspect=True, # schema-aware prompting; cached with a TTL
)
agent.setup()
2. Describe your users to Mango
Pick the object your framework already gives you for the current user and write the role logic as functions of it:
# mango_access.py
from mango.middleware import CollectionAccess, RowFilter, RedactFields, DenyTools, Budget, AuditLog
ROLE_COLLECTIONS = {
"support": ["users", "orders", "returns", "support_tickets"],
"finance": ["orders", "payments", "ledger_entries", "merchants"],
"admin": "*",
}
PII = ["users.email", "users.phone", "users.addresses", "payments.details"]
def install_access_control(agent):
agent.use(CollectionAccess(lambda u: ROLE_COLLECTIONS[u.role]))
agent.use(RowFilter("users", lambda u: {} if u.role == "admin" else {"country": u.country}))
agent.use(RedactFields(lambda u: [] if u.role == "admin" else PII))
agent.use(DenyTools(lambda u: [] if u.role == "admin" else [
"save_text_memory", # tool
"memory_train", "memory_import", "memory_export", # REST endpoints
]))
agent.use(Budget(max_turns_per_day=300))
agent.use(AuditLog())
u here is whatever your user_for returns (next step). Attributes like role and country are yours.
3. Wire authentication
FastAPI β mount the router into your app
mango_router returns an APIRouter. user_for is the only contact point with your auth: it receives the request and returns your user object, or raises HTTPException(401).
# main.py
from fastapi import FastAPI, HTTPException, Request
from mango.servers.fastapi import mango_router
from mango_setup import agent
from mango_access import install_access_control
app = FastAPI()
install_access_control(agent)
def user_for(request: Request):
user = request.state.user # set by your own auth middleware / dependency
if user is None:
raise HTTPException(status_code=401)
return user
app.include_router(mango_router(agent, user_for=user_for), prefix="/mango")
user_for may be async. Three common shapes:
# JWT (any library that verifies the token for you)
async def user_for(request: Request):
token = request.headers.get("Authorization", "").removeprefix("Bearer ").strip()
try:
claims = verify_jwt(token) # your verifier: signature, issuer, audience, expiry
except InvalidToken:
raise HTTPException(status_code=401)
return {"id": claims["sub"], "role": claims["role"], "country": claims.get("country", "")}
# Behind an API gateway that already authenticated (Kong, Envoy, Cloudflare Accessβ¦)
def user_for(request: Request):
if request.headers.get("X-Gateway-Secret") != settings.GATEWAY_SECRET:
raise HTTPException(status_code=401)
return {"id": request.headers["X-User-Id"], "role": request.headers["X-User-Role"]}
# Service-to-service with a static API key
def user_for(request: Request):
if not secrets.compare_digest(request.headers.get("X-API-Key", ""), settings.MANGO_KEY):
raise HTTPException(status_code=401)
return {"id": "reporting-job", "role": "finance"}
Give your user object an id (or sub / email / username). Mango uses it to bind sessions to users and to key budgets and audit records.
Standalone server
If Mango is its own service, the same callback goes to MangoFastAPIServer:
from mango.servers.fastapi import MangoFastAPIServer
MangoFastAPIServer(agent, user_for=user_for, cors_origins="https://app.example.com").run(port=8000)
Without user_for the server runs open and logs a warning at startup β fine on localhost, never on a network.
Not FastAPI? Call the agent directly
The HTTP layer is a thin adapter. From Django, Flask, a Celery worker or a CLI you call the agent yourself and pass the user:
# Django (async view)
async def ask(request):
body = json.loads(request.body)
response = await agent.new_session().ask(body["question"], user=request.user)
return JsonResponse({"answer": response.answer, "tools": response.tool_calls_made})
new_session() is cheap and gives each conversation its own history; keep a map conversation_id β agent if you want follow-up questions.
4. Talk to it from the frontend
const res = await fetch('/mango/ask/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ question, session_id }), // session_id: null on the first turn
})
for await (const event of sse(res)) { // see FastAPI Server for a reader
if (event.type === 'session') session_id = event.session_id
if (event.type === 'tool_result' && !event.success) showBlocked(event.preview)
if (event.type === 'answer') render(event.text)
if (event.type === 'error') showError(event.code === 'access_denied' ? event.message : 'Something went wrong')
}
5. Memory in a multi-user deployment
save_text_memory,delete_last_memory_entryand the/memory/*endpoints write to a store shared by everyone. Restrict them to trusted roles withDenyTools.- Memory examples are filtered by the caller's visible collections before they enter the prompt, so a support agent never sees a finance query as an example.
- One agent = one database = one memory directory. Do not point two databases at the same
persist_dir.
6. Production checklist
- MongoDB user with the
readrole. Mango blocks writes at the query level ($out,$merge, server-side JavaScript, admin stages, at any depth) and logs a warning if the connected user can write β but the database role is the first ring, Mango is the second. user_foron every deployment that is reachable from a network.- Scope collections with
CollectionAccessorMongoRunner(allowed_collections=[β¦]), and mask PII withRedactFields. Sampled documents and query results are sent to the LLM provider. - CORS: set
cors_origins/MANGO_CORS_ORIGINSto your frontend origin(s); the default*disables credentials as the CORS spec requires. - Reverse proxy with TLS and rate limiting in front. Mango caps question length (4 000 chars), session id format and import size (1 000 entries) but does not rate-limit.
- Time and size budgets:
MongoRunner(max_time_ms=β¦)bounds every query server-side;run_mqlcaps rows at 100 by default. - Audit: keep
AuditLog()on and ship themango.auditlogger to your log pipeline. - Sessions: tune
session_ttl_seconds/max_sessionson the router to your traffic; sessions live in process memory.