summer-engine 4.3.0 → 4.5.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 (218) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -1
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/AGENTS.md +2 -2
  7. package/CHANGELOG.md +87 -0
  8. package/README.md +67 -27
  9. package/commands/start.md +19 -15
  10. package/commands/summer.md +1 -0
  11. package/dist/cli/command-usage.d.ts +3 -1
  12. package/dist/cli/command-usage.js +10 -4
  13. package/dist/cli/commands/create.d.ts +1 -25
  14. package/dist/cli/commands/create.js +10 -138
  15. package/dist/cli/commands/dev.d.ts +66 -0
  16. package/dist/cli/commands/dev.js +234 -0
  17. package/dist/cli/commands/install.d.ts +15 -58
  18. package/dist/cli/commands/install.js +31 -481
  19. package/dist/cli/commands/login.js +1 -1
  20. package/dist/cli/commands/ops.d.ts +17 -1
  21. package/dist/cli/commands/ops.js +97 -9
  22. package/dist/cli/commands/preview.d.ts +58 -0
  23. package/dist/cli/commands/preview.js +181 -0
  24. package/dist/cli/commands/run.d.ts +1 -17
  25. package/dist/cli/commands/run.js +17 -206
  26. package/dist/cli/index.js +5 -0
  27. package/dist/core/agent-host.d.ts +3 -0
  28. package/dist/core/agent-host.js +1 -1
  29. package/dist/core/api-client.d.ts +10 -2
  30. package/dist/core/api-client.js +13 -8
  31. package/dist/core/capabilities/agent-playbook.js +1 -0
  32. package/dist/core/capabilities/apps.d.ts +9 -9
  33. package/dist/core/capabilities/asset-import.d.ts +4 -1
  34. package/dist/core/capabilities/asset-import.js +15 -6
  35. package/dist/core/capabilities/capture-gameplay.js +1 -1
  36. package/dist/core/capabilities/connect-signal.d.ts +2 -2
  37. package/dist/core/capabilities/content-make/index.d.ts +81 -0
  38. package/dist/core/capabilities/content-make/index.js +706 -0
  39. package/dist/core/capabilities/content-make/media.d.ts +57 -0
  40. package/dist/core/capabilities/content-make/media.js +172 -0
  41. package/dist/core/capabilities/content-make/plan.d.ts +130 -0
  42. package/dist/core/capabilities/content-make/plan.js +433 -0
  43. package/dist/core/capabilities/content-make/render.d.ts +64 -0
  44. package/dist/core/capabilities/content-make/render.js +370 -0
  45. package/dist/core/capabilities/content-make/study.d.ts +13 -0
  46. package/dist/core/capabilities/content-make/study.js +165 -0
  47. package/dist/core/capabilities/content-make/types.d.ts +173 -0
  48. package/dist/core/capabilities/content-make/types.js +6 -0
  49. package/dist/core/capabilities/content-make/voice.d.ts +7 -0
  50. package/dist/core/capabilities/content-make/voice.js +27 -0
  51. package/dist/core/capabilities/events.d.ts +2 -2
  52. package/dist/core/capabilities/export-game.d.ts +4 -1
  53. package/dist/core/capabilities/export-game.js +4 -3
  54. package/dist/core/capabilities/export-presets.d.ts +2 -1
  55. package/dist/core/capabilities/export-presets.js +2 -1
  56. package/dist/core/capabilities/go-live.d.ts +14 -0
  57. package/dist/core/capabilities/go-live.js +408 -0
  58. package/dist/core/capabilities/image-generation.d.ts +6 -6
  59. package/dist/core/capabilities/item-manifest.d.ts +92 -0
  60. package/dist/core/capabilities/item-manifest.js +247 -0
  61. package/dist/core/capabilities/placement.d.ts +7 -7
  62. package/dist/core/capabilities/projects-local.d.ts +68 -0
  63. package/dist/core/capabilities/projects-local.js +172 -0
  64. package/dist/core/capabilities/publish-build.d.ts +13 -2
  65. package/dist/core/capabilities/publish-build.js +87 -21
  66. package/dist/core/capabilities/publish-download.js +1 -1
  67. package/dist/core/capabilities/runtime-control.d.ts +7 -2
  68. package/dist/core/capabilities/runtime-control.js +27 -1
  69. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  70. package/dist/core/capabilities/store-checks.d.ts +45 -0
  71. package/dist/core/capabilities/store-checks.js +85 -0
  72. package/dist/core/capabilities/store-details.d.ts +37 -0
  73. package/dist/core/capabilities/store-details.js +94 -0
  74. package/dist/core/capabilities/summer-bundle.d.ts +21 -2
  75. package/dist/core/capabilities/summer-bundle.js +54 -7
  76. package/dist/core/capabilities/sync-items.d.ts +89 -0
  77. package/dist/core/capabilities/sync-items.js +352 -0
  78. package/dist/core/capabilities/tool-dispatch.js +66 -6
  79. package/dist/core/capabilities/video-ogv.d.ts +49 -0
  80. package/dist/core/capabilities/video-ogv.js +260 -0
  81. package/dist/core/capability-skew.js +2 -2
  82. package/dist/core/cli-usage-beacon.js +26 -2
  83. package/dist/core/cli-usage.d.ts +5 -2
  84. package/dist/core/cli-usage.js +8 -2
  85. package/dist/core/engine-install.d.ts +53 -0
  86. package/dist/core/engine-install.js +93 -0
  87. package/dist/core/engine-installer.d.ts +134 -0
  88. package/dist/core/engine-installer.js +760 -0
  89. package/dist/core/engine-launch.d.ts +126 -0
  90. package/dist/core/engine-launch.js +398 -0
  91. package/dist/core/engine-self-start.d.ts +63 -0
  92. package/dist/core/engine-self-start.js +282 -0
  93. package/dist/core/engine.d.ts +7 -1
  94. package/dist/core/engine.js +9 -2
  95. package/dist/core/error-codes.d.ts +4 -1
  96. package/dist/core/error-codes.js +7 -3
  97. package/dist/core/library-read.d.ts +2 -2
  98. package/dist/core/library-search.d.ts +2 -2
  99. package/dist/core/local-project.d.ts +50 -0
  100. package/dist/core/local-project.js +234 -0
  101. package/dist/core/ops/areas.js +9 -3
  102. package/dist/core/ops/catalog.d.ts +9 -0
  103. package/dist/core/ops/catalog.js +56 -4
  104. package/dist/core/ops/engine-answer.d.ts +4 -1
  105. package/dist/core/ops/engine-answer.js +16 -3
  106. package/dist/core/ops/engine.js +4 -4
  107. package/dist/core/ops/save-files.d.ts +6 -1
  108. package/dist/core/ops/save-files.js +67 -50
  109. package/dist/core/ops/search.js +9 -2
  110. package/dist/core/preview/input.d.ts +35 -0
  111. package/dist/core/preview/input.js +105 -0
  112. package/dist/core/preview/launch-config.d.ts +52 -0
  113. package/dist/core/preview/launch-config.js +137 -0
  114. package/dist/core/preview/page.d.ts +22 -0
  115. package/dist/core/preview/page.js +232 -0
  116. package/dist/core/preview/preview-source.d.ts +179 -0
  117. package/dist/core/preview/preview-source.js +350 -0
  118. package/dist/core/preview/server.d.ts +26 -0
  119. package/dist/core/preview/server.js +218 -0
  120. package/dist/core/telemetry.d.ts +20 -16
  121. package/dist/core/telemetry.js +92 -16
  122. package/dist/core/templates.d.ts +20 -0
  123. package/dist/core/templates.js +300 -3
  124. package/dist/core/tool-profiles.d.ts +2 -2
  125. package/dist/core/tool-profiles.js +1 -0
  126. package/dist/core/usage-fields.d.ts +41 -0
  127. package/dist/core/usage-fields.js +150 -0
  128. package/dist/core/util/sleep.d.ts +3 -0
  129. package/dist/core/util/sleep.js +12 -0
  130. package/dist/mcp/ops-surface.js +28 -2
  131. package/dist/mcp/server.d.ts +14 -4
  132. package/dist/mcp/server.js +42 -8
  133. package/dist/mcp/tool-surface.d.ts +8 -0
  134. package/dist/mcp/tool-surface.js +19 -2
  135. package/dist/mcp/tools/creator-tools.js +92 -14
  136. package/dist/mcp/tools/debug-tools.js +2 -0
  137. package/dist/mcp/tools/local-project-tools.d.ts +6 -0
  138. package/dist/mcp/tools/local-project-tools.js +33 -0
  139. package/dist/mcp/tools/navigation-tools.js +2 -1
  140. package/dist/mcp/tools/project-tools.js +5 -8
  141. package/dist/mcp/tools/seeing-tools.js +1 -1
  142. package/dist/mcp/tools/visual-tools.js +1 -1
  143. package/dist/mcp/tools/with-engine.js +1 -1
  144. package/gemini-extension.json +3 -1
  145. package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
  146. package/library/skills/README.md +12 -8
  147. package/library/skills/animated-loop/SKILL.md +10 -16
  148. package/library/skills/cinematic-cutscene/SKILL.md +7 -18
  149. package/library/skills/headless-scripting/SKILL.md +7 -4
  150. package/library/skills/in-game-purchases/SKILL.md +25 -8
  151. package/library/skills/in-game-purchases/resource.yaml +2 -1
  152. package/library/skills/launch-ready/SKILL.md +351 -0
  153. package/library/skills/launch-ready/resource.yaml +45 -0
  154. package/library/skills/make-content/SKILL.md +202 -0
  155. package/library/skills/make-content/resource.yaml +32 -0
  156. package/library/skills/make-game/SKILL.md +2 -0
  157. package/library/skills/make-game/resource.yaml +2 -1
  158. package/library/skills/navigate-summer/SKILL.md +4 -2
  159. package/library/skills/new-project/SKILL.md +15 -22
  160. package/library/skills/new-project/resource.yaml +2 -1
  161. package/library/skills/painted-25d-world/SKILL.md +366 -0
  162. package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
  163. package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
  164. package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
  165. package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
  166. package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
  167. package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
  168. package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
  169. package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
  170. package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
  171. package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
  172. package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
  173. package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
  174. package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
  175. package/library/skills/painted-25d-world/resource.yaml +39 -0
  176. package/library/skills/publish-your-game/SKILL.md +13 -14
  177. package/library/skills/publish-your-game/resource.yaml +4 -3
  178. package/library/skills/release-checklist/SKILL.md +188 -0
  179. package/library/skills/release-checklist/resource.yaml +40 -0
  180. package/library/skills/release-readiness/SKILL.md +1 -1
  181. package/library/skills/release-readiness/resource.yaml +1 -1
  182. package/library/skills/store-art/SKILL.md +1 -1
  183. package/library/skills/store-art/resource.yaml +1 -1
  184. package/library/skills/store-listing/SKILL.md +1 -1
  185. package/library/skills/store-listing/resource.yaml +1 -1
  186. package/library/skills/summer-cli/SKILL.md +2 -0
  187. package/library/skills/summer-store/SKILL.md +295 -39
  188. package/library/skills/summer-store/resource.yaml +9 -3
  189. package/library/skills/trailer-shot/SKILL.md +3 -3
  190. package/library/templates/README.md +5 -0
  191. package/library/tools/content-make/resource.yaml +132 -0
  192. package/library/tools/frame-shot/GUIDE.md +1 -1
  193. package/library/tools/go-live/resource.yaml +67 -0
  194. package/library/tools/import-from-url/GUIDE.md +1 -0
  195. package/library/tools/import-from-url-batch/GUIDE.md +1 -1
  196. package/library/tools/play/GUIDE.md +2 -0
  197. package/library/tools/projects-create-local/resource.yaml +55 -0
  198. package/library/tools/projects-open-local/resource.yaml +51 -0
  199. package/library/tools/publish-build/resource.yaml +3 -3
  200. package/library/tools/screenshot/GUIDE.md +1 -1
  201. package/library/tools/store-set-details/resource.yaml +114 -0
  202. package/library/tools/sync-items/resource.yaml +72 -0
  203. package/package.json +1 -1
  204. package/registry/generated/aliases.json +6 -0
  205. package/registry/generated/counts.json +3 -3
  206. package/registry/generated/gemini-extension.json +3 -1
  207. package/registry/generated/index.json +463 -37
  208. package/registry/generated/marketplace.claude.json +1 -1
  209. package/registry/generated/plugin.claude-directory.json +4 -2
  210. package/registry/generated/plugin.claude.json +3 -1
  211. package/registry/generated/plugin.codex.json +5 -3
  212. package/registry/generated/plugin.cursor.json +4 -2
  213. package/registry/generated/plugin.factory.json +4 -2
  214. package/registry/generated/server.json +2 -2
  215. package/registry/generated/skills-index.md +12 -8
  216. package/registry/generated/skills-registry.json +62 -5
  217. package/registry/operations/legacy-names.json +40 -0
  218. package/registry/operations/public-catalog.json +10144 -387
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The extra fields a usage line (telemetry.ts, cli-usage.ts) can carry. Each
3
+ * is a closed shape checked here, so nothing a person typed, no path and no
4
+ * project or file name can reach one:
5
+ *
6
+ * - `projectIdHash`: the engine's own project id hash (sha256 of the random
7
+ * id Summer Engine keeps in `.summer/local/.project_id`), the same value
8
+ * Summer already uses to route requests to a project. Not the name or path.
9
+ * - `templateId`, `templateTags`: the library template a project started
10
+ * from (`.summer/project.json`) and that template's library domains
11
+ * (`facets.domains`, e.g. `2d`, `platformer`). Only ids from the shipped
12
+ * catalog; anything else is left out.
13
+ * - `instanceCount`: how many game clients `engine.play` started.
14
+ * - `engineState`: four yes/no answers when a call failed with
15
+ * `engine_not_running` (is the app installed, is an editor running, is one
16
+ * holding a project, is the person signed in), from local files only.
17
+ */
18
+ import { existsSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { currentToolContext } from "./agent-host.js";
21
+ import { hasEnvAuthToken } from "./auth.js";
22
+ import { listEngineInstances } from "./engine.js";
23
+ import { findEngineBinary } from "./engine-install.js";
24
+ import { loadLibraryIndex } from "./library-search.js";
25
+ import { getSummerDir } from "./store.js";
26
+ import { getTemplateRegistry } from "./templates.js";
27
+ import { readProjectManifest } from "../project-memory/project-manifest.js";
28
+ const HASH = /^[0-9a-f]{16,128}$/;
29
+ const SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
30
+ const MAX_TAGS = 8;
31
+ /** Only the closed shapes above survive; everything else is dropped. */
32
+ export function cleanUsageFields(fields) {
33
+ const out = {};
34
+ if (typeof fields.projectIdHash === "string" && HASH.test(fields.projectIdHash))
35
+ out.projectIdHash = fields.projectIdHash;
36
+ if (typeof fields.templateId === "string" && SLUG.test(fields.templateId))
37
+ out.templateId = fields.templateId;
38
+ if (Array.isArray(fields.templateTags)) {
39
+ const tags = fields.templateTags.filter((tag) => typeof tag === "string" && SLUG.test(tag)).slice(0, MAX_TAGS);
40
+ if (tags.length)
41
+ out.templateTags = tags;
42
+ }
43
+ if (typeof fields.instanceCount === "number" && Number.isInteger(fields.instanceCount) && fields.instanceCount >= 0 && fields.instanceCount <= 64) {
44
+ out.instanceCount = fields.instanceCount;
45
+ }
46
+ return out;
47
+ }
48
+ // One command per CLI process: its line reads these at exit (cli-usage.ts).
49
+ let processNotes = {};
50
+ let boundProjectIdHash;
51
+ /** A tool or command noted fields for its usage line; never throws. */
52
+ export function noteToolUsage(fields) {
53
+ try {
54
+ const clean = cleanUsageFields(fields);
55
+ const context = currentToolContext();
56
+ if (context)
57
+ Object.assign(context.usage, clean);
58
+ Object.assign(processNotes, clean);
59
+ }
60
+ catch {
61
+ // usage is best-effort
62
+ }
63
+ }
64
+ /** The fields noted in this process (the CLI line), with the bound project. */
65
+ export function processUsageFields() {
66
+ return { ...(boundProjectIdHash ? { projectIdHash: boundProjectIdHash } : {}), ...processNotes };
67
+ }
68
+ /** The engine connection bound this project (api-client.ts connect and rebind). */
69
+ export function noteBoundProject(projectIdHash) {
70
+ boundProjectIdHash = typeof projectIdHash === "string" && HASH.test(projectIdHash) ? projectIdHash : undefined;
71
+ }
72
+ export function boundProject() {
73
+ return boundProjectIdHash;
74
+ }
75
+ /** The library slug for a catalog template id or slug, else null. */
76
+ export function catalogTemplateSlug(idOrSlug) {
77
+ try {
78
+ const entry = getTemplateRegistry().find((template) => template.id === idOrSlug || template.slug === idOrSlug);
79
+ return entry ? entry.slug : null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /** A catalog template's library domains (`facets.domains`), e.g. ["2d", "platformer"]. */
86
+ export function templateTags(slug) {
87
+ try {
88
+ const entry = loadLibraryIndex().find((resource) => resource.id === `template/${slug}`);
89
+ return (entry?.facets?.domains ?? []).filter((tag) => SLUG.test(tag)).slice(0, MAX_TAGS);
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
95
+ const templateByHash = new Map();
96
+ /**
97
+ * The catalog template the project with this hash started from: the running
98
+ * editor's registry entry gives its folder, `.summer/project.json` the
99
+ * template. Cached per hash; null when unknown or not a catalog template.
100
+ */
101
+ export async function templateForProject(projectIdHash) {
102
+ if (templateByHash.has(projectIdHash))
103
+ return templateByHash.get(projectIdHash) ?? null;
104
+ let slug = null;
105
+ try {
106
+ const instance = (await listEngineInstances()).find((entry) => entry.projectIdHash === projectIdHash);
107
+ const id = instance ? readProjectManifest(instance.resourceRoot)?.template?.id : undefined;
108
+ slug = typeof id === "string" ? catalogTemplateSlug(id) : null;
109
+ }
110
+ catch {
111
+ slug = null;
112
+ }
113
+ templateByHash.set(projectIdHash, slug);
114
+ return slug;
115
+ }
116
+ /** What is true on this machine when the engine could not be reached. Local files only. */
117
+ export async function engineState() {
118
+ const instances = await listEngineInstances(Date.now(), getSummerDir(), { anyHeartbeat: true }).catch(() => []);
119
+ return {
120
+ desktopInstalled: findEngineBinary() !== null,
121
+ engineRunning: instances.length > 0,
122
+ projectOpen: instances.some((instance) => !!instance.projectIdHash),
123
+ signedIn: hasEnvAuthToken() || existsSync(join(getSummerDir(), "auth-token")),
124
+ };
125
+ }
126
+ /**
127
+ * The extra fields of one usage line: what the call noted, else the bound
128
+ * project and its template, and the engine state for engine_not_running.
129
+ * Never throws.
130
+ */
131
+ export async function usageLineFields(noted, error) {
132
+ try {
133
+ const fields = cleanUsageFields(noted);
134
+ if (fields.projectIdHash && !fields.templateId) {
135
+ const template = await templateForProject(fields.projectIdHash);
136
+ if (template)
137
+ fields.templateId = template;
138
+ }
139
+ return { ...fields, ...(error === "engine_not_running" ? { engineState: await engineState() } : {}) };
140
+ }
141
+ catch {
142
+ return {};
143
+ }
144
+ }
145
+ /** Test-only: forget notes, the bound project and the template cache. */
146
+ export function _resetUsageFieldsForTests() {
147
+ processNotes = {};
148
+ boundProjectIdHash = undefined;
149
+ templateByHash.clear();
150
+ }
@@ -1 +1,4 @@
1
1
  export declare function sleep(ms: number): Promise<void>;
2
+ /** Whether `promise` settles within `ms`. The timer is cleared when it does,
3
+ * so a quick answer never keeps a CLI process alive for the full wait. */
4
+ export declare function settlesWithin(promise: Promise<unknown>, ms: number): Promise<boolean>;
@@ -1,3 +1,15 @@
1
1
  export function sleep(ms) {
2
2
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
3
3
  }
4
+ /** Whether `promise` settles within `ms`. The timer is cleared when it does,
5
+ * so a quick answer never keeps a CLI process alive for the full wait. */
6
+ export function settlesWithin(promise, ms) {
7
+ return new Promise((resolveWithin) => {
8
+ const timer = setTimeout(() => resolveWithin(false), ms);
9
+ const done = () => {
10
+ clearTimeout(timer);
11
+ resolveWithin(true);
12
+ };
13
+ promise.then(done, done);
14
+ });
15
+ }
@@ -4,7 +4,7 @@ import { localEntries, localInputSchema, localWireForm, missingInput } from "../
4
4
  import { answerOfHosted, describeFromShipped, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
5
5
  import { errorAnswer, isLocalOp, legacyNames, localToolsFor, mcpNameOf, resolveOpName } from "../core/ops/contract.js";
6
6
  import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, renderOpRefs, splitToolArgs } from "../core/ops/engine.js";
7
- import { answerOfEngineResult, engineFailure, textsOf } from "../core/ops/engine-answer.js";
7
+ import { answerOfEngineResult, codedLocalFailure, engineFailure, textsOf } from "../core/ops/engine-answer.js";
8
8
  import { TAKES_OUT, withOutField } from "../core/ops/save-files.js";
9
9
  import { loadLibraryIndex } from "../core/library-search.js";
10
10
  import { FOOTER_SUFFIX } from "../core/library-read.js";
@@ -32,7 +32,8 @@ export const SUMMER_CALL = "summer_call";
32
32
  export const OPS_MCP_INSTRUCTIONS = "Summer Engine MCP, operation catalog. Call summer_get_project_context first when Summer Engine is open. " +
33
33
  "Listed tools are the common jobs; for anything else, summer_search describes the job in words and returns the operation and its exact input, then summer_call runs it. " +
34
34
  "More than 3 scene edits: one summer_run_script with scenePath. Each engine tool's full guide: summer_docs_read. Long work answers with a job: jobs.wait. " +
35
- "Publishing, prices and other live changes answer needs_approval with a link: give it to the person; nothing changes until they approve.";
35
+ "Going live: skill/release-checklist, then store.go_live with the person's yes; a game is live once Summer's automated checks pass, so never tell anyone to wait for a review. Prices and other live changes may answer needs_approval with a link: give it to the person. " +
36
+ "Opening the Summer editor and playing the game in a window is allowed and expected: summer_play, then summer_screenshot or summer_frame_shot to see it; close what you opened, one editor at a time.";
36
37
  /** The assets profile's version: no engine tools, so it names none. */
37
38
  export const OPS_MCP_ASSETS_INSTRUCTIONS = "Summer MCP, assets profile, operation catalog: generation and the Summer asset library, no engine tools. " +
38
39
  "Read skill/summer-assets with summer_docs_read, then the asset's skill: stop at the file, skip its engine steps. " +
@@ -43,6 +44,8 @@ const DOCS_ALIASES = [
43
44
  ["summer_docs_search", "summer_search_library"],
44
45
  ["summer_docs_read", "summer_read_library"],
45
46
  ];
47
+ /** Local operations listed with the engine tools: they create and open the project the engine tools need, with no shell. */
48
+ const LOCAL_PROJECT_TOOLS = ["summer_projects_create_local", "summer_projects_open_local"];
46
49
  /** Listed with the engine tools although its operation is docs.engine_api: the offline engine class reference. */
47
50
  const ENGINE_REFERENCE = "summer_api_docs";
48
51
  /** Its v4 description, within the 300-character summary rule; the full text stays on the 3.x surface. */
@@ -89,6 +92,9 @@ function answerOfLocal(result, op, effect) {
89
92
  }
90
93
  if (record.isError) {
91
94
  const body = (payload && typeof payload === "object" ? payload : {});
95
+ const coded = codedLocalFailure(body);
96
+ if (coded)
97
+ return coded;
92
98
  const message = typeof body.message === "string" ? body.message : typeof body.error === "string" ? body.error : first.slice(0, 500) || "The tool failed.";
93
99
  const details = { tool: payload };
94
100
  if (/not found|does not exist|no such/i.test(message))
@@ -236,6 +242,26 @@ export function applyOpsSurface(server, options) {
236
242
  }),
237
243
  });
238
244
  }
245
+ // projects.create_local and projects.open_local: the one answer shape, like the engine tools.
246
+ const localProjectTools = options.engine ? LOCAL_PROJECT_TOOLS.filter((name) => tools[name] && entries.has(names[name]?.op ?? "")) : [];
247
+ for (const name of localProjectTools)
248
+ listed.add(name);
249
+ for (const name of localProjectTools) {
250
+ const tool = tools[name];
251
+ const op = names[name].op;
252
+ const entry = entries.get(op);
253
+ const handler = tool.handler;
254
+ rawHandlers.set(name, handler);
255
+ tool.update({
256
+ title: entry.title,
257
+ description: renderOpRefs(entry.summary, toolOf),
258
+ annotations: annotationsForEffect(entry.effect === "read" ? "read" : "write", Boolean(facts.get(name)?.network), entry.title),
259
+ callback: (async (...args) => {
260
+ const raw = await handler(...args);
261
+ return toolResult(answerOfLocal(raw, op, "write"), raw);
262
+ }),
263
+ });
264
+ }
239
265
  // Operations split out of a mixed 3.x tool: their own tool, the 3.x handler with the action fixed.
240
266
  for (const [op, def] of Object.entries(ENGINE_SPLIT_OPS)) {
241
267
  const base = tools[def.tool];
@@ -13,16 +13,26 @@ export declare const getCachedBootDriftNotice: typeof getCachedNotice;
13
13
  * session, before any tool call — the place for the habits end-to-end runs
14
14
  * showed an agent needs on the first turn and would otherwise learn the hard
15
15
  * way, including scripting a run of scene edits instead of hundreds of single
16
- * calls. Kept under 600 characters so it costs a few hundred tokens once; the
16
+ * calls. Kept under 800 characters so it costs a few hundred tokens once; the
17
17
  * full operating guide stays in the summer_agent_playbook prompt /
18
- * summer_get_agent_playbook tool. The last sentence names the publishing path,
19
- * the owner-approval rule and where review feedback is read.
18
+ * summer_get_agent_playbook tool. The publish sentence names the publishing
19
+ * path, the user's confirmation and that going live has no staff wait; the last
20
+ * says an agent may open the editor and play the game in a window.
20
21
  */
21
22
  export declare const SUMMER_MCP_INSTRUCTIONS: string;
22
23
  /** The same first-turn habits for a profile without engine tools (`--profile assets`). */
23
24
  export declare const SUMMER_MCP_ASSETS_INSTRUCTIONS: string;
24
25
  export declare function configureMcpEngineSelection(selection?: EngineSelection): void;
25
- export declare function getClient(): Promise<EngineApiClient>;
26
+ /** The session's editor selection (projects.open_local opens its project). */
27
+ export declare function currentMcpEngineSelection(): EngineSelection | undefined;
28
+ /** Bind the session to the project projects.open_local opened, with the client it connected. */
29
+ export declare function bindMcpProject(projectRoot: string, client: EngineApiClient): void;
30
+ /** The step's log lines go to the MCP log and stderr, never stdout (the protocol). */
31
+ export declare function logEngineSelfStart(line: string): void;
32
+ /** `launch: false`: connect only, never open the editor (summer_open lists and prints with no side effects). */
33
+ export declare function getClient(options?: {
34
+ launch?: boolean;
35
+ }): Promise<EngineApiClient>;
26
36
  export declare function resetClient(): void;
27
37
  /**
28
38
  * Build the MCP server with every Summer tool of the selected profile
@@ -9,6 +9,7 @@ import { registerAuditTools } from "./tools/audit-tools.js";
9
9
  import { WITH_ENGINE_META } from "./tools/with-engine.js";
10
10
  import { registerPlaybookPrompt, registerProjectTools, } from "./tools/project-tools.js";
11
11
  import { registerPerceptionTools } from "./tools/perception-tools.js";
12
+ import { registerLocalProjectTools } from "./tools/local-project-tools.js";
12
13
  import { registerSpatialTools } from "./tools/spatial-tools.js";
13
14
  import { registerPlacementTools } from "./tools/placement-tools.js";
14
15
  import { registerScriptTools } from "./tools/script-tools.js";
@@ -39,10 +40,12 @@ import { refreshInstalledSkills, refreshNotice } from "../installer/skills-refre
39
40
  import { OUTPUT_CAPS, PROFILE_TOOL_LIMIT, PROFILES, TOOLSETS, annotationsFor, registryAuthorities, resolveToolSelection, selectionIncludes, withMaxCharactersArg, } from "./tool-surface.js";
40
41
  import { shapeToolResult, singleEditCounter } from "./tool-results.js";
41
42
  import { recordLocalToolCall, recordMcpSession, toolErrorCode } from "../core/telemetry.js";
43
+ import { boundProject } from "../core/usage-fields.js";
42
44
  import { currentToolContext, registerMcpClientSource, runWithToolContext } from "../core/agent-host.js";
43
45
  import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../installer/version-check.js";
44
46
  import { TOOLKIT_VERSION as version } from "../core/version.js";
45
47
  import { ENGINE_FREE_NOTE, engineNotRunningFromConnectError } from "../core/error-codes.js";
48
+ import { selfStartEngine } from "../core/engine-self-start.js";
46
49
  import { resolveSurface } from "../core/tool-profiles.js";
47
50
  import { OAuthError } from "../core/oauth.js";
48
51
  let processDiagnosticsInstalled = false;
@@ -54,10 +57,11 @@ export const getCachedBootDriftNotice = getCachedNotice;
54
57
  * session, before any tool call — the place for the habits end-to-end runs
55
58
  * showed an agent needs on the first turn and would otherwise learn the hard
56
59
  * way, including scripting a run of scene edits instead of hundreds of single
57
- * calls. Kept under 600 characters so it costs a few hundred tokens once; the
60
+ * calls. Kept under 800 characters so it costs a few hundred tokens once; the
58
61
  * full operating guide stays in the summer_agent_playbook prompt /
59
- * summer_get_agent_playbook tool. The last sentence names the publishing path,
60
- * the owner-approval rule and where review feedback is read.
62
+ * summer_get_agent_playbook tool. The publish sentence names the publishing
63
+ * path, the user's confirmation and that going live has no staff wait; the last
64
+ * says an agent may open the editor and play the game in a window.
61
65
  */
62
66
  export const SUMMER_MCP_INSTRUCTIONS = "Summer Engine MCP. Call summer_get_project_context first: it binds the open project. " +
63
67
  "Before building or fixing: summer_search_library, then summer_read_library the hit. " +
@@ -65,7 +69,8 @@ export const SUMMER_MCP_INSTRUCTIONS = "Summer Engine MCP. Call summer_get_proje
65
69
  "After play: summer_get_diagnostics, not only summer_get_console. " +
66
70
  "All-black screenshot: no redraw yet, recapture. " +
67
71
  "Report verified entries via summer_library_feedback. " +
68
- "Publish: summer_export_game, summer_publish_build, summer_store_*; never alone: summer_store_submit returns the owner's approval link, summer_store_review the feedback.";
72
+ "Publish: summer_export_game, summer_publish_build, skill/release-checklist, summer_go_live, each confirmed. Live once automated checks pass: never say wait for review. " +
73
+ "Opening the editor and playing the game in a window is allowed and expected: summer_play, then summer_screenshot or summer_frame_shot; close what you opened, one editor at a time.";
69
74
  /** The same first-turn habits for a profile without engine tools (`--profile assets`). */
70
75
  export const SUMMER_MCP_ASSETS_INSTRUCTIONS = "Summer MCP, assets profile: generation and the Summer asset library, no engine tools. " +
71
76
  "Read skill/summer-assets, then the asset's skill (sound-effect, prop-model, ...): stop at the file, skip its engine steps. " +
@@ -90,7 +95,22 @@ export function configureMcpEngineSelection(selection) {
90
95
  engineSelection = selection ? { ...selection } : undefined;
91
96
  cachedClient = null;
92
97
  }
93
- export async function getClient() {
98
+ /** The session's editor selection (projects.open_local opens its project). */
99
+ export function currentMcpEngineSelection() {
100
+ return engineSelection ? { ...engineSelection } : undefined;
101
+ }
102
+ /** Bind the session to the project projects.open_local opened, with the client it connected. */
103
+ export function bindMcpProject(projectRoot, client) {
104
+ engineSelection = { cwd: projectRoot };
105
+ cachedClient = client;
106
+ }
107
+ /** The step's log lines go to the MCP log and stderr, never stdout (the protocol). */
108
+ export function logEngineSelfStart(line) {
109
+ appendMcpLogEvent("mcp:engine_self_start", { line });
110
+ writeMcpStderr("mcp:engine_self_start", { line });
111
+ }
112
+ /** `launch: false`: connect only, never open the editor (summer_open lists and prints with no side effects). */
113
+ export async function getClient(options = {}) {
94
114
  // Opt-in per-project routing (editor -> live worker -> spawned worker).
95
115
  // OFF by default: with the flag unset this block is skipped entirely and
96
116
  // src/core/headless/ is never even loaded (dynamic import). With the flag
@@ -129,8 +149,14 @@ export async function getClient() {
129
149
  // (engine_not_running). Anything else (several editors open, …) already
130
150
  // says how to pick one.
131
151
  const notRunning = engineNotRunningFromConnectError(reason);
132
- if (notRunning)
152
+ if (notRunning && options.launch === false)
133
153
  throw notRunning;
154
+ if (notRunning) {
155
+ // Open the session's project ourselves when we can, else a coded
156
+ // error with the one command that gets there (core/engine-self-start.ts).
157
+ cachedClient = await selfStartEngine(engineSelection, notRunning, { log: logEngineSelfStart });
158
+ return cachedClient;
159
+ }
134
160
  throw new Error(reason + "\n" +
135
161
  "Open the intended project in Summer Engine, or run: npx -y summer-engine@latest run <project-path>\n" +
136
162
  "Note: " + ENGINE_FREE_NOTE);
@@ -235,14 +261,21 @@ function installToolCallWrapper(server, surfaceMeta) {
235
261
  // A tool another tool runs (summer_call, a v4 operation over its 3.x
236
262
  // tool) is part of that call: only the outer call sends a usage line.
237
263
  const nested = currentToolContext() !== undefined;
264
+ // What the call notes for its line (core/usage-fields.ts), shared with any tool it runs.
265
+ let noted = {};
238
266
  const recordUsage = (ok, durationMs, error) => {
267
+ const bound = boundProject();
268
+ const usage = { ...(bound ? { projectIdHash: bound } : {}), ...noted };
239
269
  if (!nested)
240
- recordLocalToolCall({ tool: name, op: knownOp(opForToolCall(name, handlerArgs[0])), ...surfaceMeta, ok, durationMs, mcpClient: hostApp(), error });
270
+ recordLocalToolCall({ tool: name, op: knownOp(opForToolCall(name, handlerArgs[0])), ...surfaceMeta, ok, durationMs, mcpClient: hostApp(), error, usage });
241
271
  };
242
272
  let result;
243
273
  try {
244
274
  // Every Summer request the tool makes names it (core/agent-host.ts).
245
- result = await runWithToolContext(name, "mcp", () => handler(...handlerArgs));
275
+ result = await runWithToolContext(name, "mcp", () => {
276
+ noted = currentToolContext()?.usage ?? {};
277
+ return handler(...handlerArgs);
278
+ });
246
279
  }
247
280
  catch (error) {
248
281
  // A handler THROW never produced a result to classify; record it as
@@ -379,6 +412,7 @@ export function createMcpServer(selection = FULL_SELECTION, options = {}) {
379
412
  registerSeeingTools(server);
380
413
  registerAuditTools(server);
381
414
  registerProjectTools(server, { engine });
415
+ registerLocalProjectTools(server);
382
416
  registerFileTools(server);
383
417
  registerAssetTools(server);
384
418
  registerGenerateTools(server);
@@ -32,6 +32,14 @@ export declare function resolveToolSelection(options?: {
32
32
  profile?: string;
33
33
  toolsets?: string;
34
34
  }, env?: NodeJS.ProcessEnv): ToolSelection;
35
+ /**
36
+ * Hosted 3.x status tools a limited profile leaves out because summer_go_live
37
+ * answers the same question (what players get now on each platform, and
38
+ * where each upload and download stands), so the local go-live tools fit
39
+ * under PROFILE_TOOL_LIMIT without pushing hosted store tools over it. The
40
+ * full profile keeps them, and store.status answers it on the v4 surface.
41
+ */
42
+ export declare const SUPERSEDED_HOSTED_TOOLS: ReadonlySet<string>;
35
43
  /** True when the selection keeps this tool. */
36
44
  export declare function selectionIncludes(selection: ToolSelection, name: string, local: boolean): boolean;
37
45
  /** Every name the refinement sets mention (a test checks each is a real tool). */
@@ -36,8 +36,11 @@ const LOCAL_TOOLSETS = {
36
36
  "summer_get_asset",
37
37
  "summer_get_asset_download_url",
38
38
  ],
39
- ship: ["summer_export_game", "summer_export_templates", "summer_publish_build", "summer_capture_gameplay"],
39
+ ship: ["summer_export_game", "summer_export_templates", "summer_publish_build", "summer_go_live", "summer_store_set_details", "summer_capture_gameplay"],
40
40
  ui: ["summer_ui_actions", "summer_ui_activate", "summer_ui_screenshot", "summer_ui_tree"],
41
+ // Create and open a project on this computer with no shell (the full profile;
42
+ // the engine profile's clients have a shell, `summer dev`, and a tool cap).
43
+ projects: ["summer_projects_create_local", "summer_projects_open_local"],
41
44
  };
42
45
  const localToolset = new Map(Object.entries(LOCAL_TOOLSETS).flatMap(([set, names]) => names.map((name) => [name, set])));
43
46
  /** Hosted tool names by pattern, first match wins. Unknown names land in `other` (full only). */
@@ -99,9 +102,21 @@ export function resolveToolSelection(options = {}, env = process.env) {
99
102
  return { profile: "custom", toolsets: new Set(["core", ...extra]), ignored };
100
103
  return { profile, toolsets: new Set([...PROFILES[profile], ...extra]), ignored };
101
104
  }
105
+ /**
106
+ * Hosted 3.x status tools a limited profile leaves out because summer_go_live
107
+ * answers the same question (what players get now on each platform, and
108
+ * where each upload and download stands), so the local go-live tools fit
109
+ * under PROFILE_TOOL_LIMIT without pushing hosted store tools over it. The
110
+ * full profile keeps them, and store.status answers it on the v4 surface.
111
+ */
112
+ export const SUPERSEDED_HOSTED_TOOLS = new Set(["summer_store_releases", "summer_store_version_status"]);
102
113
  /** True when the selection keeps this tool. */
103
114
  export function selectionIncludes(selection, name, local) {
104
- return selection.toolsets === null || selection.toolsets.has(toolsetOf(name, local));
115
+ if (selection.toolsets === null)
116
+ return true;
117
+ if (!local && SUPERSEDED_HOSTED_TOOLS.has(name))
118
+ return false;
119
+ return selection.toolsets.has(toolsetOf(name, local));
105
120
  }
106
121
  // ── Annotations ──────────────────────────────────────────────────────────────
107
122
  /**
@@ -139,6 +154,7 @@ const NETWORK_WRITES = new Set([
139
154
  "summer_app_report_gap",
140
155
  "summer_app_update",
141
156
  "summer_slice_asset_sheet",
157
+ "summer_store_set_details",
142
158
  ]);
143
159
  /** Changes that only add something new, or only touch the editor view or the running game. */
144
160
  const ADDITIVE_OR_TRANSIENT = new Set([
@@ -181,6 +197,7 @@ const ADDITIVE_OR_TRANSIENT = new Set([
181
197
  ]);
182
198
  /** Changes where a second identical call has no further effect. */
183
199
  const IDEMPOTENT_WRITES = new Set([
200
+ "summer_store_set_details",
184
201
  "summer_set_prop",
185
202
  "summer_set_resource_property",
186
203
  "summer_project_setting",
@@ -3,24 +3,20 @@ import { captureGameplay } from "../../core/capabilities/capture-gameplay.js";
3
3
  import { exportGame } from "../../core/capabilities/export-game.js";
4
4
  import { STORE_TARGETS } from "../../core/capabilities/export-presets.js";
5
5
  import { exportTemplates, TEMPLATE_PLATFORMS } from "../../core/capabilities/export-templates.js";
6
+ import { goLive } from "../../core/capabilities/go-live.js";
6
7
  import { publishBuild } from "../../core/capabilities/publish-build.js";
7
- import { BuildToolError } from "../../core/capabilities/summer-bundle.js";
8
+ import { AI_USES, STORE_FEATURES, setStoreDetails, SURVEY_LEVELS } from "../../core/capabilities/store-details.js";
9
+ import { BuildToolError, buildToolFailure } from "../../core/capabilities/summer-bundle.js";
10
+ import { syncItems } from "../../core/capabilities/sync-items.js";
11
+ import { contentMake, contentMakeInput } from "../../core/capabilities/content-make/index.js";
8
12
  import { textJson } from "./text-json.js";
9
13
  async function creatorResult(operation) {
10
14
  try {
11
15
  return textJson(await operation());
12
16
  }
13
17
  catch (error) {
14
- if (error instanceof BuildToolError) {
15
- return textJson({
16
- ok: false,
17
- code: error.code,
18
- message: error.message.replace(` ${error.recovery}`, ""),
19
- recovery: error.recovery,
20
- ...(error.status ? { status: error.status } : {}),
21
- ...error.detail,
22
- }, true);
23
- }
18
+ if (error instanceof BuildToolError)
19
+ return textJson(buildToolFailure(error), true);
24
20
  return textJson({
25
21
  ok: false,
26
22
  code: "creator_request_invalid",
@@ -93,11 +89,11 @@ export function registerCreatorTools(server) {
93
89
  .optional()
94
90
  .describe("What to do after waitSeconds, in order; exactly one of press, key, action, click, drag, wait, shot per step."),
95
91
  }, async (args) => creatorResult(() => captureGameplay(args)));
96
- server.tool("summer_publish_build", "Upload an export from summer_export_game to the creator's game on summer.games, through the same store upload Studio uses, straight from disk. A store bundle (format bundle): declare, upload parts, seal, wait until Summer makes the Build, name its client pack, and with publish=true approve it. A web build or native download (format download): upload it as the game's store version for its platform (web, macos-universal, windows-x64, linux-x64; read from the last export) and wait until Summer has checked it. First call with confirm=false: it checks that the game exists for this account and warns when its store page does not list the export's platforms, then returns the target (game name and id, file, digest, size, version) to show the user; set confirm only after they approve. Needs \"summer login --store\". A retry with the same file and clientVersion continues the same upload. Nothing here makes a game live: the owner approves publishing (summer_store_submit).", {
97
- gameId: z.string().optional().describe("The store game id (from summer_store_list_games, or the Studio store page URL). Without it the call fails with game_required and lists your store games, or says why the store did not list them."),
92
+ server.tool("summer_publish_build", "Upload an export from summer_export_game to the creator's game on summer.games, through the same store upload Studio uses, straight from disk. A store bundle (format bundle): declare, upload parts, seal, wait until Summer makes the Build, name its client pack and wait for Summer's automated checks on each platform (checks: passed, or failed with the code and the fix). A web build or native download (format download): upload it as the game's store version for its platform (web, macos-universal, windows-x64, linux-x64; read from the last export) and wait until Summer has checked it. First call with confirm=false: it checks that the game exists for this account and warns when its store page does not list the export's platforms, then returns the target (game name and id, file, digest, size, version) to show the user; set confirm only after they approve. Needs \"summer login --store\". A retry with the same file and clientVersion continues the same upload. Going live is summer_go_live.", {
93
+ gameId: z.string().optional().describe("The store game id (from games.list, or the Studio store page URL). Without it the call fails with game_required and lists your store games, or says why the store did not list them."),
98
94
  file: z.string().optional().describe("Exported .zip. Defaults to the last summer_export_game result."),
99
95
  clientVersion: z.string().describe("Build version, vMAJOR.MINOR.PATCH (e.g. v1.0.0); a new upload needs a new version."),
100
- publish: z.boolean().default(false).describe("Bundles only: also approve the Build for players. A game that has not passed review keeps it as a preview; an agent needs the owner's approval."),
96
+ publish: z.boolean().default(false).describe("Bundles only: also make the Build available to players once its checks pass. summer_go_live does this, the release and the store page in one step."),
101
97
  platform: z
102
98
  .enum(["web", "macos-universal", "windows-x64", "linux-x64"])
103
99
  .optional()
@@ -105,4 +101,86 @@ export function registerCreatorTools(server) {
105
101
  confirm: z.boolean().default(false).describe("Set true only after the user approves the exact upload."),
106
102
  waitSeconds: z.number().int().min(0).max(1800).optional().describe("How long to wait for Summer to make the Build (default 600). On timeout, call again to keep waiting."),
107
103
  }, async (args) => creatorResult(() => publishBuild({ ...args, face: "mcp" })));
104
+ server.tool("summer_go_live", "Take a store game live for players: release its uploaded bundle Build (publicationId, or the newest upload) to every platform that passed Summer's automated checks, and publish its store page, through the same store routes Studio uses; then watch until players get it. It runs without an owner approval while the person's Agent permissions allow Publish (the default). First call with confirm=false: it reads the page, the Build and its per-platform checks and what players get now, and returns live, blocked (each failure with its fix), needs_changes (Summer's decision and feedback) or confirmation_required with the plan to show the user; set confirm only after they approve going live. With confirm=true it asks the platform and polls until live, or answers waiting with waitingOn (automated_checks, activation, or summer: a hold on Summer's side that needs nothing from the creator; never tell them to wait for a review). Call again with confirm=true to keep watching: nothing is asked twice. needs_permission: Publish is off in the person's Agent permissions. Web builds and downloads go live with the page. Needs \"summer login --store\".", {
105
+ gameId: z.string().optional().describe("The store game id (from games.list). Without it the call fails with game_required and lists your store games."),
106
+ publicationId: z.string().optional().describe("The bundle Build to release: publicationId from summer_publish_build. Default: the newest uploaded export."),
107
+ confirm: z.boolean().default(false).describe("Set true only after the user approves taking this exact page and Build live."),
108
+ waitSeconds: z.number().int().min(0).max(1800).optional().describe("How long to watch before answering (default 600). On waiting, call again to keep watching."),
109
+ }, async (args) => creatorResult(() => goLive({ ...args, face: "mcp" })));
110
+ server.tool("summer_store_set_details", "Fill the store page sections store.update_listing does not edit, on the store page draft: genres (1-5 genre tag ids from store.get_listing tags), features, credits, languageSupport, systemRequirements, contentSurvey (the age questionnaire: every answer, none included) and aiDisclosure (how AI was used; an empty uses list means no AI). Each section given replaces that section, null clears it, an omitted one keeps its value. A published page gets a new draft first. Saving never publishes: take it live with summer_go_live. Needs \"summer login --store\".", {
111
+ gameId: z.string().optional().describe("The store game id (from games.list)."),
112
+ genres: z.array(z.string()).min(1).max(5).nullable().optional().describe("Genre tag ids (category genre in store.get_listing tags), in the creator's order."),
113
+ features: z.array(z.enum(STORE_FEATURES)).min(1).nullable().optional().describe("What the game offers, from the store's closed list."),
114
+ credits: z
115
+ .object({ developer: z.string().min(1).max(120), publisher: z.string().min(1).max(120).optional(), franchise: z.string().min(1).max(120).optional() })
116
+ .nullable()
117
+ .optional()
118
+ .describe("Who made and published it; omit publisher or franchise when there is none."),
119
+ languageSupport: z
120
+ .array(z.object({ code: z.string(), interface: z.boolean(), fullAudio: z.boolean(), subtitles: z.boolean() }))
121
+ .min(1)
122
+ .max(30)
123
+ .nullable()
124
+ .optional()
125
+ .describe("One entry per language (BCP 47 code: en, pt-br, zh-hans), each with at least one kind of support."),
126
+ systemRequirements: z
127
+ .record(z.string(), z.unknown())
128
+ .nullable()
129
+ .optional()
130
+ .describe("Desktop hardware: { windows | macos | linux: { minimum: { os, processor, memory, graphics, storage, network, notes }, recommended? } }."),
131
+ contentSurvey: z
132
+ .object({
133
+ violence: z.enum(SURVEY_LEVELS.violence),
134
+ bloodGore: z.enum(SURVEY_LEVELS.bloodGore),
135
+ sexualContent: z.enum(SURVEY_LEVELS.sexualContent),
136
+ nudity: z.enum(SURVEY_LEVELS.nudity),
137
+ language: z.enum(SURVEY_LEVELS.language),
138
+ substances: z.enum(SURVEY_LEVELS.substances),
139
+ gambling: z.enum(SURVEY_LEVELS.gambling),
140
+ userGeneratedContent: z.boolean(),
141
+ onlineInteraction: z.boolean(),
142
+ inGamePurchases: z.boolean(),
143
+ notes: z.string().max(1000).optional(),
144
+ })
145
+ .nullable()
146
+ .optional()
147
+ .describe("The mature-content questionnaire; every answer is required, none is an answer."),
148
+ aiDisclosure: z
149
+ .object({ uses: z.array(z.enum(AI_USES)), description: z.string().max(2000).optional() })
150
+ .nullable()
151
+ .optional()
152
+ .describe("How AI was used to make the game; uses [] means no AI. generatedGameplay needs a description of what is generated and its guardrails."),
153
+ }, async (args) => creatorResult(() => setStoreDetails({ ...args, face: "mcp" })));
154
+ server.tool("summer_sync_items", "Bring the store game's items in line with summer.items.json in the project root, matched by each item's slug (the creator key the game's code uses, e.g. emote_ponder), so the same file gives the staging game and the production game the same items. First call with confirm=false: it checks the file, reads the game's items by slug and returns the plan (create, update with the changed fields, unchanged, blocked with the reason, and the game's items the file does not name, which are never changed) for the gateway this machine is set to; show it to the user. With confirm=true it uploads the changed images and sends the plan to the game's owner: the answer carries the approval link; nothing changes until the owner approves in Studio, then each item is created or revised, priced in Sparks and submitted for Summer's review. After Summer approves an item, the owner activates it in Studio (Review activation on the item's page); players can buy it only while the game is live. A change to a live item is a new revision, reviewed and activated again. An item with the same slug that is a Passport collectible is blocked: Passport items are not synced. One approval takes at most 50 changed items: a larger plan lists batches, confirm each with slugs. A new item needs at least one image. Never activates or releases an item. Needs \"summer login --store\". Staging or production is the gateway this machine is set to; switching takes SUMMER_GATEWAY_URL and SUMMER_MCP_RESOURCE_URL, then \"summer login --store --force\" (one store sign-in per machine; a sign-in for the other environment fails with store_login_other_environment).", {
155
+ gameId: z.string().optional().describe("The store game id on this gateway (from games.list). Staging and production games have different ids. Without it the call fails with game_required and lists your store games."),
156
+ project: z.string().optional().describe("Project folder with project.godot and summer.items.json. Defaults to the MCP's bound project, then the working directory."),
157
+ file: z.string().optional().describe("The items file, relative to the project. Default summer.items.json."),
158
+ slugs: z.array(z.string()).max(500).optional().describe("Only these items (their slugs); the rest of the file is left out of the plan. Confirm a plan of more than 50 changed items one batch (from batches) at a time."),
159
+ confirm: z.boolean().default(false).describe("Set true only after the user approves the plan."),
160
+ }, async (args) => creatorResult(() => syncItems({ ...args, face: "mcp" })));
161
+ server.tool("summer_content_make", "Make one finished, postable piece of content for a game and put it in the team's Review (Studio > Grow > Content), on this computer: it picks the reference from Summer's library that fits today (a format the game has not posted, what changed in the repo), studies its cuts and caption look, measures the footage, maps each reference shot to a clip window with motion, cuts to the reference's timings, draws the captions in its style, mixes at -14 LUFS, checks hook, motion per shot, safe zones, length and loudness, renders the reference side by side, uploads and saves the piece (failed checks: a draft). It never writes words itself: the first call answers needs_words with the beats and the kit's learnings; call again with workDir, lines (one per beat, in the reference's pattern, true to the game, never the reference's own words) and learningIds. A reference that speaks also needs voiceFile, the lines recorded in the team's voice (needs_voice). Same workDir continues where a run stopped. claim=true takes the oldest queued make request instead (nothing_queued when none; a revision answers needs_changes with what the team asked) and reports every stage to it, so Today shows it live. Needs ffmpeg and ImageMagick (and Whisper for a voice-over). Takes a few minutes.", {
162
+ contentGameId: z.string().optional().describe("The game's Content id, from content.list_games. Not needed with workDir."),
163
+ teamId: z.string().optional().describe("From team.list, when the person has more than one team."),
164
+ referenceId: z.string().optional().describe("The reference to copy, from content.list_references. Without it the best fit for today is picked."),
165
+ footage: z.array(z.string()).max(20).optional().describe("Folders (or files) of the game's footage on this computer: clips, stills, and the game's music. Default ./content/footage."),
166
+ lines: z.array(z.string()).max(80).optional().describe("Our words, one per reference beat (\"\" for a beat without words), from the needs_words answer."),
167
+ voiceFile: z.string().optional().describe("The lines recorded in the team's voice (wav, mp3, m4a), when the reference speaks."),
168
+ title: z.string().max(100).optional().describe("The piece's title in Review (also the YouTube title)."),
169
+ caption: z.string().max(2200).optional().describe("The post caption: one dry line and a question, no hashtags."),
170
+ hashtags: z.array(z.string()).max(10).optional().describe("3-5 tags, the game's tag first. Default: the kit's core tags."),
171
+ note: z.string().max(2000).optional().describe("What to make it about (\"the new boss\", or commit lines like \"3f2a1c9 Add waiting room\"): weighs the reference pick and becomes the why."),
172
+ claim: z.boolean().optional().describe("Make a queued make request (the daily scheduler's, or a Change pressed on Today) and report every stage to it: the one named by requestId, else the team's oldest."),
173
+ requestId: z.string().optional().describe("The make request to claim, from content.make."),
174
+ learningIds: z.array(z.string()).max(20).optional().describe("The kit learnings (content.get_kit learnings, listed in needs_words) the words and choices applied."),
175
+ workDir: z.string().optional().describe("The work folder an earlier call answered: continues that piece."),
176
+ out: z.string().optional().describe("Where the work goes (pieces, the reference videos and footage measurements). Default ~/.summer/content."),
177
+ music: z.string().optional().describe("The game's own music for a reference with a song (never the reference's audio). Default: the first song in the footage folders."),
178
+ font: z.string().optional().describe("The game's caption font file (.ttf/.otf)."),
179
+ referenceFile: z.string().optional().describe("The reference video, when it cannot be fetched (yt-dlp)."),
180
+ repo: z.string().optional().describe("The game's repo, for what changed today. Default the working directory."),
181
+ stage: z.enum(["plan", "all"]).optional().describe("plan: stop after the plan, to change shots in plan.json."),
182
+ upload: z.boolean().optional().describe("false: render and check only, upload nothing."),
183
+ replan: z.boolean().optional().describe("Throw away the plan and map the shots again."),
184
+ status: z.enum(["draft", "in_review"]).optional().describe("Override: in_review even with failed checks, or draft."),
185
+ }, async (args) => creatorResult(() => contentMake({ ...contentMakeInput(args), face: "mcp" })));
108
186
  }
@@ -194,6 +194,8 @@ Use this when summer_get_diagnostics shows a non-zero \`debugger.warnings\` coun
194
194
  }));
195
195
  server.tool("summer_play", `Start the game. With no parameters it runs QUIETLY inside Summer Engine's viewport (the 'main' instance); scene runs one level or UI screen instead of the main scene.
196
196
 
197
+ Playing the game in the editor's real window is allowed and expected: then summer_screenshot target:'game' to see it, summer_stop when done. One editor at a time.
198
+
197
199
  QUIET BY DEFAULT (focus:false, PlayGame agent:true): the user usually works on the same machine, so the EDITOR stays put. It does not switch to the Game tab or take keyboard focus (nor follow the game's later focus report), and skips the render-health self-check that would misread the untouched Game view as a GPU failure and flip the user's embed setting. Quiet play does NOT hide the game: it runs embedded in the Game view (visible if that tab is open) and is the running game for summer_is_running / summer_screenshot target:'game' / summer_get_diagnostics. With "Embed Game on Play" off the game gets its own OS window; engines with --summer-background (0.5.66+) open it without taking focus. The result echoes agent_quiet:true when honoured; without agent_quiet the engine predates quiet play and most likely took focus (posture_note says so). focus:true launches like the toolbar Play button (Game tab + focus): ONLY when the user is watching and asked to see the game come up.
198
200
 
199
201
  After starting, confirm boot with summer_is_running (boot time varies; never sleep a guessed delay), then summer_get_diagnostics for runtime errors.
@@ -0,0 +1,6 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * projects.create_local and projects.open_local: what `summer dev` does, for
4
+ * agents with no shell (core/capabilities/projects-local.ts).
5
+ */
6
+ export declare function registerLocalProjectTools(server: McpServer): void;