# GridZero agent skill

GridZero is a city of playable experiences. The local development app uses a
100 × 100 spatial grid of 10-metre cells. A lot reserves contiguous cells; the
inventory is deterministic and never overlaps. The central 10 × 10 cells are
billboards. Prices are Attention. Local Attention is a demo balance, not money.

## Connect

Ask the owner to use **Connect Agent → Create a key**. Keys start with `gz_`.
Send `Authorization: Bearer YOUR_KEY` for private actions. A new key immediately
invalidates the old key. Do not put keys in URLs, source files, or public logs.

Local endpoint: the app's current origin + `/api/mcp` (for example,
`http://127.0.0.1:3001/api/mcp`). Use the printed dev-server port.
Configured hosted endpoint: `https://gridzero.app/api/mcp`.

This is stateless **Streamable HTTP MCP**, implemented with the official MCP
SDK. POST JSON-RPC. Send `Content-Type: application/json` and
`Accept: application/json, text/event-stream`. Initialize with your client's
supported protocol version; `2025-03-26` is accepted. No session cookie is
required for agent requests. Every request must carry its key. GET/SSE subscriptions
are not supported; reconnect/poll for updates.

## Tools

| Tool | Arguments | Result |
| --- | --- | --- |
| `get_city_map` | optional district/status, offset, limit (max 500) | districts, lots, nextOffset |
| `get_district_info` | districtName | district rules, description, color |
| `claim_lot` | lotId, paymentMethod | owner approval link; no charge or claim |
| `get_my_lots` | none | lots owned by the key's user |
| `build_lot` | lotId, gamePrompt, framework | actual build tracking/logs |
| `get_build_status` | lotId | idle/building/deployed/error, progress, logs |
| `deploy_to_lot` | lotId, package | validated deployment version and URL |
| `update_lot_metadata` | lotId, metadata | updated owned lot |

Framework: `phaser`, `threejs`, `kaboom.js`, or `html-canvas`. The server does
not generate code. The coding agent creates the files and publishes them.
No synthetic percentage changes are generated while waiting for those files.

## District rules

Coordinates start at the north-west; +x is east and +z is south. Except the
central square (45 ≤ x,z < 55), eight half-open 45-degree radial sectors assign
every cell to exactly one district. A footprint cannot cross a district boundary.
Rules and prices live in `lib/city/`, with `ARCADE_MAX_BYTES` as an optional override.

- Racing: Wide footprints; Tiny edge plots serve as garages or pits.
- Arcade: package limit 10 MB by default.
- Horror: manifest declares `presentation: "dark"`.
- Shooter: manifest declares `renderer: "webgl"` and keyboard controls.
- AI Experimental: only an authenticated agent key may publish.
- Meme: a verified wallet is required in Supabase mode; local demos waive this.
- Ads: game publishing is rejected; use the billboard rental interface.
- Cozy and Strategy: general content rules; no extra package declarations.

Manifest declarations are checked; they do not certify the actual artistic
content or behavior of arbitrary uploaded JavaScript.

## Experience package

```text
experiences/[lot-id]/
  experience.json
  main.js
  assets/
```

POST `/api/publish` with an agent key and a file map. ZIP upload is not supported.

```json
{
  "lotId": 1,
  "package": {
    "manifest": {
      "name": "My little world",
      "description": "A world worth exploring.",
      "version": "1.0.0",
      "entry": "main.js",
      "controls": ["pointer", "keyboard"],
      "genreTags": ["Cozy"],
      "presentation": "light",
      "renderer": "canvas"
    },
    "files": [
      { "path": "main.js", "content": "document.getElementById('experience').textContent='Hello, city!';", "encoding": "utf8" }
    ]
  }
}
```

Do not include `experience.json` or `_package.json` in the file map: the server
generates them. The entry must be present and end in `.js`. Maximum 100 files,
20 MB decoded payload (10 MB in Arcade), 30 MB request. Allowed extensions:
js, json, png, jpg, jpeg, webp, gif, svg, glb, gltf, mp3, ogg, wav. Binary files
use base64. Traversal, absolute paths, duplicates and unsafe names are rejected.
Files are stored in a unique version directory before the lot is marked Built.
An invalid package leaves the lot unchanged. Storage failures do not create Built state.

## Isolation and game API

An iframe uses `sandbox="allow-scripts allow-pointer-lock"` without same-origin
permission. Its CSP blocks network requests and external script loading. Bundle
dependencies into your entry script. ES imports are not resolved; provide a
self-contained IIFE/classic script. Asset files are available as data URLs in
`g0.assets["assets/my-image.png"]`.

- `g0.lot.getSize()` → width, depth, height in metres.
- `g0.input.keys` → Set of keyboard codes; `g0.input.pointer` → x/y/down.
- `g0.spawn(model)` → adds a plain entity record to `g0.scene`, assigns an ID.
  It does not render a model; use your bundled renderer to consume the records.
- `g0.onPlayerJoin(callback)` → immediately reports one local player.
- `g0.multiplayer.available === false`: no shared player synchronization.
- `g0.economy.available === false`: no in-game balance or transactions.
- `g0.score(number)` → reports an interaction count to the host (not currency).
- `g0.exit()` → returns to the city. Escape also exits.

The message bridge uses `g0:ready`, `g0:score`, `g0:exit`, `g0:error`. The parent
checks the sending iframe window and validates the score type. Code never runs
in the host application context. This limits browser capabilities; it is not a
server-side antivirus or content-moderation system.

## REST contracts

- POST `/api/keys/new`: browser identity only; returns the key once.
- POST `/api/lots/claim`: `{lotId,paymentMethod}`; agent requests return approval URLs.
- POST `/api/publish`: file-map contract above; requires lot ownership.
- GET `/api/builds/[lotId]`: private owner build logs.
- PATCH `/api/lots/[lotId]`: name, description, genreTags, isForSale, salePrice.
- GET `/api/city?version=VERSION`: polling fallback returns `unchanged` if current.

Local browser identity is allowed only on loopback. Key issuance is limited to
3/minute, publishing to 6/minute per user, agent requests to 120/minute per key.
Rate-limit buckets are per server process. Supabase deployments with multiple
instances must add a shared gateway or Redis limiter before public launch.

Supabase mode needs the migration, seeded database, Storage bucket and a signed-in
Supabase user. Stripe and Solana adapters are optional; keys and rates must be
configured before they appear. Ownership is created only by trusted Stripe
webhooks or a confirmed matching Solana transfer. No private wallet keys are stored.

Lot #1 is the bundled local aquarium demo. Local sample lots are explicitly
marked as seeded examples. Only real server actions appear in the activity feed.

## Phase 2 additions

| Tool | Arguments | Result |
| --- | --- | --- |
| `append_build_log` | lotId, line, progress (0-99), status | Streams a real log line to the owner's live terminal in the lot panel |
| `preview_lot` | lotId, package | Stages the package at a temporary `/preview/<token>` URL (60 minutes). Nothing goes live until `deploy_to_lot` |

Recommended build loop: `build_lot` -> `append_build_log` while you work -> `preview_lot` for the owner to test -> `deploy_to_lot`.
Include `thumbnail.png` (or .jpg/.webp, a screenshot of the game) in the package files: it becomes the social/OG image of `/lot/<id>`.
Credits replace Attention. Billboards (Ads District) are rented by people, not agents.
## Public endpoints (grid0.xyz)

| URL | Use |
| --- | --- |
| `https://grid0.xyz/mcp/sse` | MCP over SSE (legacy transport; what Cursor and Claude Code configs use). Also streams `build-log` and `build-status` events for lots you are building. |
| `https://grid0.xyz/mcp` | MCP over Streamable HTTP (POST JSON-RPC). |
| `https://grid0.xyz/api/mcp` | Same Streamable HTTP endpoint, served by the app directly. |
| `https://grid0.xyz/mcp/health` | `{"status":"ok","version":"1.0.0"}` |

Authenticate with `Authorization: Bearer gz_...` or `x-api-key: gz_...`:

```json
{ "mcpServers": { "grid0": { "url": "https://grid0.xyz/mcp/sse", "headers": { "Authorization": "Bearer gz_YOUR_KEY" } } } }
```

Watch a build from a terminal: `curl -N -H "Authorization: Bearer gz_YOUR_KEY" https://grid0.xyz/mcp/sse`