Building Your First MCP Server
Build a working MCP server from scratch using the Python or TypeScript SDK. Connect it to Claude Desktop and call your first custom tool.
Premium Course Content
This lesson is part of a premium course. Upgrade to Pro to unlock all premium courses and content.
- Access all premium courses
- 1000+ AI skill templates included
- New content added weekly
Theory is done. Time to build.
In this lesson, you’ll create a working MCP server, connect it to Claude Desktop, and call your first custom tool. The whole thing takes under 50 lines of code.
🔄 Quick Recall: In the previous lesson, you learned MCP’s three-layer architecture: Host (AI app), Client (protocol handler), Server (your code). Now you’ll build the Server layer and connect it to a Host.
Choose Your Language
MCP has official SDKs for both Python and TypeScript. Pick whichever you’re more comfortable with — the concepts are identical.
| Python (FastMCP) | TypeScript | |
|---|---|---|
| Install | pip install mcp | npm install @modelcontextprotocol/sdk |
| Best for | Quick prototyping, data science | Node.js apps, web integrations |
| Server class | FastMCP | McpServer |
We’ll show Python examples in this course (they’re shorter), but the TypeScript SDK follows the same patterns.
Step 1: Set Up the Project
Create a new directory and install the SDK:
mkdir my-first-mcp
cd my-first-mcp
pip install mcp
Create a file called server.py:
from mcp.server.fastmcp import FastMCP
# Create the server
mcp = FastMCP("My First Server")
# Define a tool
@mcp.tool()
def add_numbers(a: float, b: float) -> str:
"""Add two numbers together."""
return str(a + b)
# Run the server
if __name__ == "__main__":
mcp.run(transport="stdio")
That’s it. 12 lines. You have a working MCP server with one tool.
Let’s break down what’s happening:
FastMCP("My First Server")— Creates a server instance with a display name@mcp.tool()— Registers the function as a callable tool- Type hints (
a: float, b: float) — MCP uses these to generate the tool’s input schema automatically - Docstring (
"""Add two numbers together.""") — Becomes the tool’s description that the AI sees mcp.run(transport="stdio")— Starts the server using stdio transport
✅ Quick Check: Why are Python type hints important in MCP tool definitions? (Answer: The SDK uses type hints to automatically generate the JSON Schema for the tool’s parameters. This schema tells the AI client what inputs the tool accepts, their types, and which are required — without you writing schema definitions manually.)
Step 2: Connect to Claude Desktop
To use your server, tell Claude Desktop where to find it. Edit the configuration file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Add your server:
{
"mcpServers": {
"my-first-server": {
"command": "python",
"args": ["/full/path/to/my-first-mcp/server.py"]
}
}
}
Important: Use the full absolute path to your server file. Relative paths won’t work because Claude Desktop launches the process from its own directory.
Restart Claude Desktop. You should see a hammer icon (🔨) in the input area — that means MCP tools are available.
Step 3: Test It
Type in Claude Desktop:
“What’s 42.5 plus 17.3?”
Claude will recognize this needs calculation, see your add_numbers tool, and call it. You’ll see something like:
Used tool: add_numbers Result: 59.8
42.5 plus 17.3 equals 59.8
You just gave Claude a new capability it didn’t have before. The add_numbers example is trivial — but the pattern works for anything: database queries, API calls, file operations, calculations.
Building a More Useful Server
Let’s expand the server with something practical — a word counter tool:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Text Tools")
@mcp.tool()
def count_words(text: str) -> str:
"""Count the number of words in a text."""
word_count = len(text.split())
char_count = len(text)
return f"Words: {word_count}, Characters: {char_count}"
@mcp.tool()
def reverse_text(text: str) -> str:
"""Reverse the given text."""
return text[::-1]
@mcp.tool()
def extract_emails(text: str) -> str:
"""Extract all email addresses from text."""
import re
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text)
if emails:
return "Found emails:\n" + "\n".join(emails)
return "No email addresses found."
if __name__ == "__main__":
mcp.run(transport="stdio")
Now your server has three tools. After restarting Claude Desktop, Claude can count words, reverse text, and extract emails — all by calling your local server.
✅ Quick Check: You add a new tool to your MCP server. What do you need to do for Claude Desktop to see the new tool? (Answer: Restart Claude Desktop. The client discovers available tools during the initialization handshake, so new tools aren’t visible until the connection is re-established.)
How the AI Decides to Use Your Tools
You might wonder: how does Claude know when to call your tools?
During initialization, your server sends a list of available tools with their names, descriptions, and parameter schemas. When a user sends a message, Claude’s reasoning process considers:
- Does this request match a tool’s description? — “Extract emails from this text” matches the
extract_emailstool - Are the required parameters available? — The user provided text, which is the required input
- Is calling the tool better than answering directly? — For email extraction, a regex tool is more reliable than the model guessing
This is why good descriptions and clear parameter names matter. A tool called fn1 with no description won’t get used. A tool called extract_emails with the description “Extract all email addresses from text” gets called reliably.
Debugging Tips
When things don’t work:
Server doesn’t appear in Claude Desktop:
- Check the config JSON is valid (a missing comma breaks everything)
- Verify the file path is absolute, not relative
- Restart Claude Desktop completely (not just close/reopen the window)
Server appears but tools fail:
- Check server logs in stderr (for stdio transport,
console.error()orprint()to stderr) - Test the server independently by running it and checking for import errors
- Verify Python environment matches the one in your config (virtual envs can cause mismatches)
Claude doesn’t call the tool:
- Improve the tool’s description — be specific about what it does
- Check parameter names are descriptive (not
x,y, buttext,city,query)
Practice Exercise
- Create a new MCP server with at least 2 tools of your choice (ideas: temperature converter, URL shortener info, text formatter)
- Connect it to Claude Desktop using the config file
- Test each tool by asking Claude questions that should trigger them
- Try asking something ambiguous — does Claude pick the right tool?
Key Takeaways
- FastMCP (Python) builds a working server in under 15 lines of code
- The
@mcp.tool()decorator registers functions as callable tools - Type hints generate parameter schemas automatically — always include them
- Connect to Claude Desktop via
claude_desktop_config.jsonwith the server’s command and path - Good tool descriptions and parameter names determine whether the AI uses your tools correctly
Up Next
In the next lesson, you’ll go deeper into Tools — the most-used MCP primitive. You’ll learn input validation, error handling, async operations, and patterns for building robust tools.
Knowledge Check
Complete the quiz above first
Lesson completed!