Lab 020: Build an MCP Server in PythonΒΆ
What You'll LearnΒΆ
- How to build an MCP server from scratch using FastMCP (Python)
- How to define Tools with proper schemas and descriptions
- How to run the server and connect it to the MCP Inspector and GitHub Copilot (VS Code)
- How to add HTTP/SSE transport for cloud-agent compatibility
IntroductionΒΆ
An MCP server is a program that exposes tools (functions) to AI agents via the MCP protocol. When an agent needs to perform an action β query a database, call an API, read a file β it calls your MCP server's tools.
In this lab we build a product search MCP server with two tools:
list_categoriesβ returns product categoriessearch_productsβ searches products by keyword
Prerequisites SetupΒΆ
Make sure you have Python 3.10+:
π¦ Supporting FilesΒΆ
Download these files before starting the lab
Save all files to a lab-020/ folder in your working directory.
| File | Description | Download |
|---|---|---|
outdoorgear_mcp_server_starter.py |
Starter script with TODOs | π₯ Download |
Lab ExerciseΒΆ
Step 1: Create the projectΒΆ
Create server.py:
from fastmcp import FastMCP
# Initialize the MCP server
mcp = FastMCP(
name="products-mcp-server",
description="A product catalog MCP server for learning",
)
# Mock product data (in a real server, this would query a database)
PRODUCTS = [
{"id": 1, "name": "Waterproof Hiking Boots", "category": "Footwear", "price": 129.99},
{"id": 2, "name": "Camping Tent 4-Person", "category": "Camping", "price": 249.99},
{"id": 3, "name": "LED Headlamp 500lm", "category": "Lighting", "price": 34.99},
{"id": 4, "name": "Stainless Steel Water Bottle", "category": "Hydration", "price": 24.99},
{"id": 5, "name": "Trekking Poles Set", "category": "Hiking", "price": 79.99},
{"id": 6, "name": "Solar-Powered Charger", "category": "Electronics", "price": 59.99},
{"id": 7, "name": "Thermal Sleeping Bag -10Β°C", "category": "Camping", "price": 189.99},
{"id": 8, "name": "First Aid Kit Pro", "category": "Safety", "price": 44.99},
]
Step 2: Define your ToolsΒΆ
Add the tools after the PRODUCTS list:
@mcp.tool()
def list_categories() -> list[str]:
"""List all available product categories in the catalog."""
categories = sorted(set(p["category"] for p in PRODUCTS))
return categories
@mcp.tool()
def search_products(
keyword: str,
category: str | None = None,
max_price: float | None = None,
max_results: int = 5,
) -> list[dict]:
"""
Search products by keyword.
Args:
keyword: Search term to match against product names
category: Optional category filter (use list_categories to see options)
max_price: Optional maximum price filter
max_results: Maximum number of results to return (default: 5)
"""
results = []
keyword_lower = keyword.lower()
for product in PRODUCTS:
# Filter by keyword
if keyword_lower not in product["name"].lower():
continue
# Filter by category
if category and product["category"] != category:
continue
# Filter by price
if max_price and product["price"] > max_price:
continue
results.append(product)
return results[:max_results]
@mcp.tool()
def get_product_by_id(product_id: int) -> dict | None:
"""
Get a specific product by its ID.
Args:
product_id: The unique product ID
"""
for product in PRODUCTS:
if product["id"] == product_id:
return product
return None
Step 3: Run the server (stdio mode)ΒΆ
Add the entry point at the bottom of server.py:
Run it:
The server is now listening on stdio. This is the default mode for local tools.
Step 4: Test with the MCP InspectorΒΆ
Open a new terminal and run:
The Inspector will open in your browser. Try:
- Click "Tools" to see your three tools
- Click
list_categoriesβ "Run tool" β see the categories - Click
search_productsβ fill inkeyword: "tent"β "Run tool"
You should see the Camping Tent in the results
Step 5: Run as HTTP/SSE server (for remote agents)ΒΆ
For cloud-hosted agents like Microsoft Foundry, we need HTTP/SSE transport. Add the startup option:
if __name__ == "__main__":
import sys
if "--http" in sys.argv:
# HTTP/SSE mode for remote agents
mcp.run(transport="sse", host="0.0.0.0", port=8000)
else:
# stdio mode for local tools (default)
mcp.run()
Run in HTTP mode:
You'll see:
Test with curl:
Step 6: Connect to GitHub Copilot in VS CodeΒΆ
- In VS Code, create
.vscode/mcp.jsonin your workspace:
{
"servers": {
"products": {
"type": "stdio",
"command": "python",
"args": ["server.py"],
"cwd": "${workspaceFolder}"
}
}
}
- Open GitHub Copilot Chat in VS Code
- Type:
@copilot What product categories are available?
GitHub Copilot will call your list_categories tool and include the result in its response!
VS Code MCP support
Make sure you have the GitHub Copilot extension version 1.99+ installed.
You may need to enable MCP in VS Code settings: "chat.mcp.enabled": true
Adding a Resource (Bonus)ΒΆ
MCP also supports Resources β data the agent can read. Add a resource that exposes the full product catalog:
@mcp.resource("products://catalog")
def get_product_catalog() -> str:
"""The full product catalog as CSV."""
lines = ["id,name,category,price"]
for p in PRODUCTS:
lines.append(f"{p['id']},{p['name']},{p['category']},{p['price']}")
return "\n".join(lines)
π Starter FileΒΆ
This lab includes a starter file with TODO markers to guide you through building the server:
# Copy the starter file to your working directory
cp lab-020/outdoorgear_mcp_server_starter.py products-mcp-server/server.py
cd products-mcp-server
# Install dependencies
pip install fastmcp
# Work through the TODOs in the file, then run:
python server.py
The starter contains the OutdoorGear product catalog (P001βP007) already populated. You implement: list_categories, search_products, get_product_details, and a challenge tool compare_products.
π Challenge: Add a compare_products ToolΒΆ
Once you have the basic 3 tools working, add a fourth:
@mcp.tool()
def compare_products(product_ids: list[str]) -> dict:
"""
Compare multiple products side by side.
Args:
product_ids: List of 2β4 product IDs to compare (e.g. ["P001", "P003"])
"""
# TODO: implement comparison
# Return: {"products": [...], "not_found": [...], "lightest": "...", "cheapest": "..."}
Test it in the MCP Inspector by asking:
"Compare the TrailBlazer Tent 2P and the TrailBlazer Solo. Which is lighter?"
The agent should call compare_products(["P001", "P003"]) and return a structured comparison.
SummaryΒΆ
You've built a fully functional MCP server that:
- β Defines 3 tools with proper descriptions (the LLM uses these to decide when to call)
- β Runs in stdio mode for local tools
- β Runs in HTTP/SSE mode for remote agents
- β Works with the MCP Inspector for testing
- β Integrates with GitHub Copilot in VS Code
Next StepsΒΆ
- C# version: β Lab 021 β MCP Server in C#
- Connect to Microsoft Foundry Agent Service: β Lab 030 β Foundry Agent Service + MCP
- Add real database queries: β Lab 031 β pgvector Semantic Search