summer-engine 2.7.0 → 2.8.1
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/plugin.json +2 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.cursor-plugin/plugin.json +2 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +1 -1
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +28 -0
- package/GEMINI.md +5 -4
- package/README.md +58 -21
- 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/dist/bin/summer.js +8 -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/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +136 -37
- 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/lib/api-client.d.ts +27 -8
- package/dist/lib/api-client.js +65 -14
- 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/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- 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 +8 -0
- package/dist/lib/skills-registry.js +9 -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 +8 -1
- package/dist/mcp/server.js +25 -4
- 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.d.ts +12 -0
- package/dist/mcp/tools/debug-tools.js +100 -1
- package/dist/mcp/tools/generate-tools.js +16 -6
- package/dist/mcp/tools/project-tools.js +131 -16
- package/dist/mcp/tools/scene-tools.js +166 -66
- package/dist/mcp/tools/visual-tools.js +32 -8
- package/dist/mcp/tools/with-engine.js +51 -19
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- 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/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 +52 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +56 -23
- 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 +3 -5
- 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 +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/brainstorming/scripts/start-server.sh +0 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
- 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/investigating-bugs/find-polluter.sh +0 -0
- package/skills/workflow/mcpupdate/SKILL.md +3 -3
- 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 +12 -2
- 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/render-graphs.js +0 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
|
@@ -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,2 +1,14 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Prioritized, bounded view of the engine diagnostics payload:
|
|
4
|
+
* - console.messages reordered: errors first, then warnings, then a capped tail
|
|
5
|
+
* of info/std/editor noise (engine order is newest-first; that order is
|
|
6
|
+
* preserved within each severity bucket, so the kept tail is the most recent).
|
|
7
|
+
* - debugger.errors_data untouched; debugger.warnings_data capped to the most
|
|
8
|
+
* recent DEBUGGER_WARNING_CAP entries (engine emits newest-first).
|
|
9
|
+
* - a `_view` block with honest counters and a hint pointing at includeAll.
|
|
10
|
+
* Shape-tolerant: anything unexpected passes through unchanged.
|
|
11
|
+
* Exported for unit tests.
|
|
12
|
+
*/
|
|
13
|
+
export declare function prioritizeDiagnostics(payload: unknown): unknown;
|
|
2
14
|
export declare function registerDebugTools(server: McpServer): void;
|
|
@@ -2,16 +2,115 @@ import { z } from "zod";
|
|
|
2
2
|
import { withEngine } from "./with-engine.js";
|
|
3
3
|
import { shapeEngineLogResponse } from "../../lib/log-filters.js";
|
|
4
4
|
import { createDebugReportArtifact } from "../../lib/debug-report.js";
|
|
5
|
+
// summer_get_diagnostics view shaping. The engine serves /api/state/diagnostics
|
|
6
|
+
// from a pre-published snapshot (empty args — query params are NOT forwarded on
|
|
7
|
+
// that route), so trimming has to happen here. The snapshot defaults bury
|
|
8
|
+
// task-specific failures under baseline info noise (50 console messages, up to
|
|
9
|
+
// 100 debugger entries); this reorders by severity and caps ONLY the
|
|
10
|
+
// low-severity bodies. All counts stay intact and honest. No pattern-matching
|
|
11
|
+
// against specific noise strings — severity + recency + caps only.
|
|
12
|
+
const CONSOLE_NOISE_TAIL_CAP = 10;
|
|
13
|
+
const DEBUGGER_WARNING_CAP = 20;
|
|
14
|
+
/**
|
|
15
|
+
* Prioritized, bounded view of the engine diagnostics payload:
|
|
16
|
+
* - console.messages reordered: errors first, then warnings, then a capped tail
|
|
17
|
+
* of info/std/editor noise (engine order is newest-first; that order is
|
|
18
|
+
* preserved within each severity bucket, so the kept tail is the most recent).
|
|
19
|
+
* - debugger.errors_data untouched; debugger.warnings_data capped to the most
|
|
20
|
+
* recent DEBUGGER_WARNING_CAP entries (engine emits newest-first).
|
|
21
|
+
* - a `_view` block with honest counters and a hint pointing at includeAll.
|
|
22
|
+
* Shape-tolerant: anything unexpected passes through unchanged.
|
|
23
|
+
* Exported for unit tests.
|
|
24
|
+
*/
|
|
25
|
+
export function prioritizeDiagnostics(payload) {
|
|
26
|
+
if (!payload || typeof payload !== "object")
|
|
27
|
+
return payload;
|
|
28
|
+
const root = payload;
|
|
29
|
+
if (!root.data || typeof root.data !== "object")
|
|
30
|
+
return payload;
|
|
31
|
+
const data = root.data;
|
|
32
|
+
const shapedData = { ...data };
|
|
33
|
+
let totalConsole = 0;
|
|
34
|
+
let shownConsole = 0;
|
|
35
|
+
let suppressedInfo = 0;
|
|
36
|
+
if (data.console && typeof data.console === "object") {
|
|
37
|
+
const c = data.console;
|
|
38
|
+
totalConsole = typeof c.total === "number" ? c.total : 0;
|
|
39
|
+
if (Array.isArray(c.messages)) {
|
|
40
|
+
const errors = [];
|
|
41
|
+
const warnings = [];
|
|
42
|
+
const rest = [];
|
|
43
|
+
for (const m of c.messages) {
|
|
44
|
+
const type = m && typeof m === "object" ? m.type : undefined;
|
|
45
|
+
if (type === "error")
|
|
46
|
+
errors.push(m);
|
|
47
|
+
else if (type === "warning")
|
|
48
|
+
warnings.push(m);
|
|
49
|
+
else
|
|
50
|
+
rest.push(m);
|
|
51
|
+
}
|
|
52
|
+
const keptRest = rest.slice(0, CONSOLE_NOISE_TAIL_CAP);
|
|
53
|
+
suppressedInfo = rest.length - keptRest.length;
|
|
54
|
+
const messages = [...errors, ...warnings, ...keptRest];
|
|
55
|
+
shownConsole = messages.length;
|
|
56
|
+
shapedData.console = { ...c, messages, returned: messages.length };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let totalDebugger = 0;
|
|
60
|
+
let shownDebugger = 0;
|
|
61
|
+
let suppressedDebuggerWarnings = 0;
|
|
62
|
+
if (data.debugger && typeof data.debugger === "object") {
|
|
63
|
+
const g = data.debugger;
|
|
64
|
+
totalDebugger =
|
|
65
|
+
(typeof g.errors === "number" ? g.errors : 0) +
|
|
66
|
+
(typeof g.warnings === "number" ? g.warnings : 0);
|
|
67
|
+
const errorsData = Array.isArray(g.errors_data) ? g.errors_data : [];
|
|
68
|
+
if (Array.isArray(g.warnings_data)) {
|
|
69
|
+
const keptWarnings = g.warnings_data.slice(0, DEBUGGER_WARNING_CAP);
|
|
70
|
+
suppressedDebuggerWarnings = g.warnings_data.length - keptWarnings.length;
|
|
71
|
+
shownDebugger = errorsData.length + keptWarnings.length;
|
|
72
|
+
shapedData.debugger = { ...g, warnings_data: keptWarnings };
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
shownDebugger = errorsData.length;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...root,
|
|
80
|
+
data: shapedData,
|
|
81
|
+
_view: {
|
|
82
|
+
mode: "prioritized",
|
|
83
|
+
totalConsole,
|
|
84
|
+
shownConsole,
|
|
85
|
+
suppressedInfo,
|
|
86
|
+
totalDebugger,
|
|
87
|
+
shownDebugger,
|
|
88
|
+
suppressedDebuggerWarnings,
|
|
89
|
+
hint: "All counts are complete; only low-severity message bodies were trimmed. Re-call with includeAll: true for the untrimmed payload, or use summer_get_console / summer_get_debugger_warnings for targeted reads.",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
5
93
|
export function registerDebugTools(server) {
|
|
6
94
|
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.
|
|
7
95
|
|
|
8
96
|
ALWAYS call this FIRST before diving into summer_get_console or summer_get_debugger_errors. It tells you where to look.
|
|
9
97
|
|
|
98
|
+
By default the response is a prioritized view: errors first, then warnings, then a small capped tail of recent info/std noise. Counts (console totals, debugger totals) are always complete — only low-severity message bodies are trimmed, and a "_view" block reports exactly what was suppressed. Pass includeAll: true for the full untrimmed engine payload.
|
|
99
|
+
|
|
10
100
|
Typical workflow after making changes:
|
|
11
101
|
1. summer_get_diagnostics — are there issues?
|
|
12
102
|
2. If errors: summer_get_console or summer_get_debugger_errors for details
|
|
13
103
|
3. Fix the issues
|
|
14
|
-
4. summer_get_diagnostics again to verify`, {
|
|
104
|
+
4. summer_get_diagnostics again to verify`, {
|
|
105
|
+
includeAll: z
|
|
106
|
+
.boolean()
|
|
107
|
+
.optional()
|
|
108
|
+
.default(false)
|
|
109
|
+
.describe("Return the full engine diagnostics payload untrimmed (no severity reordering, no info-noise cap)."),
|
|
110
|
+
}, async ({ includeAll }) => withEngine(async (client) => {
|
|
111
|
+
const raw = await client.getDiagnostics();
|
|
112
|
+
return includeAll ? raw : prioritizeDiagnostics(raw);
|
|
113
|
+
}));
|
|
15
114
|
server.tool("summer_get_console", `Read recent messages from the editor's Output panel.
|
|
16
115
|
|
|
17
116
|
Output is post-processed for token economy: consecutive identical messages collapse into one entry with a "(×N)" count suffix, and the response carries a "_filter" summary so you can see what was hidden. Use errors_only=true (default) to drop info/std noise; use raw=true to bypass all shaping.
|
|
@@ -274,7 +274,9 @@ Known models:
|
|
|
274
274
|
- "nano-banana-2" (default) — High quality, supports txt2img and img2img
|
|
275
275
|
- "gemini-flash" — Google Gemini 2.5 Flash, fast
|
|
276
276
|
- "flux-2" — FLUX.2, good for specific styles
|
|
277
|
-
|
|
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.
|
|
278
280
|
|
|
279
281
|
Two modes:
|
|
280
282
|
- txt2img (default): just pass prompt
|
|
@@ -371,8 +373,12 @@ Capabilities:
|
|
|
371
373
|
- "music" — Generate background music. Requires 'prompt'. Optional 'durationSeconds'.
|
|
372
374
|
- "text_to_dialogue" — Multi-voice dialogue. Requires 'inputs' array of {text, voiceId} objects.
|
|
373
375
|
|
|
374
|
-
The 'options' object is
|
|
375
|
-
|
|
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.
|
|
376
382
|
|
|
377
383
|
Returns the generated audio URL and asset metadata.
|
|
378
384
|
Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
|
|
@@ -407,7 +413,9 @@ Requires authentication: run 'npx summer-engine login' first.`, {
|
|
|
407
413
|
options: z
|
|
408
414
|
.record(z.any())
|
|
409
415
|
.optional()
|
|
410
|
-
.describe("Provider
|
|
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."),
|
|
411
419
|
}, async ({ capability, text, prompt, voiceId, modelId, durationSeconds, inputs, options }) => {
|
|
412
420
|
const body = { capability };
|
|
413
421
|
if (text)
|
|
@@ -439,7 +447,9 @@ Available models:
|
|
|
439
447
|
- "hunyuan" (default) — Hunyuan 3D v3.1 Pro, high quality
|
|
440
448
|
- "trellis" — Trellis 2, fast and detailed
|
|
441
449
|
- "meshy" — Meshy, legacy option
|
|
442
|
-
|
|
450
|
+
|
|
451
|
+
Model ids are an ALLOWLIST, not a passthrough. An unrecognised id is rejected
|
|
452
|
+
with 400 listing the accepted ids.
|
|
443
453
|
|
|
444
454
|
Available kinds:
|
|
445
455
|
- "text-to-3d" (default) — From text description. Requires 'prompt'.
|
|
@@ -699,7 +709,7 @@ Cost: ~$0.10 per clip. Confirm with user before spending.
|
|
|
699
709
|
|
|
700
710
|
Custom prompt-driven motion is on the roadmap; not yet shipped. For one-off
|
|
701
711
|
signature moves not on the curated list, fall back to hand-authoring in the
|
|
702
|
-
|
|
712
|
+
Summer Engine or importing from Mixamo.
|
|
703
713
|
|
|
704
714
|
Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
|
|
705
715
|
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
@@ -88,6 +88,62 @@ function getCurrentScene(projectState, sceneState, health) {
|
|
|
88
88
|
pickString(healthRoot, ["scene", "scenePath", "scene_path"]) ??
|
|
89
89
|
null);
|
|
90
90
|
}
|
|
91
|
+
function getScenePathFromSceneState(sceneState) {
|
|
92
|
+
const root = asRecord(sceneState);
|
|
93
|
+
return (pickString(asRecord(root?.provenance), ["scenePath", "scene_path"]) ??
|
|
94
|
+
pickString(asRecord(root?.data), ["scenePath", "scene_path"]) ??
|
|
95
|
+
null);
|
|
96
|
+
}
|
|
97
|
+
/** Default prefix set kept in summer_get_project_context when no settingsPrefix
|
|
98
|
+
* is given. The full settings dump measured 188KB (~47k tokens) on a real
|
|
99
|
+
* project; these prefixes cover what agents actually key on (main scene,
|
|
100
|
+
* window size, input actions, physics, renderer). */
|
|
101
|
+
const CONTEXT_SETTINGS_PREFIXES = [
|
|
102
|
+
"application/",
|
|
103
|
+
"display/",
|
|
104
|
+
"input/",
|
|
105
|
+
"physics/",
|
|
106
|
+
"rendering/",
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* Bound the project-state payload. The engine ignores ?prefix= on
|
|
110
|
+
* /api/state/project (snapshot-served with empty args — see
|
|
111
|
+
* ApiClient.getProjectState), so ALL filtering here is client-side:
|
|
112
|
+
* - settingsPrefix given: keep only entries under that prefix.
|
|
113
|
+
* - no prefix: keep only the curated CONTEXT_SETTINGS_PREFIXES.
|
|
114
|
+
* Trimming is always declared in the payload (settingsTruncated /
|
|
115
|
+
* totalSettings / hint); everything outside data.entries is preserved.
|
|
116
|
+
*/
|
|
117
|
+
function trimProjectSettings(projectState, settingsPrefix) {
|
|
118
|
+
const root = asRecord(projectState);
|
|
119
|
+
const data = asRecord(root?.data);
|
|
120
|
+
const entries = data?.entries;
|
|
121
|
+
if (!root || !data || !Array.isArray(entries))
|
|
122
|
+
return projectState;
|
|
123
|
+
const keyOf = (entry) => stringFrom(asRecord(entry)?.key);
|
|
124
|
+
const kept = settingsPrefix
|
|
125
|
+
? entries.filter((entry) => keyOf(entry)?.startsWith(settingsPrefix) ?? false)
|
|
126
|
+
: entries.filter((entry) => {
|
|
127
|
+
const key = keyOf(entry);
|
|
128
|
+
return key !== null && CONTEXT_SETTINGS_PREFIXES.some((p) => key.startsWith(p));
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
...root,
|
|
132
|
+
data: {
|
|
133
|
+
...data,
|
|
134
|
+
entries: kept,
|
|
135
|
+
settingsTruncated: kept.length < entries.length,
|
|
136
|
+
totalSettings: entries.length,
|
|
137
|
+
returnedSettings: kept.length,
|
|
138
|
+
...(settingsPrefix
|
|
139
|
+
? { settingsPrefix }
|
|
140
|
+
: {
|
|
141
|
+
settingsPrefixesIncluded: [...CONTEXT_SETTINGS_PREFIXES],
|
|
142
|
+
settingsHint: "Settings were trimmed to the curated prefixes above to bound payload size. Pass settingsPrefix (e.g. 'audio/' or 'layer_names/') to read another settings group.",
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
91
147
|
export function registerProjectTools(server) {
|
|
92
148
|
server.tool("summer_start_game_task", `Start here for any substantial AI game-building task.
|
|
93
149
|
|
|
@@ -180,10 +236,12 @@ anti-patterns, and recovery steps.`, {}, async () => ({
|
|
|
180
236
|
"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",
|
|
181
237
|
],
|
|
182
238
|
"4_interactive": [
|
|
183
|
-
"To prove input-driven behavior (does jump/move/shoot actually work),
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
239
|
+
"To prove input-driven behavior (does jump/move/shoot actually work), prefer RunVerification — a scripted, repeatable probe. SimulateInput is also reachable as a single op against the RUNNING game (see rawOpsViaBatch); do NOT hand this rung to the user while either route works.",
|
|
240
|
+
"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).",
|
|
241
|
+
"Unlike the editor's own --headless mode, the verify instance renders REAL PIXELS, so save_frame('name') writes a real image and Performance.TIME_FPS reports a real number. save_frame REQUIRES a name argument — save_frame() with no args is a script error (probe fails).",
|
|
242
|
+
"Mount probe scenes DEFERRED: get_tree().root.add_child.call_deferred(instance); await get_tree().process_frame; await settle(). A direct add_child during _ready can hit the parent-busy guard and 'succeed' while capturing a black frame.",
|
|
243
|
+
"press()/key() are COROUTINES — 'await press(\"move_right\", 500)' or the hold never elapses and the input does nothing.",
|
|
244
|
+
"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.",
|
|
187
245
|
],
|
|
188
246
|
},
|
|
189
247
|
// HONESTY — mirror the in-product agent's vision rules. A capture is
|
|
@@ -217,8 +275,11 @@ anti-patterns, and recovery steps.`, {}, async () => ({
|
|
|
217
275
|
// engine ops that have no dedicated tool are still reachable.
|
|
218
276
|
rawOpsViaBatch: [
|
|
219
277
|
"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.",
|
|
220
|
-
"
|
|
221
|
-
"
|
|
278
|
+
"RunVerification (hidden probe instance): summer_batch ops:[{op:'RunVerification', probe_source:'<gdscript>', max_seconds:20}]. probe_source extends SummerProbeBase and uses report(name, value)/save_frame(name)/press(action)/key(keycode)/finish(); returns {ok, results, frames, out_dir} or {ok:false, failure_reason: spawn_failed|timeout|bad_args|no_project|probe_not_node}. save_frame REQUIRES a name argument.",
|
|
279
|
+
"SimulateInput (drive the RUNNING game — summer_play first): summer_batch ops:[{op:'SimulateInput', type:'action', action:'jump', pressed:true}], sent ALONE as the only op. failure_reason 'not_running' = start the game first; 'unsupported' = the running game build predates the handler — use RunVerification instead.",
|
|
280
|
+
"SINGLE-OP CONTRACT: the engine rejects any multi-op batch containing SaveScene, InstantiateScene, ReplaceNode, SimulateInput, ViewportSnapshot, GameSnapshot, Run*/Import* or Git* ops (failure_reason 'unsupported_transport', nothing executes). summer_batch splits these into sequential requests for you; when composing raw batches keep them as their own call anyway.",
|
|
281
|
+
"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.",
|
|
282
|
+
"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.",
|
|
222
283
|
"These are runtime ops, not scene mutations — the batch undo group is a harmless no-op for them.",
|
|
223
284
|
],
|
|
224
285
|
recovery: [
|
|
@@ -228,7 +289,8 @@ anti-patterns, and recovery steps.`, {}, async () => ({
|
|
|
228
289
|
"If save fails: use the returned scenePath/error to repair the exact cause. A running game alone is not a generic scene-mutation blocker.",
|
|
229
290
|
"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.",
|
|
230
291
|
"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.",
|
|
231
|
-
"If a run op
|
|
292
|
+
"If a run op returns 'unsupported' / an unknown-op error: this engine build predates it — fall back to summer_play + summer_get_debugger_errors.",
|
|
293
|
+
"If any op returns 'unsupported_transport': it was batched with other ops. Resend it as the ONLY op in the request (nothing from the rejected batch was applied).",
|
|
232
294
|
],
|
|
233
295
|
debugging: [
|
|
234
296
|
"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.",
|
|
@@ -244,19 +306,33 @@ anti-patterns, and recovery steps.`, {}, async () => ({
|
|
|
244
306
|
- main scene path from project settings
|
|
245
307
|
- lightweight .summer project memory summary when project path is available
|
|
246
308
|
|
|
247
|
-
Use this first in every fresh chat to avoid guessing scene filenames or editing the wrong scene
|
|
248
|
-
|
|
309
|
+
Use this first in every fresh chat to avoid guessing scene filenames or editing the wrong scene.
|
|
310
|
+
|
|
311
|
+
Project settings in project.data.entries are trimmed to a curated prefix set
|
|
312
|
+
(application/, display/, input/, physics/, rendering/) to bound payload size —
|
|
313
|
+
the untrimmed dump can exceed 45k tokens. The payload declares the trim
|
|
314
|
+
(settingsTruncated, totalSettings, settingsPrefixesIncluded). Pass
|
|
315
|
+
settingsPrefix to read a specific settings group (e.g. 'audio/') instead.`, {
|
|
316
|
+
settingsPrefix: z
|
|
317
|
+
.string()
|
|
318
|
+
.optional()
|
|
319
|
+
.describe("Only return project settings whose key starts with this prefix, e.g. 'audio/' or 'application/config/'. Omit for the curated default set."),
|
|
320
|
+
}, async ({ settingsPrefix }) => withEngine(async (client) => {
|
|
321
|
+
const [health, fullProjectState, sceneState] = await Promise.all([
|
|
249
322
|
client.health(),
|
|
250
|
-
client.getProjectState(),
|
|
323
|
+
client.getProjectState(settingsPrefix),
|
|
251
324
|
client.getSceneState().catch((err) => ({
|
|
252
325
|
ok: false,
|
|
253
326
|
error: err instanceof Error ? err.message : String(err),
|
|
254
327
|
})),
|
|
255
328
|
]);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
const
|
|
329
|
+
// Derived fields read the UNtrimmed state so a narrow settingsPrefix
|
|
330
|
+
// cannot hide mainScene/projectPath from the context summary.
|
|
331
|
+
const projectState = trimProjectSettings(fullProjectState, settingsPrefix);
|
|
332
|
+
const projectPath = getProjectPath(fullProjectState, health);
|
|
333
|
+
const projectName = getProjectName(fullProjectState, health);
|
|
334
|
+
const mainScene = getMainScene(fullProjectState);
|
|
335
|
+
const currentScene = getCurrentScene(fullProjectState, sceneState, health);
|
|
260
336
|
// This tool is the deliberate (re)bind point: capture the currently-open
|
|
261
337
|
// project as the one this session's mutations are pinned to. After an
|
|
262
338
|
// engine project switch the agent calls this to intentionally follow the
|
|
@@ -321,9 +397,48 @@ Example: Bind jump to Space and W:
|
|
|
321
397
|
server.tool("summer_get_scene_tree", `Get a scene tree. Pass scenePath to read that exact in-memory/open scene;
|
|
322
398
|
omit it only when you intentionally want the currently visible editor scene.
|
|
323
399
|
Scene mutations load their explicit target, so a follow-up targeted read does
|
|
324
|
-
not require OpenScene
|
|
400
|
+
not require OpenScene.
|
|
401
|
+
|
|
402
|
+
The engine defaults to depth 2 and limit 200 nodes and SILENTLY truncates
|
|
403
|
+
deeper hierarchies (the response then carries truncated: true and a visited
|
|
404
|
+
count lower than the real node count). Pass an explicit depth (e.g. 10) to
|
|
405
|
+
read a full tree — a 102-node scene returns only 61 nodes at the defaults.`, {
|
|
325
406
|
scenePath: z.string().optional().describe("Exact res:// scene path to inspect"),
|
|
326
|
-
|
|
407
|
+
depth: z
|
|
408
|
+
.number()
|
|
409
|
+
.int()
|
|
410
|
+
.positive()
|
|
411
|
+
.optional()
|
|
412
|
+
.describe("Maximum tree depth to walk. Engine default is 2 — pass a larger value for deep hierarchies."),
|
|
413
|
+
limit: z
|
|
414
|
+
.number()
|
|
415
|
+
.int()
|
|
416
|
+
.positive()
|
|
417
|
+
.optional()
|
|
418
|
+
.describe("Maximum number of nodes to return. Engine default is 200."),
|
|
419
|
+
}, async ({ scenePath, depth, limit }) => withEngine(async (client) => {
|
|
420
|
+
if (depth === undefined && limit === undefined) {
|
|
421
|
+
return client.getSceneState(scenePath);
|
|
422
|
+
}
|
|
423
|
+
// The engine honors depth/limit ONLY on targeted (scene=) reads; an
|
|
424
|
+
// untargeted read is served from a pre-published snapshot built with
|
|
425
|
+
// the defaults, and its query params are dropped. Resolve the current
|
|
426
|
+
// scene path first so depth/limit actually take effect.
|
|
427
|
+
let target = scenePath;
|
|
428
|
+
if (!target) {
|
|
429
|
+
const snapshot = await client.getSceneState();
|
|
430
|
+
target = getScenePathFromSceneState(snapshot) ?? undefined;
|
|
431
|
+
if (!target) {
|
|
432
|
+
const record = asRecord(snapshot);
|
|
433
|
+
return {
|
|
434
|
+
...(record ?? { snapshot }),
|
|
435
|
+
depthLimitApplied: false,
|
|
436
|
+
note: "depth/limit were IGNORED: the current scene path could not be resolved, and the engine only honors depth/limit on scene-targeted reads. Pass scenePath explicitly to apply them.",
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return client.getSceneState(target, { depth, limit });
|
|
441
|
+
}));
|
|
327
442
|
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.
|
|
328
443
|
|
|
329
444
|
Use this for:
|