feat(mcp): add 8 new validated MCP servers

Add actual-mcp, beszel-mcp, gitea-mcp, ha-mcp, limesurvey-mcp,
linkwarden-mcp, mcp-grafana, and superset-mcp with full validation.

Key fixes applied:
- linkwarden-mcp: Added 'stdio' subcommand to ENTRYPOINT
- mcp-grafana: Fixed build path (./cmd/mcp-grafana) and added '--transport stdio' flag

All 8 servers validated with MCP protocol handshake.
Working MCP server count: 24 (up from 16)

💘 Generated with Crush

Assisted-by: GLM-5 via Crush <crush@charm.land>
This commit is contained in:
Charles N Wyble
2026-02-20 10:41:56 -05:00
parent cde8838133
commit d80eff6df6
19 changed files with 503 additions and 2 deletions

View File

@@ -2,7 +2,7 @@
Tracking the setup and validation of MCP/LSP servers via Docker Compose.
Last validated: 2026-02-19
Last validated: 2026-02-20
## Repository URLs Verified (2026-02-19)
@@ -48,8 +48,10 @@ All 32 vendor repositories have been verified and correctly cloned. CloneVendorR
## Validation Summary
**Working MCP Servers (16 validated with MCP handshake):**
**Working MCP Servers (24 validated with MCP handshake):**
- ✓ actual-mcp: Working (Actual Budget MCP) - requires ACTUAL_SERVER_URL, ACTUAL_PASSWORD, ACTUAL_BUDGET_SYNC_ID env vars
- ✓ audiobook-mcp: Working (audiobook-library v1.1.0) - requires AUDIOBOOK_ROOT env var
- ✓ beszel-mcp: Working (beszel-mcp) - requires BESZEL_URL, BESZEL_USERNAME, BESZEL_PASSWORD env vars
- ✓ bitwarden-mcp: Working (Bitwarden MCP Server) - requires Bitwarden credentials
- ✓ blender-mcp: Working (BlenderMCP v1.25.0) - requires Blender with addon running
- ✓ cloudron-mcp: Working (cloudron-mcp v0.1.0) - requires CLOUDRON_URL env var
@@ -60,11 +62,17 @@ All 32 vendor repositories have been verified and correctly cloned. CloneVendorR
- ✓ freecad-mcp: Working (FreeCADMCP v1.25.0) - requires FreeCAD with addon running
- ✓ ghost-mcp: Working (ghost-mcp-ts v1.0.0) - requires Ghost CMS credentials
- ✓ gimp-mcp: Working (GimpMCP v1.10.1) - requires GIMP with server running
- ✓ gitea-mcp: Working (gitea-mcp) - requires GITEA_URL and GITEA_TOKEN env vars
- ✓ grafana-mcp: Working (mcp-grafana) - requires GRAFANA_URL and GRAFANA_TOKEN env vars
- ✓ ha-mcp: Working (Home Assistant MCP) - requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN env vars
- ✓ kubernetes-mcp: Working (mcp-k8s Go binary) - requires kubeconfig mounted at /root/.kube/config
- ✓ limesurvey-mcp: Working (limesurvey-mcp) - requires LIMESURVEY_URL, LIMESURVEY_USERNAME, LIMESURVEY_PASSWORD env vars
- ✓ linkwarden-mcp: Working (linkwarden-mcp v0.0.1) - requires LINKWARDEN_URL and LINKWARDEN_TOKEN env vars
- ✓ terraform-mcp: Working (terraform-mcp-server v0.4.0) - requires credentials for HCP Terraform
- ✓ matomo-mcp: Working (matomo-mcp-client v1.0.0) - connects to openmost.io hosted service (59 tools, 31 prompts)
- ✓ mcp-redmine: Working (Redmine MCP server v1.25.0) - requires REDMINE_URL for actual usage
- ✓ paperless-mcp: Working (paperless-ngx v1.0.0) - requires baseUrl/token CLI args
- ✓ superset-mcp: Working (superset-mcp) - requires SUPERSET_URL, SUPERSET_USERNAME, SUPERSET_PASSWORD env vars
**Runtime Connection Required (crash before MCP protocol):**
- ✗ nextcloud-mcp: Requires reachable OAuth endpoint - crashes on startup with connection refused

View File

@@ -125,6 +125,46 @@
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-ansible-wrapper.sh",
"timeout": 60
},
"actual": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-actual-wrapper.sh",
"timeout": 60
},
"beszel": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-beszel-wrapper.sh",
"timeout": 60
},
"gitea": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-gitea-wrapper.sh",
"timeout": 60
},
"grafana": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-grafana-wrapper.sh",
"timeout": 60
},
"homeassistant": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-ha-wrapper.sh",
"timeout": 60
},
"limesurvey": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-limesurvey-wrapper.sh",
"timeout": 60
},
"linkwarden": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-linkwarden-wrapper.sh",
"timeout": 60
},
"superset": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-superset-wrapper.sh",
"timeout": 60
},
"postizz": {
"type": "stdio",
"command": "/home/charles/Projects/KNEL-AIMiddleware/mcp-postizz-wrapper.sh",

View File

@@ -545,6 +545,165 @@ services:
profiles:
- dev
# ==========================================
# Financial & Budgeting (1 server)
# ==========================================
# Actual Budget MCP - Budget management
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
actual-mcp:
image: kneldevstack-aimiddleware-actual-mcp
build:
context: ./vendor/actual-mcp
dockerfile: ../../dockerfiles/actual-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-actual-mcp
restart: "no"
environment:
- ACTUAL_SERVER_URL=${ACTUAL_SERVER_URL}
- ACTUAL_PASSWORD=${ACTUAL_PASSWORD}
- ACTUAL_BUDGET_SYNC_ID=${ACTUAL_BUDGET_SYNC_ID}
profiles:
- ops
# ==========================================
# System Monitoring (1 server)
# ==========================================
# Beszel MCP - System monitoring tool
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
beszel-mcp:
image: kneldevstack-aimiddleware-beszel-mcp
build:
context: ./vendor/beszel-mcp
dockerfile: ../../dockerfiles/beszel-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-beszel-mcp
restart: "no"
environment:
- PYTHONUNBUFFERED=1
- BESZEL_URL=${BESZEL_URL}
- BESZEL_USERNAME=${BESZEL_USERNAME}
- BESZEL_PASSWORD=${BESZEL_PASSWORD}
profiles:
- ops
# ==========================================
# Git Hosting (1 server)
# ==========================================
# Gitea MCP - Git hosting integration
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
gitea-mcp:
image: kneldevstack-aimiddleware-gitea-mcp
build:
context: ./vendor/gitea-mcp
dockerfile: ../../dockerfiles/gitea-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-gitea-mcp
restart: "no"
environment:
- GITEA_URL=${GITEA_URL}
- GITEA_TOKEN=${GITEA_TOKEN}
profiles:
- ops
# ==========================================
# Home Automation (1 server)
# ==========================================
# Home Assistant MCP - Complete HA control
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
ha-mcp:
image: kneldevstack-aimiddleware-ha-mcp
build:
context: ./vendor/ha-mcp
dockerfile: ../../dockerfiles/ha-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-ha-mcp
restart: "no"
environment:
- PYTHONUNBUFFERED=1
- HOMEASSISTANT_URL=${HOMEASSISTANT_URL}
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
profiles:
- ops
# ==========================================
# Survey Tools (1 server)
# ==========================================
# LimeSurvey MCP - Survey management
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
limesurvey-mcp:
image: kneldevstack-aimiddleware-limesurvey-mcp
build:
context: ./vendor/limesurvey-mcp
dockerfile: ../../dockerfiles/limesurvey-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-limesurvey-mcp
restart: "no"
environment:
- LIMESURVEY_URL=${LIMESURVEY_URL}
- LIMESURVEY_USERNAME=${LIMESURVEY_USERNAME}
- LIMESURVEY_PASSWORD=${LIMESURVEY_PASSWORD}
profiles:
- ops
# ==========================================
# Bookmark Management (1 server)
# ==========================================
# Linkwarden MCP - Bookmark management
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
linkwarden-mcp:
image: kneldevstack-aimiddleware-linkwarden-mcp
build:
context: ./vendor/linkwarden-mcp-server
dockerfile: ../../dockerfiles/linkwarden-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-linkwarden-mcp
restart: "no"
environment:
- LINKWARDEN_URL=${LINKWARDEN_URL}
- LINKWARDEN_TOKEN=${LINKWARDEN_TOKEN}
profiles:
- ops
# ==========================================
# Observability (1 server)
# ==========================================
# Grafana MCP - Dashboard and observability
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
mcp-grafana:
image: kneldevstack-aimiddleware-mcp-grafana
build:
context: ./vendor/mcp-grafana
dockerfile: ../../dockerfiles/mcp-grafana/Dockerfile
container_name: kneldevstack-aimiddleware-mcp-grafana
restart: "no"
environment:
- GRAFANA_URL=${GRAFANA_URL}
- GRAFANA_TOKEN=${GRAFANA_TOKEN}
profiles:
- ops
# ==========================================
# Business Intelligence (1 server)
# ==========================================
# Superset MCP - Apache Superset integration
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
superset-mcp:
image: kneldevstack-aimiddleware-superset-mcp
build:
context: ./vendor/superset-mcp
dockerfile: ../../dockerfiles/superset-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-superset-mcp
restart: "no"
environment:
- PYTHONUNBUFFERED=1
- SUPERSET_URL=${SUPERSET_URL}
- SUPERSET_USERNAME=${SUPERSET_USERNAME}
- SUPERSET_PASSWORD=${SUPERSET_PASSWORD}
profiles:
- ops
volumes:
ghidra-projects:
ghidra-data:

View File

@@ -0,0 +1,13 @@
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
CMD ["node", "build/index.js"]

View File

@@ -0,0 +1,14 @@
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY . .
RUN uv venv && uv pip install --no-cache -e .
ENV PYTHONUNBUFFERED=1
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT ["python", "-m", "beszel_mcp"]

View File

@@ -0,0 +1,18 @@
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o gitea-mcp .
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/gitea-mcp /usr/local/bin/gitea-mcp
ENTRYPOINT ["/usr/local/bin/gitea-mcp"]

View File

@@ -0,0 +1,14 @@
FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY . .
RUN uv venv && uv pip install --no-cache -e .
ENV PYTHONUNBUFFERED=1
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT ["python", "-m", "ha_mcp"]

View File

@@ -0,0 +1,13 @@
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]

View File

@@ -0,0 +1,18 @@
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o linkwarden-mcp-server ./cmd/linkwarden-mcp-server
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/linkwarden-mcp-server /usr/local/bin/linkwarden-mcp-server
ENTRYPOINT ["/usr/local/bin/linkwarden-mcp-server", "stdio"]

View File

@@ -0,0 +1,18 @@
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o mcp-grafana ./cmd/mcp-grafana
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/mcp-grafana /usr/local/bin/mcp-grafana
ENTRYPOINT ["/usr/local/bin/mcp-grafana", "--transport", "stdio"]

View File

@@ -0,0 +1,14 @@
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY . .
RUN uv venv && uv pip install --no-cache -e .
ENV PYTHONUNBUFFERED=1
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT ["python", "main.py"]

22
mcp-actual-wrapper.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# Wrapper script for actual-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-actual-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-actual-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e ACTUAL_SERVER_URL="${ACTUAL_SERVER_URL}" \
-e ACTUAL_PASSWORD="${ACTUAL_PASSWORD}" \
-e ACTUAL_BUDGET_SYNC_ID="${ACTUAL_BUDGET_SYNC_ID}" \
"${IMAGE_NAME}" "$@"

22
mcp-beszel-wrapper.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# Wrapper script for beszel-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-beszel-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-beszel-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e BESZEL_URL="${BESZEL_URL}" \
-e BESZEL_USERNAME="${BESZEL_USERNAME}" \
-e BESZEL_PASSWORD="${BESZEL_PASSWORD}" \
"${IMAGE_NAME}" "$@"

21
mcp-gitea-wrapper.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Wrapper script for gitea-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-gitea-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-gitea-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e GITEA_URL="${GITEA_URL}" \
-e GITEA_TOKEN="${GITEA_TOKEN}" \
"${IMAGE_NAME}" "$@"

21
mcp-grafana-wrapper.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Wrapper script for mcp-grafana
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-mcp-grafana-crush"
IMAGE_NAME="kneldevstack-aimiddleware-mcp-grafana"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e GRAFANA_URL="${GRAFANA_URL}" \
-e GRAFANA_TOKEN="${GRAFANA_TOKEN}" \
"${IMAGE_NAME}" "$@"

21
mcp-ha-wrapper.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Wrapper script for ha-mcp (Home Assistant)
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-ha-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-ha-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e HOMEASSISTANT_URL="${HOMEASSISTANT_URL}" \
-e HOMEASSISTANT_TOKEN="${HOMEASSISTANT_TOKEN}" \
"${IMAGE_NAME}" "$@"

22
mcp-limesurvey-wrapper.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# Wrapper script for limesurvey-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-limesurvey-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-limesurvey-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e LIMESURVEY_URL="${LIMESURVEY_URL}" \
-e LIMESURVEY_USERNAME="${LIMESURVEY_USERNAME}" \
-e LIMESURVEY_PASSWORD="${LIMESURVEY_PASSWORD}" \
"${IMAGE_NAME}" "$@"

21
mcp-linkwarden-wrapper.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Wrapper script for linkwarden-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-linkwarden-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-linkwarden-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e LINKWARDEN_URL="${LINKWARDEN_URL}" \
-e LINKWARDEN_TOKEN="${LINKWARDEN_TOKEN}" \
"${IMAGE_NAME}" "$@"

22
mcp-superset-wrapper.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# Wrapper script for superset-mcp
# Ensures clean container with proper name
CONTAINER_NAME="kneldevstack-aimiddleware-superset-mcp-crush"
IMAGE_NAME="kneldevstack-aimiddleware-superset-mcp"
# Force remove existing container if it exists (in any state)
if docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "${CONTAINER_NAME}" 2>/dev/null
# Wait for container to be fully removed
while docker ps -a --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; do
sleep 0.05
done
fi
# Start MCP server with explicit name and environment
exec docker run -i --rm --name "${CONTAINER_NAME}" \
-e SUPERSET_URL="${SUPERSET_URL}" \
-e SUPERSET_USERNAME="${SUPERSET_USERNAME}" \
-e SUPERSET_PASSWORD="${SUPERSET_PASSWORD}" \
"${IMAGE_NAME}" "$@"