Add MCP Server
Browse files- .env.example +2 -0
- .gitignore +22 -0
- DEPLOYMENT.md +144 -0
- README.md +99 -0
- app.py +251 -0
- pyproject.toml +15 -0
- requirements.txt +4 -0
- server.py +271 -0
- test_local.py +98 -0
.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io
|
| 2 |
+
UPSTASH_REDIS_REST_TOKEN=your-token-here
|
.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment
|
| 2 |
+
.env
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.py[cod]
|
| 5 |
+
*$py.class
|
| 6 |
+
*.so
|
| 7 |
+
.Python
|
| 8 |
+
|
| 9 |
+
# Virtual environments
|
| 10 |
+
venv/
|
| 11 |
+
ENV/
|
| 12 |
+
env/
|
| 13 |
+
|
| 14 |
+
# IDEs
|
| 15 |
+
.vscode/
|
| 16 |
+
.idea/
|
| 17 |
+
*.swp
|
| 18 |
+
*.swo
|
| 19 |
+
|
| 20 |
+
# OS
|
| 21 |
+
.DS_Store
|
| 22 |
+
Thumbs.db
|
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Deploying to Hugging Face Spaces
|
| 4 |
+
|
| 5 |
+
### Step 1: Create a New Space
|
| 6 |
+
|
| 7 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 8 |
+
2. Click "Create new Space"
|
| 9 |
+
3. Choose:
|
| 10 |
+
- **SDK:** Gradio
|
| 11 |
+
- **Space name:** `ev-utility-function` (or your preferred name)
|
| 12 |
+
- **License:** Choose appropriate license
|
| 13 |
+
- **Visibility:** Public or Private
|
| 14 |
+
|
| 15 |
+
### Step 2: Push Your Code
|
| 16 |
+
|
| 17 |
+
Option A: Using Git
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
# Clone your space repository
|
| 21 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/ev-utility-function
|
| 22 |
+
cd ev-utility-function
|
| 23 |
+
|
| 24 |
+
# Copy all files from utility-mcp-server directory
|
| 25 |
+
cp -r /path/to/utility-mcp-server/* .
|
| 26 |
+
|
| 27 |
+
# Remove server.py if you only want the Gradio interface
|
| 28 |
+
# (Keep it if you want to document the MCP server too)
|
| 29 |
+
|
| 30 |
+
# Add and commit
|
| 31 |
+
git add .
|
| 32 |
+
git commit -m "Initial commit: EV Utility Function Calculator"
|
| 33 |
+
git push
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
Option B: Using Hugging Face Web Interface
|
| 37 |
+
|
| 38 |
+
1. Upload the following files through the web interface:
|
| 39 |
+
- `app.py`
|
| 40 |
+
- `requirements.txt`
|
| 41 |
+
- `README.md`
|
| 42 |
+
- `.gitignore`
|
| 43 |
+
|
| 44 |
+
### Step 3: Configure Environment Variables
|
| 45 |
+
|
| 46 |
+
1. Go to your Space settings
|
| 47 |
+
2. Click on "Settings" → "Variables and secrets"
|
| 48 |
+
3. Add the following secrets:
|
| 49 |
+
- `UPSTASH_REDIS_REST_URL`: Your Upstash Redis URL
|
| 50 |
+
- `UPSTASH_REDIS_REST_TOKEN`: Your Upstash Redis token
|
| 51 |
+
|
| 52 |
+
### Step 4: Wait for Build
|
| 53 |
+
|
| 54 |
+
Your Space will automatically build and deploy. You can monitor the build logs in the "Logs" section.
|
| 55 |
+
|
| 56 |
+
## Using as an MCP Server
|
| 57 |
+
|
| 58 |
+
### With Claude Desktop
|
| 59 |
+
|
| 60 |
+
1. Copy `server.py` to your local machine
|
| 61 |
+
2. Create a `.env` file with your Upstash credentials
|
| 62 |
+
3. Add to `claude_desktop_config.json`:
|
| 63 |
+
|
| 64 |
+
```json
|
| 65 |
+
{
|
| 66 |
+
"mcpServers": {
|
| 67 |
+
"ev-utility": {
|
| 68 |
+
"command": "python",
|
| 69 |
+
"args": ["/absolute/path/to/utility-mcp-server/server.py"],
|
| 70 |
+
"env": {
|
| 71 |
+
"UPSTASH_REDIS_REST_URL": "https://your-redis-url.upstash.io",
|
| 72 |
+
"UPSTASH_REDIS_REST_TOKEN": "your-token-here"
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
4. Restart Claude Desktop
|
| 80 |
+
|
| 81 |
+
### With Other MCP Clients
|
| 82 |
+
|
| 83 |
+
The server uses stdio for communication. Any MCP-compatible client can connect to it.
|
| 84 |
+
|
| 85 |
+
## Testing Locally
|
| 86 |
+
|
| 87 |
+
### Test the Gradio App
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
# Install dependencies
|
| 91 |
+
pip install -r requirements.txt
|
| 92 |
+
|
| 93 |
+
# Create .env file with your credentials
|
| 94 |
+
cp .env.example .env
|
| 95 |
+
# Edit .env with your actual credentials
|
| 96 |
+
|
| 97 |
+
# Run the app
|
| 98 |
+
python app.py
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Test the MCP Server
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
# Run the MCP server
|
| 105 |
+
python server.py
|
| 106 |
+
|
| 107 |
+
# The server will communicate via stdin/stdout
|
| 108 |
+
# Use an MCP client to test, or integrate with Claude Desktop
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
## Troubleshooting
|
| 112 |
+
|
| 113 |
+
### "No module named 'mcp'"
|
| 114 |
+
|
| 115 |
+
Install the MCP package:
|
| 116 |
+
```bash
|
| 117 |
+
pip install mcp
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### "Connection to Redis failed"
|
| 121 |
+
|
| 122 |
+
1. Check your environment variables are set correctly
|
| 123 |
+
2. Verify your Upstash Redis URL and token
|
| 124 |
+
3. Ensure your IP is not blocked by Upstash
|
| 125 |
+
|
| 126 |
+
### Space Build Fails
|
| 127 |
+
|
| 128 |
+
1. Check the build logs in Hugging Face Spaces
|
| 129 |
+
2. Ensure all dependencies are in `requirements.txt`
|
| 130 |
+
3. Verify Python version compatibility (use Python 3.10+)
|
| 131 |
+
|
| 132 |
+
## Updating the Space
|
| 133 |
+
|
| 134 |
+
To update your deployed Space:
|
| 135 |
+
|
| 136 |
+
```bash
|
| 137 |
+
# Make your changes locally
|
| 138 |
+
# Commit and push
|
| 139 |
+
git add .
|
| 140 |
+
git commit -m "Update: description of changes"
|
| 141 |
+
git push
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
The Space will automatically rebuild with your changes.
|
README.md
CHANGED
|
@@ -11,3 +11,102 @@ license: mit
|
|
| 11 |
---
|
| 12 |
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 14 |
+
|
| 15 |
+
# EV Utility Function MCP Server
|
| 16 |
+
|
| 17 |
+
An MCP (Model Context Protocol) server that provides utility function calculations for electric vehicles based on user preferences.
|
| 18 |
+
|
| 19 |
+
## Features
|
| 20 |
+
|
| 21 |
+
This MCP server exposes two tools:
|
| 22 |
+
|
| 23 |
+
### 1. `calculate_utility`
|
| 24 |
+
|
| 25 |
+
Calculate the utility score for a single car based on a user's trained preferences.
|
| 26 |
+
|
| 27 |
+
**Parameters:**
|
| 28 |
+
|
| 29 |
+
- `user_id`: Username whose utility function to use
|
| 30 |
+
- `price`: Car price in euros
|
| 31 |
+
- `range`: Range in kilometers
|
| 32 |
+
- `efficiency`: Efficiency in Wh/km
|
| 33 |
+
- `acceleration`: 0-100km/h time in seconds
|
| 34 |
+
- `fast_charge`: Fast charging power in kW
|
| 35 |
+
- `seat_count`: Number of seats
|
| 36 |
+
|
| 37 |
+
**Returns:** JSON with utility score and coefficients used
|
| 38 |
+
|
| 39 |
+
### 2. `find_best_car`
|
| 40 |
+
|
| 41 |
+
Find the best car from an array based on a user's utility function.
|
| 42 |
+
|
| 43 |
+
**Parameters:**
|
| 44 |
+
|
| 45 |
+
- `user_id`: Username whose utility function to use
|
| 46 |
+
- `cars`: Array of car objects with the above features
|
| 47 |
+
|
| 48 |
+
**Returns:** JSON with the best car and all cars ranked by utility
|
| 49 |
+
|
| 50 |
+
## Setup
|
| 51 |
+
|
| 52 |
+
### Local Development
|
| 53 |
+
|
| 54 |
+
1. Install dependencies:
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
pip install -e .
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
2. Create a `.env` file with your Upstash Redis credentials:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io
|
| 64 |
+
UPSTASH_REDIS_REST_TOKEN=your-token-here
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
3. Run the MCP server:
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
python server.py
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Using with Claude Desktop
|
| 74 |
+
|
| 75 |
+
Add to your `claude_desktop_config.json`:
|
| 76 |
+
|
| 77 |
+
```json
|
| 78 |
+
{
|
| 79 |
+
"mcpServers": {
|
| 80 |
+
"ev-utility": {
|
| 81 |
+
"command": "python",
|
| 82 |
+
"args": ["/path/to/utility-mcp-server/server.py"],
|
| 83 |
+
"env": {
|
| 84 |
+
"UPSTASH_REDIS_REST_URL": "https://your-redis-url.upstash.io",
|
| 85 |
+
"UPSTASH_REDIS_REST_TOKEN": "your-token-here"
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Hugging Face Space
|
| 93 |
+
|
| 94 |
+
This server is also available as a Hugging Face Space for easy web-based access and demonstration.
|
| 95 |
+
|
| 96 |
+
## How It Works
|
| 97 |
+
|
| 98 |
+
1. User preferences (coefficients) are stored in Upstash Redis with key format `params:{user_id}`
|
| 99 |
+
2. The server fetches coefficients from Redis when calculating utilities
|
| 100 |
+
3. Features are scaled consistently with the training data
|
| 101 |
+
4. Utility is calculated as a dot product: `utility = Σ(coefficient_i × scaled_feature_i)`
|
| 102 |
+
|
| 103 |
+
## Default Coefficients
|
| 104 |
+
|
| 105 |
+
If a user_id is not found in Redis, default coefficients are used:
|
| 106 |
+
|
| 107 |
+
- price: -0.5
|
| 108 |
+
- range: 0.8
|
| 109 |
+
- efficiency: -0.3
|
| 110 |
+
- acceleration: -0.5
|
| 111 |
+
- fast_charge: 0.6
|
| 112 |
+
- seat_count: 0.4
|
app.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gradio App for EV Utility Function Calculator
|
| 3 |
+
Deployed on Hugging Face Spaces
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
from upstash_redis import Redis
|
| 10 |
+
|
| 11 |
+
# Initialize Redis client
|
| 12 |
+
redis = Redis(
|
| 13 |
+
url=os.getenv("UPSTASH_REDIS_REST_URL"),
|
| 14 |
+
token=os.getenv("UPSTASH_REDIS_REST_TOKEN")
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Feature keys in order
|
| 18 |
+
FEATURE_KEYS = ["price", "range", "efficiency", "acceleration", "fast_charge", "seat_count"]
|
| 19 |
+
|
| 20 |
+
# Default coefficients
|
| 21 |
+
DEFAULT_COEFFS = {
|
| 22 |
+
"price": -0.5,
|
| 23 |
+
"range": 0.8,
|
| 24 |
+
"efficiency": -0.3,
|
| 25 |
+
"acceleration": -0.5,
|
| 26 |
+
"fast_charge": 0.6,
|
| 27 |
+
"seat_count": 0.4,
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def get_user_coefficients(user_id: str) -> dict[str, float]:
|
| 32 |
+
"""Fetch user coefficients from Redis."""
|
| 33 |
+
redis_key = f"params:{user_id}"
|
| 34 |
+
data_str = redis.get(redis_key)
|
| 35 |
+
|
| 36 |
+
if data_str is None:
|
| 37 |
+
return DEFAULT_COEFFS
|
| 38 |
+
|
| 39 |
+
if isinstance(data_str, bytes):
|
| 40 |
+
data_str = data_str.decode('utf-8')
|
| 41 |
+
|
| 42 |
+
data = json.loads(data_str) if isinstance(data_str, str) else data_str
|
| 43 |
+
return data.get("coeffs", DEFAULT_COEFFS)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def scale_features(car_features: dict[str, float]) -> list[float]:
|
| 47 |
+
"""Scale car features."""
|
| 48 |
+
def get_val(key: str) -> float:
|
| 49 |
+
val = car_features.get(key)
|
| 50 |
+
if val is None:
|
| 51 |
+
return 0.0
|
| 52 |
+
return float(val)
|
| 53 |
+
|
| 54 |
+
scaled = [
|
| 55 |
+
get_val("price") / 1000,
|
| 56 |
+
get_val("range") / 100,
|
| 57 |
+
get_val("efficiency") / 10,
|
| 58 |
+
get_val("acceleration"),
|
| 59 |
+
get_val("fast_charge") / 100,
|
| 60 |
+
get_val("seat_count"),
|
| 61 |
+
]
|
| 62 |
+
return scaled
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def calculate_utility_score(car_features: dict[str, float], coeffs: dict[str, float]) -> float:
|
| 66 |
+
"""Calculate utility score."""
|
| 67 |
+
scaled_features = scale_features(car_features)
|
| 68 |
+
coeff_array = [coeffs[key] for key in FEATURE_KEYS]
|
| 69 |
+
utility = sum(f * c for f, c in zip(scaled_features, coeff_array))
|
| 70 |
+
return utility
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def calculate_single_utility(user_id: str, price: float, range_km: float, efficiency: float,
|
| 74 |
+
acceleration: float, fast_charge: float, seat_count: int) -> str:
|
| 75 |
+
"""Calculate utility for a single car."""
|
| 76 |
+
try:
|
| 77 |
+
coeffs = get_user_coefficients(user_id)
|
| 78 |
+
|
| 79 |
+
car_features = {
|
| 80 |
+
"price": price,
|
| 81 |
+
"range": range_km,
|
| 82 |
+
"efficiency": efficiency,
|
| 83 |
+
"acceleration": acceleration,
|
| 84 |
+
"fast_charge": fast_charge,
|
| 85 |
+
"seat_count": seat_count
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
utility = calculate_utility_score(car_features, coeffs)
|
| 89 |
+
|
| 90 |
+
result = {
|
| 91 |
+
"user_id": user_id,
|
| 92 |
+
"utility_score": round(utility, 4),
|
| 93 |
+
"coefficients_used": {k: round(v, 4) for k, v in coeffs.items()},
|
| 94 |
+
"car_features": car_features,
|
| 95 |
+
"note": "Using default coefficients" if coeffs == DEFAULT_COEFFS else "Using saved user preferences"
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
return json.dumps(result, indent=2)
|
| 99 |
+
except Exception as e:
|
| 100 |
+
return json.dumps({"error": str(e)}, indent=2)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def find_best_from_list(user_id: str, cars_json: str) -> str:
|
| 104 |
+
"""Find the best car from a JSON list."""
|
| 105 |
+
try:
|
| 106 |
+
cars = json.loads(cars_json)
|
| 107 |
+
coeffs = get_user_coefficients(user_id)
|
| 108 |
+
|
| 109 |
+
best_car = None
|
| 110 |
+
best_utility = float('-inf')
|
| 111 |
+
all_results = []
|
| 112 |
+
|
| 113 |
+
for car in cars:
|
| 114 |
+
utility = calculate_utility_score(car, coeffs)
|
| 115 |
+
car_result = {**car, "utility": round(utility, 4)}
|
| 116 |
+
all_results.append(car_result)
|
| 117 |
+
|
| 118 |
+
if utility > best_utility:
|
| 119 |
+
best_utility = utility
|
| 120 |
+
best_car = car_result
|
| 121 |
+
|
| 122 |
+
# Sort by utility descending
|
| 123 |
+
all_results.sort(key=lambda x: x["utility"], reverse=True)
|
| 124 |
+
|
| 125 |
+
result = {
|
| 126 |
+
"user_id": user_id,
|
| 127 |
+
"best_car": best_car,
|
| 128 |
+
"all_cars_ranked": all_results,
|
| 129 |
+
"coefficients_used": {k: round(v, 4) for k, v in coeffs.items()},
|
| 130 |
+
"note": "Using default coefficients" if coeffs == DEFAULT_COEFFS else "Using saved user preferences"
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
return json.dumps(result, indent=2)
|
| 134 |
+
except json.JSONDecodeError:
|
| 135 |
+
return json.dumps({"error": "Invalid JSON format"}, indent=2)
|
| 136 |
+
except Exception as e:
|
| 137 |
+
return json.dumps({"error": str(e)}, indent=2)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
# Example cars JSON
|
| 141 |
+
example_cars = json.dumps([
|
| 142 |
+
{
|
| 143 |
+
"name": "Tesla Model 3",
|
| 144 |
+
"price": 45000,
|
| 145 |
+
"range": 500,
|
| 146 |
+
"efficiency": 150,
|
| 147 |
+
"acceleration": 6.1,
|
| 148 |
+
"fast_charge": 170,
|
| 149 |
+
"seat_count": 5
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"name": "Volkswagen ID.4",
|
| 153 |
+
"price": 40000,
|
| 154 |
+
"range": 420,
|
| 155 |
+
"efficiency": 180,
|
| 156 |
+
"acceleration": 8.5,
|
| 157 |
+
"fast_charge": 125,
|
| 158 |
+
"seat_count": 5
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"name": "Hyundai Ioniq 5",
|
| 162 |
+
"price": 48000,
|
| 163 |
+
"range": 480,
|
| 164 |
+
"efficiency": 165,
|
| 165 |
+
"acceleration": 7.4,
|
| 166 |
+
"fast_charge": 220,
|
| 167 |
+
"seat_count": 5
|
| 168 |
+
}
|
| 169 |
+
], indent=2)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
# Create Gradio interface
|
| 173 |
+
with gr.Blocks(title="EV Utility Function Calculator") as demo:
|
| 174 |
+
gr.Markdown("""
|
| 175 |
+
# 🚗 EV Utility Function Calculator
|
| 176 |
+
|
| 177 |
+
This tool calculates utility scores for electric vehicles based on user preferences.
|
| 178 |
+
User preferences are stored in Upstash Redis with their trained coefficients.
|
| 179 |
+
|
| 180 |
+
**Note:** If a user_id is not found, default coefficients will be used.
|
| 181 |
+
""")
|
| 182 |
+
|
| 183 |
+
with gr.Tab("Calculate Single Car Utility"):
|
| 184 |
+
gr.Markdown("### Calculate the utility score for a single car")
|
| 185 |
+
|
| 186 |
+
with gr.Row():
|
| 187 |
+
with gr.Column():
|
| 188 |
+
user_id_single = gr.Textbox(label="User ID", value="benjo", placeholder="Enter username")
|
| 189 |
+
price = gr.Number(label="Price (€)", value=45000)
|
| 190 |
+
range_km = gr.Number(label="Range (km)", value=500)
|
| 191 |
+
efficiency = gr.Number(label="Efficiency (Wh/km)", value=150)
|
| 192 |
+
acceleration = gr.Number(label="0-100km/h (seconds)", value=6.1)
|
| 193 |
+
fast_charge = gr.Number(label="Fast Charge (kW)", value=170)
|
| 194 |
+
seat_count = gr.Number(label="Seat Count", value=5, precision=0)
|
| 195 |
+
|
| 196 |
+
with gr.Column():
|
| 197 |
+
output_single = gr.Code(label="Result", language="json")
|
| 198 |
+
|
| 199 |
+
calc_btn = gr.Button("Calculate Utility", variant="primary")
|
| 200 |
+
calc_btn.click(
|
| 201 |
+
calculate_single_utility,
|
| 202 |
+
inputs=[user_id_single, price, range_km, efficiency, acceleration, fast_charge, seat_count],
|
| 203 |
+
outputs=output_single
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
with gr.Tab("Find Best Car"):
|
| 207 |
+
gr.Markdown("### Find the best car from a list based on user preferences")
|
| 208 |
+
|
| 209 |
+
with gr.Row():
|
| 210 |
+
with gr.Column():
|
| 211 |
+
user_id_best = gr.Textbox(label="User ID", value="benjo", placeholder="Enter username")
|
| 212 |
+
cars_json = gr.Code(
|
| 213 |
+
label="Cars (JSON Array)",
|
| 214 |
+
value=example_cars,
|
| 215 |
+
language="json",
|
| 216 |
+
lines=20
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
with gr.Column():
|
| 220 |
+
output_best = gr.Code(label="Result", language="json", lines=25)
|
| 221 |
+
|
| 222 |
+
find_btn = gr.Button("Find Best Car", variant="primary")
|
| 223 |
+
find_btn.click(
|
| 224 |
+
find_best_from_list,
|
| 225 |
+
inputs=[user_id_best, cars_json],
|
| 226 |
+
outputs=output_best
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
gr.Markdown("""
|
| 230 |
+
---
|
| 231 |
+
## About
|
| 232 |
+
|
| 233 |
+
This is the web interface for the EV Utility Function MCP Server.
|
| 234 |
+
|
| 235 |
+
### MCP Server
|
| 236 |
+
|
| 237 |
+
This tool is also available as an MCP (Model Context Protocol) server that can be used with Claude Desktop
|
| 238 |
+
and other MCP-compatible clients.
|
| 239 |
+
|
| 240 |
+
**Repository:** [GitHub Link]
|
| 241 |
+
|
| 242 |
+
### How it works:
|
| 243 |
+
|
| 244 |
+
1. Users train their preferences through the AutoFinder app
|
| 245 |
+
2. Preferences are saved to Upstash Redis as coefficients
|
| 246 |
+
3. This tool fetches those coefficients and calculates utility scores
|
| 247 |
+
4. Utility = Σ(coefficient × scaled_feature)
|
| 248 |
+
""")
|
| 249 |
+
|
| 250 |
+
if __name__ == "__main__":
|
| 251 |
+
demo.launch()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "utility-mcp-server"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "MCP server for EV utility function calculations"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.10"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"mcp>=1.0.0",
|
| 9 |
+
"upstash-redis>=1.1.1",
|
| 10 |
+
"python-dotenv>=1.0.0",
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
[build-system]
|
| 14 |
+
requires = ["hatchling"]
|
| 15 |
+
build-backend = "hatchling.build"
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=5.0.0
|
| 2 |
+
upstash-redis>=1.1.1
|
| 3 |
+
python-dotenv>=1.0.0
|
| 4 |
+
mcp>=1.0.0
|
server.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
MCP Server for EV Utility Function Calculations
|
| 4 |
+
|
| 5 |
+
This server provides tools to calculate utility scores for electric vehicles
|
| 6 |
+
using personalized coefficients stored in Upstash Redis.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
from typing import Any
|
| 12 |
+
from dotenv import load_dotenv
|
| 13 |
+
from upstash_redis import Redis
|
| 14 |
+
|
| 15 |
+
from mcp.server.models import InitializationOptions
|
| 16 |
+
from mcp.server import NotificationOptions, Server
|
| 17 |
+
from mcp.server.stdio import stdio_server
|
| 18 |
+
from mcp.types import Tool, TextContent
|
| 19 |
+
|
| 20 |
+
# Load environment variables
|
| 21 |
+
load_dotenv()
|
| 22 |
+
|
| 23 |
+
# Initialize Redis client
|
| 24 |
+
redis = Redis(
|
| 25 |
+
url=os.getenv("UPSTASH_REDIS_REST_URL"),
|
| 26 |
+
token=os.getenv("UPSTASH_REDIS_REST_TOKEN")
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# Feature keys in order (must match the order in the coefficient array)
|
| 30 |
+
FEATURE_KEYS = ["price", "range", "efficiency", "acceleration", "fast_charge", "seat_count"]
|
| 31 |
+
|
| 32 |
+
# Default coefficients if user not found
|
| 33 |
+
DEFAULT_COEFFS = {
|
| 34 |
+
"price": -0.5,
|
| 35 |
+
"range": 0.8,
|
| 36 |
+
"efficiency": -0.3,
|
| 37 |
+
"acceleration": -0.5,
|
| 38 |
+
"fast_charge": 0.6,
|
| 39 |
+
"seat_count": 0.4,
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_user_coefficients(user_id: str) -> dict[str, float]:
|
| 44 |
+
"""
|
| 45 |
+
Fetch user coefficients from Redis. Returns default if not found.
|
| 46 |
+
"""
|
| 47 |
+
redis_key = f"params:{user_id}"
|
| 48 |
+
data_str = redis.get(redis_key)
|
| 49 |
+
|
| 50 |
+
if data_str is None:
|
| 51 |
+
return DEFAULT_COEFFS
|
| 52 |
+
|
| 53 |
+
# Parse the JSON data - handle both string and bytes
|
| 54 |
+
if isinstance(data_str, bytes):
|
| 55 |
+
data_str = data_str.decode('utf-8')
|
| 56 |
+
|
| 57 |
+
data = json.loads(data_str) if isinstance(data_str, str) else data_str
|
| 58 |
+
return data.get("coeffs", DEFAULT_COEFFS)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def scale_features(car_features: dict[str, float]) -> list[float]:
|
| 62 |
+
"""
|
| 63 |
+
Scale car features to match the training data scaling.
|
| 64 |
+
"""
|
| 65 |
+
def get_val(key: str) -> float:
|
| 66 |
+
val = car_features.get(key)
|
| 67 |
+
if val is None:
|
| 68 |
+
return 0.0
|
| 69 |
+
return float(val)
|
| 70 |
+
|
| 71 |
+
# ORDER MATTERS: Must match FEATURE_KEYS
|
| 72 |
+
scaled = [
|
| 73 |
+
get_val("price") / 1000,
|
| 74 |
+
get_val("range") / 100,
|
| 75 |
+
get_val("efficiency") / 10,
|
| 76 |
+
get_val("acceleration"),
|
| 77 |
+
get_val("fast_charge") / 100,
|
| 78 |
+
get_val("seat_count"),
|
| 79 |
+
]
|
| 80 |
+
return scaled
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def calculate_utility_score(car_features: dict[str, float], coeffs: dict[str, float]) -> float:
|
| 84 |
+
"""
|
| 85 |
+
Calculate utility score using dot product of features and coefficients.
|
| 86 |
+
"""
|
| 87 |
+
scaled_features = scale_features(car_features)
|
| 88 |
+
coeff_array = [coeffs[key] for key in FEATURE_KEYS]
|
| 89 |
+
|
| 90 |
+
# Dot product
|
| 91 |
+
utility = sum(f * c for f, c in zip(scaled_features, coeff_array))
|
| 92 |
+
return utility
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# Create the MCP server
|
| 96 |
+
server = Server("utility-function")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@server.list_tools()
|
| 100 |
+
async def handle_list_tools() -> list[Tool]:
|
| 101 |
+
"""
|
| 102 |
+
List available tools.
|
| 103 |
+
"""
|
| 104 |
+
return [
|
| 105 |
+
Tool(
|
| 106 |
+
name="calculate_utility",
|
| 107 |
+
description="Calculate the utility score for a car based on a user's preferences",
|
| 108 |
+
inputSchema={
|
| 109 |
+
"type": "object",
|
| 110 |
+
"properties": {
|
| 111 |
+
"user_id": {
|
| 112 |
+
"type": "string",
|
| 113 |
+
"description": "The username/ID whose utility function to use"
|
| 114 |
+
},
|
| 115 |
+
"price": {
|
| 116 |
+
"type": "number",
|
| 117 |
+
"description": "Car price in euros"
|
| 118 |
+
},
|
| 119 |
+
"range": {
|
| 120 |
+
"type": "number",
|
| 121 |
+
"description": "Range in kilometers"
|
| 122 |
+
},
|
| 123 |
+
"efficiency": {
|
| 124 |
+
"type": "number",
|
| 125 |
+
"description": "Efficiency in Wh/km"
|
| 126 |
+
},
|
| 127 |
+
"acceleration": {
|
| 128 |
+
"type": "number",
|
| 129 |
+
"description": "0-100km/h time in seconds"
|
| 130 |
+
},
|
| 131 |
+
"fast_charge": {
|
| 132 |
+
"type": "number",
|
| 133 |
+
"description": "Fast charging power in kW"
|
| 134 |
+
},
|
| 135 |
+
"seat_count": {
|
| 136 |
+
"type": "number",
|
| 137 |
+
"description": "Number of seats"
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
"required": ["user_id", "price", "range", "efficiency", "acceleration", "fast_charge", "seat_count"]
|
| 141 |
+
}
|
| 142 |
+
),
|
| 143 |
+
Tool(
|
| 144 |
+
name="find_best_car",
|
| 145 |
+
description="Find the best car from an array based on a user's utility function",
|
| 146 |
+
inputSchema={
|
| 147 |
+
"type": "object",
|
| 148 |
+
"properties": {
|
| 149 |
+
"user_id": {
|
| 150 |
+
"type": "string",
|
| 151 |
+
"description": "The username/ID whose utility function to use"
|
| 152 |
+
},
|
| 153 |
+
"cars": {
|
| 154 |
+
"type": "array",
|
| 155 |
+
"description": "Array of car objects with features",
|
| 156 |
+
"items": {
|
| 157 |
+
"type": "object",
|
| 158 |
+
"properties": {
|
| 159 |
+
"name": {"type": "string"},
|
| 160 |
+
"price": {"type": "number"},
|
| 161 |
+
"range": {"type": "number"},
|
| 162 |
+
"efficiency": {"type": "number"},
|
| 163 |
+
"acceleration": {"type": "number"},
|
| 164 |
+
"fast_charge": {"type": "number"},
|
| 165 |
+
"seat_count": {"type": "number"}
|
| 166 |
+
},
|
| 167 |
+
"required": ["price", "range", "efficiency", "acceleration", "fast_charge", "seat_count"]
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
"required": ["user_id", "cars"]
|
| 172 |
+
}
|
| 173 |
+
)
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@server.call_tool()
|
| 178 |
+
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
| 179 |
+
"""
|
| 180 |
+
Handle tool execution requests.
|
| 181 |
+
"""
|
| 182 |
+
if name == "calculate_utility":
|
| 183 |
+
user_id = arguments["user_id"]
|
| 184 |
+
|
| 185 |
+
# Get user coefficients
|
| 186 |
+
coeffs = get_user_coefficients(user_id)
|
| 187 |
+
|
| 188 |
+
# Extract car features
|
| 189 |
+
car_features = {
|
| 190 |
+
"price": arguments["price"],
|
| 191 |
+
"range": arguments["range"],
|
| 192 |
+
"efficiency": arguments["efficiency"],
|
| 193 |
+
"acceleration": arguments["acceleration"],
|
| 194 |
+
"fast_charge": arguments["fast_charge"],
|
| 195 |
+
"seat_count": arguments["seat_count"]
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
# Calculate utility
|
| 199 |
+
utility = calculate_utility_score(car_features, coeffs)
|
| 200 |
+
|
| 201 |
+
result = {
|
| 202 |
+
"user_id": user_id,
|
| 203 |
+
"utility": utility,
|
| 204 |
+
"coefficients_used": coeffs,
|
| 205 |
+
"car_features": car_features
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
return [TextContent(
|
| 209 |
+
type="text",
|
| 210 |
+
text=json.dumps(result, indent=2)
|
| 211 |
+
)]
|
| 212 |
+
|
| 213 |
+
elif name == "find_best_car":
|
| 214 |
+
user_id = arguments["user_id"]
|
| 215 |
+
cars = arguments["cars"]
|
| 216 |
+
|
| 217 |
+
# Get user coefficients
|
| 218 |
+
coeffs = get_user_coefficients(user_id)
|
| 219 |
+
|
| 220 |
+
# Calculate utility for each car
|
| 221 |
+
best_car = None
|
| 222 |
+
best_utility = float('-inf')
|
| 223 |
+
all_results = []
|
| 224 |
+
|
| 225 |
+
for car in cars:
|
| 226 |
+
utility = calculate_utility_score(car, coeffs)
|
| 227 |
+
car_result = {**car, "utility": utility}
|
| 228 |
+
all_results.append(car_result)
|
| 229 |
+
|
| 230 |
+
if utility > best_utility:
|
| 231 |
+
best_utility = utility
|
| 232 |
+
best_car = car_result
|
| 233 |
+
|
| 234 |
+
result = {
|
| 235 |
+
"user_id": user_id,
|
| 236 |
+
"best_car": best_car,
|
| 237 |
+
"all_cars_with_utilities": all_results,
|
| 238 |
+
"coefficients_used": coeffs
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
return [TextContent(
|
| 242 |
+
type="text",
|
| 243 |
+
text=json.dumps(result, indent=2)
|
| 244 |
+
)]
|
| 245 |
+
|
| 246 |
+
else:
|
| 247 |
+
raise ValueError(f"Unknown tool: {name}")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
async def main():
|
| 251 |
+
"""
|
| 252 |
+
Main entry point for the MCP server.
|
| 253 |
+
"""
|
| 254 |
+
async with stdio_server() as (read_stream, write_stream):
|
| 255 |
+
await server.run(
|
| 256 |
+
read_stream,
|
| 257 |
+
write_stream,
|
| 258 |
+
InitializationOptions(
|
| 259 |
+
server_name="utility-function",
|
| 260 |
+
server_version="0.1.0",
|
| 261 |
+
capabilities=server.get_capabilities(
|
| 262 |
+
notification_options=NotificationOptions(),
|
| 263 |
+
experimental_capabilities={},
|
| 264 |
+
)
|
| 265 |
+
)
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
import asyncio
|
| 271 |
+
asyncio.run(main())
|
test_local.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple test script to verify the utility calculation functions work correctly.
|
| 4 |
+
This doesn't test the MCP protocol itself, just the core logic.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
from server import get_user_coefficients, calculate_utility_score
|
| 9 |
+
|
| 10 |
+
def test_calculate_utility():
|
| 11 |
+
"""Test basic utility calculation."""
|
| 12 |
+
print("Testing utility calculation...")
|
| 13 |
+
|
| 14 |
+
# Test with default coefficients
|
| 15 |
+
user_id = "test_user_nonexistent"
|
| 16 |
+
coeffs = get_user_coefficients(user_id)
|
| 17 |
+
print(f"\nCoefficients for {user_id}:")
|
| 18 |
+
print(json.dumps(coeffs, indent=2))
|
| 19 |
+
|
| 20 |
+
# Example car features
|
| 21 |
+
car = {
|
| 22 |
+
"price": 45000,
|
| 23 |
+
"range": 500,
|
| 24 |
+
"efficiency": 150,
|
| 25 |
+
"acceleration": 6.1,
|
| 26 |
+
"fast_charge": 170,
|
| 27 |
+
"seat_count": 5
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
utility = calculate_utility_score(car, coeffs)
|
| 31 |
+
print(f"\nCar features:")
|
| 32 |
+
print(json.dumps(car, indent=2))
|
| 33 |
+
print(f"\nCalculated utility: {utility:.4f}")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_with_saved_user():
|
| 37 |
+
"""Test with a user that has saved preferences."""
|
| 38 |
+
print("\n" + "="*60)
|
| 39 |
+
print("Testing with saved user preferences...")
|
| 40 |
+
|
| 41 |
+
user_id = "benjo" # Should exist in Redis from the screenshot
|
| 42 |
+
coeffs = get_user_coefficients(user_id)
|
| 43 |
+
print(f"\nCoefficients for {user_id}:")
|
| 44 |
+
print(json.dumps({k: round(v, 4) for k, v in coeffs.items()}, indent=2))
|
| 45 |
+
|
| 46 |
+
# Example cars
|
| 47 |
+
cars = [
|
| 48 |
+
{
|
| 49 |
+
"name": "Tesla Model 3",
|
| 50 |
+
"price": 45000,
|
| 51 |
+
"range": 500,
|
| 52 |
+
"efficiency": 150,
|
| 53 |
+
"acceleration": 6.1,
|
| 54 |
+
"fast_charge": 170,
|
| 55 |
+
"seat_count": 5
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"name": "Volkswagen ID.4",
|
| 59 |
+
"price": 40000,
|
| 60 |
+
"range": 420,
|
| 61 |
+
"efficiency": 180,
|
| 62 |
+
"acceleration": 8.5,
|
| 63 |
+
"fast_charge": 125,
|
| 64 |
+
"seat_count": 5
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"name": "Hyundai Ioniq 5",
|
| 68 |
+
"price": 48000,
|
| 69 |
+
"range": 480,
|
| 70 |
+
"efficiency": 165,
|
| 71 |
+
"acceleration": 7.4,
|
| 72 |
+
"fast_charge": 220,
|
| 73 |
+
"seat_count": 5
|
| 74 |
+
}
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
print(f"\nComparing {len(cars)} cars:")
|
| 78 |
+
results = []
|
| 79 |
+
for car in cars:
|
| 80 |
+
utility = calculate_utility_score(car, coeffs)
|
| 81 |
+
results.append((car["name"], utility))
|
| 82 |
+
print(f" {car['name']}: {utility:.4f}")
|
| 83 |
+
|
| 84 |
+
# Find best
|
| 85 |
+
best_car, best_utility = max(results, key=lambda x: x[1])
|
| 86 |
+
print(f"\n🏆 Best car for {user_id}: {best_car} (utility: {best_utility:.4f})")
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
if __name__ == "__main__":
|
| 90 |
+
try:
|
| 91 |
+
test_calculate_utility()
|
| 92 |
+
test_with_saved_user()
|
| 93 |
+
print("\n" + "="*60)
|
| 94 |
+
print("✅ All tests completed successfully!")
|
| 95 |
+
except Exception as e:
|
| 96 |
+
print(f"\n❌ Error: {e}")
|
| 97 |
+
import traceback
|
| 98 |
+
traceback.print_exc()
|