Skip to content

Docker 安装 MCPO

Open WebUI 集成 MCP 服务,需要使用 MCPO,记录下 Docker 安装 MCPO 的过程。


MCPO 简介

MCPO:MCP(模型上下文协议)到 OpenAPI 代理服务器,可以实现将标准的 MCP 服务转换为 OpenAPI 服务。

json
{
  "mcpServers": {
    "time": {
      "command": "uvx",
      "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
    }
  }
}
json
{
  "openapi": "3.1.0",
  "info": {
    "title": "mcp-time",
    "description": "mcp-time MCP Server",
    "version": "1.13.1"
  },
  "servers": [
    {
      "url": "/time"
    }
  ],
  "paths": {
    "/get_current_time": {
      "post": {
        "summary": "Get Current Time",
        "description": "Get current time in a specific timezones",
        "operationId": "tool_get_current_time_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/get_current_time_form_model"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Response Tool Get Current Time Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/convert_time": {
      "post": {
        "summary": "Convert Time",
        "description": "Convert time between timezones",
        "operationId": "tool_convert_time_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/convert_time_form_model"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Response Tool Convert Time Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "convert_time_form_model": {
        "properties": {
          "source_timezone": {
            "type": "string",
            "title": "Source Timezone",
            "description": "Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no source timezone provided by the user."
          },
          "time": {
            "type": "string",
            "title": "Time",
            "description": "Time to convert in 24-hour format (HH:MM)"
          },
          "target_timezone": {
            "type": "string",
            "title": "Target Timezone",
            "description": "Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use 'Asia/Shanghai' as local timezone if no target timezone provided by the user."
          }
        },
        "type": "object",
        "required": [
          "source_timezone",
          "time",
          "target_timezone"
        ],
        "title": "convert_time_form_model"
      },
      "get_current_time_form_model": {
        "properties": {
          "timezone": {
            "type": "string",
            "title": "Timezone",
            "description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no timezone provided by the user."
          }
        },
        "type": "object",
        "required": [
          "timezone"
        ],
        "title": "get_current_time_form_model"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  }
}

部署准备

官方原版的 Dockerfile 太简陋,翻阅 B 站时意外找到一位大佬封装的 Docker 部署物料 mcpo_docker_use,很适合快速部署。

创建 mcpo 目录,上传下方物料,根据需要修改 .envconfig.json 文件。

text
.
├─ docker-compose.yml
├─ Dockerfile
├─ start.sh(容器入口点脚本,读取 config.json 并动态安装 MCP 工具)
├─ .env(用于存放敏感信息和配置)
└─ config.json(配置需要启动的 MCP 服务器)
yaml
services:
  mcpo:
    build:
      context: .
      args:
        # 从 .env 文件读取 PIP_SOURCE 并传递给 Dockerfile
        - PIP_SOURCE=${PIP_SOURCE:-} # 使用 :-} 避免未设置时出错
    container_name: mcpo
    restart: unless-stopped
    ports:
      - 8000:8000
    volumes:
      - ./config.json:/app/config/config.json
      - ./logs:/app/logs
      - ./data:/app/data
      - ./node_modules:/app/node_modules # 持久化 node_modules
      - ./.npm:/app/.npm # 持久化 npm 缓存
      - ./.uv_cache:/app/.cache/uv # 持久化 uv 缓存 (注意路径改为 /app/.cache/uv)
    # 使用 env_file 或 environment 传递运行时环境变量
    env_file:
      - .env # 加载 .env 文件中的所有变量作为运行时环境变量
    # 或者只传递需要的变量
    # environment:
    #   - MCPO_API_KEY=${MCPO_API_KEY}
    healthcheck:
      test: CMD curl -f http://localhost:8000/docs
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
dockerfile
FROM python:3.13-slim

LABEL org.opencontainers.image.title="mcpo"
LABEL org.opencontainers.image.description="Docker image for mcpo (Model Context Protocol OpenAPI Proxy)"
LABEL org.opencontainers.image.licenses="MIT"
LABEL maintainer="flyfox666@gmail.com, biguncle2017@gmail.com"

# Install base dependencies, Node.js, and Git in a single layer (as root)
RUN set -eux; \
    # Configure apt sources (use Aliyun mirror)
    for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/debian.sources; do \
      [ -f "$f" ] && sed -i 's|deb.debian.org|mirrors.aliyun.com|g; s|security.debian.org|mirrors.aliyun.com|g' "$f" || true; \
    done && \
    # Install curl and ca-certificates first for NodeSource script
    apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \
    # Add NodeSource repo for Node.js 22.x
    curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
    # Update again after adding new source and install remaining packages, including git
    && apt-get update && apt-get install -y --no-install-recommends \
    bash \
    jq \
    nodejs \
    git \
    # Clean up apt cache
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    # Verify npm/npx and git installation path
    && echo "--- Debug: Verifying npm/npx/git path ---" \
    && which npm \
    && which npx \
    && which git || echo "npm, npx, or git not found immediately after install"

# Create application directories (as root)
RUN mkdir -p /app/config /app/logs /app/data /app/node_modules /app/.npm /app/.cache/uv

# Copy start script (as root)
COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh

# Create non-root user and grant ownership (as root)
RUN useradd -m -d /app -s /bin/bash appuser && chown -R appuser:appuser /app

# Switch to non-root user
USER appuser
WORKDIR /app

# Set environment variables for the non-root user
# Ensure user's local bin and Node's/Git's global bin are in PATH
ENV HOME=/app \
    PATH=/app/.local/bin:/usr/bin:/usr/local/bin:$PATH \
    UV_CACHE_DIR=/app/.cache/uv \
    NPM_CONFIG_CACHE=/app/.npm \
    MCPO_LOG_DIR="/app/logs"

# Accept PIP_SOURCE build argument
ARG PIP_SOURCE=""
# Make PIP_SOURCE available as an environment variable for the RUN layer below
ENV PIP_SOURCE=${PIP_SOURCE}

# Install uv using pip, respecting PIP_SOURCE via environment variable (as non-root user)
RUN set -eux; \
    echo "--- Debug: Checking PIP_SOURCE value ---"; \
    echo "PIP_SOURCE Env Var: ${PIP_SOURCE:-<not set or empty>}"; \
    # Set PIP_INDEX_URL environment variable if PIP_SOURCE is valid
    if [ -n "$PIP_SOURCE" ] && echo "$PIP_SOURCE" | grep -q '^https://'; then \
      export PIP_INDEX_URL="$PIP_SOURCE"; \
      echo "已设置 PIP_INDEX_URL 环境变量为: $PIP_INDEX_URL"; \
    else \
      echo "未设置自定义 pip 源,将使用默认源"; \
      # Unset PIP_INDEX_URL just in case it was inherited
      unset PIP_INDEX_URL; \
    fi; \
    # Install/upgrade pip and install uv for the user (pin version for reproducibility)
    echo "--- Debug: Upgrading pip ---"; \
    python -m pip install --upgrade pip --user; \
    echo "--- Debug: Installing uv (using source: ${PIP_INDEX_URL:-<default>}) ---"; \
    python -m pip install --user uv==0.6.14; \
    # Verify uv installation
    echo "--- Debug: Verifying uv installation ---"; \
    uv --version

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8000/docs || exit 1

ENTRYPOINT ["/app/start.sh"]
shell
#!/bin/bash
set -e

# 设置所有缓存和 HOME 目录到 /app,防止权限问题
export HOME=/app
export UV_CACHE_DIR=/app/.cache/uv
export NPM_CONFIG_CACHE=/app/.npm

date_str=$(date +"%Y%m%d_%H%M%S")
log_file="/app/logs/mcpo_${date_str}.log"

# 确保持久化目录存在(全部在 /app 下)
mkdir -p /app/data /app/node_modules /app/.npm /app/.cache/uv

# 动态安装 config.json 中声明的 mcp 工具
if [ -f /app/config/config.json ]; then
  echo "检测到 /app/config/config.json,准备动态安装 MCP 工具..."
  jq -r '.mcpServers | keys[]' /app/config/config.json | while read -r key; do
    (
      command=$(jq -r ".mcpServers[\"$key\"].command" /app/config/config.json)
      args=$(jq -r ".mcpServers[\"$key\"].args | @sh" /app/config/config.json)
      envs=$(jq -r ".mcpServers[\"$key\"].env // {} | to_entries[]? | \"export \(.key)=\\\"\(.value)\\\"\"" /app/config/config.json)
      # 设置环境变量
      if [ -n "$envs" ]; then
        eval "$envs"
      fi
      # 动态安装
      if [ "$command" = "uvx" ]; then
        echo "使用 uvx 安装: $args"
        eval uvx $args
      elif [ "$command" = "npx" ]; then
        echo "使用 npx 安装: $args"
        eval npx $args
      else
        echo "未知 command: $command,跳过 $key"
      fi
    )
  done
else
  echo "未检测到 /app/config/config.json,跳过 MCP 工具动态安装。"
fi

# 启动主服务
if [ ! -z "$MCPO_API_KEY" ]; then
  uvx mcpo --host 0.0.0.0 --port 8000 --config /app/config/config.json --api-key "$MCPO_API_KEY" 2>&1 | tee -a "$log_file"
else
  uvx mcpo --host 0.0.0.0 --port 8000 --config /app/config/config.json 2>&1 | tee -a "$log_file"
fi
properties
# Docker 构建时使用的 pip 源 (可选, 留空则使用默认源)
# 例如: 阿里云 PyPI 镜像
PIP_SOURCE=https://mirrors.aliyun.com/pypi/simple/
# 例如: 清华大学 PyPI 镜像
# PIP_SOURCE=https://pypi.tuna.tsinghua.edu.cn/simple

# mcpo 运行时需要的 API Key (必需)
MCPO_API_KEY=123456
json
{
  "mcpServers": {
    "time": {
      "command": "uvx",
      "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
    },
    "fetch": {
      "command": "uvx",
      "args": ["mcp-server-fetch"]
    }
  }
}

运行容器

运行下方命令构建镜像并运行容器,成功后访问 http://ip:8000/docs 查看生成的 Swagger 文档。

shell
docker-compose up -d

运行容器后,如果需要更新 MCP Server,在修改完 config.json 后重启容器即可。

shell
docker restart mcpo

参考资料

  1. mcpo的docker封装,后台稳定运行:https://www.bilibili.com/video/BV1QtRmYhEqx/
  2. mcpo_docker_use:https://github.com/flyfox666/mcpo_docker_use