#!/bin/bash set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Banner echo -e "${BLUE}" echo "╔═══════════════════════════════════════════════════════╗" echo "║ ║" echo "║ Ollama MCP Integration Installer ║" echo "║ Kubernetes + Gitea MCP Servers ║" echo "║ ║" echo "╚═══════════════════════════════════════════════════════╝" echo -e "${NC}" # Functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } check_command() { if ! command -v $1 &> /dev/null; then log_error "$1 is not installed" return 1 fi log_success "$1 is installed" return 0 } # Check prerequisites log_info "Checking prerequisites..." MISSING_DEPS=0 if ! check_command docker; then MISSING_DEPS=1 fi if ! check_command docker-compose; then if ! docker compose version &> /dev/null; then log_error "docker-compose or 'docker compose' is not available" MISSING_DEPS=1 else log_success "docker compose plugin is installed" DOCKER_COMPOSE_CMD="docker compose" fi else DOCKER_COMPOSE_CMD="docker-compose" fi if ! check_command git; then MISSING_DEPS=1 fi if ! check_command curl; then MISSING_DEPS=1 fi if [ $MISSING_DEPS -eq 1 ]; then log_error "Missing required dependencies. Please install them first." echo "" echo "Ubuntu/Debian:" echo " sudo apt-get update" echo " sudo apt-get install -y docker.io docker-compose git curl" echo "" echo "CentOS/RHEL:" echo " sudo yum install -y docker docker-compose git curl" echo " sudo systemctl start docker" exit 1 fi # Installation directory INSTALL_DIR="${INSTALL_DIR:-$HOME/ollama-mcp}" log_info "Installation directory: $INSTALL_DIR" # Create directory structure log_info "Creating directory structure..." mkdir -p "$INSTALL_DIR"/{config,mcp-kubernetes,mcp-gitea} cd "$INSTALL_DIR" # Clone or update repository if [ -d "$INSTALL_DIR/.git" ]; then log_info "Updating existing repository..." git pull origin main || log_warning "Failed to pull updates" else log_info "Cloning repository..." git clone https://git.thedevops.dev/admin/k3s-gitops.git temp_repo cp -r temp_repo/apps/ollama-mcp/* . rm -rf temp_repo fi # Environment configuration log_info "Configuring environment..." if [ ! -f .env ]; then cat > .env << 'EOF' # Gitea Configuration GITEA_URL=https://git.thedevops.dev GITEA_TOKEN= GITEA_OWNER=admin # Kubernetes Configuration K8S_CONTEXT= K8S_NAMESPACE=default # MCP Server Ports MCP_K8S_PORT=3001 MCP_GITEA_PORT=3002 EOF log_success "Created .env file" else log_info ".env file already exists, skipping..." fi # Kubeconfig setup log_info "Setting up Kubernetes configuration..." if [ -f "$HOME/.kube/config" ]; then cp "$HOME/.kube/config" config/kubeconfig chmod 600 config/kubeconfig log_success "Copied kubeconfig from ~/.kube/config" else log_warning "No kubeconfig found at ~/.kube/config" read -p "Enter path to your kubeconfig file (or press Enter to skip): " KUBECONFIG_PATH if [ -n "$KUBECONFIG_PATH" ] && [ -f "$KUBECONFIG_PATH" ]; then cp "$KUBECONFIG_PATH" config/kubeconfig chmod 600 config/kubeconfig log_success "Copied kubeconfig from $KUBECONFIG_PATH" else log_warning "Skipping kubeconfig setup. You'll need to configure it manually." fi fi # Gitea token setup log_info "Configuring Gitea access..." echo "" echo "To get your Gitea token:" echo "1. Go to https://git.thedevops.dev" echo "2. Settings → Applications → Generate New Token" echo "3. Select scopes: repo, admin:org, write:repository" echo "" read -p "Enter your Gitea token (or press Enter to configure later): " GITEA_TOKEN if [ -n "$GITEA_TOKEN" ]; then # Update .env file sed -i "s|GITEA_TOKEN=.*|GITEA_TOKEN=$GITEA_TOKEN|" .env log_success "Gitea token configured" else log_warning "Gitea token not set. Update .env file manually before starting." fi # Create MCP Kubernetes server files log_info "Creating MCP Kubernetes server..." cat > mcp-kubernetes/package.json << 'EOF' { "name": "mcp-kubernetes-server", "version": "1.0.0", "description": "MCP Server for Kubernetes integration with Ollama", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "@kubernetes/client-node": "^0.21.0", "express": "^4.18.2" } } EOF cat > mcp-kubernetes/index.js << 'EOF' const express = require('express'); const k8s = require('@kubernetes/client-node'); const app = express(); const port = process.env.PORT || 3000; // Kubernetes client setup const kc = new k8s.KubeConfig(); kc.loadFromDefault(); const k8sApi = kc.makeApiClient(k8s.CoreV1Api); const k8sAppsApi = kc.makeApiClient(k8s.AppsV1Api); app.use(express.json()); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'mcp-kubernetes' }); }); // List pods app.post('/api/pods/list', async (req, res) => { try { const namespace = req.body.namespace || 'default'; const response = await k8sApi.listNamespacedPod(namespace); res.json(response.body); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get pod logs app.post('/api/pods/logs', async (req, res) => { try { const { name, namespace, container, tailLines } = req.body; const response = await k8sApi.readNamespacedPodLog( name, namespace || 'default', container, undefined, undefined, undefined, undefined, undefined, undefined, tailLines || 100 ); res.json({ logs: response.body }); } catch (error) { res.status(500).json({ error: error.message }); } }); // List deployments app.post('/api/deployments/list', async (req, res) => { try { const namespace = req.body.namespace || 'default'; const response = await k8sAppsApi.listNamespacedDeployment(namespace); res.json(response.body); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get deployment app.post('/api/deployments/get', async (req, res) => { try { const { name, namespace } = req.body; const response = await k8sAppsApi.readNamespacedDeployment( name, namespace || 'default' ); res.json(response.body); } catch (error) { res.status(500).json({ error: error.message }); } }); // List services app.post('/api/services/list', async (req, res) => { try { const namespace = req.body.namespace || 'default'; const response = await k8sApi.listNamespacedService(namespace); res.json(response.body); } catch (error) { res.status(500).json({ error: error.message }); } }); // List namespaces app.post('/api/namespaces/list', async (req, res) => { try { const response = await k8sApi.listNamespace(); res.json(response.body); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(port, '0.0.0.0', () => { console.log(`MCP Kubernetes server listening on port ${port}`); }); EOF cat > mcp-kubernetes/Dockerfile << 'EOF' FROM node:20-alpine WORKDIR /app COPY package.json ./ RUN npm install --production COPY index.js ./ EXPOSE 3000 CMD ["npm", "start"] EOF # Create MCP Gitea server files log_info "Creating MCP Gitea server..." cat > mcp-gitea/package.json << 'EOF' { "name": "mcp-gitea-server", "version": "1.0.0", "description": "MCP Server for Gitea integration with Ollama", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "axios": "^1.6.0", "express": "^4.18.2" } } EOF cat > mcp-gitea/index.js << 'EOF' const express = require('express'); const axios = require('axios'); const app = express(); const port = process.env.PORT || 3000; const GITEA_URL = process.env.GITEA_URL || 'https://git.thedevops.dev'; const GITEA_TOKEN = process.env.GITEA_TOKEN; const GITEA_OWNER = process.env.GITEA_OWNER || 'admin'; const gitea = axios.create({ baseURL: `${GITEA_URL}/api/v1`, headers: { 'Authorization': `token ${GITEA_TOKEN}`, 'Content-Type': 'application/json' } }); app.use(express.json()); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'mcp-gitea' }); }); // List repositories app.post('/api/repos/list', async (req, res) => { try { const owner = req.body.owner || GITEA_OWNER; const response = await gitea.get(`/users/${owner}/repos`); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get file content app.post('/api/repos/file/get', async (req, res) => { try { const { owner, repo, path, branch } = req.body; const response = await gitea.get( `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, { params: { ref: branch || 'main' } } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); // List directory contents app.post('/api/repos/tree/get', async (req, res) => { try { const { owner, repo, path, branch } = req.body; const response = await gitea.get( `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path || ''}`, { params: { ref: branch || 'main' } } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); // Create file app.post('/api/repos/file/create', async (req, res) => { try { const { owner, repo, path, content, message, branch } = req.body; const response = await gitea.post( `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, { content: Buffer.from(content).toString('base64'), message: message || 'Create file via MCP', branch: branch || 'main' } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); // Update file app.post('/api/repos/file/update', async (req, res) => { try { const { owner, repo, path, content, message, sha, branch } = req.body; const response = await gitea.put( `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, { content: Buffer.from(content).toString('base64'), message: message || 'Update file via MCP', sha: sha, branch: branch || 'main' } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); // List branches app.post('/api/repos/branches/list', async (req, res) => { try { const { owner, repo } = req.body; const response = await gitea.get( `/repos/${owner || GITEA_OWNER}/${repo}/branches` ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(port, '0.0.0.0', () => { console.log(`MCP Gitea server listening on port ${port}`); }); EOF cat > mcp-gitea/Dockerfile << 'EOF' FROM node:20-alpine WORKDIR /app COPY package.json ./ RUN npm install --production COPY index.js ./ EXPOSE 3000 CMD ["npm", "start"] EOF # Build and start containers log_info "Building Docker images..." $DOCKER_COMPOSE_CMD build log_info "Starting MCP servers..." $DOCKER_COMPOSE_CMD up -d # Wait for services to be healthy log_info "Waiting for services to start..." sleep 10 # Check status log_info "Checking service status..." $DOCKER_COMPOSE_CMD ps # Test endpoints log_info "Testing MCP servers..." if curl -s http://localhost:3001/health > /dev/null 2>&1; then log_success "MCP Kubernetes server is running" else log_warning "MCP Kubernetes server is not responding" fi if curl -s http://localhost:3002/health > /dev/null 2>&1; then log_success "MCP Gitea server is running" else log_warning "MCP Gitea server is not responding" fi # Final instructions echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ Installation Complete! ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" log_info "MCP servers are running at:" echo " - Kubernetes MCP: http://localhost:3001" echo " - Gitea MCP: http://localhost:3002" echo "" log_info "Next steps:" echo " 1. Configure Ollama to use these MCP endpoints" echo " 2. Update .env file with your Gitea token if not done" echo " 3. Verify kubeconfig in config/kubeconfig" echo "" log_info "Useful commands:" echo " View logs: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD logs -f" echo " Restart: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD restart" echo " Stop: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD down" echo " Status: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD ps" echo "" log_info "Documentation: $INSTALL_DIR/README.md" echo ""