chore(general): 更新项目配置文件
- 添加必要的配置项 - 优化现有设置 - 确保环境兼容性
This commit is contained in:
File diff suppressed because one or more lines are too long
111
lzwcai_mcpskills_mfg_data_agent/test.py
Normal file
111
lzwcai_mcpskills_mfg_data_agent/test.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
def main(**kwargs) -> dict:
|
||||||
|
"""
|
||||||
|
函数节点的入口函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: 从前端配置的参数传入,可通过变量引用获取工作流上下文
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 返回结果将作为节点输出,可被后续节点引用
|
||||||
|
"""
|
||||||
|
# 从 kwargs 获取参数,如果没有提供则使用默认值
|
||||||
|
url = kwargs.get('url', 'http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema')
|
||||||
|
|
||||||
|
# 请求头(从kwargs获取或使用默认值)
|
||||||
|
authorization_token = kwargs.get('authorization_token', 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjAxNzZiNjEyLTc2YWItNDdhMS1iYTRiLTdjNWU2ZTMxNDlmZCJ9.w7aOfDJDHtA4bwNKIvUVK2cf1yO_2F27d_eYuos-p1-XGrSQOX0D4ny0b8Js36MhXwBnF4GDcy8V1VobEN6zBA')
|
||||||
|
headers = {
|
||||||
|
'Authorization': authorization_token
|
||||||
|
}
|
||||||
|
|
||||||
|
# 请求体参数(从kwargs获取或使用默认值)
|
||||||
|
payload_id = kwargs.get('id', '2006300000000000001')
|
||||||
|
business_name = kwargs.get('businessName', 'OrderDelayWarningAnalysis')
|
||||||
|
business_description = kwargs.get('businessDescription', '订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级')
|
||||||
|
datasource_id = kwargs.get('datasourceId', '57')
|
||||||
|
sql_template = kwargs.get('sqlTemplate', 'WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = \'CLOSED\' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = \'CLOSED\'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN (\'OPEN\', \'STARTED\')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN \'RED\' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN \'YELLOW\' ELSE \'GREEN\' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN \'PRODUCTION_SEVERELY_DELAYED\' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN \'HIGH_LOGISTICS_DELAY_RISK\' WHEN gm.avg_production_days > 15 THEN \'LONG_PRODUCTION_CYCLE\' WHEN gm.defect_rate_pct > 10 THEN \'QUALITY_ISSUES\' ELSE \'NORMAL\' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = \'t\' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id WHERE EXTRACT(YEAR FROM so.order_date_utc) = 2025 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 30')
|
||||||
|
parameters = kwargs.get('parameters', {})
|
||||||
|
|
||||||
|
# 请求体(从kwargs获取或使用默认值)
|
||||||
|
payload = {
|
||||||
|
"id": payload_id,
|
||||||
|
"businessName": business_name,
|
||||||
|
"businessDescription": business_description,
|
||||||
|
"datasourceId": datasource_id,
|
||||||
|
"sqlTemplate": sql_template,
|
||||||
|
"parameters": parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
# 超时时间(从kwargs获取或使用默认值)
|
||||||
|
timeout = kwargs.get('timeout', 30)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送POST请求
|
||||||
|
response = requests.post(
|
||||||
|
url=url,
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查HTTP响应状态码
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 解析响应结果
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
# 返回接口调用结果
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"result": result
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_type": "timeout",
|
||||||
|
"message": f"接口调用超时({timeout}秒)",
|
||||||
|
"result": None
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_type": "connection_error",
|
||||||
|
"message": "无法连接到目标服务器",
|
||||||
|
"result": None
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_type": "http_error",
|
||||||
|
"status_code": response.status_code if 'response' in locals() else None,
|
||||||
|
"message": f"HTTP请求错误: {str(e)}",
|
||||||
|
"response_text": response.text if 'response' in locals() else "",
|
||||||
|
"result": None
|
||||||
|
}
|
||||||
|
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_type": "json_decode_error",
|
||||||
|
"message": "接口返回的不是有效的JSON格式",
|
||||||
|
"response_text": response.text if 'response' in locals() else "",
|
||||||
|
"result": None
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_type": "unknown_error",
|
||||||
|
"message": f"接口调用失败: {str(e)}",
|
||||||
|
"result": None
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
result = main()
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
65
lzwcai_mcpskills_template/README.md
Normal file
65
lzwcai_mcpskills_template/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# lzwcai-mcpskills-template
|
||||||
|
|
||||||
|
MCP Server 模板项目,用于快速创建新的 MCP 服务。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 提供 MCP Server 基础框架
|
||||||
|
- 支持动态工具注册
|
||||||
|
- 内置日志系统
|
||||||
|
- 支持环境变量配置
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install lzwcai-mcpskills-template
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置环境变量
|
||||||
|
export API_KEY="your-api-key"
|
||||||
|
export BASE_URL="http://your-api-server"
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
lzwcai-mcpskills-template
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量名 | 说明 | 默认值 |
|
||||||
|
|--------|------|--------|
|
||||||
|
| API_KEY | API密钥 | - |
|
||||||
|
| BASE_URL | 后端API地址 | http://localhost:8080 |
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
lzwcai_mcpskills_template/
|
||||||
|
├── main.py # 入口文件
|
||||||
|
├── pyproject.toml # 项目配置
|
||||||
|
├── README.md # 说明文档
|
||||||
|
└── lzwcai_mcpskills_template/ # 核心代码
|
||||||
|
├── main.py # MCP Server 主逻辑
|
||||||
|
├── schema_converter.py # Schema 转换器
|
||||||
|
└── utils/ # 工具模块
|
||||||
|
├── __init__.py
|
||||||
|
├── api_client.py # API 客户端
|
||||||
|
├── env_config.py # 环境变量配置
|
||||||
|
├── json_helper.py # JSON 工具
|
||||||
|
└── logger_config.py # 日志配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
基于此模板创建新项目:
|
||||||
|
|
||||||
|
1. 复制整个目录
|
||||||
|
2. 修改 `pyproject.toml` 中的项目名称
|
||||||
|
3. 修改 `lzwcai_mcpskills_template` 目录名
|
||||||
|
4. 在 `main.py` 中实现你的工具逻辑
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
281
lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py
Normal file
281
lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MCP Server 模板
|
||||||
|
支持从本地 JSON 或 API 动态加载工具配置
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
- local: 从本地 JSON 文件加载工具配置
|
||||||
|
- api: 从远程 API 加载工具配置
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
import anyio
|
||||||
|
|
||||||
|
import mcp.types as types
|
||||||
|
from mcp.server import NotificationOptions, Server
|
||||||
|
from mcp.server.models import InitializationOptions
|
||||||
|
from mcp.server.stdio import stdio_server
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .schema_converter import convert_sql_params_to_input_schema
|
||||||
|
from .utils.api_client import call_api
|
||||||
|
from .utils.env_config import get_api_key, get_base_url
|
||||||
|
from .utils.logger_config import setup_system_logging, get_logger
|
||||||
|
except ImportError:
|
||||||
|
from schema_converter import convert_sql_params_to_input_schema
|
||||||
|
from utils.api_client import call_api
|
||||||
|
from utils.env_config import get_api_key, get_base_url
|
||||||
|
from utils.logger_config import setup_system_logging, get_logger
|
||||||
|
|
||||||
|
# 初始化日志系统
|
||||||
|
setup_system_logging(app_name="lzwcai_mcpskills_template", log_level=logging.DEBUG)
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# 初始化 MCP Server
|
||||||
|
server = Server("mcpskills_template_server")
|
||||||
|
|
||||||
|
# 全局配置
|
||||||
|
_tools_config: list[dict] = []
|
||||||
|
_config: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
"""解析命令行参数"""
|
||||||
|
parser = argparse.ArgumentParser(description="MCP Skills Template Server")
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode",
|
||||||
|
type=str,
|
||||||
|
choices=["local", "api"],
|
||||||
|
default="local",
|
||||||
|
help="数据加载模式: local(本地JSON,默认) 或 api(远程API)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--json-path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="本地 JSON 文件路径 (local 模式)"
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
class DataLoader:
|
||||||
|
"""数据加载器基类"""
|
||||||
|
|
||||||
|
def load(self) -> list[dict]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class LocalLoader(DataLoader):
|
||||||
|
"""本地 JSON 文件加载器"""
|
||||||
|
|
||||||
|
def __init__(self, json_path: str = None):
|
||||||
|
if json_path is None:
|
||||||
|
json_path = os.path.join(os.path.dirname(__file__), "tools_config.json")
|
||||||
|
self.json_path = json_path
|
||||||
|
|
||||||
|
def load(self) -> list[dict]:
|
||||||
|
try:
|
||||||
|
with open(self.json_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
logger.info(f"从本地加载 {len(data)} 条配置: {self.json_path}")
|
||||||
|
return data
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"配置文件不存在: {self.json_path}")
|
||||||
|
return []
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"JSON 解析错误: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class ApiLoader(DataLoader):
|
||||||
|
"""API 远程加载器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.base_url = get_base_url()
|
||||||
|
self.api_key = get_api_key()
|
||||||
|
logger.debug(f"ApiLoader 初始化,base_url: {self.base_url}")
|
||||||
|
|
||||||
|
def load(self) -> list[dict]:
|
||||||
|
try:
|
||||||
|
# TODO: 根据实际 API 修改此处
|
||||||
|
logger.info(f"开始从 API 加载工具配置")
|
||||||
|
response = call_api("/api/tools", method="GET")
|
||||||
|
|
||||||
|
if response.get("code") != 200:
|
||||||
|
logger.error(f"获取工具配置失败: {response.get('msg')}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
data = response.get("data", [])
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
logger.info(f"从 API 加载工具配置成功,数量: {len(data)}")
|
||||||
|
return data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"API 请求失败: {e}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def create_loader(mode: str, **kwargs) -> DataLoader:
|
||||||
|
"""创建数据加载器"""
|
||||||
|
if mode == "local":
|
||||||
|
return LocalLoader(json_path=kwargs.get("json_path"))
|
||||||
|
elif mode == "api":
|
||||||
|
return ApiLoader()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的模式: {mode}")
|
||||||
|
|
||||||
|
|
||||||
|
def init_tools(loader: DataLoader):
|
||||||
|
"""初始化工具配置"""
|
||||||
|
global _tools_config
|
||||||
|
_tools_config = loader.load()
|
||||||
|
logger.info(f"已加载 {len(_tools_config)} 个工具配置")
|
||||||
|
|
||||||
|
|
||||||
|
@server.list_tools()
|
||||||
|
async def handle_list_tools() -> list[types.Tool]:
|
||||||
|
"""列出所有可用工具"""
|
||||||
|
logger.info(f"收到 ListTools 请求,当前配置数量: {len(_tools_config)}")
|
||||||
|
tools = []
|
||||||
|
|
||||||
|
for tool in _tools_config:
|
||||||
|
name = tool.get("name", "")
|
||||||
|
description = tool.get("description") or tool.get("toolPrompt", "")
|
||||||
|
sql_params = tool.get("sqlParams", "[]")
|
||||||
|
|
||||||
|
# 转换参数为 inputSchema
|
||||||
|
input_schema = convert_sql_params_to_input_schema(sql_params)
|
||||||
|
|
||||||
|
tools.append(
|
||||||
|
types.Tool(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
inputSchema=input_schema
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"ListTools 响应: 返回 {len(tools)} 个工具")
|
||||||
|
return tools
|
||||||
|
|
||||||
|
|
||||||
|
@server.call_tool()
|
||||||
|
async def handle_call_tool(
|
||||||
|
name: str,
|
||||||
|
arguments: dict | None
|
||||||
|
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||||
|
"""调用工具"""
|
||||||
|
logger.info(f"收到 CallTool 请求: name={name}, arguments={json.dumps(arguments, ensure_ascii=False) if arguments else 'None'}")
|
||||||
|
|
||||||
|
# 查找对应的工具配置
|
||||||
|
tool_config = None
|
||||||
|
for tool in _tools_config:
|
||||||
|
if tool.get("name") == name:
|
||||||
|
tool_config = tool
|
||||||
|
break
|
||||||
|
|
||||||
|
if tool_config is None:
|
||||||
|
logger.error(f"未找到工具配置: {name}")
|
||||||
|
raise ValueError(f"未知工具: {name}")
|
||||||
|
|
||||||
|
# TODO: 在这里实现你的工具逻辑
|
||||||
|
# 示例:简单返回参数
|
||||||
|
try:
|
||||||
|
result = execute_tool(name, arguments or {}, tool_config)
|
||||||
|
logger.info(f"工具执行成功: {name}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"工具执行失败: {e}", exc_info=True)
|
||||||
|
result = {"error": str(e), "tool_name": name}
|
||||||
|
|
||||||
|
return [
|
||||||
|
types.TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, ensure_ascii=False, indent=2)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def execute_tool(name: str, arguments: dict, config: dict) -> dict:
|
||||||
|
"""
|
||||||
|
执行工具逻辑
|
||||||
|
|
||||||
|
TODO: 根据实际需求修改此函数
|
||||||
|
"""
|
||||||
|
# 示例实现
|
||||||
|
if name == "example_hello_world":
|
||||||
|
return {"message": f"Hello, {arguments.get('name', 'World')}!"}
|
||||||
|
|
||||||
|
elif name == "example_calculator":
|
||||||
|
a = float(arguments.get("a", 0))
|
||||||
|
b = float(arguments.get("b", 0))
|
||||||
|
op = arguments.get("operation", "+")
|
||||||
|
|
||||||
|
if op == "+":
|
||||||
|
result = a + b
|
||||||
|
elif op == "-":
|
||||||
|
result = a - b
|
||||||
|
elif op == "*":
|
||||||
|
result = a * b
|
||||||
|
elif op == "/":
|
||||||
|
result = a / b if b != 0 else "除数不能为0"
|
||||||
|
else:
|
||||||
|
result = "未知运算符"
|
||||||
|
|
||||||
|
return {"result": result, "expression": f"{a} {op} {b}"}
|
||||||
|
|
||||||
|
# 默认返回参数
|
||||||
|
return {
|
||||||
|
"tool_name": name,
|
||||||
|
"arguments": arguments,
|
||||||
|
"message": "工具执行成功(默认实现)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def run_server():
|
||||||
|
"""运行 MCP Server (stdio 模式)"""
|
||||||
|
async with stdio_server() as streams:
|
||||||
|
await server.run(
|
||||||
|
streams[0],
|
||||||
|
streams[1],
|
||||||
|
InitializationOptions(
|
||||||
|
server_name="mcpskills_template_server",
|
||||||
|
server_version="0.1.0",
|
||||||
|
capabilities=server.get_capabilities(
|
||||||
|
notification_options=NotificationOptions(),
|
||||||
|
experimental_capabilities={},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主入口"""
|
||||||
|
global _config
|
||||||
|
|
||||||
|
logger.info("=" * 50)
|
||||||
|
logger.info("MCP Skills Template Server 启动")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
args = parse_arguments()
|
||||||
|
_config = vars(args)
|
||||||
|
logger.info(f"命令行参数: {_config}")
|
||||||
|
|
||||||
|
# 创建加载器并初始化工具
|
||||||
|
logger.info(f"使用模式: {args.mode}")
|
||||||
|
loader = create_loader(
|
||||||
|
mode=args.mode,
|
||||||
|
json_path=args.json_path
|
||||||
|
)
|
||||||
|
init_tools(loader)
|
||||||
|
|
||||||
|
logger.info("开始运行 MCP Server (stdio 模式)")
|
||||||
|
# 运行服务器
|
||||||
|
anyio.run(run_server)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"""
|
||||||
|
Schema 转换器
|
||||||
|
将 sqlParams 数组格式转换为 MCP 工具需要的 JSON Schema 格式
|
||||||
|
|
||||||
|
支持的类型:
|
||||||
|
- string: 文本输入
|
||||||
|
- paragraph: 段落/多行文本
|
||||||
|
- select: 下拉选项
|
||||||
|
- number: 数字输入
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
|
||||||
|
"""
|
||||||
|
将单个参数转换为 JSON Schema property
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: 参数配置,格式如:
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "param_name",
|
||||||
|
"displayName": "参数显示名",
|
||||||
|
"maxLength": 200,
|
||||||
|
"defaultValue": "",
|
||||||
|
"required": true,
|
||||||
|
"options": ["选项1", "选项2"] # 仅 select 类型
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (property_name, property_schema, is_required)
|
||||||
|
"""
|
||||||
|
param_type = param.get("type", "string")
|
||||||
|
param_name = param.get("name", "")
|
||||||
|
display_name = param.get("displayName", param_name)
|
||||||
|
default_value = param.get("defaultValue", "")
|
||||||
|
max_length = param.get("maxLength")
|
||||||
|
is_required = param.get("required", False)
|
||||||
|
options = param.get("options", [])
|
||||||
|
|
||||||
|
property_schema = {
|
||||||
|
"description": display_name
|
||||||
|
}
|
||||||
|
|
||||||
|
if param_type == "string":
|
||||||
|
property_schema["type"] = "string"
|
||||||
|
if max_length:
|
||||||
|
property_schema["maxLength"] = max_length
|
||||||
|
|
||||||
|
elif param_type == "paragraph":
|
||||||
|
property_schema["type"] = "string"
|
||||||
|
property_schema["format"] = "paragraph"
|
||||||
|
if max_length:
|
||||||
|
property_schema["maxLength"] = max_length
|
||||||
|
|
||||||
|
elif param_type == "select":
|
||||||
|
property_schema["type"] = "string"
|
||||||
|
if options:
|
||||||
|
property_schema["enum"] = options
|
||||||
|
|
||||||
|
elif param_type == "number":
|
||||||
|
property_schema["type"] = "number"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 默认当作 string 处理
|
||||||
|
property_schema["type"] = "string"
|
||||||
|
|
||||||
|
# 添加默认值
|
||||||
|
if default_value not in (None, ""):
|
||||||
|
if param_type == "number":
|
||||||
|
try:
|
||||||
|
property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
property_schema["default"] = default_value
|
||||||
|
else:
|
||||||
|
property_schema["default"] = default_value
|
||||||
|
|
||||||
|
return param_name, property_schema, is_required
|
||||||
|
|
||||||
|
|
||||||
|
def convert_sql_params_to_input_schema(sql_params: str | list) -> dict:
|
||||||
|
"""
|
||||||
|
将 sqlParams 转换为 MCP 工具的 inputSchema
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql_params: sqlParams 字段值,可以是 JSON 字符串或已解析的列表
|
||||||
|
格式: [{"type": "string", "name": "xxx", ...}, ...]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: MCP 工具的 inputSchema,格式如:
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {...},
|
||||||
|
"required": [...]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# 解析 JSON 字符串
|
||||||
|
if isinstance(sql_params, str):
|
||||||
|
try:
|
||||||
|
params_list = json.loads(sql_params)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {"type": "object", "properties": {}, "required": []}
|
||||||
|
else:
|
||||||
|
params_list = sql_params
|
||||||
|
|
||||||
|
if not isinstance(params_list, list):
|
||||||
|
return {"type": "object", "properties": {}, "required": []}
|
||||||
|
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for param in params_list:
|
||||||
|
if not isinstance(param, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
name, schema, is_required = convert_param_to_schema_property(param)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
input_schema["properties"][name] = schema
|
||||||
|
if is_required:
|
||||||
|
input_schema["required"].append(name)
|
||||||
|
|
||||||
|
return input_schema
|
||||||
|
|
||||||
|
|
||||||
|
def convert_tool_config_to_mcp_tool(config: dict) -> dict:
|
||||||
|
"""
|
||||||
|
将单个工具配置转换为 MCP Tool 配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 工具配置对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: MCP Tool 配置
|
||||||
|
"""
|
||||||
|
name = config.get("name", "")
|
||||||
|
description = config.get("description") or config.get("toolPrompt", "")
|
||||||
|
sql_params = config.get("sqlParams", "[]")
|
||||||
|
|
||||||
|
input_schema = convert_sql_params_to_input_schema(sql_params)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"inputSchema": input_schema,
|
||||||
|
"_raw": config # 保留原始数据
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 测试用
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 测试单个参数转换
|
||||||
|
test_param = {
|
||||||
|
"type": "select",
|
||||||
|
"name": "operation",
|
||||||
|
"displayName": "运算符",
|
||||||
|
"required": True,
|
||||||
|
"options": ["+", "-", "*", "/"]
|
||||||
|
}
|
||||||
|
|
||||||
|
name, schema, required = convert_param_to_schema_property(test_param)
|
||||||
|
print(f"参数名: {name}")
|
||||||
|
print(f"Schema: {json.dumps(schema, ensure_ascii=False, indent=2)}")
|
||||||
|
print(f"必填: {required}")
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "example_tool_001",
|
||||||
|
"name": "example_hello_world",
|
||||||
|
"description": "示例工具 - Hello World",
|
||||||
|
"toolPrompt": "这是一个示例工具,用于演示 MCP 工具的基本结构",
|
||||||
|
"sqlParams": "[{\"type\":\"string\",\"name\":\"name\",\"displayName\":\"名称\",\"maxLength\":100,\"defaultValue\":\"World\",\"required\":true}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "example_tool_002",
|
||||||
|
"name": "example_calculator",
|
||||||
|
"description": "示例工具 - 简单计算器",
|
||||||
|
"toolPrompt": "执行简单的数学计算",
|
||||||
|
"sqlParams": "[{\"type\":\"number\",\"name\":\"a\",\"displayName\":\"数字A\",\"required\":true},{\"type\":\"number\",\"name\":\"b\",\"displayName\":\"数字B\",\"required\":true},{\"type\":\"select\",\"name\":\"operation\",\"displayName\":\"运算符\",\"required\":true,\"options\":[\"+\",\"-\",\"*\",\"/\"]}]"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"""Utils package for lzwcai_mcpskills_template"""
|
||||||
|
|
||||||
|
from .json_helper import load_json
|
||||||
|
from .api_client import call_api, APIClient, get_default_client
|
||||||
|
from .env_config import get_api_key, get_base_url, get_env_config, set_env_variable
|
||||||
|
from .logger_config import setup_system_logging, get_logger
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'load_json',
|
||||||
|
'call_api',
|
||||||
|
'APIClient',
|
||||||
|
'get_default_client',
|
||||||
|
'get_api_key',
|
||||||
|
'get_base_url',
|
||||||
|
'get_env_config',
|
||||||
|
'set_env_variable',
|
||||||
|
'setup_system_logging',
|
||||||
|
'get_logger'
|
||||||
|
]
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
"""
|
||||||
|
API 调用客户端
|
||||||
|
通用的 HTTP API 客户端封装
|
||||||
|
"""
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .env_config import get_base_url, get_api_key
|
||||||
|
from .logger_config import get_logger
|
||||||
|
except ImportError:
|
||||||
|
from env_config import get_base_url, get_api_key
|
||||||
|
from logger_config import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# 默认超时配置(秒)
|
||||||
|
DEFAULT_TIMEOUT = 30.0
|
||||||
|
LONG_TIMEOUT = 300.0
|
||||||
|
|
||||||
|
|
||||||
|
class APIClient:
|
||||||
|
"""通用 API 客户端"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_url: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
default_timeout: float = DEFAULT_TIMEOUT
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化 API 客户端
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: API 基础 URL(默认从环境变量读取)
|
||||||
|
api_key: API 密钥(默认从环境变量读取)
|
||||||
|
default_timeout: 默认超时时间(秒)
|
||||||
|
"""
|
||||||
|
self.base_url = (base_url or get_base_url()).rstrip('/')
|
||||||
|
self.api_key = api_key or get_api_key()
|
||||||
|
self.default_timeout = default_timeout
|
||||||
|
self._client: Optional[httpx.Client] = None
|
||||||
|
|
||||||
|
logger.info(f"[客户端初始化] base_url={self.base_url}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> httpx.Client:
|
||||||
|
"""懒加载 HTTP 客户端"""
|
||||||
|
if self._client is None:
|
||||||
|
self._client = httpx.Client(timeout=self.default_timeout)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def _get_headers(self) -> Dict[str, str]:
|
||||||
|
"""获取请求头"""
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
if self.api_key:
|
||||||
|
headers['X-API-Key'] = self.api_key
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def request(
|
||||||
|
self,
|
||||||
|
endpoint: str,
|
||||||
|
method: str = "GET",
|
||||||
|
data: Optional[Dict[str, Any]] = None,
|
||||||
|
params: Optional[Dict[str, Any]] = None,
|
||||||
|
timeout: Optional[float] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
发送 HTTP 请求
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint: API 端点路径
|
||||||
|
method: HTTP 方法
|
||||||
|
data: 请求体数据
|
||||||
|
params: URL 查询参数
|
||||||
|
timeout: 超时时间
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API 响应数据
|
||||||
|
"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
timeout = timeout or self.default_timeout
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] {method} {url}")
|
||||||
|
|
||||||
|
if method.upper() == "GET":
|
||||||
|
response = self.client.get(
|
||||||
|
url,
|
||||||
|
headers=self._get_headers(),
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
elif method.upper() == "POST":
|
||||||
|
response = self.client.post(
|
||||||
|
url,
|
||||||
|
headers=self._get_headers(),
|
||||||
|
json=data,
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
elif method.upper() == "PUT":
|
||||||
|
response = self.client.put(
|
||||||
|
url,
|
||||||
|
headers=self._get_headers(),
|
||||||
|
json=data,
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
elif method.upper() == "DELETE":
|
||||||
|
response = self.client.delete(
|
||||||
|
url,
|
||||||
|
headers=self._get_headers(),
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
||||||
|
|
||||||
|
logger.info(f"[API响应] HTTP {response.status_code}")
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
error_msg = f"API 请求超时: {url}"
|
||||||
|
logger.error(f"[API错误] {error_msg}")
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}"
|
||||||
|
logger.error(f"[API错误] {error_msg}")
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"API 请求异常: {str(e)}"
|
||||||
|
logger.error(f"[API错误] {error_msg}", exc_info=True)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭 HTTP 客户端"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.close()
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# 懒加载的默认客户端
|
||||||
|
_default_client: Optional[APIClient] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_client() -> APIClient:
|
||||||
|
"""获取默认客户端(懒加载)"""
|
||||||
|
global _default_client
|
||||||
|
if _default_client is None:
|
||||||
|
_default_client = APIClient()
|
||||||
|
return _default_client
|
||||||
|
|
||||||
|
|
||||||
|
def call_api(
|
||||||
|
endpoint: str,
|
||||||
|
method: str = "GET",
|
||||||
|
data: Optional[Dict[str, Any]] = None,
|
||||||
|
params: Optional[Dict[str, Any]] = None,
|
||||||
|
timeout: Optional[float] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
便捷函数:调用 API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint: API 端点路径
|
||||||
|
method: HTTP 方法
|
||||||
|
data: 请求体数据
|
||||||
|
params: URL 查询参数
|
||||||
|
timeout: 超时时间
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API 响应数据
|
||||||
|
"""
|
||||||
|
return get_default_client().request(
|
||||||
|
endpoint=endpoint,
|
||||||
|
method=method,
|
||||||
|
data=data,
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""环境变量配置模块"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_key(default: str = "") -> str:
|
||||||
|
"""
|
||||||
|
获取 API 密钥环境变量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: API 密钥
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
API_KEY: API 密钥
|
||||||
|
"""
|
||||||
|
return os.environ.get("API_KEY", default)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_url(default: str = "http://localhost:8080") -> str:
|
||||||
|
"""
|
||||||
|
获取后端 API 基础 URL 环境变量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 后端 API 基础 URL
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
BASE_URL: 后端 API 基础 URL
|
||||||
|
"""
|
||||||
|
return os.environ.get("BASE_URL", default)
|
||||||
|
|
||||||
|
|
||||||
|
def get_env_config() -> dict:
|
||||||
|
"""
|
||||||
|
获取所有环境配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含所有配置的字典
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"api_key": get_api_key(),
|
||||||
|
"base_url": get_base_url()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def set_env_variable(key: str, value: str) -> None:
|
||||||
|
"""
|
||||||
|
设置环境变量(仅在当前进程中有效)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 环境变量名
|
||||||
|
value: 环境变量值
|
||||||
|
"""
|
||||||
|
os.environ[key] = value
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""JSON 文件读取工具"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(json_path: Union[str, Path]) -> Any:
|
||||||
|
"""
|
||||||
|
读取 JSON 文件并返回其内容
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path: JSON 文件的路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON 文件中解析后的数据
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: 当文件不存在时
|
||||||
|
json.JSONDecodeError: 当 JSON 格式无效时
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
path = Path(json_path)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"JSON 文件不存在: {json_path}")
|
||||||
|
|
||||||
|
if not path.is_file():
|
||||||
|
raise ValueError(f"路径不是一个文件: {json_path}")
|
||||||
|
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise json.JSONDecodeError(f"JSON 格式错误: {e.msg}", e.doc, e.pos)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"读取 JSON 文件时发生错误: {str(e)}")
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
统一日志配置模块
|
||||||
|
提供系统级别的日志配置和管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerConfig:
|
||||||
|
"""日志配置管理类"""
|
||||||
|
|
||||||
|
def __init__(self, logs_dir: str = None):
|
||||||
|
"""初始化日志配置"""
|
||||||
|
if logs_dir:
|
||||||
|
self.logs_dir = Path(logs_dir)
|
||||||
|
else:
|
||||||
|
project_root = Path(__file__).parent.parent
|
||||||
|
self.logs_dir = project_root / "logs"
|
||||||
|
|
||||||
|
self.logs_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
||||||
|
self.date_format = '%Y-%m-%d %H:%M:%S'
|
||||||
|
self.log_level = self._get_log_level_from_env()
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
|
def _get_log_level_from_env(self) -> int:
|
||||||
|
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||||
|
level_mapping = {
|
||||||
|
'DEBUG': logging.DEBUG,
|
||||||
|
'INFO': logging.INFO,
|
||||||
|
'WARNING': logging.WARNING,
|
||||||
|
'WARN': logging.WARNING,
|
||||||
|
'ERROR': logging.ERROR,
|
||||||
|
'CRITICAL': logging.CRITICAL,
|
||||||
|
'FATAL': logging.CRITICAL
|
||||||
|
}
|
||||||
|
return level_mapping.get(log_level_str, logging.INFO)
|
||||||
|
|
||||||
|
def setup_logging(self,
|
||||||
|
app_name: str = "lzwcai_mcpskills_template",
|
||||||
|
log_level: int = logging.INFO,
|
||||||
|
max_file_size: int = 10 * 1024 * 1024,
|
||||||
|
backup_count: int = 5,
|
||||||
|
console_output: bool = True) -> logging.Logger:
|
||||||
|
if self._initialized:
|
||||||
|
return logging.getLogger()
|
||||||
|
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(log_level)
|
||||||
|
|
||||||
|
for handler in root_logger.handlers[:]:
|
||||||
|
root_logger.removeHandler(handler)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||||
|
|
||||||
|
# 1. 主日志文件 - 按大小滚动
|
||||||
|
main_log_file = self.logs_dir / f"{app_name}.log"
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
main_log_file,
|
||||||
|
maxBytes=max_file_size,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
file_handler.setLevel(log_level)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# 2. 错误日志文件
|
||||||
|
error_log_file = self.logs_dir / f"{app_name}_error.log"
|
||||||
|
error_handler = RotatingFileHandler(
|
||||||
|
error_log_file,
|
||||||
|
maxBytes=max_file_size,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
error_handler.setLevel(logging.ERROR)
|
||||||
|
error_handler.setFormatter(formatter)
|
||||||
|
root_logger.addHandler(error_handler)
|
||||||
|
|
||||||
|
# 3. 按日期滚动的日志文件
|
||||||
|
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
|
||||||
|
daily_handler = TimedRotatingFileHandler(
|
||||||
|
daily_log_file,
|
||||||
|
when='midnight',
|
||||||
|
interval=1,
|
||||||
|
backupCount=30,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
daily_handler.setLevel(log_level)
|
||||||
|
daily_handler.setFormatter(formatter)
|
||||||
|
daily_handler.suffix = "%Y-%m-%d"
|
||||||
|
root_logger.addHandler(daily_handler)
|
||||||
|
|
||||||
|
# 4. 控制台输出 (MCP协议使用stdio时,必须将日志输出到stderr)
|
||||||
|
if console_output:
|
||||||
|
console_handler = logging.StreamHandler(sys.stderr)
|
||||||
|
console_handler.setLevel(log_level)
|
||||||
|
console_formatter = logging.Formatter(
|
||||||
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
self.date_format
|
||||||
|
)
|
||||||
|
console_handler.setFormatter(console_formatter)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
|
||||||
|
|
||||||
|
return root_logger
|
||||||
|
|
||||||
|
def get_module_logger(self, module_name: str) -> logging.Logger:
|
||||||
|
return logging.getLogger(module_name)
|
||||||
|
|
||||||
|
|
||||||
|
# 全局日志配置实例
|
||||||
|
logger_config = LoggerConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_system_logging(app_name: str = "lzwcai_mcpskills_template",
|
||||||
|
log_level: int = logging.INFO) -> logging.Logger:
|
||||||
|
return logger_config.setup_logging(app_name, log_level)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(name: str) -> logging.Logger:
|
||||||
|
return logger_config.get_module_logger(name)
|
||||||
15
lzwcai_mcpskills_template/main.py
Normal file
15
lzwcai_mcpskills_template/main.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Entry point for lzwcai-mcpskills-template
|
||||||
|
MCP Server 模板项目入口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 设置环境变量(根据实际需求修改)
|
||||||
|
os.environ["API_KEY"] = "your-api-key"
|
||||||
|
os.environ["BASE_URL"] = "http://localhost:8080"
|
||||||
|
|
||||||
|
# Import and run the actual MCP server
|
||||||
|
from lzwcai_mcpskills_template.main import main
|
||||||
|
main()
|
||||||
31
lzwcai_mcpskills_template/pyproject.toml
Normal file
31
lzwcai_mcpskills_template/pyproject.toml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "lzwcai_mcpskills_template"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "MCP Server 模板项目 - 用于快速创建新的 MCP 服务"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "lzwcai", email = "your-email@example.com"},
|
||||||
|
]
|
||||||
|
keywords = ["mcp", "template", "server"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"httpx>=0.28.1",
|
||||||
|
"mcp[cli]>=1.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
lzwcai_mcpskills_template = "lzwcai_mcpskills_template.main:main"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["lzwcai_mcpskills_template"]
|
||||||
Reference in New Issue
Block a user