@looping/engine-topdownTwin-stick and heist games, patrols and line of sight.
Developers
One MCP for everything a game needs: projects, a managed repo and storage, engine templates and skills, sprites, sound and meshes on your credits, hosted Blender, and deploys to the game's own address. Model spend stays on your Claude or Codex subscription.
claude mcp add --transport http looping https://neighborly-octopus-690.convex.site/mcpThen run /mcp → Authenticate. Sign-in is a link. Every connection gets its own key; see and revoke them in your Studio.
From a connected agent to a listed game with a token. Every build is checked against the game rules before it goes live.
Add the MCP to Claude or Codex and approve the link it opens.
project_create: <slug>.looping.gg reserved at once, plus a private repo and a bucket for a game built here.
Your own workflow, or the looping-build skill: engine_scaffold → repo_commit → site_deploy, with asset_start, sprite_install and audio_install for art and sound.
game_publish a local build directory (or inline files). Checked against the game rules, live at once, immutable.
Test it on its own address first; game_list puts it in the catalog when it is ready.
project_launch returns the launch page; the creator signs the token into existence.
56 tools, straight from the MCP's own definitions.
looping_whoamireadWho this key is and what it can act on: the account, the selected project (stage, credits, routing, settings, market), and which capabilities are live. Call first. If no project is selected, project_list then project_select or project_create.
project_listreadList the game projects this account owns: slug, stage, credits, repo, and play URL.
project_selectreadChoose the project (by slug) that project tools act on. The choice is remembered on the server for this key, so it holds across sessions and clients.
project_createwriteCreate a new game project (nothing is launched): a name, a brief of a few sentences, and optionally the engine (an id from engine_list). origin 'agent' (the default) provisions a private repository, blob storage, and the project's own origin at <slug>.looping.gg for a game built here; origin 'upload' reserves only the origin, for a game built elsewhere that game_publish will put live. Returns the slug and selects it. Creating costs nothing.
project_launchwriteGet the page where the deployer launches the project's token on Robinhood Chain. The launch is signed in the deployer's own wallet (pairing GG, ETH or USDG, creator fee up to 10%, optional developer buy), so an agent cannot launch: once the game has a live build and the deployer has decided to launch, call it with confirm: true and it returns launch_url to hand to them; without confirm it refuses. Everything before launch works without a token.
project_creditsreadThe project's two balances: fee balance (claimable, in the quote asset) and credit balance (USD cents: available, reserved, minted from fees, spent), with recent receipts. Generation and Blender jobs spend the credit balance; shortfalls are topped up automatically from the deployer's account credits.
infra_statusreadThe project's platform-managed repository (private by default), blob storage bucket, hostname, and Blender workspace, with provisioning state. Created automatically with a project built here.
infra_provisionwriteRetry provisioning of the repository, the bucket, and the hostname if any is pending or failed.
repo_treereadList every file in the project's managed repository (and its commit history when tracked on the platform).
repo_readreadRead one file from the managed repository. Text comes back as utf-8; binaries as base64.
repo_commitwriteCommit files to the managed repository in one commit: paths and contents (utf-8 text, or base64 with encoding). The platform is the git client, so no GitHub account is needed. Returns the commit sha and URL.
repo_archivereadA short-lived download URL for the whole repository as tar.gz, for a local checkout or export.
repo_visibilitywriteMake the managed repository public or private. Private by default; only on the deployer's explicit request.
storage_putwriteStore a blob in the project's bucket: text or base64 content, or source_url to copy a file (for example a completed asset job URL) server-side. Keys are paths like assets/boat.png.
storage_getreadRead a blob from the project's bucket (text as utf-8, binaries as base64, up to 6 MB inline).
storage_listreadList objects in the project's bucket, optionally under a prefix.
storage_deletedestructiveDelete one object from the project's bucket.
game_publishwritePut a game build live on the project's own address (<slug>.looping.gg). Over the stdio server give dir: an absolute local directory holding index.html (or a folder that contains it); the files are uploaded straight to the games worker with a signed ticket and checked there. Over the remote MCP give files inline (text, or base64 with encoding; small builds only). Either way the build must meet the game rules (index.html at the root, no server code, 100 MB total, 25 MB per file, 1,000 files, no wallet or seed-phrase prompts, no page redirects) or it is refused with the reasons. Every publish is an immutable version, playable at once on the project page for anyone with the link; it stays out of the catalog until game_list. note is what changed in this build. Returns the play URL, the build label, whether the game is listed, and the launch page URL when no token exists yet.
game_listwriteList the game in the Looping catalog (Discover and the markets) or take it off. Listing needs a live build. An unlisted game stays playable on its own address and project page for anyone with the link, which is how a creator tests a build before making it public or launching a token.
store_itemsreadThe game's store catalog and its mode (test until the token launches, then live; off when paused): every item's sku, name, kind (unlock or consumable), units, price (in dollars, converted at checkout, or an exact token amount), the tokens it can be paid in, and any pending price increase. Game code names items by sku only and reads this through the SDK (looping.store.items()).
store_item_upsertwriteAdd or change one store item. Price it in dollars (usd, converted to the chosen token at checkout with live rates) or exactly in a token (token: GAME | GG | USDG | ETH, amount in base units: 18 decimals except USDG's 6). pay lists what buyers may pay in (the game's own token GAME, GG, USDG, ETH); a token-priced item always accepts its own token. kind unlock is owned once; consumable stacks by units per purchase and is spent with looping.store.consume(). A raised price applies after 24 hours (the old one is signed until then; the change is public on the game page); a cut applies at once. The platform fee is the game's pairing (0% GG-paired, else 10%), never set here. Agree the items and prices with the creator before adding them; every change is public on the game page.
store_item_removewriteTake an item off sale. Players who own it keep it; re-adding the sku restores it.
store_salesreadPurchases as the chain settled them (and test-mode ones before launch): buyer, sku, token, amount, dollar value, fee and transaction; totals per token; how many wallets own each item. Live proceeds sit in the launchpad's fee escrow for the game's fee recipient and are claimed from the Token tab.
store_settingswritePause (enabled=false) or resume purchases for this game. While paused the catalog stays visible and no quotes are signed. A pause by Looping operators cannot be lifted here.
rooms_statusreadHow the project's multiplayer rooms are doing right now: the stored rooms policy (enabled, mode, wallets_only, max_players, messages_per_second, tick_rate, when and by whom it was set), live rooms and players plus usage per day from the rooms worker (player-seconds by mode, sandbox time, joins, and results_lost: signed results a room had to abandon; null when the worker cannot be reached, with metered_at, when usage was last read, and unreachable_since while it cannot be), open reports (wallet reports on players not yet banned; guest reports count separately) and how many players are banned. New guest ids are bounded per project per hour (a flood raises an alert). Read it before rooms_configure or player_ban, and whenever the deployer asks about online play, cost, or a complaint.
rooms_configurewriteSet the project's rooms policy; give only the fields to change. enabled false refuses every room token, so nobody can join until it is true again. mode is relay (the room passes messages through and the host client decides; co-op, party, turn-based, chat) or authoritative (the room runs the build's rules.js in a sandbox and signs verified results; versus, arena, races: pick it when cheating would harm other players). Authoritative mode requires credits on the project (rooms are metered) and caps rooms at 16 players. wallets_only refuses guests so a ban follows the wallet. max_players is the most one room may hold (2-32; a game still asks for fewer per room), messages_per_second the per-connection rate (1-30), tick_rate the simulation rate in authoritative mode (5-30). The response is the stored, normalized config: every field clamped to the platform's limits, plus updated_at and by. Rooms already open keep their mode and size; new tokens carry the new policy, and wallets_only alone reaches open rooms too (each switches on the next newer token it sees, and its guests are out at join and at their next renewal). Read the looping-fairplay skill for which mode fits the game.
players_listreadPlayers the project has seen, as pseudonymous per-game ids (never wallets): player, kind (wallet or guest), name, last_seen, and banned_until with ban_reason while a ban is in force. banned true lists current bans only; limit is 1-200. Use it to find the id a report names before player_ban, and to check that a ban or a lift landed.
player_banwriteBan a player from the project's rooms, saves, reports and every score attributed to the id for hours (1 to 8760, a year), or lift a ban with hours 0; an anonymous score (posted with no token) is not a player and is not covered, which any client can post whether banned or not. player is the pseudonymous id from the operator alert's body, players_list, or a room's join event; a wallet's ban follows the wallet across devices, a guest's ban only that browser's storage (set rooms_configure wallets_only when that matters). It takes effect at the player's next token, within 15 minutes; a connection already open is closed when its token expires. A ban resolves the reports filed against that player (rooms_status's reports_open). reason is for the deployer and operators, not shown to the player. Bans are the deployer's decision: reports never ban on their own.
site_statusreadThe project's reserved hostname (<slug>.looping.gg), what is live there, the build history for rollback, and the player API base. Reserved automatically when the project is created. The round and score routes live on that origin: POST /_looping/api/round (room token required) mints a server-seeded round { round, seed, version, exp } for the live version, and POST /_looping/api/score { round, score, tape? } records the round's score; a well-formed tape from a minted round is replayed against that version's sim.js in a sandbox and the score is marked verified when it reproduces, otherwise the score is player-reported.
site_deploywriteDeploy a bundle the platform already holds to the project's own origin: bucket_prefix (everything under that prefix in the project's bucket, e.g. builds/v3/) or repo_dir (a directory of the managed repository, e.g. dist/, or '.' for the root without docs and dotfiles); files (inline; text or base64 with encoding) also works for small bundles, though game_publish is the tool for a build. The bundle is checked against the game rules before it goes live: index.html at the bundle root, size limits, no server code, and no wallet or seed-phrase prompts; a bundle that fails is refused with the report. Each deploy is an immutable version served with a strict CSP; the project page embeds it in a sandboxed iframe. Games post scores with fetch('/_looping/api/score', {method:'POST', headers:{'x-looping-token': await rooms.token()}, body: JSON.stringify({round, score, tape})}) and read GET /_looping/api/leaderboard and GET /_looping/api/store/items: the score's player is the room token in x-looping-token and nothing else (without the header the row is anonymous). A verified score needs a round minted by POST /_looping/api/round and the input tape engine-core's recorder wrote; the engine templates do this, ship sim.js (export simulate(args): the headless round the platform replays to check a score) and pin full engine versions in the import map (https://engines.looping.gg/<pkg>@<x.y.z>/src/index.js): keep both in the bundle and never replace the pin with an @major.minor alias, or every score of the game stays player-reported. Paid content lives under paid/<sku>/ and is served only with a content ticket (rooms.paidUrl in engine-core; the looping-fairplay skill), so any other paid/ layout is refused. Purchases go through the game SDK (runtime.store in engine games, https://engines.looping.gg/sdk@0.0/src/looping.js for any HTML game: store.buy(sku) opens the platform's checkout); no keys, prices or wallet code are ever shipped in the bundle. label is what changed in this build.
site_rollbackwriteMake an earlier build live again on the project's origin (versions come from site_status history).
engine_listreadThe platform's game engines (top-down shooter, platformer, adventure, isometric, FPS, and the shared core) and asset pipelines with versions, docs, skills, and CDN URLs. Start here when building a game; then engine_docs and skill_read, then engine_scaffold.
engine_docsreadRead an engine's documentation: README.md (API and tuning), ROADMAP.md, CHANGELOG.md.
engine_scaffoldwriteCopy an engine's template game (index.html, main.js, tuning.json, levels/ or rooms/, theme.css, loop.json) into the selected project's repository and deploy it to the project's origin so there is a playable game immediately. Then iterate with repo_commit and site_deploy following the engine's skill.
skill_listreadThe skills the platform ships: how to build a game here from scratch (looping-build), publish a build made anywhere (looping-publish), build with each engine, run the asset pipelines, direct the art, use hosted Blender, and rig and animate characters. Read the one matching your task before acting.
skill_readreadRead a skill (SKILL.md) by name, e.g. looping-build, looping-publish, looping-multiplayer, looping-fairplay, looping-topdown-shooter, looping-platformer, looping-adventure, looping-isometric, looping-fps, looping-engine-core, looping-pipelines, looping-art-direction, looping-blender, looping-rig.
pipeline_readreadRead an asset pipeline definition (sprite-sheet, tileset, character-pack, sfx-set, voice-lines, music-set, cover-art, ui-kit): inputs, steps, prompts, models. Run it locally with `looping-pipeline` from @looping/pipelines, or follow its steps with asset_* tools.
asset_modelsreadCatalog of generation models billed to the project's credits: images (FLUX), playable character sprite sheets, tileset and prop sheets (Looping's own jobs, packed into the engines' atlas format), textured meshes (Meshy 6), speech, sound effects and music (ElevenLabs), with prices in cents and the price version. One account, one balance, every provider.
asset_quotereadQuote a generation job against the project's credits. Returns quote_id, cost_cents, and expiry (10 minutes). Does not spend. params: image_size (square, square_hd, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9), num_images (1–4), seed; mesh art_style, pose_mode ('t-pose' for characters), rig: true (+$0.15, Meshy rigging → preset 'meshy'), height_meters, or model meshy/rigging with source_job_id | model_url | key to rig an existing model; audio voice; sfx duration_seconds; sprite: four models. looping/sprite-3d (60¢ + a 105¢ rigged model unless mesh_job_id names one) prerenders a rigged Meshy character in Blender from one fixed camera for every facing and frame: the consistent path, the default for playable characters; params mesh_job_id, view (low top-down | high top-down | side), directions, clips (idle, walk, run), size, shading (toon | flat | textured), name, scale; needs blender_workspace ready. looping/sprite-sheet (15¢ per facing) draws four poses per facing in one image and slices it: quick, for enemies and NPCs; params size, view, clips (up to 3), directions, style, reference (this character's own key art only), name, scale. looping/tileset-sheet (15¢ per four tiles): params tiles ("char|role" pairs matching the level legend), size, style, reference. looping/prop-sheet (20¢): params props (named objects), size, style, reference. All four return a sheet plus result.skins_entry for sprite_install.
asset_startwriteStart a quoted job. Reserves the quoted cost from project credits (auto top-up from the account if short), runs the provider, settles at the quoted cost on completion, refunds on provider failure. Idempotent by request_uuid: never retry an uncertain start under a new UUID. Requires the deployer's generate consent for this session and max_cost_cents at or above the quote.
asset_statusreadInspect a job: queued, running, complete, failed, or refunded, with cost and output count. Pass request_uuid instead to find a job after an uncertain start.
asset_uploadwriteStore a file you already have (a logo, a reference sheet, a voice clip) on the platform and get a URL to cite as a generation reference (asset_quote params.reference), a storage_put source_url, or a blender_run source_url. base64 content up to 6 MB, or source_url to copy server-side. Free.
asset_waitreadWait until any of the given jobs finishes (complete, failed, or refunded) or timeout_seconds (max 50) passes, then return the finished jobs and the ids still pending. One call instead of a poll loop.
asset_urlreadA download URL and content type for a complete job's first output. Use it to look at the result, as a storage_put source_url, or as a repo_commit input.
audio_installwriteCommit a complete speech, sound-effect or music job into the repository as assets/audio/<name>.mp3 and merge it into assets/audio.json, which every engine template loads (audio.loadSounds). A sound effect named after an engine cue (hit, hurt, pickup, jump, land, shoot, explode, dash, ui, win, lose, reload) replaces the synthesized placeholder wherever the engine already plays it; other names play where the game calls audio.play(name). Music installs as a track the game starts with audio.track(name) (loop and volume optional); speech installs as a voice line for audio.say(name). kind is inferred from the job.
sprite_installwriteCommit a complete sprite job (kind sprite) into the repository as assets/<name>.png + assets/<name>.json and merge the entry into assets/skins.json (the adventure template loads it and draws characters, tiles and props from it; the other engines need loadSkins and their own draws). name is the skins key the engine looks up (player, slime, skeleton, npc, or an enemy kind you defined); scale overrides the draw scale. Look at the atlas (asset_url) before installing; anything without a skin keeps its placeholder.
blender_workspacewriteThe project's hosted Blender workspace (a private Linux machine with Blender 4.5 LTS). First call creates it and starts the install; poll until blender is ready. Subsidized by the platform; jobs are billed per run.
blender_runwriteRun Blender headless on the workspace: a Python script (bpy) plus optional inputs (text, base64, source_url, source_key: an object in the project bucket, or source_path: a motion collection file such as tools/rig_humanoid.py or humanoid-v1/motions.glb), and the output paths it writes (relative to the job directory, e.g. out/model.glb, out/preview.png). Outputs land in the project's bucket under blender/<job>/ and come back with storage keys. Billed to project credits per started job; requires the deployer's generate consent and a fresh request_uuid (never retry an uncertain run under a new UUID). 10-minute limit.
blender_jobreadInspect a Blender job: status, exit code, log tail, outputs with storage keys. Poll every few seconds while running.
blender_job_listreadThe project's recent hosted Blender jobs.
motion_listreadThe platform's motion collection: shared humanoid libraries (motion-only GLBs with named clips: walk, run, jump, vault, ledge_grab, crawl…), sample rigged characters, the 24 humanoid roles, and bone-name presets (meshy, looping). Start here before rigging or animating a character.
motion_readreadRead a collection text file: a library manifest (clips, timing, source profile), a rig profile, or a README. Paths come from motion_list.
motion_fetchwriteCopy a collection file (a library's motions.glb, a profile, a sample character GLB) into the project bucket at key, so hosted Blender jobs can take it as a source_key input and storage_get can download it.
motion_inspectreadInspect a rigged character (key in the project bucket, job: a completed mesh asset job such as a Meshy rig, or a sample): skin joints, existing clips, which bone-name preset matches, and whether a profile binds every joint (limb lengths, rest signature). Fix the rig in hosted Blender or supply { bones } until the check passes.
motion_applywriteBake selected library clips onto the character's own rig (retarget by anatomy, proportional root motion, optional contact IK windows) and write <out>/character.glb + character.json to the project bucket. Mesh, weights and materials are untouched. profile: a preset ('meshy'), 'sample:<id>', a library profile id, or { bones }. retarget (optional) overrides: root_offset_m, rotation_offsets, limb_ik 'source-endpoints' with ik_chains/ik_windows/contact_space. Free, no credits.
motion_exportwriteGrow the collection: export a character's authored clips (from hosted Blender, or a motion_apply result) as a motion-only library (<out>/motions.glb + library.json) in the project bucket. clips are names (loop, in-place assumed) or { name, loop, root_motion: 'in-place'|'embedded' }; manifest_key (a character.json) supplies exact clip metadata instead. Share the keys to have it reviewed and published.
The game SDK
Games run on their own origin in a sandboxed frame and never see a wallet, key or session. The SDK grows out of engine-core's runtime: rounds, scores the platform can verify by replaying the round, the keyless player API, multiplayer rooms on the game's own origin, and in-game purchases. A game names an item by SKU; the platform draws its own checkout over the frame from a quote it signed, the Store contract pays the game's fee recipient through the launchpad escrow (0% platform fee for GG-paired games, 10% otherwise), and the game reads what the player owns. Prices live on the platform (store_item_upsert), never in game code. Rooms come with P3.
runtime.storeengines.looping.gg/sdk@0.0/src/looping.jsimport { looping } from "https://engines.looping.gg/sdk@0.0/src/index.js";
// or, in an engine game: const runtime = createRuntime(); runtime.store / runtime.player()
await looping.player(); // { signedIn, name }: a short name, never the wallet or keys
const { items, mode } = await looping.store.items(); // the catalog, prices set on the platform
const r = await looping.store.buy("golden-lantern"); // the platform draws its own checkout over the game
// → { sku, txHash, test, count } once it is owned
await looping.store.owned(); // [{ sku, count, test }], verified on the platform: restore on load
await looping.store.consume("continues-5"); // spend one of a consumable
const room = await looping.rooms.join("harbor-1"); // P3: wss://*.looping.ggPublished to engines.looping.gg and served by engine_list, engine_docs, skill_list and skill_read.
@looping/engine-topdownTwin-stick and heist games, patrols and line of sight.
@looping/engine-platformerSide-scrollers with tuned jump arcs and tilemaps.
@looping/engine-adventureZelda-style rooms, props from a sheet, lantern lighting.
@looping/engine-isometricTile worlds viewed at an angle.
@looping/engine-fpsFirst-person 3D with physics.
coming in P3Hosted under the platform's security rules. Coming.
The security policy sent with every game on <slug>.looping.gg.