Spaces:
Sleeping
Sleeping
updates
Browse files- DOCKER_VS_GRADIO.md +412 -0
- PRIVATE_SPACE_GUIDE.md +283 -0
- STATUS_CHECK.md +208 -0
- TESTING_GUIDE.md +341 -0
- __pycache__/server.cpython-313.pyc +0 -0
- gradio_ui.py +70 -0
- pyproject.toml +1 -0
- test_deployment.py +194 -0
- test_deployment.sh +111 -0
- test_private_space.py +108 -0
- uv.lock +0 -0
DOCKER_VS_GRADIO.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker vs Gradio Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This document explains the difference between Docker and Gradio deployments on Hugging Face Spaces and why we chose Docker for your MCP server.
|
| 6 |
+
|
| 7 |
+
## Quick Comparison
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
Gradio: Python Code β Automatic UI β Human Users
|
| 11 |
+
Docker: Dockerfile β Custom Server β API Clients (AI, Apps, Services)
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
## Your Current Setup (Docker)
|
| 15 |
+
|
| 16 |
+
### Architecture
|
| 17 |
+
```
|
| 18 |
+
βββββββββββββββββββββββββββββββββββββββ
|
| 19 |
+
β Hugging Face Space β
|
| 20 |
+
β βββββββββββββββββββββββββββββββββ β
|
| 21 |
+
β β Docker Container β β
|
| 22 |
+
β β βββββββββββββββββββββββββββ β β
|
| 23 |
+
β β β FastAPI Server β β β
|
| 24 |
+
β β β (app.py) β β β
|
| 25 |
+
β β β Port: 7860 β β β
|
| 26 |
+
β β β β β β
|
| 27 |
+
β β β REST API Endpoints: β β β
|
| 28 |
+
β β β - GET /health β β β
|
| 29 |
+
β β β - GET / β β β
|
| 30 |
+
β β β - GET /tools β β β
|
| 31 |
+
β β β - POST /search β β β
|
| 32 |
+
β β βββββββββββββββββββββββββββ β β
|
| 33 |
+
β β β β β
|
| 34 |
+
β β βββββββββββββββββββββββββββ β β
|
| 35 |
+
β β β MCP Server (server.py) β β β
|
| 36 |
+
β β β YouTube Search Tool β β β
|
| 37 |
+
β β βββββββββββββββββββββββββββ β β
|
| 38 |
+
β βββββββββββββββββββββββββββββββββ β
|
| 39 |
+
βββββββββββββββββββββββββββββββββββββββ
|
| 40 |
+
β
|
| 41 |
+
REST API Calls
|
| 42 |
+
β
|
| 43 |
+
ββββββββββββββββββββ
|
| 44 |
+
β AI Assistants β
|
| 45 |
+
β Python Scripts β
|
| 46 |
+
β Web Apps β
|
| 47 |
+
ββββββββββββββββββββ
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### Access Methods
|
| 51 |
+
```bash
|
| 52 |
+
# API Access (Programmatic)
|
| 53 |
+
curl -X POST https://your-space.hf.space/search \
|
| 54 |
+
-d '{"query": "Python"}'
|
| 55 |
+
|
| 56 |
+
# Python Integration
|
| 57 |
+
import requests
|
| 58 |
+
response = requests.post(
|
| 59 |
+
"https://your-space.hf.space/search",
|
| 60 |
+
json={"query": "Python", "max_results": 5}
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# Claude Desktop Integration
|
| 64 |
+
# Add to MCP settings and use via chat
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
## Alternative: Gradio Approach
|
| 68 |
+
|
| 69 |
+
### What It Would Look Like
|
| 70 |
+
```
|
| 71 |
+
βββββββββββββββββββββββββββββββββββββββ
|
| 72 |
+
β Hugging Face Space β
|
| 73 |
+
β βββββββββββββββββββββββββββββββββ β
|
| 74 |
+
β β Gradio App β β
|
| 75 |
+
β β βββββββββββββββββββββββββββ β β
|
| 76 |
+
β β β Auto-generated UI: β β β
|
| 77 |
+
β β β [Search Query ] β β β
|
| 78 |
+
β β β [Max Results: 5 ] β β β
|
| 79 |
+
β β β [ Search Button ] β β β
|
| 80 |
+
β β β β β β
|
| 81 |
+
β β β Results: β β β
|
| 82 |
+
β β β { ... json ... } β β β
|
| 83 |
+
β β βββββββββββββββββββββββββββ β β
|
| 84 |
+
β βββββββββββββββββββββββββββββββββ β
|
| 85 |
+
βββββββββββββββββββββββββββββββββββββββ
|
| 86 |
+
β
|
| 87 |
+
Browser Access Only
|
| 88 |
+
β
|
| 89 |
+
ββββββββββββββββββββ
|
| 90 |
+
β Human Users β
|
| 91 |
+
β (via browser) β
|
| 92 |
+
ββββββββββββββββββββ
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### Gradio Code Example
|
| 96 |
+
```python
|
| 97 |
+
# app.py for Gradio
|
| 98 |
+
import gradio as gr
|
| 99 |
+
from server import search_youtube_videos
|
| 100 |
+
|
| 101 |
+
def search(query, max_results):
|
| 102 |
+
return search_youtube_videos(query, max_results)
|
| 103 |
+
|
| 104 |
+
demo = gr.Interface(
|
| 105 |
+
fn=search,
|
| 106 |
+
inputs=[
|
| 107 |
+
gr.Textbox(label="Search Query"),
|
| 108 |
+
gr.Slider(1, 10, value=5, label="Max Results")
|
| 109 |
+
],
|
| 110 |
+
outputs=gr.JSON()
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
demo.launch()
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### README.md for Gradio
|
| 117 |
+
```yaml
|
| 118 |
+
---
|
| 119 |
+
title: Basicsearch
|
| 120 |
+
sdk: gradio
|
| 121 |
+
sdk_version: 5.49.1
|
| 122 |
+
app_file: app.py
|
| 123 |
+
---
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
## Feature Comparison Matrix
|
| 127 |
+
|
| 128 |
+
| Feature | Docker (Your Choice) | Gradio Alternative |
|
| 129 |
+
|---------|---------------------|-------------------|
|
| 130 |
+
| **Primary Use Case** | API Service | Interactive Demo |
|
| 131 |
+
| **Target Users** | AI systems, apps | Humans via browser |
|
| 132 |
+
| **Setup Complexity** | Medium | Low |
|
| 133 |
+
| **Build Time** | 5-15 minutes | 2-3 minutes |
|
| 134 |
+
| **Deployment Method** | Dockerfile | Python script |
|
| 135 |
+
| | | |
|
| 136 |
+
| **Capabilities** | | |
|
| 137 |
+
| REST API | β
Full | β οΈ Limited |
|
| 138 |
+
| Custom Server | β
Yes | β No |
|
| 139 |
+
| MCP Protocol | β
Yes | β No |
|
| 140 |
+
| Visual UI | β οΈ Manual | β
Automatic |
|
| 141 |
+
| WebSocket Support | β
Yes | β οΈ Limited |
|
| 142 |
+
| Multiple Endpoints | β
Yes | β οΈ Limited |
|
| 143 |
+
| | | |
|
| 144 |
+
| **Integration** | | |
|
| 145 |
+
| cURL/HTTP Clients | β
Yes | β οΈ Limited |
|
| 146 |
+
| Python requests | β
Yes | β οΈ Via API |
|
| 147 |
+
| JavaScript fetch | β
Yes | β οΈ Via API |
|
| 148 |
+
| Claude Desktop | β
Yes | β No |
|
| 149 |
+
| Mobile Apps | β
Yes | β οΈ Browser only |
|
| 150 |
+
| | | |
|
| 151 |
+
| **Control & Flexibility** | | |
|
| 152 |
+
| Custom Dependencies | β
Full control | β οΈ Limited |
|
| 153 |
+
| Port Configuration | β
Yes | β οΈ Fixed |
|
| 154 |
+
| Environment Variables | β
Full access | β
Yes |
|
| 155 |
+
| Multi-container | β
Possible | β No |
|
| 156 |
+
| Background Tasks | β
Yes | β οΈ Limited |
|
| 157 |
+
| | | |
|
| 158 |
+
| **Development** | | |
|
| 159 |
+
| Local Testing | β
Docker/Direct | β
Easy |
|
| 160 |
+
| Debugging | β οΈ Moderate | β
Easy |
|
| 161 |
+
| Hot Reload | β οΈ Manual | β
Automatic |
|
| 162 |
+
| Custom Middleware | β
Yes | β No |
|
| 163 |
+
|
| 164 |
+
## Why Docker for Your MCP Server?
|
| 165 |
+
|
| 166 |
+
### β
Reasons We Chose Docker
|
| 167 |
+
|
| 168 |
+
1. **MCP Protocol Support**
|
| 169 |
+
- MCP servers need custom server implementations
|
| 170 |
+
- Gradio doesn't support STDIO protocol
|
| 171 |
+
- Need full control over server lifecycle
|
| 172 |
+
|
| 173 |
+
2. **API-First Design**
|
| 174 |
+
- Your service is consumed by AI assistants, not humans
|
| 175 |
+
- REST API is the primary interface
|
| 176 |
+
- Need multiple endpoints with different methods
|
| 177 |
+
|
| 178 |
+
3. **Production Requirements**
|
| 179 |
+
- Needs to be reliable and production-ready
|
| 180 |
+
- Full control over dependencies and environment
|
| 181 |
+
- Better error handling and logging
|
| 182 |
+
|
| 183 |
+
4. **Integration Flexibility**
|
| 184 |
+
- Can be called from any language/platform
|
| 185 |
+
- Works with Claude Desktop MCP settings
|
| 186 |
+
- Compatible with future integrations
|
| 187 |
+
|
| 188 |
+
5. **Scalability**
|
| 189 |
+
- Can add more endpoints easily
|
| 190 |
+
- Can add authentication/rate limiting
|
| 191 |
+
- Can integrate with databases or other services
|
| 192 |
+
|
| 193 |
+
### β Why Gradio Wasn't Suitable
|
| 194 |
+
|
| 195 |
+
1. **No API Focus**
|
| 196 |
+
- Gradio is UI-first, API-second
|
| 197 |
+
- Limited REST API capabilities
|
| 198 |
+
- Harder to integrate programmatically
|
| 199 |
+
|
| 200 |
+
2. **Limited Server Control**
|
| 201 |
+
- Can't customize server behavior
|
| 202 |
+
- Fixed port and route structure
|
| 203 |
+
- Limited middleware support
|
| 204 |
+
|
| 205 |
+
3. **Not Designed for Services**
|
| 206 |
+
- Designed for demos, not production services
|
| 207 |
+
- MCP protocol requires custom setup
|
| 208 |
+
- No native support for service-to-service communication
|
| 209 |
+
|
| 210 |
+
## Hybrid Approach (Best of Both Worlds!)
|
| 211 |
+
|
| 212 |
+
You can actually use **BOTH** Docker and Gradio:
|
| 213 |
+
|
| 214 |
+
### Option 1: Gradio as Testing UI
|
| 215 |
+
```dockerfile
|
| 216 |
+
# Dockerfile
|
| 217 |
+
FROM python:3.13-slim
|
| 218 |
+
|
| 219 |
+
# Install all dependencies
|
| 220 |
+
RUN pip install fastapi uvicorn gradio
|
| 221 |
+
|
| 222 |
+
# Copy both apps
|
| 223 |
+
COPY app.py ./ # FastAPI server
|
| 224 |
+
COPY gradio_ui.py ./ # Gradio UI
|
| 225 |
+
|
| 226 |
+
# Run both (using a process manager)
|
| 227 |
+
CMD ["python", "app.py"] # Main API
|
| 228 |
+
# Access Gradio separately for testing
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
### Option 2: Separate Spaces
|
| 232 |
+
```
|
| 233 |
+
Space 1 (Docker): API Service
|
| 234 |
+
βββ FastAPI REST API
|
| 235 |
+
βββ MCP Server
|
| 236 |
+
βββ Production endpoints
|
| 237 |
+
|
| 238 |
+
Space 2 (Gradio): Testing UI
|
| 239 |
+
βββ Gradio Interface
|
| 240 |
+
βββ Calls Space 1 API
|
| 241 |
+
βββ Human-friendly testing
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
### Option 3: Gradio + FastAPI in Same Docker
|
| 245 |
+
```python
|
| 246 |
+
# Combined approach in app.py
|
| 247 |
+
from fastapi import FastAPI
|
| 248 |
+
import gradio as gr
|
| 249 |
+
|
| 250 |
+
# FastAPI for APIs
|
| 251 |
+
app = FastAPI()
|
| 252 |
+
|
| 253 |
+
@app.get("/health")
|
| 254 |
+
def health():
|
| 255 |
+
return {"status": "ok"}
|
| 256 |
+
|
| 257 |
+
@app.post("/search")
|
| 258 |
+
def search(query: str):
|
| 259 |
+
return search_youtube_videos(query)
|
| 260 |
+
|
| 261 |
+
# Gradio for UI
|
| 262 |
+
with gr.Blocks() as gradio_app:
|
| 263 |
+
# Your UI here
|
| 264 |
+
pass
|
| 265 |
+
|
| 266 |
+
# Mount Gradio on FastAPI
|
| 267 |
+
app = gr.mount_gradio_app(app, gradio_app, path="/ui")
|
| 268 |
+
|
| 269 |
+
# Now you have:
|
| 270 |
+
# - API: https://your-space.hf.space/search
|
| 271 |
+
# - UI: https://your-space.hf.space/ui
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
## Migration: Docker β Gradio
|
| 275 |
+
|
| 276 |
+
### From Docker to Gradio (if you want simpler demo)
|
| 277 |
+
|
| 278 |
+
**Current (Docker):**
|
| 279 |
+
```yaml
|
| 280 |
+
# README.md
|
| 281 |
+
sdk: docker
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
**Change to:**
|
| 285 |
+
```yaml
|
| 286 |
+
# README.md
|
| 287 |
+
sdk: gradio
|
| 288 |
+
sdk_version: 5.49.1
|
| 289 |
+
app_file: gradio_ui.py
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
Then push. HF will rebuild automatically.
|
| 293 |
+
|
| 294 |
+
### From Gradio to Docker (if you need more power)
|
| 295 |
+
|
| 296 |
+
**Current:**
|
| 297 |
+
```yaml
|
| 298 |
+
sdk: gradio
|
| 299 |
+
app_file: app.py
|
| 300 |
+
```
|
| 301 |
+
|
| 302 |
+
**Change to:**
|
| 303 |
+
```yaml
|
| 304 |
+
sdk: docker
|
| 305 |
+
app_port: 7860
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
Add `Dockerfile`, push, and HF rebuilds.
|
| 309 |
+
|
| 310 |
+
## Performance Comparison
|
| 311 |
+
|
| 312 |
+
| Metric | Docker | Gradio |
|
| 313 |
+
|--------|--------|--------|
|
| 314 |
+
| Cold Start | 10-30s | 5-15s |
|
| 315 |
+
| Build Time | 5-15 min | 2-3 min |
|
| 316 |
+
| Memory Usage | ~500MB | ~300MB |
|
| 317 |
+
| Request Latency | <100ms | <100ms |
|
| 318 |
+
| Concurrent Users | High | Medium |
|
| 319 |
+
| API Throughput | High | Medium |
|
| 320 |
+
|
| 321 |
+
## Cost Considerations
|
| 322 |
+
|
| 323 |
+
Both are **FREE** on Hugging Face Spaces free tier:
|
| 324 |
+
- Docker: Same resources as Gradio
|
| 325 |
+
- Gradio: Same resources as Docker
|
| 326 |
+
- No difference in hosting cost
|
| 327 |
+
- Both support custom domains on Pro
|
| 328 |
+
|
| 329 |
+
## Real-World Examples
|
| 330 |
+
|
| 331 |
+
### Docker Deployments
|
| 332 |
+
```
|
| 333 |
+
β
API Services (your case)
|
| 334 |
+
β
Database-backed apps
|
| 335 |
+
β
Multi-service architectures
|
| 336 |
+
β
WebSocket servers
|
| 337 |
+
β
Custom protocols (MCP, gRPC)
|
| 338 |
+
β
Background job processors
|
| 339 |
+
```
|
| 340 |
+
|
| 341 |
+
### Gradio Deployments
|
| 342 |
+
```
|
| 343 |
+
β
ML model demos
|
| 344 |
+
β
Image classifiers
|
| 345 |
+
β
Text generators
|
| 346 |
+
β
Quick prototypes
|
| 347 |
+
β
Research showcases
|
| 348 |
+
β
Educational tools
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
## Testing Your Current Setup
|
| 352 |
+
|
| 353 |
+
Since you're using Docker, you can test both ways locally:
|
| 354 |
+
|
| 355 |
+
### Test 1: API Mode (Docker)
|
| 356 |
+
```bash
|
| 357 |
+
# Start your Docker container
|
| 358 |
+
docker build -t basicsearch .
|
| 359 |
+
docker run -p 7860:7860 basicsearch
|
| 360 |
+
|
| 361 |
+
# Test API
|
| 362 |
+
curl http://localhost:7860/health
|
| 363 |
+
```
|
| 364 |
+
|
| 365 |
+
### Test 2: Gradio UI (Optional)
|
| 366 |
+
```bash
|
| 367 |
+
# Run the Gradio UI we created
|
| 368 |
+
uv run python gradio_ui.py
|
| 369 |
+
|
| 370 |
+
# Visit http://localhost:7860 in browser
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
## Recommendation for Your Project
|
| 374 |
+
|
| 375 |
+
**Stick with Docker** because:
|
| 376 |
+
1. β
Your MCP server needs API access (primary requirement)
|
| 377 |
+
2. β
You need integration with Claude and other tools
|
| 378 |
+
3. β
Production-ready service is the goal
|
| 379 |
+
4. β
You may need to add features (auth, logging, etc.)
|
| 380 |
+
5. β
Better for long-term maintenance
|
| 381 |
+
|
| 382 |
+
**Optional:** Add Gradio UI later for:
|
| 383 |
+
- Human testing interface
|
| 384 |
+
- Demo purposes
|
| 385 |
+
- Visual debugging
|
| 386 |
+
- Client presentations
|
| 387 |
+
|
| 388 |
+
## Summary
|
| 389 |
+
|
| 390 |
+
| Aspect | Your Choice (Docker) | Alternative (Gradio) |
|
| 391 |
+
|--------|---------------------|---------------------|
|
| 392 |
+
| **What You Built** | API Service | Would be Demo UI |
|
| 393 |
+
| **Who Uses It** | AI Assistants | Humans in Browser |
|
| 394 |
+
| **How They Use It** | HTTP Requests | Click & Type |
|
| 395 |
+
| **Best For** | Production Services | Quick Demos |
|
| 396 |
+
| **Your Needs** | β
Perfect Match | β Wrong Tool |
|
| 397 |
+
|
| 398 |
+
## Next Steps
|
| 399 |
+
|
| 400 |
+
1. β
Continue with Docker deployment
|
| 401 |
+
2. β³ Wait for build to complete
|
| 402 |
+
3. π§ͺ Test with `uv run python test_deployment.py`
|
| 403 |
+
4. π (Optional) Try Gradio UI: `uv run python gradio_ui.py`
|
| 404 |
+
5. π Integrate with your AI workflows
|
| 405 |
+
|
| 406 |
+
---
|
| 407 |
+
|
| 408 |
+
**TL;DR:**
|
| 409 |
+
- **Gradio** = Easy UI for demos (humans clicking buttons)
|
| 410 |
+
- **Docker** = Powerful API for services (programs making requests)
|
| 411 |
+
- **Your choice (Docker)** = Correct for MCP server! β
|
| 412 |
+
|
PRIVATE_SPACE_GUIDE.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Private Space Testing Guide
|
| 2 |
+
|
| 3 |
+
## The 404 Problem: Is Your Space Private?
|
| 4 |
+
|
| 5 |
+
If you're getting 404 errors, your Space might be **PRIVATE**, which requires authentication to access.
|
| 6 |
+
|
| 7 |
+
## Check Your Space Visibility
|
| 8 |
+
|
| 9 |
+
The settings page should now be open in your browser. Look for:
|
| 10 |
+
|
| 11 |
+
### π Private Space
|
| 12 |
+
```
|
| 13 |
+
Visibility: Private
|
| 14 |
+
Only you and approved users can access this Space
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
### π Public Space
|
| 18 |
+
```
|
| 19 |
+
Visibility: Public
|
| 20 |
+
Anyone can access this Space
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
## Solution 1: Make Your Space Public (Recommended for APIs)
|
| 24 |
+
|
| 25 |
+
### Steps:
|
| 26 |
+
1. Go to: https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 27 |
+
2. Scroll to **"Visibility"** section
|
| 28 |
+
3. Click **"Make public"**
|
| 29 |
+
4. Confirm the change
|
| 30 |
+
|
| 31 |
+
### After Making Public:
|
| 32 |
+
```bash
|
| 33 |
+
# Wait 30 seconds, then test again
|
| 34 |
+
sleep 30
|
| 35 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 36 |
+
uv run python test_deployment.py
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### β
Pros:
|
| 40 |
+
- No authentication needed
|
| 41 |
+
- Easy to test
|
| 42 |
+
- Anyone can use your API
|
| 43 |
+
- Better for integrations
|
| 44 |
+
|
| 45 |
+
### β Cons:
|
| 46 |
+
- Anyone can see your code
|
| 47 |
+
- Anyone can use your API
|
| 48 |
+
- Public usage counts
|
| 49 |
+
|
| 50 |
+
## Solution 2: Keep Private + Use Authentication Token
|
| 51 |
+
|
| 52 |
+
If you want to keep the Space private, you'll need a Hugging Face token.
|
| 53 |
+
|
| 54 |
+
### Get Your Token:
|
| 55 |
+
1. Visit: https://huggingface.co/settings/tokens
|
| 56 |
+
2. Click **"New token"**
|
| 57 |
+
3. Name it: `basicsearch-testing`
|
| 58 |
+
4. Select permissions: **Read**
|
| 59 |
+
5. Click **"Generate"**
|
| 60 |
+
6. Copy the token (starts with `hf_...`)
|
| 61 |
+
|
| 62 |
+
### Test with Token:
|
| 63 |
+
```bash
|
| 64 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 65 |
+
|
| 66 |
+
# Replace YOUR_TOKEN with actual token
|
| 67 |
+
python test_private_space.py https://ocx2025-basicsearch.hf.space hf_YourTokenHere
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### Set as Environment Variable:
|
| 71 |
+
```bash
|
| 72 |
+
# Add to ~/.zshrc or ~/.bashrc
|
| 73 |
+
export HF_TOKEN="hf_YourTokenHere"
|
| 74 |
+
|
| 75 |
+
# Then you can just run:
|
| 76 |
+
python test_private_space.py
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### Update Test Script to Use Token:
|
| 80 |
+
```python
|
| 81 |
+
# test_deployment.py with auth
|
| 82 |
+
import os
|
| 83 |
+
import requests
|
| 84 |
+
|
| 85 |
+
token = os.getenv("HF_TOKEN")
|
| 86 |
+
headers = {}
|
| 87 |
+
if token:
|
| 88 |
+
headers["Authorization"] = f"Bearer {token}"
|
| 89 |
+
|
| 90 |
+
response = requests.get(
|
| 91 |
+
"https://ocx2025-basicsearch.hf.space/health",
|
| 92 |
+
headers=headers
|
| 93 |
+
)
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### β
Pros:
|
| 97 |
+
- Code stays private
|
| 98 |
+
- Control who can access
|
| 99 |
+
- Better security
|
| 100 |
+
|
| 101 |
+
### β Cons:
|
| 102 |
+
- Requires token management
|
| 103 |
+
- More complex testing
|
| 104 |
+
- Harder to share
|
| 105 |
+
|
| 106 |
+
## Test Right Now
|
| 107 |
+
|
| 108 |
+
### Quick Check:
|
| 109 |
+
```bash
|
| 110 |
+
# Test without auth (works if public)
|
| 111 |
+
curl https://ocx2025-basicsearch.hf.space/health
|
| 112 |
+
|
| 113 |
+
# Test with auth (works if private)
|
| 114 |
+
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
| 115 |
+
https://ocx2025-basicsearch.hf.space/health
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Expected Results:
|
| 119 |
+
|
| 120 |
+
#### If Space is Building:
|
| 121 |
+
```
|
| 122 |
+
404 - Page not found (with HF branding page)
|
| 123 |
+
```
|
| 124 |
+
**Action:** Wait 5-10 more minutes
|
| 125 |
+
|
| 126 |
+
#### If Space is Private:
|
| 127 |
+
```
|
| 128 |
+
404 - Page not found
|
| 129 |
+
```
|
| 130 |
+
**Action:** Make public OR use token
|
| 131 |
+
|
| 132 |
+
#### If Space is Public and Running:
|
| 133 |
+
```json
|
| 134 |
+
{"status": "ok"}
|
| 135 |
+
```
|
| 136 |
+
**Action:** Celebrate! π
|
| 137 |
+
|
| 138 |
+
## How to Use Private Space with Claude
|
| 139 |
+
|
| 140 |
+
If you keep it private, you'll need to configure Claude with your token:
|
| 141 |
+
|
| 142 |
+
### Claude Desktop Config:
|
| 143 |
+
```json
|
| 144 |
+
{
|
| 145 |
+
"mcpServers": {
|
| 146 |
+
"basicsearch": {
|
| 147 |
+
"url": "https://ocx2025-basicsearch.hf.space",
|
| 148 |
+
"headers": {
|
| 149 |
+
"Authorization": "Bearer hf_YourTokenHere"
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
## Docker App with Authentication
|
| 157 |
+
|
| 158 |
+
If you want your Docker app to handle authentication, update `app.py`:
|
| 159 |
+
|
| 160 |
+
```python
|
| 161 |
+
from fastapi import FastAPI, Header, HTTPException
|
| 162 |
+
|
| 163 |
+
app = FastAPI()
|
| 164 |
+
|
| 165 |
+
# Simple token check
|
| 166 |
+
API_TOKEN = os.getenv("API_TOKEN", "")
|
| 167 |
+
|
| 168 |
+
@app.get("/health")
|
| 169 |
+
async def health(authorization: str = Header(None)):
|
| 170 |
+
if API_TOKEN and authorization != f"Bearer {API_TOKEN}":
|
| 171 |
+
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 172 |
+
return {"status": "ok"}
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
## Comparison: Public vs Private
|
| 176 |
+
|
| 177 |
+
| Feature | Public | Private |
|
| 178 |
+
|---------|--------|---------|
|
| 179 |
+
| **Access** | Anyone | Token required |
|
| 180 |
+
| **Testing** | Easy | Needs token |
|
| 181 |
+
| **Security** | Low | High |
|
| 182 |
+
| **Sharing** | Easy | Controlled |
|
| 183 |
+
| **API Use** | Simple | Complex |
|
| 184 |
+
| **Best For** | Demos, Open APIs | Internal tools |
|
| 185 |
+
|
| 186 |
+
## Recommendation for Your MCP Server
|
| 187 |
+
|
| 188 |
+
### Choose Public If:
|
| 189 |
+
- β
You want easy testing
|
| 190 |
+
- β
You plan to share with others
|
| 191 |
+
- β
Your API is meant to be public
|
| 192 |
+
- β
No sensitive data involved
|
| 193 |
+
- β
You want Claude integration to be simple
|
| 194 |
+
|
| 195 |
+
### Choose Private If:
|
| 196 |
+
- β
You have sensitive code
|
| 197 |
+
- β
You want to control access
|
| 198 |
+
- β
You're in development phase
|
| 199 |
+
- β
You need usage tracking
|
| 200 |
+
- β
You have private API keys (like YouTube API)
|
| 201 |
+
|
| 202 |
+
## Current Status Check
|
| 203 |
+
|
| 204 |
+
Run this to determine what's happening:
|
| 205 |
+
|
| 206 |
+
```bash
|
| 207 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 208 |
+
|
| 209 |
+
# Check 1: Is it accessible at all?
|
| 210 |
+
curl -I https://ocx2025-basicsearch.hf.space/health
|
| 211 |
+
|
| 212 |
+
# Check 2: What's the space status?
|
| 213 |
+
open https://huggingface.co/spaces/ocx2025/basicsearch
|
| 214 |
+
|
| 215 |
+
# Check 3: What are the settings?
|
| 216 |
+
open https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
Look for:
|
| 220 |
+
1. **"Building"** β Wait and try again
|
| 221 |
+
2. **"Running" + Private** β Make public or use token
|
| 222 |
+
3. **"Running" + Public** β Should work!
|
| 223 |
+
4. **"Error"** β Check logs
|
| 224 |
+
|
| 225 |
+
## Quick Fix Checklist
|
| 226 |
+
|
| 227 |
+
- [ ] Visit: https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 228 |
+
- [ ] Check visibility: Public or Private?
|
| 229 |
+
- [ ] If Private: Click "Make public"
|
| 230 |
+
- [ ] If keeping Private: Get HF token from https://huggingface.co/settings/tokens
|
| 231 |
+
- [ ] Wait 30 seconds after change
|
| 232 |
+
- [ ] Run: `uv run python test_deployment.py`
|
| 233 |
+
- [ ] Should see: `β PASSED` messages!
|
| 234 |
+
|
| 235 |
+
## Troubleshooting Matrix
|
| 236 |
+
|
| 237 |
+
| Status | Visibility | Result | Action |
|
| 238 |
+
|--------|-----------|--------|--------|
|
| 239 |
+
| Building | Any | 404 | Wait |
|
| 240 |
+
| Running | Public | 200 OK | β
Success |
|
| 241 |
+
| Running | Public | 404 | Check logs |
|
| 242 |
+
| Running | Private | 404 | Use token or make public |
|
| 243 |
+
| Running | Private | 401 | Invalid token |
|
| 244 |
+
| Error | Any | 404 | Check build logs |
|
| 245 |
+
|
| 246 |
+
## After Making Public
|
| 247 |
+
|
| 248 |
+
Once you make it public, test immediately:
|
| 249 |
+
|
| 250 |
+
```bash
|
| 251 |
+
# Should work now!
|
| 252 |
+
curl https://ocx2025-basicsearch.hf.space/health
|
| 253 |
+
|
| 254 |
+
# Full test
|
| 255 |
+
uv run python test_deployment.py
|
| 256 |
+
|
| 257 |
+
# If it works, you'll see:
|
| 258 |
+
# β PASSED - Health Check
|
| 259 |
+
# β PASSED - Service Info
|
| 260 |
+
# ... etc
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
## Security Note
|
| 264 |
+
|
| 265 |
+
If you make the Space public, your code will be visible. Make sure:
|
| 266 |
+
- β
No API keys in code (use Hugging Face secrets)
|
| 267 |
+
- β
No passwords or tokens committed
|
| 268 |
+
- β
Your `.gitignore` includes `.env` files
|
| 269 |
+
- β
Secrets are set in Space settings, not code
|
| 270 |
+
|
| 271 |
+
Your `YOUTUBE_API_KEY` is safe because it's in Space secrets, not code! β
|
| 272 |
+
|
| 273 |
+
## Summary
|
| 274 |
+
|
| 275 |
+
**Most likely issue:** Your Space is **private** and needs authentication.
|
| 276 |
+
|
| 277 |
+
**Quick fix:**
|
| 278 |
+
1. Open settings (should be open now)
|
| 279 |
+
2. Click "Make public"
|
| 280 |
+
3. Wait 30 seconds
|
| 281 |
+
4. Run test again
|
| 282 |
+
5. Profit! π
|
| 283 |
+
|
STATUS_CHECK.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Space Status Check
|
| 2 |
+
|
| 3 |
+
## Current Status: Building or Configuration Needed
|
| 4 |
+
|
| 5 |
+
Your test shows **404 errors**, which means one of the following:
|
| 6 |
+
|
| 7 |
+
### 1. Space is Still Building β³
|
| 8 |
+
Docker builds can take 5-15 minutes.
|
| 9 |
+
|
| 10 |
+
**Check build status:**
|
| 11 |
+
1. Visit: https://huggingface.co/spaces/ocx2025/basicsearch
|
| 12 |
+
2. Look at the top of the page for:
|
| 13 |
+
- β³ "Building..." - Wait a few more minutes
|
| 14 |
+
- β
"Running" - Space is live (404 might be a different issue)
|
| 15 |
+
- β "Build Failed" - Check logs
|
| 16 |
+
|
| 17 |
+
### 2. Check Build Logs π
|
| 18 |
+
|
| 19 |
+
Visit: https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 20 |
+
|
| 21 |
+
Look for errors like:
|
| 22 |
+
- Python package installation failures
|
| 23 |
+
- Missing dependencies
|
| 24 |
+
- Docker build errors
|
| 25 |
+
- Port binding issues
|
| 26 |
+
|
| 27 |
+
### 3. Common Issues & Solutions
|
| 28 |
+
|
| 29 |
+
#### Issue: Build Timeout
|
| 30 |
+
**Symptoms:** Build stops after 10 minutes
|
| 31 |
+
**Solution:**
|
| 32 |
+
- Optimize Dockerfile
|
| 33 |
+
- Use smaller base image
|
| 34 |
+
- Pre-build dependencies
|
| 35 |
+
|
| 36 |
+
#### Issue: Port Not Exposed
|
| 37 |
+
**Symptoms:** Build succeeds but 404 errors
|
| 38 |
+
**Solution:** Check that app.py is running on port 7860
|
| 39 |
+
|
| 40 |
+
#### Issue: Missing API Key
|
| 41 |
+
**Symptoms:** Build succeeds, endpoints work but search fails
|
| 42 |
+
**Solution:** Set `YOUTUBE_API_KEY` in Space secrets
|
| 43 |
+
|
| 44 |
+
#### Issue: Wrong Entry Point
|
| 45 |
+
**Symptoms:** Container starts but doesn't respond
|
| 46 |
+
**Solution:** Verify Dockerfile CMD is correct:
|
| 47 |
+
```dockerfile
|
| 48 |
+
CMD ["python", "app.py"]
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Quick Diagnostic Steps
|
| 52 |
+
|
| 53 |
+
### Step 1: Check Space Page
|
| 54 |
+
```bash
|
| 55 |
+
open https://huggingface.co/spaces/ocx2025/basicsearch
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### Step 2: Check Build Logs
|
| 59 |
+
```bash
|
| 60 |
+
open https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Step 3: Wait and Retry
|
| 64 |
+
```bash
|
| 65 |
+
# Wait 2 minutes, then test again
|
| 66 |
+
sleep 120
|
| 67 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 68 |
+
uv run python test_deployment.py
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### Step 4: Manual Test
|
| 72 |
+
```bash
|
| 73 |
+
# Test with curl
|
| 74 |
+
curl -v https://ocx2025-basicsearch.hf.space/health
|
| 75 |
+
|
| 76 |
+
# Look for:
|
| 77 |
+
# - Connection refused = Space not started
|
| 78 |
+
# - 404 = Space running but wrong route
|
| 79 |
+
# - 200 = Success!
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## What to Look for in Logs
|
| 83 |
+
|
| 84 |
+
### β
Good Signs
|
| 85 |
+
```
|
| 86 |
+
Installing dependencies...
|
| 87 |
+
Successfully installed...
|
| 88 |
+
Server running on 0.0.0.0:7860
|
| 89 |
+
Application startup complete
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### β Bad Signs
|
| 93 |
+
```
|
| 94 |
+
Error: Could not find...
|
| 95 |
+
ModuleNotFoundError...
|
| 96 |
+
Permission denied...
|
| 97 |
+
Port already in use...
|
| 98 |
+
Build timeout...
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Next Actions
|
| 102 |
+
|
| 103 |
+
### If Still Building:
|
| 104 |
+
β° **Wait 5-10 more minutes**, then run:
|
| 105 |
+
```bash
|
| 106 |
+
uv run python test_deployment.py
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### If Build Failed:
|
| 110 |
+
1. Check logs for specific error
|
| 111 |
+
2. Fix the issue locally
|
| 112 |
+
3. Commit and push:
|
| 113 |
+
```bash
|
| 114 |
+
git add .
|
| 115 |
+
git commit -m "Fix: [describe fix]"
|
| 116 |
+
git push origin main
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
### If Running but 404:
|
| 120 |
+
Check if the Dockerfile is correct:
|
| 121 |
+
```bash
|
| 122 |
+
cat Dockerfile
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
Should end with:
|
| 126 |
+
```dockerfile
|
| 127 |
+
CMD ["python", "app.py"]
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### If Need Help Debugging:
|
| 131 |
+
1. Copy build logs
|
| 132 |
+
2. Check TESTING_GUIDE.md
|
| 133 |
+
3. Verify all files are pushed:
|
| 134 |
+
```bash
|
| 135 |
+
git status
|
| 136 |
+
git log -1
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
## Monitoring Script
|
| 140 |
+
|
| 141 |
+
Run this to continuously monitor your Space:
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
#!/bin/bash
|
| 145 |
+
while true; do
|
| 146 |
+
clear
|
| 147 |
+
echo "Checking Space Status at $(date)"
|
| 148 |
+
echo "=================================="
|
| 149 |
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://ocx2025-basicsearch.hf.space/health)
|
| 150 |
+
|
| 151 |
+
if [ "$STATUS" = "200" ]; then
|
| 152 |
+
echo "β
Space is LIVE!"
|
| 153 |
+
exit 0
|
| 154 |
+
elif [ "$STATUS" = "404" ]; then
|
| 155 |
+
echo "β³ Still building or config needed (404)"
|
| 156 |
+
else
|
| 157 |
+
echo "β Unexpected status: $STATUS"
|
| 158 |
+
fi
|
| 159 |
+
|
| 160 |
+
echo "Checking again in 30 seconds..."
|
| 161 |
+
sleep 30
|
| 162 |
+
done
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
Save as `monitor.sh`, make executable, and run:
|
| 166 |
+
```bash
|
| 167 |
+
chmod +x monitor.sh
|
| 168 |
+
./monitor.sh
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
## Expected Timeline
|
| 172 |
+
|
| 173 |
+
| Time | Status |
|
| 174 |
+
|------|--------|
|
| 175 |
+
| 0-2 min | Initializing build |
|
| 176 |
+
| 2-5 min | Installing dependencies |
|
| 177 |
+
| 5-8 min | Building Docker image |
|
| 178 |
+
| 8-10 min | Starting container |
|
| 179 |
+
| 10+ min | Should be live (if not, check logs) |
|
| 180 |
+
|
| 181 |
+
## When Space is Live
|
| 182 |
+
|
| 183 |
+
You'll see:
|
| 184 |
+
```bash
|
| 185 |
+
$ curl https://ocx2025-basicsearch.hf.space/health
|
| 186 |
+
{"status":"ok"}
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
Then run full tests:
|
| 190 |
+
```bash
|
| 191 |
+
uv run python test_deployment.py
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## Resources
|
| 195 |
+
|
| 196 |
+
- **Your Space:** https://huggingface.co/spaces/ocx2025/basicsearch
|
| 197 |
+
- **Logs:** https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 198 |
+
- **Settings:** https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 199 |
+
- **HF Status:** https://status.huggingface.co/
|
| 200 |
+
|
| 201 |
+
## TL;DR - What to Do Now
|
| 202 |
+
|
| 203 |
+
1. Visit https://huggingface.co/spaces/ocx2025/basicsearch
|
| 204 |
+
2. Check if it says "Building" at the top
|
| 205 |
+
3. If yes: β Take a coffee break (5-10 minutes)
|
| 206 |
+
4. If no: Check logs for errors
|
| 207 |
+
5. After waiting, run: `uv run python test_deployment.py`
|
| 208 |
+
|
TESTING_GUIDE.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Testing Guide for Deployed Space
|
| 2 |
+
|
| 3 |
+
## Quick Status Check
|
| 4 |
+
|
| 5 |
+
Your Space is deployed at: **https://huggingface.co/spaces/ocx2025/basicsearch**
|
| 6 |
+
|
| 7 |
+
### Check Build Status
|
| 8 |
+
|
| 9 |
+
1. Visit https://huggingface.co/spaces/ocx2025/basicsearch
|
| 10 |
+
2. Look for the build status indicator at the top
|
| 11 |
+
3. If building, you'll see "Building..." with logs
|
| 12 |
+
4. If running, you'll see "Running" status
|
| 13 |
+
|
| 14 |
+
### View Build Logs
|
| 15 |
+
|
| 16 |
+
1. Go to https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 17 |
+
2. Check for any errors during Docker build
|
| 18 |
+
3. Common issues:
|
| 19 |
+
- Missing dependencies
|
| 20 |
+
- Python version mismatch
|
| 21 |
+
- Docker build timeout
|
| 22 |
+
|
| 23 |
+
## Testing Methods
|
| 24 |
+
|
| 25 |
+
### Method 1: Automated Test Scripts (Recommended)
|
| 26 |
+
|
| 27 |
+
#### Using Python Script
|
| 28 |
+
```bash
|
| 29 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 30 |
+
|
| 31 |
+
# Run all tests
|
| 32 |
+
python test_deployment.py
|
| 33 |
+
|
| 34 |
+
# Or test a custom URL
|
| 35 |
+
python test_deployment.py https://your-space-url.hf.space
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
#### Using Bash Script
|
| 39 |
+
```bash
|
| 40 |
+
cd /Users/marjorie/Documents/GitHub/xctopus/mcp2/basicsearch
|
| 41 |
+
|
| 42 |
+
# Run all tests
|
| 43 |
+
./test_deployment.sh
|
| 44 |
+
|
| 45 |
+
# Or test a custom URL
|
| 46 |
+
./test_deployment.sh https://your-space-url.hf.space
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
The scripts will test:
|
| 50 |
+
- β
Health check endpoint
|
| 51 |
+
- β
Service info endpoint
|
| 52 |
+
- β
Tools listing
|
| 53 |
+
- β
YouTube search functionality
|
| 54 |
+
- β
Error handling
|
| 55 |
+
|
| 56 |
+
### Method 2: Manual cURL Commands
|
| 57 |
+
|
| 58 |
+
#### 1. Health Check
|
| 59 |
+
```bash
|
| 60 |
+
curl https://ocx2025-basicsearch.hf.space/health
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
**Expected Response:**
|
| 64 |
+
```json
|
| 65 |
+
{"status": "ok"}
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
#### 2. Service Information
|
| 69 |
+
```bash
|
| 70 |
+
curl https://ocx2025-basicsearch.hf.space/
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
**Expected Response:**
|
| 74 |
+
```json
|
| 75 |
+
{
|
| 76 |
+
"status": "healthy",
|
| 77 |
+
"service": "basicsearch-mcp-server",
|
| 78 |
+
"tools": ["search_youtube_videos"]
|
| 79 |
+
}
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
#### 3. List Available Tools
|
| 83 |
+
```bash
|
| 84 |
+
curl https://ocx2025-basicsearch.hf.space/tools
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
**Expected Response:**
|
| 88 |
+
```json
|
| 89 |
+
{
|
| 90 |
+
"tools": [
|
| 91 |
+
{
|
| 92 |
+
"name": "search_youtube_videos",
|
| 93 |
+
"description": "Searches YouTube for videos based on a query.",
|
| 94 |
+
"parameters": {
|
| 95 |
+
"query": "string (required)",
|
| 96 |
+
"max_results": "integer (optional, default: 5)"
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
#### 4. Search YouTube Videos
|
| 104 |
+
```bash
|
| 105 |
+
curl -X POST https://ocx2025-basicsearch.hf.space/search \
|
| 106 |
+
-H "Content-Type: application/json" \
|
| 107 |
+
-d '{
|
| 108 |
+
"query": "Python programming tutorial",
|
| 109 |
+
"max_results": 3
|
| 110 |
+
}'
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**Expected Response (if API key is set):**
|
| 114 |
+
```json
|
| 115 |
+
{
|
| 116 |
+
"results": [
|
| 117 |
+
{
|
| 118 |
+
"title": "Python Tutorial for Beginners",
|
| 119 |
+
"video_id": "abc123xyz",
|
| 120 |
+
"description": "Learn Python programming..."
|
| 121 |
+
},
|
| 122 |
+
...
|
| 123 |
+
]
|
| 124 |
+
}
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
**Expected Error (if API key NOT set):**
|
| 128 |
+
```json
|
| 129 |
+
{
|
| 130 |
+
"error": "YOUTUBE_API_KEY environment variable is not set"
|
| 131 |
+
}
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
### Method 3: Using Python `requests` Library
|
| 135 |
+
|
| 136 |
+
```python
|
| 137 |
+
import requests
|
| 138 |
+
import json
|
| 139 |
+
|
| 140 |
+
BASE_URL = "https://ocx2025-basicsearch.hf.space"
|
| 141 |
+
|
| 142 |
+
# Test health
|
| 143 |
+
response = requests.get(f"{BASE_URL}/health")
|
| 144 |
+
print(f"Health: {response.json()}")
|
| 145 |
+
|
| 146 |
+
# Test search
|
| 147 |
+
payload = {"query": "Python", "max_results": 2}
|
| 148 |
+
response = requests.post(f"{BASE_URL}/search", json=payload)
|
| 149 |
+
print(f"Search Results: {json.dumps(response.json(), indent=2)}")
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### Method 4: Using Browser
|
| 153 |
+
|
| 154 |
+
Simply visit these URLs in your browser:
|
| 155 |
+
|
| 156 |
+
1. **Health Check:** https://ocx2025-basicsearch.hf.space/health
|
| 157 |
+
2. **Service Info:** https://ocx2025-basicsearch.hf.space/
|
| 158 |
+
3. **Tools List:** https://ocx2025-basicsearch.hf.space/tools
|
| 159 |
+
|
| 160 |
+
For POST requests, use a browser extension like:
|
| 161 |
+
- [Postman](https://www.postman.com/)
|
| 162 |
+
- [Thunder Client](https://www.thunderclient.com/) (VS Code extension)
|
| 163 |
+
- [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) (VS Code extension)
|
| 164 |
+
|
| 165 |
+
## Troubleshooting
|
| 166 |
+
|
| 167 |
+
### Space Returns 404
|
| 168 |
+
**Cause:** Space is still building or failed to start
|
| 169 |
+
**Solution:**
|
| 170 |
+
1. Check build logs at https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 171 |
+
2. Wait for build to complete (can take 5-10 minutes)
|
| 172 |
+
3. Look for error messages in logs
|
| 173 |
+
|
| 174 |
+
### API Key Error
|
| 175 |
+
**Cause:** `YOUTUBE_API_KEY` not set or invalid
|
| 176 |
+
**Solution:**
|
| 177 |
+
1. Go to https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 178 |
+
2. Click "Repository secrets"
|
| 179 |
+
3. Add secret: Name=`YOUTUBE_API_KEY`, Value=`your_actual_key`
|
| 180 |
+
4. Restart the Space
|
| 181 |
+
|
| 182 |
+
### Build Timeout
|
| 183 |
+
**Cause:** Docker build taking too long
|
| 184 |
+
**Solution:**
|
| 185 |
+
1. Check Dockerfile for optimization opportunities
|
| 186 |
+
2. Verify all dependencies are necessary
|
| 187 |
+
3. Consider using a lighter base image
|
| 188 |
+
|
| 189 |
+
### Connection Timeout
|
| 190 |
+
**Cause:** Space is sleeping (free tier) or overloaded
|
| 191 |
+
**Solution:**
|
| 192 |
+
1. Visit the Space page to wake it up
|
| 193 |
+
2. Wait a few seconds and retry
|
| 194 |
+
3. Check Space status
|
| 195 |
+
|
| 196 |
+
### Rate Limiting
|
| 197 |
+
**Cause:** Too many requests to YouTube API
|
| 198 |
+
**Solution:**
|
| 199 |
+
1. Check your Google Cloud Console quota
|
| 200 |
+
2. Default is 10,000 units/day
|
| 201 |
+
3. Each search costs ~100 units
|
| 202 |
+
|
| 203 |
+
## Monitoring
|
| 204 |
+
|
| 205 |
+
### Real-time Logs
|
| 206 |
+
```bash
|
| 207 |
+
# Using curl to monitor health
|
| 208 |
+
watch -n 5 'curl -s https://ocx2025-basicsearch.hf.space/health'
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
### Check Space Status
|
| 212 |
+
```bash
|
| 213 |
+
# Simple status check
|
| 214 |
+
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" \
|
| 215 |
+
https://ocx2025-basicsearch.hf.space/health
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
### Continuous Testing
|
| 219 |
+
```bash
|
| 220 |
+
# Run tests every minute
|
| 221 |
+
while true; do
|
| 222 |
+
echo "Testing at $(date)"
|
| 223 |
+
python test_deployment.py
|
| 224 |
+
sleep 60
|
| 225 |
+
done
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
## Performance Testing
|
| 229 |
+
|
| 230 |
+
### Load Test with Apache Bench
|
| 231 |
+
```bash
|
| 232 |
+
# Test 100 requests with 10 concurrent connections
|
| 233 |
+
ab -n 100 -c 10 https://ocx2025-basicsearch.hf.space/health
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
### Load Test with Python
|
| 237 |
+
```python
|
| 238 |
+
import time
|
| 239 |
+
import requests
|
| 240 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 241 |
+
|
| 242 |
+
def test_endpoint():
|
| 243 |
+
start = time.time()
|
| 244 |
+
response = requests.get("https://ocx2025-basicsearch.hf.space/health")
|
| 245 |
+
end = time.time()
|
| 246 |
+
return response.status_code, end - start
|
| 247 |
+
|
| 248 |
+
# Run 50 concurrent requests
|
| 249 |
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
| 250 |
+
results = list(executor.map(lambda _: test_endpoint(), range(50)))
|
| 251 |
+
|
| 252 |
+
successful = sum(1 for status, _ in results if status == 200)
|
| 253 |
+
avg_time = sum(t for _, t in results) / len(results)
|
| 254 |
+
|
| 255 |
+
print(f"Successful: {successful}/50")
|
| 256 |
+
print(f"Average response time: {avg_time:.3f}s")
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
## Expected Response Times
|
| 260 |
+
|
| 261 |
+
| Endpoint | Expected Time |
|
| 262 |
+
|----------|--------------|
|
| 263 |
+
| `/health` | < 100ms |
|
| 264 |
+
| `/` | < 100ms |
|
| 265 |
+
| `/tools` | < 100ms |
|
| 266 |
+
| `/search` | 500ms - 2s (depends on YouTube API) |
|
| 267 |
+
|
| 268 |
+
## Security Testing
|
| 269 |
+
|
| 270 |
+
### Test Invalid Input
|
| 271 |
+
```bash
|
| 272 |
+
# Test with missing query
|
| 273 |
+
curl -X POST https://ocx2025-basicsearch.hf.space/search \
|
| 274 |
+
-H "Content-Type: application/json" \
|
| 275 |
+
-d '{}'
|
| 276 |
+
|
| 277 |
+
# Test with invalid JSON
|
| 278 |
+
curl -X POST https://ocx2025-basicsearch.hf.space/search \
|
| 279 |
+
-H "Content-Type: application/json" \
|
| 280 |
+
-d 'invalid json'
|
| 281 |
+
|
| 282 |
+
# Test with excessive max_results
|
| 283 |
+
curl -X POST https://ocx2025-basicsearch.hf.space/search \
|
| 284 |
+
-H "Content-Type: application/json" \
|
| 285 |
+
-d '{"query": "test", "max_results": 1000}'
|
| 286 |
+
```
|
| 287 |
+
|
| 288 |
+
## Integration Testing
|
| 289 |
+
|
| 290 |
+
### Test in Claude Desktop
|
| 291 |
+
|
| 292 |
+
If using with Claude Desktop, add to your MCP settings:
|
| 293 |
+
|
| 294 |
+
```json
|
| 295 |
+
{
|
| 296 |
+
"mcpServers": {
|
| 297 |
+
"basicsearch": {
|
| 298 |
+
"url": "https://ocx2025-basicsearch.hf.space"
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
### Test with Python SDK
|
| 305 |
+
|
| 306 |
+
```python
|
| 307 |
+
from mcp import ClientSession
|
| 308 |
+
from mcp.client.stdio import stdio_client
|
| 309 |
+
|
| 310 |
+
# Connect to your MCP server
|
| 311 |
+
async with stdio_client() as (read, write):
|
| 312 |
+
async with ClientSession(read, write) as session:
|
| 313 |
+
# Initialize
|
| 314 |
+
await session.initialize()
|
| 315 |
+
|
| 316 |
+
# Call search tool
|
| 317 |
+
result = await session.call_tool(
|
| 318 |
+
"search_youtube_videos",
|
| 319 |
+
arguments={"query": "Python", "max_results": 3}
|
| 320 |
+
)
|
| 321 |
+
print(result)
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
## Next Steps
|
| 325 |
+
|
| 326 |
+
1. β
Run automated test script: `python test_deployment.py`
|
| 327 |
+
2. β
Check build logs if tests fail
|
| 328 |
+
3. β
Set YOUTUBE_API_KEY if not already set
|
| 329 |
+
4. β
Monitor Space status
|
| 330 |
+
5. β
Test all endpoints manually
|
| 331 |
+
6. β
Perform load testing if needed
|
| 332 |
+
7. β
Integrate with your applications
|
| 333 |
+
|
| 334 |
+
## Useful Links
|
| 335 |
+
|
| 336 |
+
- **Your Space:** https://huggingface.co/spaces/ocx2025/basicsearch
|
| 337 |
+
- **Logs:** https://huggingface.co/spaces/ocx2025/basicsearch/logs
|
| 338 |
+
- **Settings:** https://huggingface.co/spaces/ocx2025/basicsearch/settings
|
| 339 |
+
- **HF Spaces Docs:** https://huggingface.co/docs/hub/spaces
|
| 340 |
+
- **YouTube API Console:** https://console.cloud.google.com/apis/dashboard
|
| 341 |
+
|
__pycache__/server.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/server.cpython-313.pyc and b/__pycache__/server.cpython-313.pyc differ
|
|
|
gradio_ui.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Optional Gradio UI for the MCP server.
|
| 3 |
+
This can run alongside the FastAPI server for visual testing.
|
| 4 |
+
"""
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from server import search_youtube_videos
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
def search_interface(query: str, max_results: int = 5):
|
| 10 |
+
"""Gradio interface for YouTube search"""
|
| 11 |
+
if not query:
|
| 12 |
+
return {"error": "Please enter a search query"}
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
results = search_youtube_videos(query, int(max_results))
|
| 16 |
+
return {
|
| 17 |
+
"success": True,
|
| 18 |
+
"count": len(results),
|
| 19 |
+
"results": results
|
| 20 |
+
}
|
| 21 |
+
except Exception as e:
|
| 22 |
+
return {
|
| 23 |
+
"success": False,
|
| 24 |
+
"error": str(e)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
# Create Gradio interface
|
| 28 |
+
with gr.Blocks(title="YouTube Search MCP Server") as demo:
|
| 29 |
+
gr.Markdown("# π YouTube Search MCP Server")
|
| 30 |
+
gr.Markdown("Test the YouTube search functionality with a visual interface.")
|
| 31 |
+
|
| 32 |
+
with gr.Row():
|
| 33 |
+
with gr.Column():
|
| 34 |
+
query_input = gr.Textbox(
|
| 35 |
+
label="Search Query",
|
| 36 |
+
placeholder="Enter search term (e.g., 'Python tutorial')",
|
| 37 |
+
lines=1
|
| 38 |
+
)
|
| 39 |
+
max_results = gr.Slider(
|
| 40 |
+
minimum=1,
|
| 41 |
+
maximum=10,
|
| 42 |
+
value=5,
|
| 43 |
+
step=1,
|
| 44 |
+
label="Maximum Results"
|
| 45 |
+
)
|
| 46 |
+
search_btn = gr.Button("π Search", variant="primary")
|
| 47 |
+
|
| 48 |
+
with gr.Column():
|
| 49 |
+
output = gr.JSON(label="Search Results")
|
| 50 |
+
|
| 51 |
+
# Examples
|
| 52 |
+
gr.Examples(
|
| 53 |
+
examples=[
|
| 54 |
+
["Python programming tutorial", 5],
|
| 55 |
+
["Machine learning basics", 3],
|
| 56 |
+
["FastAPI tutorial", 5],
|
| 57 |
+
],
|
| 58 |
+
inputs=[query_input, max_results],
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# Connect button
|
| 62 |
+
search_btn.click(
|
| 63 |
+
fn=search_interface,
|
| 64 |
+
inputs=[query_input, max_results],
|
| 65 |
+
outputs=output
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
| 70 |
+
|
pyproject.toml
CHANGED
|
@@ -8,6 +8,7 @@ dependencies = [
|
|
| 8 |
"fastapi>=0.115.0",
|
| 9 |
"fastmcp>=2.12.4",
|
| 10 |
"google-api-python-client>=2.184.0",
|
|
|
|
| 11 |
"mcp[cli]>=1.16.0",
|
| 12 |
"python-dotenv>=1.1.1",
|
| 13 |
"requests>=2.32.0",
|
|
|
|
| 8 |
"fastapi>=0.115.0",
|
| 9 |
"fastmcp>=2.12.4",
|
| 10 |
"google-api-python-client>=2.184.0",
|
| 11 |
+
"gradio>=5.49.1",
|
| 12 |
"mcp[cli]>=1.16.0",
|
| 13 |
"python-dotenv>=1.1.1",
|
| 14 |
"requests>=2.32.0",
|
test_deployment.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Python test script for Hugging Face Space deployment
|
| 4 |
+
Usage: python test_deployment.py [space-url]
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import requests
|
| 9 |
+
import json
|
| 10 |
+
from typing import Dict, Any
|
| 11 |
+
|
| 12 |
+
# ANSI color codes
|
| 13 |
+
GREEN = '\033[0;32m'
|
| 14 |
+
RED = '\033[0;31m'
|
| 15 |
+
YELLOW = '\033[1;33m'
|
| 16 |
+
BLUE = '\033[0;34m'
|
| 17 |
+
NC = '\033[0m' # No Color
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def print_test(test_name: str):
|
| 21 |
+
"""Print test header"""
|
| 22 |
+
print(f"\n{YELLOW}{test_name}{NC}")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def print_result(passed: bool, message: str, response: Any = None):
|
| 26 |
+
"""Print test result"""
|
| 27 |
+
status = f"{GREEN}β PASSED{NC}" if passed else f"{RED}β FAILED{NC}"
|
| 28 |
+
print(f"{status} - {message}")
|
| 29 |
+
if response:
|
| 30 |
+
try:
|
| 31 |
+
print(f"Response: {json.dumps(response, indent=2)}")
|
| 32 |
+
except:
|
| 33 |
+
print(f"Response: {response}")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_health(base_url: str) -> bool:
|
| 37 |
+
"""Test health endpoint"""
|
| 38 |
+
print_test("Test 1: Health Check")
|
| 39 |
+
print(f"GET {base_url}/health")
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
response = requests.get(f"{base_url}/health", timeout=10)
|
| 43 |
+
passed = response.status_code == 200
|
| 44 |
+
print_result(
|
| 45 |
+
passed,
|
| 46 |
+
f"Status: {response.status_code}",
|
| 47 |
+
response.json() if passed else response.text
|
| 48 |
+
)
|
| 49 |
+
return passed
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print_result(False, f"Error: {str(e)}")
|
| 52 |
+
return False
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def test_service_info(base_url: str) -> bool:
|
| 56 |
+
"""Test root endpoint"""
|
| 57 |
+
print_test("Test 2: Service Info")
|
| 58 |
+
print(f"GET {base_url}/")
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
response = requests.get(f"{base_url}/", timeout=10)
|
| 62 |
+
passed = response.status_code == 200
|
| 63 |
+
print_result(
|
| 64 |
+
passed,
|
| 65 |
+
f"Status: {response.status_code}",
|
| 66 |
+
response.json() if passed else response.text
|
| 67 |
+
)
|
| 68 |
+
return passed
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print_result(False, f"Error: {str(e)}")
|
| 71 |
+
return False
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_list_tools(base_url: str) -> bool:
|
| 75 |
+
"""Test tools listing endpoint"""
|
| 76 |
+
print_test("Test 3: List Available Tools")
|
| 77 |
+
print(f"GET {base_url}/tools")
|
| 78 |
+
|
| 79 |
+
try:
|
| 80 |
+
response = requests.get(f"{base_url}/tools", timeout=10)
|
| 81 |
+
passed = response.status_code == 200
|
| 82 |
+
print_result(
|
| 83 |
+
passed,
|
| 84 |
+
f"Status: {response.status_code}",
|
| 85 |
+
response.json() if passed else response.text
|
| 86 |
+
)
|
| 87 |
+
return passed
|
| 88 |
+
except Exception as e:
|
| 89 |
+
print_result(False, f"Error: {str(e)}")
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def test_youtube_search(base_url: str) -> bool:
|
| 94 |
+
"""Test YouTube search endpoint"""
|
| 95 |
+
print_test("Test 4: YouTube Video Search")
|
| 96 |
+
print(f"POST {base_url}/search")
|
| 97 |
+
|
| 98 |
+
payload = {
|
| 99 |
+
"query": "Python programming",
|
| 100 |
+
"max_results": 2
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
try:
|
| 104 |
+
response = requests.post(
|
| 105 |
+
f"{base_url}/search",
|
| 106 |
+
json=payload,
|
| 107 |
+
timeout=15
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
if response.status_code == 200:
|
| 111 |
+
print_result(True, f"Status: {response.status_code}", response.json())
|
| 112 |
+
return True
|
| 113 |
+
elif response.status_code == 500 and "YOUTUBE_API_KEY" in response.text:
|
| 114 |
+
print(f"{YELLOW}β API KEY NOT SET{NC} - Status: {response.status_code}")
|
| 115 |
+
print("Please set YOUTUBE_API_KEY in your Space settings")
|
| 116 |
+
print(f"Response: {response.json()}")
|
| 117 |
+
return False
|
| 118 |
+
else:
|
| 119 |
+
print_result(False, f"Status: {response.status_code}", response.text)
|
| 120 |
+
return False
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print_result(False, f"Error: {str(e)}")
|
| 123 |
+
return False
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def test_error_handling(base_url: str) -> bool:
|
| 127 |
+
"""Test error handling with missing query"""
|
| 128 |
+
print_test("Test 5: Error Handling (Missing Query)")
|
| 129 |
+
print(f"POST {base_url}/search (with empty query)")
|
| 130 |
+
|
| 131 |
+
payload = {}
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
response = requests.post(
|
| 135 |
+
f"{base_url}/search",
|
| 136 |
+
json=payload,
|
| 137 |
+
timeout=10
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# We expect a 400 error
|
| 141 |
+
passed = response.status_code == 400
|
| 142 |
+
print_result(
|
| 143 |
+
passed,
|
| 144 |
+
f"Status: {response.status_code} (Expected 400 error)",
|
| 145 |
+
response.json() if response.status_code == 400 else response.text
|
| 146 |
+
)
|
| 147 |
+
return passed
|
| 148 |
+
except Exception as e:
|
| 149 |
+
print_result(False, f"Error: {str(e)}")
|
| 150 |
+
return False
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def main():
|
| 154 |
+
"""Run all tests"""
|
| 155 |
+
# Get Space URL from command line or use default
|
| 156 |
+
base_url = sys.argv[1] if len(sys.argv) > 1 else "https://ocx2025-basicsearch.hf.space"
|
| 157 |
+
|
| 158 |
+
# Remove trailing slash if present
|
| 159 |
+
base_url = base_url.rstrip('/')
|
| 160 |
+
|
| 161 |
+
print("=" * 50)
|
| 162 |
+
print(f"Testing Hugging Face Space: {base_url}")
|
| 163 |
+
print("=" * 50)
|
| 164 |
+
|
| 165 |
+
# Run all tests
|
| 166 |
+
results = []
|
| 167 |
+
results.append(("Health Check", test_health(base_url)))
|
| 168 |
+
results.append(("Service Info", test_service_info(base_url)))
|
| 169 |
+
results.append(("List Tools", test_list_tools(base_url)))
|
| 170 |
+
results.append(("YouTube Search", test_youtube_search(base_url)))
|
| 171 |
+
results.append(("Error Handling", test_error_handling(base_url)))
|
| 172 |
+
|
| 173 |
+
# Print summary
|
| 174 |
+
print("\n" + "=" * 50)
|
| 175 |
+
print("Test Summary")
|
| 176 |
+
print("=" * 50)
|
| 177 |
+
|
| 178 |
+
passed = sum(1 for _, result in results if result)
|
| 179 |
+
total = len(results)
|
| 180 |
+
|
| 181 |
+
for test_name, result in results:
|
| 182 |
+
status = f"{GREEN}β{NC}" if result else f"{RED}β{NC}"
|
| 183 |
+
print(f"{status} {test_name}")
|
| 184 |
+
|
| 185 |
+
print(f"\n{BLUE}Results: {passed}/{total} tests passed{NC}")
|
| 186 |
+
print("=" * 50)
|
| 187 |
+
|
| 188 |
+
# Exit with appropriate code
|
| 189 |
+
sys.exit(0 if passed == total else 1)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
main()
|
| 194 |
+
|
test_deployment.sh
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Test script for Hugging Face Space deployment
|
| 4 |
+
# Usage: ./test_deployment.sh [space-url]
|
| 5 |
+
|
| 6 |
+
# Color codes for output
|
| 7 |
+
GREEN='\033[0;32m'
|
| 8 |
+
RED='\033[0;31m'
|
| 9 |
+
YELLOW='\033[1;33m'
|
| 10 |
+
NC='\033[0m' # No Color
|
| 11 |
+
|
| 12 |
+
# Default URL (update with your actual Space URL)
|
| 13 |
+
SPACE_URL="${1:-https://ocx2025-basicsearch.hf.space}"
|
| 14 |
+
|
| 15 |
+
echo "================================================"
|
| 16 |
+
echo "Testing Hugging Face Space: $SPACE_URL"
|
| 17 |
+
echo "================================================"
|
| 18 |
+
echo ""
|
| 19 |
+
|
| 20 |
+
# Test 1: Health Check
|
| 21 |
+
echo -e "${YELLOW}Test 1: Health Check${NC}"
|
| 22 |
+
echo "GET $SPACE_URL/health"
|
| 23 |
+
RESPONSE=$(curl -s -w "\n%{http_code}" "$SPACE_URL/health")
|
| 24 |
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
| 25 |
+
BODY=$(echo "$RESPONSE" | head -n-1)
|
| 26 |
+
|
| 27 |
+
if [ "$HTTP_CODE" = "200" ]; then
|
| 28 |
+
echo -e "${GREEN}β PASSED${NC} - Status: $HTTP_CODE"
|
| 29 |
+
echo "Response: $BODY"
|
| 30 |
+
else
|
| 31 |
+
echo -e "${RED}β FAILED${NC} - Status: $HTTP_CODE"
|
| 32 |
+
echo "Response: $BODY"
|
| 33 |
+
fi
|
| 34 |
+
echo ""
|
| 35 |
+
|
| 36 |
+
# Test 2: Service Info
|
| 37 |
+
echo -e "${YELLOW}Test 2: Service Info${NC}"
|
| 38 |
+
echo "GET $SPACE_URL/"
|
| 39 |
+
RESPONSE=$(curl -s -w "\n%{http_code}" "$SPACE_URL/")
|
| 40 |
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
| 41 |
+
BODY=$(echo "$RESPONSE" | head -n-1)
|
| 42 |
+
|
| 43 |
+
if [ "$HTTP_CODE" = "200" ]; then
|
| 44 |
+
echo -e "${GREEN}β PASSED${NC} - Status: $HTTP_CODE"
|
| 45 |
+
echo "Response: $BODY" | jq '.' 2>/dev/null || echo "Response: $BODY"
|
| 46 |
+
else
|
| 47 |
+
echo -e "${RED}β FAILED${NC} - Status: $HTTP_CODE"
|
| 48 |
+
echo "Response: $BODY"
|
| 49 |
+
fi
|
| 50 |
+
echo ""
|
| 51 |
+
|
| 52 |
+
# Test 3: List Tools
|
| 53 |
+
echo -e "${YELLOW}Test 3: List Available Tools${NC}"
|
| 54 |
+
echo "GET $SPACE_URL/tools"
|
| 55 |
+
RESPONSE=$(curl -s -w "\n%{http_code}" "$SPACE_URL/tools")
|
| 56 |
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
| 57 |
+
BODY=$(echo "$RESPONSE" | head -n-1)
|
| 58 |
+
|
| 59 |
+
if [ "$HTTP_CODE" = "200" ]; then
|
| 60 |
+
echo -e "${GREEN}β PASSED${NC} - Status: $HTTP_CODE"
|
| 61 |
+
echo "Response: $BODY" | jq '.' 2>/dev/null || echo "Response: $BODY"
|
| 62 |
+
else
|
| 63 |
+
echo -e "${RED}β FAILED${NC} - Status: $HTTP_CODE"
|
| 64 |
+
echo "Response: $BODY"
|
| 65 |
+
fi
|
| 66 |
+
echo ""
|
| 67 |
+
|
| 68 |
+
# Test 4: YouTube Search (requires API key to be set)
|
| 69 |
+
echo -e "${YELLOW}Test 4: YouTube Video Search${NC}"
|
| 70 |
+
echo "POST $SPACE_URL/search"
|
| 71 |
+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SPACE_URL/search" \
|
| 72 |
+
-H "Content-Type: application/json" \
|
| 73 |
+
-d '{"query": "Python programming", "max_results": 2}')
|
| 74 |
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
| 75 |
+
BODY=$(echo "$RESPONSE" | head -n-1)
|
| 76 |
+
|
| 77 |
+
if [ "$HTTP_CODE" = "200" ]; then
|
| 78 |
+
echo -e "${GREEN}β PASSED${NC} - Status: $HTTP_CODE"
|
| 79 |
+
echo "Response: $BODY" | jq '.' 2>/dev/null || echo "Response: $BODY"
|
| 80 |
+
elif [ "$HTTP_CODE" = "500" ] && echo "$BODY" | grep -q "YOUTUBE_API_KEY"; then
|
| 81 |
+
echo -e "${YELLOW}β API KEY NOT SET${NC} - Status: $HTTP_CODE"
|
| 82 |
+
echo "Please set YOUTUBE_API_KEY in your Space settings"
|
| 83 |
+
echo "Response: $BODY"
|
| 84 |
+
else
|
| 85 |
+
echo -e "${RED}β FAILED${NC} - Status: $HTTP_CODE"
|
| 86 |
+
echo "Response: $BODY"
|
| 87 |
+
fi
|
| 88 |
+
echo ""
|
| 89 |
+
|
| 90 |
+
# Test 5: Error handling - missing query
|
| 91 |
+
echo -e "${YELLOW}Test 5: Error Handling (Missing Query)${NC}"
|
| 92 |
+
echo "POST $SPACE_URL/search (with empty query)"
|
| 93 |
+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SPACE_URL/search" \
|
| 94 |
+
-H "Content-Type: application/json" \
|
| 95 |
+
-d '{}')
|
| 96 |
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
| 97 |
+
BODY=$(echo "$RESPONSE" | head -n-1)
|
| 98 |
+
|
| 99 |
+
if [ "$HTTP_CODE" = "400" ]; then
|
| 100 |
+
echo -e "${GREEN}β PASSED${NC} - Status: $HTTP_CODE (Expected error)"
|
| 101 |
+
echo "Response: $BODY"
|
| 102 |
+
else
|
| 103 |
+
echo -e "${YELLOW}β UNEXPECTED${NC} - Status: $HTTP_CODE"
|
| 104 |
+
echo "Response: $BODY"
|
| 105 |
+
fi
|
| 106 |
+
echo ""
|
| 107 |
+
|
| 108 |
+
echo "================================================"
|
| 109 |
+
echo "Testing Complete!"
|
| 110 |
+
echo "================================================"
|
| 111 |
+
|
test_private_space.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for PRIVATE Hugging Face Spaces with authentication.
|
| 4 |
+
Usage: uv run python test_private_space.py [space-url] [hf-token]
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import requests
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
# ANSI color codes
|
| 13 |
+
GREEN = '\033[0;32m'
|
| 14 |
+
RED = '\033[0;31m'
|
| 15 |
+
YELLOW = '\033[1;33m'
|
| 16 |
+
BLUE = '\033[0;34m'
|
| 17 |
+
NC = '\033[0m' # No Color
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def get_headers(token: str = None) -> dict:
|
| 21 |
+
"""Get headers with authentication if token provided"""
|
| 22 |
+
headers = {"Content-Type": "application/json"}
|
| 23 |
+
if token:
|
| 24 |
+
headers["Authorization"] = f"Bearer {token}"
|
| 25 |
+
return headers
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_with_auth(base_url: str, token: str = None):
|
| 29 |
+
"""Test space with optional authentication"""
|
| 30 |
+
print("=" * 50)
|
| 31 |
+
print(f"Testing Space: {base_url}")
|
| 32 |
+
if token:
|
| 33 |
+
print(f"Using token: {token[:10]}...{token[-5:]}")
|
| 34 |
+
else:
|
| 35 |
+
print("No token provided (testing public access)")
|
| 36 |
+
print("=" * 50)
|
| 37 |
+
print()
|
| 38 |
+
|
| 39 |
+
headers = get_headers(token)
|
| 40 |
+
|
| 41 |
+
# Test health endpoint
|
| 42 |
+
print(f"{YELLOW}Test: Health Check{NC}")
|
| 43 |
+
try:
|
| 44 |
+
response = requests.get(f"{base_url}/health", headers=headers, timeout=10)
|
| 45 |
+
|
| 46 |
+
if response.status_code == 200:
|
| 47 |
+
print(f"{GREEN}β SUCCESS{NC} - Space is accessible!")
|
| 48 |
+
print(f"Response: {response.json()}")
|
| 49 |
+
return True
|
| 50 |
+
elif response.status_code == 404:
|
| 51 |
+
print(f"{RED}β 404 ERROR{NC}")
|
| 52 |
+
print(f"\n{YELLOW}Possible reasons:{NC}")
|
| 53 |
+
print("1. Space is PRIVATE and needs authentication token")
|
| 54 |
+
print("2. Space is still building")
|
| 55 |
+
print("3. Space build failed")
|
| 56 |
+
print("\nTo fix:")
|
| 57 |
+
print("β’ Make space public in settings, OR")
|
| 58 |
+
print("β’ Use: uv run python test_private_space.py [url] [your-hf-token]")
|
| 59 |
+
return False
|
| 60 |
+
elif response.status_code == 401:
|
| 61 |
+
print(f"{RED}β 401 UNAUTHORIZED{NC}")
|
| 62 |
+
print("Space is PRIVATE. You need a valid Hugging Face token.")
|
| 63 |
+
print("\nGet your token:")
|
| 64 |
+
print("1. Go to https://huggingface.co/settings/tokens")
|
| 65 |
+
print("2. Create a new token with 'read' permissions")
|
| 66 |
+
print("3. Run: uv run python test_private_space.py [url] [token]")
|
| 67 |
+
return False
|
| 68 |
+
else:
|
| 69 |
+
print(f"{RED}β ERROR{NC} - Status: {response.status_code}")
|
| 70 |
+
print(f"Response: {response.text[:200]}")
|
| 71 |
+
return False
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"{RED}β ERROR{NC} - {str(e)}")
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def main():
|
| 78 |
+
# Get space URL and token
|
| 79 |
+
base_url = sys.argv[1] if len(sys.argv) > 1 else "https://ocx2025-basicsearch.hf.space"
|
| 80 |
+
token = sys.argv[2] if len(sys.argv) > 2 else os.getenv("HF_TOKEN")
|
| 81 |
+
|
| 82 |
+
# Remove trailing slash
|
| 83 |
+
base_url = base_url.rstrip('/')
|
| 84 |
+
|
| 85 |
+
# Test
|
| 86 |
+
success = test_with_auth(base_url, token)
|
| 87 |
+
|
| 88 |
+
if success:
|
| 89 |
+
print(f"\n{GREEN}β
Space is working!{NC}")
|
| 90 |
+
print(f"You can now run the full test suite:")
|
| 91 |
+
if token:
|
| 92 |
+
print(f"HF_TOKEN={token} uv run python test_deployment.py")
|
| 93 |
+
else:
|
| 94 |
+
print("uv run python test_deployment.py")
|
| 95 |
+
else:
|
| 96 |
+
print(f"\n{YELLOW}π Next Steps:{NC}")
|
| 97 |
+
print("\n1. Check if space is private:")
|
| 98 |
+
print(f" Visit: {base_url.replace('.hf.space', '').replace('https://', 'https://huggingface.co/spaces/').replace('-', '/', 1)}")
|
| 99 |
+
print("\n2. Make it PUBLIC:")
|
| 100 |
+
print(" Settings β Make public")
|
| 101 |
+
print("\n3. OR get your HF token:")
|
| 102 |
+
print(" https://huggingface.co/settings/tokens")
|
| 103 |
+
print(f" Then run: uv run python test_private_space.py {base_url} YOUR_TOKEN")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
main()
|
| 108 |
+
|
uv.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|