mashrur950 commited on
Commit
1cf571b
·
1 Parent(s): a2d87ed

done authentication

Browse files
Files changed (5) hide show
  1. PROXY_SETUP.md +202 -0
  2. app.py +3 -1
  3. proxy.py +205 -0
  4. requirements.txt +3 -0
  5. start_with_proxy.py +164 -0
PROXY_SETUP.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FleetMind MCP - Multi-Tenant Authentication Proxy
2
+
3
+ ## Overview
4
+
5
+ This setup enables **true multi-tenant authentication** for FleetMind MCP Server by using an HTTP proxy that captures API keys from client connections and injects them into server requests.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ Claude Desktop → Auth Proxy (port 7860) → FastMCP Server (port 7861)
11
+ ↓ Captures api_key
12
+ ↓ Stores session mapping
13
+ ↓ Injects into requests
14
+ ```
15
+
16
+ ## Components
17
+
18
+ ### 1. **proxy.py** - Authentication Proxy
19
+ - Listens on port **7860** (public-facing)
20
+ - Captures `api_key` from `/sse?api_key=xxx` connections
21
+ - Maps `session_id` to `api_key`
22
+ - Injects `api_key` into all `/messages/?session_id=xxx` requests
23
+ - Forwards all requests to FastMCP on port 7861
24
+
25
+ ### 2. **app.py** - FastMCP Server
26
+ - Runs on port **7861** (internal only)
27
+ - Receives requests from proxy with API keys injected
28
+ - Handles all MCP protocol operations
29
+ - Authenticates users using injected API keys
30
+
31
+ ### 3. **start_with_proxy.py** - Unified Launcher
32
+ - Starts both FastMCP and proxy together
33
+ - Handles graceful shutdown
34
+ - Monitors both processes
35
+
36
+ ## Quick Start
37
+
38
+ ### Local Development
39
+
40
+ 1. **Start the server with proxy:**
41
+ ```bash
42
+ cd fleetmind-mcp
43
+ python start_with_proxy.py
44
+ ```
45
+
46
+ 2. **Update Claude Desktop config:**
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "fleetmind_local": {
51
+ "command": "npx",
52
+ "args": [
53
+ "mcp-remote",
54
+ "http://localhost:7860/sse?api_key=fm_WGJMC9LO5KqZmqrcswel-CJFXFWLiUqJo21ebm5iKgk"
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ 3. **Restart Claude Desktop**
62
+
63
+ 4. **Test:**
64
+ - Connect from Claude Desktop
65
+ - Run any command (e.g., "count orders")
66
+ - Check console for: `🔑 Captured API key from SSE connection`
67
+
68
+ ## How It Works
69
+
70
+ ### Step 1: Initial Connection
71
+ ```
72
+ Client → GET /sse?api_key=fm_xxx
73
+ Proxy captures api_key, stores as '_pending_api_key'
74
+ Proxy forwards to FastMCP → GET /sse?api_key=fm_xxx
75
+ ```
76
+
77
+ ### Step 2: Session Linking
78
+ ```
79
+ Client → POST /messages/?session_id=abc123
80
+ Proxy sees session_id
81
+ Proxy links session_id → api_key from pending
82
+ Proxy stores mapping: {abc123: fm_xxx}
83
+ ```
84
+
85
+ ### Step 3: Tool Calls
86
+ ```
87
+ Client → POST /messages/?session_id=abc123
88
+ Proxy looks up api_key for session abc123
89
+ Proxy injects: /messages/?session_id=abc123&api_key=fm_xxx
90
+ FastMCP receives request with API key
91
+ FastMCP authenticates and executes tool
92
+ ```
93
+
94
+ ## Multi-Tenant Support
95
+
96
+ Each client connection gets its own session with its own API key:
97
+
98
+ ```
99
+ User A → session_1 → api_key_A
100
+ User B → session_2 → api_key_B
101
+ User C → session_3 → api_key_C
102
+ ```
103
+
104
+ All requests are isolated and authenticated independently!
105
+
106
+ ## Configuration
107
+
108
+ ### Environment Variables
109
+
110
+ **For proxy.py:**
111
+ - `PROXY_PORT` - Proxy listening port (default: 7860)
112
+ - `FASTMCP_PORT` - FastMCP internal port (default: 7861)
113
+ - `FASTMCP_HOST` - FastMCP host (default: localhost)
114
+
115
+ **For app.py:**
116
+ - `PORT` - FastMCP port (default: 7861)
117
+ - `HOST` - FastMCP host (default: 0.0.0.0)
118
+
119
+ ### Claude Desktop Config
120
+
121
+ **With API key in URL** (recommended):
122
+ ```json
123
+ {
124
+ "mcpServers": {
125
+ "fleetmind": {
126
+ "command": "npx",
127
+ "args": [
128
+ "mcp-remote",
129
+ "http://localhost:7860/sse?api_key=YOUR_API_KEY_HERE"
130
+ ]
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Deployment to Hugging Face
137
+
138
+ ### Option 1: Single Script (Recommended)
139
+
140
+ 1. Create `app_with_proxy.py` that runs both servers
141
+ 2. Update `Dockerfile` to install `aiohttp`
142
+ 3. Deploy to HuggingFace Space
143
+
144
+ ### Option 2: Process Manager
145
+
146
+ 1. Use `start_with_proxy.py` as entry point
147
+ 2. Update `Dockerfile`:
148
+ ```dockerfile
149
+ CMD ["python", "start_with_proxy.py"]
150
+ ```
151
+
152
+ ### Option 3: Supervisor
153
+
154
+ 1. Install supervisor
155
+ 2. Configure both processes
156
+ 3. Use supervisor as entry point
157
+
158
+ ## Troubleshooting
159
+
160
+ ### Proxy not starting
161
+ - Check port 7860 is available: `netstat -an | findstr 7860`
162
+ - Check Python version: `python --version` (need 3.7+)
163
+ - Install aiohttp: `pip install aiohttp`
164
+
165
+ ### FastMCP not starting
166
+ - Check port 7861 is available
167
+ - Check database connection
168
+ - Check environment variables in `.env`
169
+
170
+ ### API key not captured
171
+ - Check logs for "🔑 Captured API key from SSE connection"
172
+ - Verify API key is in URL: `/sse?api_key=xxx`
173
+ - Check proxy is receiving requests
174
+
175
+ ### Authentication failing
176
+ - Check session linking: "✅ Linked session ... to API key"
177
+ - Verify API key is valid in database
178
+ - Check FastMCP logs for authentication errors
179
+
180
+ ## Advantages
181
+
182
+ ✅ **True multi-tenant** - Each user has their own API key
183
+ ✅ **No FastMCP changes** - Works with existing FastMCP code
184
+ ✅ **Simple** - Only 200 lines of proxy code
185
+ ✅ **Reliable** - Standard HTTP proxy pattern
186
+ ✅ **Scalable** - Can add Redis for session storage
187
+ ✅ **Deployable** - Works on HuggingFace, AWS, anywhere
188
+
189
+ ## Future Improvements
190
+
191
+ - [ ] Add Redis for distributed session storage
192
+ - [ ] Add session expiration (TTL)
193
+ - [ ] Add request logging
194
+ - [ ] Add metrics/monitoring
195
+ - [ ] Add rate limiting per API key
196
+ - [ ] Add WebSocket support
197
+
198
+ ## Support
199
+
200
+ For issues or questions:
201
+ - GitHub: https://github.com/mashrur-rahman-fahim/fleetmind-mcp
202
+ - HuggingFace: https://huggingface.co/spaces/MCP-1st-Birthday/fleetmind-dispatch-ai
app.py CHANGED
@@ -45,7 +45,9 @@ from server import mcp
45
  # ============================================================================
46
 
47
  # HuggingFace Space default port
48
- HF_SPACE_PORT = int(os.getenv("PORT", 7860))
 
 
49
  HF_SPACE_HOST = os.getenv("HOST", "0.0.0.0")
50
 
51
  # ============================================================================
 
45
  # ============================================================================
46
 
47
  # HuggingFace Space default port
48
+ # NOTE: When using proxy.py, FastMCP runs on 7861 (internal port)
49
+ # The proxy runs on 7860 (public) and forwards requests here
50
+ HF_SPACE_PORT = int(os.getenv("PORT", 7861))
51
  HF_SPACE_HOST = os.getenv("HOST", "0.0.0.0")
52
 
53
  # ============================================================================
proxy.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FleetMind MCP Authentication Proxy
3
+ Captures API keys from initial SSE connections and injects them into tool requests.
4
+
5
+ This proxy sits between MCP clients and the FastMCP server, solving the
6
+ multi-tenant authentication problem by:
7
+ 1. Capturing api_key from initial /sse?api_key=xxx connection
8
+ 2. Storing api_key mapped to session_id
9
+ 3. Injecting api_key into subsequent /messages/?session_id=xxx requests
10
+
11
+ Architecture:
12
+ MCP Client → Proxy (port 7860) → FastMCP (port 7861)
13
+ """
14
+
15
+ import asyncio
16
+ import logging
17
+ from aiohttp import web, ClientSession, ClientTimeout
18
+ from urllib.parse import urlencode, parse_qs, urlparse, urlunparse
19
+ import sys
20
+
21
+ # Configure logging
22
+ logging.basicConfig(
23
+ level=logging.INFO,
24
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
25
+ handlers=[logging.StreamHandler()]
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Proxy configuration
30
+ PROXY_PORT = 7860 # Public-facing port (what clients connect to)
31
+ FASTMCP_PORT = 7861 # Internal FastMCP server port
32
+ FASTMCP_HOST = "localhost"
33
+
34
+ # Session storage: session_id -> api_key
35
+ session_api_keys = {}
36
+
37
+
38
+ async def proxy_handler(request):
39
+ """
40
+ Main proxy handler - forwards all requests to FastMCP server.
41
+ Captures API keys from SSE connections and injects them into tool calls.
42
+ """
43
+ path = request.path
44
+ query_params = dict(request.query)
45
+
46
+ # Extract API key if present (initial SSE connection)
47
+ api_key = query_params.get('api_key')
48
+ session_id = query_params.get('session_id')
49
+
50
+ # STEP 1: Capture API key from initial SSE connection
51
+ if api_key and path == '/sse':
52
+ logger.info(f"[AUTH] Captured API key from SSE connection: {api_key[:20]}...")
53
+ # Store temporarily - will be linked to session when we see it
54
+ session_api_keys['_pending_api_key'] = api_key
55
+
56
+ # STEP 2: Link session_id to API key (from /messages requests)
57
+ if session_id and path.startswith('/messages'):
58
+ # Check if we have a stored API key for this session
59
+ if session_id not in session_api_keys:
60
+ # Link this session to the pending API key
61
+ if '_pending_api_key' in session_api_keys:
62
+ api_key_to_store = session_api_keys['_pending_api_key']
63
+ session_api_keys[session_id] = api_key_to_store
64
+ logger.info(f"[AUTH] Linked session {session_id[:12]}... to API key")
65
+
66
+ # STEP 3: Inject API key into request for FastMCP
67
+ stored_api_key = session_api_keys.get(session_id)
68
+ if stored_api_key:
69
+ query_params['api_key'] = stored_api_key
70
+ logger.debug(f"[AUTH] Injected API key into request for session {session_id[:12]}...")
71
+
72
+ # Build target URL for FastMCP server
73
+ query_string = urlencode(query_params) if query_params else ""
74
+ target_url = f"http://{FASTMCP_HOST}:{FASTMCP_PORT}{path}"
75
+ if query_string:
76
+ target_url += f"?{query_string}"
77
+
78
+ # Forward request to FastMCP
79
+ async with ClientSession(timeout=ClientTimeout(total=300)) as session:
80
+ try:
81
+ # Copy headers
82
+ headers = dict(request.headers)
83
+ # Remove host header to avoid conflicts
84
+ headers.pop('Host', None)
85
+
86
+ # Forward request based on method
87
+ if request.method == 'GET':
88
+ async with session.get(target_url, headers=headers) as resp:
89
+ # For SSE, stream the response
90
+ if 'text/event-stream' in resp.content_type:
91
+ # Create streaming response for SSE
92
+ response = web.StreamResponse(
93
+ status=resp.status,
94
+ reason=resp.reason,
95
+ headers=dict(resp.headers)
96
+ )
97
+ await response.prepare(request)
98
+
99
+ # Stream chunks from FastMCP to client
100
+ async for chunk in resp.content.iter_any():
101
+ await response.write(chunk)
102
+
103
+ await response.write_eof()
104
+ return response
105
+ else:
106
+ # For regular responses, read entire body
107
+ body = await resp.read()
108
+ resp_headers = dict(resp.headers)
109
+ return web.Response(
110
+ body=body,
111
+ status=resp.status,
112
+ headers=resp_headers
113
+ )
114
+
115
+ elif request.method == 'POST':
116
+ body = await request.read()
117
+ async with session.post(target_url, data=body, headers=headers) as resp:
118
+ resp_body = await resp.read()
119
+ # Don't pass content_type separately - it's already in headers
120
+ resp_headers = dict(resp.headers)
121
+ return web.Response(
122
+ body=resp_body,
123
+ status=resp.status,
124
+ headers=resp_headers
125
+ )
126
+
127
+ else:
128
+ # Forward other methods
129
+ async with session.request(
130
+ request.method,
131
+ target_url,
132
+ data=await request.read(),
133
+ headers=headers
134
+ ) as resp:
135
+ body = await resp.read()
136
+ return web.Response(
137
+ body=body,
138
+ status=resp.status,
139
+ headers=dict(resp.headers),
140
+ content_type=resp.content_type
141
+ )
142
+
143
+ except Exception as e:
144
+ logger.error(f"[ERROR] Proxy error: {e}")
145
+ return web.Response(
146
+ text=f"Proxy error: {str(e)}",
147
+ status=502
148
+ )
149
+
150
+
151
+ async def health_check(request):
152
+ """Health check endpoint"""
153
+ return web.Response(text="FleetMind Proxy OK", status=200)
154
+
155
+
156
+ def create_app():
157
+ """Create and configure the proxy application"""
158
+ app = web.Application()
159
+
160
+ # Health check endpoint
161
+ app.router.add_get('/health', health_check)
162
+
163
+ # Proxy all other requests
164
+ app.router.add_route('*', '/{path:.*}', proxy_handler)
165
+
166
+ return app
167
+
168
+
169
+ async def main():
170
+ """Start the proxy server"""
171
+ print("\n" + "=" * 70)
172
+ print("FleetMind MCP Authentication Proxy")
173
+ print("=" * 70)
174
+ print(f"Proxy listening on: http://0.0.0.0:{PROXY_PORT}")
175
+ print(f"Forwarding to FastMCP: http://{FASTMCP_HOST}:{FASTMCP_PORT}")
176
+ print("=" * 70)
177
+ print("[OK] Multi-tenant authentication enabled")
178
+ print("[OK] API keys captured from SSE connections")
179
+ print("[OK] Sessions automatically linked to API keys")
180
+ print("=" * 70 + "\n")
181
+
182
+ app = create_app()
183
+ runner = web.AppRunner(app)
184
+ await runner.setup()
185
+
186
+ site = web.TCPSite(runner, '0.0.0.0', PROXY_PORT)
187
+ await site.start()
188
+
189
+ logger.info(f"[OK] Proxy server started on port {PROXY_PORT}")
190
+ logger.info(f"[OK] Forwarding to FastMCP on {FASTMCP_HOST}:{FASTMCP_PORT}")
191
+
192
+ # Keep running
193
+ try:
194
+ await asyncio.Event().wait()
195
+ except KeyboardInterrupt:
196
+ logger.info("Shutting down proxy server...")
197
+ await runner.cleanup()
198
+
199
+
200
+ if __name__ == "__main__":
201
+ try:
202
+ asyncio.run(main())
203
+ except KeyboardInterrupt:
204
+ print("\nProxy server stopped.")
205
+ sys.exit(0)
requirements.txt CHANGED
@@ -11,6 +11,9 @@ pydantic>=2.8.2
11
  fastapi>=0.104.0
12
  uvicorn>=0.24.0
13
 
 
 
 
14
  # Database
15
  psycopg2-binary>=2.9.9
16
 
 
11
  fastapi>=0.104.0
12
  uvicorn>=0.24.0
13
 
14
+ # Authentication Proxy
15
+ aiohttp>=3.9.0
16
+
17
  # Database
18
  psycopg2-binary>=2.9.9
19
 
start_with_proxy.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FleetMind MCP Server with Authentication Proxy
3
+ Launches both the FastMCP server and the authentication proxy.
4
+
5
+ This script:
6
+ 1. Starts FastMCP server on port 7861 (internal)
7
+ 2. Starts authentication proxy on port 7860 (public)
8
+ 3. Handles graceful shutdown of both processes
9
+
10
+ Usage:
11
+ python start_with_proxy.py
12
+ """
13
+
14
+ import subprocess
15
+ import sys
16
+ import time
17
+ import signal
18
+ import os
19
+ from pathlib import Path
20
+
21
+ # Process handles
22
+ fastmcp_process = None
23
+ proxy_process = None
24
+
25
+
26
+ def signal_handler(sig, frame):
27
+ """Handle Ctrl+C gracefully"""
28
+ print("\n\nShutting down FleetMind MCP Server...")
29
+
30
+ if proxy_process:
31
+ print(" -> Stopping proxy...")
32
+ proxy_process.terminate()
33
+ try:
34
+ proxy_process.wait(timeout=5)
35
+ except subprocess.TimeoutExpired:
36
+ proxy_process.kill()
37
+
38
+ if fastmcp_process:
39
+ print(" -> Stopping FastMCP server...")
40
+ fastmcp_process.terminate()
41
+ try:
42
+ fastmcp_process.wait(timeout=5)
43
+ except subprocess.TimeoutExpired:
44
+ fastmcp_process.kill()
45
+
46
+ print("[OK] FleetMind MCP Server stopped.")
47
+ sys.exit(0)
48
+
49
+
50
+ def main():
51
+ global fastmcp_process, proxy_process
52
+
53
+ # Register signal handler
54
+ signal.signal(signal.SIGINT, signal_handler)
55
+ signal.signal(signal.SIGTERM, signal_handler)
56
+
57
+ print("\n" + "=" * 70)
58
+ print("FleetMind MCP Server with Multi-Tenant Authentication")
59
+ print("=" * 70)
60
+ print("Starting components:")
61
+ print(" 1. FastMCP Server (port 7861) - Internal")
62
+ print(" 2. Auth Proxy (port 7860) - Public")
63
+ print("=" * 70 + "\n")
64
+
65
+ # Get Python executable
66
+ python_exe = sys.executable
67
+
68
+ # Start FastMCP server
69
+ print("[1/2] Starting FastMCP server on port 7861...")
70
+ try:
71
+ # Don't capture output - let it show in console
72
+ fastmcp_process = subprocess.Popen(
73
+ [python_exe, "app.py"]
74
+ )
75
+ print("[OK] FastMCP server starting (output below)...")
76
+ except Exception as e:
77
+ print(f"[ERROR] Failed to start FastMCP server: {e}")
78
+ sys.exit(1)
79
+
80
+ # Wait a bit for FastMCP to initialize
81
+ print(" Waiting for FastMCP to initialize...")
82
+ time.sleep(3)
83
+
84
+ # Check if FastMCP is still running
85
+ if fastmcp_process.poll() is not None:
86
+ print("[ERROR] FastMCP server failed to start")
87
+ print(" Check the error messages above")
88
+ sys.exit(1)
89
+
90
+ print("[OK] FastMCP server running")
91
+
92
+ # Start proxy
93
+ print("\n[2/2] Starting authentication proxy on port 7860...")
94
+ try:
95
+ # Don't capture output - let it show in console
96
+ proxy_process = subprocess.Popen(
97
+ [python_exe, "proxy.py"]
98
+ )
99
+ print("[OK] Auth proxy starting (output below)...")
100
+ except Exception as e:
101
+ print(f"[ERROR] Failed to start proxy: {e}")
102
+ if fastmcp_process:
103
+ fastmcp_process.terminate()
104
+ sys.exit(1)
105
+
106
+ # Wait a bit for proxy to initialize
107
+ time.sleep(2)
108
+
109
+ # Check if proxy is still running
110
+ if proxy_process.poll() is not None:
111
+ print("[ERROR] Proxy failed to start")
112
+ if fastmcp_process:
113
+ fastmcp_process.terminate()
114
+ sys.exit(1)
115
+
116
+ print("[OK] Auth proxy running")
117
+
118
+ print("\n" + "=" * 70)
119
+ print("[OK] FleetMind MCP Server is READY!")
120
+ print("=" * 70)
121
+ print(f"Connect to: http://localhost:7860/sse")
122
+ print(f"Claude Desktop config:")
123
+ print(f' "args": ["mcp-remote", "http://localhost:7860/sse?api_key=YOUR_KEY"]')
124
+ print("=" * 70)
125
+ print("\n[AUTH] Multi-tenant authentication is ENABLED")
126
+ print(" Each connection's API key will be captured automatically\n")
127
+ print("Press Ctrl+C to stop...\n")
128
+
129
+ # Monitor both processes
130
+ try:
131
+ while True:
132
+ # Check FastMCP
133
+ if fastmcp_process.poll() is not None:
134
+ print("[ERROR] FastMCP server stopped unexpectedly")
135
+ if proxy_process:
136
+ proxy_process.terminate()
137
+ sys.exit(1)
138
+
139
+ # Check proxy
140
+ if proxy_process.poll() is not None:
141
+ print("[ERROR] Proxy stopped unexpectedly")
142
+ if fastmcp_process:
143
+ fastmcp_process.terminate()
144
+ sys.exit(1)
145
+
146
+ # Read and display logs (non-blocking)
147
+ # This keeps the terminal responsive
148
+ time.sleep(1)
149
+
150
+ except KeyboardInterrupt:
151
+ signal_handler(signal.SIGINT, None)
152
+
153
+
154
+ if __name__ == "__main__":
155
+ # Check if required files exist
156
+ if not Path("app.py").exists():
157
+ print("[ERROR] app.py not found")
158
+ sys.exit(1)
159
+
160
+ if not Path("proxy.py").exists():
161
+ print("[ERROR] proxy.py not found")
162
+ sys.exit(1)
163
+
164
+ main()