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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +5 -3
- package/.cursor-plugin/plugin.json +4 -2
- package/.factory-plugin/plugin.json +4 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +87 -0
- package/README.md +67 -27
- package/commands/start.md +19 -15
- package/commands/summer.md +1 -0
- package/dist/cli/command-usage.d.ts +3 -1
- package/dist/cli/command-usage.js +10 -4
- package/dist/cli/commands/create.d.ts +1 -25
- package/dist/cli/commands/create.js +10 -138
- package/dist/cli/commands/dev.d.ts +66 -0
- package/dist/cli/commands/dev.js +234 -0
- package/dist/cli/commands/install.d.ts +15 -58
- package/dist/cli/commands/install.js +31 -481
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/ops.d.ts +17 -1
- package/dist/cli/commands/ops.js +97 -9
- package/dist/cli/commands/preview.d.ts +58 -0
- package/dist/cli/commands/preview.js +181 -0
- package/dist/cli/commands/run.d.ts +1 -17
- package/dist/cli/commands/run.js +17 -206
- package/dist/cli/index.js +5 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +1 -1
- package/dist/core/api-client.d.ts +10 -2
- package/dist/core/api-client.js +13 -8
- package/dist/core/capabilities/agent-playbook.js +1 -0
- package/dist/core/capabilities/apps.d.ts +9 -9
- package/dist/core/capabilities/asset-import.d.ts +4 -1
- package/dist/core/capabilities/asset-import.js +15 -6
- package/dist/core/capabilities/capture-gameplay.js +1 -1
- package/dist/core/capabilities/connect-signal.d.ts +2 -2
- package/dist/core/capabilities/content-make/index.d.ts +81 -0
- package/dist/core/capabilities/content-make/index.js +706 -0
- package/dist/core/capabilities/content-make/media.d.ts +57 -0
- package/dist/core/capabilities/content-make/media.js +172 -0
- package/dist/core/capabilities/content-make/plan.d.ts +130 -0
- package/dist/core/capabilities/content-make/plan.js +433 -0
- package/dist/core/capabilities/content-make/render.d.ts +64 -0
- package/dist/core/capabilities/content-make/render.js +370 -0
- package/dist/core/capabilities/content-make/study.d.ts +13 -0
- package/dist/core/capabilities/content-make/study.js +165 -0
- package/dist/core/capabilities/content-make/types.d.ts +173 -0
- package/dist/core/capabilities/content-make/types.js +6 -0
- package/dist/core/capabilities/content-make/voice.d.ts +7 -0
- package/dist/core/capabilities/content-make/voice.js +27 -0
- package/dist/core/capabilities/events.d.ts +2 -2
- package/dist/core/capabilities/export-game.d.ts +4 -1
- package/dist/core/capabilities/export-game.js +4 -3
- package/dist/core/capabilities/export-presets.d.ts +2 -1
- package/dist/core/capabilities/export-presets.js +2 -1
- package/dist/core/capabilities/go-live.d.ts +14 -0
- package/dist/core/capabilities/go-live.js +408 -0
- package/dist/core/capabilities/image-generation.d.ts +6 -6
- package/dist/core/capabilities/item-manifest.d.ts +92 -0
- package/dist/core/capabilities/item-manifest.js +247 -0
- package/dist/core/capabilities/placement.d.ts +7 -7
- package/dist/core/capabilities/projects-local.d.ts +68 -0
- package/dist/core/capabilities/projects-local.js +172 -0
- package/dist/core/capabilities/publish-build.d.ts +13 -2
- package/dist/core/capabilities/publish-build.js +87 -21
- package/dist/core/capabilities/publish-download.js +1 -1
- package/dist/core/capabilities/runtime-control.d.ts +7 -2
- package/dist/core/capabilities/runtime-control.js +27 -1
- package/dist/core/capabilities/seeing/args.d.ts +2 -2
- package/dist/core/capabilities/store-checks.d.ts +45 -0
- package/dist/core/capabilities/store-checks.js +85 -0
- package/dist/core/capabilities/store-details.d.ts +37 -0
- package/dist/core/capabilities/store-details.js +94 -0
- package/dist/core/capabilities/summer-bundle.d.ts +21 -2
- package/dist/core/capabilities/summer-bundle.js +54 -7
- package/dist/core/capabilities/sync-items.d.ts +89 -0
- package/dist/core/capabilities/sync-items.js +352 -0
- package/dist/core/capabilities/tool-dispatch.js +66 -6
- package/dist/core/capabilities/video-ogv.d.ts +49 -0
- package/dist/core/capabilities/video-ogv.js +260 -0
- package/dist/core/capability-skew.js +2 -2
- package/dist/core/cli-usage-beacon.js +26 -2
- package/dist/core/cli-usage.d.ts +5 -2
- package/dist/core/cli-usage.js +8 -2
- package/dist/core/engine-install.d.ts +53 -0
- package/dist/core/engine-install.js +93 -0
- package/dist/core/engine-installer.d.ts +134 -0
- package/dist/core/engine-installer.js +760 -0
- package/dist/core/engine-launch.d.ts +126 -0
- package/dist/core/engine-launch.js +398 -0
- package/dist/core/engine-self-start.d.ts +63 -0
- package/dist/core/engine-self-start.js +282 -0
- package/dist/core/engine.d.ts +7 -1
- package/dist/core/engine.js +9 -2
- package/dist/core/error-codes.d.ts +4 -1
- package/dist/core/error-codes.js +7 -3
- package/dist/core/library-read.d.ts +2 -2
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/local-project.d.ts +50 -0
- package/dist/core/local-project.js +234 -0
- package/dist/core/ops/areas.js +9 -3
- package/dist/core/ops/catalog.d.ts +9 -0
- package/dist/core/ops/catalog.js +56 -4
- package/dist/core/ops/engine-answer.d.ts +4 -1
- package/dist/core/ops/engine-answer.js +16 -3
- package/dist/core/ops/engine.js +4 -4
- package/dist/core/ops/save-files.d.ts +6 -1
- package/dist/core/ops/save-files.js +67 -50
- package/dist/core/ops/search.js +9 -2
- package/dist/core/preview/input.d.ts +35 -0
- package/dist/core/preview/input.js +105 -0
- package/dist/core/preview/launch-config.d.ts +52 -0
- package/dist/core/preview/launch-config.js +137 -0
- package/dist/core/preview/page.d.ts +22 -0
- package/dist/core/preview/page.js +232 -0
- package/dist/core/preview/preview-source.d.ts +179 -0
- package/dist/core/preview/preview-source.js +350 -0
- package/dist/core/preview/server.d.ts +26 -0
- package/dist/core/preview/server.js +218 -0
- package/dist/core/telemetry.d.ts +20 -16
- package/dist/core/telemetry.js +92 -16
- package/dist/core/templates.d.ts +20 -0
- package/dist/core/templates.js +300 -3
- package/dist/core/tool-profiles.d.ts +2 -2
- package/dist/core/tool-profiles.js +1 -0
- package/dist/core/usage-fields.d.ts +41 -0
- package/dist/core/usage-fields.js +150 -0
- package/dist/core/util/sleep.d.ts +3 -0
- package/dist/core/util/sleep.js +12 -0
- package/dist/mcp/ops-surface.js +28 -2
- package/dist/mcp/server.d.ts +14 -4
- package/dist/mcp/server.js +42 -8
- package/dist/mcp/tool-surface.d.ts +8 -0
- package/dist/mcp/tool-surface.js +19 -2
- package/dist/mcp/tools/creator-tools.js +92 -14
- package/dist/mcp/tools/debug-tools.js +2 -0
- package/dist/mcp/tools/local-project-tools.d.ts +6 -0
- package/dist/mcp/tools/local-project-tools.js +33 -0
- package/dist/mcp/tools/navigation-tools.js +2 -1
- package/dist/mcp/tools/project-tools.js +5 -8
- package/dist/mcp/tools/seeing-tools.js +1 -1
- package/dist/mcp/tools/visual-tools.js +1 -1
- package/dist/mcp/tools/with-engine.js +1 -1
- package/gemini-extension.json +3 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
- package/library/skills/README.md +12 -8
- package/library/skills/animated-loop/SKILL.md +10 -16
- package/library/skills/cinematic-cutscene/SKILL.md +7 -18
- package/library/skills/headless-scripting/SKILL.md +7 -4
- package/library/skills/in-game-purchases/SKILL.md +25 -8
- package/library/skills/in-game-purchases/resource.yaml +2 -1
- package/library/skills/launch-ready/SKILL.md +351 -0
- package/library/skills/launch-ready/resource.yaml +45 -0
- package/library/skills/make-content/SKILL.md +202 -0
- package/library/skills/make-content/resource.yaml +32 -0
- package/library/skills/make-game/SKILL.md +2 -0
- package/library/skills/make-game/resource.yaml +2 -1
- package/library/skills/navigate-summer/SKILL.md +4 -2
- package/library/skills/new-project/SKILL.md +15 -22
- package/library/skills/new-project/resource.yaml +2 -1
- package/library/skills/painted-25d-world/SKILL.md +366 -0
- package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
- package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
- package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
- package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
- package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
- package/library/skills/painted-25d-world/resource.yaml +39 -0
- package/library/skills/publish-your-game/SKILL.md +13 -14
- package/library/skills/publish-your-game/resource.yaml +4 -3
- package/library/skills/release-checklist/SKILL.md +188 -0
- package/library/skills/release-checklist/resource.yaml +40 -0
- package/library/skills/release-readiness/SKILL.md +1 -1
- package/library/skills/release-readiness/resource.yaml +1 -1
- package/library/skills/store-art/SKILL.md +1 -1
- package/library/skills/store-art/resource.yaml +1 -1
- package/library/skills/store-listing/SKILL.md +1 -1
- package/library/skills/store-listing/resource.yaml +1 -1
- package/library/skills/summer-cli/SKILL.md +2 -0
- package/library/skills/summer-store/SKILL.md +295 -39
- package/library/skills/summer-store/resource.yaml +9 -3
- package/library/skills/trailer-shot/SKILL.md +3 -3
- package/library/templates/README.md +5 -0
- package/library/tools/content-make/resource.yaml +132 -0
- package/library/tools/frame-shot/GUIDE.md +1 -1
- package/library/tools/go-live/resource.yaml +67 -0
- package/library/tools/import-from-url/GUIDE.md +1 -0
- package/library/tools/import-from-url-batch/GUIDE.md +1 -1
- package/library/tools/play/GUIDE.md +2 -0
- package/library/tools/projects-create-local/resource.yaml +55 -0
- package/library/tools/projects-open-local/resource.yaml +51 -0
- package/library/tools/publish-build/resource.yaml +3 -3
- package/library/tools/screenshot/GUIDE.md +1 -1
- package/library/tools/store-set-details/resource.yaml +114 -0
- package/library/tools/sync-items/resource.yaml +72 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +6 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -1
- package/registry/generated/index.json +463 -37
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -2
- package/registry/generated/plugin.claude.json +3 -1
- package/registry/generated/plugin.codex.json +5 -3
- package/registry/generated/plugin.cursor.json +4 -2
- package/registry/generated/plugin.factory.json +4 -2
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +12 -8
- package/registry/generated/skills-registry.json +62 -5
- package/registry/operations/legacy-names.json +40 -0
- 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>;
|
package/dist/core/util/sleep.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/mcp/ops-surface.js
CHANGED
|
@@ -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
|
-
"
|
|
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];
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
19
|
-
* the
|
|
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
|
-
|
|
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
|
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
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
|
|
60
|
-
* the
|
|
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,
|
|
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
|
-
|
|
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", () =>
|
|
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). */
|
package/dist/mcp/tool-surface.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
97
|
-
gameId: z.string().optional().describe("The store game id (from
|
|
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
|
|
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;
|