Add MCP-only deployment package for standalone knowledge base server

This commit adds a complete deployment solution for running Claude OS's
MCP (Model Context Protocol) server as a standalone service, separate
from the full coding environment. This allows other applications to
access the knowledge bases without requiring the CLI or coding tools.

Features:
- Docker-based deployment with Ollama integration
- Automated setup script with model pulling
- Read-only database mounting by default
- CORS and authentication configuration
- Complete API documentation and examples
- Python and JavaScript client examples

Also includes alternative Cloudflare Workers implementation:
- TypeScript starter template
- D1 and Vectorize integration examples
- Migration scripts and documentation
- Deployment comparison guide

Files added:
- deployment/mcp-only/Dockerfile
- deployment/mcp-only/docker-compose.yml
- deployment/mcp-only/requirements.txt
- deployment/mcp-only/setup.sh
- deployment/mcp-only/.env.example
- deployment/mcp-only/.dockerignore
- deployment/mcp-only/README.md
- deployment/mcp-only/DEPLOYMENT_COMPARISON.md
- deployment/mcp-only/examples/chat-integration.py
- deployment/mcp-only/examples/chat-integration.js
- deployment/mcp-only/cloudflare-workers/*

This addresses the need to expose MCP data to other applications
(knowledge base access, live chat) without deploying the full
Claude OS environment to production servers.
This commit is contained in:
Claude 2025-11-05 17:46:00 +00:00
parent 816109b990
commit 0e5687b0f1
No known key found for this signature in database
14 changed files with 2455 additions and 0 deletions

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 ""