Drive Pinwing from any MCP client — setup, the 37-tool reference, and prompt examples
The MCP server is a thin wrapper over the Pinwing /v1 REST API — it forwards your
bearer token and exposes 37 tools covering the full pipeline: import/upload models,
quote costs, create single and batch renders, poll status, manage overlays/textures/
environments, and download results (streamed or via presigned direct-from-storage URLs).
It runs in two transports:
node. Needed for uploading models from local file paths.Everything the tools can do is scoped by your API key. The key implies the workspace — a personal key operates on your personal library and bills you; a workspace-bound key (editor role or above) operates on that team workspace and bills its owner. There is no workspace parameter on any tool.
pinwing_sk_... — copy it now; it is shown once.streamableHttp-capable client (e.g. Claude Code, Claude Desktop):{
"mcpServers": {
"pinwing": {
"type": "streamableHttp",
"url": "https://mcp.pinwing.ai/",
"headers": {
"Authorization": "Bearer pinwing_sk_..."
}
}
}
}
The hosted server stores no key — each request carries its own
Authorizationheader, so one endpoint serves every user.mcp.pinwing.aiis the canonical endpoint; the originalmcp.dx.glhost remains fully supported — same server, same keys.
Use the stdio transport when you want the agent to upload models straight from local file
paths (upload_model with filePath), which the hosted transport can't reach.
git clone https://github.com/harrimy/dxgl
cd dxgl/api/mcp
npm install
Configure your client to launch it. Claude Desktop — edit
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or
%APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"pinwing": {
"command": "node",
"args": ["/absolute/path/to/dxgl/api/mcp/index.js"],
"env": {
"DXGL_API_KEY": "pinwing_sk_..."
}
}
}
}
| Variable | Required | Default | Description |
|---|---|---|---|
DXGL_API_KEY |
Yes | — | Your pinwing_sk_... bearer token |
DXGL_API_BASE |
No | https://api.dx.gl/v1 |
API base URL |
The stdio server sets allowLocalFiles: true, enabling upload_model to read a GLB from
a path on the same machine. The hosted transport disables this for safety.
Every tool call forwards your key as Authorization: Bearer pinwing_sk_... to the REST API,
which validates it and applies scope and role checks. Mint and revoke keys in Studio →
account menu → API → Keys.
get_account reports which workspace a key is bound to.get_download_url and get_model_file_url return
short-lived links that download directly from storage with no auth header. Until they
expire (default 1 h) they are unauthenticated capability links — treat them like passwords.37 tools, grouped.
Models
| Tool | Description |
|---|---|
ingest_model |
Import a 3D model from a URL (GLB only), optionally starting a render immediately |
upload_model |
Upload a GLB from bytes — local filePath (stdio) or base64; staged presigned-PUT under the hood |
list_models |
List models — tag filter, updatedSince delta polling, cursor pagination |
get_model |
Get model details and render history |
update_model |
Update a model's title, SKU, and/or tags |
archive_model / unarchive_model |
Hide / restore a model in the library |
delete_model |
Soft-delete a model |
check_hash |
Dedup pre-flight — does this SHA-256 already exist? |
get_model_file |
API-streamed download URL for a model's original GLB |
get_model_file_url |
Presigned direct-from-bucket GLB URL — no auth header needed |
Renders
| Tool | Description |
|---|---|
create_render |
Create a video or dataset render (costs credits; idempotent per call) |
create_batch_renders |
Create multiple renders atomically (max 100) |
list_renders |
List render jobs — status/model filters, updatedSince delta polling, cursor pagination |
get_render |
Check render status (poll until done) |
cancel_render |
Cancel a still-queued render and refund its credits |
dismiss_render |
Acknowledge an errored render (no refund) |
delete_render |
Permanently delete a terminal render and its stored assets |
quote |
Estimate credit cost before committing |
Billing
| Tool | Description |
|---|---|
get_account |
Check credit balance (paid / free / total) + token scopes and workspace binding |
list_products |
List purchasable credit packs (name, credits, price) |
get_purchases |
Credit ledger — purchases, render spends, refunds, grants |
Downloads — each returns a URL. API-streamed URLs need your Authorization header; the presigned tools don't.
| Tool | Description |
|---|---|
download_render |
API-streamed video URL for a completed render (quality=web for the light variant) |
get_download_url |
Presigned direct-from-bucket URL for any render asset (video/web/poster/full_poster/thumb/dataset) |
get_poster |
Poster image (PNG) URL (quality=full for full-res) |
get_thumb |
Thumbnail loop video (MP4) URL |
download_bundle |
ZIP of all assets for a completed render |
download_dataset |
ZIP of a dataset render's multi-view images |
get_hls |
HLS streaming status + playback URLs |
For large files, prefer the presigned tools: get_download_url (render assets) and
get_model_file_url (source GLB) return short-lived URLs that download directly from
storage with no Authorization header — ideal for ProRes videos and dataset ZIPs that
run to tens of GB, and for handing a download to another system.
Overlays
| Tool | Description |
|---|---|
upload_overlay |
Upload a PNG/JPEG/WebP as a reusable asset (≤8 MB via MCP) — layer background/foreground/decal/texture |
list_overlays |
List overlay assets (yours + system) — ids feed bgImageId, decals[].imageAssetId, materialEdits.<name>.mapAssetId |
delete_overlay |
Permanently delete an overlay asset you own |
Textures and environments
| Tool | Description |
|---|---|
upload_texture |
Upload a diffuse-replacement texture — id feeds materialEdits.<name>.mapAssetId (≤8 MB via MCP; content-sha deduped) |
upload_env |
Upload a custom HDRI as the pre-encoded gainmap triple — id feeds envId |
list_assets |
List the workspace's texture or environment assets |
delete_asset |
Delete an owned texture/environment asset |
resolve_asset_sha |
Content-sha pre-flight — reuse an existing asset instead of re-uploading |
Render settings are passed to create_render / create_batch_renders / quote. The
schema mirrors the server validator 1:1, so anything the Studio UI can author is reachable.
| JSON value | Display label | Resolution | Credits/sec | Output |
|---|---|---|---|---|
share |
Standard | 960×540 | 1 | MP4 |
standard (server default) |
HD | 1920×1080 | 4 | MP4 |
4k |
4K | 3840×2160 | 16 | MP4 |
pro |
ProRes 4444 | 3840×2160 | 64 | ProRes 4444 .mov (alpha) |
The JSON enum values are frozen for API stability; the display labels were renamed and no longer match one-to-one. Pass the JSON value as
quality.
When
qualityis omitted the server defaults tostandard(HD, 4 credits/sec) — a bare 6-second render bills 24 credits, not 6. Always passqualityexplicitly.
| Tier | Views | Resolution | Credits |
|---|---|---|---|
100x800 |
100 | 800×800 | 40 |
196x1024 |
196 | 1024×1024 | 160 |
400x2048 |
400 | 2048×2048 | 640 |
turntable (default), hero-spin, showcase, zoom-orbit, reveal, keyframes — passed as the effect field.
All optional; see each tool's input schema for exact ranges:
output, aspect, length, easing, sweepAngle, easeOutRatio, amplitude, zoomStart/zoomEnd, scaleFrom, coverage, strictCredits (hard-fail 402 instead of the free-pool fallback — the unattended-run guard)panX, tiltX, rollZ (model yaw → pitch → roll, in that order, −180…180), scaleX/scaleY/scaleZ (per-axis scale; negative mirrors that axis, uniform is a no-op; 0.01–20), translateX/translateY/translateZ (position offset, ±20; positive translateY lifts off the ground), hiddenMeshNames (exact case-sensitive mesh names; unknown names are a silent no-op — verify against the GLB)rotateY (orbit start angle — moves the camera, not the model), zoom (0.5 = far, 2.0 = close)animation, animationSpeed (−10…10, non-zero)camera, keyframes[], startAngle/endAngle, startTilt/endTilt, startZoom/endZoom, startFov/endFov, easeInSeconds/easeOutSeconds, kfSegmentDuration, loopbgColor, bgImageId, bgAlign, bgGradientfilter, vignette, bloomEnabled/bloomStrength, dofEnabled/dofStrength, n8aoEnabled/n8aoIntensity, swayEnabled/swayIntensityshadows, shadowMode, shadowDarkness, shadowSoftness, reflector, reflectorMix, reflectorFade, reflectorDepthBlurenvId, envRotation, envExposure, envMode, envOpacity, envBlur, envScale, envTint, envTintStrengthspotlightIntensity, lightsIntensity, ambientIntensity, glossIntensity, keyLightColor, fillLightColormaterialEdits (per-material overrides keyed by material name), decals[]"Import the GLB at https://example.com/chair.glb and render a 4K turntable video with a white background."
The agent calls ingest_model with the URL, create_render with { quality: "4k", bgColor: "#ffffff" }, polls get_render until done, and returns the URL from download_render.
"How much would it cost to render 50 models at 4K?"
The agent calls quote with 50 entries of { quality: "4k" } and reports 4,800 credits (fifty 6-second 4K turntables at 16 credits/second).
"Render the chair facing the label, leaning slightly toward the camera, with the packaging hidden."
The agent calls create_render with:
{ "quality": "share", "panX": 35, "tiltX": -12, "zoom": 1.3, "hiddenMeshNames": ["Packaging"] }
panX/tiltX/rollZ rotate the model (yaw first, then pitch, then roll); rotateY
instead offsets where the camera's orbit starts. hiddenMeshNames needs exact
case-sensitive mesh names from the GLB — unknown names silently hide nothing.
The hosted mcp.pinwing.ai endpoint is the same code you can run yourself. Serve multiple users
without local installs by running the HTTP transport:
cd api/mcp
node http.js
| Variable | Required | Default | Description |
|---|---|---|---|
DXGL_API_BASE |
No | https://api.dx.gl/v1 |
API base URL |
PORT |
No | 3300 |
HTTP server port |
DXGL_API_KEY is not set on the server — each client sends its own key on every request.
The endpoint is Streamable HTTP (stateless: a fresh MCP session per request, bound to the
caller's key). A legacy SSE transport is also served at GET /sse + POST /messages for
older clients. Health check: GET /health.
Authorization: Bearer pinwing_sk_... (hosted) or that DXGL_API_KEY is set (stdio).quality, so it defaulted to standard (HD, 4 credits/sec). Pass quality explicitly; use quote first to confirm.hiddenMeshNames hid nothing — mesh names are case-sensitive and must match the GLB exactly; an unknown name is a silent no-op. Inspect the model to get exact names.get_download_url, get_model_file_url) instead of the API-streamed ones for multi-GB assets.filePath uploads only work on the stdio transport (allowLocalFiles). Use base64, or run locally.