How agents reach into the real world - and how to do it without creating a security nightmare
A language model on its own is a brain in a jar. It can reason beautifully about the weather and tell you nothing about whether it is actually raining outside, because it has no senses and no hands. Tools are the senses and hands. They are the single feature that turns a model that can talk about booking a flight into an agent that can actually book one. In the last post we saw the agent loop; this post is about the Act step inside that loop.
What a tool actually is
Strip away the jargon and a tool is two things: a function, and a description the model can read. The function is ordinary code - a database query, an HTTP call, a shell command. The description is a schema that tells the model the tool’s name, what it does, and what arguments it takes. The model never sees your code; it only sees the schema and decides, in the moment, whether this tool is the right one for the goal in front of it.
def get_weather(city: str) -> dict:
"""Return current conditions for a city."""
return weather_api.current(city)
# The schema is what the MODEL sees:
{
"name": "get_weather",
"description": "Get current weather conditions for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name, e.g. 'Mumbai'"}},
"required": ["city"]
}
}
The one rule that keeps you safe: decide vs. execute
Here is the most important sentence in this entire post. The model decides; your code executes. When a model “calls a tool,” it does not run anything - it returns a structured request that names a function and its arguments. Your application receives that request, decides whether it is allowed, runs the real function, and feeds the result back into the loop.
The LLM should decide what to do, but never be the thing that does it. Keep a layer of your own code between the model’s intent and any real-world effect.
That gap between intent and execution is where you put validation, permission checks, rate limits, and human approval for anything dangerous. Collapse that gap - let the model run code directly - and you have handed an unpredictable system the keys to your infrastructure.
# The full decide -> execute -> observe cycle
response = client.chat(messages=history, tools=schemas)
for call in response.tool_calls:
if not is_allowed(call.name, call.args): # YOUR guardrail
result = "Error: not permitted."
else:
result = REGISTRYcall.name # YOUR code runs it
history.append({"role": "tool", "name": call.name, "content": str(result)})
final = client.chat(messages=history) # model uses the results
Designing tools the model can actually use
An agent is only as capable as its tools are well-designed, and most agent failures trace back to a badly described or badly scoped tool rather than a weak model. A few principles go a long way:
- Name and describe for the model, not for you. cancel_order(order_id) with a one-line description of when to use it beats a clever internal name the model has to guess about.
- Keep each tool narrow. One tool, one job. A single do_everything tool with a mode flag forces the model to reason about your implementation instead of the task.
- Validate inputs and fail informatively. Return “Error: city ‘Xyz’ not found, did you mean a valid city name?” rather than a stack trace. The model reads that error and corrects itself on the next loop.
- Prefer idempotency. Agents retry. A tool that charges a card twice when called twice is a liability; design so repeats are safe.
The M×N problem, and why MCP exists
Once tools work, a scaling problem appears. If you have M agent applications and N systems you want to connect (Slack, GitHub, your database, Google Drive), the naive world requires M×N custom integrations - every app re-implementing a connector to every system. The Model Context Protocol (MCP), introduced by Anthropic and now broadly adopted, collapses that into M+N. MCP is an open standard - often described as “USB-C for AI tools” - that defines a common way for a model to discover and call tools exposed by any compliant server.
The division of labour in the 2026 agent stack is clean: MCP standardizes how agents talk to tools and data; the Agent-to-Agent (A2A) protocol standardizes how agents talk to each other. Build a tool once as an MCP server and any MCP-aware client - LangGraph, CrewAI, ADK, a desktop assistant - can use it without bespoke glue.
# A minimal MCP server exposing one tool (Python SDK)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
def get_weather(city: str) -> dict:
"""Get current weather conditions or a city."""
return weather_api.current(city)
if __name__ == "__main__":
mcp.run() # any MCP client can now discover and call get_weather
The payoff is an ecosystem effect: the catalogue of ready-made MCP servers keeps growing, so connecting an agent to a new system increasingly means pointing it at an existing server rather than writing an integration.
Tools are your attack surface
Every tool you give an agent is also a door someone could walk through. A tool that runs shell commands or sends money is exactly as dangerous in an agent’s hands as in an attacker’s, and prompt injection - hostile instructions hidden in a web page or document the agent reads - can trick an agent into misusing the tools it has. Treat tool access as a security boundary, not a convenience:
- Least privilege. Give each agent the narrowest set of tools its job requires - nothing more.
- Never auto-execute irreversible actions. Payments, deletions, and trades should require explicit human confirmation, not an agent’s say-so.
- Validate and sanitize all tool inputs, exactly as you would for any untrusted user, because in effect they are
Tools are what make an agent useful, and MCP is making them composable across the whole ecosystem. But the discipline that matters most is the gap you keep between the model’s decision and the real action - that gap is where your validation, permissions, and human oversight live. Get the tool design and that boundary right and you have an agent that is both genuinely capable and safe to put in front of real users.
Top comments (0)