summer-engine 2.6.6 → 2.8.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.
Files changed (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/plugin.json +6 -2
  4. package/.cursor-plugin/plugin.json +5 -1
  5. package/.opencode/INSTALL.md +1 -1
  6. package/.opencode/plugins/summer.js +2 -2
  7. package/AGENTS.md +11 -7
  8. package/CHANGELOG.md +40 -0
  9. package/GEMINI.md +9 -7
  10. package/README.md +63 -21
  11. package/_persona/summer/SKILL.md +1 -0
  12. package/assets/autopilot/README.md +51 -0
  13. package/assets/autopilot/autopilot.gd +222 -0
  14. package/assets/autopilot/probe_base.gd +262 -0
  15. package/assets/autopilot/run.sh +78 -0
  16. package/commands/gameskill.md +11 -2
  17. package/commands/summer.md +13 -0
  18. package/dist/bin/summer.js +12 -0
  19. package/dist/commands/config.d.ts +2 -0
  20. package/dist/commands/config.js +53 -0
  21. package/dist/commands/create.d.ts +1 -0
  22. package/dist/commands/create.js +38 -6
  23. package/dist/commands/debug.d.ts +2 -0
  24. package/dist/commands/debug.js +44 -0
  25. package/dist/commands/doctor.js +112 -8
  26. package/dist/commands/list.js +1 -1
  27. package/dist/commands/login.d.ts +19 -0
  28. package/dist/commands/login.js +123 -36
  29. package/dist/commands/logout.js +3 -16
  30. package/dist/commands/logs.d.ts +2 -0
  31. package/dist/commands/logs.js +19 -0
  32. package/dist/commands/mcp.js +7 -2
  33. package/dist/commands/open.js +1 -1
  34. package/dist/commands/publish.d.ts +2 -0
  35. package/dist/commands/publish.js +20 -0
  36. package/dist/commands/releases.d.ts +2 -0
  37. package/dist/commands/releases.js +20 -0
  38. package/dist/commands/run.js +1 -1
  39. package/dist/commands/skills.js +1 -1
  40. package/dist/lib/api-client.d.ts +15 -4
  41. package/dist/lib/api-client.js +138 -13
  42. package/dist/lib/async-op-lifecycle.d.ts +7 -2
  43. package/dist/lib/async-op-lifecycle.js +32 -9
  44. package/dist/lib/auth.d.ts +40 -12
  45. package/dist/lib/auth.js +173 -37
  46. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  47. package/dist/lib/cloud/engine-bridge.js +32 -6
  48. package/dist/lib/config.d.ts +20 -0
  49. package/dist/lib/config.js +142 -0
  50. package/dist/lib/creator.d.ts +93 -0
  51. package/dist/lib/creator.js +393 -0
  52. package/dist/lib/debug-report.d.ts +50 -0
  53. package/dist/lib/debug-report.js +308 -0
  54. package/dist/lib/engine.d.ts +35 -2
  55. package/dist/lib/engine.js +240 -6
  56. package/dist/lib/game-task-plan.js +4 -4
  57. package/dist/lib/mcp-boot-notice.d.ts +3 -0
  58. package/dist/lib/mcp-boot-notice.js +7 -0
  59. package/dist/lib/mcp-log.d.ts +3 -0
  60. package/dist/lib/mcp-log.js +72 -0
  61. package/dist/lib/public-surface-paths.d.ts +13 -0
  62. package/dist/lib/public-surface-paths.js +42 -0
  63. package/dist/lib/skills-registry.d.ts +33 -1
  64. package/dist/lib/skills-registry.js +55 -0
  65. package/dist/lib/store.d.ts +14 -0
  66. package/dist/lib/store.js +145 -0
  67. package/dist/lib/summer-compatibility.d.ts +6 -0
  68. package/dist/lib/summer-compatibility.js +22 -0
  69. package/dist/mcp/server.d.ts +10 -3
  70. package/dist/mcp/server.js +104 -11
  71. package/dist/mcp/tools/asset-tools.js +33 -12
  72. package/dist/mcp/tools/cloud-tools.js +15 -15
  73. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  74. package/dist/mcp/tools/creator-tools.js +105 -0
  75. package/dist/mcp/tools/debug-tools.js +44 -1
  76. package/dist/mcp/tools/file-tools.d.ts +2 -0
  77. package/dist/mcp/tools/file-tools.js +154 -0
  78. package/dist/mcp/tools/generate-tools.js +159 -11
  79. package/dist/mcp/tools/project-tools.js +37 -27
  80. package/dist/mcp/tools/scene-tools.js +106 -29
  81. package/dist/mcp/tools/visual-tools.js +4 -3
  82. package/dist/mcp/tools/with-engine.js +4 -4
  83. package/hooks/session-start.sh +5 -5
  84. package/package.json +3 -1
  85. package/references/collaborative-protocol.md +50 -0
  86. package/references/gd-style.md +121 -0
  87. package/references/godot-version.md +51 -0
  88. package/references/mcp-tools-reference.md +199 -0
  89. package/references/public-surface-language.md +30 -0
  90. package/references/summer-folder.md +90 -0
  91. package/references/template-registry.md +73 -0
  92. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  93. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  94. package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
  95. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  96. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  97. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  98. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  99. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  100. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  101. package/skills/3d-assets/character-model/SKILL.md +34 -19
  102. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  103. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  104. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  105. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  106. package/skills/README.md +1 -1
  107. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  108. package/skills/animation/animation-tree/SKILL.md +39 -15
  109. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  110. package/skills/animation/generate-motion/SKILL.md +47 -15
  111. package/skills/animation/procedural-animation/SKILL.md +31 -13
  112. package/skills/animation/retarget/SKILL.md +127 -79
  113. package/skills/audio/adaptive-music/SKILL.md +24 -20
  114. package/skills/audio/ambient-bed/SKILL.md +22 -16
  115. package/skills/audio/audio-direction/SKILL.md +15 -10
  116. package/skills/audio/music-track/SKILL.md +18 -12
  117. package/skills/audio/sound-effect/SKILL.md +17 -12
  118. package/skills/audio/voice-line/SKILL.md +42 -42
  119. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  120. package/skills/debugging/debug/SKILL.md +74 -20
  121. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  122. package/skills/deployment/remote-deploy/SKILL.md +140 -0
  123. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  124. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  125. package/skills/level-design/design-level/SKILL.md +22 -8
  126. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  127. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  128. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  129. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  130. package/skills/performance/tune-performance/SKILL.md +51 -32
  131. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  132. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  133. package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
  134. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  135. package/skills/scene-and-project/play/SKILL.md +10 -4
  136. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  137. package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
  138. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  139. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  140. package/skills/video/animated-loop/SKILL.md +63 -39
  141. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  142. package/skills/video/trailer-shot/SKILL.md +30 -21
  143. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  144. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  145. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  146. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  147. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  148. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  149. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  150. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  151. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  152. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  153. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  154. package/skills/workflow/brainstorming/SKILL.md +1 -1
  155. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  156. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  157. package/skills/workflow/gameskill/SKILL.md +11 -2
  158. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  159. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  160. package/skills/workflow/mcpupdate/SKILL.md +9 -9
  161. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  162. package/skills/workflow/skill-create/SKILL.md +7 -7
  163. package/skills/workflow/skill-test/SKILL.md +3 -3
  164. package/skills/workflow/using-summer/SKILL.md +32 -3
  165. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  166. package/skills/workflow/writing-plans/SKILL.md +7 -7
  167. package/skills/workflow/writing-skills/SKILL.md +7 -5
  168. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -0,0 +1,154 @@
1
+ import { z } from "zod";
2
+ import { withEngine } from "./with-engine.js";
3
+ // MCP clients may issue tool calls concurrently. Keep the complete read->write
4
+ // transaction ordered for one project file while allowing unrelated files to
5
+ // proceed in parallel. The tail is always released in `finally`, so a rejected
6
+ // tool call cannot wedge later edits.
7
+ const fileMutationTails = new Map();
8
+ async function withFileMutationTurn(projectIdHash, path, fn) {
9
+ // Windows project paths are case-insensitive. Canonicalize only the lock key;
10
+ // preserve the caller's exact safe path in the operation sent to the engine.
11
+ const canonicalPathKey = path.replace(/\\/g, "/").toLowerCase();
12
+ const key = `${projectIdHash ?? "unbound"}\n${canonicalPathKey}`;
13
+ const previous = fileMutationTails.get(key) ?? Promise.resolve();
14
+ let release;
15
+ const gate = new Promise((resolve) => {
16
+ release = resolve;
17
+ });
18
+ const current = previous.catch(() => undefined).then(() => gate);
19
+ fileMutationTails.set(key, current);
20
+ await previous.catch(() => undefined);
21
+ try {
22
+ return await fn();
23
+ }
24
+ finally {
25
+ release();
26
+ if (fileMutationTails.get(key) === current) {
27
+ fileMutationTails.delete(key);
28
+ }
29
+ }
30
+ }
31
+ function asRecord(value) {
32
+ return value && typeof value === "object" && !Array.isArray(value)
33
+ ? value
34
+ : null;
35
+ }
36
+ function safeProjectPath(path) {
37
+ const normalized = path.trim().replace(/\\/g, "/");
38
+ if (!normalized.startsWith("res://") || normalized.includes("..")) {
39
+ throw new Error("File path must be a traversal-free res:// project path.");
40
+ }
41
+ return normalized;
42
+ }
43
+ function validSha256(value) {
44
+ return typeof value === "string" && /^[0-9a-f]{64}$/i.test(value);
45
+ }
46
+ function readTextPayload(result) {
47
+ const root = asRecord(result);
48
+ const data = asRecord(root?.data);
49
+ if (root?.ok === false) {
50
+ throw new Error(String(root.error ?? "Engine could not read the file."));
51
+ }
52
+ if (data?.encoding === "binary" || typeof data?.content !== "string") {
53
+ throw new Error("Safe text mutation refused: the engine did not return text content.");
54
+ }
55
+ if (data.truncated === true) {
56
+ throw new Error("Safe text mutation refused: the file exceeds the 1 MB read limit.");
57
+ }
58
+ const sha256 = typeof data.sha256 === "string" ? data.sha256 : undefined;
59
+ if (!validSha256(sha256)) {
60
+ throw new Error("Safe text mutation refused: the engine did not return a full-file sha256 receipt.");
61
+ }
62
+ return {
63
+ content: data.content,
64
+ sha256,
65
+ size: typeof data.size === "number" ? data.size : undefined,
66
+ };
67
+ }
68
+ function occurrenceCount(content, needle) {
69
+ let count = 0;
70
+ let offset = 0;
71
+ while (true) {
72
+ const index = content.indexOf(needle, offset);
73
+ if (index < 0)
74
+ return count;
75
+ count++;
76
+ offset = index + needle.length;
77
+ }
78
+ }
79
+ export function registerFileTools(server) {
80
+ server.tool("summer_read_file", `Read a text file from the engine-bound project and return its full-file sha256 receipt.
81
+
82
+ Use the returned sha256 as expected_sha256 when overwriting an existing file. Paths must begin with res://.`, {
83
+ path: z.string().describe("Project path, e.g. res://scripts/player.gd"),
84
+ max_bytes: z.number().int().positive().max(1_000_000).default(200_000),
85
+ }, async ({ path, max_bytes }) => withEngine(async (client) => client.readProjectFile(safeProjectPath(path), max_bytes)));
86
+ server.tool("summer_write_file", `Create or safely overwrite a complete text file through the identity-bound engine.
87
+
88
+ For a new file, set create_only:true. For an existing file, first call summer_read_file and pass its sha256 as expected_sha256. Exactly one guard is required; unguarded writes fail closed. Supports scripts, .tscn scenes, .tres resources, JSON, docs, and project config.`, {
89
+ path: z.string().describe("Project path, e.g. res://scenes/player.tscn"),
90
+ content: z.string().describe("Complete new UTF-8 file content."),
91
+ create_only: z.boolean().optional().describe("Required for a new file; refuses if the path already exists."),
92
+ expected_sha256: z.string().optional().describe("Required for overwriting an existing file; obtain from summer_read_file."),
93
+ }, async ({ path, content, create_only, expected_sha256 }) => withEngine(async (client) => {
94
+ const safePath = safeProjectPath(path);
95
+ const guardedCreate = create_only === true;
96
+ const guardedOverwrite = validSha256(expected_sha256);
97
+ if (expected_sha256 !== undefined && !guardedOverwrite) {
98
+ throw new Error("Safe write refused: expected_sha256 must be a 64-character hexadecimal sha256 from summer_read_file. Nothing was written.");
99
+ }
100
+ if (guardedCreate === guardedOverwrite) {
101
+ throw new Error("Safe write requires exactly one guard: create_only:true for a new file, or a 64-character expected_sha256 from summer_read_file for an existing file. Nothing was written.");
102
+ }
103
+ const op = {
104
+ op: "WriteFile",
105
+ path: safePath,
106
+ content,
107
+ };
108
+ if (guardedCreate)
109
+ op.mustNotExist = true;
110
+ if (guardedOverwrite)
111
+ op.expectedSha256 = expected_sha256.toLowerCase();
112
+ return withFileMutationTurn(client.getBoundProjectIdHash(), safePath, () => client.executeIdentityBoundOps([op]));
113
+ }));
114
+ server.tool("summer_replace_text", `Safely replace text in an existing project file through the identity-bound engine.
115
+
116
+ The MCP server reads the complete file, requires a unique match by default, computes the new content, and submits a sha256-guarded WriteFile. Set replace_all:true only when every exact occurrence should change.`, {
117
+ path: z.string().describe("Project path, e.g. res://scripts/player.gd"),
118
+ old_text: z.string().min(1).describe("Exact text to replace."),
119
+ new_text: z.string().describe("Replacement text; may be empty."),
120
+ replace_all: z.boolean().default(false).describe("Replace every exact occurrence instead of requiring one unique match."),
121
+ }, async ({ path, old_text, new_text, replace_all }) => withEngine(async (client) => {
122
+ const safePath = safeProjectPath(path);
123
+ return withFileMutationTurn(client.getBoundProjectIdHash(), safePath, async () => {
124
+ const current = readTextPayload(await client.readProjectFile(safePath, 1_000_000));
125
+ const matches = occurrenceCount(current.content, old_text);
126
+ if (matches === 0) {
127
+ throw new Error("Safe replace refused: old_text was not found. Nothing was written.");
128
+ }
129
+ if (!replace_all && matches !== 1) {
130
+ throw new Error(`Safe replace refused: old_text matched ${matches} times. Provide a unique span or set replace_all:true. Nothing was written.`);
131
+ }
132
+ const content = replace_all
133
+ ? current.content.split(old_text).join(new_text)
134
+ : current.content.replace(old_text, new_text);
135
+ if (content === current.content) {
136
+ return {
137
+ ok: true,
138
+ noOp: true,
139
+ path: safePath,
140
+ sha256: current.sha256,
141
+ matches,
142
+ };
143
+ }
144
+ return client.executeIdentityBoundOps([
145
+ {
146
+ op: "WriteFile",
147
+ path: safePath,
148
+ content,
149
+ expectedSha256: current.sha256,
150
+ },
151
+ ]);
152
+ });
153
+ }));
154
+ }
@@ -13,6 +13,8 @@ const TOOL_BY_ENDPOINT = {
13
13
  "/api/mcp/generate/3d": "summer_generate_3d",
14
14
  "/api/mcp/generate/video": "summer_generate_video",
15
15
  "/api/mcp/generate/motion": "summer_generate_motion",
16
+ "/api/mcp/generate/slice-asset-sheet": "summer_slice_asset_sheet",
17
+ "/api/mcp/workflows": "summer_get_studio_workflow",
16
18
  };
17
19
  // ---------------------------------------------------------------------------
18
20
  // Shared helpers
@@ -129,6 +131,37 @@ async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
129
131
  }
130
132
  return { data, status: res.status };
131
133
  }
134
+ async function mcpGet(endpoint, searchParams, timeoutMs = 30_000) {
135
+ const token = await getAuthToken();
136
+ const suffix = searchParams?.size ? `?${searchParams.toString()}` : "";
137
+ let res;
138
+ try {
139
+ res = await fetch(`${GATEWAY_URL}${endpoint}${suffix}`, {
140
+ headers: {
141
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
142
+ "X-Summer-Client": "summer-cli",
143
+ "X-Summer-Client-Version": CLI_VERSION,
144
+ "X-Summer-Client-Surface": "mcp",
145
+ "X-Summer-MCP-Endpoint": endpoint,
146
+ "X-Summer-MCP-Tool": TOOL_BY_ENDPOINT[endpoint] ?? "unknown",
147
+ },
148
+ signal: AbortSignal.timeout(timeoutMs),
149
+ });
150
+ }
151
+ catch (err) {
152
+ const msg = err instanceof Error ? err.message : String(err);
153
+ return { error: `Request failed: ${msg}`, status: 0 };
154
+ }
155
+ const data = await readJsonResponse(res);
156
+ if (!res.ok) {
157
+ return {
158
+ error: formattedApiErrorMessage(res.status, data),
159
+ data: { ...data, status: res.status },
160
+ status: res.status,
161
+ };
162
+ }
163
+ return { data, status: res.status };
164
+ }
132
165
  /**
133
166
  * Download an image URL to a temp file so the AI can Read it and show the user.
134
167
  */
@@ -153,7 +186,7 @@ async function downloadToTemp(url, prefix, ext = "png") {
153
186
  * Poll a job until completed, failed, or timeout.
154
187
  * Returns the final job status object.
155
188
  */
156
- async function pollJob(jobId, maxWaitMs = 300_000, intervalMs = 5_000) {
189
+ async function pollJob(jobId, maxWaitMs = 600_000, intervalMs = 5_000) {
157
190
  const token = await getAuthToken();
158
191
  if (!token)
159
192
  return { error: "Not signed in" };
@@ -210,6 +243,28 @@ function successResult(data) {
210
243
  // Registration
211
244
  // ---------------------------------------------------------------------------
212
245
  export function registerGenerateTools(server) {
246
+ // ========================================================================
247
+ // summer_get_studio_workflow
248
+ // ========================================================================
249
+ server.tool("summer_get_studio_workflow", `Discover the same Guided workflow recipes shown in Summer Engine Studio.
250
+
251
+ Call without workflowId to list every workflow and its support level. Call with
252
+ an id to get the exact starter prompt, ordered steps, required MCP tools, and
253
+ honest limitations. A "manual" support level means Studio still has a visual
254
+ handoff that MCP cannot perform automatically; do not pretend that stage ran.`, {
255
+ workflowId: z
256
+ .string()
257
+ .optional()
258
+ .describe("Guided workflow id, e.g. sprite-sheet, character-pack, asset-pack, or component-compiler"),
259
+ }, async ({ workflowId }) => {
260
+ const params = new URLSearchParams();
261
+ if (workflowId)
262
+ params.set("id", workflowId);
263
+ const result = await mcpGet("/api/mcp/workflows", params);
264
+ if (result.error)
265
+ return errorResult(result.error, result.data);
266
+ return successResult(result.data);
267
+ });
213
268
  // ========================================================================
214
269
  // summer_generate_image
215
270
  // ========================================================================
@@ -219,7 +274,9 @@ Known models:
219
274
  - "nano-banana-2" (default) — High quality, supports txt2img and img2img
220
275
  - "gemini-flash" — Google Gemini 2.5 Flash, fast
221
276
  - "flux-2" — FLUX.2, good for specific styles
222
- - Any fal-ai model ID works as a passthrough
277
+
278
+ Model ids are an ALLOWLIST, not a passthrough. An unrecognised id is rejected
279
+ with 400 and the response lists every accepted id — it is not silently swapped.
223
280
 
224
281
  Two modes:
225
282
  - txt2img (default): just pass prompt
@@ -277,6 +334,35 @@ Requires authentication: run 'npx summer-engine login' first.`, {
277
334
  });
278
335
  });
279
336
  // ========================================================================
337
+ // summer_slice_asset_sheet
338
+ // ========================================================================
339
+ server.tool("summer_slice_asset_sheet", `Detect and crop every distinct game asset from an existing image asset sheet.
340
+
341
+ Use the assetId returned by summer_generate_image (or another Summer image asset).
342
+ The same guided workflow used in Studio removes the sheet background when needed,
343
+ detects named/category-aware bounding boxes, crops each item, and uploads the slices.
344
+
345
+ Returns source dimensions plus the generated slice URLs, names, categories, bounding
346
+ boxes, and widget metadata when the sheet contains UI controls.
347
+
348
+ Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
349
+ Requires authentication: run 'npx summer-engine login' first.`, {
350
+ assetId: z
351
+ .string()
352
+ .describe("Summer image asset ID to slice, usually returned by summer_generate_image"),
353
+ }, async ({ assetId }) => {
354
+ const result = await mcpGenerate("/api/mcp/generate/slice-asset-sheet", { assetId }, 300_000);
355
+ if (result.error) {
356
+ return errorResult(result.error, result.data);
357
+ }
358
+ return successResult({
359
+ ...result.data,
360
+ sliceCount: Array.isArray(result.data?.slices)
361
+ ? result.data.slices.length
362
+ : 0,
363
+ });
364
+ });
365
+ // ========================================================================
280
366
  // summer_generate_audio
281
367
  // ========================================================================
282
368
  server.tool("summer_generate_audio", `Generate audio using AI providers via Summer Engine Studio.
@@ -287,8 +373,12 @@ Capabilities:
287
373
  - "music" — Generate background music. Requires 'prompt'. Optional 'durationSeconds'.
288
374
  - "text_to_dialogue" — Multi-voice dialogue. Requires 'inputs' array of {text, voiceId} objects.
289
375
 
290
- The 'options' object is passed directly to ElevenLabs, so you can set any
291
- provider-specific parameter: stability, similarity_boost, style, speed, etc.
376
+ The 'options' object is spread into the ElevenLabs SDK call at the TOP level, so
377
+ only keys the SDK accepts there have any effect. Voice tuning is NOT top-level and
378
+ is NOT snake_case: pass it as options.voiceSettings with camelCase keys —
379
+ { voiceSettings: { stability, similarityBoost, style, speed, useSpeakerBoost } }.
380
+ Flat snake_case keys are accepted by the schema, silently dropped by the SDK, and
381
+ return a normal 200 with the setting ignored.
292
382
 
293
383
  Returns the generated audio URL and asset metadata.
294
384
  Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
@@ -323,7 +413,9 @@ Requires authentication: run 'npx summer-engine login' first.`, {
323
413
  options: z
324
414
  .record(z.any())
325
415
  .optional()
326
- .describe("Provider-specific params (stability, similarity_boost, style, speed, etc.)"),
416
+ .describe("Provider params, spread at the SDK's top level. Voice tuning goes in " +
417
+ "options.voiceSettings with camelCase keys (stability, similarityBoost, " +
418
+ "style, speed, useSpeakerBoost); flat snake_case is silently ignored."),
327
419
  }, async ({ capability, text, prompt, voiceId, modelId, durationSeconds, inputs, options }) => {
328
420
  const body = { capability };
329
421
  if (text)
@@ -355,7 +447,9 @@ Available models:
355
447
  - "hunyuan" (default) — Hunyuan 3D v3.1 Pro, high quality
356
448
  - "trellis" — Trellis 2, fast and detailed
357
449
  - "meshy" — Meshy, legacy option
358
- - Any fal-ai model ID works as a passthrough
450
+
451
+ Model ids are an ALLOWLIST, not a passthrough. An unrecognised id is rejected
452
+ with 400 listing the accepted ids.
359
453
 
360
454
  Available kinds:
361
455
  - "text-to-3d" (default) — From text description. Requires 'prompt'.
@@ -377,7 +471,16 @@ Example:
377
471
  })
378
472
  // Returns jobId; poll until result includes { assetId, rigAssetId }.
379
473
 
380
- By default, waits for completion (up to 5 min) and returns the result directly.
474
+ Single-image generation automatically assesses whether the source is suitable
475
+ for 3D. If needed, Summer prepares an isolated object view or rig-safe character
476
+ pose before conversion. Set referencePreparation="always" or "never" only when
477
+ you intentionally want to override that shared Studio behavior.
478
+
479
+ For a complete animated humanoid package, pass rig=true plus animationNames or
480
+ actionIds. This routes through the shared character pipeline instead of a
481
+ one-off mesh job.
482
+
483
+ By default, waits for completion (up to 10 min) and returns the result directly.
381
484
  Set wait=false to get the jobId immediately and poll manually with summer_check_job.
382
485
 
383
486
  Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
@@ -398,21 +501,66 @@ Requires authentication: run 'npx summer-engine login' first.`, {
398
501
  .string()
399
502
  .optional()
400
503
  .describe("Source image URL for image-to-3d or texture"),
504
+ imageUrls: z
505
+ .array(z.string())
506
+ .max(4)
507
+ .optional()
508
+ .describe("Up to four views for multi-image-to-3d"),
509
+ title: z.string().optional().describe("Asset or character title"),
510
+ idempotencyKey: z
511
+ .string()
512
+ .optional()
513
+ .describe("Stable retry key so the same logical request is not billed or queued twice"),
514
+ assetIntent: z
515
+ .enum(["character", "object"])
516
+ .optional()
517
+ .describe("Controls whether hidden reference preparation targets a rig-safe character pose or isolated object view"),
518
+ referencePreparation: z
519
+ .enum(["auto", "always", "never"])
520
+ .default("auto")
521
+ .describe("Shared Studio reference-preparation policy; auto is recommended"),
522
+ rig: z.boolean().default(false).describe("Auto-rig the generated model"),
523
+ animationNames: z
524
+ .array(z.string())
525
+ .optional()
526
+ .describe("Animation names for the shared animated-character pipeline, e.g. Idle, Walk, Run, Jump"),
527
+ actionIds: z
528
+ .array(z.number().int().min(0).max(696))
529
+ .optional()
530
+ .describe("Exact Meshy action ids for the shared animated-character pipeline"),
531
+ riggingHeightMeters: z
532
+ .number()
533
+ .positive()
534
+ .max(10)
535
+ .optional()
536
+ .describe("Character height used by auto-rigging"),
401
537
  wait: z
402
538
  .boolean()
403
539
  .default(true)
404
- .describe("Wait for completion (default true, up to 5 min). Set false to get jobId immediately."),
540
+ .describe("Wait for completion (default true, up to 10 min). Set false to get jobId immediately."),
405
541
  options: z
406
542
  .record(z.any())
407
543
  .optional()
408
544
  .describe("Provider-specific params (target_polycount, topology, art_style, etc.)"),
409
- }, async ({ prompt, kind, model, imageUrl, wait, options }) => {
545
+ }, async ({ prompt, kind, model, imageUrl, imageUrls, title, idempotencyKey, assetIntent, referencePreparation, rig, animationNames, actionIds, riggingHeightMeters, wait, options, }) => {
546
+ const mergedOptions = {
547
+ ...(options ?? {}),
548
+ ...(imageUrls ? { imageUrls } : {}),
549
+ ...(assetIntent ? { assetIntent } : {}),
550
+ referencePreparation,
551
+ ...(rig ? { rig: true } : {}),
552
+ ...(animationNames ? { animationNames } : {}),
553
+ ...(actionIds ? { actionIds } : {}),
554
+ ...(riggingHeightMeters !== undefined ? { riggingHeightMeters } : {}),
555
+ };
410
556
  const result = await mcpGenerate("/api/mcp/generate/3d", {
411
557
  prompt,
412
558
  kind,
413
559
  model,
414
560
  imageUrl,
415
- options,
561
+ title,
562
+ idempotencyKey,
563
+ options: mergedOptions,
416
564
  });
417
565
  if (result.error) {
418
566
  return errorResult(result.error, result.data);
@@ -561,7 +709,7 @@ Cost: ~$0.10 per clip. Confirm with user before spending.
561
709
 
562
710
  Custom prompt-driven motion is on the roadmap; not yet shipped. For one-off
563
711
  signature moves not on the curated list, fall back to hand-authoring in the
564
- Godot editor or importing from Mixamo.
712
+ Summer Engine or importing from Mixamo.
565
713
 
566
714
  Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
567
715
  Requires authentication: run 'npx summer-engine login' first.`, {
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { ASSET_POLICIES, GAME_TASK_MODES, GAME_TASK_TARGETS, VERIFICATION_LEVELS, buildGameTaskPlan, } from "../../lib/game-task-plan.js";
3
+ import { getCachedBootDriftNotice } from "../../lib/mcp-boot-notice.js";
3
4
  import { getProjectMemorySummary } from "../../lib/project-memory.js";
4
5
  import { withEngine } from "./with-engine.js";
5
6
  function asRecord(value) {
@@ -134,18 +135,19 @@ anti-patterns, and recovery steps.`, {}, async () => ({
134
135
  {
135
136
  type: "text",
136
137
  text: JSON.stringify({
138
+ summerUpdateNotice: getCachedBootDriftNotice()?.text ?? null,
137
139
  startupChecklist: [
138
140
  "Understand the request and outline a brief plan before reaching for tools.",
139
- "Default medium is host file tools: write/edit .gd/.cs/.tscn/.tres/.json/docs/config directly as text.",
140
- "Use Summer MCP only when you need the LIVE engine: play/stop, diagnostics, screenshots/verification, navmesh or light bake, runtime inspect, or asset import.",
141
+ "Use summer_read_file, summer_write_file, and summer_replace_text for project text files so reads and guarded writes stay bound to the open engine project.",
142
+ "Use Summer MCP for project mutations and live-engine work: scenes, resources, play/stop, diagnostics, screenshots/verification, bake, runtime inspection, and asset import.",
141
143
  "Call summer_get_project_context first so you do not guess scene paths or the project language. It also BINDS this session to the currently-open project (see projectBinding below).",
142
144
  "Use projectMemory from summer_get_project_context to decide which .summer Markdown files to read before creative/audio/dialogue/level/character work.",
143
145
  "After writing code, VERIFY through the engine (see verificationLadder) instead of waiting for the user to navigate to it.",
144
146
  ],
145
147
  safeDefaults: [
146
148
  "Never guess scene filenames (main.tscn/Main.tscn) -- get them from summer_get_project_context.",
147
- "Edit files (including .tscn/.tres) with host file tools by default. Reach for MCP scene-ops only for live-engine needs (bake, play, runtime inspect) or when you want the editor to manage node ids / instancing.",
148
- "If you hand-edit a .tscn that is OPEN in the editor, reload it there afterward -- the editor's open tab can overwrite your file (clobber).",
149
+ "Use summer_replace_text for existing project text and summer_write_file with create_only:true for new files; overwrites require the sha256 from summer_read_file.",
150
+ "For live scene hierarchy and inspector changes, prefer scene tools. Guarded text writes support .tscn/.tres, and the engine schedules editor reloads after they land.",
149
151
  "Write GDScript by default; use C# only if the project already uses it.",
150
152
  "Never remove multiple top-level nodes unless the user explicitly requests destructive edits.",
151
153
  "Never change priority: locked .summer memory, voice IDs, canon, or provider bindings without explicit user confirmation.",
@@ -153,7 +155,7 @@ anti-patterns, and recovery steps.`, {}, async () => ({
153
155
  buildFlow: [
154
156
  "1. Understand what the user wants (one pass, no tool spelunking first).",
155
157
  "2. Outline the plan fast and briefly; proceed once it is clearly right.",
156
- "3. Execute in pure code -- edit .gd/.tscn/.tres as text (GDScript by default, C# only if the project already uses it).",
158
+ "3. Execute through identity-bound MCP mutations -- guarded text tools for files, scene tools for live hierarchy/inspector work (GDScript by default, C# only if the project already uses it).",
157
159
  "4. Verify through the engine using the verificationLadder below, fix, and repeat until it launches clean.",
158
160
  ],
159
161
  // The MCP-native verification ladder. Climb only as high as the
@@ -172,17 +174,18 @@ anti-patterns, and recovery steps.`, {}, async () => ({
172
174
  ],
173
175
  "3_runs": [
174
176
  "Compose the run-and-check yourself — there is no single 'verify' tool:",
175
- "summer_clear_console -> clean slate",
176
177
  "summer_play [scene] -> boot the game (or a specific scene)",
177
178
  "summer_get_debugger_errors -> runtime errors (null refs, missing nodes, physics)",
178
179
  "summer_screenshot target:'game' -> optional visual of the live frame",
179
- "summer_stop -> ALWAYS stop; scene mutations are refused while the game runs",
180
+ "summer_stop -> stop when runtime verification is finished; editor scene mutations are not categorically blocked by a running game, but an existing game instance may need a restart to observe them",
180
181
  ],
181
182
  "4_interactive": [
182
- "To prove input-driven behavior (does jump/move/shoot actually work), you have two routes when the engine build supports them (both go through summer_batch as raw ops — see rawOpsViaBatch):",
183
- "SimulateInput: inject a single action/key/mouse/axis into the RUNNING game, then re-check screenshot/debugger. Requires summer_play first.",
184
- "RunVerification: spawn a hidden, disposable game instance that runs a GDScript probe (press inputs, read state, save frames) and dies — never touches the user's editor. Returns results.json + frames. Best for a scripted 'does X happen when I do Y' assertion.",
185
- "If neither op is available on this engine build (failure_reason:'unsupported' or an unknown-op error comes back), do NOT fake it — ask the user to try the interaction and report what they see.",
183
+ "To prove input-driven behavior (does jump/move/shoot actually work), use RunVerification. It is the ONLY interactive route available to MCP, and it is a real one — do NOT hand this rung to the user.",
184
+ "RunVerification: spawn a hidden, disposable game instance that runs a GDScript probe (press inputs, read state, save frames) and dies — never touches the user's editor. Returns results.json + frames. Send it as a raw op through summer_batch (see rawOpsViaBatch).",
185
+ "Unlike the editor's own --headless mode, the verify instance renders REAL PIXELS, so save_frame() writes a real image and Performance.TIME_FPS reports a real number.",
186
+ "press()/key() are COROUTINES — 'await press(\"move_right\", 500)' or the hold never elapses and the input does nothing.",
187
+ "Assert on physics-frame-derived state (positions after N 'await get_tree().physics_frame'), which is reproducible. press(hold_ms) waits on wall clock, so distance-travelled jitters run to run — assert 'moved more than X', never an exact value.",
188
+ "SimulateInput is NOT reachable from MCP — see rawOpsViaBatch. Do not attempt it as a fallback.",
186
189
  ],
187
190
  },
188
191
  // HONESTY — mirror the in-product agent's vision rules. A capture is
@@ -204,29 +207,33 @@ anti-patterns, and recovery steps.`, {}, async () => ({
204
207
  "To intentionally follow the switch, call summer_get_project_context again to rebind, then retry.",
205
208
  ],
206
209
  liveEngineFlow: [
207
- "Use this flow ONLY when you genuinely need live engine state (navmesh/light bake, instancing into an already-open scene, runtime inspect):",
210
+ "Use this flow when you genuinely need live engine state (navmesh/light bake, scene mutation, runtime inspect):",
208
211
  "summer_get_project_context",
209
- "summer_open_main_scene (if needed)",
210
- "summer_get_scene_tree",
211
- "summer_add_node / summer_set_prop / summer_set_resource_property",
212
- "summer_save_scene",
212
+ "Choose the exact res:// scenePath. OpenScene is a user-visible tab action, not a mutation prerequisite.",
213
+ "summer_get_scene_tree with scenePath when that exact scene is already loaded; omit it only for the visible tab.",
214
+ "summer_add_node / summer_set_prop / summer_set_resource_property with scenePath",
215
+ "Mutation tools append one final SaveScene; use summer_save_scene only for a standalone save/save-as.",
213
216
  "summer_get_diagnostics",
214
217
  ],
215
218
  // summer_batch forwards each {op, ...} verbatim to the engine, so
216
219
  // engine ops that have no dedicated tool are still reachable.
217
220
  rawOpsViaBatch: [
218
221
  "summer_batch runs an array of raw engine ops in one undo group; each op is passed through untouched, so newer engine ops with no dedicated tool are still callable.",
219
- "SimulateInput (drive the RUNNING game): summer_batch ops:[{op:'SimulateInput', type:'action', action:'jump', pressed:true}]. type is 'action' | 'key' | 'mouse_click' | 'axis'. summer_play first. Structured failure_reasons (incl 'unsupported' on older builds) come back verbatim — surface them.",
220
- "RunVerification (hidden probe instance): summer_batch ops:[{op:'RunVerification', probe_source:'<gdscript>', max_seconds:20}]. probe_source extends SummerProbeBase and uses report()/save_frame()/press()/key()/finish(); returns {ok, results, frames, out_dir} or {ok:false, failure_reason: spawn_failed|timeout|bad_args|no_project}.",
222
+ "RunVerification (hidden probe instance): summer_batch ops:[{op:'RunVerification', probe_source:'<gdscript>', max_seconds:20}]. probe_source extends SummerProbeBase and uses report()/save_frame()/press()/key()/finish(); returns {ok, results, frames, out_dir} or {ok:false, failure_reason: spawn_failed|timeout|bad_args|no_project|probe_not_node}.",
223
+ "SimulateInput is NOT reachable this way, on any engine build. It needs the in-editor chat bridge's async reply channel; every queued caller (MCP, CLI) is answered with {ok:false, failure_reason:'unsupported_transport'}. Use RunVerification's press()/key() instead.",
224
+ "WriteFile and ReplaceText are rejected here by design — use summer_write_file / summer_replace_text so project identity, content guards and same-file ordering are enforced.",
225
+ "You do not need an engine op to run a shell command: your own host already has a shell. The engine binary that runs project scripts is at OS.get_executable_path() (on macOS, /Applications/Summer.app/Contents/MacOS/Summer); see the summer-cli headless-scripting skill.",
221
226
  "These are runtime ops, not scene mutations — the batch undo group is a harmless no-op for them.",
222
227
  ],
223
228
  recovery: [
224
- "If you see 'no scene open': run summer_open_main_scene.",
229
+ "If a scene mutation reports missing_scene_path: pass the exact res:// scenePath and retry.",
230
+ "If a scene target cannot load: repair the named missing/invalid dependency, then retry the same scenePath.",
225
231
  "If open_scene fails: re-check mainScene from summer_get_project_context.",
226
- "If save fails: verify scene is open and game is not running (summer_is_running / summer_stop).",
232
+ "If save fails: use the returned scenePath/error to repair the exact cause. A running game alone is not a generic scene-mutation blocker.",
227
233
  "If a mutation is rejected with identity_mismatch: the engine switched projects — call summer_get_project_context to rebind (only if you meant to follow it), then retry.",
228
- "If a .tscn you wrote keeps reverting: the editor has that scene open -- reload or close the tab, then write again.",
229
- "If a run op or SimulateInput returns 'unsupported' / an unknown-op error: this engine build predates it — fall back to summer_play + summer_get_debugger_errors, or ask the user to interact.",
234
+ "If a guarded file mutation is rejected with content mismatch: call summer_read_file again, review the new content, and retry with its new sha256 only if the edit is still valid.",
235
+ "If a run op returns 'unsupported' / an unknown-op error: this engine build predates it — fall back to summer_play + summer_get_debugger_errors.",
236
+ "If SimulateInput returns 'unsupported_transport': that is permanent for MCP, not a build gap. Rewrite the check as a RunVerification probe.",
230
237
  ],
231
238
  debugging: [
232
239
  "Set SUMMER_MCP_DEBUG=1 in the MCP server's environment to log a structured stderr line per tool call (tool, ok, durationMs, terminalState, errorClass, failureReason, retried, boundProjectIdHash). With the flag OFF, only failures are logged. Use it to see exactly which op failed and why.",
@@ -270,10 +277,11 @@ Use this first in every fresh chat to avoid guessing scene filenames or editing
270
277
  mainScene,
271
278
  boundProjectIdHash,
272
279
  projectMemory: getProjectMemorySummary(projectPath),
280
+ summerUpdateNotice: getCachedBootDriftNotice()?.text ?? null,
273
281
  guidance: mainScene
274
282
  ? "Use `summer_open_scene` with `mainScene` if no scene is open."
275
283
  : "Main scene not found in project state. Open a known scene path explicitly.",
276
- fileEditingGuidance: "Edit files (including .gd/.cs/.tscn/.tres/.json/docs) with host file tools by default. Use MCP only for live engine state: play/stop, diagnostics, screenshots/verification, navmesh or light bake, runtime inspect, and asset import. If you edit a .tscn that is open in the editor, reload it there afterward so the editor's tab does not overwrite your changes.",
284
+ fileEditingGuidance: "Use summer_read_file plus summer_replace_text or guarded summer_write_file for project files, including .gd/.cs/.tscn/.tres/.json/docs. New files require create_only:true; overwrites require the sha256 receipt from summer_read_file. Prefer scene tools for live hierarchy/inspector changes.",
277
285
  };
278
286
  }));
279
287
  server.tool("summer_open_main_scene", `Open the project's configured main scene from project settings.
@@ -315,10 +323,12 @@ Example: Bind jump to Space and W:
315
323
  ];
316
324
  return client.executeOps(ops);
317
325
  }));
318
- server.tool("summer_get_scene_tree", `Get the full scene tree structure of the currently open scene.
319
-
320
- Use this before structural edits (add/remove/replace).
321
- If you get "no edited scene", call summer_open_main_scene first.`, {}, async () => withEngine(async (client) => client.getSceneState()));
326
+ server.tool("summer_get_scene_tree", `Get a scene tree. Pass scenePath to read that exact in-memory/open scene;
327
+ omit it only when you intentionally want the currently visible editor scene.
328
+ Scene mutations load their explicit target, so a follow-up targeted read does
329
+ not require OpenScene.`, {
330
+ scenePath: z.string().optional().describe("Exact res:// scene path to inspect"),
331
+ }, async ({ scenePath }) => withEngine(async (client) => client.getSceneState(scenePath)));
322
332
  server.tool("summer_import_from_url", `Download a file from a URL and import it into the project. Triggers Godot's full import pipeline — generates .import files, extracts textures from .glb models, creates materials.
323
333
 
324
334
  Use this for: