找回密码
 立即注册
首页 业界区 业界 MCP应用docker部署,docker-compose部署

MCP应用docker部署,docker-compose部署

迭婵椟 5 天前
一、概述

前面几篇文章,MCP应用直接用的python3 server.py运行的,如果服务器重启,进程就会关掉,很不方便。
所以需要使用docker部署,实现开机自启动。
二、docker部署

mysql_mcp_server_pro

准备文件

以mysql_mcp_server_pro为例:
server_docker.py
  1. from fastmcp import FastMCP
  2. from mysql.connector import connect, Error
  3. import os
  4. mcp = FastMCP("operateMysql", host="0.0.0.0", port=9000)
  5. def get_db_config():
  6.     """从环境变量获取数据库配置信息
  7.     返回:
  8.         dict: 包含数据库连接所需的配置信息
  9.         - host: 数据库主机地址
  10.         - port: 数据库端口
  11.         - user: 数据库用户名
  12.         - password: 数据库密码
  13.         - database: 数据库名称
  14.     异常:
  15.         ValueError: 当必需的配置信息缺失时抛出
  16.     """
  17.     config = {
  18.         "host": os.getenv("MYSQL_HOST", "localhost"),
  19.         "port": int(os.getenv("MYSQL_PORT", "3306")),
  20.         "user": os.getenv("MYSQL_USER"),
  21.         "password": os.getenv("MYSQL_PASSWORD"),
  22.         "database": os.getenv("MYSQL_DATABASE"),
  23.     }
  24.     print(config)
  25.     if not all(
  26.         [
  27.             config["host"],
  28.             config["port"],
  29.             config["user"],
  30.             config["password"],
  31.             config["database"],
  32.         ]
  33.     ):
  34.         raise ValueError("缺少必需的数据库配置")
  35.     return config
  36. @mcp.tool()
  37. def execute_sql(query: str) -> list:
  38.     """执行SQL查询语句
  39.     参数:
  40.         query (str): 要执行的SQL语句,支持多条语句以分号分隔
  41.     返回:
  42.         list: 包含查询结果的TextContent列表
  43.         - 对于SELECT查询:返回CSV格式的结果,包含列名和数据
  44.         - 对于SHOW TABLES:返回数据库中的所有表名
  45.         - 对于其他查询:返回执行状态和影响行数
  46.         - 多条语句的结果以"---"分隔
  47.     异常:
  48.         Error: 当数据库连接或查询执行失败时抛出
  49.     """
  50.     config = get_db_config()
  51.     try:
  52.         with connect(**config) as conn:
  53.             with conn.cursor() as cursor:
  54.                 statements = [stmt.strip() for stmt in query.split(";") if stmt.strip()]
  55.                 results = []
  56.                 for statement in statements:
  57.                     try:
  58.                         cursor.execute(statement)
  59.                         # 检查语句是否返回了结果集 (SELECT, SHOW, EXPLAIN, etc.)
  60.                         if cursor.description:
  61.                             columns = [desc[0] for desc in cursor.description]
  62.                             rows = cursor.fetchall()
  63.                             # 将每一行的数据转换为字符串,特殊处理None值
  64.                             formatted_rows = []
  65.                             for row in rows:
  66.                                 formatted_row = [
  67.                                     "NULL" if value is None else str(value)
  68.                                     for value in row
  69.                                 ]
  70.                                 formatted_rows.append(",".join(formatted_row))
  71.                             # 将列名和数据合并为CSV格式
  72.                             results.append(
  73.                                 "\n".join([",".join(columns)] + formatted_rows)
  74.                             )
  75.                         # 如果语句没有返回结果集 (INSERT, UPDATE, DELETE, etc.)
  76.                         else:
  77.                             conn.commit()  # 只有在非查询语句时才提交
  78.                             results.append(f"查询执行成功。影响行数: {cursor.rowcount}")
  79.                     except Error as stmt_error:
  80.                         # 单条语句执行出错时,记录错误并继续执行
  81.                         results.append(
  82.                             f"执行语句 '{statement}' 出错: {str(stmt_error)}"
  83.                         )
  84.                         # 可以在这里选择是否继续执行后续语句,目前是继续
  85.                 return ["\n---\n".join(results)]
  86.     except Error as e:
  87.         print(f"执行SQL '{query}' 时出错: {e}")
  88.         return [f"执行查询时出错: {str(e)}"]
  89. @mcp.tool()
  90. def get_table_name(text: str) -> list:
  91.     """根据表的中文注释搜索数据库中的表名
  92.     参数:
  93.         text (str): 要搜索的表中文注释关键词
  94.     返回:
  95.         list: 包含查询结果的TextContent列表
  96.         - 返回匹配的表名、数据库名和表注释信息
  97.         - 结果以CSV格式返回,包含列名和数据
  98.     """
  99.     config = get_db_config()
  100.     sql = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COMMENT "
  101.     sql += f"FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{config['database']}' AND TABLE_COMMENT LIKE '%{text}%';"
  102.     return execute_sql(sql)
  103. @mcp.tool()
  104. def get_table_desc(text: str) -> list:
  105.     """获取指定表的字段结构信息
  106.     参数:
  107.         text (str): 要查询的表名,多个表名以逗号分隔
  108.     返回:
  109.         list: 包含查询结果的列表
  110.         - 返回表的字段名、字段注释等信息
  111.         - 结果按表名和字段顺序排序
  112.         - 结果以CSV格式返回,包含列名和数据
  113.     """
  114.     config = get_db_config()
  115.     # 将输入的表名按逗号分割成列表
  116.     table_names = [name.strip() for name in text.split(",")]
  117.     # 构建IN条件
  118.     table_condition = "','".join(table_names)
  119.     sql = "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_COMMENT "
  120.     sql += (
  121.         f"FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '{config['database']}' "
  122.     )
  123.     sql += f"AND TABLE_NAME IN ('{table_condition}') ORDER BY TABLE_NAME, ORDINAL_POSITION;"
  124.     return execute_sql(sql)
  125. @mcp.tool()
  126. def get_lock_tables() -> list:
  127.     """
  128.     获取当前mysql服务器InnoDB 的行级锁
  129.     返回:
  130.         list: 包含查询结果的TextContent列表
  131.     """
  132.     sql = """SELECT
  133.     p2.`HOST` AS 被阻塞方host,
  134.     p2.`USER` AS 被阻塞方用户,
  135.     r.trx_id AS 被阻塞方事务id,
  136.     r.trx_mysql_thread_id AS 被阻塞方线程号,
  137.     TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS 等待时间,
  138.     r.trx_query AS 被阻塞的查询,
  139.     l.OBJECT_NAME AS 阻塞方锁住的表,
  140.     m.LOCK_MODE AS 被阻塞方的锁模式,
  141.     m.LOCK_TYPE AS '被阻塞方的锁类型(表锁还是行锁)',
  142.     m.INDEX_NAME AS 被阻塞方锁住的索引,
  143.     m.OBJECT_SCHEMA AS 被阻塞方锁对象的数据库名,
  144.     m.OBJECT_NAME AS 被阻塞方锁对象的表名,
  145.     m.LOCK_DATA AS 被阻塞方事务锁定记录的主键值,
  146.     p.`HOST` AS 阻塞方主机,
  147.     p.`USER` AS 阻塞方用户,
  148.     b.trx_id AS 阻塞方事务id,
  149.     b.trx_mysql_thread_id AS 阻塞方线程号,
  150.     b.trx_query AS 阻塞方查询,
  151.     l.LOCK_MODE AS 阻塞方的锁模式,
  152.     l.LOCK_TYPE AS '阻塞方的锁类型(表锁还是行锁)',
  153.     l.INDEX_NAME AS 阻塞方锁住的索引,
  154.     l.OBJECT_SCHEMA AS 阻塞方锁对象的数据库名,
  155.     l.OBJECT_NAME AS 阻塞方锁对象的表名,
  156.     l.LOCK_DATA AS 阻塞方事务锁定记录的主键值,
  157.     IF(p.COMMAND = 'Sleep', CONCAT(p.TIME, ' 秒'), 0) AS 阻塞方事务空闲的时间
  158.     FROM performance_schema.data_lock_waits w
  159.     INNER JOIN performance_schema.data_locks l ON w.BLOCKING_ENGINE_LOCK_ID = l.ENGINE_LOCK_ID
  160.     INNER JOIN performance_schema.data_locks m ON w.REQUESTING_ENGINE_LOCK_ID = m.ENGINE_LOCK_ID
  161.     INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID
  162.     INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID
  163.     INNER JOIN information_schema.PROCESSLIST p ON p.ID = b.trx_mysql_thread_id
  164.     INNER JOIN information_schema.PROCESSLIST p2 ON p2.ID = r.trx_mysql_thread_id
  165.     ORDER BY 等待时间 DESC;"""
  166.     return execute_sql(sql)
  167. if __name__ == "__main__":
  168.     mcp.run(transport="sse")
复制代码
修改了之前文章的代码,去除了.env文件,直接引用系统环境变量。
注意:这里添加了host="0.0.0.0",否则k8s发布,会无法访问mcp server
 
Dockerfile
  1. FROM python:3.13.3-alpine3.21
  2. ADD . /app
  3. RUN pip3 install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple && \
  4.     pip3 install mysql-connector-python fastmcp -i https://pypi.tuna.tsinghua.edu.cn/simple
  5. WORKDIR /app
  6. EXPOSE 9000
  7. ENTRYPOINT ["python3","/app/server.py"]
复制代码
说明:
这里使用的python版本,是最新的3.13.3,镜像使用的是最小的,基于alpine
安装fastmcp模块,必须要升级pip才行,否则安装会失败。
 
编译并运行镜像

编译
  1. docker build -t mysql_mcp_server_pro:v1 .
复制代码
 
运行镜像,注意携带5个环境变量,一个个写变量太麻烦了,接下来使用docker-compose来运行
 
三、docker-compose部署

使用docker-compose方便管理docker,修改docker运行相关参数,也容易。
docker-compose.yaml
  1. services:
  2.   mysql_mcp_server_pro:
  3.     image: mysql_mcp_server_pro:v1
  4.     container_name: mysql_mcp_server_pro
  5.     ports:
  6.       - "9090:9000"
  7.     environment:
  8.       MYSQL_HOST: "192.168.20.128"
  9.       MYSQL_PORT: "3306"
  10.       MYSQL_USER: "root"
  11.       MYSQL_PASSWORD: "abcd@1234"
  12.       MYSQL_DATABASE: "test"
  13.       TZ: Asia/Shanghai
  14.     restart: always
复制代码
注意修改mysql相关环境变量
 
运行
  1. docker-compose up -d
复制代码
 
四、dify测试

上一篇文章,已经介绍了dify调用MCP工具,链接:https://www.cnblogs.com/xiao987334176/p/18827261
还是一样的dify工作流,测试即可。
注意:如果MCP server服务中断,dify是不会自动重连的,需要重启dify的plugin_daemon组件,就会重新连接MCP server
 
测试工作流
1.png

 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册