11import os
22import asyncio
3- from typing import Optional
43from pydantic import Field
54import logging
65import argparse
7- from typing import Any , Literal
86from mcp .server .fastmcp import FastMCP
97from mcp_server_rds_mysql .resource .rds_mysql_resource import RDSMySQLSDK
8+ from typing import List , Dict , Any , Optional
109
1110# 初始化MCP服务
1211mcp_server = FastMCP ("rds_mysql_mcp_server" , port = int (os .getenv ("MCP_SERVER_PORT" , "8000" )))
1312logger = logging .getLogger ("rds_mysql_mcp_server" )
1413
1514rds_mysql_resource = RDSMySQLSDK (
16- region = os .getenv ('VOLCENGINE_REGION' ), ak = os .getenv ('VOLCENGINE_ACCESS_KEY' ), sk = os .getenv ('VOLCENGINE_SECRET_KEY' ), host = os .getenv ('VOLCENGINE_ENDPOINT' )
15+ region = os .getenv ('VOLCENGINE_REGION' , "cn-beijing" ), ak = os .getenv ('VOLCENGINE_ACCESS_KEY' ), sk = os .getenv ('VOLCENGINE_SECRET_KEY' ), host = os .getenv ('VOLCENGINE_ENDPOINT' )
1716)
1817
19- from typing import List , Dict , Any , Optional
20-
2118@mcp_server .tool (
2219 name = "describe_db_instances" ,
2320 description = "查询RDS MySQL实例列表"
@@ -459,9 +456,9 @@ def modify_db_account_description(
459456
460457@mcp_server .tool (
461458 name = "create_rds_mysql_instance" ,
462- description = "创建 RDS MySQL 实例 "
459+ description = "创建RDS MySQL实例,可选择是否等待实例就绪 "
463460)
464- def create_rds_mysql_instance (
461+ async def create_rds_mysql_instance (
465462 vpc_id : str = Field (title = "私有网络 ID" , description = "需要使用describe_vpcs获取" ),
466463 subnet_id : str = Field (title = "子网 ID" , description = "需要使用describe_subnets获取,subnet_id只有一个可用区属性,多可用区时找到一个与主节点或者备节点所在的可用区相同的即可" ),
467464 db_engine_version : str = Field (default = "MySQL_8_0" , description = "数据库版本" ),
@@ -494,43 +491,21 @@ def create_rds_mysql_instance(
494491 allow_list_ids : Optional [List [str ]] = Field (default = None , description = "白名单 ID 列表" ),
495492 port : int = Field (default = 3306 , description = "默认终端的私网端口" ),
496493 instance_tags : Optional [List [Dict ]] = Field (default = None , description = "实例标签列表" ),
497- maintenance_window : Optional [Dict ] = Field (default = None , description = "维护窗口配置" )
494+ maintenance_window : Optional [Dict ] = Field (default = None , description = "维护窗口配置" ),
495+ wait_for_ready : bool = Field (default = True , description = "是否等待实例就绪后再返回,默认为True。如设为False将立即返回创建结果不等待实例就绪" ),
496+ max_wait_time : int = Field (default = 600 , description = "等待实例就绪的最长时间(秒),默认为600秒(10分钟)。仅在wait_for_ready=True时有效" ),
497+ backoff_strategy : str = Field (default = "exponential" , description = "重试策略,可选值:exponential(指数退避)、fibonacci(斐波那契序列)" , choices = ["exponential" , "fibonacci" ])
498498) -> dict [str , Any ]:
499- """创建 RDS MySQL 实例
500-
501- Args:
502- vpc_id: 私有网络 ID
503- subnet_id: 子网 ID
504- db_engine_version: 数据库版本,默认 MySQL_8_0
505- instance_name: 实例名称
506- primary_zone: 主节点可用区,默认 cn-beijing-a
507- primary_spec: 主节点规格,默认 rds.mysql.1c2g
508- secondary_count: 备节点数量,默认 1
509- secondary_zone: 备节点可用区,默认与主节点相同
510- secondary_spec: 备节点规格,默认 rds.mysql.1c2g
511- read_only_count: 只读节点数量,默认 0
512- read_only_zone: 只读节点可用区,默认 cn-beijing-a
513- read_only_spec: 只读节点规格,默认 rds.mysql.1c2g
514- storage_space: 存储空间大小(GB),默认 20
515- storage_type: 存储类型,默认 LocalSSD
516- charge_type: 付费类型,默认 PostPaid
517- auto_renew: 预付费场景下是否自动续费
518- period_unit: 预付费场景下的购买周期(Month/Year)
519- period: 预付费场景下的购买时长
520- instance_type: 实例类型,默认 DoubleNode
521- super_account_name: 高权限账号名称
522- super_account_password: 高权限账号密码
523- lower_case_table_names: 表名是否区分大小写,默认 1
524- db_time_zone: 时区
525- db_param_group_id: 参数模板 ID
526- project_name: 实例所属项目
527- allow_list_ids: 白名单 ID 列表
528- port: 默认终端的私网端口,默认 3306
529- instance_tags: 实例标签列表
530- maintenance_window: 维护窗口配置
531-
532- Returns:
533- dict: 创建结果,包含实例ID和订单号等信息
499+ """创建RDS MySQL实例,可选择是否等待实例就绪
500+
501+ 此方法默认会在内部处理等待逻辑,只有当实例状态为"Running"时才会返回结果,
502+ 无需手动轮询检查实例状态。如果设置wait_for_ready=False,则会立即返回创建结果。
503+
504+ 支持两种重试策略:
505+ - exponential: 指数退避策略,等待间隔为 initial_wait * (2^n),如 5, 10, 20, 40秒...
506+ - fibonacci: 斐波那契序列策略,等待间隔为斐波那契序列 * initial_wait,如 5, 5, 10, 15, 25秒...
507+
508+ 通过max_wait_time参数可控制最长等待时间,默认为10分钟,一般大多数实例3-5分钟可创建完成,复杂实例可能需要更长时间。
534509 """
535510 node_info = []
536511
@@ -602,8 +577,85 @@ def create_rds_mysql_instance(
602577 if maintenance_window is not None :
603578 data ["maintenance_window" ] = maintenance_window
604579
605- resp = rds_mysql_resource .create_db_instance (data )
606- return resp .to_dict ()
580+ create_resp = rds_mysql_resource .create_db_instance (data )
581+
582+ instance_id = create_resp .instance_id
583+
584+ if instance_id is None :
585+ create_result = create_resp .to_dict ()
586+ instance_id = create_result .get ("instance_id" )
587+
588+ if instance_id is None :
589+ raise ValueError (f"无法获取实例ID,API响应: { create_result } " )
590+
591+ # If we don't need to wait for the instance to be ready, return creation result immediately
592+ if not wait_for_ready :
593+ return create_resp .to_dict ()
594+
595+ # 设置初始等待参数
596+ initial_wait = 5 # 初始等待5秒
597+ max_interval = 60 # 最大等待间隔60秒
598+
599+ logger .info (f"Waiting for instance { instance_id } to be ready using { backoff_strategy } backoff strategy" )
600+
601+ # 对于斐波那契序列,预先计算前20个数,足够我们使用
602+ fibonacci_sequence = [1 , 1 ]
603+ for i in range (2 , 20 ):
604+ fibonacci_sequence .append (fibonacci_sequence [i - 1 ] + fibonacci_sequence [i - 2 ])
605+
606+ time_spent = 0
607+ retry_count = 0
608+
609+ while time_spent < max_wait_time :
610+ # 根据策略计算等待间隔
611+ if backoff_strategy == "exponential" :
612+ # 指数退避: initial_wait * 2^n, 上限为max_interval
613+ wait_interval = min (initial_wait * (2 ** retry_count ), max_interval )
614+ else : # fibonacci
615+ # 斐波那契序列: 从第3项开始为前两项之和,但从1开始
616+ index = min (retry_count , len (fibonacci_sequence ) - 1 )
617+ wait_interval = min (initial_wait * fibonacci_sequence [index ], max_interval )
618+
619+ # 等待相应时间
620+ await asyncio .sleep (wait_interval )
621+ time_spent += wait_interval
622+ retry_count += 1
623+
624+ try :
625+ logger .info (f"Checking instance status, attempt { retry_count } , " +
626+ f"waited { wait_interval } s, total time: { time_spent } s" )
627+
628+ req = {"instance_id" : instance_id }
629+ detail_resp = rds_mysql_resource .describe_db_instance_detail (req )
630+ detail = detail_resp .to_dict ()
631+
632+ # 从响应中提取实例状态
633+ instance_status = None
634+
635+ if hasattr (detail_resp , 'basic_info' ) and detail_resp .basic_info is not None :
636+ if hasattr (detail_resp .basic_info , 'instance_status' ):
637+ instance_status = detail_resp .basic_info .instance_status
638+ else :
639+ basic_info_dict = detail_resp .basic_info .to_dict () if hasattr (detail_resp .basic_info , 'to_dict' ) else {}
640+ instance_status = basic_info_dict .get ('instance_status' )
641+ else :
642+ basic_info = detail .get ('basic_info' , {})
643+ instance_status = basic_info .get ('instance_status' )
644+
645+ if instance_status == "Running" :
646+ logger .info (f"Instance { instance_id } is now running after { retry_count } attempts, { time_spent } s" )
647+ return detail
648+ elif instance_status in ["Error" , "Failed" ]:
649+ logger .error (f"Instance { instance_id } creation failed with status: { instance_status } " )
650+ raise RuntimeError (f"Instance { instance_id } creation failed with status: { instance_status } " )
651+ else :
652+ logger .info (f"Instance { instance_id } current status: { instance_status } , continuing to wait..." )
653+ except Exception as e :
654+ logger .error (f"Error checking instance status: { str (e )} , retrying..." )
655+
656+ # 超时
657+ logger .error (f"Instance { instance_id } creation timed out after { time_spent } seconds" )
658+ raise TimeoutError (f"Instance { instance_id } creation timed out after { time_spent } seconds. Please check the instance status manually." )
607659
608660
609661@mcp_server .tool (
@@ -1012,7 +1064,7 @@ def create_db_account(
10121064
10131065@mcp_server .tool (
10141066 name = "describe_vpcs" ,
1015- description = "查询满足指定条件的VPC ,用于创建实例"
1067+ description = "查询VPC 信息 ,用于创建实例"
10161068)
10171069def describe_vpcs (
10181070 page_number : int = Field (default = 1 , description = "当前页页码,最小值为1" ),
@@ -1032,7 +1084,7 @@ def describe_vpcs(
10321084
10331085@mcp_server .tool (
10341086 name = "describe_subnets" ,
1035- description = "查询满足指定条件的子网 ,用于创建实例"
1087+ description = "查询子网信息 ,用于创建实例"
10361088)
10371089def describe_subnets (
10381090 vpc_id : str = Field (
@@ -1058,9 +1110,9 @@ def main():
10581110 parser .add_argument (
10591111 "--transport" ,
10601112 "-t" ,
1061- choices = ["sse" , "stdio" ],
1113+ choices = ["sse" , "stdio" , "streamable-http" ],
10621114 default = "stdio" ,
1063- help = "Transport protocol to use (sse or stdio )" ,
1115+ help = "Transport protocol to use (sse, stdio or streamable-http )" ,
10641116 )
10651117
10661118 args = parser .parse_args ()
0 commit comments