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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/plugin.json +6 -2
- package/.cursor-plugin/plugin.json +5 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +2 -2
- package/AGENTS.md +11 -7
- package/CHANGELOG.md +40 -0
- package/GEMINI.md +9 -7
- package/README.md +63 -21
- package/_persona/summer/SKILL.md +1 -0
- package/assets/autopilot/README.md +51 -0
- package/assets/autopilot/autopilot.gd +222 -0
- package/assets/autopilot/probe_base.gd +262 -0
- package/assets/autopilot/run.sh +78 -0
- package/commands/gameskill.md +11 -2
- package/commands/summer.md +13 -0
- package/dist/bin/summer.js +12 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +38 -6
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +44 -0
- package/dist/commands/doctor.js +112 -8
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +123 -36
- package/dist/commands/logout.js +3 -16
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +19 -0
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +20 -0
- package/dist/commands/releases.d.ts +2 -0
- package/dist/commands/releases.js +20 -0
- package/dist/commands/run.js +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/lib/api-client.d.ts +15 -4
- package/dist/lib/api-client.js +138 -13
- package/dist/lib/async-op-lifecycle.d.ts +7 -2
- package/dist/lib/async-op-lifecycle.js +32 -9
- package/dist/lib/auth.d.ts +40 -12
- package/dist/lib/auth.js +173 -37
- package/dist/lib/cloud/engine-bridge.d.ts +17 -4
- package/dist/lib/cloud/engine-bridge.js +32 -6
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +142 -0
- package/dist/lib/creator.d.ts +93 -0
- package/dist/lib/creator.js +393 -0
- package/dist/lib/debug-report.d.ts +50 -0
- package/dist/lib/debug-report.js +308 -0
- package/dist/lib/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- package/dist/lib/game-task-plan.js +4 -4
- package/dist/lib/mcp-boot-notice.d.ts +3 -0
- package/dist/lib/mcp-boot-notice.js +7 -0
- package/dist/lib/mcp-log.d.ts +3 -0
- package/dist/lib/mcp-log.js +72 -0
- package/dist/lib/public-surface-paths.d.ts +13 -0
- package/dist/lib/public-surface-paths.js +42 -0
- package/dist/lib/skills-registry.d.ts +33 -1
- package/dist/lib/skills-registry.js +55 -0
- package/dist/lib/store.d.ts +14 -0
- package/dist/lib/store.js +145 -0
- package/dist/lib/summer-compatibility.d.ts +6 -0
- package/dist/lib/summer-compatibility.js +22 -0
- package/dist/mcp/server.d.ts +10 -3
- package/dist/mcp/server.js +104 -11
- package/dist/mcp/tools/asset-tools.js +33 -12
- package/dist/mcp/tools/cloud-tools.js +15 -15
- package/dist/mcp/tools/creator-tools.d.ts +2 -0
- package/dist/mcp/tools/creator-tools.js +105 -0
- package/dist/mcp/tools/debug-tools.js +44 -1
- package/dist/mcp/tools/file-tools.d.ts +2 -0
- package/dist/mcp/tools/file-tools.js +154 -0
- package/dist/mcp/tools/generate-tools.js +159 -11
- package/dist/mcp/tools/project-tools.js +37 -27
- package/dist/mcp/tools/scene-tools.js +106 -29
- package/dist/mcp/tools/visual-tools.js +4 -3
- package/dist/mcp/tools/with-engine.js +4 -4
- package/hooks/session-start.sh +5 -5
- package/package.json +3 -1
- package/references/collaborative-protocol.md +50 -0
- package/references/gd-style.md +121 -0
- package/references/godot-version.md +51 -0
- package/references/mcp-tools-reference.md +199 -0
- package/references/public-surface-language.md +30 -0
- package/references/summer-folder.md +90 -0
- package/references/template-registry.md +73 -0
- package/skills/2d-assets/character-portrait/SKILL.md +17 -12
- package/skills/2d-assets/concept-art/SKILL.md +10 -9
- package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
- package/skills/2d-assets/pixel-art/SKILL.md +36 -20
- package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
- package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
- package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
- package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
- package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
- package/skills/3d-assets/character-model/SKILL.md +34 -19
- package/skills/3d-assets/environment-kit/SKILL.md +11 -5
- package/skills/3d-assets/organic-model/SKILL.md +8 -5
- package/skills/3d-assets/prop-model/SKILL.md +9 -3
- package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
- package/skills/README.md +1 -1
- package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
- package/skills/animation/animation-tree/SKILL.md +39 -15
- package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
- package/skills/animation/generate-motion/SKILL.md +47 -15
- package/skills/animation/procedural-animation/SKILL.md +31 -13
- package/skills/animation/retarget/SKILL.md +127 -79
- package/skills/audio/adaptive-music/SKILL.md +24 -20
- package/skills/audio/ambient-bed/SKILL.md +22 -16
- package/skills/audio/audio-direction/SKILL.md +15 -10
- package/skills/audio/music-track/SKILL.md +18 -12
- package/skills/audio/sound-effect/SKILL.md +17 -12
- package/skills/audio/voice-line/SKILL.md +42 -42
- package/skills/character-controllers/fps-controller/SKILL.md +26 -16
- package/skills/debugging/debug/SKILL.md +74 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +140 -0
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
- package/skills/level-design/design-level/SKILL.md +22 -8
- package/skills/level-design/scene-to-level/SKILL.md +13 -7
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
- package/skills/performance/tune-performance/SKILL.md +51 -32
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
- package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
- package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
- package/skills/scene-and-project/make-game/SKILL.md +2 -1
- package/skills/scene-and-project/play/SKILL.md +10 -4
- package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
- package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
- package/skills/video/animated-loop/SKILL.md +63 -39
- package/skills/video/cinematic-cutscene/SKILL.md +66 -30
- package/skills/video/trailer-shot/SKILL.md +30 -21
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
- package/skills/visual-effects/game-feel/SKILL.md +51 -33
- package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
- package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
- package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
- package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
- package/skills/workflow/brainstorming/SKILL.md +1 -1
- package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
- package/skills/workflow/gameskill/SKILL.md +11 -2
- package/skills/workflow/headless-scripting/SKILL.md +407 -0
- package/skills/workflow/investigating-bugs/SKILL.md +4 -2
- package/skills/workflow/mcpupdate/SKILL.md +9 -9
- package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
- package/skills/workflow/skill-create/SKILL.md +7 -7
- package/skills/workflow/skill-test/SKILL.md +3 -3
- package/skills/workflow/using-summer/SKILL.md +32 -3
- package/skills/workflow/verification-before-completion/SKILL.md +2 -0
- package/skills/workflow/writing-plans/SKILL.md +7 -7
- package/skills/workflow/writing-skills/SKILL.md +7 -5
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
17
|
-
export
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
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.
|
|
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
|
|
349
|
-
|
|
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
|
|
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
|
|
20
|
-
server.tool("summer_cloud_push", "
|
|
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
|
|
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", "
|
|
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", "
|
|
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
|
|
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,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.
|
|
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
|
}
|