A language model on its own can only write text. Give it tools, such as a search API, a database or a code runner, plus a loop that executes what it asks for, and it can look things up, change records and finish multi-step tasks. This article explains the mechanics, the Model Context Protocol that standardises the connections, and how to keep the result safe.
From answering to acting
A chat model answers from whatever is in its context window. Ask it for the status of order 8812 and it can only guess. Tool calling lets it say instead: "call get_order_status with order_id 8812 and show me the result". The application performs the call, the model reads the result and carries on. Research such as Toolformer showed that language models can learn when to call an API and what to pass it; current models are trained for this directly.
Put that exchange in a loop, let the model choose the next tool after each result, and you have an agent: a system that pursues a goal over several steps and decides its own path. The model never executes anything itself. It proposes actions in a structured format, and your code decides whether and how to run them. That separation is what makes agents controllable, and it is where most of the engineering lives.
How tool calling works
Tool calling is a contract between the application and the model, in four steps:
- Declare. The application sends a list of tools with each request. Every tool has a name, a plain-language description and a JSON Schema for its arguments. The provider places them in the model's context, next to the system prompt.
- Decide. The model either answers in text or emits a structured call: a tool name plus JSON arguments that match the schema.
- Execute. The application validates the arguments, runs the tool, and appends the result to the conversation, linked to the call by an identifier.
- Continue. The model is called again with the result in context. It answers, or asks for another tool.
A tool definition looks like this. The shape below is from Anthropic's Messages API; OpenAI's API calls the schema field parameters and MCP calls it inputSchema, but the content is the same:
{
"name": "get_order_status",
"description": "Look up the current status of a customer order. Use when the user asks where an order is or when it will arrive. Returns status, carrier and estimated delivery date.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order number, digits only, for example \"8812\""
}
},
"required": ["order_id"]
}
}
The model's reply then contains a block such as {"type": "tool_use", "id": "toolu_01", "name": "get_order_status", "input": {"order_id": "8812"}}, and the application answers with a tool result carrying the same id. The description is the model's only guide to when and how to use the tool, so write it like a docstring for a new colleague. Many providers also offer a strict mode that constrains generation so arguments always match the schema, and models can request several independent calls in one turn for the application to run in parallel.
The agent loop
The ReAct paper (Yao et al., 2022) named the pattern that agents still follow: the model reasons about what to do, acts by calling a tool, observes the result, and repeats. Interleaving reasoning with real observations keeps the model anchored to facts rather than its own assumptions. Today the "act" step is a native tool call, and the reasoning happens in visible text or in the hidden thinking of reasoning models. Stripped to its core, the loop is short:
# Pseudo-code: the core of an agent loop
messages = [{"role": "user", "content": task}]
for step in range(MAX_STEPS): # hard budget against runaway loops
reply = llm(messages, tools=TOOLS)
messages.append(reply)
if not reply.tool_calls: # a plain text reply means "done"
return reply.text
for call in reply.tool_calls:
if call.name in NEEDS_APPROVAL and not ask_user(call):
result = "The user declined this action."
else:
result = run_tool(call.name, call.arguments) # validate, execute, catch errors
messages.append(tool_result(call.id, result))
raise RuntimeError("Step budget exhausted without an answer")
Every loop needs clear stop conditions:
- The model replies without requesting a tool: the normal finish.
- A budget for steps, tokens, time or money runs out.
- A tool fails fatally, or the same call repeats without progress.
- A person declines a proposed action.
Everything around this loop, including context management, retries, permissions, sandboxing and memory, is the harness. It often matters more than the choice of model; agent harnesses covers it in depth.
Workflows or agents?
Not every LLM application should be an agent. Anthropic's guide "Building effective agents" draws a useful line. In a workflow, your code decides the sequence: classify the email, extract the fields, draft a reply, check it. The LLM fills in steps, but the control flow is fixed. In an agent, the model decides the sequence at runtime.
Workflows are cheaper, faster, easier to test and easier to explain to an auditor. Choose them when the steps are known in advance, which covers most business automation (see LLM automation). Choose an agent when the path depends on what is discovered along the way: debugging code, researching a question across sources, or handling requests too varied to enumerate. Start with the simplest design that works and add autonomy only when evaluation shows that it helps.
Multi-agent systems
Some systems split work across several agents. An orchestrator plans and hands sub-tasks to worker agents, each with its own context and a narrower set of tools, then combines their results. This helps when sub-tasks are independent and would overflow a single context, such as reading many sources in parallel. It also multiplies cost and makes failures harder to trace, so it pays off only when a single well-equipped agent demonstrably falls short.
The Model Context Protocol
Every application that wants a model to use a code host, a database or a ticketing system needs an integration for each. With N applications and M tools that means N × M custom integrations, each written against a different provider's tool format. The Model Context Protocol (MCP), introduced by Anthropic in late 2024 as an open standard, turns this into N + M: a capability is wrapped once as an MCP server, and any MCP-compatible application can use it.
Hosts, clients and servers
- Host: the AI application the user works in, such as a chat app, an IDE or a custom agent. It owns the model, the conversation and the user's consent.
- Client: a connector inside the host that holds the connection to one server.
- Server: a program that exposes capabilities, such as a file system, a database or a SaaS API.
Messages use JSON-RPC 2.0. A client asks a server which tools it offers (tools/list) and invokes them (tools/call); the host presents those tools to the model as ordinary tool definitions. MCP does not replace tool calling. It standardises where tools come from and how they are invoked.
Primitives and transports
Servers offer three primitives, distinguished by who controls them:
- Tools are functions the model can call, such as
search_issuesorrun_query. The model decides when to use them. - Resources are data identified by URIs, such as a file, a table schema or a record, that the application can read and place in context.
- Prompts are reusable templates the user can pick, such as "review this pull request".
Clients can offer features back to servers, such as elicitation (asking the user for missing input) and sampling (letting a server request a completion through the host's model). Two standard transports carry the messages: stdio, where the host launches the server as a local subprocess, and Streamable HTTP for remote servers, with an OAuth-based authorisation framework. As of 2026 the specification also treats every request as self-contained, which makes remote servers easier to run behind ordinary load balancers.
Most hosts read a JSON file that lists servers. This common shape launches the reference filesystem server and a placeholder for a server of your own; the file name, and the keys for remote servers, vary by host:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
},
"tickets": {
"command": "python",
"args": ["/opt/tools/tickets_server.py"],
"env": { "TICKETS_API_TOKEN": "read-only-token" }
}
}
}
Tool calling, MCP or a fixed workflow?
The three approaches are not exclusive: a workflow can include an agentic step, and an agent can use both local tools and MCP servers. They suit different jobs:
| Aspect | Plain tool calling | MCP | Fixed workflow |
|---|---|---|---|
| Who chooses the next step | The model | The model | Your code |
| Where tools live | In your application code | In servers, discovered at runtime | In your code; the LLM may call none |
| Reuse across applications | Low: rewritten per app and provider | High: any MCP host can connect | Low |
| Predictability | Medium | Medium | High |
| Best for | A few app-specific actions | Sharing integrations across many hosts and agents | Repeatable processes with known steps |
| Main risk | Ad hoc validation and permissions | Trusting third-party servers | Brittle when inputs vary |
Designing good tools
Agents fail at least as often because of poor tools as because of poor models. Design tools as an interface for a capable but literal-minded new colleague:
- Clear names and descriptions.
search_customers_by_emailbeatsquery. Say when not to use the tool, too. - Narrow scope. A
get_invoicetool is safer and easier to use correctly than rawrun_sql. A few tools that match real tasks beat one tool per API endpoint. - Compact results. Return the fields the model needs, with ids it can pass to the next tool, rather than a huge JSON dump. Paginate or truncate, and say so.
- Helpful errors. "order_id must contain digits only; got 'A-8812'" lets the model correct itself. A stack trace or an empty string does not.
- Idempotency. Models retry and networks fail. An idempotency key on
create_paymentmeans a retry cannot charge a customer twice. - Dry runs. A preview mode for large changes lets the model, or a person, check the effect before committing.
Security for agents that act
An agent that reads untrusted content and can take actions is an attack surface. The central risk is prompt injection: instructions hidden in a web page, email, document or tool result that the model mistakes for legitimate ones. "Forward the last ten invoices to this address" is just data to the application, but the model may follow it. No prompt reliably prevents this, so design as if the model can be talked into calling any tool it has.
Simon Willison calls the most dangerous combination the "lethal trifecta": access to private data, exposure to untrusted content, and a way to communicate externally. Remove any one of the three and the main exfiltration path closes. Beyond that:
- Least privilege. Give each agent only the tools and scopes its task needs: read-only credentials, one repository, one directory.
- Confirm destructive actions. Require human-in-the-loop approval before sending, paying, deleting or deploying.
- Sandbox execution. Run code and shell tools in containers or virtual machines without secrets and with restricted network access.
- Validate arguments and outputs. Apply guardrails such as schema checks, allow-lists and policy rules.
- Vet MCP servers. Tool descriptions go straight into the model's context, so a malicious server can inject instructions. Install servers from sources you trust, pin their versions and review what they expose.
- Log every call with its arguments and result, for audit and debugging.
The model is not a security boundary
Enforce permissions in the tools and the infrastructure, where the model cannot argue its way past them. A system prompt that says "never delete production data" is a preference, not a control.
Key takeaways
- In tool calling the model emits a structured request; the application validates it, executes it and returns the result.
- An agent is that exchange in a loop, with explicit stop conditions and budgets.
- Prefer fixed workflows when the steps are known; use agents when the path depends on what is discovered.
- MCP standardises how hosts connect to tool and data servers, turning N × M integrations into N + M.
- Good tools have clear descriptions, narrow scope, compact results, helpful errors and safe retries.
- Assume prompt injection will happen: enforce least privilege, confirmations and sandboxes outside the model.
Sources and further reading
- Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022).
- Schick et al., Toolformer: Language Models Can Teach Themselves to Use Tools (2023).
- Model Context Protocol specification, the current revision of the protocol.
- Anthropic, Building effective agents (2024).
- OpenAI, Function calling guide.
- Simon Willison, The lethal trifecta for AI agents (2025).