Chapter 4: FastMCP Tools (Tool, ToolManager)
In Chapter 3: Sharing Data - FastMCP Resources (Resource, ResourceManager), we learned how to make data available for clients to read using Resource objects, like putting books in a digital library. That’s great for sharing information, but what if we want the client to be able to ask the server to do something?
Imagine you want your server to not just provide data, but to perform calculations, interact with a database, or control some hardware. For example, maybe you want a client application (like an AI assistant) to be able to ask your server, “What’s 5 plus 7?”. The server needs to perform the addition and send back the result.
This is where FastMCP Tools come in. They allow your server to expose functions that clients can call remotely.
The Workshop Analogy: Tools and the Foreman
Think of your FastMCP server as a well-equipped workshop. Inside this workshop, you have various specialized tools:
- A drill (
Tool) - A screwdriver (
Tool) - A calculator (
Tool)
Each Tool is designed for a specific job. When someone (a client) needs a job done, they don’t operate the tool directly. Instead, they go to the workshop foreman (the ToolManager) and say:
“I need to use the calculator tool. Please add these numbers: 5 and 7.”
The foreman (ToolManager) knows exactly where the calculator tool is and how it works. It takes the request, operates the calculator with the provided numbers (5, 7), gets the result (12), and gives it back to the person who asked.
Tool: A specific function or capability your server offers (like the calculator). It has a name and accepts specific inputs (arguments).ToolManager: The internal manager withinFastMCPthat keeps track of all availableToolobjects and handles requests to use them (the foreman). Clients interact with theToolManagerviaFastMCP.
Clients can ask the ToolManager (via FastMCP) to list all available tools (listTools) and then request to execute a specific tool by its name, providing the necessary arguments (callTool).
Adding Tools to Your Workshop: Using @server.tool()
Just like we used @server.resource() to add data “books” to our library, FastMCP provides the @server.tool() decorator to easily add action “tools” to our workshop (managed by the ToolManager).
Let’s create a simple server with a calculator tool that can add two numbers.
File: calculator_server.py
# 1. Import FastMCP
from mcp.server.fastmcp import FastMCP
# 2. Create the server instance
server = FastMCP(name="CalculatorServer")
# 3. Use the @server.tool() decorator to define our tool
@server.tool(name="add", description="Adds two numbers together.")
def add_numbers(num1: int, num2: int) -> int:
"""
This function is registered as the 'add' tool.
'num1: int' and 'num2: int' tell FastMCP the tool expects
two integer arguments named 'num1' and 'num2'.
'-> int' tells FastMCP the tool will return an integer.
"""
print(f"Tool 'add' called with {num1} and {num2}") # Server-side log
# 4. The function's logic performs the action
result = num1 + num2
print(f"Returning result: {result}")
return result
# 5. Standard run block
if __name__ == "__main__":
print(f"Starting {server.name}...")
server.run() # Start listening
print(f"{server.name} finished.")
Explanation:
server = FastMCP(...): Creates our server (the workshop). Internally, this also creates aToolManager(the foreman).@server.tool(...): This is our decorator “button” for adding tools.- We use the
.tool()method of ourserverobject as a decorator. name="add": We tellFastMCPthat clients should use the nameaddto call this tool.description="...": A helpful description for clients.
- We use the
def add_numbers(num1: int, num2: int) -> int:: This is a standard Python function.num1: int,num2: int: These type hints are crucial! They tellFastMCPwhat arguments the tool expects (two integers namednum1andnum2).FastMCPuses this to validate input from clients and to generate documentation about the tool.-> int: This type hint indicates that the function will return an integer result.
- Function Body: This contains the actual logic for our tool – adding the numbers.
server.run(): Starts the server. TheToolManagernow knows about theaddtool.
If you run this server (mcp run calculator_server.py), a client could:
- Call
listToolsand see theaddtool listed, along with its description and expected arguments (num1(int),num2(int)). - Call
callToolwith the nameaddand arguments like{"num1": 5, "num2": 7}. FastMCPwould ask theToolManagerto execute theaddtool. TheToolManagerwould find youradd_numbersfunction, check that the arguments match (5and7are integers), call the function, get the integer result12, and send it back to the client.
How Clients Use Tools
You don’t need to worry about writing client code right now, but it’s helpful to understand the basic interaction:
- Discovery: The client first asks the server, “What tools do you have?” (using the MCP
listToolsmethod). The server, guided by itsToolManager, responds with a list of tools, including their names, descriptions, and what arguments they expect (based on your Python function signature and the@server.tooldecorator). - Invocation: The client then decides to use a specific tool. It sends a request like, “Please execute the tool named ‘add’ with these arguments:
num1is5,num2is7.” (using the MCPcallToolmethod). - Execution & Response: The server receives this request.
FastMCPhands it off to theToolManager. TheToolManagerfinds the correct Python function (add_numbers), validates and passes the arguments (5,7), executes the function, gets the return value (12), and sends this result back to the client.
The Foreman: ToolManager Behind the Scenes
While you primarily interact with @server.tool(), the ToolManager is the component within FastMCP that does the heavy lifting for tools.
When FastMCP starts, it creates a ToolManager instance. Every time you use the @server.tool() decorator, you’re essentially telling FastMCP to register that function with its ToolManager.
The ToolManager:
- Keeps a dictionary mapping tool names (like
"add") to the correspondingToolobjects (which contain information about your function, its parameters, etc.). - Provides the list of tools when
FastMCPneeds to respond to alistToolsrequest. - Looks up the correct
Toolobject whenFastMCPreceives acallToolrequest. - Validates the arguments provided by the client against the tool’s expected parameters (using the information gathered from type hints).
- Calls your actual Python function with the validated arguments.
- Handles potential errors during tool execution.
You usually don’t need to interact with ToolManager directly; @server.tool() is the convenient interface.
How Tools Work Under the Hood
Let’s trace the journey of our add tool from definition to execution.
1. Registration (When the server code loads):
- Python executes your
calculator_server.py. - It reaches the
@server.tool(name="add", ...)line abovedef add_numbers(...). - This calls the
server.tool()method. InsideFastMCP, this ultimately calls_tool_manager.add_tool(). - The
ToolManager.add_toolmethod inspects theadd_numbersfunction:- Gets its name (
add_numbers, but overridden byname="add"). - Gets its description (from the decorator or docstring).
- Looks at the parameters (
num1: int,num2: int) and return type (-> int) using Python’s introspection features. - Uses this information to build a schema describing the expected input arguments (like a mini-form definition).
- Creates an internal
Toolobject containing all this information (the function itself, its name, description, argument schema).
- Gets its name (
- The
ToolManagerstores thisToolobject in its internal dictionary, keyed by the name"add".
2. Invocation (When a client calls the tool):
- A client sends an MCP message:
{"method": "callTool", "params": {"name": "add", "arguments": {"num1": 5, "num2": 7}}}. FastMCPreceives this message and identifies it as acallToolrequest for the tool namedadd.FastMCPcalls its internalcall_toolhandler method.- This handler asks the
ToolManager: “Please execute the tool namedaddwith arguments{'num1': 5, 'num2': 7}.” (calling_tool_manager.call_tool). - The
ToolManagerlooks up"add"in its dictionary and finds the correspondingToolobject. - The
Toolobject (or theToolManagerusing it) validates the provided arguments ({'num1': 5, 'num2': 7}) against the stored argument schema (checks ifnum1andnum2are present and are integers). - If validation passes, the
Toolobject calls the original Python function (add_numbers) with the arguments unpacked:add_numbers(num1=5, num2=7). - Your
add_numbersfunction runs, calculates12, and returns it. - The
ToolManagerreceives the result12. FastMCPtakes the result, packages it into a valid MCPcallToolresponse message, and sends it back to the client.
Simplified Sequence Diagram (callTool for add):
sequenceDiagram
participant Client
participant FastMCP_Server as FastMCP (calculator_server.py)
participant ToolMgr as ToolManager (_tool_manager)
participant AddTool as Tool (wraps add_numbers)
participant AddFunc as add_numbers()
Client->>+FastMCP_Server: Send MCP Request: callTool(name="add", args={"num1": 5, "num2": 7})
FastMCP_Server->>+ToolMgr: call_tool(name="add", args={...})
ToolMgr->>ToolMgr: Find Tool object for "add"
ToolMgr->>+AddTool: tool.run(arguments={...})
AddTool->>AddTool: Validate args against schema
AddTool->>+AddFunc: Call add_numbers(num1=5, num2=7)
AddFunc-->>-AddTool: Return 12
AddTool-->>-ToolMgr: Return 12
ToolMgr-->>-FastMCP_Server: Return 12
FastMCP_Server->>-Client: Send MCP Response: result=12
Looking at the Code (Briefly):
You don’t need to memorize this, but seeing the structure can help.
- Registration (
@server.tool->add_tool->ToolManager.add_tool):- In
server/fastmcp/server.py, theFastMCP.tooldecorator returns an inner function that callsself.add_tool(fn, ...). FastMCP.add_toolsimply callsself._tool_manager.add_tool(fn, ...).
# Inside server/fastmcp/tools/tool_manager.py (Simplified ToolManager.add_tool) from .base import Tool # Tool class definition is in base.py class ToolManager: # ... (init, get_tool, list_tools) ... def add_tool(self, fn, name=None, description=None) -> Tool: # 1. Create a Tool object from the function tool = Tool.from_function(fn, name=name, description=description) # 2. Check for duplicates (optional warning) if tool.name in self._tools: # ... handle duplicate ... pass # 3. Store the Tool object in the dictionary self._tools[tool.name] = tool logger.debug(f"Registered tool: {tool.name}") return tool - In
- Invocation (
FastMCP.call_tool->ToolManager.call_tool->Tool.run):- In
server/fastmcp/server.py, theFastMCP.call_toolmethod (which handles incomingcallToolrequests) callsself._tool_manager.call_tool(name, arguments, ...).
# Inside server/fastmcp/tools/tool_manager.py (Simplified ToolManager.call_tool) class ToolManager: # ... (init, add_tool, list_tools) ... async def call_tool(self, name, arguments, context=None): # 1. Find the tool by name tool = self.get_tool(name) if not tool: raise ToolError(f"Unknown tool: {name}") # 2. Tell the Tool object to run with the arguments logger.debug(f"Calling tool: {name} with args: {arguments}") result = await tool.run(arguments, context=context) return result- The
Tool.runmethod (inserver/fastmcp/tools/base.py) handles argument validation (using theFuncMetadatagenerated during registration) and finally calls your original Python function (add_numbers).
- In
Conclusion
You’ve now learned about FastMCP Tools, the way to expose actions and computations from your server for clients to execute.
- Tools (
Tool) are server-side functions callable by clients, identified by a name. - The
ToolManageris the internal component that registers and dispatches tool calls (like a workshop foreman). - The
@server.tool()decorator is the easy way to register a Python function as a tool. - Type hints in your function signature are essential for defining the tool’s arguments and return type, enabling automatic validation and documentation.
- Clients use
listToolsto discover tools andcallToolto execute them.
Tools are fundamental for building interactive applications where the client needs the server to perform specific tasks beyond just retrieving data.
In the next chapter, we’ll explore another powerful feature of FastMCP for interacting with Large Language Models: Chapter 5: FastMCP Prompts (Prompt, PromptManager).
Generated by AI Codebase Knowledge Builder