Now that I’ve shared why I love MCP and explored some of its fundamental building blocks, it’s time to roll up our sleeves and build a simple MCP server from scratch.
The goal here is to provide a gentle introduction to building MCP servers and demonstrate how powerful it is — without writing a lot of code.
For this post, I’ll focus on just one type of MCP server feature: Tools. I believe tool calling is the most exciting part of MCP.
Also, I’ll be writing the MCP server in Python.
So with that context, let’s dive right in.
The What
We’re going to build an MCP server that can:
- Fetch the real-time stock price for a given ticker symbol.
- Fetch historical stock splits for a given ticker symbol.
The How
We’ll implement each requirement as a tool call. Tool calling (also known as function calling) is how LLMs interact with MCP.
Step 1: Define Tools as Python Functions
import yfinance as yf
def get_current_stock_price(ticker):
return {"currency": "USD", "value": yf.Ticker(ticker).info["open"]}
def get_historical_stock_splits(ticker):
history = []
for timestamp, ratio in yf.Ticker(ticker).splits.to_dict().items():
history.append(
{
"date": timestamp.strftime("%A, %B %d, %Y") + f", {timestamp.tzname()}",
"ratio": ratio,
}
)
return {"total": len(history), "history": history}
This is a super simple implementation — just 17 lines of Python code. Notice how the function names are straightforward and self-explanatory. That’s one way to help the LLM understand how to interact with the MCP.
Step 2: Convert the Python Code to MCP Server
import yfinance as yf
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Stocks")
@mcp.tool()
def get_current_stock_price(ticker):
"""Get Current Stock Price for a given Ticker Symbol"""
return {"currency": "USD", "value": yf.Ticker(ticker).info["open"]}
@mcp.tool()
def get_historical_stock_splits(ticker):
"""Get list of historical stock splits"""
history = []
for timestamp, ratio in yf.Ticker(ticker).splits.to_dict().items():
history.append(
{
"date": timestamp.strftime("%A, %B %d, %Y") + f", {timestamp.tzname()}",
"ratio": ratio,
}
)
return {"total": len(history), "history": history}
if __name__ == "__main__":
mcp.run(transport="stdio")
We did three key things here:
-
Decorated each Python function with the
@mcp.tool()
decorator. -
Added docstrings to each function to provide clear descriptions. These help the LLM understand how to use the tool.
-
Started the MCP server in
stdio
mode.
And that’s it — your MCP server is ready!
As you can see, developing an MCP server is incredibly straightforward.
Installation
Now, let’s install and run this.
Funny enough, installing this MCP server is more involved than building it. That’s because you need to package it in a way that Claude can load both the server and the required libraries: yfinance
and mcp
(the MCP Python SDK). I hope, and believe this will get easier in the coming months.
Step 1: Project Setup
If you’ve made it this far, I’ll assume you’re a programmer — or at least comfortable running commands in a terminal.
MCP documentation recommends using uv init
to set up your MCP server. This isn’t mandatory — you can use poetry
, or any other method — as long as you can provide Claude a command to launch the MCP server with its dependencies.
I’ll use uv init
for this guide.
- Start the project:
uv init mcp-stocks
cd mcp-stocks
- Install dependencies:
uv add yfinance
uv add 'mcp[cli]'
-
Copy the above code into the
main.py
file and save it. -
Grab the
venv
Python path and use it to runmain.py
.
This is necessary so that the correct virtual environment (with all dependencies) is used:
echo "command: \"`pwd`/.venv/bin/python\""
echo "args: [\"`pwd`/main.py\"]"
Step 2: Configure Claude for Desktop
- Use the output in your Claude MCP Config.
{
"mcpServers": {
"stocks": {
"command": "<replace-with-actual-python-path>",
"args": [
"<replace-with-actual-main-path>"
]
}
}
}
Once you restart Claude, you should be able to use both tools immediately.
Here’s a quick demo:
That’s it! You’ve built and deployed a working MCP server with real tools.