From 88694fcdeeab94a5bea5f8ae39af138a02f283b5 Mon Sep 17 00:00:00 2001 From: MangoFanFanw Date: Tue, 31 Mar 2026 22:17:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Docker=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 64 +++++++++++++++++ DEPLOY.md | 149 ++++++++++++++++++++++++++++++++++++++++ Dockerfile | 71 +++++++++++++++++++ docker-compose.yml | 54 +++++++++++++++ src/njupt_mcp/server.py | 10 ++- 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 DEPLOY.md create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5fb14ea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,64 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +.venv/ +ENV/ +env/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +tox/ + +# Git +.git/ +.gitignore + +# Documentation +docs/ +*.md +!README.md + +# Docker +Dockerfile* +docker-compose* +.docker/ + +# Local data (应该在运行时通过卷挂载) +data/ +*.html +*.env +.env* + +# Logs +*.log +logs/ diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..7c9b43a --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,149 @@ +# NJUPT MCP Server - Docker 部署指南 + +## 🚀 快速开始 + +### 方式一:使用 Docker Compose(推荐) + +```bash +# 1. 构建并启动 +docker-compose up -d + +# 2. 查看日志 +docker-compose logs -f + +# 3. 停止服务 +docker-compose down +``` + +### 方式二:使用 Docker 命令 + +```bash +# 1. 构建镜像 +docker build -t njupt-mcp:latest . + +# 2. 运行容器 +docker run -d \ + --name njupt-mcp \ + -p 8000:8000 \ + -e COURSE_SCHEDULE=/app/data/course_schedule.html \ + -v $(pwd)/src/njupt_mcp/resources/course_schedule/B240423-22.html:/app/data/course_schedule.html:ro \ + --restart unless-stopped \ + njupt-mcp:latest + +# 3. 查看日志 +docker logs -f njupt-mcp + +# 4. 停止并删除 +docker stop njupt-mcp +docker rm njupt-mcp +``` + +## ⚙️ 配置说明 + +### 环境变量 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `COURSE_SCHEDULE` | 课表 HTML 文件路径(容器内) | `/app/data/course_schedule.html` | + +### 传输模式 + +容器默认使用 **SSE 模式** 运行,可通过修改 `docker-compose.yml` 或启动命令切换: + +```yaml +# stdio 模式(主要用于调试) +command: ["--transport", "stdio"] + +# streamable-http 模式 +command: ["--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"] +``` + +## 📁 数据挂载 + +课表文件通过 Docker Volume 挂载到容器内,这样无需重新构建镜像即可更新课表: + +```yaml +volumes: + - /主机/路径/到/课表.html:/app/data/course_schedule.html:ro +``` + +`:ro` 表示只读挂载,增加安全性。 + +## 🔒 安全建议 + +1. **使用非 root 用户**:Dockerfile 已配置使用 `appuser` (UID 1000) 运行 +2. **只读挂载**:课表文件以只读方式挂载 +3. **资源限制**:docker-compose.yml 中配置了 CPU 和内存限制 +4. **健康检查**:自动检测服务状态,失败时重启 + +## 🌐 访问方式 + +启动后,MCP 服务器在以下地址可用: + +- **SSE 端点**: `http://<服务器IP>:8000/sse` +- **消息端点**: `http://<服务器IP>:8000/messages` + +在 MCP Inspector 中配置服务器 URL: +``` +http://your-server-ip:8000/sse +``` + +## 🐛 常见问题 + +### 1. 课表文件找不到 + +确保挂载路径正确: +```bash +# 检查容器内文件是否存在 +docker exec njupt-mcp ls -la /app/data/ +``` + +### 2. 端口冲突 + +修改 `docker-compose.yml` 中的端口映射: +```yaml +ports: + - "8080:8000" # 主机8080映射到容器8000 +``` + +### 3. 权限问题 + +如果课表文件权限不足: +```bash +chmod 644 /path/to/course_schedule.html +``` + +### 4. 查看详细日志 + +```bash +# 调试模式启动 +docker run -e DEBUG=1 njupt-mcp:latest + +# 或修改 docker-compose.yml +environment: + - DEBUG=1 +``` + +## 🔄 更新部署 + +更新代码后重新构建: + +```bash +# 拉取最新代码后 +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` + +## 📊 监控 + +查看容器状态: +```bash +docker ps +docker stats njupt-mcp +``` + +查看资源使用: +```bash +docker system df +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd58145 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,71 @@ +# =========================================== +# NJUPT MCP Server - Dockerfile +# =========================================== + +# ---- 阶段 1: 构建依赖 ---- +FROM python:3.12-slim AS builder + +# 安装构建工具 +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# 使用 uv 加速依赖安装(可选但推荐) +RUN pip install --no-cache-dir uv + +# 设置工作目录 +WORKDIR /build + +# 创建虚拟环境 +RUN uv venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# 复制项目文件 +COPY pyproject.toml . +COPY README.md . +COPY src/ ./src/ + +# 安装依赖和包(非 editable 模式) +RUN uv pip install . + +# ---- 阶段 2: 运行镜像 ---- +FROM python:3.12-slim AS runtime + +# 设置环境变量 +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHON_ENV=production + +# 创建非 root 用户(安全最佳实践) +RUN groupadd --gid 1000 appgroup && \ + useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser + +# 从 builder 复制虚拟环境 +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# 设置工作目录 +WORKDIR /app + +# 包已经在 builder 阶段安装到虚拟环境,无需重新安装 +# 但如果需要,可以复制源码供参考(可选) +COPY src/ ./src/ + +# 创建数据目录并设置权限 +RUN mkdir -p /app/data && chown -R appuser:appgroup /app + +# 切换到非 root 用户 +USER appuser + +# 暴露端口(SSE 模式默认 8000) +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/sse')" 2>/dev/null || exit 1 + +# 默认启动命令(SSE 模式) +ENTRYPOINT ["njupt-mcp"] +CMD ["--transport", "sse", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dd8f74a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +version: "3.8" + +services: + njupt-mcp: + build: + context: . + dockerfile: Dockerfile + image: njupt-mcp:latest + container_name: njupt-mcp-server + + # 端口映射(主机端口:容器端口) + ports: + - "8000:8000" + + # 环境变量 + environment: + # 课表 HTML 文件路径(容器内路径) + - COURSE_SCHEDULE=/app/data/course_schedule.html + # 可选:调试模式 + # - DEBUG=1 + + # 数据卷挂载(将主机上的课表文件映射到容器) + volumes: + # 挂载课表文件(请确保路径正确) + - ./src/njupt_mcp/resources/course_schedule/B240423-22.html:/app/data/course_schedule.html:ro + # 可选:挂载日志目录 + - ./logs:/app/logs + + # 重启策略 + restart: unless-stopped + + # 资源限制 + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.25' + memory: 128M + + # 健康检查 + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/sse')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + +# 可选:使用已有网络 +# networks: +# default: +# external: +# name: my-network diff --git a/src/njupt_mcp/server.py b/src/njupt_mcp/server.py index a8ab015..11ae4a3 100644 --- a/src/njupt_mcp/server.py +++ b/src/njupt_mcp/server.py @@ -14,6 +14,7 @@ from dotenv import load_dotenv from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from mcp.server.fastmcp import FastMCP +from mcp.server.transport_security import TransportSecuritySettings from mcp.types import ToolAnnotations from njupt_mcp.resources.types import course_dict_serializer @@ -61,7 +62,14 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[dict]: # 1. 创建标准的 FastMCP 实例 -mcp = FastMCP("njupt-mcp", lifespan=app_lifespan) +# 禁用 DNS rebinding protection(Docker 环境中 Host header 验证会失败) +mcp = FastMCP( + "njupt-mcp", + lifespan=app_lifespan, + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=False, + ), +) # 2. 创建 FastAPI 实例并配置 CORS app = FastAPI()