Understanding MCP Tools
In the previous lesson, you learned that an LLM is like a brilliant mind trapped in a room. Tools are the hands that let it reach out and interact with the outside world.
You actually saw a tool in action during the very first example: the dog-age-to-human-age calculator.
When you asked the model to convert dog years, it didn’t try to guess the math based on its training data. Instead, it recognized that it had a specific tool designed for that exact purpose. It handed off the numbers to the tool, waited for the calculation, and presented the result back to you.
What Are Tools?
In the context of the Model Context Protocol (MCP), tools are executable functions that a server exposes to a host application. They allow the model to interact with external systems.
For example, tools can enable models to:
- Create events in Google Calendar.
- Insert rows into a Google Sheet or a database.
- Search specifically for data across internal documentation.
- Send messages via Slack or Email.
- Execute code or perform complex mathematical calculations.
Essentially, anything you can imagine doing via code or an API can be wrapped as a tool and given to an LLM.
The Model is the Driver
It is important to understand the hierarchy of interaction in MCP. As a user, you do not call tools directly.
You don’t click a button that says “Run calculate_dog_in_human_year.” Instead, you simply chat with the model. You might say, “My dog is 5 years old, how old is that in human years?”
Here is what happens behind the scenes:
- Discovery: When the connection starts, the Host (like Claude Desktop) asks the MCP Server: “What tools do you have?” The server replies with a list.
- Intent: You provide a prompt to the model.
-
Decision: The model analyzes your request. It determines that it cannot fulfill the request with its internal knowledge alone, but it sees a tool named
calculate_dog_in_human_yearthat seems perfect for the job. -
Execution: The model formulates the inputs for that tool (extracting the number
5from your text) and requests the server to execute it. -
Result: The server runs the code, performs the math, and sends the result (e.g.,
36) back to the model. The model then translates that raw number into a friendly natural language response for you.
The Anatomy of a Tool
To the model, a tool isn’t magic; it’s a clearly defined schema. MCP uses JSON Schema to tell the model exactly what a tool does and what inputs it requires.
Consider the dog age calculator you are about to build. The definition the model sees looks something like this:
{
"name": "calculate_dog_in_human_year",
"description": "Calculates a dog's age in human years based on the following rules:\n- 1st year: 15 human years\n- 2nd year: +9 human years (Total 24)\n- 3rd year onwards: +4 human years for every year",
"inputSchema": {
"type": "object",
"properties": {
"dog_age": {
"type": "integer",
"description": "The age of the dog in years"
}
},
"required": ["dog_age"]
}
}
Because this definition is standardized, any MCP-compliant client can understand it. You define the tool once, and it works in Claude Desktop, Cursor, VS Code, or any other MCP host.
Human in the Loop
Because tools can perform actions—like modifying databases or sending emails—safety is a critical component of the protocol.
Tools are model-controlled, meaning the AI decides when to use them, but they often require human approval to actually run.
When an LLM decides it wants to use a sensitive tool, the Host application will typically pause and show you a request: “Claude wants to run delete_database_row. Allow this?” This ensures that you maintain control over actions that have real-world consequences.
Setting Up Your Project
Now that you understand the theory, it is time to build. You will recreate the dog age calculator, but this time you will set it up in a dedicated project environment to prepare for deeper inspection.
Open your terminal and create a new directory for this lesson:
$ mkdir lesson_2
$ cd lesson_2
Initialize a new project using uv. This creates a standalone environment for your dependencies:
$ uv init
Next, create a file named tools_demo_server.py. You can use your favorite code editor to create this file. Paste the following content into it:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Hello World MCP", json_response=True)
@mcp.tool()
def calculate_dog_in_human_year(dog_age: int) -> int:
"""
Calculates a dog's age in human years based on the following rules:
- 1st year: 15 human years
- 2nd year: +9 human years (Total 24)
- 3rd year onwards: +4 human years for every year
"""
if dog_age < 0:
return "Age cannot be negative"
if dog_age == 0:
return 0
elif dog_age <= 1:
# 1 year old = 15 human years
return dog_age * 15
elif dog_age <= 2:
# 2 years old = 24 human years
# (15 for the first year + 9 for the second)
return 15 + ((dog_age - 1) * 9)
else:
# 2+ years old = 24 + 4 years for every year after 2
return 24 + ((dog_age - 2) * 4)
if __name__ == "__main__":
mcp.run(transport="streamable-http")
This code initializes a FastMCP server and registers a single tool using the @mcp.tool() decorator. Notice how the docstring (the text inside """) provides the exact description that appeared in the JSON schema example earlier.
Finally, run the server:
$ uv run --with mcp tools_demo_server.py
Your server is now running! In the next section, you will learn how to verify and test this tool using the MCP Inspector instead of relying on Claude Desktop.