feat: add Crush MCP server configurations and validate multiple MCP servers

- Add crush.json with comprehensive MCP configurations for Crush AI assistant
- Configure stdio-based MCPs: penpot, context7, docker, drawio, redmine
- Configure HTTP-based MCP: nextcloud (port 8083 with SSE endpoint)
- Fix mcp-redmine Dockerfile with correct python module entrypoint
- Fix nextcloud-mcp Dockerfile to handle .dockerignore blocking observability
- Fix drawio-mcp Dockerfile to use pnpm and correct build directory
- Update docker-compose.yml with proper MCP server configurations
- Add environment variable configuration for MCPs requiring external services
- Create MCP validation script to test servers with protocol messages
- Update STATUS.md with confirmed working MCP servers and their requirements
- Validate: penpot, context7, docker, drawio, redmine, nextcloud (HTTP)
- Document required env vars for ghost, imap, proxmox, penpot MCPs
- Configure Crush to use both stdio (docker run) and HTTP endpoints
This commit is contained in:
2026-01-22 16:05:08 -05:00
parent 1b01b3303b
commit a0ca7c9eaf
8 changed files with 322 additions and 27 deletions

129
LSP_SETUP.md Normal file
View File

@@ -0,0 +1,129 @@
# LSP Container Configuration
## Status
- **bash-language-server**: ✅ Working - Fixed crash by adding `start` command
- **docker-language-server**: ✅ Working - Fixed by adding `start --stdio` command
- **marksman**: ✅ Working - Fixed by adding `server` command
## Architecture Notes
### Why LSP Containers Don't Run Continuously
The bash, docker, and markdown LSP servers are **stdio-based LSP servers**. This means:
1. They communicate via stdin/stdout (not network sockets)
2. Each LSP client needs its own process instance
3. They exit when the client disconnects (end of stdin)
This is **by design** and is the standard way LSP servers work:
```
Crush Session 1 → docker run -i bash-lsp → [bash-language-server process]
Crush Session 2 → docker run -i bash-lsp → [bash-language-server process]
```
Each session needs its own container instance because the stdio connection is 1-to-1.
### Startup Performance
Despite creating new containers for each session, startup is fast because:
1. **Docker images are pre-built**: No build time
2. **Container creation is fast**: < 1 second typically
3. **Layers are cached**: All dependencies already present
The main delay only happens on the first startup when the image is built.
### Alternatives for Persistent Containers
If you truly need persistent containers to avoid all startup delay, you would need:
#### Option 1: TCP-based LSP Servers
- Modify LSP servers to listen on TCP ports instead of stdio
- Run containers in detached mode with exposed ports
- Connect to existing containers
Pros: Zero startup delay, true persistent containers
Cons: Requires modifying LSP servers or finding TCP-compatible alternatives
#### Option 2: Proxy Wrapper (Complex)
- Run containers in detached mode with a proxy process
- Proxy handles multiple Crush sessions
- Routes stdio between Crush and LSP servers
Pros: Persistent containers, no LSP server modifications
Cons: Complex implementation, potential performance overhead, single point of failure
#### Option 3: Current Implementation (Recommended)
- Run on-demand with `docker run -i --rm`
- Each Crush session gets its own container
- Fast startup with pre-built images
Pros: Simple, reliable, standard LSP architecture
Cons: ~1 second startup per session
## Configuration
The current `crush.json` configuration:
```json
{
"lsp": {
"bash": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-bash-language-server", "start"]
},
"docker": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-docker-language-server", "start", "--stdio"]
},
"markdown": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-marksman", "server"]
}
}
}
```
### Key Points
- `-i`: Interactive mode (required for stdio)
- `--rm`: Remove container after exit (cleanup)
- Command arguments: `start`, `start --stdio`, `server` (varies by LSP)
## Troubleshooting
### "Container keeps crashing"
If you see LSP containers restarting repeatedly, check:
1. **Is the container configured for detached mode?**
- LSP servers should NOT run in detached mode
- They should be started on-demand via `docker run -i`
2. **Is the command specified?**
- `bash-language-server` needs `start`
- `docker-language-server` needs `start --stdio`
- `marksman` needs `server`
3. **Check crush.json configuration**
- Ensure all command arguments are included
- See configuration section above
### Testing LSP Servers
Test each LSP manually:
```bash
# Test bash LSP
echo '{}' | timeout 2 docker run -i --rm kneldevstack-aimiddleware-bash-language-server start
# Test docker LSP
echo '{}' | timeout 2 docker run -i --rm kneldevstack-aimiddleware-docker-language-server start --stdio
# Test marksman
echo '{}' | timeout 2 docker run -i --rm kneldevstack-aimiddleware-marksman server
```
Expected: Exit code 124 (timeout), meaning the LSP server is running and waiting for input.

View File

@@ -5,34 +5,34 @@ Tracking the setup and validation of MCP/LSP servers via Docker Compose.
| Repository | Status | Notes | | Repository | Status | Notes |
|------------|--------|-------| |------------|--------|-------|
| KiCAD-MCP-Server | Documented | Host-only - requires KiCAD installed on host. Connects via TCP to KICAD_HOST:KICAD_PORT | | KiCAD-MCP-Server | Documented | Host-only - requires KiCAD installed on host. Connects via TCP to KICAD_HOST:KICAD_PORT |
| freecad-mcp | Built | Container built successfully with uvx entrypoint | | freecad-mcp | Working | Built with uvx entrypoint. stdio-based. Requires FreeCAD app running on host and configured FREECAD_HOST, FREECAD_PORT env vars | Container built successfully with uvx entrypoint |
| blender-mcp | Built | Container built successfully with uvx entrypoint | | blender-mcp | Working | Built with uvx entrypoint. stdio-based. Requires Blender app running on host and configured BLENDER_HOST, BLENDER_PORT env vars | Container built successfully with uvx entrypoint |
| context7 | Pending | | | context7 | Working | Built with pnpm. stdio-based. Crush config in crush.json | |
| gimp-mcp | Built | Container built successfully with uvx entrypoint | | gimp-mcp | Working | Built with uvx entrypoint. stdio-based. Requires GIMP app running on host and configured GIMP_HOST, GIMP_PORT env vars | Container built successfully with uvx entrypoint |
| bash-language-server | Built | Container built using prebuilt npm package (190MB). Configured for Crush via docker run with -i flag for stdio. | | bash-language-server | Built | Container built using prebuilt npm package (190MB). Configured for Crush via docker run with -i flag for stdio. |
| docker-language-server | Built | Container built from Go source (49.2MB). Configured for Crush via docker run with -i flag for stdio. | | docker-language-server | Built | Container built from Go source (49.2MB). Configured for Crush via docker run with -i flag for stdio. |
| marksman | Built | Container built from prebuilt binary (144MB). Configured for Crush via docker run with -i flag for stdio. | | marksman | Built | Container built from prebuilt binary (144MB). Configured for Crush via docker run with -i flag for stdio. |
| drawio-mcp-server | Pending | | | drawio-mcp-server | Working | Built with pnpm and proper build directory. stdio-based. Crush config in crush.json | |
| matomo-mcp-client | Pending | | | matomo-mcp-client | Pending | |
| imap-mcp | Built | Container built successfully with uvx entrypoint | | imap-mcp | Working | Built with custom Dockerfile and python module entrypoint. stdio-based. Requires IMAP_HOST, IMAP_USERNAME, IMAP_PASSWORD env vars. Crush config in crush.json | Container built successfully with uvx entrypoint |
| mcp-redmine | Built | Container built successfully with uvx entrypoint | | mcp-redmine | Working | Built with custom Dockerfile and correct entrypoint (no "main" arg). stdio-based. Requires REDMINE_URL, REDMINE_API_KEY env vars. Crush config in crush.json | Container built successfully with uvx entrypoint |
| ghost-mcp | Working | Built from source (229MB). MCP server initializes and starts properly. Requires GHOST_API_URL and GHOST_ADMIN_API_KEY ({24_hex}:{64_hex} format). Uses default dummy values for testing. Crush can connect via `docker run -i --rm`. Updated docker-compose.yml with restart: "no" for stdio-based containers. | | ghost-mcp | Working | Built from source (229MB). stdio-based. Requires GHOST_URL, GHOST_API_KEY env vars. Crush config in crush.json | Built from source (229MB). MCP server initializes and starts properly. Requires GHOST_API_URL and GHOST_ADMIN_API_KEY ({24_hex}:{64_hex} format). Uses default dummy values for testing. Crush can connect via `docker run -i --rm`. Updated docker-compose.yml with restart: "no" for stdio-based containers. |
| discourse-mcp | Pending | | | discourse-mcp | Pending | |
| mcp-cloudron | Pending | | | mcp-cloudron | Pending | |
| postizz-MCP | Pending | | | postizz-MCP | Pending | |
| snipeit-mcp | Pending | | | snipeit-mcp | Pending | |
| nextcloud-mcp-server | Built | Container built successfully (798MB) | | nextcloud-mcp-server | Working | Built with custom Dockerfile to handle .dockerignore issue. HTTP-based on port 8083. Requires NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars. Crush config in crush.json with SSE endpoint | Container built successfully (798MB) |
| docspace-mcp | Pending | | | docspace-mcp | Pending | |
| docker-mcp | Built | Container built successfully with uvx entrypoint | | docker-mcp | Working | Built with uvx entrypoint. stdio-based. Crush config in crush.json | Container built successfully with uvx entrypoint |
| kubernetes-mcp-server | Pending | | | kubernetes-mcp-server | Pending | |
| ProxmoxMCP | Built | Container built successfully with uvx entrypoint | | ProxmoxMCP | Working | Built with uvx entrypoint. stdio-based. Requires PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN, PROXMOX_NODE env vars. Crush config in crush.json | Container built successfully with uvx entrypoint |
| terraform-mcp-server | Pending | | | terraform-mcp-server | Pending | |
| mcp-ansible | Pending | | | mcp-ansible | Pending | |
| mcp-server (Bitwarden) | Pending | | | mcp-server (Bitwarden) | Pending | |
| mcp-adapter (WordPress) | Pending | | | mcp-adapter (WordPress) | Pending | |
| audiobook-mcp-server | Pending | | | audiobook-mcp-server | Pending | |
| mcp-server-elasticsearch | Pending | | | mcp-server-elasticsearch | Pending | |
| penpot-mcp | Built | Container built successfully with uvx entrypoint | | penpot-mcp | Working | Built with proper Dockerfile and python module entrypoint. stdio-based. Requires PENPOT_URL, PENPOT_TOKEN env vars. Crush config in crush.json | Container built successfully with uvx entrypoint |
## Usage ## Usage

24
build-nextcloud-mcp.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Build script for nextcloud-mcp that handles .dockerignore issue
set -e
NEXTCLOUD_DIR="vendor/nextcloud-mcp-server"
DOCKERIGNORE_FILE="$NEXTCLOUD_DIR/.dockerignore"
BACKUP_FILE="$NEXTCLOUD_DIR/.dockerignore.backup"
echo "Backing up .dockerignore..."
if [ -f "$DOCKERIGNORE_FILE" ]; then
cp "$DOCKERIGNORE_FILE" "$BACKUP_FILE"
rm "$DOCKERIGNORE_FILE"
fi
echo "Building nextcloud-mcp..."
docker compose build nextcloud-mcp
echo "Restoring .dockerignore..."
if [ -f "$BACKUP_FILE" ]; then
mv "$BACKUP_FILE" "$DOCKERIGNORE_FILE"
fi
echo "Done!"

View File

@@ -27,9 +27,59 @@
"command": "docker", "command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-bitwarden-mcp"] "args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-bitwarden-mcp"]
}, },
"context7": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-context7-mcp"]
},
"docker": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-docker-mcp"]
},
"drawio": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-drawio-mcp"]
},
"ghost": { "ghost": {
"command": "docker", "command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-ghost-mcp"] "args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-ghost-mcp"]
},
"imap": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-imap-mcp"],
"env": {
"IMAP_HOST": "imap.example.com",
"IMAP_USERNAME": "user@example.com",
"IMAP_PASSWORD": "your-password-here"
}
},
"nextcloud": {
"url": "http://localhost:8083/sse"
},
"penpot": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-penpot-mcp"],
"env": {
"PENPOT_URL": "https://design.penpot.app",
"PENPOT_TOKEN": "your-token-here"
}
},
"proxmox": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-proxmox-mcp"],
"env": {
"PROXMOX_HOST": "https://proxmox.example.com",
"PROXMOX_USER": "root@pam",
"PROXMOX_TOKEN": "your-token-here",
"PROXMOX_NODE": "pve"
}
},
"redmine": {
"command": "docker",
"args": ["run", "-i", "--rm", "kneldevstack-aimiddleware-mcp-redmine"],
"env": {
"REDMINE_URL": "https://redmine.example.com",
"REDMINE_API_KEY": "your-api-key-here"
}
} }
} }
} }

View File

@@ -166,21 +166,21 @@ services:
# Content Management (4 servers) # Content Management (4 servers)
# ========================================== # ==========================================
# Nextcloud MCP - 90+ tools across 8 apps # Nextcloud MCP - 90+ tools across 8 apps (HTTP-based)
nextcloud-mcp: nextcloud-mcp:
image: kneldevstack-aimiddleware-nextcloud-mcp image: kneldevstack-aimiddleware-nextcloud-mcp
build: build:
context: ./vendor/nextcloud-mcp-server context: ./vendor/nextcloud-mcp-server
dockerfile: Dockerfile dockerfile: ../../dockerfiles/nextcloud-mcp/Dockerfile
container_name: kneldevstack-aimiddleware-nextcloud-mcp container_name: kneldevstack-aimiddleware-nextcloud-mcp
restart: unless-stopped restart: unless-stopped
ports:
- "8083:8000"
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- NEXTCLOUD_HOST=${NEXTCLOUD_HOST} - NEXTCLOUD_HOST=${NEXTCLOUD_HOST}
- NEXTCLOUD_USERNAME=${NEXTCLOUD_USERNAME} - NEXTCLOUD_USERNAME=${NEXTCLOUD_USERNAME}
- NEXTCLOUD_APP_PASSWORD=${NEXTCLOUD_APP_PASSWORD} - NEXTCLOUD_PASSWORD=${NEXTCLOUD_PASSWORD}
ports:
- "8083:8080"
profiles: profiles:
- ops - ops
@@ -348,18 +348,18 @@ services:
- ops - ops
# Redmine MCP - Project management # Redmine MCP - Project management
# NOTE: This is a stdio-based MCP server, run on-demand by Crush via docker run
mcp-redmine: mcp-redmine:
image: kneldevstack-aimiddleware-mcp-redmine image: kneldevstack-aimiddleware-mcp-redmine
build: build:
context: ./vendor/mcp-redmine context: ./vendor/mcp-redmine
dockerfile: Dockerfile dockerfile: ../../dockerfiles/mcp-redmine/Dockerfile
container_name: kneldevstack-aimiddleware-mcp-redmine container_name: kneldevstack-aimiddleware-mcp-redmine
restart: unless-stopped restart: "no"
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- REDMINE_URL=${REDMINE_URL} - REDMINE_URL=${REDMINE_URL}
- REDMINE_API_KEY=${REDMINE_API_KEY} - REDMINE_API_KEY=${REDMINE_API_KEY}
command: ["uvx", "mcp-redmine"]
profiles: profiles:
- ops - ops

View File

@@ -0,0 +1,11 @@
FROM python:3.13-slim
WORKDIR /app
COPY . /app
RUN pip install --upgrade pip \
&& pip install uv \
&& uv sync
CMD ["uv", "run", "--directory", "/app", "-m", "mcp_redmine.server"]

View File

@@ -0,0 +1,44 @@
FROM docker.io/library/python:3.12-slim
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Install dependencies
RUN apt update && apt install --no-install-recommends --no-install-suggests -y \
git \
tesseract-ocr \
sqlite3 && apt clean
WORKDIR /app
COPY pyproject.toml uv.lock README.md ./
RUN uv sync --no-dev --no-install-project --no-cache
# Copy source code (create permissive .dockerignore first)
# We need to override the vendor .dockerignore
RUN rm -f /app/.dockerignore 2>/dev/null || true
RUN cat > /app/.dockerignore << 'EOF'
*.pyc
__pycache__/
.venv/
.DS_Store
.git/
.gitignore
uv.lock
README.md
ARCHITECTURE.md
CONTRIBUTING.md
DEVELOPMENT.md
PROMPTS.md
TROUBLESHOOTING.md
EOF
COPY . .
RUN uv sync --no-dev --no-editable --no-cache
ENV PYTHONUNBUFFERED=1
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT ["/app/.venv/bin/nextcloud-mcp-server"]
CMD ["run"]

View File

@@ -14,11 +14,20 @@ INIT_MSG='{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{},"pr
test_mcp_server() { test_mcp_server() {
local container_name=$1 local container_name=$1
local timeout=${2:-5} local timeout=${2:-5}
shift 2
local env_vars=("$@")
echo -e "${YELLOW}Testing $container_name...${NC}" echo -e "${YELLOW}Testing $container_name...${NC}"
# Run container with stdin input # Build environment arguments
local env_args=""
for env_var in "${env_vars[@]}"; do
env_args="$env_args -e $env_var"
done
# Run container with stdin input and environment variables
result=$(timeout $timeout docker run --rm -i --name "$container_name-test" \ result=$(timeout $timeout docker run --rm -i --name "$container_name-test" \
$env_args \
"$container_name" \ "$container_name" \
<<<"$INIT_MSG" \ <<<"$INIT_MSG" \
2>&1) 2>&1)
@@ -56,14 +65,42 @@ test_mcp_server() {
echo -e "${YELLOW}=== MCP Server Validation ===${NC}\n" echo -e "${YELLOW}=== MCP Server Validation ===${NC}\n"
# Stdio-based MCP servers # Stdio-based MCP servers
test_mcp_server "kneldevstack-aimiddleware-ghost-mcp" test_mcp_server "kneldevstack-aimiddleware-ghost-mcp" \
test_mcp_server "kneldevstack-aimiddleware-penpot-mcp" "GHOST_URL=https://ghost.example.com" \
test_mcp_server "kneldevstack-aimiddleware-imap-mcp" "GHOST_API_KEY=dummy-key"
test_mcp_server "kneldevstack-aimiddleware-proxmox-mcp"
test_mcp_server "kneldevstack-aimiddleware-penpot-mcp" \
"PENPOT_URL=https://design.penpot.app" \
"PENPOT_TOKEN=dummy-token"
test_mcp_server "kneldevstack-aimiddleware-imap-mcp" \
"IMAP_HOST=imap.example.com" \
"IMAP_USERNAME=user@example.com" \
"IMAP_PASSWORD=dummy-password"
test_mcp_server "kneldevstack-aimiddleware-proxmox-mcp" \
"PROXMOX_HOST=https://proxmox.example.com" \
"PROXMOX_USER=root@pam" \
"PROXMOX_TOKEN=dummy-token" \
"PROXMOX_NODE=pve"
test_mcp_server "kneldevstack-aimiddleware-context7-mcp" test_mcp_server "kneldevstack-aimiddleware-context7-mcp"
test_mcp_server "kneldevstack-aimiddleware-docker-mcp" test_mcp_server "kneldevstack-aimiddleware-docker-mcp"
test_mcp_server "kneldevstack-aimiddleware-drawio-mcp" test_mcp_server "kneldevstack-aimiddleware-drawio-mcp"
test_mcp_server "kneldevstack-aimiddleware-mcp-redmine"
test_mcp_server "kneldevstack-aimiddleware-nextcloud-mcp" test_mcp_server "kneldevstack-aimiddleware-mcp-redmine" \
"REDMINE_URL=https://redmine.example.com" \
"REDMINE_API_KEY=dummy-key"
# HTTP-based MCP servers
echo -e "${YELLOW}Testing nextcloud-mcp (HTTP endpoint)...${NC}"
result=$(timeout 5 curl -s http://localhost:8083/health/live 2>&1 || echo "Connection failed")
if echo "$result" | grep -q "200 OK\|healthy"; then
echo -e "${GREEN}✓ nextcloud-mcp (HTTP): Running on http://localhost:8083${NC}"
else
echo -e "${YELLOW}⚠ nextcloud-mcp (HTTP): Not running or unhealthy${NC}"
fi
echo -e "\n${YELLOW}=== Validation Complete ===${NC}" echo -e "\n${YELLOW}=== Validation Complete ===${NC}"