"""
SVG 转换工具 FastMCP 服务
基于 svg_converter.py 工具类的 FastMCP 服务封装,提供完整的SVG文件转换功能。
支持将SVG转换为PNG、ICO、JPG等格式,优先使用Cairo C库进行高质量渲染。
作者: Rusian Huu
版本: 1.0.0
"""
import os
import json
from pathlib import Path
from typing import Optional, List, Dict, Any, Union
from pydantic import Field
from typing_extensions import Annotated
from fastmcp import FastMCP
from .svg_converter import SVGConverter, SVGConverterError, DependencyError, ConversionError
# 创建 FastMCP 服务实例
mcp = FastMCP(
name="SVG Converter Tools",
instructions="""
这是一个专业的SVG文件转换服务,提供以下功能:
1. SVG文件转换 - 支持PNG、ICO、JPG格式输出
2. 批量转换 - 一次处理多个SVG文件
3. 引擎信息查询 - 查看当前可用的转换引擎
4. SVG信息分析 - 获取SVG文件的详细信息
转换引擎优先级:Cairo C库 > SVGLib > PIL(后备方案)
支持中文字符渲染和透明背景处理。
"""
)
# 支持的输出格式
SUPPORTED_FORMATS = ['png', 'ico', 'jpg', 'jpeg']
@mcp.tool
def convert_svg_file(
svg_file_path: Annotated[str, Field(description="SVG文件的完整路径")],
output_file_path: Annotated[str, Field(description="输出文件的完整路径,如果不指定则自动生成")] = "",
output_format: Annotated[str, Field(description="输出格式:png, ico, jpg, jpeg")] = "png",
width: Annotated[Optional[int], Field(description="输出宽度(像素),不指定则保持原始比例", ge=1, le=8192)] = None,
height: Annotated[Optional[int], Field(description="输出高度(像素),不指定则保持原始比例", ge=1, le=8192)] = None,
scale: Annotated[Optional[float], Field(description="缩放比例,与width/height互斥", gt=0, le=10)] = None,
quality: Annotated[int, Field(description="JPG质量(1-100)", ge=1, le=100)] = 95,
background: Annotated[str, Field(description="背景颜色,如'white', '#FFFFFF', 'transparent'")] = "white",
transparent: Annotated[bool, Field(description="是否使用透明背景(仅PNG/ICO支持)")] = False,
prefer_engine: Annotated[str, Field(description="首选转换引擎:auto, cairosvg, svglib, pil")] = "auto"
) -> Dict[str, Any]:
"""
将SVG文件转换为指定格式的图像文件
支持PNG、ICO、JPG格式输出,自动选择最佳转换引擎。
对于包含中文字符的SVG,会使用特殊的渲染方法确保正确显示。
"""
try:
# 参数验证
if output_format.lower() not in SUPPORTED_FORMATS:
return {
"success": False,
"error": f"不支持的输出格式: {output_format}。支持的格式: {', '.join(SUPPORTED_FORMATS)}"
}
if not os.path.exists(svg_file_path):
return {
"success": False,
"error": f"SVG文件不存在: {svg_file_path}"
}
# 创建转换器实例
converter = SVGConverter(prefer_engine=prefer_engine)
# 执行转换
result_path = converter.convert_file(
svg_path=svg_file_path,
output_path=output_file_path if output_file_path else None,
output_format=output_format,
width=width,
height=height,
scale=scale,
quality=quality,
background=background,
transparent=transparent
)
# 获取输出文件信息
output_path = Path(result_path)
file_size = output_path.stat().st_size
return {
"success": True,
"output_path": str(output_path),
"file_size_bytes": file_size,
"file_size_mb": round(file_size / (1024 * 1024), 2),
"format": output_format.lower(),
"engine_used": converter.engine,
"message": f"成功转换 {svg_file_path} 为 {output_format.upper()} 格式"
}
except DependencyError as e:
return {
"success": False,
"error": f"依赖库缺失: {str(e)}",
"suggestion": "请安装必要的依赖库,建议使用中国镜像源加速下载"
}
except ConversionError as e:
return {
"success": False,
"error": f"转换失败: {str(e)}"
}
except Exception as e:
return {
"success": False,
"error": f"未知错误: {str(e)}"
}
@mcp.tool
def convert_svg_string(
svg_content: Annotated[str, Field(description="SVG文件的字符串内容")],
output_file_path: Annotated[str, Field(description="输出文件的完整路径")],
output_format: Annotated[str, Field(description="输出格式:png, ico, jpg, jpeg")] = "png",
width: Annotated[Optional[int], Field(description="输出宽度(像素)", ge=1, le=8192)] = None,
height: Annotated[Optional[int], Field(description="输出高度(像素)", ge=1, le=8192)] = None,
scale: Annotated[Optional[float], Field(description="缩放比例", gt=0, le=10)] = None,
quality: Annotated[int, Field(description="JPG质量(1-100)", ge=1, le=100)] = 95,
background: Annotated[str, Field(description="背景颜色")] = "white",
transparent: Annotated[bool, Field(description="是否使用透明背景")] = False,
prefer_engine: Annotated[str, Field(description="首选转换引擎")] = "auto"
) -> Dict[str, Any]:
"""
将SVG字符串内容转换为指定格式的图像文件
适用于从内存中的SVG内容直接转换,无需先保存为文件。
"""
try:
# 参数验证
if output_format.lower() not in SUPPORTED_FORMATS:
return {
"success": False,
"error": f"不支持的输出格式: {output_format}。支持的格式: {', '.join(SUPPORTED_FORMATS)}"
}
if not svg_content.strip():
return {
"success": False,
"error": "SVG内容不能为空"
}
# 创建转换器实例
converter = SVGConverter(prefer_engine=prefer_engine)
# 执行转换
result_path = converter.convert_string(
svg_content=svg_content,
output_path=output_file_path,
output_format=output_format,
width=width,
height=height,
scale=scale,
quality=quality,
background=background,
transparent=transparent
)
# 获取输出文件信息
output_path = Path(result_path)
file_size = output_path.stat().st_size
return {
"success": True,
"output_path": str(output_path),
"file_size_bytes": file_size,
"file_size_mb": round(file_size / (1024 * 1024), 2),
"format": output_format.lower(),
"engine_used": converter.engine,
"message": f"成功从SVG字符串转换为 {output_format.upper()} 格式"
}
except DependencyError as e:
return {
"success": False,
"error": f"依赖库缺失: {str(e)}",
"suggestion": "请安装必要的依赖库"
}
except ConversionError as e:
return {
"success": False,
"error": f"转换失败: {str(e)}"
}
except Exception as e:
return {
"success": False,
"error": f"未知错误: {str(e)}"
}
@mcp.tool
def batch_convert_svg_files(
svg_files: Annotated[List[str], Field(description="SVG文件路径列表")],
output_directory: Annotated[str, Field(description="输出目录路径")],
output_format: Annotated[str, Field(description="输出格式:png, ico, jpg, jpeg")] = "png",
width: Annotated[Optional[int], Field(description="输出宽度(像素)", ge=1, le=8192)] = None,
height: Annotated[Optional[int], Field(description="输出高度(像素)", ge=1, le=8192)] = None,
quality: Annotated[int, Field(description="JPG质量(1-100)", ge=1, le=100)] = 95,
background: Annotated[str, Field(description="背景颜色")] = "white",
transparent: Annotated[bool, Field(description="是否使用透明背景")] = False,
prefer_engine: Annotated[str, Field(description="首选转换引擎")] = "auto"
) -> Dict[str, Any]:
"""
批量转换多个SVG文件
一次性处理多个SVG文件,提供详细的转换结果报告。
"""
try:
# 参数验证
if output_format.lower() not in SUPPORTED_FORMATS:
return {
"success": False,
"error": f"不支持的输出格式: {output_format}。支持的格式: {', '.join(SUPPORTED_FORMATS)}"
}
if not svg_files:
return {
"success": False,
"error": "SVG文件列表不能为空"
}
# 检查文件是否存在
missing_files = [f for f in svg_files if not os.path.exists(f)]
if missing_files:
return {
"success": False,
"error": f"以下文件不存在: {', '.join(missing_files)}"
}
# 执行批量转换
results = SVGConverter.batch_convert(
svg_files=svg_files,
output_dir=output_directory,
output_format=output_format,
width=width,
height=height,
quality=quality,
background=background,
transparent=transparent
)
# 统计结果
successful = [r for r in results if r[2]] # r[2] 是成功标志
failed = [r for r in results if not r[2]]
return {
"success": True,
"total_files": len(svg_files),
"successful_count": len(successful),
"failed_count": len(failed),
"successful_files": [{"input": r[0], "output": r[1]} for r in successful],
"failed_files": [{"input": r[0], "error": r[3]} for r in failed],
"output_directory": output_directory,
"format": output_format.lower(),
"message": f"批量转换完成:成功 {len(successful)} 个,失败 {len(failed)} 个"
}
except Exception as e:
return {
"success": False,
"error": f"批量转换失败: {str(e)}"
}
@mcp.tool
def get_converter_engine_info(
prefer_engine: Annotated[str, Field(description="首选转换引擎:auto, cairosvg, svglib, pil")] = "auto"
) -> Dict[str, Any]:
"""
获取SVG转换器的引擎信息
显示当前可用的转换引擎状态和推荐配置。
"""
try:
converter = SVGConverter(prefer_engine=prefer_engine)
engine_info = converter.get_engine_info()
return {
"success": True,
"current_engine": engine_info["current_engine"],
"available_engines": engine_info["available_engines"],
"engine_descriptions": engine_info["engine_descriptions"],
"recommendations": {
"best_quality": "cairosvg (需要Cairo C库)",
"good_compatibility": "svglib (需要ReportLab)",
"fallback": "pil (基本功能,已内置)"
},
"installation_tips": {
"cairosvg": "pip install cairosvg -i https://pypi.tuna.tsinghua.edu.cn/simple/",
"svglib": "pip install svglib reportlab -i https://pypi.tuna.tsinghua.edu.cn/simple/",
"pil": "pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple/"
}
}
except Exception as e:
return {
"success": False,
"error": f"获取引擎信息失败: {str(e)}"
}
@mcp.tool
def get_svg_file_info(
svg_file_path: Annotated[str, Field(description="SVG文件的完整路径")]
) -> Dict[str, Any]:
"""
获取SVG文件的详细信息
分析SVG文件的尺寸、内容复杂度等信息。
"""
try:
if not os.path.exists(svg_file_path):
return {
"success": False,
"error": f"SVG文件不存在: {svg_file_path}"
}
converter = SVGConverter()
svg_info = converter.get_svg_info(svg_file_path)
# 获取文件基本信息
file_path = Path(svg_file_path)
file_size = file_path.stat().st_size
return {
"success": True,
"file_path": str(file_path),
"file_name": file_path.name,
"file_size_bytes": file_size,
"file_size_kb": round(file_size / 1024, 2),
"svg_info": svg_info,
"conversion_recommendations": {
"suggested_formats": ["png"] if svg_info.get("has_text") else ["png", "jpg"],
"transparency_support": ["png", "ico"],
"best_quality_engine": "cairosvg" if svg_info.get("has_text") else "auto"
}
}
except Exception as e:
return {
"success": False,
"error": f"获取SVG文件信息失败: {str(e)}"
}
@mcp.tool
def get_svg_string_info(
svg_content: Annotated[str, Field(description="SVG文件的字符串内容")]
) -> Dict[str, Any]:
"""
获取SVG字符串内容的详细信息
分析SVG内容的尺寸、复杂度等信息,无需文件。
"""
try:
if not svg_content.strip():
return {
"success": False,
"error": "SVG内容不能为空"
}
converter = SVGConverter()
svg_info = converter.get_svg_info_from_string(svg_content)
# 分析内容特征
content_size = len(svg_content.encode('utf-8'))
has_chinese = bool(__import__('re').search(r'[\u4e00-\u9fff]', svg_content))
return {
"success": True,
"content_size_bytes": content_size,
"content_size_kb": round(content_size / 1024, 2),
"has_chinese_characters": has_chinese,
"svg_info": svg_info,
"conversion_recommendations": {
"suggested_formats": ["png"] if svg_info.get("has_text") or has_chinese else ["png", "jpg"],
"transparency_support": ["png", "ico"],
"best_quality_engine": "cairosvg" if svg_info.get("has_text") or has_chinese else "auto",
"chinese_text_note": "检测到中文字符,建议使用Cairo引擎以获得最佳渲染效果" if has_chinese else None
}
}
except Exception as e:
return {
"success": False,
"error": f"获取SVG内容信息失败: {str(e)}"
}
if __name__ == "__main__":
mcp.run()