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 withinFastMCP
that keeps track of all availableTool
objects and handles requests to use them (the foreman). Clients interact with theToolManager
viaFastMCP
.
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 ourserver
object as a decorator. name="add"
: We tellFastMCP
that clients should use the nameadd
to 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 tellFastMCP
what arguments the tool expects (two integers namednum1
andnum2
).FastMCP
uses 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. TheToolManager
now knows about theadd
tool.
If you run this server (mcp run calculator_server.py
), a client could:
- Call
listTools
and see theadd
tool listed, along with its description and expected arguments (num1
(int),num2
(int)). - Call
callTool
with the nameadd
and arguments like{"num1": 5, "num2": 7}
. FastMCP
would ask theToolManager
to execute theadd
tool. TheToolManager
would find youradd_numbers
function, check that the arguments match (5
and7
are 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
listTools
method). 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.tool
decorator). - Invocation: The client then decides to use a specific tool. It sends a request like, “Please execute the tool named ‘add’ with these arguments:
num1
is5
,num2
is7
.” (using the MCPcallTool
method). - Execution & Response: The server receives this request.
FastMCP
hands it off to theToolManager
. TheToolManager
finds 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 correspondingTool
objects (which contain information about your function, its parameters, etc.). - Provides the list of tools when
FastMCP
needs to respond to alistTools
request. - Looks up the correct
Tool
object whenFastMCP
receives acallTool
request. - 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_tool
method inspects theadd_numbers
function:- 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
Tool
object containing all this information (the function itself, its name, description, argument schema).
- Gets its name (
- The
ToolManager
stores thisTool
object 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}}}
. FastMCP
receives this message and identifies it as acallTool
request for the tool namedadd
.FastMCP
calls its internalcall_tool
handler method.- This handler asks the
ToolManager
: “Please execute the tool namedadd
with arguments{'num1': 5, 'num2': 7}
.” (calling_tool_manager.call_tool
). - The
ToolManager
looks up"add"
in its dictionary and finds the correspondingTool
object. - The
Tool
object (or theToolManager
using it) validates the provided arguments ({'num1': 5, 'num2': 7}
) against the stored argument schema (checks ifnum1
andnum2
are present and are integers). - If validation passes, the
Tool
object calls the original Python function (add_numbers
) with the arguments unpacked:add_numbers(num1=5, num2=7)
. - Your
add_numbers
function runs, calculates12
, and returns it. - The
ToolManager
receives the result12
. FastMCP
takes the result, packages it into a valid MCPcallTool
response 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.tool
decorator returns an inner function that callsself.add_tool(fn, ...)
. FastMCP.add_tool
simply 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_tool
method (which handles incomingcallTool
requests) 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.run
method (inserver/fastmcp/tools/base.py
) handles argument validation (using theFuncMetadata
generated 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
ToolManager
is 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
listTools
to discover tools andcallTool
to 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