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
@@ -7,16 +7,18 @@ import { registerDebugTools } from "./tools/debug-tools.js";
7
7
  import { registerVisualTools } from "./tools/visual-tools.js";
8
8
  import { WITH_ENGINE_META } from "./tools/with-engine.js";
9
9
  import { registerProjectTools } from "./tools/project-tools.js";
10
+ import { registerFileTools } from "./tools/file-tools.js";
10
11
  import { registerAssetTools } from "./tools/asset-tools.js";
11
12
  import { registerGenerateTools } from "./tools/generate-tools.js";
12
13
  import { registerCloudTools } from "./tools/cloud-tools.js";
14
+ import { registerCreatorTools } from "./tools/creator-tools.js";
15
+ import { getCachedBootDriftNotice as getCachedNotice, setCachedBootDriftNotice, } from "../lib/mcp-boot-notice.js";
16
+ import { appendMcpLogEvent } from "../lib/mcp-log.js";
13
17
  import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../lib/version-check.js";
14
18
  const require = createRequire(import.meta.url);
15
19
  const { version } = require("../../package.json");
16
- let cachedBootDriftNotice = null;
17
- export function getCachedBootDriftNotice() {
18
- return cachedBootDriftNotice;
19
- }
20
+ let processDiagnosticsInstalled = false;
21
+ export const getCachedBootDriftNotice = getCachedNotice;
20
22
  /**
21
23
  * Fire-and-forget probe of the npm registry on MCP boot. Caches the result for
22
24
  * the session. We never await this in startup so a slow / offline registry
@@ -27,9 +29,14 @@ async function probeBootDrift() {
27
29
  const registry = await fetchLatestRegistryVersion();
28
30
  // Agent is unknown at the MCP layer; use a generic placeholder. Doctor still
29
31
  // prescribes the correct agent-aware command via setup recommendations.
30
- cachedBootDriftNotice = buildBootDriftNotice(version, registry);
32
+ setCachedBootDriftNotice(buildBootDriftNotice(version, registry));
31
33
  }
32
34
  let cachedClient = null;
35
+ let engineSelection;
36
+ export function configureMcpEngineSelection(selection) {
37
+ engineSelection = selection ? { ...selection } : undefined;
38
+ cachedClient = null;
39
+ }
33
40
  export async function getClient() {
34
41
  if (cachedClient) {
35
42
  // The engine rotates its api-token (and can change ports) on every launch, so
@@ -45,12 +52,16 @@ export async function getClient() {
45
52
  }
46
53
  }
47
54
  try {
48
- cachedClient = await EngineApiClient.connect();
55
+ cachedClient = await EngineApiClient.connect(engineSelection);
49
56
  return cachedClient;
50
57
  }
51
- catch {
58
+ catch (error) {
52
59
  cachedClient = null;
53
- throw new Error("Summer Engine is not running. Open it first, or run: npx summer-engine run\n" +
60
+ const reason = error instanceof Error
61
+ ? error.message
62
+ : "Summer Engine is not running.";
63
+ throw new Error(reason + "\n" +
64
+ "Open the intended project in Summer Engine, or run: npx summer-engine run\n" +
54
65
  "Note: only tools that touch the local project need the engine. Cloud tools " +
55
66
  "(summer_generate_*, summer_search_assets, summer_list_my_assets, summer_get_asset, " +
56
67
  "summer_check_job) work right now without it — they only need 'npx summer-engine login'.");
@@ -69,8 +80,56 @@ export function resetClient() {
69
80
  * out to produce pathological payloads in practice, the right fix is
70
81
  * adding range/depth/filter parameters to that tool — not a blanket cap.
71
82
  */
83
+ function errorDetails(error) {
84
+ if (error instanceof Error) {
85
+ return {
86
+ name: error.name,
87
+ message: error.message,
88
+ stack: error.stack,
89
+ };
90
+ }
91
+ return { message: String(error) };
92
+ }
93
+ function writeMcpStderr(event, details = {}) {
94
+ process.stderr.write(`[summer-mcp] ${JSON.stringify({ event, ...details })}\n`);
95
+ }
96
+ function installMcpProcessDiagnostics() {
97
+ if (processDiagnosticsInstalled)
98
+ return;
99
+ processDiagnosticsInstalled = true;
100
+ process.once("uncaughtException", (error) => {
101
+ const details = errorDetails(error);
102
+ appendMcpLogEvent("mcp:uncaught_exception", details);
103
+ writeMcpStderr("mcp:uncaught_exception", {
104
+ message: details.message,
105
+ log: "~/.summer/mcp.log",
106
+ });
107
+ process.exit(1);
108
+ });
109
+ process.once("unhandledRejection", (reason) => {
110
+ const details = errorDetails(reason);
111
+ appendMcpLogEvent("mcp:unhandled_rejection", details);
112
+ writeMcpStderr("mcp:unhandled_rejection", {
113
+ message: details.message,
114
+ log: "~/.summer/mcp.log",
115
+ });
116
+ process.exit(1);
117
+ });
118
+ process.once("SIGTERM", () => {
119
+ appendMcpLogEvent("mcp:sigterm", { pid: process.pid });
120
+ process.exit(0);
121
+ });
122
+ process.once("SIGINT", () => {
123
+ appendMcpLogEvent("mcp:sigint", { pid: process.pid });
124
+ process.exit(0);
125
+ });
126
+ process.once("exit", (code) => {
127
+ appendMcpLogEvent("mcp:exit", { pid: process.pid, code });
128
+ });
129
+ }
72
130
  function installResultSizeLogger(server) {
73
131
  const original = server.tool.bind(server);
132
+ let registeredToolCount = 0;
74
133
  server.tool = function patchedTool(...args) {
75
134
  if (args.length < 2)
76
135
  return original(...args);
@@ -81,6 +140,7 @@ function installResultSizeLogger(server) {
81
140
  const handler = args[lastIdx];
82
141
  if (typeof handler !== "function")
83
142
  return original(...args);
143
+ registeredToolCount += 1;
84
144
  const wrapped = async (...handlerArgs) => {
85
145
  const startedAt = Date.now();
86
146
  const result = await handler(...handlerArgs);
@@ -133,6 +193,11 @@ function installResultSizeLogger(server) {
133
193
  chars,
134
194
  bytes,
135
195
  });
196
+ appendMcpLogEvent("mcp:result_size", {
197
+ toolName: name,
198
+ chars,
199
+ bytes,
200
+ });
136
201
  process.stderr.write(`[summer-mcp] ${line}\n`);
137
202
  }
138
203
  }
@@ -146,25 +211,53 @@ function installResultSizeLogger(server) {
146
211
  newArgs[lastIdx] = wrapped;
147
212
  return original(...newArgs);
148
213
  };
214
+ return () => registeredToolCount;
149
215
  }
150
- export async function startMcpServer() {
216
+ export async function startMcpServer(options = {}) {
217
+ configureMcpEngineSelection({
218
+ instanceId: options.instanceId ?? process.env.SUMMER_ENGINE_INSTANCE_ID,
219
+ projectPath: options.projectPath ?? process.env.SUMMER_ENGINE_PROJECT,
220
+ cwd: options.cwd ?? process.cwd(),
221
+ });
222
+ installMcpProcessDiagnostics();
223
+ appendMcpLogEvent("mcp:start", {
224
+ version,
225
+ pid: process.pid,
226
+ node: process.version,
227
+ cwd: process.cwd(),
228
+ selection: {
229
+ instanceId: options.instanceId ? "explicit" : undefined,
230
+ projectPath: options.projectPath ? "explicit" : undefined,
231
+ cwd: options.cwd ?? process.cwd(),
232
+ },
233
+ });
151
234
  const server = new McpServer({
152
235
  name: "summer-engine",
153
236
  version,
154
237
  });
155
238
  // Passive observability: log result-size to stderr but do not modify
156
239
  // results. See installResultSizeLogger above.
157
- installResultSizeLogger(server);
240
+ const getRegisteredToolCount = installResultSizeLogger(server);
158
241
  registerSceneTools(server);
159
242
  registerDebugTools(server);
160
243
  registerVisualTools(server);
161
244
  registerProjectTools(server);
245
+ registerFileTools(server);
162
246
  registerAssetTools(server);
163
247
  registerGenerateTools(server);
164
248
  registerCloudTools(server);
249
+ registerCreatorTools(server);
165
250
  // Fire-and-forget — never block tool registration on the npm registry.
166
- void probeBootDrift();
251
+ void probeBootDrift().catch((error) => {
252
+ setCachedBootDriftNotice(null);
253
+ appendMcpLogEvent("mcp:boot_drift_probe_failed", errorDetails(error));
254
+ });
167
255
  const transport = new StdioServerTransport();
168
256
  await server.connect(transport);
257
+ appendMcpLogEvent("mcp:ready", {
258
+ version,
259
+ pid: process.pid,
260
+ toolCount: getRegisteredToolCount(),
261
+ });
169
262
  process.stderr.write(`[summer-mcp] MCP server running v${version}.\n`);
170
263
  }
@@ -168,7 +168,10 @@ async function buildImportEntriesForAsset(asset, targetPath) {
168
168
  return { imports: [{ url: fileUrl, path }], importPath: path };
169
169
  }
170
170
  async function importResolvedAsset(args) {
171
- const { asset, parent, path, name } = args;
171
+ const { asset, parent, scenePath, path, name } = args;
172
+ if (parent && !scenePath) {
173
+ throw new Error("scenePath is required when importing an asset into a scene");
174
+ }
172
175
  const { imports, importPath } = await buildImportEntriesForAsset(asset, path);
173
176
  const client = await getClient();
174
177
  const importResult = imports.length === 1
@@ -176,20 +179,30 @@ async function importResolvedAsset(args) {
176
179
  { op: "ImportFromUrl", url: imports[0].url, path: imports[0].path },
177
180
  ])
178
181
  : await client.executeOps([{ op: "ImportFromUrlBatch", imports }]);
179
- const ok = importResult?.results?.[0]?.ok;
180
- if (!ok) {
181
- throw new Error("Could not import asset. Check engine logs.");
182
+ const importReceipts = importResult?.results ?? [];
183
+ const importFailure = importReceipts.find((receipt) => receipt?.ok === false);
184
+ if (importResult?.status === "error" || importFailure) {
185
+ throw new Error(importFailure?.error || "Could not import asset. Check engine logs.");
182
186
  }
183
187
  let addedToScene = false;
188
+ let sceneReceipt = null;
184
189
  if (parent && asset.type === "3d_model") {
185
- await client.executeOps([
190
+ sceneReceipt = await client.executeIdentityBoundOps([
186
191
  {
187
192
  op: "InstantiateScene",
188
193
  parent,
189
194
  scene: importPath,
190
195
  name: sanitizeNodeName(name || asset.title),
191
196
  },
192
- ]);
197
+ { op: "SaveScene" },
198
+ ], { scenePath });
199
+ const placementReceipts = sceneReceipt?.results ?? [];
200
+ const placementFailure = placementReceipts.find((receipt) => receipt?.ok !== true);
201
+ if (sceneReceipt?.status === "error" ||
202
+ placementReceipts.length !== 2 ||
203
+ placementFailure) {
204
+ throw new Error(placementFailure?.error || `Could not add asset to ${scenePath}`);
205
+ }
193
206
  addedToScene = true;
194
207
  }
195
208
  return {
@@ -200,6 +213,8 @@ async function importResolvedAsset(args) {
200
213
  importedTo: importPath,
201
214
  addedToScene,
202
215
  parent: parent || null,
216
+ scenePath: scenePath || null,
217
+ sceneReceipt,
203
218
  };
204
219
  }
205
220
  function jsonSuccess(data) {
@@ -345,8 +360,8 @@ Cloud tool — works WITHOUT the Summer Engine app open.`, {
345
360
  Use this when the asset was just generated, selected from my assets, or returned
346
361
  by search. Unlike summer_import_asset, this does not search or guess: it fetches
347
362
  the asset by ID, downloads it through Summer Engine, and runs Godot's import
348
- pipeline. For 3D models, pass parent to instantiate it in the open scene after
349
- import.
363
+ pipeline. For 3D models, pass parent and scenePath to instantiate it in that
364
+ exact scene; it does not need to be the active editor tab.
350
365
 
351
366
  Requires the Summer Engine app to be open with the project loaded (generation
352
367
  itself does not — only this import step does).`, {
@@ -359,11 +374,15 @@ itself does not — only this import step does).`, {
359
374
  .string()
360
375
  .optional()
361
376
  .describe("Optional parent node path. For 3D models only; imports and instantiates under this node."),
377
+ scenePath: z
378
+ .string()
379
+ .optional()
380
+ .describe("Required with parent. Exact res:// scene that receives the 3D model."),
362
381
  name: z
363
382
  .string()
364
383
  .optional()
365
384
  .describe("Optional scene node name when parent is provided."),
366
- }, async ({ assetId, path, parent, name }) => {
385
+ }, async ({ assetId, path, parent, scenePath, name }) => {
367
386
  const fetched = await getAssetApi(assetId);
368
387
  if (fetched.error || !fetched.asset) {
369
388
  return jsonError({
@@ -375,6 +394,7 @@ itself does not — only this import step does).`, {
375
394
  const imported = await importResolvedAsset({
376
395
  asset: fetched.asset,
377
396
  parent,
397
+ scenePath,
378
398
  path,
379
399
  name,
380
400
  });
@@ -390,7 +410,7 @@ itself does not — only this import step does).`, {
390
410
  return jsonError({
391
411
  error: "import_failed",
392
412
  message: msg,
393
- hint: "Make sure Summer Engine is running and the target scene is open.",
413
+ hint: "Make sure Summer Engine is running and pass the exact scenePath when parent is set.",
394
414
  });
395
415
  }
396
416
  });
@@ -402,9 +422,10 @@ Searches, picks the top result, downloads and imports it, then optionally adds i
402
422
  Requires authentication. If the user gets an auth error, they need to run 'npx summer-engine login' in their terminal first. Summer Engine must be running.`, {
403
423
  query: z.string().describe("What to find, e.g. 'low-poly tree', 'wooden crate'"),
404
424
  parent: z.string().optional().describe("Parent node path to add the asset under, e.g. './World'. If omitted, only imports (no scene placement)"),
425
+ scenePath: z.string().optional().describe("Required with parent. Exact res:// target scene path."),
405
426
  assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("3d_model").describe("Preferred asset type"),
406
427
  source: z.enum(["library", "my_assets", "all"]).default("all").describe("Where to search before importing"),
407
- }, async ({ query, parent, assetType, source }) => {
428
+ }, async ({ query, parent, scenePath, assetType, source }) => {
408
429
  const searchResult = await searchAssetsApi({ query, assetType, limit: 5, source });
409
430
  if (searchResult.error) {
410
431
  return {
@@ -443,7 +464,7 @@ Requires authentication. If the user gets an auth error, they need to run 'npx s
443
464
  };
444
465
  }
445
466
  try {
446
- const imported = await importResolvedAsset({ asset: best, parent });
467
+ const imported = await importResolvedAsset({ asset: best, parent, scenePath });
447
468
  // 2D sprites and audio: import only; user adds to scene manually
448
469
  return {
449
470
  content: [
@@ -15,28 +15,28 @@ function textJson(value) {
15
15
  };
16
16
  }
17
17
  export function registerCloudTools(server) {
18
- server.tool("summer_cloud_init", "Enable Summer Cloud for a project.", projectArg.shape, async (args) => textJson(await cloudInit({ project: args.project, face: "mcp" })));
19
- server.tool("summer_cloud_status", "Show Summer Cloud sync status.", projectArg.shape, async (args) => textJson(await cloudStatus({ project: args.project, face: "mcp" })));
20
- server.tool("summer_cloud_push", "Push local project changes to Summer Cloud.", {
18
+ server.tool("summer_cloud_init", "Enable Summer Cloud (content-addressed whole-project sync) for a project, writing the git-tracked summer-cloud.json binding. Use once per project before the first push, or after deleting the binding to fork the project into a new cloud line.", projectArg.shape, async (args) => textJson(await cloudInit({ project: args.project, face: "mcp" })));
19
+ server.tool("summer_cloud_status", "Show what a sync would move: file counts, paths queued to push or pull, and any waiting conflict sets. Always call this before push or pull so you can report the delete and change counts to the user; never sync blind.", projectArg.shape, async (args) => textJson(await cloudStatus({ project: args.project, face: "mcp" })));
20
+ server.tool("summer_cloud_push", "Upload local project changes to Summer Cloud as a new restorable version (use to back up big assets, move to another machine, or snapshot before a risky bulk op). confirmDeletes gates large deletions (more than 10 files, or >=20% of the tree, or the whole tree): run summer_cloud_status first, show the exact delete count to the user, and only set it once they confirm. Never pass confirmDeletes blindly.", {
21
21
  ...projectArg.shape,
22
- confirmDeletes: z.boolean().default(false).describe("Required when a push would delete many cloud files."),
23
- bootstrap: bootstrap.optional().describe("No-base bootstrap choice."),
24
- adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path."),
22
+ confirmDeletes: z.boolean().default(false).describe("Gate for large cloud deletions. Required when a push would delete more than 10 files, at least 20% of the tracked tree, or the entire tree. Surface the delete count from summer_cloud_status to the user before setting this; never set it blindly."),
23
+ bootstrap: bootstrap.optional().describe("No-base bootstrap choice when local and cloud both have files: keep-cloud, keep-local, or merge. Present the choice to the user."),
24
+ adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path. Only set after confirming the move is intentional, not a copy."),
25
25
  }, async (args) => textJson(await cloudPush({ ...args, face: "mcp" })));
26
- server.tool("summer_cloud_pull", "Pull Summer Cloud changes into the local project.", {
26
+ server.tool("summer_cloud_pull", "Download Summer Cloud changes into the local project (use to fetch a teammate's bytes or hydrate on a second machine). A local checkpoint is written before any destructive apply, and concurrent edits become recoverable conflict sets rather than silent overwrites. Run summer_cloud_status first.", {
27
27
  ...projectArg.shape,
28
- bootstrap: bootstrap.optional().describe("No-base bootstrap choice."),
29
- adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path."),
28
+ bootstrap: bootstrap.optional().describe("No-base bootstrap choice when local and cloud both have files: keep-cloud, keep-local, or merge. Present the choice to the user."),
29
+ adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path. Only set after confirming the move is intentional, not a copy."),
30
30
  }, async (args) => textJson(await cloudPull({ ...args, face: "mcp" })));
31
- server.tool("summer_cloud_restore", "Restore a retained cloud version (creates a new head version server-side, then pulls), or a local pre-sync checkpoint via checkpointStamp.", {
31
+ server.tool("summer_cloud_restore", "Roll a project back to an earlier state. Pass version to restore a retained cloud version (creates a new head version server-side, then pulls); pass checkpointStamp to restore a local pre-sync checkpoint for unpushed work. The restore restores bytes but does not delete files a later sync added, so review the extraneous-file list it returns.", {
32
32
  ...projectArg.shape,
33
- version: z.number().int().positive().optional().describe("Cloud version to restore."),
34
- checkpointStamp: z.string().optional().describe("Local checkpoint stamp (see summer_cloud_checkpoints)."),
33
+ version: z.number().int().positive().optional().describe("Cloud version to restore (see summer_cloud_status / version history)."),
34
+ checkpointStamp: z.string().optional().describe("Local checkpoint stamp (see summer_cloud_checkpoints). Use for unpushed local-only work."),
35
35
  }, async (args) => textJson(await cloudRestore({ project: args.project, version: args.version, checkpoint: args.checkpointStamp, face: "mcp" })));
36
- server.tool("summer_cloud_checkpoints", "List local pre-sync checkpoints.", projectArg.shape, async (args) => textJson(await cloudCheckpoints({ project: args.project, face: "mcp" })));
37
- server.tool("summer_cloud_conflicts", "List local Summer Cloud conflict sets, or restore a preserved conflict file with restorePath.", {
36
+ server.tool("summer_cloud_checkpoints", "List the local pre-sync checkpoints taken before destructive applies. Use to find a checkpoint stamp to pass to summer_cloud_restore when recovering local-only work.", projectArg.shape, async (args) => textJson(await cloudCheckpoints({ project: args.project, face: "mcp" })));
37
+ server.tool("summer_cloud_conflicts", "List preserved Summer Cloud conflict sets (the losing bytes from concurrent edits, never discarded), or recover one by passing restorePath to re-enter it as a fresh edit. Use after a push or pull reports conflicts so the user can choose what to keep.", {
38
38
  ...projectArg.shape,
39
- restorePath: z.string().optional().describe("Project-relative path to restore from the conflict sets."),
39
+ restorePath: z.string().optional().describe("Project-relative path to restore from the conflict sets, re-entered as a fresh edit."),
40
40
  set: z.string().optional().describe("Conflict set stamp; defaults to the newest set containing the path."),
41
41
  }, async (args) => textJson(await cloudConflicts({ project: args.project, restorePath: args.restorePath, set: args.set, face: "mcp" })));
42
42
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCreatorTools(server: McpServer): void;
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+ import { CreatorOperationError, listCreatorReleases, publishCreator, readCreatorLogs, } from "../../lib/creator.js";
3
+ import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, setConfigValue, unsetConfigValue, } from "../../lib/config.js";
4
+ function textJson(value, isError = false) {
5
+ return {
6
+ content: [
7
+ {
8
+ type: "text",
9
+ text: JSON.stringify(value, null, 2),
10
+ },
11
+ ],
12
+ ...(isError ? { isError: true } : {}),
13
+ };
14
+ }
15
+ async function creatorResult(operation) {
16
+ try {
17
+ return textJson(await operation());
18
+ }
19
+ catch (error) {
20
+ if (error instanceof CreatorOperationError) {
21
+ return textJson({
22
+ ok: false,
23
+ code: error.code,
24
+ operation: error.operation,
25
+ message: error.message,
26
+ recovery: error.recovery,
27
+ }, true);
28
+ }
29
+ return textJson({
30
+ ok: false,
31
+ code: "creator_request_invalid",
32
+ message: error instanceof Error ? error.message : String(error),
33
+ }, true);
34
+ }
35
+ }
36
+ export function registerCreatorTools(server) {
37
+ server.tool("summer_creator_publish", "Publish an exact exported Summer .pck through the versioned Summercraft creator API. First call with confirm=false and present the returned project, version, digest, size, artifact path, channel, and notes; set confirm only after the user approves that exact target. The server independently verifies token scope, ownership, bytes, and review state.", {
38
+ project: z.string().optional().describe("Project root. Defaults to the current working directory."),
39
+ artifact: z.string().describe("Exact path to the exported Summer .pck artifact."),
40
+ version: z.string().describe("Immutable release version."),
41
+ manifest: z.string().optional().describe("Optional path to a JSON release manifest."),
42
+ projectId: z.string().optional().describe("Creator project ID. Defaults to ~/.summer/config.json."),
43
+ channel: z.string().optional().describe("Release channel. Defaults to the configured channel or production."),
44
+ notes: z.string().optional().describe("Release notes shown during confirmation and recorded in the local audit."),
45
+ confirm: z.boolean().default(false).describe("Set true only after the user approves the exact release target."),
46
+ }, async (args) => creatorResult(() => publishCreator({
47
+ ...args,
48
+ face: "mcp",
49
+ })));
50
+ server.tool("summer_creator_releases", "List real creator-owned releases from the versioned Summercraft creator API. The server independently verifies the exact publish scope and project ownership.", {
51
+ projectId: z.string().optional().describe("Creator project ID. Defaults to ~/.summer/config.json."),
52
+ limit: z.number().int().min(1).max(100).default(20),
53
+ cursor: z.string().optional().describe("Opaque nextCursor from the previous page."),
54
+ }, async (args) => creatorResult(() => listCreatorReleases({
55
+ ...args,
56
+ face: "mcp",
57
+ })));
58
+ server.tool("summer_creator_logs", "Read creator runtime logs for a project or release. If the required platform route or token scope is unavailable, returns the exact recovery action instead of placeholder logs.", {
59
+ projectId: z.string().optional().describe("Creator project ID. Defaults to ~/.summer/config.json."),
60
+ releaseId: z.string().optional().describe("Optional release ID."),
61
+ limit: z.number().int().min(1).max(1000).default(100),
62
+ }, async (args) => creatorResult(() => readCreatorLogs({
63
+ ...args,
64
+ face: "mcp",
65
+ })));
66
+ server.tool("summer_creator_config", "Read or update the shared non-secret ~/.summer configuration used by the CLI and this MCP. Mutations require confirm=true. Tokens are never returned or accepted by this tool.", {
67
+ action: z.enum(["list", "get", "set", "unset"]),
68
+ key: z.enum(CONFIG_KEYS).optional(),
69
+ value: z.string().optional(),
70
+ confirm: z.boolean().default(false),
71
+ }, async ({ action, key, value, confirm }) => creatorResult(async () => {
72
+ if (action === "list") {
73
+ const config = await readSummerConfig();
74
+ return {
75
+ ok: true,
76
+ values: Object.fromEntries(CONFIG_KEYS.map((name) => [
77
+ name,
78
+ getConfigValue(config, name) ?? null,
79
+ ])),
80
+ };
81
+ }
82
+ if (!key || !isConfigKey(key)) {
83
+ throw new Error(`A valid key is required. Recovery: use one of ${CONFIG_KEYS.join(", ")}.`);
84
+ }
85
+ if (action === "get") {
86
+ return {
87
+ ok: true,
88
+ key,
89
+ value: getConfigValue(await readSummerConfig(), key) ?? null,
90
+ };
91
+ }
92
+ if (!confirm) {
93
+ throw new Error(`Changing ${key} requires confirmation. Recovery: show the exact change to the user, then retry with confirm=true.`);
94
+ }
95
+ if (action === "set") {
96
+ if (value === undefined) {
97
+ throw new Error(`A value is required for ${key}. Recovery: provide value and retry with confirm=true.`);
98
+ }
99
+ const config = await setConfigValue(key, value);
100
+ return { ok: true, key, value: getConfigValue(config, key) };
101
+ }
102
+ const config = await unsetConfigValue(key);
103
+ return { ok: true, key, value: getConfigValue(config, key) ?? null };
104
+ }));
105
+ }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { withEngine } from "./with-engine.js";
3
3
  import { shapeEngineLogResponse } from "../../lib/log-filters.js";
4
+ import { createDebugReportArtifact } from "../../lib/debug-report.js";
4
5
  export function registerDebugTools(server) {
5
6
  server.tool("summer_get_diagnostics", `Quick overview of all errors and warnings from both the editor console and the runtime debugger. Returns error counts and a guidance message.
6
7
 
@@ -92,11 +93,53 @@ After starting, use summer_get_diagnostics to check for runtime errors.
92
93
  You can run a specific scene instead of the main scene — useful for testing individual levels or UI screens.`, {
93
94
  scene: z.string().optional().describe("Scene to run instead of main scene, e.g. 'res://levels/test_level.tscn'"),
94
95
  }, async ({ scene }) => withEngine(async (client) => client.play(scene)));
95
- server.tool("summer_stop", "Stop the running game. Call this before making scene changes — some operations require the game to not be running.", {}, async () => withEngine(async (client) => client.stop()));
96
+ server.tool("summer_stop", "Stop the running game. Use after runtime verification or when you intentionally need to restart the running instance; ordinary editor scene mutations do not require a blanket stop.", {}, async () => withEngine(async (client) => client.stop()));
96
97
  server.tool("summer_is_running", "Check if the game is currently running. Returns the active scene path if running.", {}, async () => withEngine(async (client) => client.executeOps([{ op: "IsGameRunning" }])));
97
98
  server.tool("summer_get_script_errors", `Check a GDScript file for parse/compile errors without running the game.
98
99
 
99
100
  Use after writing or editing a .gd file to verify it compiles. Returns line numbers, error messages, and severity. Much faster than running the game to discover script errors.`, {
100
101
  path: z.string().describe("Script path, e.g. 'res://scripts/player.gd' or 'res://player_controller.gd'"),
101
102
  }, async ({ path }) => withEngine(async (client) => client.getScriptErrors(path)));
103
+ server.tool("summer_create_debug_report", `Create a support-ready Markdown report for /summer debug.
104
+
105
+ Use this when the user says "/summer debug", asks to send Summer a bug report,
106
+ or needs a portable artifact from a failing Codex/cloud/agent session. The
107
+ report includes Summer doctor checks, engine health, diagnostics, console
108
+ output, debugger errors/warnings, and an agent handoff prompt. It omits auth
109
+ tokens and project file contents, but the user should still review it before
110
+ sending because local paths and stack traces may appear.`, {
111
+ issue: z.string().optional().describe("User-visible issue or repro summary."),
112
+ output_path: z.string().optional().describe("Where to write the Markdown report. Defaults to the open project root, or the current working directory if no project is open."),
113
+ include_play_session: z.boolean().optional().default(false).describe("Launch the game briefly and collect post-play diagnostics."),
114
+ play_wait_ms: z.number().optional().default(2500).describe("Milliseconds to wait after launching the game when include_play_session is true."),
115
+ max_console_lines: z.number().optional().default(200).describe("Console lines to include after filtering/deduping."),
116
+ max_debugger_entries: z.number().optional().default(100).describe("Debugger error/warning entries to include after filtering/deduping."),
117
+ include_doctor: z.boolean().optional().default(true).describe("Include summer doctor checks in the report."),
118
+ }, async ({ issue, output_path, include_play_session, play_wait_ms, max_console_lines, max_debugger_entries, include_doctor, }) => {
119
+ const artifact = await createDebugReportArtifact({
120
+ issue,
121
+ outputPath: output_path,
122
+ includePlaySession: include_play_session,
123
+ playWaitMs: play_wait_ms,
124
+ maxConsoleLines: max_console_lines,
125
+ maxDebuggerEntries: max_debugger_entries,
126
+ includeDoctor: include_doctor,
127
+ });
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: JSON.stringify({
133
+ ok: true,
134
+ path: artifact.path,
135
+ engineConnected: artifact.report.engine.connected,
136
+ generatedAt: artifact.report.generatedAt,
137
+ issue: artifact.report.issue,
138
+ doctorSummary: artifact.report.doctor?.summary ?? null,
139
+ reviewNote: "Report omits auth tokens, but the user should review local paths and stack traces before sending.",
140
+ }, null, 2),
141
+ },
142
+ ],
143
+ };
144
+ });
102
145
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerFileTools(server: McpServer): void;