mashrur950 commited on
Commit
b338c26
·
1 Parent(s): 70cf1f6

added api key based authentication

Browse files
Files changed (6) hide show
  1. README.md +146 -17
  2. app.py +161 -22
  3. database/api_keys.py +261 -0
  4. generate_api_key.py +142 -0
  5. requirements.txt +0 -1
  6. server.py +26 -90
README.md CHANGED
@@ -72,6 +72,7 @@ FleetMind is a production-ready **Model Context Protocol (MCP) server** that tra
72
 
73
  ### Key Features
74
 
 
75
  ✅ **29 AI Tools** - Order, Driver & Assignment Management (including Gemini 2.0 Flash AI)
76
  ✅ **2 Real-Time Resources** - Live data feeds (orders://all, drivers://all)
77
  ✅ **Google Maps Integration** - Geocoding & Route Calculation with traffic data
@@ -155,30 +156,70 @@ FleetMind isn't just an MCP server—it's a **blueprint for enterprise AI integr
155
 
156
  ### Connect from Claude Desktop
157
 
158
- 1. **Install Claude Desktop** from https://claude.ai/download
159
 
160
- 2. **Configure MCP Server** - Edit your `claude_desktop_config.json`:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  **For Production (HuggingFace Space):**
163
  ```json
164
  {
165
  "mcpServers": {
166
- "fleetmind_Prod": {
167
  "command": "npx",
168
  "args": [
169
  "mcp-remote",
170
  "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"
171
- ]
 
 
 
172
  }
173
  }
174
  }
175
  ```
176
 
177
- **For Local Development:**
 
 
178
  ```json
179
  {
180
  "mcpServers": {
181
- "fleetmind": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  "command": "npx",
183
  "args": [
184
  "mcp-remote",
@@ -188,26 +229,35 @@ FleetMind isn't just an MCP server—it's a **blueprint for enterprise AI integr
188
  }
189
  }
190
  ```
 
191
 
192
  **Both (Production + Local):**
193
  ```json
194
  {
195
  "mcpServers": {
196
- "fleetmind": {
197
  "command": "npx",
198
- "args": ["mcp-remote", "http://localhost:7860/sse"]
 
 
 
199
  },
200
- "fleetmind_Prod": {
201
  "command": "npx",
202
- "args": ["mcp-remote", "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"]
 
 
 
203
  }
204
  }
205
  }
206
  ```
207
 
208
- 3. **Restart Claude Desktop** - FleetMind tools will appear automatically!
 
 
209
 
210
- 4. **Try it out:**
211
  - "Create an urgent delivery order for Sarah at 456 Oak Ave, San Francisco"
212
  - "Use intelligent AI assignment to find the best driver for this order"
213
  - "Show me all available drivers"
@@ -472,6 +522,7 @@ CREATE TABLE drivers (
472
  - Python 3.10+
473
  - PostgreSQL database (or use Neon serverless)
474
  - Google Maps API key
 
475
 
476
  ### Setup
477
 
@@ -488,17 +539,40 @@ cp .env.example .env
488
  # Edit .env with your credentials:
489
  # DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
490
  # GOOGLE_MAPS_API_KEY
 
 
491
 
492
- # Test server
493
- python -c "import server; print('Server ready!')"
494
-
495
- # Run locally (stdio mode for Claude Desktop)
496
- python server.py
497
 
498
  # Run locally (SSE mode for web clients)
499
  python app.py
500
  ```
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  ### Testing
503
 
504
  ```bash
@@ -554,6 +628,53 @@ Upload files directly to HF Space:
554
 
555
  ---
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  ## 📝 Environment Variables
558
 
559
  Required:
@@ -567,6 +688,10 @@ DB_PASSWORD=your_password
567
 
568
  # Google Maps API
569
  GOOGLE_MAPS_API_KEY=your_api_key
 
 
 
 
570
  ```
571
 
572
  Optional:
@@ -575,6 +700,10 @@ Optional:
575
  PORT=7860
576
  HOST=0.0.0.0
577
  LOG_LEVEL=INFO
 
 
 
 
578
  ```
579
 
580
  ---
 
72
 
73
  ### Key Features
74
 
75
+ ✅ **🔑 API Key Authentication** - Secure multi-tenant access with personal API keys
76
  ✅ **29 AI Tools** - Order, Driver & Assignment Management (including Gemini 2.0 Flash AI)
77
  ✅ **2 Real-Time Resources** - Live data feeds (orders://all, drivers://all)
78
  ✅ **Google Maps Integration** - Geocoding & Route Calculation with traffic data
 
156
 
157
  ### Connect from Claude Desktop
158
 
159
+ #### **Step 1: Get Your API Key** 🔑
160
 
161
+ Visit the FleetMind server and generate your personal API key:
162
+
163
+ 👉 **https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/generate-key**
164
+
165
+ 1. Enter your email address
166
+ 2. Enter your name (optional)
167
+ 3. Click "Generate API Key"
168
+ 4. **Copy your API key immediately** - it won't be shown again!
169
+
170
+ Your API key will look like: `fm_xxxxxxxxxxxxxxxxxxxxxxxx`
171
+
172
+ #### **Step 2: Install Claude Desktop**
173
+
174
+ Download from: https://claude.ai/download
175
+
176
+ #### **Step 3: Configure MCP Server**
177
+
178
+ Edit your `claude_desktop_config.json`:
179
 
180
  **For Production (HuggingFace Space):**
181
  ```json
182
  {
183
  "mcpServers": {
184
+ "fleetmind": {
185
  "command": "npx",
186
  "args": [
187
  "mcp-remote",
188
  "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"
189
+ ],
190
+ "env": {
191
+ "FLEETMIND_API_KEY": "fm_your_api_key_here"
192
+ }
193
  }
194
  }
195
  }
196
  ```
197
 
198
+ ⚠️ **Important:** Replace `fm_your_api_key_here` with your actual API key from Step 1!
199
+
200
+ **For Local Development (with API Key):**
201
  ```json
202
  {
203
  "mcpServers": {
204
+ "fleetmind_local": {
205
+ "command": "npx",
206
+ "args": [
207
+ "mcp-remote",
208
+ "http://localhost:7860/sse"
209
+ ],
210
+ "env": {
211
+ "FLEETMIND_API_KEY": "your_local_api_key"
212
+ }
213
+ }
214
+ }
215
+ }
216
+ ```
217
+
218
+ **For Local Development (SKIP_AUTH mode):**
219
+ ```json
220
+ {
221
+ "mcpServers": {
222
+ "fleetmind_local": {
223
  "command": "npx",
224
  "args": [
225
  "mcp-remote",
 
229
  }
230
  }
231
  ```
232
+ *Set `SKIP_AUTH=true` in your `.env` file for local testing without API keys*
233
 
234
  **Both (Production + Local):**
235
  ```json
236
  {
237
  "mcpServers": {
238
+ "fleetmind_local": {
239
  "command": "npx",
240
+ "args": ["mcp-remote", "http://localhost:7860/sse"],
241
+ "env": {
242
+ "FLEETMIND_API_KEY": "your_local_key"
243
+ }
244
  },
245
+ "fleetmind": {
246
  "command": "npx",
247
+ "args": ["mcp-remote", "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"],
248
+ "env": {
249
+ "FLEETMIND_API_KEY": "your_production_key"
250
+ }
251
  }
252
  }
253
  }
254
  ```
255
 
256
+ #### **Step 4: Restart Claude Desktop**
257
+
258
+ Restart Claude Desktop - FleetMind tools will appear automatically!
259
 
260
+ #### **Step 5: Try It Out!**
261
  - "Create an urgent delivery order for Sarah at 456 Oak Ave, San Francisco"
262
  - "Use intelligent AI assignment to find the best driver for this order"
263
  - "Show me all available drivers"
 
522
  - Python 3.10+
523
  - PostgreSQL database (or use Neon serverless)
524
  - Google Maps API key
525
+ - Google Gemini API key (for AI features)
526
 
527
  ### Setup
528
 
 
539
  # Edit .env with your credentials:
540
  # DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
541
  # GOOGLE_MAPS_API_KEY
542
+ # GOOGLE_API_KEY (for Gemini)
543
+ # SKIP_AUTH=true # For local development without API keys
544
 
545
+ # Run database migrations
546
+ python database/migrations/001_initial_schema.py
547
+ # ... run all migrations
 
 
548
 
549
  # Run locally (SSE mode for web clients)
550
  python app.py
551
  ```
552
 
553
+ ### API Key Management (Local)
554
+
555
+ **Generate API Keys:**
556
+ ```bash
557
+ # Interactive mode
558
+ python generate_api_key.py
559
+
560
+ # With arguments
561
+ python generate_api_key.py --email user@example.com --name "Test User"
562
+
563
+ # List all keys
564
+ python generate_api_key.py --list
565
+
566
+ # Revoke a key
567
+ python generate_api_key.py --revoke user@example.com
568
+ ```
569
+
570
+ **Web Interface:**
571
+ Visit http://localhost:7860/generate-key to generate keys via web form.
572
+
573
+ **Development Mode (No API Keys):**
574
+ Set `SKIP_AUTH=true` in `.env` to bypass authentication for local testing.
575
+
576
  ### Testing
577
 
578
  ```bash
 
628
 
629
  ---
630
 
631
+ ## 🔒 Authentication & Security
632
+
633
+ FleetMind uses **API Key authentication** for secure multi-tenant access:
634
+
635
+ ### How It Works
636
+
637
+ 1. **User generates API key** via web interface (`/generate-key`)
638
+ 2. **API key is hashed** (SHA-256) before storage - never stored in plaintext
639
+ 3. **User adds key** to Claude Desktop config in `env.FLEETMIND_API_KEY`
640
+ 4. **Server validates key** on each request
641
+ 5. **Data is isolated** - each user only sees their own orders/drivers
642
+
643
+ ### Security Features
644
+
645
+ - ✅ **One-Time Display** - API keys shown only once during generation
646
+ - ✅ **Hashed Storage** - SHA-256 hashing, never stored in plaintext
647
+ - ✅ **Multi-Tenant** - Complete data isolation per user
648
+ - ✅ **Easy Revocation** - Keys can be revoked via CLI or web interface
649
+ - ✅ **Development Mode** - `SKIP_AUTH=true` for local testing
650
+
651
+ ### API Key Format
652
+
653
+ ```
654
+ fm_LAISKBMKmQxoapDbLIhr_KqbVL69AS58wBtdpYfbQ10
655
+ ```
656
+
657
+ - Prefix: `fm_` (FleetMind)
658
+ - 43 random characters (URL-safe base64)
659
+ - Unique per user
660
+
661
+ ### Managing Keys
662
+
663
+ ```bash
664
+ # Generate new key
665
+ python generate_api_key.py --email user@example.com
666
+
667
+ # List all keys
668
+ python generate_api_key.py --list
669
+
670
+ # Revoke a key
671
+ python generate_api_key.py --revoke user@example.com
672
+ ```
673
+
674
+ Or use the web interface: `http://localhost:7860/generate-key`
675
+
676
+ ---
677
+
678
  ## 📝 Environment Variables
679
 
680
  Required:
 
688
 
689
  # Google Maps API
690
  GOOGLE_MAPS_API_KEY=your_api_key
691
+
692
+ # Google Gemini API
693
+ GOOGLE_API_KEY=your_gemini_key
694
+ AI_PROVIDER=gemini
695
  ```
696
 
697
  Optional:
 
700
  PORT=7860
701
  HOST=0.0.0.0
702
  LOG_LEVEL=INFO
703
+ SERVER_URL=http://localhost:7860
704
+
705
+ # Authentication
706
+ SKIP_AUTH=true # Set to false for production
707
  ```
708
 
709
  ---
app.py CHANGED
@@ -94,25 +94,9 @@ if __name__ == "__main__":
94
  logger.info("=" * 70)
95
 
96
  try:
97
- # Add landing page and OAuth metadata using custom_route decorator
98
  from starlette.responses import HTMLResponse, JSONResponse
99
-
100
- @mcp.custom_route("/.well-known/oauth-protected-resource", methods=["GET"])
101
- async def oauth_metadata(request):
102
- """OAuth 2.0 Protected Resource Metadata (RFC 9728)"""
103
- return JSONResponse({
104
- "resource": os.getenv('SERVER_URL', 'http://localhost:7860'),
105
- "authorization_servers": [
106
- "https://test.stytch.com/v1/public"
107
- ],
108
- "scopes_supported": [
109
- "orders:read", "orders:write",
110
- "drivers:read", "drivers:write",
111
- "assignments:manage", "admin"
112
- ],
113
- "bearer_methods_supported": ["header"],
114
- "resource_signing_alg_values_supported": ["RS256"]
115
- })
116
 
117
  @mcp.custom_route("/", methods=["GET"])
118
  async def landing_page(request):
@@ -155,7 +139,15 @@ if __name__ == "__main__":
155
  <code>https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse</code>
156
  </div>
157
 
158
- <h3>⚙️ Claude Desktop Configuration</h3>
 
 
 
 
 
 
 
 
159
  <p>Add this to your <code>claude_desktop_config.json</code> file:</p>
160
  <pre>{
161
  "mcpServers": {
@@ -164,16 +156,20 @@ if __name__ == "__main__":
164
  "args": [
165
  "mcp-remote",
166
  "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"
167
- ]
 
 
 
168
  }
169
  }
170
  }</pre>
171
 
172
- <h3>📋 How to Connect</h3>
173
  <ol>
 
174
  <li>Install <a href="https://claude.ai/download" target="_blank">Claude Desktop</a></li>
175
  <li>Locate your <code>claude_desktop_config.json</code> file</li>
176
- <li>Add the configuration shown above</li>
177
  <li>Restart Claude Desktop</li>
178
  <li>Look for "FleetMind" in the 🔌 tools menu</li>
179
  </ol>
@@ -211,6 +207,7 @@ if __name__ == "__main__":
211
 
212
  <h2>⭐ Key Features</h2>
213
  <ul>
 
214
  <li><strong>🧠 Gemini 2.0 Flash AI</strong> - Intelligent order assignment with detailed reasoning</li>
215
  <li><strong>🌦️ Weather-Aware Routing</strong> - Safety-first delivery planning with OpenWeatherMap</li>
216
  <li><strong>🚦 Real-Time Traffic</strong> - Google Routes API integration with live traffic data</li>
@@ -239,7 +236,149 @@ if __name__ == "__main__":
239
  </html>
240
  """)
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  logger.info("[OK] Landing page added at / route")
 
243
  logger.info("[OK] MCP SSE endpoint available at /sse")
244
 
245
  # Run MCP server with SSE transport (includes both /sse and custom routes)
 
94
  logger.info("=" * 70)
95
 
96
  try:
97
+ # Add web routes for landing page and API key generation
98
  from starlette.responses import HTMLResponse, JSONResponse
99
+ from database.api_keys import generate_api_key as db_generate_api_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  @mcp.custom_route("/", methods=["GET"])
102
  async def landing_page(request):
 
139
  <code>https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse</code>
140
  </div>
141
 
142
+ <h3>🔑 Step 1: Get Your API Key</h3>
143
+ <p style="text-align: center; margin: 20px 0;">
144
+ <a href="/generate-key" style="display: inline-block; background: #3b82f6; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: bold; font-size: 18px;">
145
+ Generate API Key →
146
+ </a>
147
+ </p>
148
+ <p>Click the button above to generate your unique API key. You'll need this to authenticate with the server.</p>
149
+
150
+ <h3>⚙️ Step 2: Configure Claude Desktop</h3>
151
  <p>Add this to your <code>claude_desktop_config.json</code> file:</p>
152
  <pre>{
153
  "mcpServers": {
 
156
  "args": [
157
  "mcp-remote",
158
  "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"
159
+ ],
160
+ "env": {
161
+ "FLEETMIND_API_KEY": "fm_your_api_key_here"
162
+ }
163
  }
164
  }
165
  }</pre>
166
 
167
+ <h3>📋 Step 3: Connect</h3>
168
  <ol>
169
+ <li><strong>Generate your API key</strong> using the button above</li>
170
  <li>Install <a href="https://claude.ai/download" target="_blank">Claude Desktop</a></li>
171
  <li>Locate your <code>claude_desktop_config.json</code> file</li>
172
+ <li>Add the configuration (replace <code>fm_your_api_key_here</code> with your actual key)</li>
173
  <li>Restart Claude Desktop</li>
174
  <li>Look for "FleetMind" in the 🔌 tools menu</li>
175
  </ol>
 
207
 
208
  <h2>⭐ Key Features</h2>
209
  <ul>
210
+ <li><strong>🔑 API Key Authentication</strong> - Secure multi-tenant access with personal API keys</li>
211
  <li><strong>🧠 Gemini 2.0 Flash AI</strong> - Intelligent order assignment with detailed reasoning</li>
212
  <li><strong>🌦️ Weather-Aware Routing</strong> - Safety-first delivery planning with OpenWeatherMap</li>
213
  <li><strong>🚦 Real-Time Traffic</strong> - Google Routes API integration with live traffic data</li>
 
236
  </html>
237
  """)
238
 
239
+ @mcp.custom_route("/generate-key", methods=["GET", "POST"])
240
+ async def generate_key_page(request):
241
+ """API Key generation page"""
242
+ if request.method == "GET":
243
+ return HTMLResponse("""
244
+ <!DOCTYPE html>
245
+ <html>
246
+ <head>
247
+ <title>Generate FleetMind API Key</title>
248
+ <style>
249
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
250
+ .container { background: #1e293b; padding: 40px; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); }
251
+ h1 { color: #f1f5f9; }
252
+ input { width: 100%; padding: 12px; margin: 10px 0; border-radius: 6px; border: 1px solid #334155; background: #0f172a; color: #e2e8f0; font-size: 16px; }
253
+ button { background: #3b82f6; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 16px; cursor: pointer; width: 100%; }
254
+ button:hover { background: #2563eb; }
255
+ .info { background: #1e3a5f; padding: 15px; border-radius: 6px; margin: 15px 0; border-left: 4px solid #3b82f6; }
256
+ </style>
257
+ </head>
258
+ <body>
259
+ <div class="container">
260
+ <h1>🔑 Generate API Key</h1>
261
+ <p>Create your FleetMind MCP Server API key</p>
262
+
263
+ <div class="info">
264
+ <strong>📋 What you'll need:</strong><br>
265
+ • Your email address<br>
266
+ • Your name (optional)<br>
267
+ <br>
268
+ After generation, you'll get an API key to use with Claude Desktop.
269
+ </div>
270
+
271
+ <form method="POST">
272
+ <input type="email" name="email" placeholder="Your email address" required>
273
+ <input type="text" name="name" placeholder="Your name (optional)">
274
+ <button type="submit">Generate API Key</button>
275
+ </form>
276
+
277
+ <p style="text-align: center; margin-top: 20px;">
278
+ <a href="/" style="color: #60a5fa; text-decoration: none;">← Back to Home</a>
279
+ </p>
280
+ </div>
281
+ </body>
282
+ </html>
283
+ """)
284
+
285
+ # POST - Generate API key
286
+ else:
287
+ try:
288
+ form_data = await request.form()
289
+ email = form_data.get("email")
290
+ name = form_data.get("name") or None
291
+
292
+ if not email:
293
+ return HTMLResponse("<h1>Error: Email is required</h1>", status_code=400)
294
+
295
+ result = db_generate_api_key(email, name)
296
+
297
+ if not result['success']:
298
+ return HTMLResponse(f"<h1>Error: {result['error']}</h1>", status_code=400)
299
+
300
+ # Success - show the API key (one time only!)
301
+ return HTMLResponse(f"""
302
+ <!DOCTYPE html>
303
+ <html>
304
+ <head>
305
+ <title>Your FleetMind API Key</title>
306
+ <style>
307
+ body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }}
308
+ .container {{ background: #1e293b; padding: 40px; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); }}
309
+ h1 {{ color: #f1f5f9; }}
310
+ .success {{ background: #10b981; padding: 15px; border-radius: 6px; margin: 15px 0; }}
311
+ .warning {{ background: #ef4444; padding: 15px; border-radius: 6px; margin: 15px 0; }}
312
+ code {{ background: #334155; color: #60a5fa; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; display: block; margin: 10px 0; word-wrap: break-word; font-size: 14px; }}
313
+ pre {{ background: #0f172a; color: #f1f5f9; padding: 20px; border-radius: 8px; overflow-x: auto; border: 1px solid #334155; }}
314
+ button {{ background: #3b82f6; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 16px; cursor: pointer; margin: 10px 5px; }}
315
+ button:hover {{ background: #2563eb; }}
316
+ </style>
317
+ <script>
318
+ function copyKey() {{
319
+ navigator.clipboard.writeText('{result["api_key"]}');
320
+ alert('API key copied to clipboard!');
321
+ }}
322
+ </script>
323
+ </head>
324
+ <body>
325
+ <div class="container">
326
+ <h1>✅ API Key Generated!</h1>
327
+
328
+ <div class="success">
329
+ <strong>Your API key has been created successfully</strong>
330
+ </div>
331
+
332
+ <p><strong>Email:</strong> {result["email"]}</p>
333
+ <p><strong>Name:</strong> {result["name"]}</p>
334
+ <p><strong>User ID:</strong> {result["user_id"]}</p>
335
+
336
+ <div class="warning">
337
+ <strong>⚠️ SAVE THIS KEY NOW - IT WON'T BE SHOWN AGAIN!</strong>
338
+ </div>
339
+
340
+ <h2>🔑 Your API Key:</h2>
341
+ <code>{result["api_key"]}</code>
342
+ <button onclick="copyKey()">📋 Copy Key</button>
343
+
344
+ <h2>📋 Claude Desktop Setup:</h2>
345
+ <p>Add this to your <code>claude_desktop_config.json</code>:</p>
346
+ <pre>{{
347
+ "mcpServers": {{
348
+ "fleetmind": {{
349
+ "command": "npx",
350
+ "args": [
351
+ "mcp-remote",
352
+ "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"
353
+ ],
354
+ "env": {{
355
+ "FLEETMIND_API_KEY": "{result["api_key"]}"
356
+ }}
357
+ }}
358
+ }}
359
+ }}</pre>
360
+
361
+ <h2>🚀 Next Steps:</h2>
362
+ <ol>
363
+ <li>Copy your API key (click the button above)</li>
364
+ <li>Add it to your Claude Desktop config</li>
365
+ <li>Restart Claude Desktop</li>
366
+ <li>Start using FleetMind tools!</li>
367
+ </ol>
368
+
369
+ <p style="text-align: center; margin-top: 30px;">
370
+ <a href="/" style="color: #60a5fa; text-decoration: none;">← Back to Home</a>
371
+ </p>
372
+ </div>
373
+ </body>
374
+ </html>
375
+ """)
376
+
377
+ except Exception as e:
378
+ return HTMLResponse(f"<h1>Error: {str(e)}</h1>", status_code=500)
379
+
380
  logger.info("[OK] Landing page added at / route")
381
+ logger.info("[OK] API key generation page added at /generate-key")
382
  logger.info("[OK] MCP SSE endpoint available at /sse")
383
 
384
  # Run MCP server with SSE transport (includes both /sse and custom routes)
database/api_keys.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API Key Authentication System for FleetMind MCP Server
3
+
4
+ Simple API key management for multi-tenant authentication without OAuth complexity.
5
+ Works with Claude Desktop and mcp-remote today!
6
+
7
+ Usage:
8
+ 1. User generates API key via web interface or CLI
9
+ 2. User adds API key to Claude Desktop config
10
+ 3. MCP server validates key and returns user_id
11
+ 4. Multi-tenant isolation works automatically
12
+ """
13
+
14
+ import os
15
+ import secrets
16
+ import hashlib
17
+ from datetime import datetime
18
+ from typing import Optional, Dict
19
+ from database.connection import get_db_connection
20
+
21
+ def create_api_keys_table():
22
+ """Create api_keys table if it doesn't exist"""
23
+ conn = get_db_connection()
24
+ cursor = conn.cursor()
25
+
26
+ cursor.execute("""
27
+ CREATE TABLE IF NOT EXISTS api_keys (
28
+ key_id SERIAL PRIMARY KEY,
29
+ user_id VARCHAR(100) NOT NULL,
30
+ email VARCHAR(255) NOT NULL,
31
+ name VARCHAR(255),
32
+ api_key_hash VARCHAR(64) NOT NULL UNIQUE,
33
+ api_key_prefix VARCHAR(20) NOT NULL,
34
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
35
+ last_used_at TIMESTAMP,
36
+ is_active BOOLEAN DEFAULT true,
37
+ UNIQUE(user_id)
38
+ )
39
+ """)
40
+
41
+ cursor.execute("""
42
+ CREATE INDEX IF NOT EXISTS idx_api_keys_hash
43
+ ON api_keys(api_key_hash)
44
+ """)
45
+
46
+ cursor.execute("""
47
+ CREATE INDEX IF NOT EXISTS idx_api_keys_user
48
+ ON api_keys(user_id)
49
+ """)
50
+
51
+ conn.commit()
52
+ cursor.close()
53
+ conn.close()
54
+
55
+ def generate_api_key(email: str, name: str = None) -> Dict[str, str]:
56
+ """
57
+ Generate a new API key for a user
58
+
59
+ Args:
60
+ email: User's email address (used as identifier)
61
+ name: User's display name (optional)
62
+
63
+ Returns:
64
+ dict with api_key (show once!), user_id, email, name
65
+ """
66
+ # Generate secure random API key
67
+ api_key = f"fm_{secrets.token_urlsafe(32)}" # fm_ prefix for FleetMind
68
+
69
+ # Hash the API key for storage (never store plain text!)
70
+ api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()
71
+
72
+ # Store prefix for display (first 12 chars)
73
+ api_key_prefix = api_key[:12]
74
+
75
+ # Generate user_id from email
76
+ user_id = f"user_{hashlib.md5(email.encode()).hexdigest()[:12]}"
77
+
78
+ conn = get_db_connection()
79
+ cursor = conn.cursor()
80
+
81
+ try:
82
+ # Check if user already has a key
83
+ cursor.execute("SELECT user_id FROM api_keys WHERE email = %s", (email,))
84
+ existing = cursor.fetchone()
85
+
86
+ if existing:
87
+ cursor.close()
88
+ conn.close()
89
+ return {
90
+ "success": False,
91
+ "error": "User already has an API key. Revoke the old key first."
92
+ }
93
+
94
+ # Insert new API key
95
+ cursor.execute("""
96
+ INSERT INTO api_keys (user_id, email, name, api_key_hash, api_key_prefix)
97
+ VALUES (%s, %s, %s, %s, %s)
98
+ RETURNING user_id, email, name, created_at
99
+ """, (user_id, email, name, api_key_hash, api_key_prefix))
100
+
101
+ result = cursor.fetchone()
102
+ conn.commit()
103
+
104
+ if not result:
105
+ raise Exception("Failed to insert API key")
106
+
107
+ # Unpack the result tuple
108
+ ret_user_id, ret_email, ret_name, ret_created_at = result
109
+
110
+ return {
111
+ "success": True,
112
+ "api_key": api_key, # SHOW THIS ONCE! Never displayed again
113
+ "user_id": ret_user_id,
114
+ "email": ret_email,
115
+ "name": ret_name or "FleetMind User",
116
+ "created_at": str(ret_created_at) if ret_created_at else "",
117
+ "message": "⚠️ IMPORTANT: Save this API key now! It won't be shown again."
118
+ }
119
+
120
+ except Exception as e:
121
+ conn.rollback()
122
+ import traceback
123
+ error_details = traceback.format_exc()
124
+ print(f"API Key Generation Error: {e}")
125
+ print(f"Error details: {error_details}")
126
+ return {
127
+ "success": False,
128
+ "error": f"Failed to generate API key: {str(e)}"
129
+ }
130
+ finally:
131
+ cursor.close()
132
+ conn.close()
133
+
134
+ def verify_api_key(api_key: str) -> Optional[Dict[str, str]]:
135
+ """
136
+ Verify API key and return user info
137
+
138
+ Args:
139
+ api_key: The API key to verify
140
+
141
+ Returns:
142
+ User info dict if valid, None if invalid
143
+ """
144
+ if not api_key or not api_key.startswith("fm_"):
145
+ return None
146
+
147
+ # Hash the provided key
148
+ api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()
149
+
150
+ conn = get_db_connection()
151
+ cursor = conn.cursor()
152
+
153
+ try:
154
+ # Look up the key
155
+ cursor.execute("""
156
+ SELECT user_id, email, name, is_active
157
+ FROM api_keys
158
+ WHERE api_key_hash = %s
159
+ """, (api_key_hash,))
160
+
161
+ result = cursor.fetchone()
162
+
163
+ if not result:
164
+ return None
165
+
166
+ user_id, email, name, is_active = result
167
+
168
+ if not is_active:
169
+ return None
170
+
171
+ # Update last_used_at
172
+ cursor.execute("""
173
+ UPDATE api_keys
174
+ SET last_used_at = CURRENT_TIMESTAMP
175
+ WHERE api_key_hash = %s
176
+ """, (api_key_hash,))
177
+ conn.commit()
178
+
179
+ return {
180
+ 'user_id': user_id,
181
+ 'email': email,
182
+ 'name': name or 'FleetMind User',
183
+ 'scopes': ['orders:read', 'orders:write', 'drivers:read', 'drivers:write', 'assignments:manage']
184
+ }
185
+
186
+ except Exception as e:
187
+ print(f"API key verification error: {e}")
188
+ return None
189
+ finally:
190
+ cursor.close()
191
+ conn.close()
192
+
193
+ def list_api_keys() -> list:
194
+ """List all API keys (without showing actual keys)"""
195
+ conn = get_db_connection()
196
+ cursor = conn.cursor()
197
+
198
+ cursor.execute("""
199
+ SELECT user_id, email, name, api_key_prefix, created_at, last_used_at, is_active
200
+ FROM api_keys
201
+ ORDER BY created_at DESC
202
+ """)
203
+
204
+ keys = []
205
+ for row in cursor.fetchall():
206
+ keys.append({
207
+ 'user_id': row[0],
208
+ 'email': row[1],
209
+ 'name': row[2],
210
+ 'key_preview': f"{row[3]}...",
211
+ 'created_at': row[4].isoformat(),
212
+ 'last_used_at': row[5].isoformat() if row[5] else None,
213
+ 'is_active': row[6]
214
+ })
215
+
216
+ cursor.close()
217
+ conn.close()
218
+ return keys
219
+
220
+ def revoke_api_key(email: str) -> Dict[str, any]:
221
+ """Revoke (deactivate) an API key"""
222
+ conn = get_db_connection()
223
+ cursor = conn.cursor()
224
+
225
+ try:
226
+ cursor.execute("""
227
+ UPDATE api_keys
228
+ SET is_active = false
229
+ WHERE email = %s
230
+ RETURNING user_id, email
231
+ """, (email,))
232
+
233
+ result = cursor.fetchone()
234
+ conn.commit()
235
+
236
+ if result:
237
+ return {
238
+ "success": True,
239
+ "message": f"API key revoked for {result[1]}"
240
+ }
241
+ else:
242
+ return {
243
+ "success": False,
244
+ "error": "No API key found for this email"
245
+ }
246
+
247
+ except Exception as e:
248
+ conn.rollback()
249
+ return {
250
+ "success": False,
251
+ "error": f"Failed to revoke key: {str(e)}"
252
+ }
253
+ finally:
254
+ cursor.close()
255
+ conn.close()
256
+
257
+ # Initialize table on import
258
+ try:
259
+ create_api_keys_table()
260
+ except:
261
+ pass # Table might already exist
generate_api_key.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ FleetMind API Key Generator
4
+
5
+ Generate API keys for users to authenticate with FleetMind MCP Server.
6
+
7
+ Usage:
8
+ python generate_api_key.py
9
+ python generate_api_key.py --email user@example.com --name "John Doe"
10
+ python generate_api_key.py --list
11
+ python generate_api_key.py --revoke user@example.com
12
+ """
13
+
14
+ import argparse
15
+ import sys
16
+ from database.api_keys import generate_api_key, list_api_keys, revoke_api_key
17
+
18
+ def main():
19
+ parser = argparse.ArgumentParser(
20
+ description='FleetMind API Key Management',
21
+ formatter_class=argparse.RawDescriptionHelpFormatter,
22
+ epilog="""
23
+ Examples:
24
+ # Interactive mode (prompts for email and name)
25
+ python generate_api_key.py
26
+
27
+ # Generate key with arguments
28
+ python generate_api_key.py --email alice@company.com --name "Alice Smith"
29
+
30
+ # List all API keys
31
+ python generate_api_key.py --list
32
+
33
+ # Revoke a key
34
+ python generate_api_key.py --revoke alice@company.com
35
+ """
36
+ )
37
+
38
+ parser.add_argument('--email', help='User email address')
39
+ parser.add_argument('--name', help='User display name')
40
+ parser.add_argument('--list', action='store_true', help='List all API keys')
41
+ parser.add_argument('--revoke', metavar='EMAIL', help='Revoke API key for email')
42
+
43
+ args = parser.parse_args()
44
+
45
+ # List API keys
46
+ if args.list:
47
+ print("\n" + "="*80)
48
+ print("FLEETMIND API KEYS")
49
+ print("="*80 + "\n")
50
+
51
+ keys = list_api_keys()
52
+ if not keys:
53
+ print("No API keys found.\n")
54
+ return
55
+
56
+ for key in keys:
57
+ status = "✅ Active" if key['is_active'] else "❌ Revoked"
58
+ print(f"Email: {key['email']}")
59
+ print(f"Name: {key['name']}")
60
+ print(f"User ID: {key['user_id']}")
61
+ print(f"Key: {key['key_preview']}")
62
+ print(f"Created: {key['created_at']}")
63
+ print(f"Last Used: {key['last_used_at'] or 'Never'}")
64
+ print(f"Status: {status}")
65
+ print("-" * 80)
66
+
67
+ print()
68
+ return
69
+
70
+ # Revoke API key
71
+ if args.revoke:
72
+ print(f"\n⚠️ Revoking API key for {args.revoke}...")
73
+ result = revoke_api_key(args.revoke)
74
+
75
+ if result['success']:
76
+ print(f"✅ {result['message']}\n")
77
+ else:
78
+ print(f"❌ Error: {result['error']}\n")
79
+ sys.exit(1)
80
+ return
81
+
82
+ # Generate new API key
83
+ if not args.email:
84
+ # Interactive mode
85
+ print("\n" + "="*80)
86
+ print("GENERATE NEW FLEETMIND API KEY")
87
+ print("="*80 + "\n")
88
+
89
+ email = input("Enter user email: ").strip()
90
+ if not email:
91
+ print("❌ Email is required")
92
+ sys.exit(1)
93
+
94
+ name = input("Enter user name (optional): ").strip() or None
95
+ else:
96
+ email = args.email
97
+ name = args.name
98
+
99
+ print(f"\nGenerating API key for {email}...")
100
+ result = generate_api_key(email, name)
101
+
102
+ if not result['success']:
103
+ print(f"\n❌ Error: {result['error']}\n")
104
+ sys.exit(1)
105
+
106
+ # Success! Display the API key
107
+ print("\n" + "="*80)
108
+ print("✅ API KEY GENERATED SUCCESSFULLY")
109
+ print("="*80 + "\n")
110
+
111
+ print(f"Email: {result['email']}")
112
+ print(f"Name: {result['name']}")
113
+ print(f"User ID: {result['user_id']}")
114
+ print(f"Created: {result['created_at']}")
115
+ print()
116
+ print("🔑 API KEY (copy this now - it won't be shown again!):")
117
+ print("-" * 80)
118
+ print(result['api_key'])
119
+ print("-" * 80)
120
+ print()
121
+ print("📋 Add this to your Claude Desktop config:")
122
+ print()
123
+ print(' {')
124
+ print(' "mcpServers": {')
125
+ print(' "fleetmind": {')
126
+ print(' "command": "npx",')
127
+ print(' "args": [')
128
+ print(' "mcp-remote",')
129
+ print(' "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"')
130
+ print(' ],')
131
+ print(' "env": {')
132
+ print(f' "FLEETMIND_API_KEY": "{result["api_key"]}"')
133
+ print(' }')
134
+ print(' }')
135
+ print(' }')
136
+ print(' }')
137
+ print()
138
+ print("⚠️ IMPORTANT: Save this API key securely. You won't be able to see it again!")
139
+ print()
140
+
141
+ if __name__ == "__main__":
142
+ main()
requirements.txt CHANGED
@@ -17,7 +17,6 @@ psycopg2-binary>=2.9.9
17
  # API Clients
18
  googlemaps>=4.10.0
19
  requests>=2.31.0
20
- stytch>=8.0.0
21
 
22
  # Utilities
23
  python-dotenv>=1.0.0
 
17
  # API Clients
18
  googlemaps>=4.10.0
19
  requests>=2.31.0
 
20
 
21
  # Utilities
22
  python-dotenv>=1.0.0
server.py CHANGED
@@ -18,15 +18,13 @@ from datetime import datetime
18
  sys.path.insert(0, str(Path(__file__).parent))
19
 
20
  from fastmcp import FastMCP
21
- from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier, AccessToken
22
- from pydantic import AnyHttpUrl
23
 
24
  # Import existing services (unchanged)
25
  from chat.geocoding import GeocodingService
26
  from database.connection import execute_query, execute_write, test_connection
27
 
28
- # Import authentication module
29
- from database.user_context import verify_token, check_permission, get_required_scope
30
 
31
  # Configure logging
32
  logging.basicConfig(
@@ -39,65 +37,9 @@ logging.basicConfig(
39
  )
40
  logger = logging.getLogger(__name__)
41
 
42
- # ============================================================================
43
- # OAUTH AUTHENTICATION SETUP
44
- # ============================================================================
45
-
46
- class StytchTokenVerifier(TokenVerifier):
47
- """Token verifier for Stytch session tokens"""
48
-
49
- def __init__(self):
50
- super().__init__(
51
- base_url=os.getenv('SERVER_URL', 'http://localhost:7860'),
52
- required_scopes=None # Scope checking handled per-tool
53
- )
54
-
55
- async def verify_token(self, token: str) -> AccessToken | None:
56
- """Verify Stytch session token and return AccessToken"""
57
- try:
58
- # Use existing Stytch verification function
59
- user_info = verify_token(token)
60
-
61
- if not user_info:
62
- logger.debug(f"Token verification failed")
63
- return None
64
-
65
- # Convert to FastMCP AccessToken format
66
- access_token = AccessToken(
67
- token=token,
68
- client_id=user_info['user_id'],
69
- scopes=user_info.get('scopes', []),
70
- resource_owner=user_info.get('email'),
71
- claims={
72
- 'user_id': user_info['user_id'],
73
- 'email': user_info['email'],
74
- 'name': user_info.get('name', 'Unknown User'),
75
- }
76
- )
77
-
78
- logger.info(f"Token verified for user: {user_info['email']} (user_id: {user_info['user_id']})")
79
- return access_token
80
-
81
- except Exception as e:
82
- logger.error(f"Token verification error: {e}")
83
- return None
84
-
85
- # Create OAuth authentication provider (but don't apply globally yet)
86
- auth_provider = RemoteAuthProvider(
87
- token_verifier=StytchTokenVerifier(),
88
- authorization_servers=[AnyHttpUrl('https://test.stytch.com/v1/public')],
89
- base_url=os.getenv('SERVER_URL', 'http://localhost:7860'),
90
- resource_name="FleetMind Dispatch Coordinator"
91
- )
92
-
93
- logger.info("OAuth authentication provider configured with Stytch")
94
-
95
  # ============================================================================
96
  # MCP SERVER INITIALIZATION
97
  # ============================================================================
98
-
99
- # NOTE: Not using auth=auth_provider here because it would block SSE connections
100
- # Instead, we manually verify tokens in each tool using get_authenticated_user()
101
  mcp = FastMCP(
102
  name="FleetMind Dispatch Coordinator",
103
  version="1.0.0"
@@ -116,21 +58,36 @@ except Exception as e:
116
  logger.error(f"Database: Connection failed - {e}")
117
 
118
  # ============================================================================
119
- # AUTHENTICATION
120
  # ============================================================================
121
 
122
- # Note: OAuth metadata endpoint will be added via app.py instead of here
123
- # FastMCP doesn't expose direct app access for custom routes
124
-
125
  def get_authenticated_user():
126
  """
127
- Extract and verify user from authentication token via FastMCP
 
 
 
 
128
 
129
  Returns:
130
  User info dict with user_id, email, scopes, name or None if not authenticated
131
  """
132
  try:
133
- # Development bypass mode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  if os.getenv("SKIP_AUTH") == "true":
135
  logger.debug("SKIP_AUTH enabled - using development user")
136
  return {
@@ -140,31 +97,10 @@ def get_authenticated_user():
140
  'name': 'Development User'
141
  }
142
 
143
- # Extract token from FastMCP request context
144
- from fastmcp.server.dependencies import get_access_token
145
-
146
- access_token = get_access_token()
147
-
148
- if not access_token:
149
- logger.debug("No access token found in request context")
150
- return None
151
-
152
- # Token already verified by FastMCP auth middleware
153
- # Extract user info from AccessToken claims
154
- user_info = {
155
- 'user_id': access_token.claims.get('user_id') or access_token.client_id,
156
- 'email': access_token.resource_owner or access_token.claims.get('email', 'unknown'),
157
- 'scopes': access_token.scopes or [],
158
- 'name': access_token.claims.get('name', 'Unknown User')
159
- }
160
-
161
- logger.info(f"Authenticated user: {user_info['email']} (user_id: {user_info['user_id']})")
162
- return user_info
163
-
164
- except RuntimeError as e:
165
- # No HTTP request context available (likely stdio mode or testing)
166
- logger.debug(f"No request context available: {e}")
167
  return None
 
168
  except Exception as e:
169
  logger.error(f"Authentication error: {e}")
170
  return None
 
18
  sys.path.insert(0, str(Path(__file__).parent))
19
 
20
  from fastmcp import FastMCP
 
 
21
 
22
  # Import existing services (unchanged)
23
  from chat.geocoding import GeocodingService
24
  from database.connection import execute_query, execute_write, test_connection
25
 
26
+ # Import permission checking
27
+ from database.user_context import check_permission, get_required_scope
28
 
29
  # Configure logging
30
  logging.basicConfig(
 
37
  )
38
  logger = logging.getLogger(__name__)
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  # ============================================================================
41
  # MCP SERVER INITIALIZATION
42
  # ============================================================================
 
 
 
43
  mcp = FastMCP(
44
  name="FleetMind Dispatch Coordinator",
45
  version="1.0.0"
 
58
  logger.error(f"Database: Connection failed - {e}")
59
 
60
  # ============================================================================
61
+ # AUTHENTICATION - API KEY SYSTEM
62
  # ============================================================================
63
 
 
 
 
64
  def get_authenticated_user():
65
  """
66
+ Extract and verify user via API Key authentication
67
+
68
+ Supports 2 authentication methods:
69
+ 1. API Key (from FLEETMIND_API_KEY env var) - Production & user auth
70
+ 2. Development Mode (SKIP_AUTH=true) - Local testing only
71
 
72
  Returns:
73
  User info dict with user_id, email, scopes, name or None if not authenticated
74
  """
75
  try:
76
+ # METHOD 1: API Key Authentication
77
+ # Users set this in their Claude Desktop config:
78
+ # "env": {"FLEETMIND_API_KEY": "fm_xxxxx"}
79
+ api_key = os.getenv("FLEETMIND_API_KEY")
80
+ if api_key:
81
+ from database.api_keys import verify_api_key
82
+ user_info = verify_api_key(api_key)
83
+ if user_info:
84
+ logger.info(f"✅ API Key auth: {user_info['email']} (user_id: {user_info['user_id']})")
85
+ return user_info
86
+ else:
87
+ logger.warning(f"❌ Invalid API key provided")
88
+ return None
89
+
90
+ # METHOD 2: Development bypass mode (local testing only)
91
  if os.getenv("SKIP_AUTH") == "true":
92
  logger.debug("SKIP_AUTH enabled - using development user")
93
  return {
 
97
  'name': 'Development User'
98
  }
99
 
100
+ # No authentication provided
101
+ logger.debug("No API key or SKIP_AUTH - authentication required")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  return None
103
+
104
  except Exception as e:
105
  logger.error(f"Authentication error: {e}")
106
  return None