---
name: build-game
version: 1.0.0
description: "Build, validate, test, and publish a MiraDock game with the Game SDK and MCP tools."
---

# Build a Game

Use this skill when a human wants to build or publish a game on MiraDock. Ask for their game idea if it is not already supplied.

## Connect and read the spec

Connect to the Streamable HTTP MCP server at https://miradock.com/mcp. Use a write token from https://miradock.com/app/mcp in the Authorization: Bearer <YOUR_WRITE_TOKEN> header, or OAuth 2.1 with mcp:read and mcp:write via https://miradock.com/.well-known/oauth-protected-resource. Configure credentials in your MCP client, never in game HTML. Game management and stats are owner only; a bucket-scoped write token can manage its owner's games without selecting a bucket.

Call get_game_sdk_spec first. Read the complete spec, including the minimal compliant game in section 12. The same spec is at https://miradock.com/docs/game-sdk and https://miradock.com/llms-full.txt. Read-only credentials can read and validate; obtain write access before publishing.

## Scaffold

Start with the section 12 HTML example and adapt it to the human's idea. Supply title, slug, description, genreTags, coverImageUrl, orientation, minPlayers, and maxPlayers as MCP metadata, not in the HTML.

- Deliver one self-contained HTML file, at most 2 MiB of UTF-8 after sanitization, with inline scripts and styles.
- Include <meta name="miradock-sdk" content="1"> and call MiraDock.ready() within 10 seconds. The host injects window.MiraDock; do not install or import the SDK.
- No network calls: no fetch, XMLHttpRequest, or WebSocket. Images/fonts may use the origins allowed by the spec.
- No browser storage: no localStorage, sessionStorage, indexedDB, or cookies. Use MiraDock.save and debounce writes.
- Check MiraDock.capabilities before optional operations. Multiplayer is unsupported in v1.
- Never place prices in game HTML. Configure prices only with set_game_skus; let the host render checkout and payment UI. Gate paid content on host entitlements and the entitlements event.

## Validate → fix → validate

Call validate_game with { html }. It returns { ok, errors, warnings }; validation writes nothing.

1. If ok is false, inspect every error (code, message, and optional line), fix the HTML, and call validate_game again on the complete revised file.
2. Repeat this validate-fix loop until ok is true. Do not publish while any validation error remains.
3. Review warnings, fix applicable ones, and rerun validation after every HTML change. Shop/donation warnings can reflect settings that have not been configured yet. Static validation cannot prove runtime behavior or the ready deadline.

## Publish a draft and self-test

Call publish_game with { html, title, ...metadata, publish: false }. Save the returned { gameId, slug, status, playUrl, validation }. Drafts are visible only to the owner.

If a browser is available, open playUrl in the owner's signed-in session. Confirm the game loads, calls ready within 10 seconds, responds to controls, and handles saves, pause/resume, and optional capabilities without console errors. Do not make it public just to bypass draft access. If no browser or owner session is available, record that runtime testing was not performed and give the human the draft URL.

For runtime fixes, edit locally, run the validate-fix loop again, then call update_game with { gameId, html } to update the existing draft. Reload playUrl and retest. Do not create duplicate games for revisions.

## Configure leaderboards, SKUs, and monetization

After draft testing, configure in this order using gameId or slug:

1. set_game_leaderboards with the full boards array: [{ boardId, name, sort: "desc" or "asc", period: "alltime" or "weekly" or "daily" }]. The section 12 example uses boardId "taps". Use [] if no boards are needed and remove any calls to unused boards from HTML. Replacement removes omitted boards and their entries, so review removal warnings on existing games.
2. set_game_skus with the full skus array: [{ sku, name, description, kind: "item" or "access" or "consumable", priceCents, sortOrder }]. Prices are integer USD cents, minimum 50. Use [] for a free game. Existing kinds cannot change; removed SKUs become inactive while entitlements remain. Do not use donation SKUs or the reserved supporter ID; donations are configured next. Definitions can be saved before payout onboarding.
3. set_game_monetization with { gameId, donations: false, shop: false } for a free game. Enable requested donations/shop only after the creator completes payout onboarding at https://miradock.com/app/earnings and platform payments are enabled. If onboarding or payments block enabling, leave both false, explain the remaining step, and do not claim sales are active.

If a browser is available, retest configured boards, entitlement handling, and the host shop. Do not make real purchases as a self-test. Fix any HTML through validate_game and update_game before continuing.

## Make the game public

Call update_game with { gameId, status: "published" } when the game is ready to publish. This revalidates the stored HTML. Handle any validation_error through the same validate-fix loop. Never set suspended or combine status with the publish alias.

Return the playUrl, gameId, publication status, validation result, configured boards/SKUs, monetization state, and what was or was not browser-tested. If the human only requested a draft, keep status draft. Later use get_game_stats for owner-only plays, unique players, leaderboard sizes, and metrics; revenue is currently null.
