Content
# pixijs-mcp
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets an AI agent
**inspect and control any running [PixiJS](https://pixijs.com) application** live in the
browser. It attaches to a Chrome tab over the Chrome DevTools Protocol (CDP), discovers the
PixiJS `Application`, and exposes the scene graph, renderer/ticker stats, screenshots, and
mutation tools.
Use it to ask questions like _"what's in my stage right now?"_, _"why is this sprite
invisible?"_, _"what's my current FPS?"_, or to poke at node properties and slow down the
ticker while debugging animations.
```
MCP client (Cursor, Claude, …) ──stdio──► pixijs-mcp ──CDP──► Chrome tab (your PixiJS app)
```
## Quick start
### 1. Make your PixiJS app discoverable
Expose your `Application` on the global scope (this is also what the official PixiJS
DevTools extension looks for):
```js
import { Application } from 'pixi.js'
const app = new Application()
await app.init({ width: 800, height: 600 })
// Either of these is enough:
globalThis.__PIXI_APP__ = app
// or
globalThis.__PIXI_DEVTOOLS__ = { app }
```
If you can't set a global, you can instead point the server at a custom expression with the
`PIXI_APP_EXPR` environment variable (see [Configuration](#configuration)).
### 2. Run Chrome with remote debugging
```bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
# Linux
google-chrome --remote-debugging-port=9222
# Windows
chrome.exe --remote-debugging-port=9222
```
Open your PixiJS app in that Chrome instance.
### 3. Install & build
```bash
npm install
npm run build
```
Or run straight from source with no build step using `tsx`:
```bash
npm run dev
```
### 4. Register with your MCP client
Once published to npm you can run it with `npx -y pixijs-mcp`. To run a local checkout
instead, swap the command for `node /absolute/path/to/pixijs-mcp/dist/index.js` (or
`npx tsx /absolute/path/to/pixijs-mcp/src/index.ts` to skip the build).
#### Cursor
Add to `~/.cursor/mcp.json` (global) or project `.cursor/mcp.json`:
```json
{
"mcpServers": {
"pixijs": {
"command": "npx",
"args": ["-y", "pixijs-mcp"],
"env": {
"PIXI_URL": "localhost:3000"
}
}
}
}
```
#### Claude Desktop
Edit `claude_desktop_config.json` (Settings → Developer → Edit Config), then restart Claude.
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"pixijs": {
"command": "npx",
"args": ["-y", "pixijs-mcp"],
"env": {
"PIXI_URL": "localhost:3000"
}
}
}
}
```
#### Claude Code (CLI)
Register it with `claude mcp add` (use `--scope user` to make it available in every project):
```bash
claude mcp add pixijs \
--scope user \
--env PIXI_URL=localhost:3000 \
-- npx -y pixijs-mcp
```
Then check it with `claude mcp list` and `claude mcp get pixijs`.
## Configuration
All configuration is via environment variables:
| Variable | Default | Purpose |
| --------------- | ----------- | ------------------------------------------------------------------------------------------------ |
| `CDP_HOST` | `localhost` | Chrome DevTools host. |
| `CDP_PORT` | `9222` | Chrome remote-debugging port. |
| `PIXI_URL` | _(unset)_ | Substring used to pick the tab. When unset, attaches to the first regular page tab. |
| `PIXI_APP_EXPR` | _(unset)_ | JS expression returning your `Application` if it isn't on a known global (e.g. `window.myGame.app`). |
## Tools
### Inspection
| Tool | Description |
| ----------------- | -------------------------------------------------------------------------------------------- |
| `check_connection`| Verify CDP reachability and whether a PixiJS app was found (reports PixiJS version). |
| `get_app_info` | Renderer type/size/resolution, screen dims, ticker stats, stage child count. |
| `get_ticker` | FPS, minFPS, maxFPS, deltaMS, deltaTime, speed, started, listener count. |
| `count_nodes` | Total display objects and a breakdown by type. |
| `get_scene_tree` | Recursively serialize the scene graph (configurable `path` + `depth`). |
| `get_node` | Detailed single-node info: transform, bounds, texture/text, direct children. |
| `find_nodes` | Search the tree by label and/or type substring. |
### Visuals & raw access
| Tool | Description |
| ---------------- | -------------------------------------------------------------------- |
| `get_screenshot` | PNG screenshot of the page (optionally at a higher `scale`). |
| `eval` | Evaluate an arbitrary JS expression in the page (escape hatch). |
| `send_key` | Dispatch a keyboard press (named keys or single letters/digits). |
### Mutation & control
| Tool | Description |
| ------------------- | ------------------------------------------------------------------------------------------- |
| `set_node_property` | Set `x`, `y`, `alpha`, `rotation`, `angle`, `visible`, `renderable`, `zIndex`, `tint`, `scaleX`, `scaleY`. |
| `control_ticker` | `stop` / `start` / `update` the render loop (step a single frame while stopped). |
| `set_ticker_speed` | Set the ticker speed multiplier (slow-mo / fast-forward). |
## Node paths
Nodes are addressed by a `/`-joined chain of child indices from the stage. `""` is the
stage; `"0"` is its first child; `"0/2/1"` is the second child of the third child of the
first child. Use `find_nodes` to discover paths, then `get_node` / `set_node_property` to
act on them.
## Architecture
The page-side logic lives in a single real function (`src/agent.ts`) rather than a pile of
interpolated code strings. On first use it is serialized with `Function.prototype.toString()`
and injected once to install `window.__PIXI_MCP__`. Each tool then calls a named method on
`window.__PIXI_MCP__.api`, passing its arguments as **JSON data** — request data is never
interpolated into page code, which avoids escaping and injection pitfalls. The agent is
re-injected automatically if the page reloads. The only dynamic code is the optional,
operator-provided `PIXI_APP_EXPR`.
## Compatibility
Designed for **PixiJS v8** (uses `label`, `app.renderer`, `app.ticker`). It also works with
v6/v7 apps that expose `name`/`stage`/`renderer`/`ticker`, and degrades gracefully when only
a stage or renderer is exposed.
## License
[MIT](./LICENSE)