gemini-image-mcp
by nguyennamkkb
README.md
# gemini-image-mcp
MCP server cho **AI Image Studio**: agent vừa **chạy service** (bridge), vừa **gọi API tạo ảnh** —
ảnh do Gemini sinh trong chính trình duyệt của bạn, kết quả trả về dưới dạng file.
```
MCP client (Claude Desktop / Cursor / VS Code)
│ stdio
▼
gemini-image-mcp ──► bridge (127.0.0.1) ──► trang studio trong trình duyệt ──► Gemini
```
4 tool: `bridge_status` · `bridge_start` · `bridge_stop` · `image_generate`
## Bắt đầu trong 3 bước
**1. Thêm config vào MCP client**
*Claude Desktop* (`~/Library/Application Support/Claude/claude_desktop_config.json`;
Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
```json
{
"mcpServers": {
"gemini-image-mcp": {
"command": "npx",
"args": ["-y", "github:nguyennamkkb/gemini-image-mcp"]
}
}
}
```
*Cursor* (`~/.cursor/mcp.json`) và *VS Code* (`.vscode/mcp.json`): cùng dạng `mcpServers` ở trên
(VS Code còn nhận `{"servers": {…: {"type": "stdio", …}}}`).
*opencode* — dùng 2 dòng ngắn (đừng dán 1 dòng dài, dễ bị xuống dòng làm `github:…` thành lệnh riêng):
```bash
SPEC=github:nguyennamkkb/gemini-image-mcp
opencode mcp add --global gemini-image-mcp -- npx -y $SPEC
opencode mcp list # phải thấy "✓ gemini-image-mcp connected" (lần đầu chờ ~10s cho npx tải)
```
Hoặc viết tay vào `~/.config/opencode/opencode.json` (toàn cục) hay `opencode.json` ở gốc project:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gemini-image-mcp": {
"type": "local",
"command": ["npx", "-y", "github:nguyennamkkb/gemini-image-mcp"],
"enabled": true,
"timeout": 30000
}
}
}
```
Hai điểm bắt buộc phải biết cho opencode:
- **Đặt `timeout` ≥ 30000.** Lần chạy đầu `npx` phải tải package (~9s đo thực tế), mà mặc định
opencode chỉ chờ 5000ms để lấy danh sách tool → server hiện `pending`/failed dù cấu hình đúng.
- `opencode.json` ở gốc project **chỉ được đọc khi thư mục đó là git workspace** (ngoài git repo
opencode báo "No MCP servers configured"). Muốn dùng mọi nơi thì thêm `--global` như trên.
- Shape phẳng `"mcp": {"gemini-image-mcp": {…}}` (như trên) và shape lồng `"mcp": {"servers": {…}}`
(CLI v2 sinh ra) đều chạy — đã thử cả hai trên opencode 2.0.32.
> Config trên luôn lấy bản mới nhất từ nhánh `main`. Muốn cố định, thêm `#v1.0.2` (hoặc commit sha)
> vào sau tên repo, ví dụ `github:nguyennamkkb/gemini-image-mcp#v1.0.2`.
**2. Khởi động lại client** (thoát hẳn rồi mở lại). Trong danh sách tool phải thấy đủ 4 tool ở trên.
**3. Mở trang studio thành worker.** Nhờ agent gọi `bridge_start` — nó trả về `pageUrl`
(dạng `http://127.0.0.1:8765/?token=…`). **Mở link đó trong Chrome** rồi để yên tab đấy.
Sau đó `image_generate` mới có worker để chạy.
> Đang dùng **Gemini Canvas** (để Canvas cấp key Gemini) thì gọi
> `bridge_start {"allowCanvas": true}` và mở trang studio trong tab Canvas — xem
> [ràng buộc trình duyệt](#vì-sao-phải-có-trang-studio-mở).
## Workflow đang dùng: Gemini Canvas (1–1)
Cách dùng **cố định của máy này** — worker là **tab Gemini Canvas**, không phải tab local.
Mọi thứ khác chạy tự động; đọc mục này trước khi sửa/nâng cấp để không đi lạc hướng.
```
opencode ──stdio──► gemini-image-mcp ──HTTP/WS──► bridge (127.0.0.1:8765)
▲
HTTP long-poll │ (Canvas cấm WebSocket)
│
tab Gemini Canvas (ai_image_studio.html) ──► Gemini
(Canvas tự cấp key — KHÔNG cần key trong trang)
```
### 1. Config MCP (`opencode.json` ở gốc project)
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gemini-image-mcp": {
"type": "local",
"command": ["npx", "-y", "github:nguyennamkkb/gemini-image-mcp"],
"enabled": true,
"timeout": 30000,
"environment": {
"AI_STUDIO_ALLOW_CANVAS": "true",
"AI_STUDIO_TOKEN": "aistudio-1-1-7f2c9d41b8e35a60"
}
}
}
}
```
| Dòng | Vì sao cần |
|---|---|
| `timeout: 30000` | Lần đầu `npx` tải package ~9s; mặc định opencode chờ 5000ms → server hiện `pending` dù đúng config |
| `AI_STUDIO_ALLOW_CANVAS: "true"` | Bridge **tự start** (do `image_generate`) luôn có `--allow-canvas` → nhận worker Canvas |
| `AI_STUDIO_TOKEN` | Token **cố định** để bridge ↔ trang bắt tay, không lệch khi file `~/.ai-image-studio/.token` bị xoá |
Giá trị cần dán vào tab Canvas (một lần, localStorage giữ mãi):
- URL: `ws://127.0.0.1:8765/ws`
- Token: `aistudio-1-1-7f2c9d41b8e35a60`
> Để trống ô token cũng được — trang tự dò qua `GET /api/discover` (endpoint này trả token),
> nên hai bên luôn khớp kể cả khi bạn quên.
**Nếu dùng Claude Code (`.mcp.json`)** — cùng env nhưng tên key khác:
```json
{
"mcpServers": {
"gemini-image-mcp": {
"command": "npx",
"args": ["-y", "github:nguyennamkkb/gemini-image-mcp"],
"env": { "AI_STUDIO_ALLOW_CANVAS": "true", "AI_STUDIO_TOKEN": "aistudio-1-1-7f2c9d41b8e35a60" }
}
}
}
```
| opencode | Claude Code | Ghi chú |
|---|---|---|
| `mcp` | `mcpServers` | key gốc — **`mcpServers` KHÔNG có tác dụng với opencode** (và ngược lại) |
| `command` (mảng) | `command` (chuỗi) + `args` (mảng) | |
| `environment` | `env` | biến cho tiến trình server (đặt `AI_STUDIO_*` ở đây) |
| `timeout: 30000` | — | Claude Code chỉnh bằng env `MCP_TIMEOUT` / `MCP_TOOL_TIMEOUT` của **client**, đặt trong `env` của server là vô tác dụng: `export MCP_TOOL_TIMEOUT=600000` (ảnh có thể mất vài phút) |
### 2. Chrome phải tắt Local Network Access (Chrome 142+)
Trang Canvas là origin `https://…usercontent.goog`; Chrome 155 **chặn** nó gọi `http://127.0.0.1`
→ mọi cổng báo `Failed to fetch`. Bắt buộc mở Chrome kèm cờ:
```bash
# alias đã có trong ~/.zshrc:
alias chromedev='open -na "Google Chrome" --args --disable-features=LocalNetworkAccessChecks'
pkill -f "Google Chrome" # PHẢI thoát hẳn Chrome trước khi chạy chromedev
chromedev # dùng profile mặc định (giữ đăng nhập Google/Gemini)
# kiểm chứng cờ đã vào (kỳ vọng ≥1):
ps aux | grep "Contents/MacOS/Google Chrome" | grep -v grep | grep -c LocalNetworkAccessChecks
```
### 3. Vận hành hằng ngày
1. `chromedev` → mở lại tab **Gemini Canvas** chứa `ai_image_studio.html` → để yên tab đó
2. Gọi `image_generate {"prompt": "…"}` từ opencode — bridge tự start, tự chờ worker, trả `filePath`
3. Tab Canvas hiện `Đang tạo ảnh` / worker `h_… HTTP long-poll` là dấu hiệu đúng
**Quy tắc bất di bất dịch**
- **KHÔNG sửa `ai_image_studio.html`.** Bản trong tab Canvas là bản bạn đã dán; sửa file trong repo
sẽ làm lệch hai bên và gây nhầm lẫn. Mọi bản vá nằm ở `bridge/` + `mcp/`.
- *Ngoại lệ đã chấp nhận (1.0.9):* ô token trong panel "Cấu hình API local" đổi
`type="password"` → `type="text"` để **nhìn thấy token cố định** cho tiện đối chiếu hai bên.
Đây là token local (loopback + origin policy), không phải khoá Gemini. Sửa trên do người dùng
chủ động; tab Canvas muốn thấy y hệt thì **dán lại** `ai_image_studio.html` mới vào Canvas một lần.
- **Không cần key Gemini** trong trang (Canvas cấp) và **không cần dán token** (tự dò).
- Chỉ cần **1 worker** (tab Canvas). Bridge ở chế độ canvas **không mở tab local** nữa, nên không
có chuyện job rơi vào tab không key (xem bảng sự cố bên dưới).
### 4. Nâng cấp lên bản mới (repo không dùng tag, luôn theo `main`)
```bash
# phía repo (người phát hành):
# 1) sửa code → 2) tăng "version" trong package.json → 3) commit & push main
git commit -am "..." && git push origin main
# phía máy dùng (bắt buộc, nếu không npx vẫn dùng bản cũ đã cache):
rm -rf ~/.npm/_npx/*/node_modules/gemini-image-mcp ~/.npm/_npx/<hash>
# rồi thoát hẳn opencode và mở lại → npx tải bản mới
```
Kiểm tra đang chạy bản nào:
```bash
opencode mcp list # phải thấy ✓ gemini-image-mcp connected
# trong phiên: gọi bridge_status {} → "version" của bridge
# initialize (client tự gọi) → serverInfo.version = version trong package.json
python3 -c "import json;print(json.load(open('$(ls -d ~/.npm/_npx/*/node_modules/gemini-image-mcp/package.json | head -1)'))['version'])"
```
Muốn cố định một bản thay vì theo `main`: thêm `#<commit-sha>` sau tên repo trong `command`.
### 5. Sự cố thường gặp (đã gặp thật, kèm cách sửa)
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Trang Canvas: mọi cổng `Failed to fetch` | Chrome chặn LNA (142+) | Thoát hẳn Chrome → `chromedev` → mở lại tab Canvas |
| `origin-not-allowed` / HTTP 403 từ origin Canvas | Bridge chạy thiếu `--allow-canvas` | Từ 1.0.5: `bridge_start {allowCanvas:true}` tự dừng + start lại bridge thiếu quyền; hoặc đặt `AI_STUDIO_ALLOW_CANVAS=true` |
| `502 Method doesn't allow unregistered callers` | Tab local (`?token=…`) không có key Gemini lại nhận job | Từ 1.0.6: bridge chế độ canvas **không mở tab local** → đóng tab local nếu còn mở |
| `503 chưa có worker` | Tab Canvas chưa mở/chưa bấm "Thử lại" | `bridge_status` → mở tab Canvas theo `pageUrl`/panel |
| Server `pending`/failed trong opencode | `timeout` < 30000, hoặc config sai key (`mcpServers` là của Claude, opencode dùng `mcp`) | Sửa `opencode.json` như mục 1 |
| Bridge start nhưng token lệch với trang | Token ngẫu nhiên bị sinh lại (xoá `~/.ai-image-studio/.token`) | Dùng `AI_STUDIO_TOKEN` cố định như mục 1 |
Dừng dịch vụ khi cần: `bridge_stop {"port": 8765}` (chỉ kill khi `/api/status` xác nhận đúng bridge).
## Dùng
```
bridge_status {} → bridge nào đang chạy, worker nào nối, link mở trang
bridge_start {"allowCanvas": true} → chạy service nền, trả pageUrl + pid + logFile
image_generate {"prompt":"mèo phi hành gia, 3D cinematic","aspectRatio":"16:9"}
image_generate {"prompt":"biến thể lúc hoàng hôn","refImagePath":"/tmp/in.png","inline":true}
bridge_stop {"port":8765} → dừng bridge (chỉ kill khi /api/status xác nhận đúng bridge)
```
`image_generate` trả về `{ok, filePath, bytes, mime, aspectRatio, tookMs, workerId}`; đặt
`inline:true` để kèm luôn ảnh vào kết quả cho client hiển thị.
## Dữ liệu lưu ở đâu
| Đường dẫn | Nội dung |
|---|---|
| `<project đang gọi tool>/generated-images/` | Ảnh tạo ra **khi client chạy trong một project** (opencode/Cursor/VS Code spawn MCP server với `cwd` = gốc workspace, đã đo thực tế) |
| `~/Pictures/ai-image-studio/` | Ảnh tạo ra khi không xác định được project (ví dụ Claude Desktop) |
| `~/.ai-image-studio/.token` | Token bí mật để local API/bridge xác thực (sinh tự động, quyền `600`) |
| `~/.ai-image-studio/logs/` | Log của bridge do MCP chạy nền |
| `~/.ai-image-studio/.bridge-state.json` | pid/port của bridge mà MCP đã chạy (để `bridge_stop` biết) |
Hai biến môi trường để đổi chỗ lưu (thêm vào `"env"` trong config nếu cần):
| Biến | Mặc định | Ý nghĩa |
|---|---|---|
| `AI_STUDIO_HOME` | `~/.ai-image-studio` | Token, log, state của bridge |
| `AI_STUDIO_OUTPUT` | *(tự chọn theo project)* | Ép nơi lưu ảnh, ghi đè cả quy tắc theo project |
| `AI_STUDIO_ALLOW_CANVAS` | *(tắt)* | `true` → bridge tự start (bởi `image_generate`) luôn mang `--allow-canvas`, sẵn sàng nhận worker Gemini Canvas |
| `AI_STUDIO_START_ARGS` | *(trống)* | Chèn thêm tham số cho bridge tự start, ví dụ `--allow-origin https://your-host` |
| `AI_STUDIO_TOKEN` | *(tự sinh)* | **Token cố định** để bridge ↔ trang studio bắt tay (thay vì token ngẫu nhiên sinh một lần rồi lưu ở `~/.ai-image-studio/.token`). Đặt trong `env` của config MCP ⇒ bridge và MCP dùng chung đúng một token, dán vào trang Canvas một lần là dùng mãi. Tên cũ `BRIDGE_TOKEN` vẫn chấp nhận |
### Ảnh lưu vào đâu
Thứ tự ưu tiên (cao → thấp), mỗi kết quả `image_generate` đều trả `filePath` + `outputDir` + `outputSource` để biết vì sao:
1. `savePath` — đường dẫn file chính xác truyền trong lệnh.
2. `outputDir` — thư mục truyền trong lệnh (tự đặt tên file).
3. `AI_STUDIO_OUTPUT` — biến môi trường.
4. `<project đang gọi tool>/generated-images` — khi `cwd` của server là một project (có `.git`/`package.json`).
5. `~/Pictures/ai-image-studio` — khi không xác định được project.
Muốn ảnh luôn vào project hiện tại khi dùng opencode thì không cần làm gì thêm (nó spawn server ở gốc
workspace). Muốn đổi chỗ khác thì đặt `AI_STUDIO_OUTPUT` trong `"environment"`/`"env"` của config.
## Yêu cầu
- **Node ≥ 20** (MCP server + bridge đều zero-dependency ngoài SDK chính thức).
- **Một trang studio đang mở làm worker**: hoặc mở link bridge (`pageUrl`) trong Chrome, hoặc
dán `ai_image_studio.html` vào **Gemini Canvas** (Canvas cấp key Gemini nên không cần API key).
## Vì sao phải có trang studio mở
Ảnh do Gemini sinh **trong trình duyệt** (Canvas tự inject key), nên luôn cần ít nhất một worker là
trang `ai_image_studio.html` đang mở. Trình duyệt cũng chặn khá nhiều đường về máy local, và bridge
đã xử lý từng trường hợp:
| Trang chạy ở | Rào cản | Cách dùng |
|---|---|---|
| `http://127.0.0.1:8765/` (link `pageUrl`) | không có | Mở link, xong. WebSocket chạy luôn |
| Tab **Gemini Canvas** | Canvas cấm `new WebSocket`; Chrome chặn Local Network Access với origin công khai; origin Canvas không phải loopback | `bridge_start {"allowCanvas": true}` + Chrome chạy kèm `--disable-features=LocalNetworkAccessChecks` |
| Origin khác (host LAN, https khác) | như trên, và phải khai báo origin | `bridge_start {"allowOrigin": ["https://your-host"]}` |
Chi tiết + bằng chứng đo được: [bridge/README.md](bridge/README.md). MCP: [mcp/README.md](mcp/README.md).
## Phát triển
```bash
npm install
npm run smoke # handshake + tools/list + bridge_status + kiểm tra đường dẫn
npm run smoke -- --with-service # thêm vòng đời start → status → stop
npm run smoke -- --with-image # thêm tạo ảnh thật (cần worker đang mở)
npm pack # gói phát hành, kiểm tra nội dung .tgz
```
Cấu trúc:
| Đường dẫn | Vai trò |
|---|---|
| `mcp/server.mjs` | MCP server (stdio) + 4 tool |
| `mcp/smoke-test.mjs` | Test qua stdio JSON-RPC thật |
| `bridge/server.mjs` | Bridge: WS hub + HTTP API + HTTP long-poll cho Canvas |
| `ai_image_studio.html` | Trang studio (UI + client bridge) |
### Phát hành bản mới
Không dùng tag — người dùng luôn lấy bản mới nhất từ `main`:
1. Sửa code, tăng `version` trong `package.json` (version này được MCP trả trong `initialize`
nên kiểm tra được đang chạy bản nào).
2. `git commit && git push origin main`
3. Người dùng chạy lại client là xong (`npx` sẽ lấy bản mới). Ai muốn cố định thì pin
`github:nguyennamkkb/gemini-image-mcp#<commit-sha>`.
Các tag cũ (`v1.0.0` → `v1.0.2`) vẫn còn trên remote để ai đang pin thì không gãy; xoá bằng
`git push origin :refs/tags/v1.0.0` nếu không muốn giữ.
## License
MIT — xem [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues