Skip to content

Lab 020: Build an MCP Server in PythonΒΆ

Level: L200 Path: πŸ”Œ MCP Time: ~45 min πŸ’° Cost: Free β€” Runs locally, no cloud account needed

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:

  1. list_categories β€” returns product categories
  2. search_products β€” searches products by keyword

Prerequisites SetupΒΆ

pip install fastmcp

Make sure you have Python 3.10+:

python --version


Quick Start with GitHub Codespaces

Open in GitHub Codespaces

All dependencies are pre-installed in the devcontainer.

πŸ“¦ 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ΒΆ

mkdir products-mcp-server
cd products-mcp-server

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:

if __name__ == "__main__":
    mcp.run()

Run it:

python server.py

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:

npx @modelcontextprotocol/inspector python server.py

The Inspector will open in your browser. Try:

  1. Click "Tools" to see your three tools
  2. Click list_categories β†’ "Run tool" β†’ see the categories
  3. Click search_products β†’ fill in keyword: "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:

python server.py --http

You'll see:

INFO:     Uvicorn running on http://0.0.0.0:8000

Test with curl:

curl http://localhost:8000/sse

Step 6: Connect to GitHub Copilot in VS CodeΒΆ

  1. In VS Code, create .vscode/mcp.json in your workspace:
{
  "servers": {
    "products": {
      "type": "stdio",
      "command": "python",
      "args": ["server.py"],
      "cwd": "${workspaceFolder}"
    }
  }
}
  1. Open GitHub Copilot Chat in VS Code
  2. 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:

lab-020/
└── outdoorgear_mcp_server_starter.py   ← 6 TODOs to complete
# 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ΒΆ