Add Docker deployment with streamable-http transport for hosted MCP
Some checks failed
Test Dashboard / test-and-dashboard (push) Has been cancelled
Some checks failed
Test Dashboard / test-and-dashboard (push) Has been cancelled
- Add Dockerfile with multi-stage build using uv - Add docker-compose.yml with caddy-docker-proxy labels for /mcp endpoint - Add .env.example for deployment configuration - Update Makefile with docker-* targets - Update server.py to support MCP_TRANSPORT env var: - 'stdio' (default): Local CLI usage with Claude Code - 'streamable-http': Hosted HTTP mode behind reverse proxy Hosted server will be available at: https://mcwaddams.supported.systems/mcp
This commit is contained in:
parent
3d469e5696
commit
322ed78427
18
.env.example
Normal file
18
.env.example
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# mcwaddams MCP Server - Environment Configuration
|
||||||
|
# Copy to .env and customize
|
||||||
|
|
||||||
|
# Docker Compose project name (prevents container name conflicts)
|
||||||
|
COMPOSE_PROJECT_NAME=mcwaddams
|
||||||
|
|
||||||
|
# Hostname for caddy-docker-proxy
|
||||||
|
MCWADDAMS_HOST=mcwaddams.supported.systems
|
||||||
|
|
||||||
|
# Debug mode (enables verbose logging)
|
||||||
|
DEBUG=false
|
||||||
|
|
||||||
|
# Transport mode: stdio (local), streamable-http (hosted)
|
||||||
|
MCP_TRANSPORT=streamable-http
|
||||||
|
|
||||||
|
# Server binding (for streamable-http mode)
|
||||||
|
MCP_HOST=0.0.0.0
|
||||||
|
MCP_PORT=8000
|
||||||
76
Dockerfile
Normal file
76
Dockerfile
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# mcwaddams MCP Server - Production Dockerfile
|
||||||
|
# "I was told there would be document extraction..."
|
||||||
|
|
||||||
|
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy dependency files first for better caching
|
||||||
|
COPY pyproject.toml uv.lock* ./
|
||||||
|
|
||||||
|
# Install dependencies (without the project itself)
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync --frozen --no-install-project --no-dev
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY src/ ./src/
|
||||||
|
COPY README.md LICENSE ./
|
||||||
|
|
||||||
|
# Install the project
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync --frozen --no-dev
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Production image
|
||||||
|
# ============================================
|
||||||
|
FROM python:3.12-slim-bookworm AS production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN groupadd --gid 1000 mcwaddams && \
|
||||||
|
useradd --uid 1000 --gid mcwaddams --shell /bin/bash --create-home mcwaddams
|
||||||
|
|
||||||
|
# Install runtime dependencies (for some document processing)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libxml2 \
|
||||||
|
libxslt1.1 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy virtual environment from builder
|
||||||
|
COPY --from=builder /app/.venv /app/.venv
|
||||||
|
|
||||||
|
# Copy source
|
||||||
|
COPY --from=builder /app/src /app/src
|
||||||
|
COPY --from=builder /app/README.md /app/LICENSE ./
|
||||||
|
|
||||||
|
# Set environment
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
ENV PYTHONPATH="/app/src"
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV UV_COMPILE_BYTECODE=1
|
||||||
|
|
||||||
|
# Default to streamable-http transport for hosted mode
|
||||||
|
ENV MCP_TRANSPORT=streamable-http
|
||||||
|
ENV MCP_HOST=0.0.0.0
|
||||||
|
ENV MCP_PORT=8000
|
||||||
|
|
||||||
|
# Temp directory for document processing
|
||||||
|
RUN mkdir -p /tmp/mcwaddams && chown mcwaddams:mcwaddams /tmp/mcwaddams
|
||||||
|
ENV OFFICE_TEMP_DIR=/tmp/mcwaddams
|
||||||
|
|
||||||
|
USER mcwaddams
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')" || exit 1
|
||||||
|
|
||||||
|
# Run the server
|
||||||
|
CMD ["python", "-m", "mcwaddams.server"]
|
||||||
73
Makefile
73
Makefile
@ -1,14 +1,24 @@
|
|||||||
# Makefile for MCP Office Tools
|
# Makefile for MCP Office Tools
|
||||||
# Provides convenient commands for testing, development, and dashboard generation
|
# Provides convenient commands for testing, development, and dashboard generation
|
||||||
|
|
||||||
.PHONY: help test test-dashboard test-pytest test-torture view-dashboard clean install format lint type-check
|
.PHONY: help test test-dashboard test-pytest test-torture view-dashboard clean install format lint type-check \
|
||||||
|
docker-build docker-up docker-down docker-logs docker-shell docker-restart docker-clean dev-http
|
||||||
|
|
||||||
# Default target - show help
|
# Default target - show help
|
||||||
help:
|
help:
|
||||||
@echo "MCP Office Tools - Available Commands"
|
@echo "MCP Office Tools - Available Commands"
|
||||||
@echo "======================================"
|
@echo "======================================"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Testing & Dashboard:"
|
@echo "Docker / Hosted Server:"
|
||||||
|
@echo " make docker-build - Build Docker image"
|
||||||
|
@echo " make docker-up - Start server (detached, with caddy)"
|
||||||
|
@echo " make docker-down - Stop server"
|
||||||
|
@echo " make docker-logs - View server logs"
|
||||||
|
@echo " make docker-restart - Restart server"
|
||||||
|
@echo " make docker-clean - Remove Docker resources"
|
||||||
|
@echo " make dev-http - Run locally with HTTP transport"
|
||||||
|
@echo ""
|
||||||
|
@echo "Testing & Dashboard:
|
||||||
@echo " make test - Run all tests with dashboard generation"
|
@echo " make test - Run all tests with dashboard generation"
|
||||||
@echo " make test-dashboard - Alias for 'make test'"
|
@echo " make test-dashboard - Alias for 'make test'"
|
||||||
@echo " make test-pytest - Run only pytest tests"
|
@echo " make test-pytest - Run only pytest tests"
|
||||||
@ -111,6 +121,65 @@ build:
|
|||||||
@uv build
|
@uv build
|
||||||
@echo "✅ Build complete! Packages in dist/"
|
@echo "✅ Build complete! Packages in dist/"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Docker / Hosted Server Commands
|
||||||
|
# ====================================
|
||||||
|
|
||||||
|
# Ensure .env exists for Docker
|
||||||
|
.env:
|
||||||
|
@if [ ! -f .env ]; then \
|
||||||
|
echo "Creating .env from .env.example..."; \
|
||||||
|
cp .env.example .env; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build Docker image
|
||||||
|
docker-build:
|
||||||
|
@echo "🐳 Building mcwaddams Docker image..."
|
||||||
|
@docker compose build
|
||||||
|
@echo "✅ Docker build complete!"
|
||||||
|
|
||||||
|
# Start server (production mode with caddy-docker-proxy)
|
||||||
|
docker-up: .env
|
||||||
|
@echo "🚀 Starting mcwaddams MCP server..."
|
||||||
|
@docker compose up -d
|
||||||
|
@sleep 2
|
||||||
|
@docker compose logs --tail=20
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Server running! Connect via:"
|
||||||
|
@echo " https://$${MCWADDAMS_HOST:-mcwaddams.supported.systems}/mcp"
|
||||||
|
|
||||||
|
# Stop server
|
||||||
|
docker-down:
|
||||||
|
@echo "🛑 Stopping mcwaddams server..."
|
||||||
|
@docker compose down
|
||||||
|
@echo "✅ Server stopped"
|
||||||
|
|
||||||
|
# View server logs
|
||||||
|
docker-logs:
|
||||||
|
@docker compose logs -f
|
||||||
|
|
||||||
|
# Open shell in running container
|
||||||
|
docker-shell:
|
||||||
|
@docker compose exec mcwaddams /bin/bash
|
||||||
|
|
||||||
|
# Restart server
|
||||||
|
docker-restart: docker-down docker-up
|
||||||
|
|
||||||
|
# Clean up Docker resources
|
||||||
|
docker-clean:
|
||||||
|
@echo "🧹 Cleaning Docker resources..."
|
||||||
|
@docker compose down -v --rmi local
|
||||||
|
@echo "✅ Docker cleanup complete!"
|
||||||
|
|
||||||
|
# Development: run locally with streamable-http transport
|
||||||
|
dev-http:
|
||||||
|
@echo "🌐 Starting mcwaddams with streamable-http transport..."
|
||||||
|
@MCP_TRANSPORT=streamable-http MCP_HOST=127.0.0.1 MCP_PORT=8000 uv run python -m mcwaddams.server
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Project Information
|
||||||
|
# ====================================
|
||||||
|
|
||||||
# Show project info
|
# Show project info
|
||||||
info:
|
info:
|
||||||
@echo "MCP Office Tools - Project Information"
|
@echo "MCP Office Tools - Project Information"
|
||||||
|
|||||||
49
docker-compose.yml
Normal file
49
docker-compose.yml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# mcwaddams MCP Server - Docker Compose
|
||||||
|
# "I could set the building on fire..."
|
||||||
|
|
||||||
|
services:
|
||||||
|
mcwaddams:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: production
|
||||||
|
container_name: mcwaddams-mcp
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MCP_TRANSPORT=streamable-http
|
||||||
|
- MCP_HOST=0.0.0.0
|
||||||
|
- MCP_PORT=8000
|
||||||
|
- DEBUG=${DEBUG:-false}
|
||||||
|
- OFFICE_TEMP_DIR=/tmp/mcwaddams
|
||||||
|
volumes:
|
||||||
|
# Temp directory for document processing
|
||||||
|
- mcwaddams-temp:/tmp/mcwaddams
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
labels:
|
||||||
|
# Caddy-docker-proxy labels for /mcp endpoint
|
||||||
|
caddy: ${MCWADDAMS_HOST:-mcwaddams.supported.systems}
|
||||||
|
caddy.@mcp.path: /mcp/*
|
||||||
|
caddy.@mcp.path_strip: /mcp
|
||||||
|
caddy.handle: "@mcp"
|
||||||
|
caddy.handle.reverse_proxy: "{{upstreams 8000}}"
|
||||||
|
caddy.handle.reverse_proxy.flush_interval: "-1"
|
||||||
|
caddy.handle.reverse_proxy.transport: "http"
|
||||||
|
caddy.handle.reverse_proxy.transport.read_timeout: "0"
|
||||||
|
caddy.handle.reverse_proxy.transport.write_timeout: "0"
|
||||||
|
caddy.handle.reverse_proxy.stream_timeout: "24h"
|
||||||
|
caddy.handle.reverse_proxy.header_up.Connection: "{http.request.header.Connection}"
|
||||||
|
caddy.handle.reverse_proxy.header_up.Upgrade: "{http.request.header.Upgrade}"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mcwaddams-temp:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
@ -554,10 +554,46 @@ This is a complete editorial workflow for manuscript review."""
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for the MCP Office Tools server."""
|
"""Entry point for the MCP Office Tools server.
|
||||||
# CRITICAL: show_banner=False is required for stdio transport!
|
|
||||||
# FastMCP's banner prints ASCII art to stdout which breaks JSON-RPC protocol
|
Supports two transport modes via MCP_TRANSPORT environment variable:
|
||||||
app.run(show_banner=False)
|
- 'stdio' (default): For local CLI usage with Claude Code
|
||||||
|
- 'streamable-http': For hosted HTTP mode behind reverse proxy
|
||||||
|
|
||||||
|
Additional env vars for streamable-http mode:
|
||||||
|
- MCP_HOST: Bind address (default: 0.0.0.0)
|
||||||
|
- MCP_PORT: Port number (default: 8000)
|
||||||
|
"""
|
||||||
|
transport = os.environ.get("MCP_TRANSPORT", "stdio").lower()
|
||||||
|
|
||||||
|
if transport == "streamable-http":
|
||||||
|
# HTTP transport for hosted/Docker mode
|
||||||
|
host = os.environ.get("MCP_HOST", "0.0.0.0")
|
||||||
|
port = int(os.environ.get("MCP_PORT", "8000"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
from importlib.metadata import version
|
||||||
|
pkg_version = version("mcwaddams")
|
||||||
|
except Exception:
|
||||||
|
pkg_version = "0.1.0"
|
||||||
|
|
||||||
|
print(f"🖨️ mcwaddams v{pkg_version}")
|
||||||
|
print(f"📋 MCP Office Tools - Document Extraction Server")
|
||||||
|
print(f"🌐 Starting streamable-http transport on {host}:{port}")
|
||||||
|
print(f" Endpoint: http://{host}:{port}/mcp")
|
||||||
|
print()
|
||||||
|
|
||||||
|
app.run(
|
||||||
|
transport="streamable-http",
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Default stdio transport for local CLI usage
|
||||||
|
# CRITICAL: show_banner=False is required for stdio transport!
|
||||||
|
# FastMCP's banner prints ASCII art to stdout which breaks JSON-RPC protocol
|
||||||
|
app.run(show_banner=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user