Perchance Stable Diffusion MCP Server
by Yuser00123
README.md
# Perchance Stable Diffusion MCP Server
MCP server for **https://perchance.org/stable-diffusion-ai** - Free, no sign-up, no limits Stable Diffusion generator.
Built after deep reverse-engineering of Perchance's actual APIs. Provides **15 tools** for image generation via fully **open-source unlimited self-hosted** stack (no Browserless, no API keys, no limits).
## ๐ Open-Source Unlimited Stack (2026-09-24 Breakthrough) โ
TESTED WORKING
**Solution:** `curl_cffi` + `SeleniumBase UC Mode` bypasses Cloudflare & Turnstile **unlimited free, no proxy, no API key**.
```python
from curl_cffi import requests
r = requests.get("https://perchance.org/api/getAccessCodeForAdPoweredStuff",
impersonate="chrome",
headers={"Referer":"https://perchance.org/stable-diffusion-ai","Origin":"https://perchance.org"})
# 200 OK 64 hex: b0023d06ec6433f8ff9b4d2bef4dc91d9102c05f7fbc17b1a149a1a6be7c7d6f
# No proxy, unlimited
```
- โ
Ad code bypass: **100% working unlimited**, no proxy (curl_cffi TLS impersonation)
- โ
Turnstile solving: **SeleniumBase UC Mode** (Chromium undetected) - TESTED, generates userKey 64 hex valid ~30s
- โ
Generate/download bypass: Same TLS impersonation works
- โ
Full generation proof: `opensource_cat.jpeg` 42k, `opensource_landscape.jpeg` 66k
- ๐ Full guide: `OPENSOURCE_GUIDE.md`
**MCP Tools (15, all open-source unlimited):**
- `get_ad_code_opensource` - Unlimited ad code via curl_cffi
- `get_opensource_status` - Check self-hosted stack
- `generate_image_opensource` - **Main tool**: Unlimited generation (curl_cffi + SeleniumBase UC) โ
- `refresh_user_key_opensource` - Unlimited userKey via self-hosted
- `generate_image` - Standard generation (requires fresh userKey)
- `generate_image_official` - Official API v1 (iframe + postMessage, permanent URLs)
- `generate_image_embed` - Embed API via Playwright
- Plus 8 more tools for styles, verification, batch, download
Built after deep reverse-engineering of Perchance's actual APIs. Fully open-source unlimited, no Browserless needed.
## ๐ฌ Research Summary - How Perchance Works (Updated 2026-09-24)
### Two APIs Discovered
#### 1. Official Image API v1 (Recommended for external apps)
**Endpoint:** `https://perchance.org/perchance-ai-api`
**Docs:** https://perchance.org/perchance-ai-api
**Method:** iframe + postMessage, no keys, permanent URLs
```javascript
// 1) warm up once per user โ visits perchance.org so Cloudflare passes them
function warmUp() {
return new Promise(res => {
var f = document.createElement("iframe");
f.src = "https://perchance.org";
f.onload = () => { f.remove(); res(true); };
document.body.appendChild(f);
});
}
// 2) generate + cache (images are permanent URLs on user.uploads.dev โ never challenged)
function generateImage(prompt, opts) {
return new Promise((resolve, reject) => {
var p = new URLSearchParams({ prompt, format: "json", id: Math.random().toString(36).slice(2) });
if (opts) Object.keys(opts).forEach(k => p.set(k, opts[k]));
var f = document.createElement("iframe");
f.src = "https://perchance.org/perchance-ai-api?" + p;
function onMsg(e) {
var d = e.data || {};
if (d.api !== "perchance-image-api" || !d.result || d.result.id !== p.get("id")) return;
finish(d.result.ok ? resolve : reject, d.result);
}
window.addEventListener("message", onMsg);
document.body.appendChild(f);
});
}
warmUp().then(() => generateImage("a cute cat", { resolution: "768x768", seed: 7 }))
.then(r => { imgEl.src = r.url; }); // r = { ok, url, seed, dataUrl, ... }
```
- **Parameters:** `prompt`, `negativePrompt`, `resolution` (512x512|512x768|768x512|768x768), `guidanceScale` (1-30), `seed`, `removeBackground`, `format` (html|json|redirect)
- **Result:** Permanent `user.uploads.dev` URLs, never challenged, cacheable
#### 2. Internal API (Used by stable-diffusion-ai generator, faster, more control)
**Endpoints:**
- `https://image-generation.perchance.org/api/verifyUser?browserId=...&token=...&thread=...`
- `https://image-generation.perchance.org/api/checkUserVerificationStatus?userKey=...`
- `https://image-generation.perchance.org/api/generate?userKey=...&requestId=...&adAccessCode=...&v=...`
- `https://image-generation.perchance.org/api/downloadTemporaryImage?imageId=...` (legacy)
- `https://image-generation.perchance.org/api/downloadTemporaryImageViaProxy?t=v1....` (new, quick-tunnel backend)
- `https://perchance.org/api/getAccessCodeForAdPoweredStuff`
**Flow (Updated 2026-09-24):**
1. **Browser Identity:** `localStorage['generation-v2-browser']` = 32-char hex browserId
- `generation-identity-v2.js` handles this: `[...crypto.getRandomValues(new Uint8Array(16))].map(n => n.toString(16).padStart(2,'0')).join('')`
- Stores userKey as `localStorage['generation-v2:{browserId}:userKey-{thread}']` = 64-char hex
2. **Ad Access Code:** `GET https://perchance.org/api/getAccessCodeForAdPoweredStuff?__cacheBust=...` โ 64-char hex
- **NEW:** Now requires full headers (UA, Referer: https://perchance.org/stable-diffusion-ai, Origin: https://perchance.org) and proxy in some regions, else returns Cloudflare Just a moment challenge
- Implemented with curl fallback in client.py because httpx TLS fingerprint gets blocked
3. **Verification (Turnstile):**
- Tokenless: `GET /api/verifyUser?browserId={32hex}&thread=0&__cacheBust=...` โ `{"status":"failed_verification","reason":"token_required"}` or `already_verified`
- With token: Loads Turnstile from `https://challenges.cloudflare.com/turnstile/v0/api.js` with sitekey `0x4AAAAAAAA8g8NphwaSOT59`
- Solves Turnstile, gets token, then `GET /api/verifyUser?browserId={32hex}&token={turnstileToken}&thread=0` โ `{"status":"success","userKey":"64hex"}`
- `GET /api/checkUserVerificationStatus?userKey={64hex}` โ `{"status":"verified"}` or `not_verified`
- **NEW:** userKey is IP-bound and short-lived (~30s), expires quickly, bound to residential IP
4. **Generation (Updated):**
- `POST /api/generate?userKey={64hex}&requestId=0.{random}&adAccessCode={64hex}&v=9b43eec2e71907610e4e9317b54c208495f6850086e92239a869811d2e2d77ee&__cacheBust=...`
- **v hash changed 2026-09-24:** from `f4fd5ce3a6a768bf12d73c3d6a678d1fb5811e4a24b41c7964a13dc789c811cd` to `9b43eec2e71907610e4e9317b54c208495f6850086e92239a869811d2e2d77ee` (extracted from embed JS)
- Body: `{prompt, negativePrompt, seed, resolution, guidanceScale, channel:"stable-diffusion-ai", subChannel:"public", userKey, adAccessCode, requestId}`
- Responses:
- `{"status":"success","imageId":"...","fileExtension":"jpeg","seed":123,"imageDownloadUrl":"/api/downloadTemporaryImageViaProxy?t=v1....",...}` โ **NEW** proxy download URL
- `{"status":"success","imageDataUrls":["data:image/jpeg;base64,..."]}` โ direct data URL
- `{"status":"invalid_key"}` โ key expired, need refresh
- `{"status":"invalid_access_code"}` โ refresh ad code
- `{"status":"waiting_for_prev_request_to_finish"}` โ wait 8s retry
- `{"status":"client_update_required"}` โ v hash outdated, need update
5. **Download (Updated):**
- Legacy: `/api/downloadTemporaryImage?imageId=...`
- **NEW:** `/api/downloadTemporaryImageViaProxy?t=v1.Tyl8PPYSXrs1EMop.sUSxeY...` (37541 bytes JPEG, headers: X-Perchance-Proxy-Backend: quick-tunnel, X-Image-Sha256, X-Expires-At)
- Token is IP-bound, single-use, expires in ~5 minutes, requires cf_clearance cookie
- Implemented with fallback: try proxy_download first, then imageId, then Playwright fetch inside page context to handle cookies
6. **Embed:** `https://image-generation.perchance.org/embed#JSON` where JSON contains prompt etc. Embed posts `postMessage({type:"finished", dataUrl, seedUsed, id})` to parent.
- **NEW:** Embed JS now includes `hiddenSafeWait` worker timer to handle hidden tab throttling, `paintTick` fix for backgrounded tabs, and `reportImageDownloadFailure` beacon
7. **Frontend:** https://perchance.org/stable-diffusion-ai imports `t2i-framework-plugin-v2` which imports `text-to-image-plugin` which creates iframe to embed.
### Cloudflare Challenges (Updated)
Both `perchance.org` and `image-generation.perchance.org` use Cloudflare managed challenges:
- **Embed page** (`/embed`) - returns challenge HTML with `Just a moment...` initially, then real content after JS challenge solves. Title becomes `Perchance Image Generation Embed` with `waitingEl`.
- **API endpoints** (`/api/verifyUser`, `/api/generate`, `/api/getAccessCodeForAdPoweredStuff`, `/api/checkUserVerificationStatus`) - now often return 403 Just a moment challenge for datacenter IPs, even after embed passed. Requires proxy + full headers + cf_clearance.
- **Ad code endpoint** - now blocked without proxy (returns Just a moment challenge), works via Webshare proxy `31.59.20.176:6754` with UA+Referer+Origin+Accept headers. httpx still gets 403 due to TLS fingerprint, curl fallback works.
- **Download proxy endpoint** - returns 403 if token expired, IP mismatch, or missing cf_clearance cookie. Must be fetched inside Playwright page context that has clearance.
**Error 600010:** Turnstile generic challenge failure - bot behavior detected.
**Workarounds implemented:**
- `get_ad_access_code()` tries httpx then curl fallback with full headers and proxy
- `download_image()` tries httpx first, then Playwright fetch with cf_clearance
- `_find_proxy_download()` recursively finds proxy token in generate response (from eeemoon/perchance PR #9)
- `CLIENT_VERSION_HASH` updated to `9b43eec2e71907610e4e9317b54c208495f6850086e92239a869811d2e2d77ee`
- Playwright with `playwright-stealth` and `LD_LIBRARY_PATH` fix for E2B sandbox
- Sync Playwright works better than async in sandbox (async gets EPIPE after 90s)
- For local use, no proxy needed, residential IP passes Cloudflare
### eeemoon/perchance Fix Applied
From https://github.com/eeemoon/perchance/pull/9 (Fix Perchance proxy image downloads):
- Support new token-based `/downloadTemporaryImageViaProxy` endpoint
- Recursively discover proxy download URLs or v1 tokens in image generation responses
- Keep previous `/downloadTemporaryImage?imageId=...` as fallback
- Implemented in `client.py` as `_find_proxy_download()` and updated `download_image()`
## ๐ ๏ธ Tools Provided (11 tools)
- `list_styles` - List 76 art styles from t2i-styles (Cinematic, Anime, Oil Painting, etc.)
- `get_generator_info` - Info about generator, APIs, resolutions, how it works, env vars, notes (includes new v hash and proxy download)
- `get_user_key_status` - Check cached browserId and userKey validity
- `refresh_user_key` - Try to obtain userKey via browser (may fail in sandbox with 600010, then manual instructions)
- `verify_with_token` - **NEW** Verify with browserId and Turnstile token to get fresh userKey (valid ~30s, IP-bound)
- `generate_image` - Generate via INTERNAL API (fastest, requires userKey, supports art styles, saves to file, now supports proxy_download token)
- `generate_image_official` - Generate via OFFICIAL API v1 (no key, permanent URLs on user.uploads.dev, iframe+postMessage)
- `generate_image_embed` - **NEW** Generate via embed page directly using Playwright (most reliable locally, handles Turnstile)
- `generate_batch` - Generate multiple images (supports both APIs)
- `download_image_tool` - Download by imageId or proxy_token (v1....)
- `get_ad_code` - Get current ad access code (public, 64 hex, now with curl fallback)
## ๐ฆ Installation
```bash
pip install -r requirements.txt
playwright install chromium
# If in E2B sandbox or missing libs:
bash install_deps.sh
export LD_LIBRARY_PATH=/tmp/debs/out/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
```
## ๐ Usage as MCP Server
Add to your MCP client config (e.g. Claude Desktop, Cursor, etc.):
```json
{
"mcpServers": {
"perchance": {
"command": "python",
"args": ["/path/to/perchance-mcp/server.py"],
"env": {
"PERCHANCE_USER_KEY": "your-64-char-hex-key-optional-but-recommended-for-sandbox",
"PERCHANCE_PROXY": "http://user:pass@host:port-optional-for-sandbox"
}
}
}
}
```
Or run directly:
```bash
python server.py
# or
bash run.sh
```
## ๐ Manually Obtaining userKey and Turnstile Token
If `refresh_user_key` fails with Turnstile 600010 or 403:
### userKey (64 hex, IP-bound, ~30s expiry):
1. Open **https://perchance.org/stable-diffusion-ai** in Chrome/Firefox (real browser, not headless)
2. Open DevTools > Application > Local Storage
3. Check `https://image-generation.perchance.org` or `https://c0c3b93a12cdb77c30974f19f33289a7.perchance.org`
4. Find key like `generation-v2:{browserId}:userKey-0` with 64 hex chars value
5. Copy value and set env var:
```bash
export PERCHANCE_USER_KEY="abc123...64chars"
echo "abc123...64chars" > ~/.perchance-mcp/user_key.txt
```
### Turnstile Token (for fresh userKey):
1. Open https://perchance.org/stable-diffusion-ai in real browser
2. DevTools > Network tab, filter `verifyUser`
3. Generate an image
4. Find request like `verifyUser?browserId=fa2e9a...&token=0.XXXX...&thread=0` - copy token param (long string)
5. Use tool `verify_with_token` with browserId and token within 30 seconds, from same IP (run MCP locally)
Then `generate_image` will work via HTTP.
## ๐ก Example Prompts
- "a tiny robot watering a houseplant, cinematic lighting"
- "cyberpunk city at night, neon lights, rain"
- "Studio Ghibli style forest spirit"
- "a cute cat pixel art"
Use with art styles:
- `art_style="Cinematic"` - cinematic shot, dynamic lighting, 75mm, Technicolor
- `art_style="Anime"` - anime art, masterpiece, 4k
- `art_style="Oil Painting"` - oil on canvas, masterpiece
- `art_style="Pixel Art"` - 16-bit, 128px
- See `list_styles` for all 76 styles
## ๐ Files
- `client.py` - Core Perchance API client (both internal and official APIs, handles ad code with curl fallback, userKey, verification, generation with new v hash, download with proxy token, styles, LD_LIBRARY_PATH fix, _find_proxy_download from eeemoon)
- `server.py` - MCP server with 11 tools (FastMCP, includes new verify_with_token and generate_image_embed)
- `styles.json` - 76 art styles parsed from t2i-styles generator
- `cli.py` - CLI for testing
- `run.sh` - Runner with LD_LIBRARY_PATH fix
- `install_deps.sh` - Install chromium deps manually for sandbox
- `mcp_config.json` - Example MCP config with proxy
- `requirements.txt` - Dependencies
## ๐งช Testing
```bash
# Test ad code (should work even in sandbox via curl fallback)
python -c "import asyncio; from client import get_ad_access_code; print(asyncio.run(get_ad_access_code()))"
# Test userKey status
python -c "import asyncio, json; from server import get_user_key_status; print(asyncio.run(get_user_key_status()))"
# Test generation (requires valid userKey, best run locally)
python cli.py "a cute cat" --style Cinematic --resolution 768x768
python cli.py "a cute cat" --official --output cat.jpg
python cli.py "a cute cat" --embed --output cat_embed.jpg
```
## โ ๏ธ Notes (Updated)
- **Sandbox limitation:** In E2B or datacenter IPs, Cloudflare blocks embed and official API with 403, and Turnstile fails. Internal API HTTP generation still works if you provide valid `PERCHANCE_USER_KEY` and `PERCHANCE_PROXY`. Sync Playwright works better than async (async gets EPIPE after 90s).
- **v hash updated:** `CLIENT_VERSION_HASH` changed 2026-09-24 to `9b43eec2e71907610e4e9317b54c208495f6850086e92239a869811d2e2d77ee` - old hash `f4fd5ce3...` now returns `client_update_required`
- **New download endpoint:** `/api/downloadTemporaryImageViaProxy?t=v1....` replaces imageId method, uses quick-tunnel backend, IP-bound, single-use, ~5min expiry, requires cf_clearance cookie - implemented with Playwright fallback
- **Ad code now requires proxy:** Without proxy returns Just a moment challenge, with Webshare proxy `31.59.20.176:6754` and full headers works via curl fallback (httpx still 403 due to TLS fingerprint)
- **userKey IP-bound:** Key `e6ccce59a4f2dd052d3569e7706a35879e030f305b64ba41377c01063139976a` for browserId `fa2e9a2af105eca0e4f15ea2b7b35bc1` is already_verified in residential but not_verified from sandbox/proxy, and generate returns invalid_key via proxy - proves need for fresh Turnstile token <30s
- **Permanent URLs:** Official API returns permanent `user.uploads.dev` URLs that are never challenged - cache them!
- **Rate limits:** `waiting_for_prev_request_to_finish` may occur if you spam - wait 8s and retry (handled automatically)
## ๐ License
MIT - For research/educational use. Perchance's terms apply for image generation. Respect their service.
## ๐ Credits
- Perchance (https://perchance.org) for free unlimited AI image generation
- Reverse-engineered from https://image-generation.perchance.org/embed (58k inline JS with verifyUser, hiddenSafeWait, paintTick, reportImageDownloadFailure)
- Inspired by https://github.com/eeemoon/perchance and PR #9 (Fix Perchance proxy image downloads by NovaUnboundAi)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues