Tools Primitive: Model-Controlled Actions with Side Effects
~15 min read
Tools are executable functions the AI can invoke, usually triggered by the model's own choice — but since they can have real side effects, MCP implementations often require explicit user permission before execution.
Tools, in MCP's architecture, are exactly what they sound like: functions that do something on behalf of the AI model, typically operations that have real effects or require computation beyond what the AI can do on its own — calling an external API, running a calculation, querying a live system. Importantly, tools are usually triggered by the AI model's OWN choice: the LLM, via the host application, decides to call a tool when it determines it needs that specific functionality to answer the current request.
Concretely, in an MCP server's code, a simple weather tool gets registered with a decorator like @mcp.tool(), turning a plain Python function into something the AI can invoke. When the AI calls tools/call with a name like "get_weather" and arguments like {"location": "San Francisco"}, the server executes the corresponding function and returns a structured result (say, temperature and conditions as a dictionary) — the client receives that JSON result and makes it available to the AI, which can then use or verbalize that information in its response.
Because tools can do things like file I/O or network calls — genuine side effects, not just read-only lookups — an MCP implementation often requires that the user explicitly permit a tool call before it executes. A concrete example: an MCP client might show 'The AI wants to use the get_weather tool, allow yes/no?' the first time that tool is invoked, specifically to prevent abuse and keep a human in control of powerful actions rather than letting the model silently execute anything it decides to.
Tools are analogous to 'functions' in classic function calling, but MCP uses them in a more flexible, dynamic context — model-controlled in the sense that the AI decides WHEN to invoke them, but developer/governance-approved in HOW they actually execute, via the permission layer and whatever constraints the server itself enforces.
💻 Code example
# An MCP server exposing a tool — decorated so the AI can discover
# and invoke it via the MCP protocol.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
def get_weather(location: str) -> dict:
"""Get current weather conditions for a location."""
# Real implementation would call a weather API here
return {"location": location, "temperature": 22, "conditions": "light rain"}
# When the AI calls tools/call with name="get_weather" and
# {"location": "San Francisco"}, the server runs get_weather("San Francisco")
# and returns the structured dict — which the client surfaces to the AI
# Client-side permission gate (conceptual) — often required before
# a tool with side effects (network calls, file I/O) actually executes
def request_permission(tool_name: str) -> bool:
return input(f"AI wants to use '{tool_name}'. Allow? (y/n): ").lower() == "y"
💬 Deep Dive with AI
Key points
- •Tools are executable functions the AI can invoke, typically operations with real effects or computation beyond the AI's own capabilities
- •Usually model-controlled: the LLM itself decides when it needs a tool's functionality
- •Registered on the server (e.g. via @mcp.tool()), invoked via tools/call with a name and arguments, returning structured results
- •Since tools can have side effects (file I/O, network calls), MCP implementations often require explicit user permission before execution
- •Analogous to classic function-calling 'functions,' but used in MCP's more flexible, dynamic, discoverable context