Skip to main content
Glama
Yuser00123

Perchance Stable Diffusion MCP Server

by Yuser00123

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.

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.

Related MCP server: cf-image-gen MCP Server

๐Ÿ”ฌ Research Summary - How Perchance Works (Updated 2026-09-24)

Two APIs Discovered

Endpoint: https://perchance.org/perchance-ai-api Docs: https://perchance.org/perchance-ai-api Method: iframe + postMessage, no keys, permanent URLs

// 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

  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

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.):

{
  "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:

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:

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

# 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

Related MCP Connectors

Related MCP Servers