Skip to content

Commit c308c5b

Browse files
committed
Add HTTP MCP server, debug setup, and README updates
- Add main_http.py: HTTP-mode MCP expenses server and resources/prompts - Add .vscode/launch.json: debugpy "Attach to MCP Server" config (port 5678) - Update .vscode/mcp.json: ensure debug and http server entries and set PYTHONUNBUFFERED - Add debugpy to pyproject.toml and uv.lock (lock file entries for debugpy) - Update README: add Table of Contents, clarify MCP Inspector launch commands, and add debugging instructions for stdio and HTTP servers
1 parent 8a06b95 commit c308c5b

File tree

6 files changed

+264
-17
lines changed

6 files changed

+264
-17
lines changed

.vscode/launch.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Attach to MCP Server",
6+
"type": "debugpy",
7+
"request": "attach",
8+
"connect": {
9+
"host": "localhost",
10+
"port": 5678
11+
},
12+
"pathMappings": [
13+
{
14+
"localRoot": "${workspaceFolder}",
15+
"remoteRoot": "${workspaceFolder}"
16+
}
17+
]
18+
}
19+
]
20+
}

.vscode/mcp.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,32 @@
77
"args": [
88
"run",
99
"main.py"
10-
]
10+
],
11+
"env": {
12+
"PYTHONUNBUFFERED": "1"
13+
}
14+
},
15+
"expenses-mcp-debug": {
16+
"type": "stdio",
17+
"command": "uv",
18+
"cwd": "${workspaceFolder}",
19+
"args": [
20+
"run",
21+
"--",
22+
"python",
23+
"-m",
24+
"debugpy",
25+
"--listen",
26+
"0.0.0.0:5678",
27+
"main.py"
28+
],
29+
"env": {
30+
"PYTHONUNBUFFERED": "1"
31+
}
32+
},
33+
"expenses-mcp-http": {
34+
"type": "http",
35+
"url": "http://localhost:8000/mcp"
1136
}
1237
},
1338
"inputs": []

README.md

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ This repository implements a **minimal MCP expense tracker**.
44

55
The Model Context Protocol (MCP) is an open standard that enables LLMs to connect to external data sources and tools.
66

7+
## Table of Contents
8+
9+
- [Getting Started](#getting-started)
10+
- [Environment Setup](#environment-setup)
11+
- [Run the MCP Server in VS Code](#run-the-mcp-server-in-vs-code)
12+
- [GitHub Copilot Chat Integration](#github-copilot-chat-integration)
13+
- [MCP Inspector](#mcp-inspector)
14+
- [Debugging](#debugging)
15+
- [Debugging with VS Code and debugpy](#debugging-with-vs-code-and-debugpy)
16+
- [Testing with MCP Inspector](#testing-with-mcp-inspector)
17+
- [Contributing](#contributing)
18+
719
## Getting Started
820

921
### Environment Setup
@@ -31,32 +43,29 @@ If you prefer a plain local environment, use **uv** for dependency management:
3143
uv sync
3244
```
3345

46+
### Use MCP Server with GitHub Copilot Chat
47+
3448
### Run the MCP Server in VS Code
3549

3650
1. Open `.vscode/mcp.json` in the editor
37-
2. Click the **Start** button (▶) above the server name `expenses-mcp`
38-
3. Confirm in the output panel that the server is running
39-
40-
### GitHub Copilot Chat Integration
41-
42-
Make sure the MCP server is running, then:
43-
51+
1. Click the **Start** button (▶) above the server name `expenses-mcp`
52+
1. Confirm in the output panel that the server is running
4453
1. Open the GitHub Copilot Chat panel (bottom right, or via Command Palette: `GitHub Copilot: Focus Chat`)
45-
2. Click the **Tools** icon (wrench) at the bottom of the chat panel
46-
3. Ensure `expenses-mcp` is selected in the list of available tools
47-
4. Ask Copilot to invoke the tool:
54+
1. Click the **Tools** icon (wrench) at the bottom of the chat panel
55+
1. Ensure `expenses-mcp` is selected in the list of available tools
56+
1. Ask Copilot to invoke the tool:
4857
- "Use add_expense to record a $12 lunch today paid with visa"
4958
- "Read the expenses resource"
5059

5160
### MCP Inspector
5261

5362
The MCP Inspector is a browser-based visual testing and debugging tool for MCP servers.
5463

55-
**Launch the inspector in GitHub Codespaces:**
64+
#### Launch the MCP Inspector in GitHub Codespaces
5665

5766
1. Run the following command in the terminal:
5867
```bash
59-
.devcontainer/launch-inspector.sh
68+
.devcontainer/launch-inspector-codespace.sh
6069
```
6170

6271
2. Note the **Inspector Proxy Address** and **Session Token** from the terminal output
@@ -70,18 +79,18 @@ The MCP Inspector is a browser-based visual testing and debugging tool for MCP s
7079
- **Command**: `uv`
7180
- **Arguments**: `run main.py`
7281

73-
**Launch the inspector inside of a Dev Container:**
82+
#### Launch the MCP Inspector inside of a Dev Container
7483

7584
1. Run the following command in the terminal:
7685
```bash
77-
HOST=0.0.0.0 DANGEROUSLY_OMIT_AUTH=true npx -y @modelcontextprotocol/inspector uv run main.py
86+
HOST=0.0.0.0 DANGEROUSLY_OMIT_AUTH=true npx -y @modelcontextprotocol/inspector
7887
```
7988
2. Open `http://localhost:6274` in your browser
8089
3. The Inspector should now connect to your MCP server
8190

8291
> **Note:** `HOST=0.0.0.0` is required in devcontainer environments to bind the Inspector to all network interfaces, allowing proper communication between the UI and proxy server. `DANGEROUSLY_OMIT_AUTH=true` disables authentication - only use in trusted development environments.
8392
84-
**Launch the inspector locally without Dev Container:**
93+
#### Launch the inspector locally without Dev Container:**
8594

8695
1. Run the following command in the terminal:
8796
```bash
@@ -90,6 +99,47 @@ The MCP Inspector is a browser-based visual testing and debugging tool for MCP s
9099
2. The Inspector will automatically open in your browser at `http://localhost:6274`
91100

92101

102+
---
103+
104+
## Debugging
105+
106+
You can attach the VS Code debugger to the running MCP server to set breakpoints and inspect code execution.
107+
108+
### Debugging stdio server with GitHub Copilot
109+
110+
To debug the stdio server (`main.py`) while using it with GitHub Copilot:
111+
112+
1. Open `.vscode/mcp.json` in the editor
113+
114+
2. Start the **expenses-mcp-debug** server (instead of expenses-mcp)
115+
116+
3. In VS Code, open the Run and Debug panel (Ctrl+Shift+D / Cmd+Shift+D)
117+
118+
4. Select **"Attach to MCP Server"** from the dropdown and click the play button (or press F5)
119+
120+
5. In GitHub Copilot Chat, make sure **expenses-mcp-debug** is selected in the tools menu
121+
122+
6. Set breakpoints in `main.py` and use the server from GitHub Copilot Chat. Breakpoints will be hit when tools are invoked.
123+
124+
### Debugging HTTP server with MCP Inspector
125+
126+
To debug the HTTP server (`main_http.py`) while testing with the MCP Inspector:
127+
128+
1. Start the HTTP server with debugpy enabled:
129+
```bash
130+
uv run -- python -m debugpy --listen 0.0.0.0:5678 main_http.py
131+
```
132+
133+
2. In VS Code, open the Run and Debug panel (Ctrl+Shift+D / Cmd+Shift+D)
134+
135+
3. Select **"Attach to MCP Server"** from the dropdown and click the play button (or press F5)
136+
137+
4. Open the MCP Inspector UI and configure:
138+
- **Transport Type**: `HTTP`
139+
- **URL**: `http://localhost:8000`
140+
- Click **Connect**
141+
142+
5. Set breakpoints in `main_http.py` and test your tools from the Inspector UI. Breakpoints will be hit when you invoke tools.
93143

94144
---
95145

main_http.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import csv
2+
import logging
3+
from datetime import date
4+
from enum import Enum
5+
from pathlib import Path
6+
from typing import Annotated
7+
8+
from fastmcp import FastMCP
9+
10+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
11+
logger = logging.getLogger("ExpensesMCP")
12+
13+
14+
SCRIPT_DIR = Path(__file__).parent
15+
EXPENSES_FILE = SCRIPT_DIR / "expenses.csv"
16+
17+
18+
mcp = FastMCP("Expenses Tracker")
19+
20+
21+
class PaymentMethod(Enum):
22+
AMEX = "amex"
23+
VISA = "visa"
24+
CASH = "cash"
25+
26+
27+
class Category(Enum):
28+
FOOD = "food"
29+
TRANSPORT = "transport"
30+
ENTERTAINMENT = "entertainment"
31+
SHOPPING = "shopping"
32+
GADGET = "gadget"
33+
OTHER = "other"
34+
35+
36+
@mcp.tool
37+
async def add_expense(
38+
date: Annotated[date, "Date of the expense in YYYY-MM-DD format"],
39+
amount: Annotated[float, "Positive numeric amount of the expense"],
40+
category: Annotated[Category, "Category label"],
41+
description: Annotated[str, "Human-readable description of the expense"],
42+
payment_method: Annotated[PaymentMethod, "Payment method used"],
43+
):
44+
"""Add a new expense to the expenses.csv file."""
45+
if amount <= 0:
46+
return "Error: Amount must be positive"
47+
48+
date_iso = date.isoformat()
49+
logger.info(f"Adding expense: ${amount} for {description} on {date_iso}")
50+
51+
try:
52+
file_exists = EXPENSES_FILE.exists()
53+
54+
with open(EXPENSES_FILE, "a", newline="", encoding="utf-8") as file:
55+
writer = csv.writer(file)
56+
57+
if not file_exists:
58+
writer.writerow(
59+
["date", "amount", "category", "description", "payment_method"]
60+
)
61+
62+
writer.writerow(
63+
[date_iso, amount, category.value, description, payment_method.name]
64+
)
65+
66+
return f"Successfully added expense: ${amount} for {description} on {date_iso}"
67+
68+
except Exception as e:
69+
logger.error(f"Error adding expense: {str(e)}")
70+
return "Error: Unable to add expense"
71+
72+
73+
@mcp.resource("resource://expenses")
74+
async def get_expenses_data():
75+
"""Get raw expense data from CSV file"""
76+
logger.info("Expenses data accessed")
77+
78+
try:
79+
with open(EXPENSES_FILE, "r", newline="", encoding="utf-8") as file:
80+
reader = csv.DictReader(file)
81+
expenses_data = list(reader)
82+
83+
csv_content = f"Expense data ({len(expenses_data)} entries):\n\n"
84+
for expense in expenses_data:
85+
csv_content += (
86+
f"Date: {expense['date']}, "
87+
f"Amount: ${expense['amount']}, "
88+
f"Category: {expense['category']}, "
89+
f"Description: {expense['description']}, "
90+
f"Payment: {expense['payment_method']}\n"
91+
)
92+
93+
return csv_content
94+
95+
except FileNotFoundError:
96+
logger.error("Expenses file not found")
97+
return "Error: Expense data unavailable"
98+
except Exception as e:
99+
logger.error(f"Error reading expenses: {str(e)}")
100+
return "Error: Unable to retrieve expense data"
101+
102+
103+
@mcp.prompt
104+
def create_expense_prompt(
105+
date: str,
106+
amount: float,
107+
category: str,
108+
description: str,
109+
payment_method: str
110+
) -> str:
111+
112+
"""Generate a prompt to add a new expense using the add_expense tool."""
113+
114+
logger.info(f"Expense prompt created for: {description}")
115+
116+
return f"""
117+
Please add the following expense:
118+
- Date: {date}
119+
- Amount: ${amount}
120+
- Category: {category}
121+
- Description: {description}
122+
- Payment Method: {payment_method}
123+
Use the `add_expense` tool to record this transaction.
124+
"""
125+
126+
127+
if __name__ == "__main__":
128+
logger.info("MCP Expenses server starting (HTTP mode on port 8000)")
129+
# Run in HTTP mode on port 8000
130+
mcp.run(transport="http", host="0.0.0.0", port=8000)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
88
"fastmcp>=2.12.5",
9+
"debugpy>=1.8.0",
910
]

uv.lock

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)