# LobbyLab (full text) > The LobbyLab docs for developers and their coding agents in one file: quickstart, agents, the client > SDK, rooms, the wire protocol and status. Generated from the docs bundled in the `lobbylab` npm package; > `npx -y lobbylab docs ` prints each part. Every error code has a page at > https://lobbylab.gg/docs/errors, and the planned REST routes are at https://lobbylab.gg/docs/api.md. Note: The `lobbylab` npm package is not published yet, so the `npx -y lobbylab` commands on this page do not work today. Everything else here is current. Request an alpha key at https://lobbylab.gg/developers. # LobbyLab quickstart LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: alpha. There are no public keys yet, and `@lobbylab/client` and `@lobbylab/node` are not on npm yet; both arrive with the alpha. What works today: the `lobbylab` package's `docs` and `mcp` commands, and lobbylab.gg, where people describe a game, LobbyLab builds it, and friends play it on phone or web. Read these docs offline with `npx -y lobbylab docs [topic]`. For coding agents: `npx -y lobbylab mcp` (see the agents doc). ## What works today - `npx -y lobbylab docs [topic]` prints these docs: `quickstart`, `agents`, `client`, `rooms`, `protocol`, `status`. - `npx -y lobbylab mcp` starts a local MCP server with two read-only tools, `lobbylab_docs` and `lobbylab_multiplayer_quickstart`. It needs no key and makes no network calls. - The wire protocol the API reuses, `multiplayer-wire/v0`, already runs every room on https://lobbylab.gg (see the protocol doc). - https://lobbylab.gg itself: describe a game, LobbyLab builds it, and friends play it together on phone or web. ## What arrives with the alpha - Projects with a publishable key (`llpk_...`, safe in a browser, checked against your allowed origins) and secret keys (`llsk_...`, backend only). - `@lobbylab/client` (browser, Node, Bun, Deno, React Native) and `@lobbylab/node` (player tokens from your own login, server calls). - Rooms with 6-letter invite codes, quick-join matchmaking and a lobby list of open rooms. - Shared state: a shared document, one slot per player, private data only its owner sees, write rules per path, guards for races, messages to all, others, the host or chosen players, and host controls. - Two room flavors: turn-based (every change saved) and live (a fixed 20 Hz tick). - Reconnection handled by the SDK. ## Planned after that - `npx -y lobbylab@1 init`: a short-lived sandbox project with no account, keys written to `.env` and `.env.local`. - MCP tools that create projects and rooms, report usage and add test players. - Game rules that run on LobbyLab's servers. - SDKs for more engines (Flutter and Godot first). ## The planned client API (preview: may change, not installable yet) ```js import { LobbyLab } from "@lobbylab/client"; const lobby = new LobbyLab({ key: process.env.LOBBYLAB_PUBLISHABLE_KEY }); const room = await lobby.joinOrCreate("default"); room.on("message", ({ from, topic, data }) => console.log(from, topic, data)); room.send("hello", { at: Date.now() }); ``` Do not add `@lobbylab/client` or `@lobbylab/node` to a project yet: npm has no usable release of either. ## Next step Request an alpha key at https://lobbylab.gg/developers. Until you have one, keep your game's networking behind one small module (connect, send, and a callback when the state changes), so moving to LobbyLab later changes one file. --- # LobbyLab for coding agents LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: alpha. There are no public keys yet, and `@lobbylab/client` and `@lobbylab/node` are not on npm yet; both arrive with the alpha. What works today: the `lobbylab` package's `docs` and `mcp` commands, and lobbylab.gg, where people describe a game, LobbyLab builds it, and friends play it on phone or web. ## Install the MCP server It runs locally over stdio, needs no key and makes no network calls. Tools: `lobbylab_docs` (read these docs) and `lobbylab_multiplayer_quickstart` (the honest starting point for a stack). Both are read-only. Claude Code: ```sh claude mcp add lobbylab -- npx -y lobbylab mcp ``` Codex: ```sh codex mcp add lobbylab -- npx -y lobbylab mcp ``` Cursor (`.cursor/mcp.json`) and most other MCP clients: ```json { "mcpServers": { "lobbylab": { "command": "npx", "args": ["-y", "lobbylab", "mcp"] } } } ``` VS Code (`.vscode/mcp.json`): ```json { "servers": { "lobbylab": { "type": "stdio", "command": "npx", "args": ["-y", "lobbylab", "mcp"] } } } ``` On native Windows, if a client cannot start `npx`, run it through `cmd`: command `cmd`, arguments `/c npx -y lobbylab mcp`. Without MCP, the same text is one command away: `npx -y lobbylab docs`. ## Rules for agents during the alpha - Do not install `@lobbylab/client` or `@lobbylab/node`, and do not write code that imports them, until the person has an alpha key and the packages are on npm. - Do not invent keys, endpoints, package versions or features. If the person needs something the alpha does not have yet, say so plainly. - Keys, once the person has them: the publishable key goes in `LOBBYLAB_PUBLISHABLE_KEY` in `.env` (safe in the browser). The secret key (`llsk_...`) goes only in `.env.local` for a backend: never in client code, never printed into chat, logs or commits. - Test with two players by opening two browser tabs; each tab is its own player. - If the person only wants to play a game with friends and not build one, lobbylab.gg builds and hosts it for them today. ## AGENTS.md section for a project that uses LobbyLab ```markdown ## Multiplayer (LobbyLab) This project uses LobbyLab for online multiplayer. Do not write a WebSocket or socket.io server. - Status: alpha. `@lobbylab/client` is not on npm yet. Add it only once the person has an alpha key (request one at https://lobbylab.gg/developers) and the package is published. - Client: `@lobbylab/client`. The publishable key is LOBBYLAB_PUBLISHABLE_KEY in .env (safe in the browser). - Never put the secret key (llsk_...) in client code. It lives in .env.local for your backend only. - Docs for agents: `npx -y lobbylab docs`, or the MCP server `npx -y lobbylab mcp`. - Test with two players: open two browser tabs; each tab is its own player. ``` ## A prompt to copy > Make this game online multiplayer with LobbyLab. First run `npx -y lobbylab docs` and read it. Keep my UI and engine. Tell me what works today during the alpha and what I need to get started, then use `@lobbylab/client` for rooms, invite codes and shared state once I have an alpha key. Test with two browser tabs and give me the invite link. ## Coming with the alpha and after - `@lobbylab/client` and `@lobbylab/node` on npm. - `npx -y lobbylab@1 init`: a sandbox project with no account; the keys are written to `.env` and `.env.local`, never printed. - MCP tools to create projects and rooms, read status and usage, and add test players to a room. - A Claude Code plugin with a LobbyLab skill and this MCP server, and AGENTS.md in every LobbyLab template. --- # LobbyLab client SDK (`@lobbylab/client`) LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: preview. `@lobbylab/client` and `@lobbylab/node` are not on npm yet; both arrive with the alpha, and the API below may change before then. Do not add them to a project yet. To connect today from any language, use the wire protocol (the protocol doc). ## What the SDK does for you - Signs players in: anonymous players with your publishable key, or players from your own login through a token your backend mints with `@lobbylab/node`. - Creates, joins and quick-joins rooms, and lists open public rooms. - Keeps each player's view of the room current: the shared document, every player's public slot, the player's own private data, the host, room metadata and whether the room is locked. - Sends state changes and messages, and resolves or rejects each with the server's answer. - Reconnects on its own, and each change carries its own id, so a retry never applies it twice; your state is current after every reconnect. - Runs in browsers, Node, Bun, Deno and React Native, with no dependencies. ## Connect ```js import { LobbyLab } from "@lobbylab/client"; const lobby = new LobbyLab({ key: "llpk_...", // publishable key: safe in a browser, checked against your allowed origins name: "Maya", // display name for an anonymous player // or: authEndpoint: "/api/lobbylab-token", for players from your own login }); ``` Keys: a publishable key (`llpk_...`) for clients and a secret key (`llsk_...`) for your backend only. A secret key sent from a browser is refused with `secret_key_in_browser`. ## Rooms ```js const room = await lobby.joinOrCreate("default"); // quick join: the fullest open public room of this type, or a new one const byCode = await lobby.join("KXPTQM"); // a 6-letter room code a friend shared const mine = await lobby.create({ maxPlayers: 4 }); // a new room; share mine.code const open = await lobby.listRooms({ type: "default" }); ``` A room type (set once for your project, for example `default` or `race`) decides whether rooms are turn-based or live (20 Hz), how many players they hold, whether they are public, and who may write where. ## Shared state Every player sees a `RoomView`: ```ts interface RoomView { doc: Json; // the shared document everyone reads players: Record; // one public slot per player, by memberId private: Json | null; // this player's private data only host: string | null; // the host's memberId meta: Record; // public room metadata, at most 2 KB locked: boolean; version: number; // goes up by one on every accepted change } ``` ```js room.state.on("change", (view, info) => render(view)); await room.state.set("doc/board/4", "x"); // resolves when the server accepts it await room.state.inc("doc/score/red", 1); await room.state.push("doc/log", { move: "e4" }, { max: 50 }); await room.state.transact( [{ op: "set", path: "doc/turn", value: "blue" }], { guards: [{ path: "doc/turn", equals: "red" }] }, // refused with guard_failed if someone moved first ); room.state.me.set({ x: 12, y: 40 }); // your own slot; in live rooms it is your input each tick ``` Paths start with `doc/`, `players//`, `private//` or `meta/`. Up to 16 operations go in one change, applied in order, all or none. A refused change rejects with a code such as `forbidden_path`, `guard_failed`, `version_conflict` or `too_large`. ## Messages ```js room.send("emote", { kind: "wave" }); // to everyone room.send("hint", { cell: 3 }, { to: ["m_2"] }); // to chosen players ("all", "others", "host", or member ids) room.on("message:emote", ({ from, data }) => showEmote(from, data)); ``` A message's data is at most 4 KB. ## Members, the host and the room's end ```js room.members.on("change", () => drawRoster(room.members.list())); if (room.isHost) await room.lock(true); // the host can also kick, set metadata, hand over the host role and close room.on("reconnecting", () => showBanner("Reconnecting")); room.on("closed", ({ reason }) => showEnd(reason)); // host_closed, empty, time_limit, host_left, ... await room.leave(); ``` The room lifecycle, limits and close reasons are in the rooms doc; every error code has a page at https://lobbylab.gg/docs/errors. ## Your backend: `@lobbylab/node` ```js import { LobbyLabServer } from "@lobbylab/node"; const lobbylab = new LobbyLabServer({ secretKey: process.env.LOBBYLAB_SECRET_KEY }); const { token } = await lobbylab.tokens.create({ userId: user.id, name: user.displayName }); ``` Use it to mint player tokens for your own signed-in players, create reserved rooms, list rooms and read usage. It uses only `fetch` and Web Crypto, so it also runs in Workers, Vercel Edge, Deno and Bun. ## Until the SDK ships Keep your game's networking behind one small module (connect, send, and a callback when the state changes), so moving to LobbyLab later changes one file. The planned REST routes are in the API reference: https://lobbylab.gg/docs/api. --- # LobbyLab rooms: the room lifecycle LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: the room engine below runs every room on lobbylab.gg today. The parts marked (API) arrive with the Multiplayer API alpha and may change before then. ## Two kinds of rooms, one engine - **Rooms of games built on LobbyLab** (every game on lobbylab.gg): the game's own rules run on LobbyLab's servers, and the room has a lobby, a start and an end. - **Rooms for your own game** (API): your game keeps its rules; the room holds shared state, messages and members. It is open from the moment it is created (there is no start call) until it closes. Both use the same sockets, members, reconnection and the `multiplayer-wire/v0` protocol. ## Rooms of games built on LobbyLab ```text lobby ──the host starts──> active ──the host ends it, the game ends it, the time limit, or faults──> ended ``` | Phase | What happens | |---|---| | `lobby` | Players join and take seats. The host picks the settings, for example how many AI players fill empty seats | | `active` | The game runs: turn-based games change on each accepted action, live games also advance on a fixed tick | | `ended` | Final. Nothing more happens, and the sockets close. Start a new room to play again | A finished match does not end the room: games show the results and offer a rematch, so everyone stays together. A live game's room ends after its time limit (at most 120 minutes) or when its rules fail three ticks in a row. ## Rooms for your own game (API) A room is `open` from creation until it is `closed`, with one of these reasons: | Reason | When | |---|---| | `host_closed` | The host closed the room | | `empty` | Everyone left and nobody came back within the room type's `emptyTtlSec` (live 10 to 600 s, default 60; turn-based 60 s to 24 h, default 15 min) | | `time_limit` | The room reached `maxLifetimeMin` (live up to 120 minutes, the default; turn-based up to 24 hours, the default) | | `host_left` | The host left and the room type does not hand the host role on (`hostMigration: "none"`) | | `server_closed` | Your backend closed it with the secret key | | `project_disabled` | The project was turned off | The host role passes to the longest-seated player when the host leaves (`hostMigration: "oldest"`, the default), after `hostGraceSec` (default 10 s). A locked room refuses new joins; seated members can still reconnect. ## Members - Everyone connected to a room is a **member** with a `memberId`, a display name, whether they are connected now, and roles. - The **host** starts the game (rooms of games on LobbyLab) or controls the room (API: kick, lock, room metadata, hand over the host role, close). - **Players** hold seats; **spectators** watch and do not count toward `maxPlayers` (API). - **AI players** fill empty seats in games that have them, chosen by the host before the start. They follow the game's own rules. ## Joining 1. Get a seat: its credential and socket address. On lobbylab.gg the invite link (`https://lobbylab.gg/join/`) does this. With the API: `POST /v1/rooms/join` with a room code, `POST /v1/rooms/{roomId}/join`, or quick join with `POST /v1/matchmake`, which seats you in the fullest open public room of a type or creates one. 2. Open the WebSocket and send `hello` (see the protocol doc). The server answers `ready` with your `memberId`, the phase and your roles, then the current view. 3. Games decide whether friends can join after the start (drop in) or only in the lobby. Room codes are 6 letters from `BCDFGHJKLMNPQRSTVWXZ`: no vowels, so a code never spells a word (API). ## Staying connected - **Reconnecting.** A new `hello` with the same seat returns the same member and a fresh view. Clients reconnect after 0.8 s, 1.6 s, 3.2 s, then every 5 s; the SDK does this for you. - **Liveness.** The server pings every 4 seconds and drops a socket after 2 missed pings; the member shows as disconnected until it is back. - **Seats.** A `4401` close means the seat credential is no longer valid: ask for a fresh one once (API: `POST /v1/rooms/{roomId}/seat`), then reconnect. - **Leaving.** `POST /v1/rooms/{roomId}/leave` (API), a kick, a seat left disconnected past `seatTimeoutSec`, or the project turned off. The member's sockets close with `4403` and the reason. ## Turn-based and live rooms | | Turn-based (`tick: 0`) | Live (`tick: 20`) | |---|---|---| | Changes | One accepted change at a time, each saved | Each player's input slot, applied on a 20 Hz tick | | What clients get | `snapshot` after each change | `frame` messages at the frame rate | | Sizes (API) | state up to 64 KB | a player slot up to 1 KB; the view up to 8 KB per frame | | Good for | Board, card, trivia and word games | Racing, arenas, action games | ## Limits (API) - 1 to 16 players per room (default 8), plus spectators (default 8, up to 32 by plan). - A room seats at most 4 x `maxPlayers` members over its life; after that, joins get `room_full`. - A player can be in at most 3 rooms at once (`player_rooms_limit_reached`). - Rooms at once and players online at once have plan limits (`rooms_limit_reached`, `ccu_limit_reached`). Every error code has its own page: https://lobbylab.gg/docs/errors. ## How long rooms are kept LobbyLab keeps a room's record, including the names used in it, for up to 30 days after the room ends, then deletes it. See the Privacy Policy at https://lobbylab.gg/legal/privacy. --- # LobbyLab wire protocol (multiplayer-wire/v0) LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: `multiplayer-wire/v0` runs every room on lobbylab.gg today. The Multiplayer API alpha reuses it on its own socket path. The REST routes that hand out a seat and the `lobbylab.*` intent names below arrive with the alpha and may change before then. The SDKs hide this protocol; you need it only to write a client in another language. Messages are JSON text frames. Every message has a `type`. ## Handshake 1. Get a seat. Planned for the alpha: `POST https://api.lobbylab.gg/v1/rooms/join` with `{"code":"KXPTQM"}` returns `seat.wsUrl`, `seat.memberId` and `seat.credential`. 2. Open a WebSocket to `seat.wsUrl`. Within 5 seconds, send: ```json {"type":"hello","wireVersion":"multiplayer-wire/v0","sessionId":"","credential":"","logicalStreamId":""} ``` 3. The server answers `ready`, then a `snapshot` (turn-based rooms) or `frame` messages (live rooms), and a `members` list. Without a `hello` in 5 seconds, the server closes with `4401`. ## Client messages | type | Fields | Use | |---|---|---| | `hello` | `wireVersion`, `sessionId` (1 to 200 characters), `credential` (16 to 2,000), `logicalStreamId` (8 to 200) | The first message on every socket | | `intent` | `intentId` (8 to 200 characters, unique), `clientSequence` (goes up on each socket), `name` (1 to 100), `payload`, optional `basedOnRevision` | A change or action. Retrying with the same `intentId` is safe | | `input` | `seq`, `payload` (at most 1 KiB) | Live rooms only: your whole player slot. `seq` must keep increasing across reconnects, so start it at the current time in milliseconds | | `ping` | `nonce` | The server answers `pong` with the same nonce | | `resync` | none | Asks for a fresh snapshot | ## Server messages | type | Meaning | |---|---| | `ready` | You are seated: `memberId`, `phase`, `revision`, `roles`; in live rooms also the tick, frame and input rates | | `members` | The member list (`memberId`, `displayName`, `connected`, `roles`), sent on every change | | `snapshot` | Turn-based rooms: `revision`, `viewHash`, your `view` after a change, and its `events` | | `frame` | Live rooms: `tick`, `serverTimeMs`, your `view`, `events` and `inputAck` (your highest input `seq` used) | | `ack` | Your intent was accepted: `intentId`, `triggerSequence`, `revision` | | `rejection` | Your intent was refused: `code`, `retryable`, and `intentId` when known | | `phase` | The room phase changed; `ended` means the room closed | | `pong`, `fatal_error` | Liveness answer; unrecoverable error with a `code` | ## Intents planned for the alpha ```ts { name: "lobbylab.state", payload: { ops: StateOp[]; guards?: { path: string; equals: Json }[]; ifVersion?: number } } { name: "lobbylab.send", payload: { topic: string; data: Json; to?: "all" | "others" | "host" | string[]; echo?: boolean } } { name: "lobbylab.host", payload: { action: "kick" | "lock" | "meta" | "transfer" | "close"; ... } } ``` ## Close codes | Code | Meaning | What a client does | |---|---|---| | `4401` | Bad or expired seat credential | Renew the seat once, then reconnect; stop if that fails | | `4403` | Removed (left, kicked, banned, room closed) | Stop and report the reason | | `4400` | A message the server could not accept | Stop | | `4429` | Rate or abuse limit | Stop and show the error | | `1011`, `1006`, `1001`, any other code, or no answer | Transient | Reconnect after 0.8 s, 1.6 s, 3.2 s, then every 5 s | ## Staying connected - The server sends WebSocket protocol pings every 4 seconds and drops a socket after 2 missed pings. Browsers answer these on their own. - A new `hello` with the same seat returns the same member and a fresh snapshot or frame, so state is current after every reconnect. --- # LobbyLab status: what works today and what is coming LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through `@lobbylab/client`, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers. Status: alpha. There are no public keys yet, and `@lobbylab/client` and `@lobbylab/node` are not on npm yet; both arrive with the alpha. What works today: the `lobbylab` package's `docs` and `mcp` commands, and lobbylab.gg, where people describe a game, LobbyLab builds it, and friends play it on phone or web. | Piece | State | |---|---| | lobbylab.gg (describe a game, LobbyLab builds it, friends play it) | Live | | Wire protocol `multiplayer-wire/v0` | Live: every room on lobbylab.gg uses it | | `lobbylab` on npm: `docs` and `mcp` commands | Available in this package | | MCP tools `lobbylab_docs`, `lobbylab_multiplayer_quickstart` | Available (read-only, bundled docs) | | Multiplayer API (`api.lobbylab.gg`), keys, rooms, invite codes, matchmaking, shared state | Not live yet: `api.lobbylab.gg` does not answer until the alpha opens. Request access at https://lobbylab.gg/developers | | `@lobbylab/client`, `@lobbylab/node` | Arrive with the alpha | | `npx -y lobbylab@1 init` sandbox with no account | Planned after the alpha opens | | MCP tools that create projects and rooms, show usage, add test players | Planned after the alpha opens | | Game rules that run on LobbyLab's servers | Later | | SDKs for Flutter and Godot | Later | | Plans and pricing | Not charging during the alpha | This package never claims a feature before it exists. If a command or tool you expect is missing, it has not shipped yet.