doubao-image-server 1.0.0__tar.gz
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.
- doubao_image_server-1.0.0/PKG-INFO +10 -0
- doubao_image_server-1.0.0/README.md +21 -0
- doubao_image_server-1.0.0/pyproject.toml +17 -0
- doubao_image_server-1.0.0/setup.cfg +4 -0
- doubao_image_server-1.0.0/setup.py +10 -0
- doubao_image_server-1.0.0/src/doubao_image_server/__init__.py +303 -0
- doubao_image_server-1.0.0/src/doubao_image_server/__main__.py +3 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/PKG-INFO +10 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/SOURCES.txt +11 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/dependency_links.txt +1 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/entry_points.txt +2 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/requires.txt +4 -0
- doubao_image_server-1.0.0/src/doubao_image_server.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doubao-image-server
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 基于火山引擎豆包 Seedream 的图片/视频生成 MCP 服务器
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: mcp>=1.0.0
|
|
7
|
+
Requires-Dist: httpx
|
|
8
|
+
Requires-Dist: Pillow
|
|
9
|
+
Requires-Dist: openai
|
|
10
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# doubao-image-server
|
|
2
|
+
|
|
3
|
+
基于火山引擎豆包 Seedream 的图片/视频生成 MCP 服务器。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pipx run doubao-image-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 配置
|
|
12
|
+
|
|
13
|
+
环境变量 `DOUBAO_API_KEY` 或启动后调用 `set_api_key`。
|
|
14
|
+
|
|
15
|
+
## 工具
|
|
16
|
+
|
|
17
|
+
- `set_api_key` — 设置 API Key
|
|
18
|
+
- `text_to_image` — 文生图
|
|
19
|
+
- `image_to_image` — 图生图
|
|
20
|
+
- `text_to_video` — 文生视频
|
|
21
|
+
- `image_to_video` — 图生视频
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "doubao-image-server"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "基于火山引擎豆包 Seedream 的图片/视频生成 MCP 服务器"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"mcp>=1.0.0",
|
|
8
|
+
"httpx",
|
|
9
|
+
"Pillow",
|
|
10
|
+
"openai",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
doubao-image-server = "doubao_image_server:cli"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
setup(
|
|
3
|
+
name="doubao-image-server",
|
|
4
|
+
version="1.0.0",
|
|
5
|
+
packages=find_packages(where="src"),
|
|
6
|
+
package_dir={"": "src"},
|
|
7
|
+
install_requires=["mcp>=1.0.0", "httpx", "Pillow", "openai"],
|
|
8
|
+
entry_points={"console_scripts": ["doubao-image-server=doubao_image_server:cli"]},
|
|
9
|
+
python_requires=">=3.10",
|
|
10
|
+
)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""
|
|
2
|
+
豆包 MCP Server — 文生图 · 图生图 · 文生视频 · 图生视频
|
|
3
|
+
模型:Seedream 4.5 / Seedance 2.0
|
|
4
|
+
协议:FastMCP + stdio
|
|
5
|
+
|
|
6
|
+
✅ 图片:返回缩略图预览(768px JPEG)+ 独立 HD 链接工具(本 AI 替你同时调,一次全出)
|
|
7
|
+
✅ 视频:保持原样
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
import io
|
|
13
|
+
import httpx
|
|
14
|
+
from PIL import Image as PILImage
|
|
15
|
+
from typing import Any, Dict
|
|
16
|
+
from openai import OpenAI
|
|
17
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
18
|
+
|
|
19
|
+
_DEFAULT_API_KEY = os.environ.get("DOUBAO_API_KEY", "")
|
|
20
|
+
|
|
21
|
+
mcp = FastMCP("doubao-image-gen")
|
|
22
|
+
_client: OpenAI | None = None
|
|
23
|
+
|
|
24
|
+
# ── 缓存最近一次图片的原图 URL ──
|
|
25
|
+
_last_ttx_url: str | None = None # 文生图
|
|
26
|
+
_last_itx_url: str | None = None # 图生图
|
|
27
|
+
|
|
28
|
+
PREVIEW_MAX_SIDE = 768
|
|
29
|
+
PREVIEW_QUALITY = 75
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _init_client():
|
|
33
|
+
global _client
|
|
34
|
+
if _client is None:
|
|
35
|
+
_client = OpenAI(
|
|
36
|
+
base_url="https://ark.cn-beijing.volces.com/api/v3",
|
|
37
|
+
api_key=_DEFAULT_API_KEY,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_client() -> OpenAI:
|
|
42
|
+
if _client is None:
|
|
43
|
+
_init_client()
|
|
44
|
+
return _client
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _download_and_compress(url: str) -> bytes:
|
|
48
|
+
"""下载原图 → 压缩为 768px JPEG(~8K token)"""
|
|
49
|
+
resp = httpx.get(url, follow_redirects=True, timeout=30)
|
|
50
|
+
resp.raise_for_status()
|
|
51
|
+
|
|
52
|
+
img = PILImage.open(io.BytesIO(resp.content))
|
|
53
|
+
if img.mode in ("RGBA", "P"):
|
|
54
|
+
img = img.convert("RGB")
|
|
55
|
+
|
|
56
|
+
w, h = img.size
|
|
57
|
+
if w > PREVIEW_MAX_SIDE or h > PREVIEW_MAX_SIDE:
|
|
58
|
+
ratio = min(PREVIEW_MAX_SIDE / w, PREVIEW_MAX_SIDE / h)
|
|
59
|
+
img = img.resize((int(w * ratio), int(h * ratio)), PILImage.LANCZOS)
|
|
60
|
+
|
|
61
|
+
buf = io.BytesIO()
|
|
62
|
+
img.save(buf, format="JPEG", quality=PREVIEW_QUALITY, optimize=True)
|
|
63
|
+
return buf.getvalue()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ========== ① 设置 API Key ==========
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def set_api_key(api_key: str) -> str:
|
|
70
|
+
"""设置火山引擎 API Key"""
|
|
71
|
+
global _client
|
|
72
|
+
_client = OpenAI(
|
|
73
|
+
base_url="https://ark.cn-beijing.volces.com/api/v3",
|
|
74
|
+
api_key=api_key,
|
|
75
|
+
)
|
|
76
|
+
return "✅ API Key 已更新"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ========== ② 文生图(缩略图)==========
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
def text_to_image(
|
|
83
|
+
prompt: str,
|
|
84
|
+
size: str = "2560x1440",
|
|
85
|
+
model: str = "doubao-seedream-4-5-251128",
|
|
86
|
+
) -> Image:
|
|
87
|
+
"""
|
|
88
|
+
文生图 - 返回缩略图预览(768px JPEG,省 token)。
|
|
89
|
+
我会同时自动调 text_to_image_hd_url 给你高清原图链接,无需你手动操作。
|
|
90
|
+
"""
|
|
91
|
+
global _last_ttx_url
|
|
92
|
+
|
|
93
|
+
client = get_client()
|
|
94
|
+
resp = client.images.generate(
|
|
95
|
+
model=model,
|
|
96
|
+
prompt=prompt,
|
|
97
|
+
size=size,
|
|
98
|
+
response_format="url",
|
|
99
|
+
)
|
|
100
|
+
if not resp.data or not resp.data[0].url:
|
|
101
|
+
raise RuntimeError("未返回图片数据")
|
|
102
|
+
|
|
103
|
+
_last_ttx_url = resp.data[0].url
|
|
104
|
+
thumb = _download_and_compress(_last_ttx_url)
|
|
105
|
+
|
|
106
|
+
return Image(data=thumb, format="jpeg")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
def text_to_image_hd_url() -> str:
|
|
111
|
+
"""获取最近一次文生图的高清原图链接(有时效,请尽快下载保存)"""
|
|
112
|
+
if not _last_ttx_url:
|
|
113
|
+
return "❌ 没有缓存的原图,请先调用 text_to_image 生成图片"
|
|
114
|
+
return f"📥 **高清原图链接**: {_last_ttx_url}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ========== ③ 图生图(缩略图)==========
|
|
118
|
+
|
|
119
|
+
@mcp.tool()
|
|
120
|
+
def image_to_image(
|
|
121
|
+
prompt: str,
|
|
122
|
+
image_url: str,
|
|
123
|
+
size: str = "2560x1440",
|
|
124
|
+
model: str = "doubao-seedream-4-5-251128",
|
|
125
|
+
) -> Image:
|
|
126
|
+
"""
|
|
127
|
+
图生图 - 返回缩略图预览(768px JPEG,省 token)。
|
|
128
|
+
我会同时自动调 image_to_image_hd_url 给你高清原图链接,无需你手动操作。
|
|
129
|
+
"""
|
|
130
|
+
global _last_itx_url
|
|
131
|
+
|
|
132
|
+
client = get_client()
|
|
133
|
+
resp = client.images.generate(
|
|
134
|
+
model=model,
|
|
135
|
+
prompt=prompt,
|
|
136
|
+
image=image_url,
|
|
137
|
+
size=size,
|
|
138
|
+
response_format="url",
|
|
139
|
+
)
|
|
140
|
+
if not resp.data or not resp.data[0].url:
|
|
141
|
+
raise RuntimeError("未返回图片数据")
|
|
142
|
+
|
|
143
|
+
_last_itx_url = resp.data[0].url
|
|
144
|
+
thumb = _download_and_compress(_last_itx_url)
|
|
145
|
+
|
|
146
|
+
return Image(data=thumb, format="jpeg")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcp.tool()
|
|
150
|
+
def image_to_image_hd_url() -> str:
|
|
151
|
+
"""获取最近一次图生图的高清原图链接(有时效,请尽快下载保存)"""
|
|
152
|
+
if not _last_itx_url:
|
|
153
|
+
return "❌ 没有缓存的原图,请先调用 image_to_image 生成图片"
|
|
154
|
+
return f"📥 **高清原图链接**: {_last_itx_url}"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ========== ④ 文生视频(不动)==========
|
|
158
|
+
|
|
159
|
+
@mcp.tool()
|
|
160
|
+
def text_to_video(
|
|
161
|
+
prompt: str,
|
|
162
|
+
resolution: str = "720p",
|
|
163
|
+
ratio: str = "16:9",
|
|
164
|
+
model: str = "doubao-seedance-2-0-260128",
|
|
165
|
+
) -> Dict[str, Any]:
|
|
166
|
+
"""文生视频"""
|
|
167
|
+
try:
|
|
168
|
+
client = get_client()
|
|
169
|
+
if ratio and "--ratio" not in prompt:
|
|
170
|
+
prompt += f" --ratio {ratio}"
|
|
171
|
+
if resolution and "--resolution" not in prompt:
|
|
172
|
+
prompt += f" --resolution {resolution}"
|
|
173
|
+
|
|
174
|
+
task = client.content_generation.tasks.create(
|
|
175
|
+
model=model, content=[{"type": "text", "text": prompt}]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for _ in range(60):
|
|
179
|
+
time.sleep(5)
|
|
180
|
+
t = client.content_generation.tasks.get(task_id=task.id)
|
|
181
|
+
if t.status == "succeeded":
|
|
182
|
+
return {"success": True, "video_url": t.content.video_url}
|
|
183
|
+
elif t.status in ("failed", "canceled"):
|
|
184
|
+
return {"success": False, "error": f"任务 {t.status}"}
|
|
185
|
+
return {"success": False, "error": "超时"}
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return {"success": False, "error": str(e)}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ========== ⑤ 图生视频(不动)==========
|
|
191
|
+
|
|
192
|
+
@mcp.tool()
|
|
193
|
+
def image_to_video(
|
|
194
|
+
prompt: str,
|
|
195
|
+
image_url: str,
|
|
196
|
+
resolution: str = "720p",
|
|
197
|
+
ratio: str = "16:9",
|
|
198
|
+
model: str = "doubao-seedance-2-0-260128",
|
|
199
|
+
) -> Dict[str, Any]:
|
|
200
|
+
"""图生视频"""
|
|
201
|
+
try:
|
|
202
|
+
client = get_client()
|
|
203
|
+
if ratio and "--ratio" not in prompt:
|
|
204
|
+
prompt += f" --ratio {ratio}"
|
|
205
|
+
if resolution and "--resolution" not in prompt:
|
|
206
|
+
prompt += f" --resolution {resolution}"
|
|
207
|
+
|
|
208
|
+
task = client.content_generation.tasks.create(
|
|
209
|
+
model=model,
|
|
210
|
+
content=[
|
|
211
|
+
{"type": "text", "text": prompt},
|
|
212
|
+
{"type": "image_url", "image_url": {"url": image_url}},
|
|
213
|
+
],
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
for _ in range(60):
|
|
217
|
+
time.sleep(5)
|
|
218
|
+
t = client.content_generation.tasks.get(task_id=task.id)
|
|
219
|
+
if t.status == "succeeded":
|
|
220
|
+
return {"success": True, "video_url": t.content.video_url}
|
|
221
|
+
elif t.status in ("failed", "canceled"):
|
|
222
|
+
return {"success": False, "error": f"任务 {t.status}"}
|
|
223
|
+
return {"success": False, "error": "超时"}
|
|
224
|
+
except Exception as e:
|
|
225
|
+
return {"success": False, "error": str(e)}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ========== 启动 ==========
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
_init_client()
|
|
232
|
+
import sys
|
|
233
|
+
if "--http" in sys.argv:
|
|
234
|
+
from flask import Flask, request, jsonify
|
|
235
|
+
app = Flask(__name__)
|
|
236
|
+
|
|
237
|
+
@app.route("/generate", methods=["POST"])
|
|
238
|
+
def api_generate():
|
|
239
|
+
data = request.get_json(silent=True) or {}
|
|
240
|
+
prompt = data.get("prompt", "")
|
|
241
|
+
size = data.get("size", "2560x1440")
|
|
242
|
+
if not prompt:
|
|
243
|
+
return jsonify({"error": "缺少 prompt"}), 400
|
|
244
|
+
import httpx
|
|
245
|
+
client = get_client()
|
|
246
|
+
try:
|
|
247
|
+
resp = client.images.generate(
|
|
248
|
+
model="doubao-seedream-4-5-251128", prompt=prompt,
|
|
249
|
+
size=size, response_format="url",
|
|
250
|
+
)
|
|
251
|
+
if resp.data and resp.data[0].url:
|
|
252
|
+
return jsonify({"url": resp.data[0].url})
|
|
253
|
+
return jsonify({"error": "未返回图片"}), 500
|
|
254
|
+
except Exception as e:
|
|
255
|
+
return jsonify({"error": str(e)}), 500
|
|
256
|
+
|
|
257
|
+
@app.route("/edit", methods=["POST"])
|
|
258
|
+
def api_edit():
|
|
259
|
+
data = request.get_json(silent=True) or {}
|
|
260
|
+
prompt = data.get("prompt", "")
|
|
261
|
+
image_url = data.get("image_url", "")
|
|
262
|
+
size = data.get("size", "2560x1440")
|
|
263
|
+
if not prompt or not image_url:
|
|
264
|
+
return jsonify({"error": "缺少 prompt 或 image_url"}), 400
|
|
265
|
+
client = get_client()
|
|
266
|
+
try:
|
|
267
|
+
resp = client.images.generate(
|
|
268
|
+
model="doubao-seedream-4-5-251128", prompt=prompt,
|
|
269
|
+
image=image_url, size=size, response_format="url",
|
|
270
|
+
)
|
|
271
|
+
if resp.data and resp.data[0].url:
|
|
272
|
+
return jsonify({"url": resp.data[0].url})
|
|
273
|
+
return jsonify({"error": "未返回图片"}), 500
|
|
274
|
+
except Exception as e:
|
|
275
|
+
return jsonify({"error": str(e)}), 500
|
|
276
|
+
|
|
277
|
+
import __main__ as main_mod
|
|
278
|
+
@app.route("/download")
|
|
279
|
+
def download_script():
|
|
280
|
+
return __import__("flask").send_file(main_mod.__file__, as_attachment=True, download_name="doubao_image_server.py")
|
|
281
|
+
|
|
282
|
+
@app.route("/")
|
|
283
|
+
def index():
|
|
284
|
+
return jsonify({"status": "ok", "endpoints": ["/generate", "/edit", "/download"]})
|
|
285
|
+
|
|
286
|
+
port = int(os.environ.get("PORT", 8000))
|
|
287
|
+
app.run(host="0.0.0.0", port=port, threaded=True)
|
|
288
|
+
elif "--sse" in sys.argv:
|
|
289
|
+
mcp.run(transport="sse")
|
|
290
|
+
else:
|
|
291
|
+
mcp.run(transport="stdio")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def cli():
|
|
295
|
+
"""pipx/pip 命令行入口"""
|
|
296
|
+
import sys
|
|
297
|
+
sys.argv = sys.argv
|
|
298
|
+
if "--http" in sys.argv:
|
|
299
|
+
_start_http()
|
|
300
|
+
elif "--sse" in sys.argv:
|
|
301
|
+
mcp.run(transport="sse")
|
|
302
|
+
else:
|
|
303
|
+
mcp.run(transport="stdio")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doubao-image-server
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 基于火山引擎豆包 Seedream 的图片/视频生成 MCP 服务器
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: mcp>=1.0.0
|
|
7
|
+
Requires-Dist: httpx
|
|
8
|
+
Requires-Dist: Pillow
|
|
9
|
+
Requires-Dist: openai
|
|
10
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
src/doubao_image_server/__init__.py
|
|
5
|
+
src/doubao_image_server/__main__.py
|
|
6
|
+
src/doubao_image_server.egg-info/PKG-INFO
|
|
7
|
+
src/doubao_image_server.egg-info/SOURCES.txt
|
|
8
|
+
src/doubao_image_server.egg-info/dependency_links.txt
|
|
9
|
+
src/doubao_image_server.egg-info/entry_points.txt
|
|
10
|
+
src/doubao_image_server.egg-info/requires.txt
|
|
11
|
+
src/doubao_image_server.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
doubao_image_server
|