Skip to main content
Alongside the tools that mirror the Fanvue API, the MCP server includes custom tools, each purpose-built around a complete job and easy to spot by its custom__ prefix. This page is the reference for every custom tool: the arguments it accepts and the fields it returns. Each tool returns a single JSON object as the text content of its result.
The custom toolset will grow over time as more jobs get their own purpose-built tool.

The image post flow

Publishing an image post takes three steps — two tool calls with an image upload in between:
1

Start the upload

Call custom__start-image-upload. It returns a mediaUuid, an uploadId and a short-lived uploadUrl for one image.
2

Upload the image

HTTP PUT the raw image bytes (not base64) to uploadUrl, with no Authorization header. Keep the ETag response header — it confirms the upload in the next step.
3

Create the post

Call custom__create-image-post with image: { mediaUuid, uploadId, etag } and the post fields. The post is created once the image is ready to display.
Run the flow once per image: a paid post with its own teaser image needs two uploads — one for the locked image, one for the free preview.

custom__start-image-upload

Starts an image post (step 1 of 3) by reserving an upload slot for one image and returning everything the upload step needs. Takes no arguments. Requires the write:media scope.

Result

mediaUuid
string
required
UUID of the new media item. Pass it to custom__create-image-post, and use it to reference the media in your library afterwards.
uploadId
string
required
Opaque identifier of this upload. Pass it to custom__create-image-post exactly as received — don’t parse or modify it.
uploadUrl
string
required
Short-lived URL to PUT the raw image bytes to. If it expires before the upload happens, call custom__start-image-upload again for a fresh one — an unused slot is harmless.
instructions
string
required
A reminder of the remaining steps, embedded in the result so an assistant can complete the flow without consulting this page.
Example result
{
  "mediaUuid": "b8c47a91-3f2d-4a55-b7e8-1c9d2e4f5a6b",
  "uploadId": "dXBsb2FkLXNlc3Npb24tZXhhbXBsZQ",
  "uploadUrl": "https://storage.example.com/uploads/b8c47a91?signature=…",
  "instructions": "PUT the raw image bytes (not base64) to uploadUrl with no Authorization header, and keep the ETag response header. …"
}

custom__create-image-post

Publishes a post carrying the uploaded image (step 3 of 3). It accepts the same post fields as create a new post — text, audience, pricing, scheduling, expiry, collections — plus the identifiers of the uploaded image. The post is only created once the image has finished processing and is ready to display, so a post never reaches the feed with broken media. Requires the write:media, read:media and write:post scopes.

Arguments

image
object
required
The uploaded image to attach to the post.
audience
string
required
Who can view the post: subscribers or followers-and-subscribers.
text
string
Text content of the post, up to 5,000 characters.
price
number
Price in cents for a pay-to-view post, minimum 300 ($3.00). Omit for a free post.
publishAt
string
Future date/time to publish the post, in ISO 8601 format with a timezone (for example 2026-07-01T18:00:00Z). When set, the post is scheduled instead of published immediately.
expiresAt
string
Date/time when the post expires, in ISO 8601 format with a timezone.
previewImage
object
Free teaser image shown to non-subscribers before they unlock a paid post. Upload it through its own custom__start-image-upload call; the fields are the same as image.
collectionUuids
string[]
UUIDs of the collections to add the post to.

Result

The created post — the same shape as the create a new post response.
uuid
string
required
Unique identifier of the created post.
createdAt
string
required
Date/time when the post was created (ISO 8601 format).
text
string | null
required
Text content of the post.
price
number | null
required
Price in cents for paid posts.
mediaPreviewUuid
string | null
required
UUID of the free preview media, when a previewImage was supplied.
audience
string
required
Audience the post is visible to.
publishAt
string | null
required
Future date/time when the post will be published, for scheduled posts.
publishedAt
string | null
required
Date/time when the post was published. null for scheduled posts that have not gone live yet.
expiresAt
string | null
required
Date/time when the post expires.
Example result
{
  "uuid": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2026-06-12T18:00:00.000Z",
  "text": "Tonight's drop 🔥",
  "price": 999,
  "mediaPreviewUuid": "f1e2d3c4-b5a6-4789-8123-456789abcdef",
  "audience": "subscribers",
  "publishAt": null,
  "publishedAt": "2026-06-12T18:00:00.000Z",
  "expiresAt": null
}

If a call fails

  • A failed call never publishes a broken post: if the image could not be processed, no post is created and the error names the media that failed.
  • An image that was already uploaded is not lost — the error lists the media UUIDs that remain in your media library, ready to attach to a post.
  • The call is safe to retry with the same arguments; a retry picks up where the failed call left off rather than uploading or posting twice.
  • If the error reports a rejected upload, check that the bytes were PUT to the uploadUrl and that etag is the ETag header from that PUT. An expired uploadUrl means starting over with custom__start-image-upload.