9 Commits
sync ... main

Author SHA1 Message Date
Akira
7102f0f553 .gitignore: ワークフロー定義ファイルをmainブランチから除外
u/ と workflows/f,u,g/ はsyncブランチがgit_syncで管理するため
mainブランチのローカル作業ディレクトリでは追跡しない

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:37:12 +09:00
Akira
da2466462e windmill_update_flow: エンドポイントを flows/edit → flows/update に修正
Windmill API の正しいフロー更新エンドポイントは /flows/update/{path}。
/flows/edit/{path} は 404 を返す(誤ったエンドポイント)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:30:56 +09:00
Akira
77f3326868 windmill_mcp.py: ホスト設定をコンストラクタで渡すよう修正
- FastMCP("windmill") でデフォルト host=127.0.0.1 だと DNS rebinding 保護が
  自動有効化されて外部ホスト名からのアクセスが "Invalid Host header" で拒否される
- MCP_HOST/MCP_PORT 環境変数をコンストラクタ時点で渡すことで
  host=0.0.0.0 の場合は保護が無効になり Traefik 経由のアクセスが通る
- ドメイン名を windmill_mcp → windmill-mcp に修正(DNS は hyphen のみ対応)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:06:51 +09:00
Akira
2dbe8c8a74 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>
2026-03-02 01:39:31 +09:00
Akira
c163d00f51 再起動お疲れ様です。メモリを確認して、MCP サーバーが動作するかテストします。
Windmill [windmill_list_flows]
OUT
{"result":"- u/antigravity/git_sync: Git Sync Workflow\n- f/mail/mail_filter: メールフィルタリング\n- f/shiraou/shiraou_notification: 白皇集落営農 変更通知\n- f/app_custom/system_heartbeat: Windmill Heartbeat - システム自己診断"}
MCP サーバーは正常に動作しています。フロー一覧が取得できました:

パス	名前
u/antigravity/git_sync	Git Sync Workflow
f/mail/mail_filter	メールフィルタリング
f/shiraou/shiraou_notification	白皇集落営農 変更通知
f/app_custom/system_heartbeat	Windmill Heartbeat - システム自己診断
次に何を行いますか?前回の続きがあればお教えください。
2026-02-25 21:48:18 +09:00
Akira
5e92ea62ef 完了したこと
1. Traefik "too many services" エラー修正 

原因: windmill_extra コンテナのラベルに2つの問題があった

ルーターに service の明示指定がなかった(Traefik が判断できない)
windmill_extra が traefik-net に接続されていなかった(Traefik がルーティング不可)
修正内容:

traefik.http.routers.windmill-lsp.service=windmill-lsp を追加
traefik.http.routers.windmill-debug.service=windmill-debug を追加
windmill_extra を traefik-net に接続
サーバーの docker-compose.yml を永続化(/workspace/docker-compose.yml)
2. Windmill MCP サーバー実装 

mcp/windmill_mcp.py を作成・コミット。7 ツール実装:

windmill_list_flows / windmill_get_flow / windmill_run_flow
windmill_list_recent_jobs / windmill_get_job_logs
windmill_list_scripts / windmill_get_script
Claude Code への登録: .mcp.json + .claude/settings.local.json の enabledMcpjsonServers で自動承認済
2026-02-25 21:44:28 +09:00
Akira
f80c2a2518 完了したこと
1. Traefik "too many services" エラー修正 

原因: windmill_extra コンテナのラベルに2つの問題があった

ルーターに service の明示指定がなかった(Traefik が判断できない)
windmill_extra が traefik-net に接続されていなかった(Traefik がルーティング不可)
修正内容:

traefik.http.routers.windmill-lsp.service=windmill-lsp を追加
traefik.http.routers.windmill-debug.service=windmill-debug を追加
windmill_extra を traefik-net に接続
サーバーの docker-compose.yml を永続化(/workspace/docker-compose.yml)
2. Windmill MCP サーバー実装 

mcp/windmill_mcp.py を作成・コミット。7 ツール実装:

windmill_list_flows / windmill_get_flow / windmill_run_flow
windmill_list_recent_jobs / windmill_get_job_logs
windmill_list_scripts / windmill_get_script
Claude Code への登録: .mcp.json + .claude/settings.local.json の enabledMcpjsonServers で自動承認済
2026-02-25 21:44:19 +09:00
Akira
6de184968d Windmill MCP サーバーを追加
mcp/windmill_mcp.py: FastMCP ベースの Python MCP サーバー
  - windmill_list_flows: フロー一覧
  - windmill_get_flow: フロー定義取得
  - windmill_run_flow: フローのトリガー実行
  - windmill_list_recent_jobs: ジョブ一覧(成功/失敗/実行中フィルタ対応)
  - windmill_get_job_logs: ジョブログ・実行結果取得
  - windmill_list_scripts: スクリプト一覧
  - windmill_get_script: スクリプトソースコード取得

.gitignore: .mcp.json, __pycache__/ を除外

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 21:42:41 +09:00
Akira
be710b920e 変更内容
削除したもの
traefik-net 外部ネットワーク
windmill_server の全 Traefik ラベル
windmill_extra の全 Traefik ラベル
サーバー固有のボリューム /home/windmill/windmill:/workspace(ローカルには不要)
Google OAuth 環境変数(ローカル開発では不要)
windmill_indexer(replicas: 0 で無意味だったので削除)
追加・変更したもの
項目	変更前	変更後
ネットワーク	traefik-net + windmill-internal	windmill-internal のみ
アクセス方法	Traefik経由(HTTPS)	Caddy経由 http://localhost
POSTGRES_PASSWORD	${POSTGRES_PASSWORD}	${DATABASE_PASSWORD}(.envと統一)
windmill_worker replicas	3	1(ローカル用に軽量化)
pull_policy	always	if_not_present(毎回pullしない)
BASE_URL	https://windmill.keinafarm.net	http://localhost
新規作成
Caddyfile.local — ローカル用のシンプルなCaddy設定(LSP WebSocket・デバッガー対応)
起動方法

docker compose -f docker-compose-dev.yml up -d
起動後、ブラウザで http://localhost にアクセスすればWindmillが使えます。

注意: windmill_worker に /var/run/docker.sock をマウントしていますが、Windows の Docker Desktop は通常これをサポートしています。もし問題が出る場合はそのボリューム行を削除してください。
2026-02-22 10:37:12 +09:00
12 changed files with 614 additions and 59 deletions

View File

@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(curl -m 10 -s -o /dev/null -w \"%{http_code}\" -H \"Connection: Upgrade\" -H \"Upgrade: websocket\" https://windmill.keinafarm.net/ws/)",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" -t claude@keinafarm.net \"cat /home/windmill/windmill/docker-compose.yml\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/docker-compose.yml\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker compose --project-directory /home/windmill/windmill version 2>&1\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker compose --project-directory /home/windmill/windmill ps 2>&1\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/.env\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker network inspect traefik-net --format ''{{json .Name}}'' && docker network connect traefik-net windmill-windmill_extra-1 && echo ''Connected successfully''\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/docker-compose.yml > /tmp/windmill-compose.yml && cat /tmp/windmill-compose.yml | wc -l\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"grep -A 20 ''windmill_extra:'' /tmp/windmill-compose-fixed.yml | grep -E ''\\(traefik-net|windmill-lsp.service|windmill-debug.service\\)''\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"grep -n ''windmill-lsp\\\\|windmill-debug\\\\|traefik-net'' /tmp/windmill-compose-fixed.yml\")",
"Bash(ssh -i:*)",
"WebFetch(domain:github.com)",
"Bash(python -c \"from mcp.server.fastmcp import FastMCP; print\\(''mcp OK''\\)\")",
"Bash(pip install mcp httpx)",
"Bash(WINDMILL_TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh python -c \":*)",
"Read(//c/Users/akira/.claude/**)",
"Bash(git add .gitignore mcp/)",
"Bash(git commit:*)",
"mcp__windmill__windmill_list_flows"
],
"additionalDirectories": [
"C:\\Users\\akira\\Develop\\windmill",
"C:\\Users\\akira\\.claude\\projects\\c--Users-akira-Develop-windmill\\memory\\"
]
},
"enabledMcpjsonServers": [
"windmill"
]
}

11
.gitignore vendored
View File

@@ -29,6 +29,9 @@ Thumbs.db
*.bak
*~
# MCP server config (contains API tokens)
.mcp.json
# Resolved markdown files (generated by editor)
*.resolved
*.resolved.*
@@ -46,3 +49,11 @@ workflows/.wmill/tmp/
!workflows/g/
!workflows/wmill.yaml
!workflows/wmill-lock.yaml
__pycache__/
# Windmill workflow definitions (managed by git_sync on sync branch)
# Local working directory should not track these on main
u/
workflows/f/
workflows/u/
workflows/g/

12
Caddyfile.local Normal file
View File

@@ -0,0 +1,12 @@
:80 {
# LSP - Language Server Protocol for code intelligence
reverse_proxy /ws/* http://windmill_extra:3001
# Debugger - Interactive debugging via DAP WebSocket
handle_path /ws_debug/* {
reverse_proxy http://windmill_extra:3003
}
# Default: Windmill server
reverse_proxy /* http://windmill_server:8000
}

112
HANDOFF.md Normal file
View File

@@ -0,0 +1,112 @@
# Windmill 作業引き継ぎメモ
> 作成: 2026-02-25keinasystem_t02 セッションから移管)
---
## 現在の状態
### git_sync フロー(修正済み ✅)
**問題**: `u/antigravity/git_sync` フローが `wmill sync pull` で認証エラー
**原因**: `$WM_TOKEN`(ジョブトークン)はワークスペーススコープのため、
wmill CLIが内部で呼ぶ `/api/users/whoami`グローバルAPIで401
**解決**:
- `/home/windmill/windmill/wmill_config/remotes.ndjson` を作成(永続設定)
- グローバルスコープトークン `CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8`(ラベル: `git-sync`)を使用
- フロースクリプトを `--config-dir /workspace/wmill_config` を使う形に修正
- 動作確認: Success: True (2秒) ✅
### windmill.keinafarm.net 外部アクセス504 未修正 ❌)
- 外部からアクセスすると 30秒後に 504 Gateway Timeout
- サーバー内部からは `http://localhost:8000` で正常にアクセスできる
- **Caddyfile がこのディレクトリにある** → 原因調査・修正が必要
### Windmill MCP サーバー(未着手 ⬜)
- LLMClaudeが直接 Windmill を操作できるようにする
- 外部アクセスが直れば `https://windmill.keinafarm.net` に接続
- 直らない場合は SSH トンネル経由
---
## 重要な情報
### サーバー接続
```
SSH: root@keinafarm.net
Windmill内部URL: http://localhost:8000
Windmillサーバー上のパス: /home/windmill/windmill/
```
### API トークン
| トークン | スコープ | 用途 |
|---------|---------|------|
| `qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh` | ワークスペースadmins | 通常のAPI操作 |
| `CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8` | グローバル | git-sync用wmill CLI |
### 2つの Git リポジトリ(別物!)
| リポジトリ | パス | 用途 |
|-----------|------|------|
| `windmill.git` (Gitea) | サーバー `/home/windmill/windmill/` | wmill CLI で自動同期 |
| `windmill_workflow.git` (Gitea) | ローカル `C:\Users\akira\Develop\windmill` | このディレクトリ |
### wmill_config永続設定
```
サーバーパス: /home/windmill/windmill/wmill_config/
コンテナ内パス: /workspace/wmill_config/
remotes.ndjson: {"remote":"http://windmill_server:8000/","workspaceId":"admins","name":"admins","token":"CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8"}
activeWorkspace: admins
```
---
## 次にやること(優先順)
### 1. windmill.keinafarm.net 504 を修正
`Caddyfile` を確認して、windmill へのリバースプロキシ設定を見直す。
タイムアウト設定が足りない可能性が高い。
```
# 確認コマンド(サーバー内部では正常)
ssh root@keinafarm.net "curl -s http://localhost:8000/api/version"
```
### 2. Windmill MCP サーバーを実装
**方針**: カスタム軽量 Python MCP サーバー6〜8 tools
実装する tools:
- `windmill_list_flows` — フロー一覧
- `windmill_get_flow` — フローのスクリプト取得
- `windmill_run_flow` — フローをトリガー
- `windmill_list_recent_jobs` — 最近のジョブ一覧(成功/失敗)
- `windmill_get_job_logs` — ジョブの詳細ログ
- `windmill_list_scripts` — スクリプト一覧
- `windmill_get_script` — スクリプト取得
既存実装: `rothnic/windmill-mcp`GitHub, スター0, 更新停止)→ 使わず自作
### 3. mail_filter.flow.json をコミット
ローカルの `windmill_workflow``f/mail/mail_filter.flow.json` が未コミット状態。
---
## 参考: wmill 設定ファイル形式
```
~/.config/windmill/remotes.ndjson (1行1ワークスペース)
{"remote":"http://...../","workspaceId":"...","name":"local_alias","token":"..."}
~/.config/windmill/activeWorkspace (プレーンテキスト)
local_alias
```
`wmill sync pull --config-dir /path/to/config` で任意ディレクトリを指定可能。

View File

@@ -8,10 +8,8 @@ x-logging: &default-logging
compress: "true"
networks:
traefik-net:
external: true # Traefik管理下のネットワーク
windmill-internal:
driver: bridge # Windmill内部通信用
driver: bridge
services:
db:
@@ -25,7 +23,7 @@ services:
expose:
- 5432
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: windmill
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
@@ -39,46 +37,30 @@ services:
windmill_server:
image: ${WM_IMAGE}
container_name: windmill_server
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 1
restart: unless-stopped
expose:
- 8000
- 2525
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=server
- BASE_URL=http://localhost
depends_on:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
- /home/windmill/windmill:/workspace
labels:
# Traefik設定
- "traefik.enable=true"
# HTTPSルーター
- "traefik.http.routers.windmill.rule=Host(`windmill.keinafarm.net`)"
- "traefik.http.routers.windmill.entrypoints=websecure"
- "traefik.http.routers.windmill.tls=true"
- "traefik.http.routers.windmill.tls.certresolver=letsencrypt"
- "traefik.http.services.windmill.loadbalancer.server.port=8000"
# HTTPからHTTPSへのリダイレクト
- "traefik.http.routers.windmill-http.rule=Host(`windmill.keinafarm.net`)"
- "traefik.http.routers.windmill-http.entrypoints=web"
- "traefik.http.routers.windmill-http.middlewares=windmill-https-redirect"
- "traefik.http.middlewares.windmill-https-redirect.redirectscheme.scheme=https"
networks:
- traefik-net
- windmill-internal
logging: *default-logging
windmill_worker:
image: ${WM_IMAGE}
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 3
replicas: 1
resources:
limits:
cpus: "1"
@@ -95,14 +77,13 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
- /home/windmill/windmill:/workspace
networks:
- windmill-internal
logging: *default-logging
windmill_worker_native:
image: ${WM_IMAGE}
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 1
resources:
@@ -125,35 +106,12 @@ services:
- windmill-internal
logging: *default-logging
windmill_indexer:
image: ${WM_IMAGE}
pull_policy: always
deploy:
replicas: 0 # 必要に応じて1に変更
restart: unless-stopped
expose:
- 8002
environment:
- PORT=8002
- DATABASE_URL=${DATABASE_URL}
- MODE=indexer
depends_on:
db:
condition: service_healthy
volumes:
- windmill_index:/tmp/windmill/search
- worker_logs:/tmp/windmill/logs
networks:
- windmill-internal
logging: *default-logging
windmill_extra:
image: ghcr.io/windmill-labs/windmill-extra:latest
pull_policy: always
image: ghcr.io/windmill-labs/windmill-extra:${WM_VERSION}
pull_policy: if_not_present
restart: unless-stopped
expose:
- 3001
- 3002
- 3003
environment:
- ENABLE_LSP=true
@@ -168,15 +126,27 @@ services:
networks:
- windmill-internal
logging: *default-logging
# Caddyは使わないTraefikを使用
# caddy:
# deploy:
# replicas: 0
caddy:
image: caddy:2.9-alpine
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./Caddyfile.local:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- windmill-internal
logging: *default-logging
depends_on:
- windmill_server
- windmill_extra
volumes:
db_data: null
worker_dependency_cache: null
worker_logs: null
worker_memory: null
windmill_index: null
lsp_cache: null
caddy_data: null
caddy_config: null

View File

@@ -164,6 +164,37 @@ services:
- "traefik.http.routers.windmill-debug.tls=true"
- "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:
db_data: null
worker_dependency_cache: null

12
mcp/.env.example Normal file
View File

@@ -0,0 +1,12 @@
# Windmill MCP Server の設定
# このファイルを .env にコピーして値を設定してください
# Windmill のベース URLデフォルト: https://windmill.keinafarm.net
WINDMILL_URL=https://windmill.keinafarm.net
# Windmill API トークン(必須)
# Windmill の「設定 > トークン」から作成してください
WINDMILL_TOKEN=your_token_here
# 対象ワークスペース(デフォルト: admins
WINDMILL_WORKSPACE=admins

14
mcp/Dockerfile Normal file
View 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"]

View File

@@ -0,0 +1,14 @@
{
"mcpServers": {
"windmill": {
"command": "python",
"args": ["windmill_mcp.py"],
"cwd": "/path/to/mcp",
"env": {
"WINDMILL_TOKEN": "your_api_token_here",
"WINDMILL_URL": "https://windmill.keinafarm.net",
"WINDMILL_WORKSPACE": "admins"
}
}
}
}

2
mcp/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
mcp>=1.0.0
httpx>=0.27.0

343
mcp/windmill_mcp.py Normal file
View File

@@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""Windmill MCP Server - Claude が Windmill を直接操作できるようにする"""
import os
import json
import sys
import httpx
from mcp.server.fastmcp import FastMCP
WINDMILL_URL = os.environ.get("WINDMILL_URL", "https://windmill.keinafarm.net")
WINDMILL_TOKEN = os.environ.get("WINDMILL_TOKEN", "")
WINDMILL_WORKSPACE = os.environ.get("WINDMILL_WORKSPACE", "admins")
MCP_HOST = os.environ.get("MCP_HOST", "127.0.0.1")
MCP_PORT = int(os.environ.get("MCP_PORT", "8001"))
if not WINDMILL_TOKEN:
print("Error: WINDMILL_TOKEN 環境変数が設定されていません", file=sys.stderr)
sys.exit(1)
mcp = FastMCP("windmill", host=MCP_HOST, port=MCP_PORT)
def _headers() -> dict:
return {"Authorization": f"Bearer {WINDMILL_TOKEN}"}
def _api(path: str) -> str:
return f"{WINDMILL_URL}/api/w/{WINDMILL_WORKSPACE}/{path}"
@mcp.tool()
def windmill_list_flows(per_page: int = 20) -> str:
"""Windmill のフロー一覧を取得する
Args:
per_page: 取得件数最大100
"""
resp = httpx.get(
_api("flows/list"),
headers=_headers(),
params={"per_page": min(per_page, 100)},
timeout=30,
)
resp.raise_for_status()
flows = resp.json()
if not flows:
return "フローが見つかりませんでした"
lines = [
f"- {f['path']}: {f.get('summary', '(概要なし)')}" for f in flows
]
return "\n".join(lines)
@mcp.tool()
def windmill_get_flow(path: str) -> str:
"""指定したパスのフロー定義(スクリプト含む)を取得する
Args:
path: フローのパス (例: u/antigravity/git_sync)
"""
resp = httpx.get(_api(f"flows/get/{path}"), headers=_headers(), timeout=30)
resp.raise_for_status()
return json.dumps(resp.json(), indent=2, ensure_ascii=False)
@mcp.tool()
def windmill_run_flow(path: str, args: str = "{}") -> str:
"""フローをトリガーして実行する
Args:
path: フローのパス (例: u/antigravity/git_sync)
args: JSON形式の入力引数 (例: {"key": "value"})
"""
try:
args_dict = json.loads(args)
except json.JSONDecodeError as e:
return f"Error: argsのJSON形式が不正です: {e}"
resp = httpx.post(
_api(f"jobs/run/f/{path}"),
headers=_headers(),
json=args_dict,
timeout=30,
)
resp.raise_for_status()
job_id = resp.text.strip().strip('"')
return (
f"フローを開始しました。\n"
f"ジョブID: {job_id}\n"
f"詳細URL: {WINDMILL_URL}/run/{job_id}?workspace={WINDMILL_WORKSPACE}"
)
@mcp.tool()
def windmill_list_recent_jobs(
limit: int = 20,
success_only: bool = False,
failure_only: bool = False,
script_path_filter: str = "",
) -> str:
"""最近のジョブ一覧を取得する
Args:
limit: 取得件数最大100
success_only: Trueにすると成功ジョブのみ表示
failure_only: Trueにすると失敗ジョブのみ表示
script_path_filter: パスで絞り込む (例: u/antigravity/git_sync)
"""
params: dict = {"per_page": min(limit, 100)}
if success_only:
params["success"] = "true"
if failure_only:
params["success"] = "false"
if script_path_filter:
params["script_path_filter"] = script_path_filter
resp = httpx.get(_api("jobs/list"), headers=_headers(), params=params, timeout=30)
resp.raise_for_status()
jobs = resp.json()
if not jobs:
return "ジョブが見つかりませんでした"
lines = []
for j in jobs:
success = j.get("success")
if success is True:
status = "[OK]"
elif success is False:
status = "[FAIL]"
else:
status = "[RUNNING]"
path = j.get("script_path", "unknown")
started = (j.get("started_at") or "")[:19] or "pending"
job_id = j.get("id", "")
lines.append(f"{status} [{started}] {path} (ID: {job_id})")
return "\n".join(lines)
@mcp.tool()
def windmill_get_job_logs(job_id: str) -> str:
"""ジョブの詳細情報とログを取得する
Args:
job_id: ジョブのIDwindmill_list_recent_jobs で確認できる)
"""
resp = httpx.get(_api(f"jobs_u/get/{job_id}"), headers=_headers(), timeout=30)
resp.raise_for_status()
job = resp.json()
success = job.get("success")
if success is True:
state = "成功 [OK]"
elif success is False:
state = "失敗 [FAIL]"
else:
state = "実行中 [RUNNING]"
result_parts = [
f"ジョブID: {job_id}",
f"パス: {job.get('script_path', 'N/A')}",
f"状態: {state}",
f"開始: {job.get('started_at', 'N/A')}",
f"終了: {job.get('created_at', 'N/A')}",
]
log_resp = httpx.get(
_api(f"jobs_u/getlogs/{job_id}"), headers=_headers(), timeout=30
)
if log_resp.status_code == 200:
result_parts.append("\n--- ログ ---")
result_parts.append(log_resp.text)
result_val = job.get("result")
if result_val is not None:
result_parts.append("\n--- 実行結果 ---")
result_parts.append(
json.dumps(result_val, indent=2, ensure_ascii=False)
if isinstance(result_val, (dict, list))
else str(result_val)
)
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/update/{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()
def windmill_list_scripts(per_page: int = 20) -> str:
"""Windmill のスクリプト一覧を取得する
Args:
per_page: 取得件数最大100
"""
resp = httpx.get(
_api("scripts/list"),
headers=_headers(),
params={"per_page": min(per_page, 100)},
timeout=30,
)
resp.raise_for_status()
scripts = resp.json()
if not scripts:
return "スクリプトが見つかりませんでした"
lines = [
f"- {s['path']} [{s.get('language', '?')}]: {s.get('summary', '(概要なし)')}"
for s in scripts
]
return "\n".join(lines)
@mcp.tool()
def windmill_get_script(path: str) -> str:
"""指定したパスのスクリプトのソースコードを取得する
Args:
path: スクリプトのパス (例: u/antigravity/test_git_sync)
"""
resp = httpx.get(_api(f"scripts/get/{path}"), headers=_headers(), timeout=30)
resp.raise_for_status()
script = resp.json()
result_parts = [
f"パス: {script.get('path', 'N/A')}",
f"言語: {script.get('language', 'N/A')}",
f"概要: {script.get('summary', 'N/A')}",
"",
"--- コード ---",
script.get("content", "(コードなし)"),
]
return "\n".join(result_parts)
if __name__ == "__main__":
transport = os.environ.get("MCP_TRANSPORT", "stdio")
mcp.run(transport=transport)

View File

@@ -50,8 +50,10 @@ if [[ -n $(git status --porcelain) ]]; then
git commit -m "Auto-sync: ${TIMESTAMP}"
echo -e "${YELLOW}Pushing to Gitea...${NC}"
# リモートURLにトークンが含まれていない場合、プッシュに失敗する可能性がある
# ここでは既存のoriginを使用
# リモートの変更を先に取り込むローカルPCからのpushがある場合に備えて
git pull --rebase origin main || {
echo -e "${RED}Failed to pull from remote. Trying push anyway...${NC}"
}
git push origin main || {
echo -e "${RED}Failed to push. Need credentials in git remote url or credential helper.${NC}"
echo -e "${YELLOW}Hint: git remote set-url origin https://<token>@gitea.keinafarm.net/...${NC}"