Skip to main content
NO SUBSCRIPTION NEEDED

Credits never expire. Pay once for the assets you need. Failed generations cost nothing.Failed = refunded

See prices

spawnpop MCP server docs

How to connect an AI agent to spawnpop: the endpoint, authentication, every tool with its inputs and outputs, how money is handled, the errors you will see, and how to get the finished files into Roblox Studio. Everything here is taken from the running server.

What it is

spawnpop makes Roblox game assets: a GUI set, icons, a thumbnail, 3D props and music. The MCP server lets an agent do that on a person's behalf. It plans a kit from one sentence, tells the agent the credit price, makes cheap drafts, generates the finals the person approved, and hands back signed download links plus the next calls for Roblox Studio. For the product side, see the connect page and pricing.

Endpoint facts
ItemValue
Endpointhttps://spawnpop.com/mcp
TransportStreamable HTTP, JSON replies. One JSON-RPC 2.0 message per POST.
SessionsNone. The server is stateless, so there is no session id to keep. There are no server-sent events, and batch arrays are rejected.
Protocol versions2025-11-25, 2025-06-18, 2025-03-26
Methodsinitialize, ping, tools/list, tools/call, and notifications (answered with 202).
AuthenticationHeader Authorization: Bearer <key>
Request sizeUp to 64 KB per request.
Without a keyinitialize, ping, tools/list and plan_kit only. Everything else returns HTTP 401.

Get a key

  1. Open spawnpop.com/mcp and choose Connect. A key is created after a first credit purchase, because a key is how an agent spends credits. Buying as a guest works; you do not need an account.
  2. The key starts with sp_live and is shown once. Copy it straight into an environment variable named SPAWNPOP_API_KEY.
  3. Choose a read key (can call get_status, get_files and list_projects) or a read and generate key (can also plan, draft, approve and spend credits). Pick read-only for dashboards and monitoring agents.
  4. An account holds up to 3 keys. Revoke any of them in Settings, API keys; a revoked key stops working at once.

Never commit a key. Put it in an environment variable or a secret store and reference it from the config, as in the snippets below. Keys are stored hashed, so a lost key cannot be shown again; create a new one.

Set up your agent

Set SPAWNPOP_API_KEY in your shell first, then add the server. Each snippet reads the key from the environment, so the key is not written into a file.

Claude Code
claude mcp add --transport http spawnpop https://spawnpop.com/mcp --header "Authorization: Bearer $SPAWNPOP_API_KEY"
Codex CLI
# ~/.codex/config.toml
[mcp_servers.spawnpop]
url = "https://spawnpop.com/mcp"
bearer_token_env_var = "SPAWNPOP_API_KEY"
Cursor
{
  "mcpServers": {
    "spawnpop": {
      "url": "https://spawnpop.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SPAWNPOP_API_KEY}"
      }
    }
  }
}
Any client: raw JSON-RPC with curl
export SPAWNPOP_API_KEY=<your key>   # created on /mcp

# 1. initialize
curl -s https://spawnpop.com/mcp \
  -H "Authorization: Bearer $SPAWNPOP_API_KEY" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-agent","version":"0.1"}}}'

# 2. list the tools
curl -s https://spawnpop.com/mcp \
  -H "Authorization: Bearer $SPAWNPOP_API_KEY" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. call one (plan_kit also works without the Authorization header)
curl -s https://spawnpop.com/mcp \
  -H "Authorization: Bearer $SPAWNPOP_API_KEY" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"plan_kit","arguments":{"description":"a candy pet collecting simulator","genre":"simulator"}}}'

Claude Desktop is not supported yet because it requires OAuth sign-in, which this server does not offer. Any client that can send a bearer header to a Streamable HTTP server works. After connecting, ask the agent to run tools/list; it should see the seven tools below.

Tool reference

All tools return both readable text and a structuredContent object. The tables list the fields of that object. The server also sends short instructions in the initialize reply that summarise the workflow, so a well-behaved client already shows them to the model.

plan_kit

Turns a one-sentence game idea into a priced list of assets (GUI, icons, thumbnail, 3D props, music). With an API key the plan is saved as a project you can draft and generate from. Without a key you get the same plan and price plus a link for the human to connect.

Key access: none needed to price; generate to save a project. Credits: No charge.

plan_kit input
InputTypeRequiredMeaning
descriptionstring, 3 to 500 charactersyesThe game idea in plain words.
genreone of simulator, tycoon, tower_defense, obby, rng, horror, roleplay, fightingnoSteers the plan. Left out, the planner picks one.
stylestring, up to 300 charactersnoArt direction, for example "chunky pastel cartoon". It is carried into every later image.
plan_kit output
Output fieldMeaning
projectIdThe project to use in later calls. null when you called without a key.
genreThe genre the plan was made for.
themeWhat the game is about, extracted from your idea: palette, materials, motifs, logoText, currency, shopItems (name and price), hudLabels. Every later image uses it, so the kit matches. May be absent on older projects.
itemsOne row per asset: id, name, assetType, count, credits (price of all copies), generatable and draftable flags. Items with generatable false are listed but not made. A gui_preview item (the themed preview sheet: an optional paid add-on, optional true, addOnFor the GUI kit id, generate only, draftable false) appears when features says it is on.
features, stylesOnly for accounts with the quality features: features says which are on, styles.gameIcon lists the game icon looks you can pass to generate as options.iconStyle.
totalCreditsPrice of generating every generatable item once.
balance, canAffordWith a key: the current balance and whether it covers totalCredits.
payUrlWith a key and a short balance: the link to show the human.
connectUrl, messageForUserWithout a key: where the human connects, and a sentence you can show them as is.

Errors you can get: forbidden_scope (read-only key), invalid (description refused or malformed), rate_limited (keyless callers).

  • Keyless calls are limited to 5 plans an hour per network. A key is not needed to see a price.
  • Descriptions that name a known brand or character, or hit the content filter, are refused with invalid before anything is charged.

draft

Makes low-cost preview images with an AI fit check, so finals are only bought for what works. Drafting an item that already has a draft counts as a redraft at the same price.

Key access: generate. Credits: 10 credits per draft image.

draft input
InputTypeRequiredMeaning
projectIdstringyesThe projectId that plan_kit returned.
itemIdsarray of 1 to 40 item idsyesItems from the plan. Items with draftable false (for example music) have no draft step and are refused with invalid.
variants1 or 4, default 1noDrafts per copy. 4 costs four times as much.
confirmCreditsinteger, 0 or moreyesThe exact price you showed the user. If the real price differs, nothing is charged and price_changed comes back.
requestIdstring, 8 to 64 characters of letters, digits, _ or -yesYour idempotency key. Use a new one for each new purchase and the same one to retry that purchase.
draft output
Output fieldMeaning
jobIdsOne id per queued draft.
creditsChargedWhat this call took. A replay with the same requestId takes nothing.
balanceBalance after the call.

Errors you can get: insufficient_credits, price_changed, invalid, not_found, rate_limited, provider.

  • Price = number of copies drafted x variants x the per-draft price. Use that to compute confirmCredits.

approve

Marks one draft per item copy as the reference its final is made from. Use the outputId that get_status shows for the draft.

Key access: generate. Credits: No charge.

approve input
InputTypeRequiredMeaning
projectIdstringyesThe projectId that plan_kit returned.
approvalsarray of 1 to 40 objectsyesEach object: itemId (string), copy (integer 0 to 49, default 0), outputId (string, from get_status drafts).
approve output
Output fieldMeaning
oktrue when all approvals were stored.
approvedHow many approvals were stored.

Errors you can get: invalid, not_found.

  • A draft whose fit check says misfit can still be approved; unjudged never blocks approval.

generate

Starts the final jobs and charges for them. Pass "approved" as itemIds to make every item whose copies all have an approved draft and no final yet. The optional preview sheet is never part of "approved" unless options.includePreview is true.

Key access: generate. Credits: Per item, see the price table.

generate input
InputTypeRequiredMeaning
projectIdstringyesThe projectId that plan_kit returned.
itemIdsarray of 1 to 40 item ids, or the string "approved"yesWhat to make. Ids that are not generatable come back in skipped and cost nothing.
confirmCreditsinteger, 0 or moreyesThe exact price you showed the user. If the real price differs, nothing is charged and price_changed comes back.
requestIdstring, 8 to 64 characters of letters, digits, _ or -yesYour idempotency key. Use a new one for each new purchase and the same one to retry that purchase.
optionsobject, optional: { iconStyle: "avatar_3d" | "cartoon", includePreview: boolean }noiconStyle picks the game icon look, only when plan_kit returned styles.gameIcon for the project; it is stored only after the price and balance checks pass. includePreview adds the optional themed preview sheet (priced in plan_kit) to "approved", together with its GUI kit.
generate output
Output fieldMeaning
jobIdsOne id per copy that was started.
creditsChargedWhat this call actually took from the balance.
skippedItem ids that are not generatable.
balanceBalance after the call.
replayedtrue when this requestId had already started these jobs; nothing was charged again.

Errors you can get: insufficient_credits, price_changed, invalid, not_found, rate_limited, provider.

  • To retry items that failed, call generate again with a new requestId. Failed jobs are refunded automatically.
  • The same requestId always returns the same jobs, so a lost response is safe to retry.

get_status

Reports every plan item, every draft with its AI fit verdict, the approvals and the balance. waitSeconds holds the call open and returns as soon as anything changes, so the agent does not need to poll fast.

Key access: read. Credits: No charge.

get_status input
InputTypeRequiredMeaning
projectIdstringyesThe projectId that plan_kit returned.
waitSecondsinteger 0 to 25, default 0noMaximum seconds to wait for a change. The call can return sooner. Use 25 while jobs run.
get_status output
Output fieldMeaning
itemsOne row per item: itemId, name, assetType, status, copies, note. status is not_started, drafting, drafted, queued, processing, succeeded, partial or failed.
draftsitemId, copy, outputId, status, verdict, reasons, suggestedPrompt and a signed url for each draft. verdict is fit, misfit, unjudged or pending.
approvedThe itemId and copy pairs that have an approved draft.
summaryCounts of succeeded, failed and running jobs, and mixed (some succeeded, some failed).
balanceCurrent credit balance.
donetrue when nothing is running and at least one item succeeded.

Errors you can get: not_found, forbidden_scope, unauthorized.

  • This is the only tool that blocks. Keep the loop going until done is true or summary.running is 0.

get_files

Lists finished files with download URLs that stay valid for 60 minutes, plus a ready next-step block for Roblox Studio and Open Cloud.

Key access: read. Credits: No charge.

get_files input
InputTypeRequiredMeaning
projectIdstringyesThe projectId that plan_kit returned.
get_files output
Output fieldMeaning
filesitemId, assetType, name, kind (element, image, audio, audio_loop, glb, obj, fbx or kit_zip), url, bytes, width, height, triangles, and bboxStuds for glb models.
roblox.uploadImagestool "upload_image" and the image URLs to pass to it.
roblox.luauA Luau script that builds a ScreenGui from image asset ids. Ids are placeholders for you to fill.
roblox.modelsFor each glb: its file name and an Open Cloud curl template.
roblox.audioA note for audio files, or null.
roblox.safetyReminder to keep any Roblox key in your own shell.

Errors you can get: not_found, forbidden_scope, unauthorized.

  • URLs are signed for the key owner. Download them in the same session; call get_files again for fresh ones.

list_projects

Your recent projects, newest first. Use it to find a projectId after a restart.

Key access: read. Credits: No charge.

list_projects input
InputTypeRequiredMeaning
limitinteger 1 to 50, default 20noHow many projects to return.
list_projects output
Output fieldMeaning
projectsprojectId, title, genre and createdAt (ISO 8601) for each.

Errors you can get: forbidden_scope, unauthorized.

The agent workflow

The recommended order keeps the human informed and the spend small:

  1. plan_kit with the game idea. Show the human the items and totalCredits.
  2. draft the items for 10 credits per draft image. Each draft comes with an AI fit verdict.
  3. get_status with waitSeconds 25 until the drafts are ready. Show the human the draft URLs.
  4. approve the drafts that fit. No charge.
  5. generate with itemIds "approved", the confirmed price and a new requestId.
  6. get_status again until done is true, then get_files.
  7. Import into Roblox Studio (see below).

Worked example

A person asks for a candy pet simulator. The totals below are computed from the live price list, so they stay correct if prices change. Responses are trimmed to the interesting fields. Ids such as proj_8f3k2 are examples.

1. plan_kit (arguments)
{
  "name": "plan_kit",
  "arguments": {
    "description": "a candy pet collecting simulator",
    "genre": "simulator",
    "style": "chunky pastel cartoon"
  }
}
plan_kit result: the balance does not cover the kit yet
{
  "projectId": "proj_8f3k2",
  "genre": "simulator",
  "theme": {
    "palette": [
      "#ff8fb1",
      "#ffd36e",
      "#7ad7c1"
    ],
    "currency": "Candy",
    "motifs": [
      "lollipops",
      "striped awnings"
    ]
  },
  "items": [
    {
      "id": "gui-kit",
      "name": "Candy GUI set",
      "assetType": "gui_set",
      "count": 1,
      "credits": 120,
      "generatable": true,
      "draftable": true
    },
    {
      "id": "game-icon",
      "name": "Game icon",
      "assetType": "game_icon",
      "count": 1,
      "credits": 40,
      "generatable": true,
      "draftable": true
    },
    {
      "id": "pets",
      "name": "Pet props",
      "assetType": "prop_3d",
      "count": 2,
      "credits": 160,
      "generatable": true,
      "draftable": true
    }
  ],
  "totalCredits": 320,
  "balance": 150,
  "canAfford": false,
  "payUrl": "https://spawnpop.com/mcp/pay?t=<token>"
}

The kit costs 320 credits and the plan shows that the balance is lower. Drafts are cheap, so the agent drafts first.

2. draft (arguments)
{
  "name": "draft",
  "arguments": {
    "projectId": "proj_8f3k2",
    "itemIds": [
      "gui-kit",
      "game-icon",
      "pets"
    ],
    "variants": 1,
    "confirmCredits": 40,
    "requestId": "candy-drafts-001"
  }
}
draft result
{
  "jobIds": [
    "job_a1",
    "job_a2",
    "job_a3",
    "job_a4"
  ],
  "creditsCharged": 40,
  "balance": 110
}
3. get_status result after waiting
{
  "items": [
    {
      "itemId": "gui-kit",
      "name": "Candy GUI set",
      "assetType": "gui_set",
      "status": "drafted",
      "copies": 1,
      "note": "Draft ready; approve it, then generate"
    }
  ],
  "drafts": [
    {
      "itemId": "gui-kit",
      "copy": 0,
      "outputId": "out_77",
      "status": "succeeded",
      "verdict": "fit",
      "reasons": [],
      "url": "https://spawnpop.com/api/assets/dl/out_77?u=...&exp=...&sig=..."
    }
  ],
  "summary": {
    "succeeded": 0,
    "failed": 0,
    "running": 0,
    "mixed": false
  },
  "balance": 110,
  "done": false
}
4. approve (arguments)
{
  "name": "approve",
  "arguments": {
    "projectId": "proj_8f3k2",
    "approvals": [
      {
        "itemId": "gui-kit",
        "copy": 0,
        "outputId": "out_77"
      }
    ]
  }
}
5. generate (arguments)
{
  "name": "generate",
  "arguments": {
    "projectId": "proj_8f3k2",
    "itemIds": "approved",
    "confirmCredits": 120,
    "requestId": "candy-final-001"
  }
}

The balance is a little short for the GUI set, so the call is refused before anything is charged:

generate result: insufficient_credits
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "Not enough credits: needs 120, balance 110. Show the user payUrl; after payment retry this exact call with the same requestId."
    }
  ],
  "structuredContent": {
    "code": "insufficient_credits",
    "message": "Not enough credits: needs 120, balance 110. Show the user payUrl; after payment retry this exact call with the same requestId.",
    "needed": 120,
    "balance": 110,
    "shortfall": 10,
    "payUrl": "https://spawnpop.com/mcp/pay?t=<token>",
    "messageForUser": "This needs 120 credits and the account has 110. Open https://spawnpop.com/mcp/pay?t=<token> to add credits (no sign-up needed), then tell me to continue."
  }
}

The agent shows messageForUser to the human, who opens the link and pays. The agent then repeats step 5 unchanged, with the same requestId:

generate result after payment
{
  "jobIds": [
    "job_b1"
  ],
  "creditsCharged": 120,
  "skipped": [],
  "balance": 310,
  "replayed": false
}
6. get_files result (trimmed)
{
  "files": [
    {
      "itemId": "gui-kit",
      "assetType": "gui_set",
      "name": "buy-button.png",
      "kind": "element",
      "url": "https://spawnpop.com/api/assets/dl/out_91?u=...&exp=...&sig=...",
      "bytes": 48211,
      "width": 512,
      "height": 256
    }
  ],
  "roblox": {
    "uploadImages": {
      "tool": "upload_image",
      "urls": [
        "https://spawnpop.com/api/assets/dl/out_91?..."
      ]
    },
    "luau": "-- builds a ScreenGui from the ids you fill in",
    "models": [],
    "audio": null,
    "safety": "Set ROBLOX_API_KEY in your own shell."
  }
}

Credits and payments for agents

Credits are bought in advance, never expire, and failed generations are refunded to the balance. The rules an agent must follow:

  • confirmCredits. Every spending call (draft, generate) must carry the exact price. Compute it from the credits field of the plan items, and show it to the human first.
  • requestId. A string of 8 to 64 letters, digits, _ or -. The same requestId returns the same jobs and charges nothing the second time, so retry freely after a timeout or after the human has paid. Use a new one for each new purchase, including a retry of items that failed.
  • insufficient_credits. Nothing is charged. The result carries needed, balance, shortfall, payUrl and messageForUser. Give the message to the human, wait for them to say they have paid, then repeat the exact same call. The link works without an account and expires after 24 hours.
  • price_changed. If the price moved between your plan and your call, nothing is charged and totalCredits holds the new price. Show it to the human and call again with the new value.
  • Refunds. A job that fails is refunded automatically; get_status shows it as failed. See the refund policy.

Prices

Credits per generated item
Asset typeCredits
GUI set (a matching set of panels and buttons)120 credits
Single GUI element80 credits
Icon pack100 credits
Game thumbnail80 credits
Game icon40 credits
3D prop (GLB model), per copy80 credits
Music track60 credits
Clothing shirt80 credits
Clothing pants80 credits
Draft image (any item)10 credits

The tool results always carry the current price, so read it from there instead of copying this table into your agent. The pricing page lists the credit packs.

Rate limits

Rate limits
LimitValueWhat you get when you hit it
All calls with one key30 a minuteHTTP 429 with a Retry-After header and retryAfterSeconds in the body.
Spending calls with one key (plan_kit, draft, generate)20 an hourA tool result with code rate_limited and retryAfterSeconds.
Keyless plan_kit from one network5 an hourA tool result with code rate_limited and retryAfterSeconds.

Limits use fixed windows, and retryAfterSeconds is the time until the window ends. When a provider is briefly unavailable the result has code provider, refunded true and a retryAfterSeconds; wait that long and retry.

Errors

Tool failures come back as a normal result with isError true and a structuredContent.code. Authentication and transport problems are HTTP statuses. Malformed JSON-RPC gets a JSON-RPC error object.

Error codes
CodeWhereMeaningWhat the agent should do
unauthorizedHTTP 401The key is missing, wrong or revoked. The body is identical in all three cases.Stop and ask the human to check the key in Settings. Do not retry.
forbidden_scopetool resultThe key is read-only and the tool needs generate.Tell the human to create a key with generate access.
invalidtool resultAny tool can return this: a field failed validation, an id is not in the project, or the description was refused. The message names the field.Fix the input from the message and call again. Nothing was charged.
not_foundtool resultThe project does not belong to this key owner or does not exist.Call list_projects for a valid projectId.
insufficient_creditstool resultThe balance is below the price. Fields: needed, balance, shortfall, payUrl, messageForUser.Show messageForUser (it contains payUrl) to the human and wait. After they pay, repeat the exact same call with the same requestId.
price_changedtool resultconfirmCredits no longer matches the price. Field: totalCredits. Nothing was charged.Show the new price to the human, then call again with confirmCredits set to totalCredits.
conflicttool resultThe request conflicts with the project state.Call get_status, then decide again.
rate_limitedtool result or HTTP 429Too many calls. Field: retryAfterSeconds. Over the per-key call budget it is HTTP 429 with a Retry-After header; for the spending budget it is the seconds until the hourly window ends; for keyless plan_kit it is always 3600; for a limit hit inside a job it is 60.Wait retryAfterSeconds, then retry the same call.
providertool resultThe work could not run right now. Field refunded is true, so nothing is kept. When the service is temporarily unavailable (for example while prices are being updated) retryAfterSeconds is 60.Wait retryAfterSeconds if given (otherwise a few seconds), then retry. Reuse the requestId if no job started; call get_status if unsure.
servertool resultAn error on our side.Call get_status to see whether work started, then retry with the same requestId.
parse_errorHTTP 400The body is not valid JSON. The reply is a JSON-RPC error object with code -32700.Fix the JSON and send again.
method_not_allowedHTTP 405The endpoint only accepts POST.Send JSON-RPC messages with POST.
too_largeHTTP 413The request body is over 64 KB.Send a smaller request.

Using the files in Roblox Studio

get_files returns a roblox block so the agent does not have to guess. In Roblox Studio, open Assistant, then Manage MCP Servers, and enable Studio as an MCP server; add it to the same agent that has spawnpop.

  1. Images. Pass roblox.uploadImages.urls to Studio's upload_image tool. The URLs are valid for 60 minutes. Each upload gives an asset id.
  2. Place them. Fill the ids into the Luau script from roblox.luau and run it with execute_luau in Edit mode. It builds a ScreenGui in StarterGui:
Luau (from get_files, ids filled in)
-- Run with execute_luau in Edit mode, after filling the ids from upload_image.
local ids = {
  ["buy-button.png"] = "rbxassetid://REPLACE_WITH_ID_FOR_BUY_BUTTON_PNG",
}
local gui = Instance.new("ScreenGui")
gui.Name = "SpawnpopKit"
gui.ResetOnSpawn = false
local frame = Instance.new("Frame")
frame.Size = UDim2.fromScale(1, 1)
frame.BackgroundTransparency = 1
frame.Parent = gui
local grid = Instance.new("UIGridLayout")
grid.CellSize = UDim2.fromOffset(96, 96)
grid.Parent = frame
for name, id in pairs(ids) do
  local label = Instance.new("ImageLabel")
  label.Name = name
  label.Image = id
  label.BackgroundTransparency = 1
  label.ScaleType = Enum.ScaleType.Fit
  label.Parent = frame
end
gui.Parent = game:GetService("StarterGui")
  1. 3D models and audio. These go through Roblox Open Cloud. The user runs the curl commands from roblox.models in their own terminal with their own key:
Open Cloud upload (run by the user)
# Keep the key in your own shell. Never paste it into an AI chat.
export ROBLOX_API_KEY=...        # created in the Roblox Creator Hub, scoped to asset upload
export ROBLOX_USER_ID=...        # the creator account that will own the asset

curl -L -o "pet.glb" "<url from get_files>"
curl -X POST "https://apis.roblox.com/assets/v1/assets" -H "x-api-key: $ROBLOX_API_KEY" \
  -F 'request={"assetType":"Model","displayName":"pet","description":"Made with spawnpop","creationContext":{"creator":{"userId":"'"$ROBLOX_USER_ID"'"}}};type=application/json' \
  -F "fileContent=@pet.glb;type=model/gltf-binary"
# The reply holds an operation path. Poll GET https://apis.roblox.com/assets/v1/operations/<id> until done, then read assetId.

Never paste your Roblox API key into an AI chat, and never send it to spawnpop. spawnpop does not ask for one. Keep it in $ROBLOX_API_KEY in your own shell. Roblox sets its own limits and review rules for uploaded audio and models; check them in the Creator Hub before you upload.

FAQ

Do I need an API key to try the MCP server?

Not to see a price. plan_kit works without a key and returns the plan, the credit price and a link for the human to connect. Every other tool needs a key, and a key is created after a first credit purchase. A person can hold up to 3 active keys.

Which transport and protocol versions does the server speak?

Streamable HTTP with plain JSON replies: each request is a POST of one JSON-RPC 2.0 message and gets one JSON response. There are no sessions and no server-sent events, and batch requests are not supported. Protocol versions 2025-11-25, 2025-06-18, 2025-03-26 are accepted; an unknown version is answered with 2025-11-25.

Can my agent spend credits without the user knowing?

No. Every spending call must repeat the exact price in confirmCredits, which the agent should have shown the human first, and a key can be limited to read-only. If the price moves, nothing is charged and the agent gets price_changed. The human can revoke a key at any time in Settings.

What happens if a call times out or the agent retries?

Retry with the same requestId. The same requestId returns the same jobs and charges nothing a second time. Use a new requestId only when you really want to buy again, for example to remake items that failed.

What does the agent do when the balance runs out?

It receives insufficient_credits with a payUrl. The agent shows that link to the human, who can pay without creating an account, and then repeats the same call with the same requestId. The link names the account to credit and expires after 24 hours.

How do the generated files get into Roblox Studio?

get_files returns signed download URLs and the exact next steps. Images go through the upload_image tool of Roblox Studio's own MCP server, and a Luau snippet places them in a ScreenGui. 3D models go through Roblox Open Cloud with a key that stays in your own shell. spawnpop never asks for a Roblox key.

More questions? See help, security or the GUI generator for what the assets look like. The genres you can pass to plan_kit are simulator, tycoon, tower_defense, obby, rng, horror, roleplay, fighting.