Merge branch 'main' of https://github.com/brobertsaz/claude-os
This commit is contained in:
commit
ea779fba43
15 changed files with 2456 additions and 42 deletions
43
README.md
43
README.md
|
|
@ -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/
|
||||||
|
|
|
||||||
48
deployment/mcp-only/.env.example
Normal file
48
deployment/mcp-only/.env.example
Normal 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
|
||||||
382
deployment/mcp-only/DEPLOYMENT_COMPARISON.md
Normal file
382
deployment/mcp-only/DEPLOYMENT_COMPARISON.md
Normal 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)
|
||||||
53
deployment/mcp-only/Dockerfile
Normal file
53
deployment/mcp-only/Dockerfile
Normal 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"]
|
||||||
460
deployment/mcp-only/README.md
Normal file
460
deployment/mcp-only/README.md
Normal 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!
|
||||||
328
deployment/mcp-only/cloudflare-workers/README.md
Normal file
328
deployment/mcp-only/cloudflare-workers/README.md
Normal 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)
|
||||||
28
deployment/mcp-only/cloudflare-workers/package.json
Normal file
28
deployment/mcp-only/cloudflare-workers/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
303
deployment/mcp-only/cloudflare-workers/src/index.ts
Normal file
303
deployment/mcp-only/cloudflare-workers/src/index.ts
Normal 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.
|
||||||
|
*/
|
||||||
23
deployment/mcp-only/cloudflare-workers/tsconfig.json
Normal file
23
deployment/mcp-only/cloudflare-workers/tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
23
deployment/mcp-only/cloudflare-workers/wrangler.toml
Normal file
23
deployment/mcp-only/cloudflare-workers/wrangler.toml
Normal 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"
|
||||||
69
deployment/mcp-only/docker-compose.yml
Normal file
69
deployment/mcp-only/docker-compose.yml
Normal 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
|
||||||
290
deployment/mcp-only/examples/chat-integration.js
Normal file
290
deployment/mcp-only/examples/chat-integration.js
Normal 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
|
||||||
|
};
|
||||||
226
deployment/mcp-only/examples/chat-integration.py
Normal file
226
deployment/mcp-only/examples/chat-integration.py
Normal 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}")
|
||||||
44
deployment/mcp-only/requirements.txt
Normal file
44
deployment/mcp-only/requirements.txt
Normal 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
178
deployment/mcp-only/setup.sh
Executable 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 ""
|
||||||
Loading…
Add table
Reference in a new issue