🦌 MCP Server for Santa: Managing Reindeer with Azure Functions
🎄 How to Create an MCP Server for Santa: Integrating Reindeer with Copilot Studio
📑 Table of Contents
- Introduction
- What Is an MCP Server?
- Advantages of Using MCP Server
- Step 1: Create Azure Function App
- Step 2: Deploy on Azure
- Step 3: Configure API Management
- Step 4: Configure MCP Server
- Step 5: Use the API in Copilot Studio
- Monitoring and Debugging
- Real Use Cases
- Conclusion
- Next Steps
Introduction
In this article, I’ll show you how to create a Christmas-themed MCP Server (Model Context Protocol) and deploy it on Azure to integrate it with Copilot Studio. We’ll build an API for managing reindeer from the North Pole, connect it to API Management, and leverage the MCP Server preview feature so Copilot Studio can query available reindeer for gift delivery.
Christmas context: This MCP Server is part of Santa’s Headquarters: Multi-agents in Copilot Studio where the Reindeer Preparing Elf accesses data about available reindeer.
What Is an MCP Server?
The Model Context Protocol (MCP) is an open standard that allows AI models to securely and structurally access tools and data. An MCP Server exposes your services (APIs, databases, etc.) so Copilot Studio can discover and use them automatically.
In our case, we expose the Santa’s Reindeer API so the Reindeer Preparing Elf can:
- Query the status of reindeer
- Verify availability by date
- Assign the optimal team for each delivery
Advantages of Using MCP Server with Copilot Studio
- 🚀 Quick integration without complex code
- 🔐 Security through API Management
- 📊 Scalability with serverless on Azure
- 🤖 Process automation with AI
- 💰 Cost-effective with pay-as-you-go architecture
- 🦌 Real-time reindeer management
Step 1: Create an Azure Function App in Python
We’ll create a Function App with three HTTP endpoints that manage Santa’s reindeer for Headquarters.
Project Structure
ReindeerAPI/
├── function_app.py
├── requirements.txt
├── host.json
├── local.settings.json
├── .gitignore
└── README.md
Understanding Azure Functions Structure
Before creating functions, it’s important to understand how they’re structured in Azure Functions v4 with Python.
Main Components
-
Required Imports
azure.functions: Main library for Azure Functionsjson: For serializing/deserializing JSON data
-
FunctionApp Instance
app = func.FunctionApp()This is the base application that contains all endpoints.
-
Decorators
@app.route(): Defines an HTTP route@app.function_name(): Unique function name (optional)- Defines HTTP method (GET, POST, PUT, DELETE, etc.)
-
Function Signature
def my_function(req: func.HttpRequest) -> func.HttpResponse:- Receives an
HttpRequest - Returns an
HttpResponse
- Receives an
Parts of an HTTP Function
@app.function_name("get_reindeer") # Unique name (optional)
@app.route(route="reindeer") # Endpoint route
def get_reindeer(req: func.HttpRequest) -> func.HttpResponse:
"""Returns a list of available reindeer""" # Docstring
# LOGIC: Process the request
reindeer = [
{"id": 1, "name": "Rudolph", "energy": 95, "available": True},
{"id": 2, "name": "Donner", "energy": 88, "available": True}
]
# RESPONSE: Return data with status code
return func.HttpResponse(
json.dumps(reindeer), # Body (converted to JSON)
status_code=200, # HTTP code
mimetype="application/json" # Content type
)
Create the Function App
import azure.functions as func
import json
from datetime import datetime, timedelta
app = func.FunctionApp()
# Endpoint 1: Get all Santa's reindeer
@app.function_name("reindeer_list")
@app.route(route="reindeer")
def get_reindeer(req: func.HttpRequest) -> func.HttpResponse:
"""
Gets the complete list of Santa's reindeer with their availability
for the North Pole Headquarters. Used by the Reindeer Preparing Elf.
"""
reindeer = [
{
"id": 1,
"name": "Rudolph",
"emoji": "🔴",
"description": "The guide reindeer with the bright red nose",
"available": True,
"last_flight": "2025-11-27",
"energy_level": 95,
"speed_kmh": 250,
"capacity_kg": 500,
"next_available": "2025-12-01T08:00:00Z",
"flight_history": [
{"date": "2025-11-27", "duration_hours": 4, "distance_km": 850},
{"date": "2025-11-26", "duration_hours": 3.5, "distance_km": 750}
]
},
{
"id": 2,
"name": "Donner",
"emoji": "🦌",
"description": "Fast and reliable, excellent for long routes",
"available": True,
"last_flight": "2025-11-28",
"energy_level": 88,
"speed_kmh": 240,
"capacity_kg": 480,
"next_available": "2025-12-01T09:00:00Z",
"flight_history": []
},
{
"id": 3,
"name": "Blitzen",
"emoji": "⚡",
"description": "The lightning striker, fastest of all",
"available": True,
"last_flight": "2025-11-25",
"energy_level": 92,
"speed_kmh": 280,
"capacity_kg": 490,
"next_available": "2025-12-01T07:00:00Z",
"flight_history": []
},
{
"id": 4,
"name": "Cupid",
"emoji": "💘",
"description": "Strong and dependable, handles difficult terrain",
"available": True,
"last_flight": "2025-11-27",
"energy_level": 85,
"speed_kmh": 235,
"capacity_kg": 510,
"next_available": "2025-12-01T10:00:00Z",
"flight_history": []
},
{
"id": 5,
"name": "Comet",
"emoji": "☄️",
"description": "Stable flyer, good for weather conditions",
"available": False,
"last_flight": "2025-11-28",
"energy_level": 60,
"speed_kmh": 230,
"capacity_kg": 475,
"next_available": "2025-12-02T14:00:00Z",
"flight_history": []
},
{
"id": 6,
"name": "Dasher",
"emoji": "💨",
"description": "Quick starter, excellent acceleration",
"available": True,
"last_flight": "2025-11-26",
"energy_level": 90,
"speed_kmh": 260,
"capacity_kg": 485,
"next_available": "2025-12-01T08:30:00Z",
"flight_history": []
},
{
"id": 7,
"name": "Prancer",
"emoji": "🎩",
"description": "Elegant and precise, for premium routes",
"available": True,
"last_flight": "2025-11-28",
"energy_level": 93,
"speed_kmh": 245,
"capacity_kg": 495,
"next_available": "2025-12-01T07:30:00Z",
"flight_history": []
}
]
return func.HttpResponse(
json.dumps(reindeer),
status_code=200,
mimetype="application/json"
)
# Endpoint 2: Get reindeer by ID
@app.function_name("get_reindeer_by_id")
@app.route(route="reindeer/{reindeer_id}")
def get_reindeer_by_id(req: func.HttpRequest) -> func.HttpResponse:
"""
Gets a specific reindeer by ID with detailed information.
Path parameter: reindeer_id (integer)
"""
reindeer_id = req.route_params.get("reindeer_id")
if not reindeer_id:
return func.HttpResponse(
json.dumps({"error": "reindeer_id is required"}),
status_code=400,
mimetype="application/json"
)
# Simplified: In production, query a database
reindeer_data = {
"1": {
"id": 1,
"name": "Rudolph",
"description": "The guide reindeer with the bright red nose",
"available": True,
"energy_level": 95
},
"2": {
"id": 2,
"name": "Donner",
"description": "Fast and reliable",
"available": True,
"energy_level": 88
}
}
reindeer = reindeer_data.get(str(reindeer_id))
if not reindeer:
return func.HttpResponse(
json.dumps({"error": f"Reindeer {reindeer_id} not found"}),
status_code=404,
mimetype="application/json"
)
return func.HttpResponse(
json.dumps(reindeer),
status_code=200,
mimetype="application/json"
)
# Endpoint 3: Assign reindeer for delivery
@app.function_name("assign_reindeer")
@app.route(route="assign", methods=["POST"])
def assign_reindeer(req: func.HttpRequest) -> func.HttpResponse:
"""
Assigns a reindeer for a delivery based on optimal criteria.
Body: {"delivery_date": "2025-12-01", "distance_km": 500, "priority": "high"}
"""
try:
req_body = req.get_json()
except ValueError:
return func.HttpResponse(
json.dumps({"error": "Invalid JSON"}),
status_code=400,
mimetype="application/json"
)
delivery_date = req_body.get("delivery_date")
distance_km = req_body.get("distance_km", 500)
priority = req_body.get("priority", "normal")
if not delivery_date:
return func.HttpResponse(
json.dumps({"error": "delivery_date is required"}),
status_code=400,
mimetype="application/json"
)
# Assign the best reindeer based on distance and priority
assignment = {
"delivery_date": delivery_date,
"distance_km": distance_km,
"priority": priority,
"assigned_reindeer": "Rudolph",
"reindeer_id": 1,
"estimated_time_hours": distance_km / 250,
"status": "ASSIGNED",
"confirmation_code": "SANTA-2025-001-RUD"
}
return func.HttpResponse(
json.dumps(assignment),
status_code=200,
mimetype="application/json"
)
Summary of Endpoint Types Used
| Endpoint | Method | Purpose | Path Parameter | Body |
|---|---|---|---|---|
/reindeer | GET | Get all reindeer | No | No |
/reindeer/{reindeer_id} | GET | Get specific reindeer | Yes (ID) | No |
/assign | POST | Assign reindeer for delivery | No | Yes (JSON) |
Step 2: Deploy the Function App on Azure
To deploy to Azure:
# 1. Create a resource group
az group create --name SantasHQ --location eastus
# 2. Create a storage account
az storage account create \
--name santastorageacct \
--resource-group SantasHQ \
--location eastus
# 3. Create the Function App
az functionapp create \
--resource-group SantasHQ \
--consumption-plan-location eastus \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--name ReindeerAPI \
--storage-account santastorageacct
# 4. Deploy the code
func azure functionapp publish ReindeerAPI
Step 3: Configure API Management
To secure and manage the API:
# 1. Create API Management instance
az apim create \
--name SantasAPIM \
--resource-group SantasHQ \
--publisher-name "Santa's Headquarters" \
--publisher-email "santa@northpole.com" \
--sku-name Developer
# 2. Create API
az apim api create \
--resource-group SantasHQ \
--apim-name SantasAPIM \
--display-name "Reindeer API" \
--path reindeer \
--protocols https
# 3. Add operations
az apim api operation create \
--resource-group SantasHQ \
--apim-name SantasAPIM \
--api-id reindeer \
--operation-id list-reindeer \
--display-name "List All Reindeer" \
--method GET \
--url-template "/reindeer"
Step 4: Configure the MCP Server in Azure API Management (Preview)
Azure API Management includes a built-in MCP Server feature (currently in preview) that allows you to expose your managed APIs as MCP servers. This is the recommended approach for production MCP configurations.
Create MCP Server in API Management Portal
-
Navigate to MCP Servers:
- Open Azure Portal
- Go to your API Management instance (SantasAPIM)
- In the left menu, under APIs, select MCP Servers
-
Create New MCP Server:
- Click + Create MCP server
- Select Expose an API as an MCP server
-
Configure Backend MCP Server:
- Select the API: Choose your Reindeer API from the dropdown
- Select operations to expose as tools:
- ✓ List All Reindeer (
GET /reindeer) - ✓ Get Reindeer Details (
GET /reindeer/{reindeer_id}) - ✓ Assign Reindeer for Delivery (
POST /assign)
- ✓ List All Reindeer (
-
Configure New MCP Server Details:
- Name:
Reindeer MCP - Description:
Manages reindeer availability and assignments for Santa's delivery operations
- Name:
-
Click Create
- Azure API Management generates the MCP server endpoint
- Operations become available as “tools” for AI agents
- Endpoint format:
https://{apim-name}.azure-api-ms.net/mcp
Configure Access Policies (Optional but Recommended)
Add policies to your MCP server for enhanced security:
<!-- Authentication: Require API Key -->
<validate-azure-ad-token failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<client-certificate-thumbprint>thumbprint</client-certificate-thumbprint>
</validate-azure-ad-token>
<!-- Rate Limiting: Prevent abuse -->
<rate-limit-by-key calls="100" renewal-period="60" counter-key="@(context.Request.Headers.GetValueOrDefault("Subscription-Key"))"/>
<!-- IP Filtering: Only allow Copilot Studio IPs -->
<ip-filter action="allow">
<address>40.70.0.0/16</address>
<address>20.42.0.0/16</address>
</ip-filter>
Get Your MCP Server Endpoint
After creation, copy the MCP server endpoint:
- Go back to MCP Servers list
- Click your Reindeer MCP server
- Copy the Endpoint URL:
https://santasapim.azure-api-ms.net/mcp - Note the Access Key for authentication
Step 5: Connect MCP Server to Copilot Studio (or GitHub Copilot)
Option A: Connect with GitHub Copilot (Agent Mode)
- Open Visual Studio Code
- Select Agent Mode in GitHub Copilot (if available)
- Add MCP Server:
- Navigate to Copilot Agent Settings
- Add MCP server endpoint:
https://santasapim.azure-api-ms.net/mcp - Provide access key for authentication
- Select Tools:
- Check the boxes for reindeer operations you want available
- The agent now has access to these tools
Option B: Connect with Copilot Studio
- Go to Copilot Studio → Settings → Connected services
- Add Custom MCP Server (if available)
- Configure:
- Server URL:
https://santasapim.azure-api-ms.net/mcp - API Key: Your API Management subscription key
- Name: Reindeer MCP
- Server URL:
- Save and test the connection
Use the MCP Server in Your Agent
In your Reindeer Preparing Elf agent:
- Go to Actions or Knowledge sources
- The MCP tools are now available as actions:
List All Reindeer- Get available reindeerGet Reindeer Details- Check specific reindeer statusAssign Reindeer for Delivery- Assign for specific delivery
- Create agent instructions to use these tools when needed
Monitoring and Debugging
Monitor the Function App
# View logs
az functionapp logs tail --name ReindeerAPI --resource-group SantasHQ
# Monitor performance
az monitor metrics list \
--resource /subscriptions/{subscription-id}/resourceGroups/SantasHQ/providers/Microsoft.Web/sites/ReindeerAPI \
--metric "FunctionExecutionCount"
Monitor MCP Server in API Management
Monitor your MCP server through Azure Portal:
-
Go to your API Management instance → MCP Servers
-
Click your Reindeer MCP server
-
View Metrics:
- Requests: Total API calls through MCP
- Successful Requests: Requests completed successfully
- Failed Requests: API errors and failures
- Average Response Time: Performance monitoring
- Gateway CPU: API Management resource usage
- Gateway Memory: Memory consumption
-
Set up Alerts:
- Go to Monitoring → Alerts
- Create alert rule for high error rate or slow responses
- Configure notifications (email, Slack, etc.)
-
Enable Application Insights (optional):
- Configure in API Management settings
- Get detailed diagnostic logs and tracing
- Track agent calls to MCP tools
Real Use Cases
Example 1: Check Reindeer Availability
User: "Check which reindeer are available for tomorrow"
→ Reindeer Preparing Elf calls /reindeer
→ Gets list of available reindeer
→ Returns recommendations to Coordinator
Example 2: Assign for Delivery
User: "Assign a reindeer for a 500km delivery"
→ Route Optimization Elf calls /assign with POST
→ MCP Server processes and returns assignment
→ Coordinator confirms the assignment
Conclusion
This MCP Server demonstrates how to:
- Build a Python-based Azure Function
- Deploy serverless infrastructure
- Integrate with Copilot Studio
- Create real business automation
The Reindeer MCP is just the beginning of Santa’s AI-powered North Pole! 🎅🦌✨