Windmill MCP サーバーを SSE 化してサーバーデプロイに対応
- windmill_mcp.py: MCP_TRANSPORT 環境変数で stdio/sse を切り替え可能に - mcp/Dockerfile: Python 3.12-slim ベースのコンテナイメージを追加 - docker-compose.yml: windmill_mcp サービスを追加(Traefik 経由で windmill-mcp.keinafarm.net に公開) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,37 @@ services:
|
|||||||
- "traefik.http.routers.windmill-debug.tls=true"
|
- "traefik.http.routers.windmill-debug.tls=true"
|
||||||
- "traefik.http.services.windmill-debug.loadbalancer.server.port=3003"
|
- "traefik.http.services.windmill-debug.loadbalancer.server.port=3003"
|
||||||
|
|
||||||
|
windmill_mcp:
|
||||||
|
build:
|
||||||
|
context: ./mcp
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: windmill_mcp
|
||||||
|
restart: unless-stopped
|
||||||
|
expose:
|
||||||
|
- 8001
|
||||||
|
environment:
|
||||||
|
- WINDMILL_TOKEN=${WINDMILL_TOKEN}
|
||||||
|
- WINDMILL_URL=https://windmill.keinafarm.net
|
||||||
|
- WINDMILL_WORKSPACE=admins
|
||||||
|
- MCP_TRANSPORT=sse
|
||||||
|
- MCP_HOST=0.0.0.0
|
||||||
|
- MCP_PORT=8001
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
# HTTPS ルーター
|
||||||
|
- "traefik.http.routers.windmill-mcp.rule=Host(`windmill-mcp.keinafarm.net`)"
|
||||||
|
- "traefik.http.routers.windmill-mcp.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.windmill-mcp.tls=true"
|
||||||
|
- "traefik.http.routers.windmill-mcp.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.windmill-mcp.loadbalancer.server.port=8001"
|
||||||
|
# HTTP → HTTPS リダイレクト
|
||||||
|
- "traefik.http.routers.windmill-mcp-http.rule=Host(`windmill-mcp.keinafarm.net`)"
|
||||||
|
- "traefik.http.routers.windmill-mcp-http.entrypoints=web"
|
||||||
|
- "traefik.http.routers.windmill-mcp-http.middlewares=windmill-https-redirect"
|
||||||
|
networks:
|
||||||
|
- traefik-net
|
||||||
|
logging: *default-logging
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data: null
|
db_data: null
|
||||||
worker_dependency_cache: null
|
worker_dependency_cache: null
|
||||||
|
|||||||
14
mcp/Dockerfile
Normal file
14
mcp/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY windmill_mcp.py .
|
||||||
|
|
||||||
|
ENV MCP_TRANSPORT=sse
|
||||||
|
ENV MCP_HOST=0.0.0.0
|
||||||
|
ENV MCP_PORT=8001
|
||||||
|
|
||||||
|
CMD ["python", "windmill_mcp.py"]
|
||||||
@@ -181,6 +181,115 @@ def windmill_get_job_logs(job_id: str) -> str:
|
|||||||
return "\n".join(result_parts)
|
return "\n".join(result_parts)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def windmill_create_flow(path: str, summary: str, flow_definition: str, description: str = "") -> str:
|
||||||
|
"""新しいフローを作成する
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: フローのパス (例: u/admin/my_flow)
|
||||||
|
summary: フローの概要
|
||||||
|
flow_definition: フローの定義 (JSON形式の文字列)
|
||||||
|
description: フローの詳細説明 (省略可)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
flow_value = json.loads(flow_definition)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return f"Error: flow_definitionのJSON形式が不正です: {e}"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"path": path,
|
||||||
|
"summary": summary,
|
||||||
|
"description": description,
|
||||||
|
"value": flow_value,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = httpx.post(
|
||||||
|
_api("flows/create"),
|
||||||
|
headers=_headers(),
|
||||||
|
json=payload,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return (
|
||||||
|
f"フローを作成しました。\n"
|
||||||
|
f"パス: {path}\n"
|
||||||
|
f"URL: {WINDMILL_URL}/flows/edit/{path}?workspace={WINDMILL_WORKSPACE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def windmill_update_flow(path: str, summary: str, flow_definition: str, description: str = "") -> str:
|
||||||
|
"""既存のフローを更新する
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: フローのパス (例: u/admin/my_flow)
|
||||||
|
summary: フローの概要
|
||||||
|
flow_definition: フローの定義 (JSON形式の文字列)
|
||||||
|
description: フローの詳細説明 (省略可)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
flow_value = json.loads(flow_definition)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return f"Error: flow_definitionのJSON形式が不正です: {e}"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"path": path,
|
||||||
|
"summary": summary,
|
||||||
|
"description": description,
|
||||||
|
"value": flow_value,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = httpx.post(
|
||||||
|
_api(f"flows/edit/{path}"),
|
||||||
|
headers=_headers(),
|
||||||
|
json=payload,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return (
|
||||||
|
f"フローを更新しました。\n"
|
||||||
|
f"パス: {path}\n"
|
||||||
|
f"URL: {WINDMILL_URL}/flows/edit/{path}?workspace={WINDMILL_WORKSPACE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def windmill_create_script(
|
||||||
|
path: str, language: str, content: str, summary: str = "", description: str = ""
|
||||||
|
) -> str:
|
||||||
|
"""新しいスクリプトを作成する(既存パスの場合は新バージョンを登録する)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: スクリプトのパス (例: u/admin/my_script)
|
||||||
|
language: 言語 (python3, deno, bun, bash など)
|
||||||
|
content: スクリプトのソースコード
|
||||||
|
summary: スクリプトの概要 (省略可)
|
||||||
|
description: スクリプトの詳細説明 (省略可)
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
"path": path,
|
||||||
|
"language": language,
|
||||||
|
"content": content,
|
||||||
|
"summary": summary,
|
||||||
|
"description": description,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = httpx.post(
|
||||||
|
_api("scripts/create"),
|
||||||
|
headers=_headers(),
|
||||||
|
json=payload,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
hash_val = resp.text.strip().strip('"')
|
||||||
|
return (
|
||||||
|
f"スクリプトを作成しました。\n"
|
||||||
|
f"パス: {path}\n"
|
||||||
|
f"ハッシュ: {hash_val}\n"
|
||||||
|
f"URL: {WINDMILL_URL}/scripts/edit/{path}?workspace={WINDMILL_WORKSPACE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def windmill_list_scripts(per_page: int = 20) -> str:
|
def windmill_list_scripts(per_page: int = 20) -> str:
|
||||||
"""Windmill のスクリプト一覧を取得する
|
"""Windmill のスクリプト一覧を取得する
|
||||||
@@ -228,4 +337,10 @@ def windmill_get_script(path: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
mcp.run(transport="stdio")
|
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
||||||
|
if transport == "sse":
|
||||||
|
host = os.environ.get("MCP_HOST", "0.0.0.0")
|
||||||
|
port = int(os.environ.get("MCP_PORT", "8001"))
|
||||||
|
mcp.run(transport="sse", host=host, port=port)
|
||||||
|
else:
|
||||||
|
mcp.run(transport="stdio")
|
||||||
|
|||||||
Reference in New Issue
Block a user