This commit is contained in:
Bob Roberts 2025-11-06 20:25:59 -06:00
commit ea779fba43
15 changed files with 2456 additions and 42 deletions

View file

@ -646,48 +646,7 @@ curl http://localhost:8051/api/projects/1/kanban
--- ---
## 🏗️ Architecture ## 📂 Template System
```
┌─────────────────────────────────────────────────┐
│ Claude Code CLI │
│ (with Claude OS integration) │
└───────────────────┬─────────────────────────────┘
│ MCP HTTP
┌───────────────────▼─────────────────────────────┐
│ MCP Server (Port 8051) │
│ FastAPI Backend │
└───────────────────┬─────────────────────────────┘
│
┌───────────┴────────────┐
│ │
┌───────▼────────────┐ ┌────────▼──────────┐
│ RAG Engine │ │ React UI │
│ (llama-index) │ │ (Port 5173) │
│ • Vector Search │ │ • Project Mgmt │
│ • Hybrid Search │ │ • KB Browser │
│ • Reranking │ │ • Doc Upload │
└───────┬────────────┘ └───────────────────┘
│
┌───────▼────────────────────────────────────────┐
│ SQLite + sqlite-vec (Local Database) │
│ • projects │
│ • knowledge_bases │
│ • documents (with embeddings) │
│ • Single-file database │
└────────────────────────────────────────────────┘
│
└─────────────────────┐
│
┌─────────▼──────┐
│ Ollama │
│ (Port 11434) │
│ • llama3.1 │
│ • Embeddings │
└────────────────┘
```
### Template System
``` ```
claude-os/ claude-os/

View file

@ -0,0 +1,48 @@
# Claude OS MCP-Only Deployment Configuration
# ======================
# Database Configuration
# ======================
SQLITE_DB_PATH=/app/data/claude-os.db
# ======================
# MCP Server Configuration
# ======================
MCP_SERVER_HOST=0.0.0.0
MCP_SERVER_PORT=8051
# ======================
# Ollama Configuration
# ======================
OLLAMA_HOST=http://ollama:11434
OLLAMA_EMBED_MODEL=nomic-embed-text
OLLAMA_MODEL=llama3.1:latest
# ======================
# CORS Configuration
# ======================
# Comma-separated list of allowed origins
# Use "*" for development, specific domains for production
CORS_ORIGINS=*
# ======================
# Authentication (Optional)
# ======================
# Uncomment to enable JWT authentication
# JWT_SECRET_KEY=your-super-secret-key-change-this-in-production
# JWT_ALGORITHM=HS256
# API_USERNAME=admin
# API_PASSWORD=changeme
# ======================
# RAG Configuration
# ======================
CHUNK_SIZE=512
CHUNK_OVERLAP=128
TOP_K_RETRIEVAL=20
SIMILARITY_TOP_K=20
# ======================
# Logging
# ======================
LOG_LEVEL=INFO

View file

@ -0,0 +1,382 @@
# Deployment Options: Your Server vs Cloudflare Workers
You have two main options for deploying your MCP server. Here's a detailed comparison to help you choose.
## Option 1: Docker on Your Server (This Repo)
### What It Is
- Run the MCP server as a Docker container on your own server
- Sits alongside your other applications
- Uses your existing SQLite database directly
### Pros ✅
1. **Dead Simple Setup**
- Your database is already there
- Just mount it as a volume
- No data migration needed
2. **Zero Code Changes**
- Use the exact Python code from Claude OS
- No rewriting required
- Full feature parity
3. **Fast Local Access**
- Your other apps can access via `http://mcp-server:8051`
- No network latency
- Can use Docker networks
4. **Full Control**
- Choose your own models (Ollama)
- Adjust configuration freely
- Access to all RAG features (hybrid, rerank, agentic)
5. **Cost**
- Free (you're already paying for the server)
- No additional services needed
6. **Debugging**
- Direct access to logs
- Can inspect database
- Easy to troubleshoot
### Cons ❌
1. **Server Requirements**
- Needs ~500MB disk space
- ~200-500MB RAM
- Must manage Docker containers
2. **Scaling**
- Limited to your server's resources
- Need load balancer for multiple instances
3. **Availability**
- Single point of failure
- Need to manage uptime yourself
### When to Choose This
✅ **Use Docker on your server if:**
- Your apps are already on that server
- You want the simplest setup
- You have a single server or small deployment
- You want to use your existing database directly
- You need all RAG features (hybrid search, reranking)
- You don't need global distribution
### Example Architecture
```
┌──────────────────────────────────┐
│ Your Server │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Chat App │───▶│MCP Server│ │
│ └──────────┘ └─────┬────┘ │
│ │ │
│ ┌──────────┐ │ │
│ │ Docs App │─────────┤ │
│ └──────────┘ │ │
│ ▼ │
│ ┌──────────┐ │
│ │SQLite DB │ │
│ └──────────┘ │
│ │
└──────────────────────────────────┘
```
---
## Option 2: Cloudflare Workers (Like cloudflare_mcp)
### What It Is
- Serverless functions running on Cloudflare's edge network
- JavaScript/TypeScript only
- Uses Cloudflare D1 (SQLite) and Vectorize (vectors)
### How It Works
1. **Export your SQLite data to Cloudflare D1**
```bash
# Export schema and data
sqlite3 data/claude-os.db .dump > dump.sql
# Import to D1
wrangler d1 execute claude-os-db --file=dump.sql
```
2. **Rewrite MCP endpoints in JavaScript**
- Translate Python → TypeScript
- Use Cloudflare APIs instead of SQLiteManager
- Use Cloudflare AI for embeddings
3. **Deploy to Workers**
```bash
wrangler deploy
```
### Pros ✅
1. **Global Distribution**
- Runs in 300+ cities worldwide
- Ultra-low latency for users anywhere
- Automatic geo-routing
2. **Auto-Scaling**
- Handles 0 to millions of requests
- No capacity planning
- Pay per use
3. **High Availability**
- Built-in redundancy
- 100% uptime SLA (on paid plans)
- No maintenance windows
4. **No Server Management**
- Completely serverless
- Automatic updates
- No Docker, no VMs
5. **Free Tier**
- 100,000 requests/day free
- 10GB D1 storage free
- Good for most use cases
### Cons ❌
1. **Major Rewrite Required**
- Must port Python → JavaScript/TypeScript
- Reimplement all RAG logic
- Could take days/weeks
2. **Cloudflare Limitations**
- D1: 500MB database limit (free tier)
- Workers: 10ms CPU time per request (free tier)
- Must use Cloudflare AI models (different from Ollama)
3. **Data Migration**
- Must export SQLite → D1
- Must export vectors → Vectorize
- Complex migration process
4. **Limited RAG Features**
- Cloudflare AI has fewer models
- Harder to implement advanced features
- No LlamaIndex integration
5. **Debugging**
- Harder to debug serverless
- Limited logging (on free tier)
- Can't inspect database easily
6. **Vendor Lock-in**
- Tied to Cloudflare ecosystem
- Migration away is difficult
### When to Choose This
✅ **Use Cloudflare Workers if:**
- Your apps are distributed globally
- You need extreme scalability
- You want zero server management
- You're comfortable rewriting in JavaScript
- Your database is < 500MB (or you can split it)
- You don't need advanced RAG features
### Example Architecture
```
Global Users
│
▼
┌───────────────────────┐
│ Cloudflare Edge │
│ (300+ locations) │
│ │
│ ┌─────────────────┐ │
│ │ MCP Worker │ │
│ │ (TypeScript) │ │
│ └────┬────────────┘ │
│ │ │
│ ├──▶ D1 (SQLite)│
│ └──▶ Vectorize │
│ │
└───────────────────────┘
▲
│
Your Chat/Docs Apps
```
---
## Side-by-Side Comparison
| Feature | Your Server (Docker) | Cloudflare Workers |
|---------|---------------------|-------------------|
| **Setup Time** | 5 minutes | Days (rewrite code) |
| **Code Changes** | None | Full rewrite |
| **Data Migration** | None | Complex |
| **Server Management** | Yes | No |
| **Scaling** | Manual | Automatic |
| **Global Latency** | Higher | Ultra-low |
| **Cost (small)** | $0 (existing server) | $0 (free tier) |
| **Cost (large)** | Fixed | Pay-per-use |
| **Database Size** | Unlimited | 500MB (free tier) |
| **RAG Features** | Full | Limited |
| **Model Choice** | Any (Ollama) | Cloudflare AI only |
| **Debugging** | Easy | Harder |
| **Vendor Lock-in** | None | Cloudflare |
---
## Recommended Approach
### For Your Use Case
Based on what you described:
- ✅ You have MCPs populated with data locally
- ✅ You have other apps that need this data
- ✅ One needs it as a knowledge base
- ✅ Another needs it for live chat
**I recommend: Start with Docker on your server (Option 1)**
### Why?
1. **Fastest to deploy** - 5 minutes vs days
2. **No code changes** - Use existing Python code
3. **No data migration** - Mount your existing DB
4. **Full features** - All RAG capabilities work
5. **Same server** - Your apps can access locally
### Migration Path (If Needed Later)
```
Phase 1: Docker on your server (NOW)
↓
Get your apps working
Test performance
Measure traffic
↓
Phase 2: Evaluate needs (LATER)
↓
If scaling is needed → Consider Cloudflare
If working well → Stay with Docker
```
---
## Quick Start: Docker on Your Server
```bash
# 1. Navigate to deployment directory
cd deployment/mcp-only/
# 2. Run setup script
./setup.sh
# 3. Done! MCP server is running at http://localhost:8051
# 4. Update your apps to use:
# http://mcp-server:8051 (if on same Docker network)
# http://localhost:8051 (if on same server)
```
---
## Hybrid Approach (Best of Both Worlds)
You can also do both:
```
┌────────────────────────────────┐
│ Your Server (Primary) │
│ ┌────────┐ ┌────────────┐ │
│ │Chat App│───▶│MCP (Docker)│ │
│ └────────┘ └────────────┘ │
│ ┌────────┐ ▲ │
│ │Docs App│──────────┘ │
│ └────────┘ │
└────────────────────────────────┘
│
│ (Optional: Sync to Cloudflare)
▼
┌────────────────────────────────┐
│ Cloudflare (Secondary) │
│ ┌──────────────────────────┐ │
│ │ Read-only MCP (Workers) │ │
│ │ For public docs search │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
```
**Use Docker for:**
- Internal apps (chat, docs)
- Write operations
- Full RAG features
**Use Cloudflare for:**
- Public-facing documentation search
- Global users
- Read-only queries
---
## Example: Cloudflare Workers Code (If You Choose That Path)
See the `cloudflare-workers/` directory for a basic example of how to implement the MCP endpoints on Cloudflare Workers.
**Note:** This is a simplified example. A full implementation would require:
- Complete D1 database migration
- Vector embeddings in Vectorize
- All 12 MCP tools reimplemented
- Proper error handling and auth
- Testing and deployment
---
## Decision Flowchart
```
Do you need global distribution?
├─ No → Use Docker on your server ✅
│
└─ Yes
│
Are your apps < 100k requests/day?
├─ Yes
│ │
│ Is your database < 500MB?
│ ├─ Yes → Consider Cloudflare Workers
│ └─ No → Use Docker on your server ✅
│
└─ No → Use Docker + CDN or multiple regions
```
---
## My Recommendation
**Start with Docker on your server.** It's:
- ✅ 10x faster to set up
- ✅ No code changes
- ✅ Uses your existing data
- ✅ Full feature set
- ✅ Easy to debug
Then, **if you need global scaling**, consider:
1. Adding a CDN in front of your Docker deployment
2. Deploying Docker containers in multiple regions
3. Only then consider Cloudflare Workers (requires significant effort)
Most applications don't need Cloudflare-level scaling. Your Docker deployment will likely handle thousands of requests per second, which is probably more than enough.
---
## Questions?
See:
- `README.md` - Full Docker setup guide
- `examples/` - Integration examples
- `cloudflare-workers/` - Cloudflare Workers starter (if you choose that path)

View file

@ -0,0 +1,53 @@
FROM python:3.11-slim as builder
# Install system dependencies for building
RUN apt-get update && apt-get install -y \
gcc \
g++ \
make \
libmagic1 \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy requirements and install Python dependencies
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# Final stage
FROM python:3.11-slim
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
libmagic1 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Set working directory
WORKDIR /app
# Copy only necessary application files
COPY mcp_server/ /app/mcp_server/
COPY app/core/ /app/app/core/
COPY app/db/ /app/app/db/
COPY app/utils/ /app/app/utils/
COPY app/__init__.py /app/app/__init__.py
# Create data directory for database
RUN mkdir -p /app/data
# Expose MCP server port
EXPOSE 8051
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8051/health || exit 1
# Run MCP server
CMD ["python", "-m", "uvicorn", "mcp_server.server:app", "--host", "0.0.0.0", "--port", "8051"]

View file

@ -0,0 +1,460 @@
# Claude OS - MCP-Only Deployment
This deployment package extracts **just the MCP knowledge base server** from Claude OS, allowing you to expose your populated knowledge bases to other applications without running the full coding environment.
## 🎯 What This Does
- ✅ Exposes MCP HTTP endpoints for knowledge base queries
- ✅ Serves your existing SQLite database (read-only by default)
- ✅ Provides RAG (Retrieval Augmented Generation) search
- ✅ Lightweight: No CLI, no coding tools, no UI
- ✅ Perfect for: Knowledge base APIs, live chat backends, documentation search
## 🏗️ Architecture
```
┌─────────────────┐
│ Your Other Apps│
│ (Chat, Docs) │
└────────┬────────┘
│ HTTP/JSON-RPC
↓
┌─────────────────┐
│ MCP Server │
│ (Port 8051) │
└────────┬────────┘
│
├──→ SQLite DB (your local data/claude-os.db)
└──→ Ollama (embeddings + LLM)
```
## 📦 What's Included
- **MCP Server**: HTTP endpoints for all knowledge base operations
- **SQLite Manager**: Database access layer
- **RAG Engine**: Semantic search with hybrid modes
- **Ollama**: Local AI for embeddings and LLM (required)
## 🚀 Quick Start
### Option 1: Using Your Existing Local Database (Recommended)
This is the easiest approach - just mount your existing `data/claude-os.db`:
```bash
# From the deployment/mcp-only directory
docker-compose up -d
# This will:
# 1. Start Ollama container
# 2. Start MCP server with your DB mounted at ../../data (read-only)
# 3. Pull required models on first run
```
Your MCP server will be available at: `http://localhost:8051`
### Option 2: Copy Database to Server
If you're deploying to a remote server:
```bash
# 1. Copy your local database to the server
scp ../../data/claude-os.db user@server:/path/to/claude-os/data/
# 2. On the server, start services
docker-compose up -d
```
### Option 3: Volume-Based (For Persistent Changes)
If you want the Docker container to have its own copy:
```bash
# 1. Edit docker-compose.yml - uncomment the mcp_data volume
# 2. Copy your database into the volume
docker-compose up -d
docker cp ../../data/claude-os.db claude-os-mcp:/app/data/
```
## 🔧 Configuration
### Environment Variables
Edit `docker-compose.yml` to configure:
```yaml
environment:
# Database (required)
SQLITE_DB_PATH: /app/data/claude-os.db
# Ollama (required)
OLLAMA_HOST: http://ollama:11434
OLLAMA_EMBED_MODEL: nomic-embed-text
OLLAMA_MODEL: llama3.1:latest
# CORS - Important for your other apps!
CORS_ORIGINS: "https://your-chat-app.com,https://your-docs-app.com"
# Optional: Authentication
JWT_SECRET_KEY: your-secret-key-here
API_USERNAME: admin
API_PASSWORD: changeme
```
### First-Time Setup: Pull Ollama Models
On first run, you need to download the models:
```bash
# Wait for containers to start
docker-compose up -d
# Pull embedding model (required)
docker exec claude-os-ollama ollama pull nomic-embed-text
# Pull LLM model (optional, for agentic queries)
docker exec claude-os-ollama ollama pull llama3.1:latest
```
## 🔌 Using the MCP Endpoints
### Health Check
```bash
curl http://localhost:8051/health
```
### List All Knowledge Bases
```bash
curl -X POST http://localhost:8051/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_knowledge_bases",
"arguments": {}
},
"id": 1
}'
```
### Search Knowledge Base
```bash
curl -X POST http://localhost:8051/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search_knowledge_base",
"arguments": {
"kb_name": "my-docs",
"query": "How do I configure authentication?",
"top_k": 10
}
},
"id": 1
}'
```
### KB-Specific Endpoint (Cleaner URLs)
```bash
# Search a specific KB directly
curl -X POST http://localhost:8051/mcp/kb/my-docs \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "authentication setup",
"top_k": 5
}
},
"id": 1
}'
```
## 🔗 Integration Examples
### Python Client
```python
import requests
def search_knowledge(kb_name: str, query: str, top_k: int = 10):
response = requests.post(
"http://localhost:8051/mcp",
json={
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search_knowledge_base",
"arguments": {
"kb_name": kb_name,
"query": query,
"top_k": top_k
}
},
"id": 1
}
)
return response.json()["result"]
# Use in your chat app
results = search_knowledge("my-docs", "How to deploy?")
print(results)
```
### JavaScript/Node.js Client
```javascript
async function searchKnowledge(kbName, query, topK = 10) {
const response = await fetch('http://localhost:8051/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'search_knowledge_base',
arguments: { kb_name: kbName, query, top_k: topK }
},
id: 1
})
});
const data = await response.json();
return data.result;
}
// Use in your app
const results = await searchKnowledge('my-docs', 'How to deploy?');
console.log(results);
```
## 📊 Available MCP Tools
### Global Endpoint (`/mcp`)
| Tool | Description |
|------|-------------|
| `search_knowledge_base` | Semantic search across any KB |
| `list_knowledge_bases` | List all available KBs |
| `list_knowledge_bases_by_type` | Filter KBs by type |
| `create_knowledge_base` | Create new KB (if writable) |
| `get_kb_stats` | Get KB statistics |
| `list_documents` | List documents in KB |
| `ingest_agent_os_profile` | Import Agent OS data |
| `get_agent_os_stats` | Agent OS statistics |
| `get_standards` | Retrieve standards |
| `get_workflows` | Retrieve workflows |
| `get_specs` | Retrieve specifications |
| `get_product_context` | Get product context |
### KB-Specific Endpoint (`/mcp/kb/{kb_slug}`)
| Tool | Description |
|------|-------------|
| `search` | Search within specific KB |
| `get_stats` | Get stats for specific KB |
| `list_documents` | List documents in KB |
## 🔒 Security Considerations
### 1. Read-Only Database (Default)
The default `docker-compose.yml` mounts your database as **read-only** (`:ro` flag). This prevents the container from modifying your local data.
To allow writes (e.g., for creating new KBs), change:
```yaml
volumes:
- ../../data:/app/data:ro # Read-only
```
to:
```yaml
volumes:
- ../../data:/app/data # Read-write
```
### 2. Enable Authentication
Uncomment and set these in `docker-compose.yml`:
```yaml
JWT_SECRET_KEY: "your-strong-secret-key-here"
API_USERNAME: "admin"
API_PASSWORD: "secure-password-here"
```
Then authenticate:
```bash
# Get token
curl -X POST http://localhost:8051/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=secure-password-here"
# Use token in requests
curl -X POST http://localhost:8051/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '...'
```
### 3. CORS Configuration
Restrict `CORS_ORIGINS` to your specific domains:
```yaml
CORS_ORIGINS: "https://chat.yourapp.com,https://docs.yourapp.com"
```
### 4. Network Isolation
If deploying with your other apps, use Docker networks:
```yaml
networks:
- your_app_network
```
## 🌐 Cloudflare Workers Deployment
For a serverless approach similar to [cloudflare_mcp](https://github.com/forayconsulting/cloudflare_mcp):
### What You'd Need
1. **Export your SQLite DB to Cloudflare D1** (their SQLite service)
2. **Rewrite MCP endpoints as Workers** (JavaScript/TypeScript)
3. **Use Cloudflare AI** instead of Ollama (for embeddings)
### Pros of Cloudflare Approach
- ✅ Globally distributed
- ✅ Auto-scaling
- ✅ No server management
### Cons
- ❌ Must rewrite Python → JavaScript
- ❌ Cloudflare AI models (less flexibility)
- ❌ D1 has size limits (500MB free tier)
- ❌ More complex to set up initially
**This Docker approach** is simpler if you just need to expose your existing data to your apps on a server you control.
## 📈 Monitoring
### View Logs
```bash
# MCP Server logs
docker-compose logs -f mcp-server
# Ollama logs
docker-compose logs -f ollama
```
### Check Health
```bash
# MCP Server
curl http://localhost:8051/health
# Ollama
curl http://localhost:11434/api/tags
```
## 🛠️ Troubleshooting
### Models Not Found
```bash
# Pull models manually
docker exec claude-os-ollama ollama pull nomic-embed-text
docker exec claude-os-ollama ollama pull llama3.1:latest
```
### Database Not Found
```bash
# Verify database path
docker exec claude-os-mcp ls -la /app/data/
# Check if mounted correctly
docker inspect claude-os-mcp | grep -A 10 Mounts
```
### CORS Errors
Update `CORS_ORIGINS` in `docker-compose.yml` to include your app's domain.
### Port Conflicts
If port 8051 or 11434 are in use, change them:
```yaml
ports:
- "9051:8051" # Use port 9051 externally
```
## 🔄 Updating Your Database
### While Container is Running
Your database is mounted from `../../data/claude-os.db`. Any changes you make locally (using Claude OS) will be immediately visible to the container.
```bash
# No restart needed - changes are live!
# Just query the MCP endpoint and you'll see new data
```
### Deploying to Production
1. **Option A: Sync database periodically**
```bash
# Cron job to sync from local to server
rsync -avz data/claude-os.db user@server:/path/to/data/
```
2. **Option B: Use shared network storage**
- Mount same NFS/S3 volume on both local and server
3. **Option C: Use database replication**
- Set up SQLite replication (more complex)
## 📚 Next Steps
1. **Deploy this to your server** alongside your other apps
2. **Update CORS** to allow your chat/docs apps
3. **Enable authentication** for production
4. **Configure monitoring** (logs, health checks)
5. **Set up database sync** if needed
## 🆚 Comparison: This vs. Full Claude OS
| Feature | MCP-Only | Full Claude OS |
|---------|----------|----------------|
| Knowledge Base API | ✅ | ✅ |
| CLI Coding Assistant | ❌ | ✅ |
| Frontend UI | ❌ | ✅ |
| File Watchers | ❌ | ✅ |
| Spec Tracking | ❌ | ✅ |
| Project Management | ❌ | ✅ |
| Container Size | ~500MB | ~2GB |
| Memory Usage | ~200MB | ~1GB |
## 📞 Support
For issues specific to:
- **MCP Endpoints**: See `/docs/API_REFERENCE.md` in main repo
- **Deployment**: Open issue in claude-os repo
- **Your Apps Integration**: This is the right approach!
## 🎉 You're All Set!
Your knowledge bases are now available as an API for your other applications to consume. No coding environment needed - just pure data access!

View file

@ -0,0 +1,328 @@
# Cloudflare Workers Deployment (Alternative Approach)
This directory contains a **starter template** for deploying Claude OS MCP endpoints on Cloudflare Workers, similar to the [cloudflare_mcp](https://github.com/forayconsulting/cloudflare_mcp) project.
> ⚠️ **Important**: This is a **significant undertaking** compared to the Docker approach. Only use this if you need global distribution and auto-scaling.
## What This Requires
### 1. Cloudflare Account Setup
- Cloudflare account (free tier available)
- D1 database (Cloudflare's SQLite service)
- Vectorize (Cloudflare's vector search service)
- Workers AI (for embeddings)
### 2. Code Rewrite
- Rewrite all Python code → JavaScript/TypeScript
- Reimplement SQLiteManager → D1 queries
- Reimplement RAGEngine → Vectorize queries
- Port all 12 MCP tools
### 3. Data Migration
- Export SQLite schema and data
- Import to D1
- Extract vectors and import to Vectorize
- Set up sync process (if needed)
## Estimated Effort
| Task | Time Estimate |
|------|--------------|
| Setup Cloudflare infrastructure | 2-4 hours |
| Migrate database schema | 1-2 hours |
| Migrate data and vectors | 2-4 hours |
| Rewrite MCP endpoints | 8-16 hours |
| Testing and debugging | 4-8 hours |
| **Total** | **17-34 hours** |
Compare to Docker approach: **5 minutes**
## When to Use This
✅ Only use Cloudflare Workers if:
- You have **global users** across continents
- You need **automatic scaling** beyond a single server
- You're okay with **vendor lock-in** to Cloudflare
- You have **time to rewrite** the codebase in JavaScript
- Your database is **< 500MB** (free tier limit)
Otherwise, use the Docker approach.
## Quick Start
### 1. Install Wrangler (Cloudflare CLI)
```bash
npm install -g wrangler
# Login to Cloudflare
wrangler login
```
### 2. Create D1 Database
```bash
# Create database
wrangler d1 create claude-os-db
# This will output a database ID - save it!
```
### 3. Export Your SQLite Database
```bash
# From the root of claude-os repo
sqlite3 data/claude-os.db .dump > deployment/mcp-only/cloudflare-workers/schema.sql
```
### 4. Import to D1
```bash
cd deployment/mcp-only/cloudflare-workers
# Import schema and data
wrangler d1 execute claude-os-db --file=schema.sql
```
### 5. Create Vectorize Index
```bash
# Create vector index (for embeddings)
wrangler vectorize create claude-os-vectors --dimensions=768 --metric=cosine
# Note: You'll need to export and import your vectors separately
```
### 6. Configure wrangler.toml
Edit `wrangler.toml` with your database and vectorize IDs:
```toml
name = "claude-os-mcp"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "claude-os-db"
database_id = "your-database-id-here"
[[vectorize]]
binding = "VECTORIZE"
index_name = "claude-os-vectors"
index_id = "your-vectorize-id-here"
[ai]
binding = "AI"
```
### 7. Implement MCP Endpoints
See `src/index.ts` for a basic example. You'll need to implement all tools:
```typescript
// Example structure
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { pathname } = new URL(request.url);
// Health check
if (pathname === '/health') {
return new Response(JSON.stringify({ status: 'ok' }));
}
// MCP endpoint
if (pathname === '/mcp') {
const body = await request.json();
const result = await handleMCPRequest(body, env);
return new Response(JSON.stringify(result));
}
return new Response('Not found', { status: 404 });
}
};
```
### 8. Deploy
```bash
wrangler deploy
```
Your MCP server will be available at: `https://claude-os-mcp.your-subdomain.workers.dev`
## Code Structure
```
cloudflare-workers/
├── src/
│ ├── index.ts # Main worker entry point
│ ├── handlers/
│ │ ├── search.ts # Search knowledge base
│ │ ├── list.ts # List KBs
│ │ └── ... # Other tools
│ ├── db/
│ │ ├── queries.ts # D1 query helpers
│ │ └── schema.ts # Type definitions
│ └── utils/
│ ├── embeddings.ts # Workers AI embeddings
│ └── vectorize.ts # Vectorize queries
├── wrangler.toml # Cloudflare config
├── package.json
├── tsconfig.json
└── schema.sql # Exported database
```
## Key Differences from Python Version
### Database Access
**Python (SQLiteManager):**
```python
def query_documents(kb_name, query_embedding, n_results):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT ... FROM documents WHERE ...")
return cursor.fetchall()
```
**TypeScript (D1):**
```typescript
async function queryDocuments(env: Env, kbName: string, n: number) {
const results = await env.DB.prepare(
"SELECT ... FROM documents WHERE ..."
).all();
return results;
}
```
### Vector Search
**Python (sqlite-vec):**
```python
cursor.execute("""
SELECT *, vec_distance_cosine(embedding, ?) as distance
FROM documents
ORDER BY distance
LIMIT ?
""", [query_embedding, n_results])
```
**TypeScript (Vectorize):**
```typescript
const matches = await env.VECTORIZE.query(queryEmbedding, {
topK: n,
returnMetadata: true
});
```
### Embeddings
**Python (Ollama):**
```python
from llama_index.embeddings.ollama import OllamaEmbedding
embedding_model = OllamaEmbedding(
model_name="nomic-embed-text",
base_url="http://localhost:11434"
)
embedding = embedding_model.get_query_embedding(text)
```
**TypeScript (Workers AI):**
```typescript
const embedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: queryText
});
```
## Migration Script Example
Here's a script to export your vectors for Vectorize:
```python
# export_vectors.py
import sqlite3
import json
conn = sqlite3.connect('../../data/claude-os.db')
cursor = conn.cursor()
cursor.execute("""
SELECT id, embedding, metadata
FROM documents
""")
vectors = []
for doc_id, embedding, metadata in cursor.fetchall():
vectors.append({
'id': str(doc_id),
'values': list(embedding), # Convert BLOB to list
'metadata': json.loads(metadata)
})
with open('vectors.json', 'w') as f:
json.dump(vectors, f)
```
Then import to Vectorize:
```bash
# You'll need to use the Vectorize API
# See: https://developers.cloudflare.com/vectorize/
```
## Limitations
### Free Tier
- D1: 5 GB storage, 5M rows read/day, 100K writes/day
- Vectorize: 30M queries/month, 10M dimensions stored
- Workers: 100K requests/day, 10ms CPU per request
### Paid Tier Required For
- Large databases (> 500MB)
- High query volume (> 100K/day)
- Long-running queries (> 10ms CPU)
## Testing Locally
```bash
# Install dependencies
npm install
# Run local dev server
wrangler dev
# Test endpoint
curl http://localhost:8787/health
```
## Comparison with Docker Approach
| Feature | Docker | Cloudflare Workers |
|---------|--------|-------------------|
| Setup time | 5 min | 17-34 hours |
| Code changes | None | Complete rewrite |
| Database | Direct use | Must migrate |
| Scaling | Manual | Automatic |
| Cost (small) | $0 | $0 |
| Cost (large) | Fixed | Variable |
| Latency | Regional | Global |
## My Recommendation
**Unless you have specific requirements for global distribution, use the Docker approach.**
The Cloudflare Workers approach is powerful but requires significant development effort. It's best suited for:
- Public-facing APIs with global users
- Applications that need to scale to millions of requests
- Teams with JavaScript/TypeScript expertise
For most use cases, Docker on your server is simpler, faster, and more maintainable.
## Resources
- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
- [D1 Database Docs](https://developers.cloudflare.com/d1/)
- [Vectorize Docs](https://developers.cloudflare.com/vectorize/)
- [Workers AI Docs](https://developers.cloudflare.com/workers-ai/)
- [Example: cloudflare_mcp](https://github.com/forayconsulting/cloudflare_mcp)

View file

@ -0,0 +1,28 @@
{
"name": "claude-os-mcp-cloudflare",
"version": "1.0.0",
"description": "Claude OS MCP Server on Cloudflare Workers",
"main": "src/index.ts",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"tail": "wrangler tail",
"d1:create": "wrangler d1 create claude-os-db",
"d1:import": "wrangler d1 execute claude-os-db --file=schema.sql",
"vectorize:create": "wrangler vectorize create claude-os-vectors --dimensions=768 --metric=cosine"
},
"keywords": [
"mcp",
"knowledge-base",
"rag",
"cloudflare",
"workers"
],
"author": "",
"license": "MIT",
"devDependencies": {
"@cloudflare/workers-types": "^4.20240129.0",
"typescript": "^5.3.3",
"wrangler": "^3.22.0"
}
}

View file

@ -0,0 +1,303 @@
/**
* Claude OS MCP Server - Cloudflare Workers Implementation
*
* This is a BASIC EXAMPLE showing how to implement MCP endpoints
* on Cloudflare Workers. A production implementation would require
* all 12 tools and proper error handling.
*/
export interface Env {
DB: D1Database; // D1 database binding
VECTORIZE: VectorizeIndex; // Vectorize binding
AI: any; // Workers AI binding
}
interface MCPRequest {
jsonrpc: string;
method: string;
params?: {
name?: string;
arguments?: any;
};
id: number;
}
interface MCPResponse {
jsonrpc: string;
result?: any;
error?: {
code: number;
message: string;
};
id: number;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// CORS headers
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
// Handle preflight
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
// Health check
if (url.pathname === '/health') {
return new Response(
JSON.stringify({ status: 'ok', version: '1.0.0' }),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
);
}
// MCP endpoint
if (url.pathname === '/mcp' && request.method === 'POST') {
try {
const body: MCPRequest = await request.json();
const response = await handleMCPRequest(body, env);
return new Response(JSON.stringify(response), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32603,
message: error instanceof Error ? error.message : 'Internal error'
},
id: null
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
);
}
}
return new Response('Not found', { status: 404, headers: corsHeaders });
},
};
/**
* Handle MCP JSON-RPC requests
*/
async function handleMCPRequest(request: MCPRequest, env: Env): Promise<MCPResponse> {
const { method, params, id } = request;
// Initialize method
if (method === 'initialize') {
return {
jsonrpc: '2.0',
result: {
protocolVersion: '2024-11-05',
serverInfo: {
name: 'claude-os-mcp',
version: '1.0.0'
},
capabilities: {
tools: {}
}
},
id
};
}
// List available tools
if (method === 'tools/list') {
return {
jsonrpc: '2.0',
result: {
tools: [
{
name: 'search_knowledge_base',
description: 'Search a knowledge base for relevant information',
inputSchema: {
type: 'object',
properties: {
kb_name: { type: 'string' },
query: { type: 'string' },
top_k: { type: 'number' }
},
required: ['kb_name', 'query']
}
},
{
name: 'list_knowledge_bases',
description: 'List all available knowledge bases',
inputSchema: {
type: 'object',
properties: {}
}
}
// TODO: Add remaining 10 tools
]
},
id
};
}
// Call a tool
if (method === 'tools/call') {
const toolName = params?.name;
const args = params?.arguments || {};
let result;
switch (toolName) {
case 'search_knowledge_base':
result = await searchKnowledgeBase(env, args);
break;
case 'list_knowledge_bases':
result = await listKnowledgeBases(env);
break;
// TODO: Implement remaining tools:
// - list_knowledge_bases_by_type
// - create_knowledge_base
// - get_kb_stats
// - list_documents
// - ingest_agent_os_profile
// - get_agent_os_stats
// - get_standards
// - get_workflows
// - get_specs
// - get_product_context
default:
return {
jsonrpc: '2.0',
error: {
code: -32601,
message: `Unknown tool: ${toolName}`
},
id
};
}
return {
jsonrpc: '2.0',
result,
id
};
}
return {
jsonrpc: '2.0',
error: {
code: -32601,
message: `Unknown method: ${method}`
},
id
};
}
/**
* Search knowledge base using vector similarity
*/
async function searchKnowledgeBase(
env: Env,
args: { kb_name: string; query: string; top_k?: number }
): Promise<any> {
const { kb_name, query, top_k = 10 } = args;
// 1. Get KB ID
const kb = await env.DB.prepare(
'SELECT id, slug FROM knowledge_bases WHERE name = ? OR slug = ?'
).bind(kb_name, kb_name).first();
if (!kb) {
throw new Error(`Knowledge base not found: ${kb_name}`);
}
// 2. Generate query embedding using Workers AI
const embeddingResponse: any = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: query
});
const queryEmbedding = embeddingResponse.data[0];
// 3. Search vectors using Vectorize
const matches = await env.VECTORIZE.query(queryEmbedding, {
topK: top_k,
returnMetadata: true,
filter: { kb_id: kb.id } // Filter by knowledge base
});
// 4. Fetch full document content from D1
const documentIds = matches.matches.map(m => m.id);
if (documentIds.length === 0) {
return {
results: [],
kb_name,
query,
count: 0
};
}
const placeholders = documentIds.map(() => '?').join(',');
const documents = await env.DB.prepare(
`SELECT id, content, metadata FROM documents WHERE id IN (${placeholders})`
).bind(...documentIds).all();
// 5. Combine results
const results = matches.matches.map((match, idx) => {
const doc = documents.results.find(d => d.id === match.id);
return {
text: doc?.content || '',
metadata: doc?.metadata ? JSON.parse(doc.metadata) : {},
score: match.score,
distance: 1 - match.score
};
});
return {
results,
kb_name,
query,
count: results.length,
search_mode: 'vector'
};
}
/**
* List all knowledge bases
*/
async function listKnowledgeBases(env: Env): Promise<any> {
const result = await env.DB.prepare(
'SELECT id, name, slug, kb_type, description, created_at FROM knowledge_bases ORDER BY created_at DESC'
).all();
return {
knowledge_bases: result.results.map(kb => ({
id: kb.id,
name: kb.name,
slug: kb.slug,
kb_type: kb.kb_type,
description: kb.description,
created_at: kb.created_at
})),
count: result.results.length
};
}
/**
* Additional helper functions would go here:
* - getKBStats()
* - listDocuments()
* - createKnowledgeBase()
* - etc.
*/

View file

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2021",
"lib": ["ES2021"],
"module": "ES2022",
"moduleResolution": "node",
"types": ["@cloudflare/workers-types"],
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"jsx": "react",
"incremental": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View file

@ -0,0 +1,23 @@
name = "claude-os-mcp"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# D1 Database (SQLite)
[[d1_databases]]
binding = "DB"
database_name = "claude-os-db"
database_id = "your-database-id-here" # Get this from: wrangler d1 create claude-os-db
# Vectorize (Vector Search)
[[vectorize]]
binding = "VECTORIZE"
index_name = "claude-os-vectors"
index_id = "your-vectorize-id-here" # Get this from: wrangler vectorize create
# Workers AI (Embeddings & LLM)
[ai]
binding = "AI"
# Environment variables
[vars]
MCP_VERSION = "1.0.0"

View file

@ -0,0 +1,69 @@
version: '3.8'
services:
# Ollama service (required for embeddings & LLM)
ollama:
image: ollama/ollama:latest
container_name: claude-os-ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
# MCP Server (knowledge base access only)
mcp-server:
build:
context: ../.. # Build from root of claude-os
dockerfile: deployment/mcp-only/Dockerfile
container_name: claude-os-mcp
ports:
- "8051:8051"
volumes:
# Mount your existing database from local machine
- ../../data:/app/data:ro # Read-only: your populated DB
# Or use a volume if you want to persist changes
# - mcp_data:/app/data
environment:
# Database
SQLITE_DB_PATH: /app/data/claude-os.db
# MCP Server
MCP_SERVER_HOST: 0.0.0.0
MCP_SERVER_PORT: 8051
# Ollama connection
OLLAMA_HOST: http://ollama:11434
OLLAMA_EMBED_MODEL: nomic-embed-text
OLLAMA_MODEL: llama3.1:latest
# Optional: Authentication
# JWT_SECRET_KEY: your-secret-key-here
# JWT_ALGORITHM: HS256
# API_USERNAME: admin
# API_PASSWORD: changeme
# Optional: CORS (adjust for your other apps)
CORS_ORIGINS: "*"
depends_on:
ollama:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8051/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
volumes:
ollama_data:
driver: local
# Uncomment if you want persistent MCP data in Docker
# mcp_data:
# driver: local

View file

@ -0,0 +1,290 @@
/**
* Example: Integrating Claude OS MCP with a Node.js/Express Chat Application
*
* This example shows how to use the MCP server as a knowledge base
* backend for a chat API.
*/
const express = require('express');
const axios = require('axios');
class ClaudeOSKnowledgeBase {
constructor(baseUrl = 'http://localhost:8051', apiToken = null) {
this.baseUrl = baseUrl;
this.headers = { 'Content-Type': 'application/json' };
if (apiToken) {
this.headers['Authorization'] = `Bearer ${apiToken}`;
}
}
/**
* Make a JSON-RPC call to the MCP server
*/
async callTool(toolName, arguments, kbSlug = null) {
// Use KB-specific endpoint if slug provided
let url = `${this.baseUrl}/mcp`;
if (kbSlug) {
url = `${this.baseUrl}/mcp/kb/${kbSlug}`;
}
const payload = {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: toolName,
arguments: arguments
},
id: 1
};
try {
const response = await axios.post(url, payload, {
headers: this.headers
});
if (response.data.error) {
throw new Error(`MCP Error: ${JSON.stringify(response.data.error)}`);
}
return response.data.result || {};
} catch (error) {
throw new Error(`Failed to call MCP: ${error.message}`);
}
}
/**
* Search a knowledge base for relevant information
*/
async search(kbName, query, options = {}) {
const {
topK = 10,
useHybrid = false,
useRerank = false
} = options;
const result = await this.callTool('search_knowledge_base', {
kb_name: kbName,
query: query,
top_k: topK,
use_hybrid: useHybrid,
use_rerank: useRerank
});
return result.results || [];
}
/**
* List all available knowledge bases
*/
async listKnowledgeBases() {
const result = await this.callTool('list_knowledge_bases', {});
return result.knowledge_bases || [];
}
/**
* Get statistics for a knowledge base
*/
async getKBStats(kbName) {
return await this.callTool('get_kb_stats', { kb_name: kbName });
}
}
/**
* Create an Express.js API for the chat application
*/
function createChatAPI() {
const app = express();
app.use(express.json());
// Initialize MCP client
const mcp = new ClaudeOSKnowledgeBase(
process.env.MCP_BASE_URL || 'http://localhost:8051',
process.env.MCP_API_TOKEN || null
);
/**
* POST /api/chat
* Ask a question and get a knowledge-based response
*/
app.post('/api/chat', async (req, res) => {
try {
const { question, kb_name, options = {} } = req.body;
if (!question || !kb_name) {
return res.status(400).json({
error: 'Missing required fields: question and kb_name'
});
}
// Search the knowledge base
const results = await mcp.search(kb_name, question, {
topK: options.top_k || 5,
useHybrid: options.use_hybrid !== false, // default true
useRerank: options.use_rerank !== false // default true
});
if (results.length === 0) {
return res.json({
answer: "I couldn't find any relevant information in the knowledge base.",
sources: []
});
}
// Format the context
const sources = results.map((result, idx) => ({
id: idx + 1,
text: result.text,
filename: result.metadata?.filename || 'Unknown',
score: result.score || 0
}));
// In a real app, you'd call your LLM here with the context
// For this example, just return the sources
res.json({
answer: `Found ${results.length} relevant results. (In production, this would be processed by an LLM to generate a natural language answer)`,
sources: sources,
question: question
});
} catch (error) {
console.error('Chat error:', error);
res.status(500).json({
error: error.message
});
}
});
/**
* GET /api/knowledge-bases
* List all available knowledge bases
*/
app.get('/api/knowledge-bases', async (req, res) => {
try {
const kbs = await mcp.listKnowledgeBases();
res.json({
knowledge_bases: kbs.map(kb => ({
name: kb.name,
slug: kb.slug,
type: kb.kb_type,
description: kb.description
}))
});
} catch (error) {
console.error('Error listing KBs:', error);
res.status(500).json({
error: error.message
});
}
});
/**
* GET /api/knowledge-bases/:name/stats
* Get statistics for a specific knowledge base
*/
app.get('/api/knowledge-bases/:name/stats', async (req, res) => {
try {
const stats = await mcp.getKBStats(req.params.name);
res.json(stats);
} catch (error) {
console.error('Error getting stats:', error);
res.status(500).json({
error: error.message
});
}
});
/**
* GET /health
* Health check endpoint
*/
app.get('/health', async (req, res) => {
try {
// Try to list KBs to verify MCP connection
await mcp.listKnowledgeBases();
res.json({ status: 'ok', mcp_connected: true });
} catch (error) {
res.status(503).json({
status: 'degraded',
mcp_connected: false,
error: error.message
});
}
});
return app;
}
/**
* Example usage with streaming responses (for real-time chat)
*/
async function streamingChatExample(kbName, question) {
const mcp = new ClaudeOSKnowledgeBase('http://localhost:8051');
// 1. Search the knowledge base
const results = await mcp.search(kbName, question, {
topK: 5,
useHybrid: true,
useRerank: true
});
// 2. Build context for LLM
const context = results
.map((r, idx) => `[${idx + 1}] ${r.text}`)
.join('\n\n---\n\n');
console.log('Context for LLM:');
console.log(context);
// 3. In a real app, stream the LLM response
// using OpenAI SDK, Anthropic SDK, etc.
/*
const stream = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{
role: 'system',
content: `You are a helpful assistant. Answer the user's question based on the following context:\n\n${context}`
},
{
role: 'user',
content: question
}
],
stream: true
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
*/
}
// Run the server if this file is executed directly
if (require.main === module) {
const app = createChatAPI();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Chat API server running on port ${PORT}`);
console.log('');
console.log('Endpoints:');
console.log(` POST http://localhost:${PORT}/api/chat`);
console.log(` GET http://localhost:${PORT}/api/knowledge-bases`);
console.log(` GET http://localhost:${PORT}/api/knowledge-bases/:name/stats`);
console.log(` GET http://localhost:${PORT}/health`);
console.log('');
console.log('Example request:');
console.log(` curl -X POST http://localhost:${PORT}/api/chat \\`);
console.log(` -H "Content-Type: application/json" \\`);
console.log(` -d '{"question": "How do I configure auth?", "kb_name": "my-docs"}'`);
});
}
module.exports = {
ClaudeOSKnowledgeBase,
createChatAPI,
streamingChatExample
};

View file

@ -0,0 +1,226 @@
"""
Example: Integrating Claude OS MCP with a Chat Application
This example shows how to use the MCP server as a knowledge base
backend for a chat application.
"""
import requests
from typing import List, Dict, Optional
class ClaudeOSKnowledgeBase:
"""Client for Claude OS MCP Server"""
def __init__(
self,
base_url: str = "http://localhost:8051",
api_token: Optional[str] = None
):
self.base_url = base_url
self.headers = {"Content-Type": "application/json"}
if api_token:
self.headers["Authorization"] = f"Bearer {api_token}"
def _call_tool(
self,
tool_name: str,
arguments: Dict,
kb_slug: Optional[str] = None
) -> Dict:
"""Make a JSON-RPC call to the MCP server"""
# Use KB-specific endpoint if slug provided
url = f"{self.base_url}/mcp"
if kb_slug:
url = f"{self.base_url}/mcp/kb/{kb_slug}"
payload = {
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments
},
"id": 1
}
response = requests.post(url, json=payload, headers=self.headers)
response.raise_for_status()
result = response.json()
if "error" in result:
raise Exception(f"MCP Error: {result['error']}")
return result.get("result", {})
def search(
self,
kb_name: str,
query: str,
top_k: int = 10,
use_hybrid: bool = False,
use_rerank: bool = False
) -> List[Dict]:
"""
Search a knowledge base for relevant information
Args:
kb_name: Name of the knowledge base to search
query: User's question or search query
top_k: Number of results to return
use_hybrid: Enable hybrid search (vector + keyword)
use_rerank: Enable reranking for better results
Returns:
List of relevant document chunks with metadata
"""
result = self._call_tool(
"search_knowledge_base",
{
"kb_name": kb_name,
"query": query,
"top_k": top_k,
"use_hybrid": use_hybrid,
"use_rerank": use_rerank
}
)
# Parse the results
if isinstance(result, dict) and "results" in result:
return result["results"]
return []
def list_knowledge_bases(self) -> List[Dict]:
"""List all available knowledge bases"""
result = self._call_tool("list_knowledge_bases", {})
return result.get("knowledge_bases", [])
def get_kb_stats(self, kb_name: str) -> Dict:
"""Get statistics for a knowledge base"""
result = self._call_tool("get_kb_stats", {"kb_name": kb_name})
return result
class ChatWithKnowledge:
"""
Example chat application that uses Claude OS knowledge bases
"""
def __init__(self, mcp_client: ClaudeOSKnowledgeBase):
self.mcp = mcp_client
def answer_question(
self,
question: str,
kb_name: str,
use_hybrid: bool = True,
use_rerank: bool = True
) -> str:
"""
Answer a user question using the knowledge base
This is a simple example - in a real app, you'd:
1. Search the knowledge base
2. Pass results + question to your LLM
3. Generate a response grounded in the retrieved docs
"""
# 1. Search the knowledge base
results = self.mcp.search(
kb_name=kb_name,
query=question,
top_k=5,
use_hybrid=use_hybrid,
use_rerank=use_rerank
)
if not results:
return "I couldn't find any relevant information in the knowledge base."
# 2. Format the context from search results
context_parts = []
for idx, result in enumerate(results, 1):
text = result.get("text", "")
metadata = result.get("metadata", {})
filename = metadata.get("filename", "Unknown")
score = result.get("score", 0)
context_parts.append(
f"[{idx}] {filename} (relevance: {score:.2f})\n{text}\n"
)
context = "\n---\n".join(context_parts)
# 3. In a real app, you'd call your LLM here
# For this example, just return the context
response = f"""Based on the knowledge base, here's what I found:
{context}
Question: {question}
(In a production app, this would be processed by an LLM to generate a natural language answer)
"""
return response
def list_available_topics(self) -> str:
"""List all available knowledge bases"""
kbs = self.mcp.list_knowledge_bases()
if not kbs:
return "No knowledge bases available."
response = "Available topics:\n\n"
for kb in kbs:
name = kb.get("name", "Unknown")
description = kb.get("description", "No description")
kb_type = kb.get("kb_type", "generic")
response += f"- **{name}** ({kb_type})\n {description}\n\n"
return response
# Example usage
if __name__ == "__main__":
# Initialize the MCP client
mcp = ClaudeOSKnowledgeBase(
base_url="http://localhost:8051",
# api_token="your-jwt-token-here" # Uncomment if auth enabled
)
# Create chat interface
chat = ChatWithKnowledge(mcp)
# Example 1: List available knowledge bases
print("=" * 60)
print("AVAILABLE KNOWLEDGE BASES")
print("=" * 60)
print(chat.list_available_topics())
print()
# Example 2: Ask a question
print("=" * 60)
print("ASKING A QUESTION")
print("=" * 60)
question = "How do I configure authentication?"
kb_name = "my-docs" # Change to your actual KB name
print(f"Question: {question}")
print(f"Knowledge Base: {kb_name}")
print()
try:
answer = chat.answer_question(
question=question,
kb_name=kb_name,
use_hybrid=True,
use_rerank=True
)
print(answer)
except Exception as e:
print(f"Error: {e}")

View file

@ -0,0 +1,44 @@
# Minimal requirements for MCP server only (no coding tools)
# Web Framework
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
python-multipart>=0.0.6
# CORS & Rate Limiting
slowapi>=0.1.9
# Database & Vector Search
sqlite-vec>=0.1.0
# RAG Framework
llama-index-core>=0.10.0
llama-index-embeddings-ollama>=0.1.0
llama-index-llms-ollama>=0.1.0
# Reranking & Search
sentence-transformers>=2.2.0
rank-bm25>=0.2.2
# Document Processing
PyMuPDF>=1.23.0
pypdf>=3.17.0
python-magic>=0.4.27
# Code Parsing
tree-sitter>=0.20.0
tree-sitter-python>=0.20.0
tree-sitter-javascript>=0.20.0
tree-sitter-typescript>=0.20.0
# Utilities
python-dotenv>=1.0.0
pydantic>=2.0.0
pydantic-settings>=2.0.0
# Authentication (optional)
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
# Logging
python-json-logger>=2.0.7

178
deployment/mcp-only/setup.sh Executable file
View file

@ -0,0 +1,178 @@
#!/bin/bash
# Claude OS MCP-Only Setup Script
# This script helps with first-time deployment
set -e
echo "🚀 Claude OS MCP-Only Deployment Setup"
echo "======================================="
echo ""
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
echo "❌ Docker is not installed. Please install Docker first."
echo " Visit: https://docs.docker.com/get-docker/"
exit 1
fi
# Check if docker-compose is installed
if ! command -v docker-compose &> /dev/null; then
echo "❌ docker-compose is not installed. Please install docker-compose first."
echo " Visit: https://docs.docker.com/compose/install/"
exit 1
fi
echo "✅ Docker and docker-compose are installed"
echo ""
# Check if database exists
DB_PATH="../../data/claude-os.db"
if [ ! -f "$DB_PATH" ]; then
echo "⚠️ Database not found at: $DB_PATH"
echo ""
echo " This deployment expects your existing Claude OS database."
echo " Please ensure Claude OS is set up locally first, or"
echo " adjust the volume mount in docker-compose.yml"
echo ""
read -p " Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
else
echo "✅ Database found at: $DB_PATH"
# Get database size
DB_SIZE=$(du -h "$DB_PATH" | cut -f1)
echo " Size: $DB_SIZE"
fi
echo ""
# Create .env from example if it doesn't exist
if [ ! -f ".env" ]; then
echo "📝 Creating .env from .env.example..."
cp .env.example .env
echo "✅ Created .env file"
echo " Please review and update .env with your settings"
echo ""
else
echo "✅ .env file already exists"
echo ""
fi
# Prompt for CORS origins
echo "🔧 Configuration"
echo "---------------"
read -p "Enter CORS origins (comma-separated, or * for all) [*]: " CORS_INPUT
CORS_INPUT=${CORS_INPUT:-*}
# Update .env
sed -i.bak "s|CORS_ORIGINS=.*|CORS_ORIGINS=$CORS_INPUT|" .env && rm .env.bak
echo "✅ CORS_ORIGINS set to: $CORS_INPUT"
echo ""
# Ask about authentication
read -p "Enable authentication? (y/n) [n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
# Generate a random secret
JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "CHANGE-THIS-SECRET-$(date +%s)")
read -p "Enter API username [admin]: " API_USER
API_USER=${API_USER:-admin}
read -s -p "Enter API password: " API_PASS
echo
if [ -z "$API_PASS" ]; then
echo "❌ Password cannot be empty"
exit 1
fi
# Uncomment and set auth variables in .env
sed -i.bak "s|# JWT_SECRET_KEY=.*|JWT_SECRET_KEY=$JWT_SECRET|" .env && rm .env.bak
sed -i.bak "s|# JWT_ALGORITHM=.*|JWT_ALGORITHM=HS256|" .env && rm .env.bak
sed -i.bak "s|# API_USERNAME=.*|API_USERNAME=$API_USER|" .env && rm .env.bak
sed -i.bak "s|# API_PASSWORD=.*|API_PASSWORD=$API_PASS|" .env && rm .env.bak
echo "✅ Authentication enabled"
else
echo "⚠️ Authentication disabled (not recommended for production)"
fi
echo ""
# Start services
echo "🐳 Starting Docker containers..."
echo "-------------------------------"
docker-compose up -d
echo ""
echo "⏳ Waiting for services to start..."
sleep 5
# Wait for Ollama to be ready
echo "Checking Ollama status..."
OLLAMA_READY=0
for i in {1..30}; do
if docker exec claude-os-ollama ollama list &>/dev/null; then
OLLAMA_READY=1
break
fi
echo " Waiting for Ollama... ($i/30)"
sleep 2
done
if [ $OLLAMA_READY -eq 0 ]; then
echo "⚠️ Ollama is taking longer than expected to start"
echo " You can check logs with: docker-compose logs ollama"
else
echo "✅ Ollama is ready"
fi
echo ""
# Pull required models
echo "📥 Pulling required Ollama models..."
echo "-----------------------------------"
echo "This may take a few minutes on first run..."
echo ""
echo "Pulling nomic-embed-text (embeddings)..."
docker exec claude-os-ollama ollama pull nomic-embed-text
echo "Pulling llama3.1:latest (LLM)..."
docker exec claude-os-ollama ollama pull llama3.1:latest
echo ""
echo "✅ Models downloaded successfully"
echo ""
# Check MCP server health
echo "🏥 Checking MCP server health..."
sleep 3
if curl -f http://localhost:8051/health &>/dev/null; then
echo "✅ MCP server is healthy!"
else
echo "⚠️ MCP server is not responding yet"
echo " Check logs with: docker-compose logs mcp-server"
fi
echo ""
echo "========================================="
echo "✨ Setup Complete!"
echo "========================================="
echo ""
echo "Your MCP server is now running at:"
echo " http://localhost:8051"
echo ""
echo "Test it with:"
echo " curl http://localhost:8051/health"
echo ""
echo "View logs:"
echo " docker-compose logs -f mcp-server"
echo ""
echo "Stop services:"
echo " docker-compose down"
echo ""
echo "See README.md for usage examples and API documentation"
echo ""