Tools and MCP
Give your agent tools from MCP servers, run the built-in tool-calling loop, expose your agent as an MCP server, and trace every invocation.
An agent that can only chat is half an agent. The runtime that hosts your
handler.py ships a full tool harness: your agent can consume external
MCP tool servers, run a capped tool-calling
loop for you, and expose its own tools as an MCP server that other agents can
consume. Every call is traced with an invocation ID you can follow.
Connect MCP tool servers
Declare external MCP servers in the AGENT_MCP_SERVERS environment variable —
a JSON array the platform injects per agent, so adding a tool server is a
config change, not a code change:
[
{"name": "docs", "url": "https://mcp.example.com/mcp", "token_env": "MCP_DOCS_TOKEN"},
{"name": "legacy", "url": "https://mcp.legacy.example.com/sse", "transport": "sse"}
]token_envnames another environment variable that holds the bearer token. Never put the token itself inAGENT_MCP_SERVERS— each secret is its own secret entry, injected like your provider keys.- URLs must be
https://. Tool calls can carry sensitive arguments over the wire, so plainhttp://is refused unless you setALLOW_INSECURE_MCP=true(local development only). transportdefaults tostreamable-http; use"sse"for legacy SSE servers.
The alawadi_agent SDK then gives you the tools as portable descriptors:
from alawadi_agent import list_all_tools, call_tool
tools = list_all_tools() # every configured server, tagged with its name
# → [{"name": "search", "description": "...", "input_schema": {...}, "server": "docs"}]
result = call_tool("docs", "search", {"query": "refund policy"})
# → {"text": "...", "structured": {...} | None, "is_error": False}Async handlers use the list_tools_async / list_all_tools_async /
call_tool_async variants instead.
The tool-calling loop: run_agent()
The common agent shape is "model + tools, loop until the model stops calling
tools". run_agent is the runtime's canonical implementation of that loop —
hard-capped at max_steps model round-trips, with tool errors fed back to the
model as data instead of crashing your invocation:
from alawadi_agent import run_agent, list_all_tools
def handle(request):
tools = list_all_tools() + [{
"name": "add",
"description": "Add two numbers",
"input_schema": {"type": "object",
"properties": {"a": {"type": "number"}, "b": {"type": "number"}}},
"fn": lambda a, b: a + b, # local callable
}]
return run_agent(request.get("message", ""), tools=tools, max_steps=8)Tools may be MCP descriptors from list_all_tools() (they carry a server
key and are executed remotely) or local callables with an fn key. The loop
converts them to OpenAI function-tool format for you. It returns
{"output": str, "steps": [...]} — steps records each round-trip's content
and tool calls, which is what powers the tracing below.
The cap matters: a runaway tool loop is a runaway bill. When max_steps is
hit, one final call without tools forces a text answer from whatever the
loop gathered.
Expose your agent as an MCP server
The same contract works in reverse. Export TOOLS and handle_tool from your
handler.py and the runtime serves them as an MCP server at POST /mcp on
your agent's hostname — Streamable HTTP JSON-RPC, supporting initialize,
ping, tools/list, and tools/call:
TOOLS = [{"name": "greet", "description": "Greet someone",
"input_schema": {"type": "object",
"properties": {"who": {"type": "string"}}}]
def handle_tool(name, arguments):
if name == "greet":
return "hello " + arguments.get("who", "world")
raise KeyError(name)/mcp sits behind the same invoke-token auth as /invoke, so only
callers you trust can enumerate or run your tools. Another agent can then
consume yours by declaring it in its own AGENT_MCP_SERVERS with
"url": "https://<name>-<id>.alawadi.cloud/mcp". If your handler does not
export TOOLS and handle_tool, /mcp returns 404
(mcp_not_available).
Trace every invocation
Every /invoke call and every MCP tools/call returns an
X-Invocation-Id response header and is recorded in an in-memory ring
buffer — id, timestamp, kind, status, and latency, never request bodies or
tokens. GET /invocations (same invoke-token auth as /invoke) returns the
recent invocations, newest first:
curl https://my-agent-a1b2c3.alawadi.cloud/invocations \
-H "Authorization: Bearer $AGENT_INVOKE_TOKEN"The buffer keeps the last 200 invocations by default; tune it with
AGENT_INVOCATION_LOG_SIZE. This is the feed behind the per-agent history in
the portal — correlate it with the X-Invocation-Id you logged on the caller
side when debugging a bad run.
Next
- AI Agents — create, deploy, and secure the agent itself.
- AI Agents API reference — every management endpoint, with schemas.
- Billing and usage — how tool-calling token spend is metered.