alawadi.cloudDocs

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_env names another environment variable that holds the bearer token. Never put the token itself in AGENT_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 plain http:// is refused unless you set ALLOW_INSECURE_MCP=true (local development only).
  • transport defaults to streamable-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

On this page