Skip to main content
Glama
README.md
# dsh-zimage-mcp

Zero-dependency MCP (stdio) server that wraps the Hugging Face Space
**[mrfakename/Z-Image-Turbo](https://huggingface.co/spaces/mrfakename/Z-Image-Turbo)**
(Alibaba **Tongyi Z-Image** turbo text-to-image, running on ZeroGPU A10G)
and exposes it as agent-friendly tools. Built for the
[DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness)
MCP bridge, but works with **any MCP client** (Claude Desktop, Cursor, …).

```
prompt ──▶ mcp__zimage__generate_image ──▶ Z-Image-Turbo Space (Gradio REST)
                    │
                    ├─ downloads the PNG locally (default Documents/DSH/zimage-output)
                    └─ returns JSON: savedPath + url + seedUsed + elapsedMs
```

## Why a wrapper (and not the Space's own MCP endpoint)?

The Space is Gradio ≥5, so it already exposes `https://mrfakename-z-image-turbo.hf.space/gradio_api/mcp`.
Pointing a client there works, **but**:

| | Space's built-in MCP | this server |
|---|---|---|
| Tool result | image inline as base64 content (hundreds of KB into the model context) | trimmed JSON: **local file path**, temporary URL, seed |
| Where the image lives | Space `/tmp/gradio` (garbage-collected) | your disk (survives cleanup) |
| ZeroGPU errors | raw | relayed with actionable hints (quota / sleeping space) |
| Token handling | none | `HF_TOKEN` (env or `~/.dsh/.credentials.yaml`) for higher quota |
| Dependencies | — | **zero** (plain Node ≥18, newline-delimited JSON-RPC over stdio) |

## Tools

### `generate_image`
| arg | type | default | notes |
|---|---|---|---|
| `prompt` | string | required | natural language; EN/JA/ZH all work (≤4000 chars) |
| `width` | int 512–2048 | 1024 | Space slider bounds |
| `height` | int 512–2048 | 1024 | |
| `steps` | int 1–20 | 9 | turbo model: 6–12 is plenty |
| `seed` | int | 0 | used when `randomizeSeed:false` |
| `randomizeSeed` | bool | `true` | `false` = reproducible with `seed` |
| `save` | bool | `true` | download result locally |
| `outPath` | string | auto | absolute path override |

Returns `{ok, savedPath, url, seedUsed, width, height, steps, elapsedMs, bytes, note}`.
Typical wall time **10–40 s** (ZeroGPU queue dependent). Point an image-reading
tool (or a multimodal model) at `savedPath` to inspect the result.

### `space_status`
`{stage, hardware, sdk, likes, note}` — RUNNING / SLEEPING / PAUSED diagnosis.

### `save_image`
`{url, outPath?} → {savedPath, bytes}` — persist a generation URL before the
Space cleans up `/tmp/gradio`.

## DSH registration

In `~/.dsh/profiles/web/cordis.patch.yml` (requires host restart, or HMR
hot-swap of the mcp-client entry):

```yaml
- insert:
    - id: mcp-zimage
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: zimage
        transport: stdio
        command: 'C:\Program Files\nodejs\node.exe'
        args:
          - 'C:\Users\motch\Documents\DSH\dsh-zimage-mcp\zimage-mcp.mjs'
        env:
          HF_TOKEN: !!js process.env.HF_TOKEN || ''
        failOnStartupError: false
        toolCallTimeoutMs: 600000
```

Tools appear as `mcp__zimage__generate_image`, `mcp__zimage__space_status`,
`mcp__zimage__save_image`. `toolCallTimeoutMs` must exceed the Space's worst
queue wait; 10 min is a safe ceiling.

## ZeroGPU quota

Generation is billed against ZeroGPU quota:

- **Anonymous** (per IP): tiny — a few short generations per window; when
  exhausted the Space returns `You have exceeded your ZeroGPU quota (Ns
  requested vs. Ns left)`. The tool relays this verbatim; wait for the window
  to reset and retry.
- **With a free HF token** (`HF_TOKEN`): substantially more GPU-seconds.
  Create one at <https://huggingface.co/settings/tokens> (read is enough),
  then either set the `HF_TOKEN` Windows/host environment variable or put
  `HF_TOKEN: hf_xxx` in `~/.dsh/.credentials.yaml` — the server picks it up
  (env first, credentials file as fallback; the placeholder `unused` is
  ignored). No restart of this server is needed between calls, but a new
  server process re-reads both.

## Configuration (env)

| var | default | meaning |
|---|---|---|
| `HF_TOKEN` | *(auto)* | Bearer token for the Space API; empty → anonymous |
| `ZIMAGE_TIMEOUT_MS` | `570000` | per-generation SSE timeout |
| `ZIMAGE_OUTPUT_DIR` | `%USERPROFILE%\Documents\DSH\zimage-output` | default save directory |

## How it talks to the Space

Plain Gradio REST, no `gradio_client` dependency:

```
POST /gradio_api/call/generate_image   {"data":[prompt,h,w,steps,seed,randomize]} → {"event_id": …}
GET  /gradio_api/call/generate_image/<event_id>   (text/event-stream)
     event: progress|process_starts|heartbeat … until
     event: complete  data=[[{url,path,orig_name,mime_type}, seedUsed]]
     event: error     data=[{"error": "You have exceeded your ZeroGPU quota …"}]
```

Slider bounds (verified 2026-09-01): width/height 512–2048, steps 1–20,
defaults 1024/1024/9.

## Test

```powershell
node test/server-test.mjs --skip-live   # protocol round-trips only
node test/server-test.mjs               # + one real 512x512/4-step generation
```

All assertions passing 2026-09-01 (protocol 5/5; live suite validated
end-to-end against the running Space).

## License

MIT — see [LICENSE](LICENSE).