imgenx-mcp 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ import sys
2
+ sys.path.insert(0, '../../..')
3
+ import base64
4
+ import time
5
+ from pathlib import Path
6
+ from typing import List, Dict
7
+ from volcenginesdkarkruntime import Ark
8
+
9
+ from imgenx.predictor.base.base_video_generator import BaseVideoGenerator
10
+
11
+
12
+ class DoubaoVideoGenerator(BaseVideoGenerator):
13
+
14
+ def __init__(self, model: str, api_key: str):
15
+ self.model = model
16
+ self.client = Ark(
17
+ base_url='https://ark.cn-beijing.volces.com/api/v3',
18
+ api_key=api_key,
19
+ )
20
+
21
+ def text_to_video(self, prompt: str, resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> str:
22
+ create_result = self.client.content_generation.tasks.create(
23
+ model=self.model,
24
+ content=[
25
+ {
26
+ 'type': 'text',
27
+ 'text': prompt.strip()
28
+ }
29
+ ],
30
+ extra_body={
31
+ 'resolution': resolution,
32
+ 'ratio': ratio,
33
+ 'duration': duration,
34
+ }
35
+ )
36
+ task_id = create_result.id
37
+
38
+ while True:
39
+ get_result = self.client.content_generation.tasks.get(task_id=task_id)
40
+ status = get_result.status
41
+ if status == "succeeded":
42
+ return get_result.content.video_url
43
+ elif status == "failed":
44
+ raise Exception(str(get_result.error))
45
+ else:
46
+ time.sleep(3)
47
+
48
+ def image_to_video(self, prompt: str, first_frame: str, last_frame: str|None = None,
49
+ resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> str:
50
+ if not first_frame.startswith('http'):
51
+ first_frame = self._image_to_base64(first_frame)
52
+
53
+ if last_frame is not None and not last_frame.startswith('http'):
54
+ last_frame = self._image_to_base64(last_frame)
55
+
56
+ content = [
57
+ {
58
+ 'type': 'text',
59
+ 'text': prompt.strip()
60
+ },
61
+ {
62
+ 'type': 'image_url',
63
+ 'image_url': {
64
+ 'url': first_frame
65
+ },
66
+ 'role': 'first_frame'
67
+ }
68
+ ]
69
+
70
+ if last_frame is not None:
71
+ content.append({
72
+ 'type': 'image_url',
73
+ 'image_url': {
74
+ 'url': last_frame
75
+ },
76
+ 'role': 'last_frame'
77
+ })
78
+
79
+ create_result = self.client.content_generation.tasks.create(
80
+ model=self.model,
81
+ content=content,
82
+ extra_body={
83
+ 'resolution': resolution,
84
+ 'ratio': ratio,
85
+ 'duration': duration,
86
+ }
87
+ )
88
+ task_id = create_result.id
89
+
90
+ while True:
91
+ get_result = self.client.content_generation.tasks.get(task_id=task_id)
92
+ status = get_result.status
93
+ if status == "succeeded":
94
+ return get_result.content.video_url
95
+ elif status == "failed":
96
+ raise Exception(str(get_result.error))
97
+ else:
98
+ time.sleep(3)
99
+
100
+ def _image_to_base64(self, image_path: str) -> str:
101
+ image_path = Path(image_path)
102
+
103
+ with open(image_path, 'rb') as image_file:
104
+ base64_image = base64.b64encode(image_file.read()).decode('utf-8')
105
+ base64_image = f'data:image/{image_path.suffix.strip(".")};base64,{base64_image}'
106
+
107
+ return base64_image
108
+
109
+
110
+ if __name__ == '__main__':
111
+ import os
112
+ from dotenv import load_dotenv
113
+
114
+ load_dotenv()
115
+
116
+ api_key = os.getenv('IMGENX_API_KEY')
117
+ model = 'doubao-seedance-1-0-pro-fast-251015'
118
+
119
+ generator = DoubaoVideoGenerator(model, api_key)
120
+ image = '/Volumes/DATA/个人/project/imgenx-mcp-server/logo.jpg'
121
+ result = generator.image_to_video('一个人在运动', resolution='720p', ratio='16:9', first_frame=image, duration=5)
122
+ print(result)
imgenx/script.py ADDED
@@ -0,0 +1,103 @@
1
+ import os
2
+ from typing import List, Dict
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+
6
+ import requests
7
+ from dotenv import load_dotenv
8
+
9
+ from imgenx import factory
10
+
11
+
12
+ def text_to_image(model: str, api_key: str, prompt: str, size: str) -> List[Dict[str, str]]:
13
+ generator = factory.create_image_generator(model, api_key)
14
+ url_list = generator.text_to_image(prompt, size)
15
+ return url_list
16
+
17
+
18
+ def image_to_image(model: str, api_key: str, prompt: str, images: List[str], size: str) -> List[Dict[str, str]]:
19
+ generator = factory.create_image_generator(model, api_key)
20
+ url_list = generator.image_to_image(prompt, images, size)
21
+ return url_list
22
+
23
+
24
+ def text_to_video(model: str, api_key: str, prompt: str,
25
+ resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> str:
26
+ generator = factory.create_video_generator(model, api_key)
27
+ url = generator.text_to_video(prompt, resolution, ratio, duration)
28
+ return url
29
+
30
+
31
+ def image_to_video(model: str, api_key: str, prompt: str, first_frame: str, last_frame: str|None = None,
32
+ resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> str:
33
+ generator = factory.create_video_generator(model, api_key)
34
+ url = generator.image_to_video(prompt, first_frame, last_frame, resolution, ratio, duration)
35
+ return url
36
+
37
+
38
+ def gen_image(prompt: str, size: str, output: str, images: List[str] = None):
39
+ print('Generate images...')
40
+
41
+ load_dotenv()
42
+ model = os.getenv('IMGENX_IMAGE_MODEL')
43
+ api_key = os.getenv('IMGENX_API_KEY')
44
+
45
+ if model is None:
46
+ raise ValueError('Envrioment variable IMGENX_IMAGE_MODEL is empty.')
47
+
48
+ if api_key is None:
49
+ raise ValueError('Envrioment variable IMGENX_API_KEY is empty.')
50
+
51
+ output = Path(output)
52
+
53
+ if output.exists() and output.is_file():
54
+ raise ValueError(f'Output path {output} already exists.')
55
+
56
+ if images is not None and len(images) > 0:
57
+ url_list = image_to_image(model, api_key, prompt, images, size)
58
+ else:
59
+ url_list = text_to_image(model, api_key, prompt, size)
60
+
61
+ if output.is_dir():
62
+ path_list = [f'{output}/{datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}_{i + 1}.png' for i in range(len(url_list))]
63
+ elif len(url_list) == 1:
64
+ path_list = [output]
65
+ else:
66
+ path_list = [f'{output.parent}/{output.stem}_{i + 1}{output.suffix if output.suffix else ".jpg"}' for i in range(len(url_list))]
67
+
68
+ for url_item, path in zip(url_list, path_list):
69
+ response = requests.get(url_item['url'])
70
+ Path(path).write_bytes(response.content)
71
+ print(f'Save image to {path}')
72
+
73
+
74
+ def gen_video(prompt: str, first_frame: str|None = None, last_frame: str|None = None,
75
+ resolution: str = '720p', ratio: str = '16:9', duration: int = 5, output: str = None):
76
+ print('Generate video...')
77
+
78
+ load_dotenv()
79
+ model = os.getenv('IMGENX_VIDEO_MODEL')
80
+ api_key = os.getenv('IMGENX_API_KEY')
81
+
82
+ if model is None:
83
+ raise ValueError('Envrioment variable IMGENX_VIDEO_MODEL is empty.')
84
+
85
+ if api_key is None:
86
+ raise ValueError('Envrioment variable IMGENX_API_KEY is empty.')
87
+
88
+ if output is None:
89
+ output = f'{datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}.mp4'
90
+ else:
91
+ output = Path(output).with_name(f'{Path(output).stem}_{datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}{Path(output).suffix}')
92
+
93
+ if output.exists() and output.is_file():
94
+ raise ValueError(f'Output path {output} already exists.')
95
+
96
+ if first_frame is None and last_frame is None:
97
+ url = text_to_video(model, api_key, prompt, resolution, ratio, duration)
98
+ else:
99
+ url = image_to_video(model, api_key, prompt, first_frame, last_frame, resolution, ratio, duration)
100
+
101
+ response = requests.get(url)
102
+ Path(output).write_bytes(response.content)
103
+ print(f'Save video to {output}')
imgenx/server.py ADDED
@@ -0,0 +1,378 @@
1
+ import os
2
+ import re
3
+ from pathlib import Path
4
+ from typing import List, Dict, Tuple
5
+ from urllib.parse import urlparse
6
+
7
+ import requests
8
+ from dotenv import load_dotenv
9
+ from fastmcp import FastMCP
10
+ from fastmcp.exceptions import ToolError
11
+ from fastmcp.server.dependencies import get_http_headers
12
+
13
+ from imgenx import factory
14
+ from imgenx import operator
15
+ from imgenx.oss_service import get_oss_service
16
+
17
+
18
+ load_dotenv()
19
+
20
+ mcp = FastMCP(
21
+ name='imgenx-mcp-server',
22
+ instructions='图片视频生成工具,自动上传到 OSS 并返回永久 CDN URL',
23
+ )
24
+
25
+
26
+ def _upload_url_to_oss(url: str, business_dir: str = 'images') -> Dict[str, str]:
27
+ """内部函数:下载 URL 并上传到 OSS"""
28
+ try:
29
+ # 下载文件
30
+ response = requests.get(url, timeout=60)
31
+ response.raise_for_status()
32
+
33
+ # 从 URL 提取文件扩展名
34
+ path = urlparse(url).path
35
+ ext = Path(path).suffix or '.jpg'
36
+ filename = f'generated{ext}'
37
+
38
+ # 上传到 OSS
39
+ oss_service = get_oss_service()
40
+ result = oss_service.upload_bytes(response.content, filename, business_dir=business_dir)
41
+
42
+ return result
43
+ except Exception as e:
44
+ raise ToolError(f'上传到 OSS 失败: {e}')
45
+
46
+
47
+ @mcp.tool
48
+ def text_to_image(prompt: str, size: str = '2K') -> List[Dict[str, str]]:
49
+ '''根据输入的提示词生成图片,自动上传到 OSS 并返回永久 CDN URL。
50
+ 确保用 Markdown 格式输出图片,例如:![title](cdn_url)
51
+
52
+ Args:
53
+ prompt (str): 生成图片的提示词
54
+ size (str): 生成图像的分辨率或宽高像素值
55
+ 分辨率可选值:'1K'、'2K', '4K'
56
+ 宽高像素可选值:2048x2048、2304x1728、1728x2304、2560x1440、1440x2560、2496x1664、1664x2496、3024x1296
57
+
58
+ Returns:
59
+ List[Dict[str, str]]: 包含 cdn_url(CDN地址)、oss_url(OSS地址)、object_key(存储路径)
60
+ '''
61
+ headers = get_http_headers(include_all=True)
62
+ model = headers.get('imgenx_image_model', os.getenv('IMGENX_IMAGE_MODEL'))
63
+ api_key = headers.get('imgenx_api_key', os.getenv('IMGENX_API_KEY'))
64
+
65
+ if model is None:
66
+ raise ToolError('IMGENX_IMAGE_MODEL is None')
67
+
68
+ if api_key is None:
69
+ raise ToolError('IMGENX_API_KEY is None')
70
+
71
+ try:
72
+ # 生成图片
73
+ generator = factory.create_image_generator(model, api_key)
74
+ temp_url_list = generator.text_to_image(prompt, size)
75
+
76
+ # 自动上传到 OSS
77
+ result_list = []
78
+ for item in temp_url_list:
79
+ temp_url = item.get('url')
80
+ oss_result = _upload_url_to_oss(temp_url, business_dir='images')
81
+ result_list.append(oss_result)
82
+
83
+ return result_list
84
+ except Exception as e:
85
+ raise ToolError(f'Error: {e}')
86
+
87
+
88
+ @mcp.tool
89
+ def image_to_image(prompt: str, images: List[str], size: str = '2K') -> List[Dict[str, str]]:
90
+ '''根据输入的提示词和图片生成新图片,自动上传到 OSS 并返回永久 CDN URL。
91
+ 确保用 Markdown 格式输出图片,例如:![title](cdn_url)
92
+
93
+ Args:
94
+ prompt (str): 生成图片的提示词
95
+ images (List[str]): 输入图片 url 列表或文件路径列表
96
+ size (str): 生成图像的分辨率或宽高像素值
97
+ 分辨率可选值:'1K'、'2K', '4K'
98
+ 宽高像素可选值:2048x2048、2304x1728、1728x2304、2560x1440、1440x2560、2496x1664、1664x2496、3024x1296
99
+
100
+ Returns:
101
+ List[Dict[str, str]]: 包含 cdn_url(CDN地址)、oss_url(OSS地址)、object_key(存储路径)
102
+ '''
103
+ headers = get_http_headers(include_all=True)
104
+ model = headers.get('imgenx_image_model', os.getenv('IMGENX_IMAGE_MODEL'))
105
+ api_key = headers.get('imgenx_api_key', os.getenv('IMGENX_API_KEY'))
106
+
107
+ if model is None:
108
+ raise ToolError('IMGENX_IMAGE_MODEL is None')
109
+
110
+ if api_key is None:
111
+ raise ToolError('IMGENX_API_KEY is None')
112
+
113
+ try:
114
+ # 生成图片
115
+ generator = factory.create_image_generator(model, api_key)
116
+ temp_url_list = generator.image_to_image(prompt, images, size)
117
+
118
+ # 自动上传到 OSS
119
+ result_list = []
120
+ for item in temp_url_list:
121
+ temp_url = item.get('url')
122
+ oss_result = _upload_url_to_oss(temp_url, business_dir='images')
123
+ result_list.append(oss_result)
124
+
125
+ return result_list
126
+ except Exception as e:
127
+ raise ToolError(f'Error: {e}')
128
+
129
+
130
+ @mcp.tool
131
+ def text_to_video(prompt: str, resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> Dict[str, str]:
132
+ '''根据输入的提示词生成视频,自动上传到 OSS 并返回永久 CDN URL。
133
+ 确保用 Markdown 格式输出视频,例如:[title](cdn_url)
134
+
135
+ Args:
136
+ prompt (str): 生成视频的提示词
137
+ resolution (str): 生成视频的分辨率:480p、720p、1080p
138
+ ratio (str): 生成视频的比例:16:9、4:3、1:1、3:4、9:16、21:9
139
+ duration (int): 生成视频的时长,单位秒,支持 2~12 秒
140
+
141
+ Returns:
142
+ Dict[str, str]: 包含 cdn_url(CDN地址)、oss_url(OSS地址)、object_key(存储路径)
143
+ '''
144
+ headers = get_http_headers(include_all=True)
145
+ model = headers.get('imgenx_video_model', os.getenv('IMGENX_VIDEO_MODEL'))
146
+ api_key = headers.get('imgenx_api_key', os.getenv('IMGENX_API_KEY'))
147
+
148
+ if model is None:
149
+ raise ToolError('IMGENX_VIDEO_MODEL is None')
150
+
151
+ if api_key is None:
152
+ raise ToolError('IMGENX_API_KEY is None')
153
+
154
+ try:
155
+ # 生成视频
156
+ generator = factory.create_video_generator(model, api_key)
157
+ temp_url = generator.text_to_video(prompt, resolution, ratio, duration)
158
+
159
+ # 自动上传到 OSS
160
+ result = _upload_url_to_oss(temp_url, business_dir='videos')
161
+
162
+ return result
163
+ except Exception as e:
164
+ raise ToolError(f'Error: {e}')
165
+
166
+
167
+ @mcp.tool
168
+ def image_to_video(prompt: str, first_frame: str, last_frame: str|None = None,
169
+ resolution: str = '720p', ratio: str = '16:9', duration: int = 5) -> Dict[str, str]:
170
+ '''根据输入的提示词和视频首尾帧图片生成视频,自动上传到 OSS 并返回永久 CDN URL。
171
+ 确保用 Markdown 格式输出视频,例如:[title](cdn_url)
172
+
173
+ Args:
174
+ prompt (str): 生成视频的提示词
175
+ first_frame (str): 视频的首帧图片 url 或文件路径
176
+ last_frame (str|None): 视频的尾图片 url 或文件路径,默认 None
177
+ resolution (str): 生成视频的分辨率:480p、720p、1080p
178
+ ratio (str): 生成视频的比例:16:9、4:3、1:1、3:4、9:16、21:9
179
+ duration (int): 生成视频的时长,单位秒,支持 2~12 秒
180
+
181
+ Returns:
182
+ Dict[str, str]: 包含 cdn_url(CDN地址)、oss_url(OSS地址)、object_key(存储路径)
183
+ '''
184
+ headers = get_http_headers(include_all=True)
185
+ model = headers.get('imgenx_video_model', os.getenv('IMGENX_VIDEO_MODEL'))
186
+ api_key = headers.get('imgenx_api_key', os.getenv('IMGENX_API_KEY'))
187
+
188
+ if model is None:
189
+ raise ToolError('IMGENX_VIDEO_MODEL is None')
190
+
191
+ if api_key is None:
192
+ raise ToolError('IMGENX_API_KEY is None')
193
+
194
+ try:
195
+ # 生成视频
196
+ generator = factory.create_video_generator(model, api_key)
197
+ temp_url = generator.image_to_video(prompt, first_frame, last_frame, resolution, ratio, duration)
198
+
199
+ # 自动上传到 OSS
200
+ result = _upload_url_to_oss(temp_url, business_dir='videos')
201
+
202
+ return result
203
+ except Exception as e:
204
+ raise ToolError(f'Error: {e}')
205
+
206
+
207
+ @mcp.tool
208
+ def analyze_image(prompt: str, image: str) -> str:
209
+ '''分析图片获取精确的信息,确保用户需要分析、编辑、裁剪图片时先调用此工具。
210
+ 确保尽量用精确数字描述图片信息。
211
+ 输出图片裁剪区域时,确保给出精确**小数比例坐标**,坐标为左上角和右下角:x1(left), y1(upper), x2(right), y2(lower)
212
+
213
+ Args:
214
+ prompt (str): 分析图片的提示词
215
+ image (str): 图片路径或 URL
216
+
217
+ Returns:
218
+ str: 图片分析结果
219
+ '''
220
+ headers = get_http_headers(include_all=True)
221
+ model = headers.get('imgenx_analyzer_model', os.getenv('IMGENX_ANALYZER_MODEL'))
222
+ api_key = headers.get('imgenx_api_key', os.getenv('IMGENX_API_KEY'))
223
+
224
+ if model is None:
225
+ raise ToolError('IMGENX_ANALYZER_MODEL is None')
226
+
227
+ if api_key is None:
228
+ raise ToolError('IMGENX_API_KEY is None')
229
+
230
+ try:
231
+ info = operator.get_image_info(image)
232
+ prompt = f'image info: {info}\n\n{prompt}'
233
+
234
+ analyzer = factory.create_image_analyzer(model, api_key)
235
+ result = analyzer.analyze(prompt, image)
236
+ except Exception as e:
237
+ raise ToolError(f'Error: {e}')
238
+
239
+ return result
240
+
241
+
242
+ @mcp.tool
243
+ def get_image_info(image: str) -> Dict[str, str]:
244
+ '''获取图片分辨率和类型信息,确保用户需要获取图片分辨率和类型信息时调用此工具。
245
+
246
+ Args:
247
+ image (str): 图片路径或 URL
248
+
249
+ Returns:
250
+ Dict[str,str]: 分辨率和类型信息
251
+ '''
252
+ try:
253
+ info = operator.get_image_info(image)
254
+ except Exception as e:
255
+ raise ToolError(f'Error: {e}')
256
+
257
+ return info
258
+
259
+
260
+ @mcp.tool
261
+ def crop_image(image: str, box: str, output: str) -> Dict[str, str]:
262
+ '''框裁剪图片,确保用户需要裁剪图片时调用此工具。
263
+ Args:
264
+ image (str): 图片路径或 URL
265
+ box (str): 小数比例坐标,x1(left), y1(upper), x2(right), y2(lower)
266
+ output (str): 输出文件路径(后缀决定格式)
267
+
268
+ Returns:
269
+ Dict[str,str]: 生成图片的 path
270
+ '''
271
+ try:
272
+ operator.crop_image(image, box, output)
273
+ except Exception as e:
274
+ raise ToolError(f'Error: {e}')
275
+
276
+ p = Path(output).resolve()
277
+ return {'title': p.name, 'path': str(p)}
278
+
279
+
280
+ @mcp.tool
281
+ def resize_image(image: str, size: str, output: str, keep_aspect: bool = True) -> Dict[str, str]:
282
+ '''调整图片尺寸,确保用户需要调整图片尺寸时调用此工具。
283
+
284
+ Args:
285
+ image (str): 图片路径或 URL
286
+ size (str): "WIDTHxHEIGHT"
287
+ output (str): 输出文件路径
288
+ keep_aspect (bool): 是否保持比例(True 为等比不超过目标尺寸)
289
+
290
+ Returns:
291
+ Dict[str,str]: 生成图片的 path
292
+ '''
293
+ try:
294
+ operator.resize_image(image, size, output, keep_aspect=keep_aspect)
295
+ except Exception as e:
296
+ raise ToolError(f'Error: {e}')
297
+
298
+ p = Path(output).resolve()
299
+ return {'title': p.name, 'path': str(p)}
300
+
301
+
302
+ @mcp.tool
303
+ def convert_image(image: str, format: str, output: str, quality: int = 90) -> Dict[str, str]:
304
+ '''格式转换,确保用户需要转换图片格式时调用此工具。
305
+
306
+ Args:
307
+ image (str): 图片路径或 URL
308
+ format (str): 目标格式:PNG/JPEG/JPG/WEBP
309
+ output (str): 输出文件路径
310
+ quality (int): 压缩质量(针对有损格式)
311
+
312
+ Returns:
313
+ Dict[str,str]: 生成图片的 path
314
+ '''
315
+ try:
316
+ operator.convert_image(image, format, output, quality=quality)
317
+ except Exception as e:
318
+ raise ToolError(f'Error: {e}')
319
+
320
+ p = Path(output).resolve()
321
+ return {'title': p.name, 'path': str(p)}
322
+
323
+
324
+ @mcp.tool
325
+ def adjust_image(image: str, output: str, brightness: float = 1.0, contrast: float = 1.0, saturation: float = 1.0) -> Dict[str, str]:
326
+ '''基础图像调整:亮度/对比度/饱和度,确保用户需要调整图片时调用此工具。
327
+
328
+ Args:
329
+ image (str): 图片路径或 URL
330
+ output (str): 输出文件路径
331
+ brightness (float): 亮度,默认 1.0
332
+ contrast (float): 对比度,默认 1.0
333
+ saturation (float): 饱和度,默认 1.0
334
+
335
+ Returns:
336
+ Dict[str,str]: 生成图片的 path
337
+ '''
338
+ try:
339
+ operator.adjust_image(image, output, brightness=brightness, contrast=contrast, saturation=saturation)
340
+ except Exception as e:
341
+ raise ToolError(f'Error: {e}')
342
+
343
+ p = Path(output).resolve()
344
+ return {'title': p.name, 'path': str(p)}
345
+
346
+
347
+ @mcp.tool
348
+ def paste_image(front_image: str, background_image: str, output: str, position: Tuple[int, int]) -> Dict[str, str]:
349
+ '''将图片粘贴到背景图片上,确保用户需要粘贴图片时调用此工具。
350
+ 粘贴图片前,需要用 `resize_image` 工具调整 `front_image` 到适合的尺寸。
351
+ 调用 `analyze_image` 工具确定粘贴位置。
352
+
353
+ Args:
354
+ front_image (str): 图片路径或 URL
355
+ background_image (str): 背景图片路径或 URL
356
+ output (str): 输出文件路径
357
+ position (Tuple[int, int]): 粘贴位置的 (x, y),背景图片的左上角像素坐标
358
+
359
+ Returns:
360
+ Dict[str,str]: 生成图片的 path
361
+ '''
362
+ try:
363
+ operator.paste_image(front_image, background_image, position, output)
364
+ except Exception as e:
365
+ raise ToolError(f'Error: {e}')
366
+
367
+ p = Path(output).resolve()
368
+ return {'title': p.name, 'path': str(p)}
369
+
370
+
371
+ @mcp.custom_route('/health', methods=['GET'])
372
+ def health() -> str:
373
+ return 'success'
374
+
375
+
376
+ @mcp.custom_route('/healthy', methods=['GET'])
377
+ def healthy() -> str:
378
+ return 'success'