522 lines
14 KiB
Bash
522 lines
14 KiB
Bash
#!/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 ""
|