the loop, not a single call

tools = [{"name": "get_weather", "parameters": {...}}]

response = call_model(messages, tools=tools)

if response.wants_tool_call:
    result = run_actual_function(response.tool_call)     # your code, not the model's
    messages.append(response.tool_call)
    messages.append({"role": "tool", "content": result})
    response = call_model(messages, tools=tools)          # call again with the result

# repeat until the model returns a normal text answer instead of another tool call
The model never executes anything itself — it emits a structured request (name + arguments), your code runs the actual function, and the result goes back in as a new message. This loop is the foundation everything in AI Agents is built on.

designing tool schemas that get called correctly

PracticeWhy
Write the tool description like documentation for a new engineer, not a code commentThe model has no other context about when to use the tool
Keep parameter names unambiguous (city_name, not q)Reduces malformed or wrong-field calls
Mark truly required fields as required in the schemaConstrained decoding will enforce it, avoiding a class of missing-argument errors
Keep the total number of exposed tools reasonableTool selection accuracy degrades as the list grows into the dozens

where MCP fits

The Model Context Protocol standardizes how tools/data sources are described and exposed to a model-calling client, so a tool server can be written once and used by any MCP-compatible client instead of every application hand-rolling its own tool-schema glue code. It's the same underlying function-calling loop above, with a standard wire protocol around discovery and invocation. Full coverage: MCP.

where to go from here

MCP — the standardized protocol built on this same loop.
AI Agents & Agentic Workflows — chaining tool calls into multi-step autonomous behavior.
Structured Output & JSON Mode — the schema-constraint mechanism tool calling relies on.