A FastMCP 4 server exposing PicX Studio image and video generation to any MCP client over sessionless Streamable HTTP.
Hosted endpoint: https://mcp.picxstudio.com/mcp
FastMCP 4's theme is "stateless transport without stateless application code." The protocol revision it targets — 2026-07-28 — removes session affinity entirely. Any replica behind an ordinary load balancer can serve any request. No sticky sessions, no cookie forwarding, no shared in-memory state between requests.
This is not optional for us: MCP clients (Cursor, Claude Code) use fetch() internally and do not forward Set-Cookie headers, so sticky-session load balancing cannot work regardless of LB configuration. FastMCP 4's stateless_http=True mode is the only viable path to horizontal scaling.
FastMCP 4 also negotiates both protocol eras (legacy SSE and modern Streamable HTTP) from a single deployment, so older clients are not stranded.
| # | Tool | Status | Notes |
|---|---|---|---|
| 1 | picx_generate_image |
✅ Working | Inline, 5–20s |
| 2 | picx_edit_image |
✅ Working | Requires upload first (API rejects data URIs) |
| 3 | picx_generate_video |
✅ Working | Background task (task=True); all seven modes (text, image, reference, frames, extend, lipsync, edit) |
| 4 | picx_get_generation |
✅ Working | Poll a generation by ID |
| 5 | picx_get_generation_events |
✅ Working | Bounded read of the SSE progress stream |
| 6 | picx_get_generation_deliveries |
✅ Working | Webhook deliveries for one generation |
| 7 | picx_upload_asset |
✅ Working | Returns a CDN URL usable by edit tools |
| 8 | picx_list_assets |
✅ Working | |
| 9 | picx_delete_asset |
✅ Working | |
| 10 | picx_list_models |
✅ Working | Cached (5 min) |
| 11 | picx_search_templates |
✅ Working | 50K+ catalogue; cached |
| 12 | picx_get_template |
✅ Working | |
| 13 | picx_get_webhook_deliveries |
✅ Working | List a webhook endpoint's delivery history |
| 14 | picx_redeliver_webhook |
✅ Working | Replays a stored delivery — real outbound POST |
| 15 | picx_get_account |
✅ Working | |
| 16 | picx_get_usage |
✅ Working | |
| 17 | picx_get_tier |
✅ Working | |
| 18 | picx_list_generations |
🔴 Blocked | GET /v1/generations returns 404 — endpoint not shipped yet |
All seven modes are exposed on picx_generate_video. The tool validates each mode's required fields client-side and returns a clear message instead of a raw 422:
| Mode | prompt |
Also requires |
|---|---|---|
text |
required | — |
image |
required | image_url |
reference |
required | reference_urls (1–10) |
frames |
required | start_frame_url (end_frame_url optional) |
extend |
required | source_video_url |
lipsync |
none | source_video_url and audio_url |
edit |
required | source_video_url and image_url |
lipsync is the only mode where prompt is optional — the audio track drives the output.
picx_get_webhook_deliveries and picx_redeliver_webhook are the API-key-authorized subset of the webhook surface — reading a webhook's delivery history and replaying a stored delivery. Endpoint CRUD (create/list/update/delete/test) is a session-authenticated /api operation, not reachable by a pxsk_ key, so it is intentionally not exposed here. picx_redeliver_webhook re-fires the same signed payload as a real outbound POST to the customer's endpoint.
picx_list_generations: Implemented and ready to activate, but blocked on the backend shippingGET /v1/generations. It is 404-guarded, so it returns an empty list with a notice until the endpoint ships.- Tier limits: Per-tier rate limit and daily cap visibility may be unavailable until the account endpoint exposes them.
- OAuth: Not yet wired (Phase 5). API-key auth works today.
# Clone and install
git clone https://github.com/Type-Think-AI/picx-mcp.git
cd picx-mcp
uv sync
# Configure
cp .env.example .env
# Edit .env — set PICX_API_KEY to your key from https://ai.picxstudio.com/api
# Run
python -m picx_mcpThe server starts on http://localhost:8000. The MCP endpoint is at /mcp, health at /health.
{
"mcpServers": {
"picx": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer pxsk_your_api_key_here"
}
}
}
}{
"mcpServers": {
"picx": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer ${PICX_API_KEY}"
}
}
}
}{
"mcpServers": {
"picx": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer ${PICX_API_KEY}"
}
}
}
}{
"mcp": {
"servers": {
"picx": {
"type": "http",
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer ${PICX_API_KEY}"
}
}
}
}
}Replace localhost:8000 with mcp.picxstudio.com once the hosted service is live.
Two auth planes, one enforcement point:
API Key (pxsk_…) |
OAuth (Phase 5, not yet available) | |
|---|---|---|
| Who | Developers, CI, scripted agents, self-hosters | Everyday users on hosted clients |
| Obtained from | ai.picxstudio.com/api | One-click consent screen |
| How it works | Key forwarded per-request — the server stores no credential | OAuth resolves to a session key |
| Revocation | Delete the key | Revoke the grant — real keys untouched |
Both paths converge on the same /v1 enforcement: scopes, rate limits, daily credit cap, request logging. There is no weaker second path.
The MCP server never holds a credential. It forwards the caller's API key (or resolved session key) to /v1. A key it never stores is a key it cannot leak.
MCP Client ──▶ PicX MCP Server ──▶ api.picxstudio.com/v1 ──▶ Provider + Storage
(this repo) (owns everything below)
This server is a translation layer. It converts MCP tool calls into /v1 API calls and translates results back as resource links. It intentionally does NOT:
- Call any model provider directly.
/v1owns the provider integration. - Touch money.
/v1owns credit deduction, pricing, discounts, idempotency, and refund-on-provider-failure. - Store media. Results are permanent CDN URLs; nothing is cached or proxied.
- Maintain session state.
stateless_http=Truemeans each request is self-contained.
Why not call providers directly? /v1 already performs: auth → rate limit → daily cap → scope check → price from config → apply discount → idempotency check → deduct credits → call provider → refund on failure → write request log. Reimplementing any of that here would eventually diverge, and a divergence in money logic is a billing bug — silent, and permanently trust-eroding.
The whole thesis of choosing FastMCP 4 is that no session affinity is required. To prove it locally:
docker compose up --scale app=2This starts two server replicas behind a round-robin proxy plus a Valkey instance. The test that validates the architecture:
- Start an interactive tool call on replica A (triggers
InputRequiredResult) - Resume the interaction — the request lands on replica B
- It succeeds, because
REQUEST_STATE_KEYis shared
If REQUEST_STATE_KEY is not set (or differs between replicas), interactive rounds will fail with a state validation error. This is intentional — it makes misconfiguration loud rather than subtly wrong.
| Variable | Required | Description |
|---|---|---|
PICX_API_BASE |
No (default: https://api.picxstudio.com/v1) |
PicX API root. Must end in /v1. |
REQUEST_STATE_KEY |
Yes (multi-replica) | ≥32 bytes, byte-identical across all replicas. Protects interactive round state. |
REDIS_URL |
Yes | Valkey/Redis URL. Backs tasks, response cache, OAuth storage. |
SESSION_CREDIT_CEILING |
No (default: 2000) | Max credits one MCP session may spend, independent of the account's daily cap. |
CONFIRM_CREDIT_THRESHOLD |
No (default: 200) | Above this, the tool returns input_required to confirm before spending. |
JWT_SIGNING_KEY |
Phase 5 | Explicit JWT key. Without it, tokens die when the OAuth client secret rotates. |
STORAGE_ENCRYPTION_KEY |
Phase 5 | Fernet key. Without it, upstream OAuth tokens are stored in plaintext. |
GOOGLE_CLIENT_ID |
Phase 5 | Google OAuth client ID. |
GOOGLE_CLIENT_SECRET |
Phase 5 | Google OAuth client secret. |
PICX_MCP_BASE_URL |
Phase 5 (default: https://mcp.picxstudio.com) |
Public URL for OAuth callbacks. |
- Every generation costs credits. This server does not bypass pricing — that is the point.
- Per-session ceiling (default 2000 credits) bounds a prompt-injected credit drain. This is separate from the account's 13,000/day cap.
- Confirmation prompt above the threshold (default 200 credits) before spending.
- No offline/local generation. All generation hits the PicX API over the network.
- Video is async. Even with
task=Truehiding the polling, generation takes minutes — an agent must wait. - Rate limits are the API's, not this server's: 60 req/min, 10K req/day by default. The MCP server adds no additional limit.
- The server is in beta. FastMCP 4 is
4.0.0b3. Expect rough edges.