Skip to content

Commit 4bdb858

Browse files
committed
initial
1 parent 8d0c745 commit 4bdb858

5 files changed

Lines changed: 156 additions & 66 deletions

File tree

README.md

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Python Todo List App with MCP Integration
22

3-
A Flask-based todo list application that exposes operations as MCP (Model Context Protocol) tools over HTTP and can be deployed to Azure App Service.
3+
A FastAPI-based todo list application that exposes operations as MCP (Model Context Protocol) tools over HTTP and can be deployed to Azure App Service.
44

55
## Features
66

77
-**CRUD Operations**: Create, read, update, delete todos
88
- 🎯 **Priority Management**: Set priority levels (low, medium, high)
99
-**Mark Complete/Incomplete**: Toggle completion status
10-
- 🌐 **Web Interface**: Clean, responsive Bootstrap UI
11-
- 💬 **Chat Interface**: Placeholder for future LLM integration
10+
- 🌐 **Web Interface**: Clean, responsive UI with copy-to-clipboard functionality
1211
- 🔧 **MCP Tools**: HTTP-accessible tools for external integrations
12+
- 📋 **MCP URL Display**: Dynamic MCP server URL with one-click copy functionality
1313
- ☁️ **Azure Ready**: Optimized for Azure App Service deployment
1414

1515
## MCP Tools Available
@@ -26,15 +26,15 @@ The application exposes the following MCP tools over HTTP:
2626

2727
### Prerequisites
2828

29-
- Python 3.12+
29+
- Python 3.11+
3030
- Virtual environment (venv)
3131

3232
### Setup
3333

3434
1. **Clone and setup environment**:
3535
```bash
36-
git clone <repository-url>
37-
cd python-todo-mcp-agent
36+
git clone https://github.com/Azure-Samples/app-service-python-todo-mcp
37+
cd app-service-python-todo-mcp
3838
python -m venv .venv
3939
.venv\Scripts\activate # Windows
4040
# or
@@ -46,22 +46,15 @@ The application exposes the following MCP tools over HTTP:
4646
pip install -r requirements.txt
4747
```
4848

49-
3. **Set environment variables** (optional):
50-
```bash
51-
set SECRET_KEY=your-secret-key-here
52-
set DATABASE_URL=sqlite:///todos.db
53-
```
54-
55-
4. **Run the application**:
49+
3. **Run the application**:
5650
```bash
5751
python main.py
5852
```
5953

60-
5. **Access the application**:
54+
4. **Access the application**:
6155
- Web Interface: http://localhost:8000
62-
- Chat Interface: http://localhost:8000/chat
6356
- Health Check: http://localhost:8000/health
64-
- MCP Endpoint: http://localhost:8000/mcp
57+
- MCP Endpoint: http://localhost:8000/mcp/stream
6558
- MCP Tools: http://localhost:8000/mcp/tools/*
6659

6760
## Azure Deployment
@@ -78,13 +71,7 @@ The application exposes the following MCP tools over HTTP:
7871
azd init
7972
```
8073

81-
2. **Set environment variables**:
82-
```bash
83-
azd env set SECRET_KEY "your-production-secret-key"
84-
azd env set DATABASE_URL "sqlite:///todos.db"
85-
```
86-
87-
3. **Deploy to Azure**:
74+
2. **Deploy to Azure**:
8875
```bash
8976
azd up
9077
```
@@ -94,20 +81,19 @@ The application exposes the following MCP tools over HTTP:
9481
The deployment creates:
9582

9683
- **App Service Plan**: P0V3 (Premium V3, Linux)
97-
- **App Service**: Python 3.12 runtime
98-
- **Managed Identity**: Secure Azure resource access
84+
- **App Service**: Python 3.11 runtime with Uvicorn ASGI server
9985

10086
## Architecture
10187

10288
```
10389
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
104-
│ Web Browser │────│ Flask App │────│ SQLite DB │
90+
│ Web Browser │────│ FastAPI App │────│ SQLite DB │
10591
│ (Todo UI) │ │ (CRUD API) │ │ (Data Store) │
10692
└─────────────────┘ └──────────────────┘ └─────────────────┘
10793
10894
┌──────────────────┐
10995
│ MCP Server │
110-
│ (HTTP Tools)
96+
│ (HTTP Stream)
11197
└──────────────────┘
11298
11399
┌──────────────────┐
@@ -127,46 +113,38 @@ The deployment creates:
127113
- `PATCH /api/todos/{id}/complete` - Toggle completion
128114

129115
### MCP Integration
130-
- `GET /mcp` - MCP server info and available tools
131-
- `POST /mcp` - MCP protocol endpoint (JSON-RPC 2.0)
116+
- `GET /mcp/stream` - MCP server endpoint (HTTP streaming)
117+
- `POST /mcp/stream` - MCP protocol endpoint (JSON-RPC 2.0)
132118
- `GET/POST /mcp/tools/create_todo` - Direct tool access
133119
- `GET/POST /mcp/tools/list_todos` - Direct tool access
134120
- `POST /mcp/tools/update_todo` - Direct tool access
135121
- `POST /mcp/tools/delete_todo` - Direct tool access
136122
- `POST /mcp/tools/mark_todo_complete` - Direct tool access
137123

138-
## Environment Variables
124+
## MCP Configuration
139125

140-
| Variable | Description | Default |
141-
|----------|-------------|---------|
142-
| `SECRET_KEY` | Flask secret key for sessions | `dev-secret-key` |
143-
| `DATABASE_URL` | Database connection string | `sqlite:///todos.db` |
126+
The application automatically displays the MCP server URL in the web interface with environment-aware detection:
144127

145-
## Development Notes
128+
- **Local Development**: `http://localhost:8000/mcp/stream`
129+
- **Azure App Service**: `https://your-app-name.azurewebsites.net/mcp/stream`
146130

147-
- **Database**: Uses SQLite for simplicity (can be upgraded to Azure SQL)
148-
- **Styling**: Bootstrap 5 with Font Awesome icons
149-
- **Frontend**: Vanilla JavaScript with fetch API
150-
- **Backend**: Flask with SQLAlchemy ORM
151-
- **MCP**: Native JSON-RPC 2.0 implementation in Flask
152-
- **Security**: HTTPS only, managed identity, CORS enabled
131+
### VS Code MCP Integration
153132

154-
## Future Enhancements
133+
To use this app as an MCP server in VS Code, add the following to your `.vscode/mcp.json`:
155134

156-
- 🤖 **LLM Integration**: Connect chat interface to language models
157-
- 🗄️ **Database Upgrade**: Migrate to Azure SQL Database
158-
- 🔐 **Authentication**: Add user accounts and auth
159-
- 📱 **Mobile App**: React Native or Flutter companion
160-
- 🔍 **Search**: Full-text search capabilities
161-
- 📊 **Analytics**: Usage metrics and insights
162-
163-
## Contributing
135+
```json
136+
{
137+
"servers": {
138+
"todo-mcp-server": {
139+
"url": "http://localhost:8000/mcp/stream",
140+
"type": "http"
141+
}
142+
},
143+
"inputs": []
144+
}
145+
```
164146

165-
1. Fork the repository
166-
2. Create a feature branch
167-
3. Make your changes
168-
4. Add tests if applicable
169-
5. Submit a pull request
147+
For the deployed Azure version, replace the URL with your App Service URL.
170148

171149
## License
172150

infra/main.bicep

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ param environmentName string
66
@description('Primary location for all resources')
77
param location string = resourceGroup().location
88

9-
@description('Secret key for Flask application')
10-
@secure()
11-
param secretKey string = ''
9+
1210

1311
// Generate unique resource token
1412
var resourceToken = uniqueString(subscription().id, resourceGroup().id, location, environmentName)
@@ -63,10 +61,6 @@ resource appService 'Microsoft.Web/sites@2024-04-01' = {
6361
ftpsState: 'FtpsOnly'
6462
appCommandLine: 'python -m uvicorn main:app --host 0.0.0.0 --port 8000'
6563
appSettings: [
66-
{
67-
name: 'SECRET_KEY'
68-
value: secretKey
69-
}
7064
{
7165
name: 'WEBSITES_PORT'
7266
value: '8000'

main.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pydantic import BaseModel
1010
from typing import Any, Dict, Optional
1111
import logging
12+
import os
1213
from contextlib import asynccontextmanager
1314
from datetime import datetime
1415

@@ -398,7 +399,19 @@ async def lifespan(app: FastAPI):
398399
@app.get("/", response_class=HTMLResponse)
399400
async def root(request: Request):
400401
"""Serve the main todo list page"""
401-
return templates.TemplateResponse("index.html", {"request": request})
402+
# Determine if running locally or in Azure App Service
403+
host = request.headers.get("host", "localhost:8000")
404+
405+
# Check if running in Azure App Service
406+
if "azurewebsites.net" in host:
407+
mcp_server_url = f"https://{host}/mcp/stream"
408+
else:
409+
mcp_server_url = f"http://{host}/mcp/stream"
410+
411+
return templates.TemplateResponse("index.html", {
412+
"request": request,
413+
"mcp_server_url": mcp_server_url
414+
})
402415

403416
@app.get("/health")
404417
async def health():

static/css/style.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,67 @@ body {
2929
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
3030
}
3131

32+
.mcp-info {
33+
margin-top: 15px;
34+
padding: 15px;
35+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
36+
border-radius: 8px;
37+
border-left: 4px solid #007bff;
38+
display: flex;
39+
align-items: center;
40+
justify-content: center;
41+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
42+
}
43+
44+
.mcp-url {
45+
color: #495057;
46+
font-size: 0.95em;
47+
display: flex;
48+
align-items: center;
49+
gap: 8px;
50+
}
51+
52+
.mcp-url code {
53+
background: #f8f9fa;
54+
padding: 8px 12px;
55+
border-radius: 6px;
56+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
57+
font-size: 0.9em;
58+
color: #0d6efd;
59+
border: 1px solid #e9ecef;
60+
font-weight: 500;
61+
letter-spacing: -0.5px;
62+
display: inline-block;
63+
word-break: break-all;
64+
}
65+
66+
.copy-btn {
67+
background: #007bff;
68+
color: white;
69+
border: none;
70+
cursor: pointer;
71+
padding: 8px 12px;
72+
border-radius: 6px;
73+
font-size: 0.9em;
74+
transition: all 0.2s ease;
75+
display: flex;
76+
align-items: center;
77+
gap: 4px;
78+
font-weight: 500;
79+
box-shadow: 0 2px 4px rgba(0,123,255,0.2);
80+
}
81+
82+
.copy-btn:hover {
83+
background: #0056b3;
84+
transform: translateY(-1px);
85+
box-shadow: 0 4px 8px rgba(0,123,255,0.3);
86+
}
87+
88+
.copy-btn:active {
89+
transform: translateY(0px);
90+
box-shadow: 0 2px 4px rgba(0,123,255,0.2);
91+
}
92+
3293
.header h1 {
3394
color: #2c3e50;
3495
margin-bottom: 10px;

templates/index.html

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>App Service To-do List App</title>
6+
<title>App Service MCP To-do List App</title>
77
<link rel="stylesheet" href="/static/css/style.css">
88
</head>
99
<body>
1010
<div class="container">
1111
<!-- Header -->
1212
<div class="header">
13-
<h1>App Service To-do List App</h1>
14-
<p>With MCP and Agent Integration</p>
13+
<h1>App Service MCP To-do List App</h1>
14+
<div class="mcp-info">
15+
<div class="mcp-url">
16+
<span>MCP Server:</span>
17+
<code id="mcpUrl">{{ mcp_server_url }}</code>
18+
<button id="copyBtn" class="copy-btn" onclick="copyMcpUrl()" title="Copy MCP Server URL">
19+
📋 Copy
20+
</button>
21+
</div>
22+
</div>
1523
</div>
1624

1725
<!-- Add Todo Section -->
@@ -55,5 +63,41 @@ <h2>Your Todos</h2>
5563
</div>
5664

5765
<script src="/static/js/todo.js"></script>
66+
<script>
67+
function copyMcpUrl() {
68+
const mcpUrl = document.getElementById('mcpUrl').textContent;
69+
const copyBtn = document.getElementById('copyBtn');
70+
71+
navigator.clipboard.writeText(mcpUrl).then(function() {
72+
// Change button text temporarily to show success
73+
const originalText = copyBtn.innerHTML;
74+
copyBtn.innerHTML = '✅ Copied!';
75+
copyBtn.style.background = '#28a745';
76+
77+
setTimeout(function() {
78+
copyBtn.innerHTML = originalText;
79+
copyBtn.style.background = '';
80+
}, 2000);
81+
}).catch(function(err) {
82+
// Fallback for older browsers
83+
const textArea = document.createElement('textarea');
84+
textArea.value = mcpUrl;
85+
document.body.appendChild(textArea);
86+
textArea.select();
87+
document.execCommand('copy');
88+
document.body.removeChild(textArea);
89+
90+
// Show success feedback
91+
const originalText = copyBtn.innerHTML;
92+
copyBtn.innerHTML = '✅ Copied!';
93+
copyBtn.style.background = '#28a745';
94+
95+
setTimeout(function() {
96+
copyBtn.innerHTML = originalText;
97+
copyBtn.style.background = '';
98+
}, 2000);
99+
});
100+
}
101+
</script>
58102
</body>
59103
</html>

0 commit comments

Comments
 (0)