sd-api-mcp 0.2.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.
- sd_api_mcp/__init__.py +3 -0
- sd_api_mcp/client.py +118 -0
- sd_api_mcp/server.py +287 -0
- sd_api_mcp-0.2.0.dist-info/METADATA +258 -0
- sd_api_mcp-0.2.0.dist-info/RECORD +7 -0
- sd_api_mcp-0.2.0.dist-info/WHEEL +4 -0
- sd_api_mcp-0.2.0.dist-info/entry_points.txt +2 -0
sd_api_mcp/__init__.py
ADDED
sd_api_mcp/client.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""SD WebUI API クライアント。"""
|
|
2
|
+
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SDAPIError(Exception):
|
|
9
|
+
"""SD WebUI API エラーの基底クラス。"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SDConnectionError(SDAPIError):
|
|
13
|
+
"""SD WebUI への接続失敗。"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SDTimeoutError(SDAPIError):
|
|
17
|
+
"""SD WebUI へのリクエストタイムアウト。"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@asynccontextmanager
|
|
21
|
+
async def _convert_httpx_errors():
|
|
22
|
+
"""httpx例外をドメイン例外に変換するコンテキストマネージャ。"""
|
|
23
|
+
try:
|
|
24
|
+
yield
|
|
25
|
+
except httpx.ConnectError as e:
|
|
26
|
+
raise SDConnectionError(str(e)) from e
|
|
27
|
+
except httpx.TimeoutException as e:
|
|
28
|
+
raise SDTimeoutError(str(e)) from e
|
|
29
|
+
except httpx.HTTPStatusError as e:
|
|
30
|
+
raise SDAPIError(str(e)) from e
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _with_controlnet(payload: dict, units: list[dict] | None) -> dict:
|
|
34
|
+
"""ControlNetユニットをペイロードに追加する(DRY)。"""
|
|
35
|
+
if not units:
|
|
36
|
+
return payload
|
|
37
|
+
return {**payload, "alwayson_scripts": {"controlnet": {"args": units}}}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _extract_images(data: dict, error_msg: str) -> list[str]:
|
|
41
|
+
"""レスポンスから画像リストを取り出す(DRY)。"""
|
|
42
|
+
images = data.get("images", [])
|
|
43
|
+
if not images:
|
|
44
|
+
raise SDAPIError(error_msg)
|
|
45
|
+
return images
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SDWebUIClient:
|
|
49
|
+
"""SD WebUI API への非同期HTTPクライアント。"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
base_url: str,
|
|
54
|
+
timeout: float,
|
|
55
|
+
auth: tuple[str, str] | None,
|
|
56
|
+
) -> None:
|
|
57
|
+
auth_param = httpx.BasicAuth(*auth) if auth else None
|
|
58
|
+
self._client = httpx.AsyncClient(
|
|
59
|
+
base_url=base_url,
|
|
60
|
+
timeout=timeout,
|
|
61
|
+
auth=auth_param,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
async def _get(self, path: str) -> list | dict:
|
|
65
|
+
async with _convert_httpx_errors():
|
|
66
|
+
resp = await self._client.get(path)
|
|
67
|
+
resp.raise_for_status()
|
|
68
|
+
return resp.json()
|
|
69
|
+
|
|
70
|
+
async def _post(self, path: str, payload: dict) -> list | dict:
|
|
71
|
+
async with _convert_httpx_errors():
|
|
72
|
+
resp = await self._client.post(path, json=payload)
|
|
73
|
+
resp.raise_for_status()
|
|
74
|
+
return resp.json()
|
|
75
|
+
|
|
76
|
+
async def get_sd_models(self) -> list[str]:
|
|
77
|
+
data = await self._get("/sdapi/v1/sd-models")
|
|
78
|
+
return [m["title"] for m in data] # type: ignore[index]
|
|
79
|
+
|
|
80
|
+
async def set_sd_model(self, model_name: str) -> None:
|
|
81
|
+
await self._post("/sdapi/v1/options", {"sd_model_checkpoint": model_name})
|
|
82
|
+
|
|
83
|
+
async def get_upscalers(self) -> list[str]:
|
|
84
|
+
data = await self._get("/sdapi/v1/upscalers")
|
|
85
|
+
return [u["name"] for u in data] # type: ignore[index]
|
|
86
|
+
|
|
87
|
+
async def get_controlnet_models(self) -> list[str]:
|
|
88
|
+
data = await self._get("/controlnet/model_list")
|
|
89
|
+
assert isinstance(data, dict)
|
|
90
|
+
return data["model_list"]
|
|
91
|
+
|
|
92
|
+
async def get_controlnet_modules(self) -> list[str]:
|
|
93
|
+
data = await self._get("/controlnet/module_list")
|
|
94
|
+
assert isinstance(data, dict)
|
|
95
|
+
return data["module_list"]
|
|
96
|
+
|
|
97
|
+
async def txt2img(
|
|
98
|
+
self,
|
|
99
|
+
payload: dict,
|
|
100
|
+
controlnet_units: list[dict] | None = None,
|
|
101
|
+
) -> list[str]:
|
|
102
|
+
data = await self._post("/sdapi/v1/txt2img", _with_controlnet(payload, controlnet_units))
|
|
103
|
+
assert isinstance(data, dict)
|
|
104
|
+
return _extract_images(data, "画像が生成されませんでした")
|
|
105
|
+
|
|
106
|
+
async def img2img(
|
|
107
|
+
self,
|
|
108
|
+
payload: dict,
|
|
109
|
+
controlnet_units: list[dict] | None = None,
|
|
110
|
+
) -> list[str]:
|
|
111
|
+
data = await self._post("/sdapi/v1/img2img", _with_controlnet(payload, controlnet_units))
|
|
112
|
+
assert isinstance(data, dict)
|
|
113
|
+
return _extract_images(data, "画像が生成されませんでした")
|
|
114
|
+
|
|
115
|
+
async def upscale_images(self, payload: dict) -> list[str]:
|
|
116
|
+
data = await self._post("/sdapi/v1/extra-batch-images", payload)
|
|
117
|
+
assert isinstance(data, dict)
|
|
118
|
+
return _extract_images(data, "アップスケール結果がありませんでした")
|
sd_api_mcp/server.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""MCP server for Stable Diffusion WebUI API."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
5
|
+
import uuid
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
from fastmcp.exceptions import ToolError
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from sd_api_mcp.client import SDAPIError, SDConnectionError, SDTimeoutError, SDWebUIClient
|
|
15
|
+
|
|
16
|
+
_client: SDWebUIClient | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def lifespan(server: FastMCP):
|
|
21
|
+
global _client
|
|
22
|
+
base_url = os.environ.get("SD_WEBUI_URL", "http://host.docker.internal:7860")
|
|
23
|
+
timeout = float(os.environ.get("REQUEST_TIMEOUT", "300"))
|
|
24
|
+
user = os.environ.get("SD_AUTH_USER")
|
|
25
|
+
password = os.environ.get("SD_AUTH_PASS")
|
|
26
|
+
auth = (user, password) if user and password else None
|
|
27
|
+
_client = SDWebUIClient(base_url=base_url, timeout=timeout, auth=auth)
|
|
28
|
+
yield
|
|
29
|
+
_client = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
mcp = FastMCP(name="sd-api-mcp", lifespan=lifespan)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ControlNetUnit(BaseModel):
|
|
36
|
+
image: str
|
|
37
|
+
model: str
|
|
38
|
+
module: str = "none"
|
|
39
|
+
weight: float = 1.0
|
|
40
|
+
guidance_start: float = 0.0
|
|
41
|
+
guidance_end: float = 1.0
|
|
42
|
+
control_mode: int = 0
|
|
43
|
+
resize_mode: int = 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _save_image(b64: str, output_dir: str) -> str:
|
|
47
|
+
"""base64画像をPNGとして保存し、絶対パスを返す(DRY)。"""
|
|
48
|
+
out = Path(output_dir)
|
|
49
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
path = out / f"sd_{uuid.uuid4().hex}.png"
|
|
51
|
+
path.write_bytes(base64.b64decode(b64))
|
|
52
|
+
return str(path.resolve())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _to_tool_error(e: SDAPIError) -> ToolError:
|
|
56
|
+
if isinstance(e, SDConnectionError):
|
|
57
|
+
return ToolError(f"SD WebUI に接続できません: {e}")
|
|
58
|
+
if isinstance(e, SDTimeoutError):
|
|
59
|
+
return ToolError(f"リクエストがタイムアウトしました: {e}")
|
|
60
|
+
return ToolError(f"API エラー: {e}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read_image_b64(path: str) -> str:
|
|
64
|
+
"""画像ファイルをbase64文字列に変換する(DRY)。"""
|
|
65
|
+
src = Path(path)
|
|
66
|
+
if not src.exists():
|
|
67
|
+
raise ToolError(f"画像ファイルが見つかりません: {path}")
|
|
68
|
+
return base64.b64encode(src.read_bytes()).decode()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _build_controlnet_units(units: list[ControlNetUnit] | None) -> list[dict] | None:
|
|
72
|
+
"""ControlNetUnitリストをdictリストに変換する(DRY)。"""
|
|
73
|
+
if not units:
|
|
74
|
+
return None
|
|
75
|
+
return [{**u.model_dump(exclude={"image"}), "image": _read_image_b64(u.image)} for u in units]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mcp.tool()
|
|
79
|
+
async def txt2img(
|
|
80
|
+
prompt: Annotated[str, Field(description="生成プロンプト")],
|
|
81
|
+
negative_prompt: Annotated[str, Field(description="ネガティブプロンプト")] = "",
|
|
82
|
+
steps: Annotated[int, Field(description="サンプリングステップ数", ge=1, le=150)] = 20,
|
|
83
|
+
width: Annotated[int, Field(description="画像の幅", ge=64, le=2048)] = 512,
|
|
84
|
+
height: Annotated[int, Field(description="画像の高さ", ge=64, le=2048)] = 512,
|
|
85
|
+
cfg_scale: Annotated[float, Field(description="CFGスケール", ge=1.0, le=30.0)] = 7.0,
|
|
86
|
+
sampler_name: Annotated[str, Field(description="サンプラー名")] = "Euler a",
|
|
87
|
+
scheduler_name: Annotated[str, Field(description="スケジューラー名")] = "Automatic",
|
|
88
|
+
seed: Annotated[int, Field(description="シード値(-1でランダム)", ge=-1)] = -1,
|
|
89
|
+
batch_size: Annotated[int, Field(description="生成枚数", ge=1, le=4)] = 1,
|
|
90
|
+
restore_faces: Annotated[bool, Field(description="顔修復を有効にする")] = False,
|
|
91
|
+
tiling: Annotated[bool, Field(description="タイリング画像を生成する")] = False,
|
|
92
|
+
distilled_cfg_scale: Annotated[
|
|
93
|
+
float, Field(description="Distilled CFGスケール", ge=1.0, le=30.0)
|
|
94
|
+
] = 3.5,
|
|
95
|
+
controlnet_units: Annotated[
|
|
96
|
+
list[ControlNetUnit] | None, Field(description="ControlNetユニットのリスト")
|
|
97
|
+
] = None,
|
|
98
|
+
output_dir: Annotated[str, Field(description="出力ディレクトリ")] = "./output",
|
|
99
|
+
) -> list[str]:
|
|
100
|
+
"""テキストプロンプトから画像を生成してファイルに保存する。
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
保存された画像ファイルの絶対パスリスト
|
|
104
|
+
"""
|
|
105
|
+
assert _client is not None
|
|
106
|
+
payload = {
|
|
107
|
+
"prompt": prompt,
|
|
108
|
+
"negative_prompt": negative_prompt,
|
|
109
|
+
"steps": steps,
|
|
110
|
+
"width": width,
|
|
111
|
+
"height": height,
|
|
112
|
+
"cfg_scale": cfg_scale,
|
|
113
|
+
"sampler_name": sampler_name,
|
|
114
|
+
"scheduler": scheduler_name,
|
|
115
|
+
"seed": seed,
|
|
116
|
+
"n_iter": batch_size,
|
|
117
|
+
"restore_faces": restore_faces,
|
|
118
|
+
"tiling": tiling,
|
|
119
|
+
"distilled_cfg_scale": distilled_cfg_scale,
|
|
120
|
+
}
|
|
121
|
+
try:
|
|
122
|
+
cn_units = _build_controlnet_units(controlnet_units)
|
|
123
|
+
images = await _client.txt2img(payload, controlnet_units=cn_units)
|
|
124
|
+
except SDAPIError as e:
|
|
125
|
+
raise _to_tool_error(e) from e
|
|
126
|
+
return [_save_image(b64, output_dir) for b64 in images]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@mcp.tool()
|
|
130
|
+
async def img2img(
|
|
131
|
+
init_image: Annotated[str, Field(description="入力画像のファイルパス")],
|
|
132
|
+
prompt: Annotated[str, Field(description="生成プロンプト")],
|
|
133
|
+
negative_prompt: Annotated[str, Field(description="ネガティブプロンプト")] = "",
|
|
134
|
+
denoising_strength: Annotated[
|
|
135
|
+
float, Field(description="デノイズ強度(0.0〜1.0)", ge=0.0, le=1.0)
|
|
136
|
+
] = 0.75,
|
|
137
|
+
steps: Annotated[int, Field(description="サンプリングステップ数", ge=1, le=150)] = 20,
|
|
138
|
+
width: Annotated[int, Field(description="画像の幅", ge=64, le=2048)] = 512,
|
|
139
|
+
height: Annotated[int, Field(description="画像の高さ", ge=64, le=2048)] = 512,
|
|
140
|
+
cfg_scale: Annotated[float, Field(description="CFGスケール", ge=1.0, le=30.0)] = 7.0,
|
|
141
|
+
sampler_name: Annotated[str, Field(description="サンプラー名")] = "Euler a",
|
|
142
|
+
scheduler_name: Annotated[str, Field(description="スケジューラー名")] = "Automatic",
|
|
143
|
+
seed: Annotated[int, Field(description="シード値(-1でランダム)", ge=-1)] = -1,
|
|
144
|
+
batch_size: Annotated[int, Field(description="生成枚数", ge=1, le=4)] = 1,
|
|
145
|
+
restore_faces: Annotated[bool, Field(description="顔修復を有効にする")] = False,
|
|
146
|
+
tiling: Annotated[bool, Field(description="タイリング画像を生成する")] = False,
|
|
147
|
+
controlnet_units: Annotated[
|
|
148
|
+
list[ControlNetUnit] | None, Field(description="ControlNetユニットのリスト")
|
|
149
|
+
] = None,
|
|
150
|
+
output_dir: Annotated[str, Field(description="出力ディレクトリ")] = "./output",
|
|
151
|
+
) -> list[str]:
|
|
152
|
+
"""入力画像とプロンプトから画像を生成してファイルに保存する。
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
保存された画像ファイルの絶対パスリスト
|
|
156
|
+
"""
|
|
157
|
+
assert _client is not None
|
|
158
|
+
init_b64 = _read_image_b64(init_image)
|
|
159
|
+
payload = {
|
|
160
|
+
"init_images": [init_b64],
|
|
161
|
+
"prompt": prompt,
|
|
162
|
+
"negative_prompt": negative_prompt,
|
|
163
|
+
"denoising_strength": denoising_strength,
|
|
164
|
+
"steps": steps,
|
|
165
|
+
"width": width,
|
|
166
|
+
"height": height,
|
|
167
|
+
"cfg_scale": cfg_scale,
|
|
168
|
+
"sampler_name": sampler_name,
|
|
169
|
+
"scheduler": scheduler_name,
|
|
170
|
+
"seed": seed,
|
|
171
|
+
"n_iter": batch_size,
|
|
172
|
+
"restore_faces": restore_faces,
|
|
173
|
+
"tiling": tiling,
|
|
174
|
+
}
|
|
175
|
+
try:
|
|
176
|
+
cn_units = _build_controlnet_units(controlnet_units)
|
|
177
|
+
images = await _client.img2img(payload, controlnet_units=cn_units)
|
|
178
|
+
except SDAPIError as e:
|
|
179
|
+
raise _to_tool_error(e) from e
|
|
180
|
+
return [_save_image(b64, output_dir) for b64 in images]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@mcp.tool()
|
|
184
|
+
async def get_sd_models() -> list[str]:
|
|
185
|
+
"""利用可能なStable Diffusionモデルの一覧を返す。"""
|
|
186
|
+
assert _client is not None
|
|
187
|
+
try:
|
|
188
|
+
return await _client.get_sd_models()
|
|
189
|
+
except SDAPIError as e:
|
|
190
|
+
raise _to_tool_error(e) from e
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@mcp.tool()
|
|
194
|
+
async def set_sd_model(
|
|
195
|
+
model_name: Annotated[str, Field(description="設定するモデル名")],
|
|
196
|
+
) -> str:
|
|
197
|
+
"""アクティブなStable Diffusionモデルを変更する。"""
|
|
198
|
+
assert _client is not None
|
|
199
|
+
try:
|
|
200
|
+
await _client.set_sd_model(model_name)
|
|
201
|
+
except SDAPIError as e:
|
|
202
|
+
raise _to_tool_error(e) from e
|
|
203
|
+
return f"モデルを設定しました: {model_name}"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@mcp.tool()
|
|
207
|
+
async def get_sd_upscalers() -> list[str]:
|
|
208
|
+
"""利用可能なアップスケーラーの一覧を返す。"""
|
|
209
|
+
assert _client is not None
|
|
210
|
+
try:
|
|
211
|
+
return await _client.get_upscalers()
|
|
212
|
+
except SDAPIError as e:
|
|
213
|
+
raise _to_tool_error(e) from e
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@mcp.tool()
|
|
217
|
+
async def upscale_images(
|
|
218
|
+
images: Annotated[list[str], Field(description="アップスケールする画像ファイルパスのリスト")],
|
|
219
|
+
resize_mode: Annotated[int, Field(description="0=倍率指定, 1=サイズ指定", ge=0, le=1)] = 0,
|
|
220
|
+
upscaling_resize: Annotated[
|
|
221
|
+
float, Field(description="アップスケール倍率(resize_mode=0時)", ge=1.0)
|
|
222
|
+
] = 4.0,
|
|
223
|
+
upscaling_resize_w: Annotated[int, Field(description="目標幅px(resize_mode=1時)", ge=64)] = 512,
|
|
224
|
+
upscaling_resize_h: Annotated[
|
|
225
|
+
int, Field(description="目標高さpx(resize_mode=1時)", ge=64)
|
|
226
|
+
] = 512,
|
|
227
|
+
upscaler_1: Annotated[str, Field(description="プライマリアップスケーラー")] = "R-ESRGAN 4x+",
|
|
228
|
+
upscaler_2: Annotated[str, Field(description="セカンダリアップスケーラー")] = "None",
|
|
229
|
+
output_dir: Annotated[str, Field(description="出力ディレクトリ")] = "./output",
|
|
230
|
+
) -> list[str]:
|
|
231
|
+
"""画像をアップスケールしてファイルに保存する。
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
保存された画像ファイルの絶対パスリスト
|
|
235
|
+
"""
|
|
236
|
+
assert _client is not None
|
|
237
|
+
image_list = [{"data": _read_image_b64(p), "name": Path(p).name} for p in images]
|
|
238
|
+
payload = {
|
|
239
|
+
"resize_mode": resize_mode,
|
|
240
|
+
"show_extras_results": True,
|
|
241
|
+
"gfpgan_visibility": 0,
|
|
242
|
+
"codeformer_visibility": 0,
|
|
243
|
+
"codeformer_weight": 0,
|
|
244
|
+
"upscaling_resize": upscaling_resize,
|
|
245
|
+
"upscaling_resize_w": upscaling_resize_w,
|
|
246
|
+
"upscaling_resize_h": upscaling_resize_h,
|
|
247
|
+
"upscaling_crop": True,
|
|
248
|
+
"upscaler_1": upscaler_1,
|
|
249
|
+
"upscaler_2": upscaler_2,
|
|
250
|
+
"extras_upscaler_2_visibility": 0,
|
|
251
|
+
"upscale_first": False,
|
|
252
|
+
"imageList": image_list,
|
|
253
|
+
}
|
|
254
|
+
try:
|
|
255
|
+
result_images = await _client.upscale_images(payload)
|
|
256
|
+
except SDAPIError as e:
|
|
257
|
+
raise _to_tool_error(e) from e
|
|
258
|
+
return [_save_image(b64, output_dir) for b64 in result_images]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@mcp.tool()
|
|
262
|
+
async def get_controlnet_models() -> list[str]:
|
|
263
|
+
"""利用可能なControlNetモデルの一覧を返す。"""
|
|
264
|
+
assert _client is not None
|
|
265
|
+
try:
|
|
266
|
+
return await _client.get_controlnet_models()
|
|
267
|
+
except SDAPIError as e:
|
|
268
|
+
raise _to_tool_error(e) from e
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@mcp.tool()
|
|
272
|
+
async def get_controlnet_modules() -> list[str]:
|
|
273
|
+
"""利用可能なControlNetプリプロセッサの一覧を返す。"""
|
|
274
|
+
assert _client is not None
|
|
275
|
+
try:
|
|
276
|
+
return await _client.get_controlnet_modules()
|
|
277
|
+
except SDAPIError as e:
|
|
278
|
+
raise _to_tool_error(e) from e
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def main() -> None:
|
|
282
|
+
"""Start the MCP server."""
|
|
283
|
+
mcp.run()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
main()
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sd-api-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MCP server wrapping the Stable Diffusion WebUI API
|
|
5
|
+
Project-URL: Homepage, https://github.com/HizZaniya/sd-api-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/HizZaniya/sd-api-mcp
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/HizZaniya/sd-api-mcp/issues
|
|
8
|
+
Author: HizZaniya
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: image-generation,mcp,stable-diffusion,webui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Requires-Dist: fastmcp>=3.3.1
|
|
19
|
+
Requires-Dist: httpx>=0.28.0
|
|
20
|
+
Requires-Dist: pillow>=11.0.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# SD API MCP
|
|
24
|
+
|
|
25
|
+
> Stable Diffusion WebUI API (AUTOMATIC1111/ForgeUI) を操作する MCP サーバ
|
|
26
|
+
|
|
27
|
+
## 前提条件
|
|
28
|
+
|
|
29
|
+
- [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)(または ForgeUI)が起動していること
|
|
30
|
+
- [uv](https://docs.astral.sh/uv/) がインストール済みであること
|
|
31
|
+
|
|
32
|
+
## MCPクライアント設定
|
|
33
|
+
|
|
34
|
+
### Claude Code
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude mcp add sd-api-mcp uvx sd-api-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
または `.claude/settings.json` に追加:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"sd-api-mcp": {
|
|
46
|
+
"command": "uvx",
|
|
47
|
+
"args": ["sd-api-mcp"],
|
|
48
|
+
"env": {
|
|
49
|
+
"SD_WEBUI_URL": "http://localhost:7860"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Cursor
|
|
57
|
+
|
|
58
|
+
`.cursor/mcp.json` に追加:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"sd-api-mcp": {
|
|
64
|
+
"command": "uvx",
|
|
65
|
+
"args": ["sd-api-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"SD_WEBUI_URL": "http://localhost:7860"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### VS Code (GitHub Copilot)
|
|
75
|
+
|
|
76
|
+
`.vscode/mcp.json` に追加:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"servers": {
|
|
81
|
+
"sd-api-mcp": {
|
|
82
|
+
"command": "uvx",
|
|
83
|
+
"args": ["sd-api-mcp"],
|
|
84
|
+
"env": {
|
|
85
|
+
"SD_WEBUI_URL": "http://localhost:7860"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 環境変数
|
|
93
|
+
|
|
94
|
+
| 変数名 | デフォルト | 説明 |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `SD_WEBUI_URL` | `http://host.docker.internal:7860` | SD WebUI のベース URL |
|
|
97
|
+
| `SD_AUTH_USER` | (なし) | Basic 認証ユーザー名(任意) |
|
|
98
|
+
| `SD_AUTH_PASS` | (なし) | Basic 認証パスワード(任意) |
|
|
99
|
+
| `SD_OUTPUT_DIR` | `./output` | 生成画像の保存先ディレクトリ |
|
|
100
|
+
| `REQUEST_TIMEOUT` | `300` | HTTP リクエストのタイムアウト秒数 |
|
|
101
|
+
|
|
102
|
+
## ツールリファレンス
|
|
103
|
+
|
|
104
|
+
### `txt2img`
|
|
105
|
+
|
|
106
|
+
テキストプロンプトから画像を生成し、ファイルに保存します。
|
|
107
|
+
|
|
108
|
+
| パラメータ | 型 | デフォルト | 説明 |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `prompt` | str | 必須 | 生成プロンプト |
|
|
111
|
+
| `negative_prompt` | str | `""` | ネガティブプロンプト |
|
|
112
|
+
| `steps` | int | `20` | サンプリングステップ数(1〜150) |
|
|
113
|
+
| `width` | int | `512` | 画像の幅 px(64〜2048) |
|
|
114
|
+
| `height` | int | `512` | 画像の高さ px(64〜2048) |
|
|
115
|
+
| `cfg_scale` | float | `7.0` | CFG スケール(1.0〜30.0) |
|
|
116
|
+
| `sampler_name` | str | `"Euler a"` | サンプラー名 |
|
|
117
|
+
| `scheduler_name` | str | `"Automatic"` | スケジューラー名 |
|
|
118
|
+
| `seed` | int | `-1` | シード値(-1 でランダム) |
|
|
119
|
+
| `batch_size` | int | `1` | 生成枚数(1〜4) |
|
|
120
|
+
| `restore_faces` | bool | `false` | 顔修復を有効にする |
|
|
121
|
+
| `tiling` | bool | `false` | タイリング画像を生成する |
|
|
122
|
+
| `distilled_cfg_scale` | float | `3.5` | Distilled CFG スケール(1.0〜30.0) |
|
|
123
|
+
| `controlnet_units` | list | `null` | ControlNet ユニットのリスト |
|
|
124
|
+
| `output_dir` | str | `"./output"` | 出力ディレクトリ |
|
|
125
|
+
|
|
126
|
+
**戻り値**: 生成された画像ファイルの絶対パスリスト
|
|
127
|
+
|
|
128
|
+
### `img2img`
|
|
129
|
+
|
|
130
|
+
入力画像とプロンプトから画像を生成し、ファイルに保存します。
|
|
131
|
+
|
|
132
|
+
| パラメータ | 型 | デフォルト | 説明 |
|
|
133
|
+
|---|---|---|---|
|
|
134
|
+
| `init_image` | str | 必須 | 入力画像のファイルパス |
|
|
135
|
+
| `prompt` | str | 必須 | 生成プロンプト |
|
|
136
|
+
| `negative_prompt` | str | `""` | ネガティブプロンプト |
|
|
137
|
+
| `denoising_strength` | float | `0.75` | デノイズ強度(0.0〜1.0) |
|
|
138
|
+
| `steps` | int | `20` | サンプリングステップ数(1〜150) |
|
|
139
|
+
| `width` | int | `512` | 画像の幅 px(64〜2048) |
|
|
140
|
+
| `height` | int | `512` | 画像の高さ px(64〜2048) |
|
|
141
|
+
| `cfg_scale` | float | `7.0` | CFG スケール(1.0〜30.0) |
|
|
142
|
+
| `sampler_name` | str | `"Euler a"` | サンプラー名 |
|
|
143
|
+
| `scheduler_name` | str | `"Automatic"` | スケジューラー名 |
|
|
144
|
+
| `seed` | int | `-1` | シード値(-1 でランダム) |
|
|
145
|
+
| `batch_size` | int | `1` | 生成枚数(1〜4) |
|
|
146
|
+
| `restore_faces` | bool | `false` | 顔修復を有効にする |
|
|
147
|
+
| `tiling` | bool | `false` | タイリング画像を生成する |
|
|
148
|
+
| `controlnet_units` | list | `null` | ControlNet ユニットのリスト |
|
|
149
|
+
| `output_dir` | str | `"./output"` | 出力ディレクトリ |
|
|
150
|
+
|
|
151
|
+
**戻り値**: 生成された画像ファイルの絶対パスリスト
|
|
152
|
+
|
|
153
|
+
### `get_sd_models`
|
|
154
|
+
|
|
155
|
+
利用可能な Stable Diffusion モデルの一覧を返します。
|
|
156
|
+
|
|
157
|
+
パラメータなし。**戻り値**: モデル名の文字列リスト
|
|
158
|
+
|
|
159
|
+
### `set_sd_model`
|
|
160
|
+
|
|
161
|
+
アクティブな Stable Diffusion モデルを変更します。
|
|
162
|
+
|
|
163
|
+
| パラメータ | 型 | デフォルト | 説明 |
|
|
164
|
+
|---|---|---|---|
|
|
165
|
+
| `model_name` | str | 必須 | 設定するモデル名 |
|
|
166
|
+
|
|
167
|
+
**戻り値**: 設定完了メッセージ(文字列)
|
|
168
|
+
|
|
169
|
+
### `get_sd_upscalers`
|
|
170
|
+
|
|
171
|
+
利用可能なアップスケーラーの一覧を返します。
|
|
172
|
+
|
|
173
|
+
パラメータなし。**戻り値**: アップスケーラー名の文字列リスト
|
|
174
|
+
|
|
175
|
+
### `upscale_images`
|
|
176
|
+
|
|
177
|
+
画像をアップスケールして保存します。
|
|
178
|
+
|
|
179
|
+
| パラメータ | 型 | デフォルト | 説明 |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| `images` | list[str] | 必須 | アップスケールする画像ファイルパスのリスト |
|
|
182
|
+
| `resize_mode` | int | `0` | 0=倍率指定, 1=サイズ指定 |
|
|
183
|
+
| `upscaling_resize` | float | `4.0` | アップスケール倍率(`resize_mode=0` 時) |
|
|
184
|
+
| `upscaling_resize_w` | int | `512` | 目標幅 px(`resize_mode=1` 時) |
|
|
185
|
+
| `upscaling_resize_h` | int | `512` | 目標高さ px(`resize_mode=1` 時) |
|
|
186
|
+
| `upscaler_1` | str | `"R-ESRGAN 4x+"` | プライマリアップスケーラー |
|
|
187
|
+
| `upscaler_2` | str | `"None"` | セカンダリアップスケーラー |
|
|
188
|
+
| `output_dir` | str | `"./output"` | 出力ディレクトリ |
|
|
189
|
+
|
|
190
|
+
**戻り値**: 保存された画像ファイルの絶対パスリスト
|
|
191
|
+
|
|
192
|
+
### `get_controlnet_models`
|
|
193
|
+
|
|
194
|
+
利用可能な ControlNet モデルの一覧を返します。
|
|
195
|
+
|
|
196
|
+
パラメータなし。**戻り値**: ControlNet モデル名の文字列リスト
|
|
197
|
+
|
|
198
|
+
### `get_controlnet_modules`
|
|
199
|
+
|
|
200
|
+
利用可能な ControlNet プリプロセッサの一覧を返します。
|
|
201
|
+
|
|
202
|
+
パラメータなし。**戻り値**: ControlNet プリプロセッサ名の文字列リスト
|
|
203
|
+
|
|
204
|
+
## 開発者向け
|
|
205
|
+
|
|
206
|
+
### 環境構築
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone https://github.com/HizZaniya/sd-api-mcp.git
|
|
210
|
+
cd sd-api-mcp
|
|
211
|
+
uv sync --group dev
|
|
212
|
+
pre-commit install
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### テスト実行
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv run pytest -v
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
型チェック・Lint:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
uv run ty check
|
|
225
|
+
uv run ruff check .
|
|
226
|
+
uv run ruff format --check .
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### ファイル構成
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
src/sd_api_mcp/
|
|
233
|
+
├── server.py # MCP プロトコル層 — ツール定義・リクエスト受付
|
|
234
|
+
└── client.py # SD WebUI API 通信層 — httpx による HTTP クライアント
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- `server.py`: FastMCP のツールデコレータでツールを定義し、MCP プロトコルを処理する。SD WebUI への直接アクセスは行わない。
|
|
238
|
+
- `client.py`: httpx を使って SD WebUI の REST API と通信する。MCP プロトコルを知らない。
|
|
239
|
+
|
|
240
|
+
### リリース手順
|
|
241
|
+
|
|
242
|
+
1. Conventional Commits 形式でコミットを積む(例: `feat: add new tool`, `fix: handle timeout`)
|
|
243
|
+
2. GitHub Actions の **Release** ワークフローを手動トリガーする
|
|
244
|
+
3. `cz bump` がコミット履歴からバージョンを自動決定し、`CHANGELOG.md` を更新してタグを作成
|
|
245
|
+
4. **Publish** ワークフローが自動実行され、TestPyPI で検証後 PyPI に公開される
|
|
246
|
+
|
|
247
|
+
### コントリビューション
|
|
248
|
+
|
|
249
|
+
1. `main` ブランチから feature ブランチを作成する
|
|
250
|
+
2. テストを先に書いてから実装する(TDD)
|
|
251
|
+
3. PR を作成する(テンプレートに従うこと)
|
|
252
|
+
4. CI(lint / format / typecheck / test)がすべてパスしたらレビューを依頼する
|
|
253
|
+
|
|
254
|
+
コミットメッセージは [Conventional Commits](https://www.conventionalcommits.org/) 形式を推奨します。
|
|
255
|
+
|
|
256
|
+
## ライセンス
|
|
257
|
+
|
|
258
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
sd_api_mcp/__init__.py,sha256=aQ6MNW0I99c_o3rwCvBYIW1Ad4TzTeTzESEdehAbEug,72
|
|
2
|
+
sd_api_mcp/client.py,sha256=9ie7eW90Wmi0fe0OIk0qW_qDhh37Nfec1NtMZRm4caU,3990
|
|
3
|
+
sd_api_mcp/server.py,sha256=O2iB_pespIElwL0DpiJ8kqf-rkOTW2ZBA3LXV3x8TvI,11071
|
|
4
|
+
sd_api_mcp-0.2.0.dist-info/METADATA,sha256=mKrCZ8s_c-TJc8abPKomhQ80S6FVV-SZxkZSs-LJEf8,8640
|
|
5
|
+
sd_api_mcp-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
6
|
+
sd_api_mcp-0.2.0.dist-info/entry_points.txt,sha256=tNjlh_UWzrz_UB_weHy3kcKIWZeqaFOpjA48JUp-9BI,54
|
|
7
|
+
sd_api_mcp-0.2.0.dist-info/RECORD,,
|