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:
parent
816109b990
commit
0e5687b0f1
14 changed files with 2455 additions and 0 deletions
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