@debuffjp/mcp 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/bin/debuff-mcp.js +3 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +72 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +241 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +50 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +119 -0
- package/dist/session.js.map +1 -0
- package/package.json +63 -4
package/README.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @debuffjp/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server that lets AI agents inspect, build, test and **play** a debuff game. It uses the same evidence a human would want: real state, real frames, real test results.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
New projects from `npm create debuff` include a `.mcp.json`, so MCP clients that read it (such as Claude Code) pick the server up automatically. For other clients:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
claude mcp add debuff -- npx -y @debuffjp/mcp # Claude Code
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"debuff": { "command": "npx", "args": ["-y", "@debuffjp/mcp"] }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run it from the game's folder, or pass `--root <dir>` (or set `DEBUFF_ROOT`).
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `inspect_project` | Dependencies, plugins and their contributions, assets, last build size |
|
|
28
|
+
| `list_assets` | Every asset with its type, size and importer, plus warnings and possibly unused ids |
|
|
29
|
+
| `build` | Production build plus size report; `leaks` must be empty |
|
|
30
|
+
| `run_scenarios` | Run `scenarios/*.json` headless; per-step results, errors, captures |
|
|
31
|
+
| `fuzz` | Play with random input; each crash, stuck state or back-and-forth loop comes with an explained error and a scenario that replays it |
|
|
32
|
+
| `game_start` | Attach to the live game, or open a private headless copy, paused (see below) |
|
|
33
|
+
| `game_step` | Advance fixed simulation steps (pausing first); returns state |
|
|
34
|
+
| `game_pause` | Pause or resume a live game |
|
|
35
|
+
| `game_frames` | Recent frame breakdown: update/render ms, catch-up steps, quads, draw calls, long tasks |
|
|
36
|
+
| `game_notes` | Moments the human flagged in the overlay, with the state at that tick |
|
|
37
|
+
| `game_replay` | Save the live session up to a tick (for example a note's) as a scenario in `.debuff/replays/` that reproduces it |
|
|
38
|
+
| `game_input` | `press` / `release` keys; `tap`, `pointer_down`, `pointer_move`, `pointer_up` |
|
|
39
|
+
| `game_state` | Scene, tick, renderer, frame stats, your `inspect()` data |
|
|
40
|
+
| `game_params` / `game_set_param` | Read and tune typed params (validated) |
|
|
41
|
+
| `game_capture` | The current frame as a PNG image |
|
|
42
|
+
| `game_errors` | Runtime errors, explained: whose problem (your code, a rejected call, a debuff bug), the source line, a hint |
|
|
43
|
+
| `game_stop` | Close the game |
|
|
44
|
+
|
|
45
|
+
## Prompts
|
|
46
|
+
|
|
47
|
+
Workflows clients can offer as slash commands: `playtest`, `fix-errors`, `tune-feel`, `add-mechanic` and `explain-stutter`.
|
|
48
|
+
|
|
49
|
+
## Live and headless
|
|
50
|
+
|
|
51
|
+
If you have the game open under `debuff dev`, `game_start` attaches to **that tab** (mode `live`). The overlay shows an **agent** badge, and every pause, step and param change the agent makes appears in its activity log, where you can undo it. Type a note in the overlay ("jump feels floaty here") to flag a moment; the agent reads it, along with the game state at that tick, through `game_notes`. `game_stop` resumes your game and disconnects.
|
|
52
|
+
|
|
53
|
+
Otherwise, or with `mode: "headless"`, the agent gets a private copy in a headless browser. Time only advances through `game_step`, so what it sees is reproducible, and a scenario can replay it exactly.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const rootFlag = args.indexOf("--root");
|
|
7
|
+
const root = resolve(rootFlag >= 0 ? (args[rootFlag + 1] ?? ".") : (process.env.DEBUFF_ROOT ?? process.cwd()));
|
|
8
|
+
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
9
|
+
const server = createServer({ root, version });
|
|
10
|
+
await server.connect(new StdioServerTransport());
|
|
11
|
+
// stdout carries the protocol, so diagnostics go to stderr.
|
|
12
|
+
console.error(`debuff MCP server v${version} ready (root: ${root})`);
|
|
13
|
+
const shutdown = async () => {
|
|
14
|
+
await server.close();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
};
|
|
17
|
+
process.on("SIGINT", shutdown);
|
|
18
|
+
process.on("SIGTERM", shutdown);
|
|
19
|
+
process.stdin.on("close", shutdown);
|
|
20
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC/G,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAC;AAEzH,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC/C,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AAEjD,4DAA4D;AAC5D,OAAO,CAAC,KAAK,CAAC,sBAAsB,OAAO,iBAAiB,IAAI,GAAG,CAAC,CAAC;AAErE,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;IAC1B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;AACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAsB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAoB,eAAe,EAAE,WAAW,EAAkB,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ready-made workflows for agents, exposed as MCP prompts (slash commands in clients like Claude Code).
|
|
4
|
+
* Each one is a short playbook built only from this server's tools and the project's npm scripts.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerPrompts(server: McpServer): void;
|
|
7
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2FvD"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const user = (text) => ({ messages: [{ role: "user", content: { type: "text", text } }] });
|
|
3
|
+
/** Rules every workflow shares: evidence first, the human owns creative calls. */
|
|
4
|
+
const RULES = `Rules: the human makes creative calls (art, feel, difficulty, names). Propose options instead of deciding. Don't update screenshot baselines without their approval. If game_errors reports kind "debuff-bug", tell the human and share its reportUrl instead of working around debuff.`;
|
|
5
|
+
/**
|
|
6
|
+
* Ready-made workflows for agents, exposed as MCP prompts (slash commands in clients like Claude Code).
|
|
7
|
+
* Each one is a short playbook built only from this server's tools and the project's npm scripts.
|
|
8
|
+
*/
|
|
9
|
+
export function registerPrompts(server) {
|
|
10
|
+
server.registerPrompt("playtest", {
|
|
11
|
+
title: "Playtest the game",
|
|
12
|
+
description: "Play the game, look for bugs and rough edges, and report findings with evidence.",
|
|
13
|
+
argsSchema: { focus: z.string().optional().describe("What to focus on, e.g. 'the second level' or 'jumping'.") },
|
|
14
|
+
}, ({ focus }) => user(`Playtest this debuff game${focus ? `, focusing on ${focus}` : ""}.
|
|
15
|
+
|
|
16
|
+
1. Call game_start. In live mode, read game_notes first: they're moments the human flagged.
|
|
17
|
+
2. Call game_state and game_capture to see where things are, then play: game_input for keys and pointer, game_step to advance time (60 steps = 1 second). Capture before and after anything interesting.
|
|
18
|
+
3. Watch game_errors throughout, and use game_frames if anything stutters.
|
|
19
|
+
4. Report a short list of findings. For each: what you did, what happened, what you expected, a capture, and severity (bug, feel, polish). Suggest fixes, but don't change code yet.
|
|
20
|
+
|
|
21
|
+
${RULES}`));
|
|
22
|
+
server.registerPrompt("fix-errors", {
|
|
23
|
+
title: "Fix runtime errors",
|
|
24
|
+
description: "Read the explained runtime errors, fix the ones in this game's code, and verify.",
|
|
25
|
+
}, () => user(`Fix this game's runtime errors.
|
|
26
|
+
|
|
27
|
+
1. Call game_start, then game_errors. Each error says whose problem it is (kind), where (a source file and line) and what to try (hint).
|
|
28
|
+
2. kind "your-code": open the file at "where", fix the cause rather than the symptom, and keep the change small.
|
|
29
|
+
kind "debuff-usage": debuff rejected a call. The message says what it expected, so fix the call site.
|
|
30
|
+
kind "debuff-bug": don't patch around it. Tell the human and share reportUrl.
|
|
31
|
+
kind "outside": usually a browser extension. Mention it and move on.
|
|
32
|
+
3. Reload (game_start again), reproduce the steps that caused the error with game_input and game_step, and confirm game_errors stays empty.
|
|
33
|
+
4. Run \`npm test\` and \`npm run build\`, then report what you changed and how you verified it.
|
|
34
|
+
|
|
35
|
+
${RULES}`));
|
|
36
|
+
server.registerPrompt("tune-feel", {
|
|
37
|
+
title: "Tune game feel",
|
|
38
|
+
description: "Propose param changes for a feel goal, try them live, and let the human choose.",
|
|
39
|
+
argsSchema: { goal: z.string().describe("The feel to aim for, e.g. 'jumps feel floaty' or 'make movement snappier'.") },
|
|
40
|
+
}, ({ goal }) => user(`Help tune this game's feel. Goal: ${goal}
|
|
41
|
+
|
|
42
|
+
1. Call game_start, then game_params to see every tunable value with its range and units. In live mode, read game_notes too.
|
|
43
|
+
2. Propose two or three variants, each a small set of param changes with a one-line reason. Prefer params over code changes.
|
|
44
|
+
3. With the human's go-ahead, apply one variant with game_set_param, play a few seconds (game_input and game_step) and capture it. The human can undo every change in the dev panel.
|
|
45
|
+
4. Once the human picks a variant, update the defaults in game.params.define(...) in the code, and add or adjust a scenario if the change affects behaviour.
|
|
46
|
+
|
|
47
|
+
Feel is a creative call: present the options and let the human choose. ${RULES}`));
|
|
48
|
+
server.registerPrompt("add-mechanic", {
|
|
49
|
+
title: "Add a mechanic",
|
|
50
|
+
description: "Implement a gameplay mechanic in small steps, with a scenario that proves it works.",
|
|
51
|
+
argsSchema: { mechanic: z.string().describe("The mechanic, e.g. 'a double jump' or 'coins that respawn'.") },
|
|
52
|
+
}, ({ mechanic }) => user(`Add this mechanic: ${mechanic}
|
|
53
|
+
|
|
54
|
+
1. Call inspect_project and read src/main.ts to see how scenes, input and params are set up. Confirm the plan with the human in two or three lines before building, and ask about anything creative (look, sound, numbers).
|
|
55
|
+
2. Implement the smallest version that works. Put tunable numbers in game.params.define(...), use debuff's draw, input and juice APIs, and keep dev-only code inside if (import.meta.env.DEV).
|
|
56
|
+
3. Add a scenario in scenarios/ that drives the inputs and checks the outcome with expect on state from the scene's inspect().
|
|
57
|
+
4. Verify: run_scenarios, then build (its leaks list must be empty), then game_start and game_capture to show it working. Check game_errors is empty.
|
|
58
|
+
5. Report what you built, the params added, and the evidence.
|
|
59
|
+
|
|
60
|
+
${RULES}`));
|
|
61
|
+
server.registerPrompt("explain-stutter", {
|
|
62
|
+
title: "Explain stutter",
|
|
63
|
+
description: "Find out why frames drop, and whether the game or something outside it is the cause.",
|
|
64
|
+
}, () => user(`Find out why this game stutters.
|
|
65
|
+
|
|
66
|
+
1. Call game_start (live mode gives real timings; headless timings only reflect stepping), then game_frames with count 240.
|
|
67
|
+
2. Classify the dropped frames (intervalMs over about 25): high updateMs means game logic; high renderMs with many quads or drawCalls means drawing; steps over 1 means the loop was catching up; longTaskMs or little work means something outside the game (GC, the browser, other tabs).
|
|
68
|
+
3. Point to the likely code (the update or render of the active scene, from game_state) and propose a fix. Measure again after any change.
|
|
69
|
+
|
|
70
|
+
${RULES}`));
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;AAErH,kFAAkF;AAClF,MAAM,KAAK,GAAG,yRAAyR,CAAC;AAExS;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,UAAU,EACV;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,kFAAkF;QAC/F,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yDAAyD,CAAC,EAAE;KACjH,EACD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACZ,IAAI,CAAC,4BAA4B,KAAK,CAAC,CAAC,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;EAO1E,KAAK,EAAE,CAAC,CACP,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,YAAY,EACZ;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,kFAAkF;KAChG,EACD,GAAG,EAAE,CACH,IAAI,CAAC;;;;;;;;;;EAUT,KAAK,EAAE,CAAC,CACP,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,WAAW,EACX;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,iFAAiF;QAC9F,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4EAA4E,CAAC,EAAE;KACxH,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACX,IAAI,CAAC,qCAAqC,IAAI;;;;;;;yEAOqB,KAAK,EAAE,CAAC,CAC9E,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,qFAAqF;QAClG,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6DAA6D,CAAC,EAAE;KAC7G,EACD,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CACf,IAAI,CAAC,sBAAsB,QAAQ;;;;;;;;EAQvC,KAAK,EAAE,CAAC,CACP,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,sFAAsF;KACpG,EACD,GAAG,EAAE,CACH,IAAI,CAAC;;;;;;EAMT,KAAK,EAAE,CAAC,CACP,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface ServerOptions {
|
|
3
|
+
/** Default project root for tools that don't receive one. */
|
|
4
|
+
root: string;
|
|
5
|
+
version: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* debuff's MCP server: project tools (inspect, assets, build, scenarios) and live-game tools that drive
|
|
9
|
+
* a running game through the same dev hooks scenarios use. Every result is JSON, plus images for captures.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createServer(options: ServerOptions): McpServer;
|
|
12
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAkV9D"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { buildReport, compileAssets, findUnused, inspectProject, loadConfig, runFuzz, runScenarios, viteConfig } from "@debuffjp/cli";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { registerPrompts } from "./prompts.js";
|
|
7
|
+
import { startSession } from "./session.js";
|
|
8
|
+
const json = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
9
|
+
const fail = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
10
|
+
/**
|
|
11
|
+
* debuff's MCP server: project tools (inspect, assets, build, scenarios) and live-game tools that drive
|
|
12
|
+
* a running game through the same dev hooks scenarios use. Every result is JSON, plus images for captures.
|
|
13
|
+
*/
|
|
14
|
+
export function createServer(options) {
|
|
15
|
+
const server = new McpServer({ name: "debuff", version: options.version }, {
|
|
16
|
+
instructions: "Tools for building 2D web games with debuff. Start with inspect_project. To see the game, call game_start, then game_state / game_capture. " +
|
|
17
|
+
"If the human has the game open under `debuff dev`, game_start attaches to it (mode live): they see your actions in their overlay, and their " +
|
|
18
|
+
"flagged moments are in game_notes, so read those first. Otherwise you get a private paused copy (mode headless) where time only advances through game_step. " +
|
|
19
|
+
"Use game_pause before inspecting a live game, and game_frames to explain stutter. Prompts (playtest, fix-errors, tune-feel, add-mechanic, explain-stutter) give step-by-step workflows. Prefer tuning params (game_params, game_set_param) over code changes, " +
|
|
20
|
+
"and verify changes with run_scenarios. Creative direction belongs to the human: propose alternatives rather than deciding art or feel.",
|
|
21
|
+
});
|
|
22
|
+
const rootOf = (root) => resolve(options.root, root ?? ".");
|
|
23
|
+
let session;
|
|
24
|
+
const live = () => {
|
|
25
|
+
if (!session)
|
|
26
|
+
throw new Error("No game running. Call game_start first.");
|
|
27
|
+
return session;
|
|
28
|
+
};
|
|
29
|
+
const safely = (fn) => async (args) => {
|
|
30
|
+
try {
|
|
31
|
+
return await fn(args);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const root = {
|
|
38
|
+
root: z.string().optional().describe("Project root. Absolute path, or relative to the server's root. Default: the server's root."),
|
|
39
|
+
};
|
|
40
|
+
// ---- project tools -------------------------------------------------------------------------
|
|
41
|
+
server.registerTool("inspect_project", {
|
|
42
|
+
title: "Inspect project",
|
|
43
|
+
description: "Summary of the project: dependencies, plugins and their contributions, assets, last build size.",
|
|
44
|
+
inputSchema: root,
|
|
45
|
+
annotations: { readOnlyHint: true },
|
|
46
|
+
}, safely(async ({ root: r }) => json(await inspectProject(rootOf(r)))));
|
|
47
|
+
server.registerTool("list_assets", {
|
|
48
|
+
title: "List assets",
|
|
49
|
+
description: "Run the asset pipeline: every asset id, type, size and importer, plus generated files, warnings and possibly unused ids.",
|
|
50
|
+
inputSchema: root,
|
|
51
|
+
annotations: { readOnlyHint: true },
|
|
52
|
+
}, safely(async ({ root: r }) => {
|
|
53
|
+
const dir = rootOf(r);
|
|
54
|
+
const { plugins, atlas } = await loadConfig(dir);
|
|
55
|
+
const { manifest, importedBy, outputs, warnings } = await compileAssets(dir, { plugins, atlas });
|
|
56
|
+
return json({ assets: manifest.assets, importedBy, generated: [...outputs.keys()], warnings, unused: findUnused(dir, manifest) });
|
|
57
|
+
}));
|
|
58
|
+
server.registerTool("build", {
|
|
59
|
+
title: "Production build",
|
|
60
|
+
description: "Build for production and return the size report (raw, gzip, brotli per file). `leaks` lists dev-only code found in the output; it must be empty.",
|
|
61
|
+
inputSchema: root,
|
|
62
|
+
}, safely(async ({ root: r }) => {
|
|
63
|
+
const dir = rootOf(r);
|
|
64
|
+
const { build } = await import("vite");
|
|
65
|
+
await build(await viteConfig(dir, { logLevel: "silent" }));
|
|
66
|
+
return json(buildReport(resolve(dir, "dist")));
|
|
67
|
+
}));
|
|
68
|
+
server.registerTool("run_scenarios", {
|
|
69
|
+
title: "Run scenarios",
|
|
70
|
+
description: "Run scenarios/*.json (or the given files) in a headless browser. Returns per-step results, runtime errors, captures and final state.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
...root,
|
|
73
|
+
files: z.array(z.string()).optional().describe("Scenario files relative to the root. Default: all."),
|
|
74
|
+
update: z.boolean().optional().describe("Accept new captures as baselines. Only when the human has approved the visual change."),
|
|
75
|
+
},
|
|
76
|
+
}, safely(async ({ root: r, files, update }) => {
|
|
77
|
+
const dir = rootOf(r);
|
|
78
|
+
const { plugins } = await loadConfig(dir);
|
|
79
|
+
const reports = await runScenarios({ root: dir, files: files ?? [], plugins, update: update ?? false, strict: false });
|
|
80
|
+
return json({ passed: reports.filter((x) => x.ok).length, total: reports.length, reports });
|
|
81
|
+
}));
|
|
82
|
+
server.registerTool("fuzz", {
|
|
83
|
+
title: "Fuzz playtest",
|
|
84
|
+
description: "Play the game headlessly with random but plausible input (movement keys held, action keys tapped) to find runtime errors, stuck states and back-and-forth loops. " +
|
|
85
|
+
"Each finding includes the explained error (kind, source-mapped file:line, hint) and `repro`: a scenario file that replays it exactly. " +
|
|
86
|
+
"Pass that file to run_scenarios to reproduce, then again after fixing to verify. The same seed gives the same inputs.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
...root,
|
|
89
|
+
seconds: z.number().min(1).max(600).optional().describe("Simulated seconds of play per run. Default 60."),
|
|
90
|
+
runs: z.number().int().min(1).max(20).optional().describe("Independent runs. Default 3."),
|
|
91
|
+
seed: z.number().int().optional().describe("Input seed; reuse it to repeat a run exactly."),
|
|
92
|
+
keys: z.array(z.string()).optional().describe("KeyboardEvent.code values to press. Default: arrows, WASD, Space, Enter, Z, X."),
|
|
93
|
+
},
|
|
94
|
+
}, safely(async ({ root: r, seconds, runs, seed, keys }) => json(await runFuzz({
|
|
95
|
+
root: rootOf(r),
|
|
96
|
+
...(seconds === undefined ? {} : { seconds }),
|
|
97
|
+
...(runs === undefined ? {} : { runs }),
|
|
98
|
+
...(seed === undefined ? {} : { seed }),
|
|
99
|
+
...(keys ? { keys } : {}),
|
|
100
|
+
}))));
|
|
101
|
+
// ---- live game tools -----------------------------------------------------------------------
|
|
102
|
+
server.registerTool("game_start", {
|
|
103
|
+
title: "Start game",
|
|
104
|
+
description: "Connect to the game. auto (default): attach to the human's live game if `debuff dev` has it open in a browser, else start a private " +
|
|
105
|
+
"headless copy, paused. Returns the mode, state and how many notes the human has flagged. Replaces any running session.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
...root,
|
|
108
|
+
page: z.string().optional().describe('Path to open in headless mode. Default "/".'),
|
|
109
|
+
mode: z.enum(["auto", "live", "headless"]).optional().describe("Default auto."),
|
|
110
|
+
},
|
|
111
|
+
}, safely(async ({ root: r, page, mode }) => {
|
|
112
|
+
await session?.stop();
|
|
113
|
+
session = undefined;
|
|
114
|
+
session = await startSession(rootOf(r), { ...(page ? { page } : {}), ...(mode ? { mode } : {}) });
|
|
115
|
+
return json({ mode: session.mode, url: session.url, state: await session.call("state") });
|
|
116
|
+
}));
|
|
117
|
+
server.registerTool("game_state", {
|
|
118
|
+
title: "Game state",
|
|
119
|
+
description: "Current scene, tick, time, renderer, frame stats, the scene's inspect() data and plugin inspect data.",
|
|
120
|
+
inputSchema: {},
|
|
121
|
+
annotations: { readOnlyHint: true },
|
|
122
|
+
}, safely(async () => json(await live().call("state"))));
|
|
123
|
+
server.registerTool("game_step", {
|
|
124
|
+
title: "Advance time",
|
|
125
|
+
description: "Advance the simulation by fixed steps (60 per second by default) and return the new state.",
|
|
126
|
+
inputSchema: { steps: z.number().int().min(1).max(36_000).describe("Fixed simulation steps to run.") },
|
|
127
|
+
}, safely(async ({ steps }) => json(await live().call("step", steps))));
|
|
128
|
+
server.registerTool("game_input", {
|
|
129
|
+
title: "Send input",
|
|
130
|
+
description: "Keyboard or pointer input. press/release take a KeyboardEvent.code (e.g. Space, ArrowLeft, KeyZ). tap/pointer_down/pointer_move take logical x, y. " +
|
|
131
|
+
"Input takes effect on the next game_step.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
action: z.enum(["press", "release", "tap", "pointer_down", "pointer_move", "pointer_up"]),
|
|
134
|
+
code: z.string().optional().describe("For press/release."),
|
|
135
|
+
x: z.number().optional(),
|
|
136
|
+
y: z.number().optional(),
|
|
137
|
+
},
|
|
138
|
+
}, safely(async ({ action, code, x, y }) => {
|
|
139
|
+
const s = live();
|
|
140
|
+
if (action === "press" || action === "release") {
|
|
141
|
+
if (!code)
|
|
142
|
+
throw new Error(`${action} needs a key code`);
|
|
143
|
+
await s.call(action, code);
|
|
144
|
+
}
|
|
145
|
+
else if (action === "pointer_up") {
|
|
146
|
+
await s.call("pointerUp");
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
if (x === undefined || y === undefined)
|
|
150
|
+
throw new Error(`${action} needs x and y`);
|
|
151
|
+
await s.call({ tap: "tap", pointer_down: "pointerDown", pointer_move: "pointerMove" }[action], x, y);
|
|
152
|
+
}
|
|
153
|
+
return json({ ok: true });
|
|
154
|
+
}));
|
|
155
|
+
server.registerTool("game_params", {
|
|
156
|
+
title: "List params",
|
|
157
|
+
description: "Every tunable param group: schema (type, default, min, max, units, description) and current values.",
|
|
158
|
+
inputSchema: {},
|
|
159
|
+
annotations: { readOnlyHint: true },
|
|
160
|
+
}, safely(async () => json(await live().call("params"))));
|
|
161
|
+
server.registerTool("game_set_param", {
|
|
162
|
+
title: "Set param",
|
|
163
|
+
description: "Set a tunable param live. Values are validated against the param's type and range. Changes last until game_start or a reload.",
|
|
164
|
+
inputSchema: { group: z.string(), key: z.string(), value: z.union([z.number(), z.boolean()]) },
|
|
165
|
+
}, safely(async ({ group, key, value }) => {
|
|
166
|
+
await live().call("setParam", group, key, value);
|
|
167
|
+
return json({ ok: true, group, key, value });
|
|
168
|
+
}));
|
|
169
|
+
server.registerTool("game_capture", {
|
|
170
|
+
title: "Capture frame",
|
|
171
|
+
description: "Screenshot of the current frame as a PNG image.",
|
|
172
|
+
inputSchema: {},
|
|
173
|
+
annotations: { readOnlyHint: true },
|
|
174
|
+
}, safely(async () => {
|
|
175
|
+
const dataUrl = await live().call("capture");
|
|
176
|
+
return { content: [{ type: "image", data: dataUrl.slice(dataUrl.indexOf(",") + 1), mimeType: "image/png" }] };
|
|
177
|
+
}));
|
|
178
|
+
server.registerTool("game_pause", {
|
|
179
|
+
title: "Pause or resume",
|
|
180
|
+
description: "Pause or resume a live game (headless games are always paused). game_step also pauses first.",
|
|
181
|
+
inputSchema: { paused: z.boolean() },
|
|
182
|
+
}, safely(async ({ paused }) => json(await live().call(paused ? "pause" : "resume"))));
|
|
183
|
+
server.registerTool("game_frames", {
|
|
184
|
+
title: "Frame breakdown",
|
|
185
|
+
description: "Recent frames, oldest first: intervalMs (what the player feels), updateMs, renderMs, steps (>1 means catching up), quads, drawCalls and " +
|
|
186
|
+
"longTaskMs (browser-reported main-thread stalls). Use it to explain stutter; in headless mode times reflect stepping, not real play.",
|
|
187
|
+
inputSchema: { count: z.number().int().min(1).max(240).optional().describe("Default 60.") },
|
|
188
|
+
annotations: { readOnlyHint: true },
|
|
189
|
+
}, safely(async ({ count }) => json(await live().call("frames", count ?? 60))));
|
|
190
|
+
server.registerTool("game_notes", {
|
|
191
|
+
title: "Human notes",
|
|
192
|
+
description: "Moments the human flagged in the overlay (live mode): their words, the tick, the scene's inspect() state then, and the frames leading up to it.",
|
|
193
|
+
inputSchema: {},
|
|
194
|
+
annotations: { readOnlyHint: true },
|
|
195
|
+
}, safely(async () => json(await live().call("notes"))));
|
|
196
|
+
server.registerTool("game_replay", {
|
|
197
|
+
title: "Save a replay",
|
|
198
|
+
description: "Everything played since the game loaded (keys, pointer, param changes) up to `toTick` (default: now), saved as a scenario " +
|
|
199
|
+
"in .debuff/replays/ that replays it exactly. Use a note's `tick` to reproduce what the human saw when they flagged it, then " +
|
|
200
|
+
"run_scenarios with the returned file. `warnings` lists anything a replay can't reproduce (for example slow motion).",
|
|
201
|
+
inputSchema: { toTick: z.number().int().min(0).optional().describe("Last tick to include. Default: the current tick.") },
|
|
202
|
+
}, safely(async ({ toTick }) => {
|
|
203
|
+
const replay = await live().call("replay", ...(toTick === undefined ? [] : [toTick]));
|
|
204
|
+
const dir = join(options.root, ".debuff", "replays");
|
|
205
|
+
mkdirSync(dir, { recursive: true });
|
|
206
|
+
const file = join(dir, `replay-tick-${replay.ticks}.json`);
|
|
207
|
+
writeFileSync(file, `${JSON.stringify({ name: replay.name, steps: replay.steps }, null, 2)}\n`);
|
|
208
|
+
return json({
|
|
209
|
+
file: relative(options.root, file).split("\\").join("/"),
|
|
210
|
+
ticks: replay.ticks,
|
|
211
|
+
steps: replay.steps.length,
|
|
212
|
+
warnings: replay.warnings,
|
|
213
|
+
});
|
|
214
|
+
}));
|
|
215
|
+
server.registerTool("game_errors", {
|
|
216
|
+
title: "Runtime errors",
|
|
217
|
+
description: "Runtime errors since the game started, explained: kind (your-code, debuff-usage, debuff-bug, outside), where (source file:line, " +
|
|
218
|
+
"source-mapped), a hint, the loop phase and tick, and a repeat count. A loop error stops the game; fix it, then game_pause false to resume. " +
|
|
219
|
+
"For debuff-bug, tell the human and share reportUrl rather than working around it.",
|
|
220
|
+
inputSchema: {},
|
|
221
|
+
annotations: { readOnlyHint: true },
|
|
222
|
+
}, safely(async () => json(await live().call("errors"))));
|
|
223
|
+
server.registerTool("game_stop", {
|
|
224
|
+
title: "Stop game",
|
|
225
|
+
description: "Headless: close the browser and dev server. Live: resume the human's game and disconnect.",
|
|
226
|
+
inputSchema: {},
|
|
227
|
+
}, safely(async () => {
|
|
228
|
+
await session?.stop();
|
|
229
|
+
session = undefined;
|
|
230
|
+
return json({ ok: true });
|
|
231
|
+
}));
|
|
232
|
+
registerPrompts(server);
|
|
233
|
+
const close = server.close.bind(server);
|
|
234
|
+
server.close = async () => {
|
|
235
|
+
await session?.stop();
|
|
236
|
+
session = undefined;
|
|
237
|
+
await close();
|
|
238
|
+
};
|
|
239
|
+
return server;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A running game an agent can drive through the dev hooks. */
|
|
2
|
+
export interface GameSession {
|
|
3
|
+
/**
|
|
4
|
+
* "live": the game the human has open under `debuff dev`, running in real time; the agent's actions
|
|
5
|
+
* show in their overlay. "headless": a private paused copy where time only advances via step.
|
|
6
|
+
*/
|
|
7
|
+
readonly mode: "live" | "headless";
|
|
8
|
+
readonly url: string;
|
|
9
|
+
call<R = unknown>(method: string, ...args: unknown[]): Promise<R>;
|
|
10
|
+
stop(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export type StartMode = "auto" | "live" | "headless";
|
|
13
|
+
/** Live if `debuff dev` is running with a page open (or `mode` says so), else headless. */
|
|
14
|
+
export declare function startSession(root: string, options?: {
|
|
15
|
+
mode?: StartMode;
|
|
16
|
+
page?: string;
|
|
17
|
+
width?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
}): Promise<GameSession>;
|
|
20
|
+
/** The developer's own browser tab, reached through the dev server's agent link. */
|
|
21
|
+
export declare class LiveSession implements GameSession {
|
|
22
|
+
readonly url: string;
|
|
23
|
+
private readonly attachment;
|
|
24
|
+
readonly mode = "live";
|
|
25
|
+
private constructor();
|
|
26
|
+
static find(root: string): Promise<LiveSession | undefined>;
|
|
27
|
+
call<R = unknown>(method: string, ...args: unknown[]): Promise<R>;
|
|
28
|
+
stop(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A private game: the project's dev server plus a headless browser page in controlled mode
|
|
32
|
+
* (`?debuff-scenario`), so time only advances when an agent steps it.
|
|
33
|
+
*/
|
|
34
|
+
export declare class HeadlessSession implements GameSession {
|
|
35
|
+
readonly url: string;
|
|
36
|
+
private readonly server;
|
|
37
|
+
private readonly browser;
|
|
38
|
+
private readonly page;
|
|
39
|
+
readonly mode = "headless";
|
|
40
|
+
private constructor();
|
|
41
|
+
static start(root: string, options?: {
|
|
42
|
+
page?: string;
|
|
43
|
+
width?: number;
|
|
44
|
+
height?: number;
|
|
45
|
+
}): Promise<HeadlessSession>;
|
|
46
|
+
/** Call a `window.__DEBUFF_DEV__` method. */
|
|
47
|
+
call<R = unknown>(method: string, ...args: unknown[]): Promise<R>;
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAeA,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAErD,2FAA2F;AAC3F,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACjF,OAAO,CAAC,WAAW,CAAC,CAUtB;AAED,oFAAoF;AACpF,qBAAa,WAAY,YAAW,WAAW;IAG3C,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAH7B,QAAQ,CAAC,IAAI,UAAU;IACvB,OAAO,eAGH;IAEJ,OAAa,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAoBhE;IAEK,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAStE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1B;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,WAAW;IAG/C,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,QAAQ,CAAC,IAAI,cAAc;IAC3B,OAAO,eAKH;IAEJ,OAAa,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAyB3H;IAED,6CAA6C;IAC7C,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAEhE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;CACF"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { AGENT_PATHS, callHook, createDevServer, DEV_SERVER_FILE, hooksReady, launchBrowser, } from "@debuffjp/cli";
|
|
4
|
+
/** Live if `debuff dev` is running with a page open (or `mode` says so), else headless. */
|
|
5
|
+
export async function startSession(root, options = {}) {
|
|
6
|
+
const mode = options.mode ?? "auto";
|
|
7
|
+
if (mode !== "headless") {
|
|
8
|
+
const live = await LiveSession.find(root);
|
|
9
|
+
if (live)
|
|
10
|
+
return live;
|
|
11
|
+
if (mode === "live") {
|
|
12
|
+
throw new Error("No live game found. Run `debuff dev` and open the game in a browser, or use mode headless.");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return HeadlessSession.start(root, options);
|
|
16
|
+
}
|
|
17
|
+
/** The developer's own browser tab, reached through the dev server's agent link. */
|
|
18
|
+
export class LiveSession {
|
|
19
|
+
url;
|
|
20
|
+
attachment;
|
|
21
|
+
mode = "live";
|
|
22
|
+
constructor(url, attachment) {
|
|
23
|
+
this.url = url;
|
|
24
|
+
this.attachment = attachment;
|
|
25
|
+
}
|
|
26
|
+
static async find(root) {
|
|
27
|
+
let info;
|
|
28
|
+
try {
|
|
29
|
+
info = JSON.parse(readFileSync(join(root, DEV_SERVER_FILE), "utf8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(new URL(AGENT_PATHS.status, info.url), { signal: AbortSignal.timeout(1500) });
|
|
36
|
+
const status = (await res.json());
|
|
37
|
+
if (status.pages === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
// Hold an attach connection while this session lives, so the overlay shows the agent until it stops
|
|
40
|
+
// (or the agent process exits), not just right after an action.
|
|
41
|
+
const attachment = new AbortController();
|
|
42
|
+
const held = await fetch(new URL(AGENT_PATHS.attach, info.url), { signal: attachment.signal });
|
|
43
|
+
void held.body?.pipeTo(new WritableStream()).catch(() => undefined);
|
|
44
|
+
return new LiveSession(info.url, attachment);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined; // stale file: the dev server is gone
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async call(method, ...args) {
|
|
51
|
+
const res = await fetch(new URL(AGENT_PATHS.rpc, this.url), {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: { "Content-Type": "application/json" },
|
|
54
|
+
body: JSON.stringify({ method, args }),
|
|
55
|
+
});
|
|
56
|
+
const body = (await res.json());
|
|
57
|
+
if (!body.ok)
|
|
58
|
+
throw new Error(body.error);
|
|
59
|
+
return body.result;
|
|
60
|
+
}
|
|
61
|
+
async stop() {
|
|
62
|
+
// The human's tab: leave it running, just let go of any pause the agent made.
|
|
63
|
+
await this.call("resume").catch(() => undefined);
|
|
64
|
+
this.attachment.abort();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A private game: the project's dev server plus a headless browser page in controlled mode
|
|
69
|
+
* (`?debuff-scenario`), so time only advances when an agent steps it.
|
|
70
|
+
*/
|
|
71
|
+
export class HeadlessSession {
|
|
72
|
+
url;
|
|
73
|
+
server;
|
|
74
|
+
browser;
|
|
75
|
+
page;
|
|
76
|
+
mode = "headless";
|
|
77
|
+
constructor(url, server, browser, page) {
|
|
78
|
+
this.url = url;
|
|
79
|
+
this.server = server;
|
|
80
|
+
this.browser = browser;
|
|
81
|
+
this.page = page;
|
|
82
|
+
}
|
|
83
|
+
static async start(root, options = {}) {
|
|
84
|
+
const server = await createDevServer(root, { port: 0, logLevel: "error" });
|
|
85
|
+
await server.listen();
|
|
86
|
+
const base = server.resolvedUrls?.local[0];
|
|
87
|
+
if (!base) {
|
|
88
|
+
await server.close();
|
|
89
|
+
throw new Error("Dev server did not report a URL");
|
|
90
|
+
}
|
|
91
|
+
let browser;
|
|
92
|
+
try {
|
|
93
|
+
browser = await launchBrowser();
|
|
94
|
+
const page = await browser.newPage({
|
|
95
|
+
viewport: { width: options.width ?? 960, height: options.height ?? 540 },
|
|
96
|
+
deviceScaleFactor: 1,
|
|
97
|
+
});
|
|
98
|
+
const url = new URL(options.page ?? "/", base);
|
|
99
|
+
url.searchParams.set("debuff-scenario", "1");
|
|
100
|
+
await page.goto(url.toString());
|
|
101
|
+
await page.waitForFunction(hooksReady, undefined, { timeout: 15_000 });
|
|
102
|
+
return new HeadlessSession(url.toString(), server, browser, page);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
await browser?.close();
|
|
106
|
+
await server.close();
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Call a `window.__DEBUFF_DEV__` method. */
|
|
111
|
+
call(method, ...args) {
|
|
112
|
+
return this.page.evaluate(callHook, [method, args]);
|
|
113
|
+
}
|
|
114
|
+
async stop() {
|
|
115
|
+
await this.browser.close();
|
|
116
|
+
await this.server.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@debuffjp/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for debuff: lets AI agents inspect, build, test and play a debuff game",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"game",
|
|
7
|
+
"gamedev",
|
|
8
|
+
"game-engine",
|
|
9
|
+
"2d",
|
|
10
|
+
"html5-game",
|
|
11
|
+
"browser-game",
|
|
12
|
+
"debuff",
|
|
13
|
+
"mcp",
|
|
14
|
+
"mcp-server",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"ai-agents",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"agents"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"homepage": "https://debuff.jp",
|
|
22
|
+
"bugs": "https://github.com/debuffjp/debuff/issues",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/debuffjp/debuff.git",
|
|
26
|
+
"directory": "tooling/mcp"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"debuff-mcp": "bin/debuff-mcp.js"
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"bin",
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -b"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@debuffjp/cli": "0.2.0",
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
54
|
+
"zod": "^4.6.7",
|
|
55
|
+
"playwright-core": "^1.64.1",
|
|
56
|
+
"vite": "^8.3.4"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^26.6.5"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public",
|
|
63
|
+
"provenance": false
|
|
64
|
+
}
|
|
65
|
+
}
|