summer-engine 3.4.1 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +5 -1
  3. package/.codex-plugin/plugin.json +5 -1
  4. package/.cursor-plugin/plugin.json +5 -1
  5. package/.factory-plugin/plugin.json +5 -1
  6. package/CHANGELOG.md +31 -0
  7. package/README.md +5 -1
  8. package/dist/cli/commands/setup.js +24 -4
  9. package/dist/core/capabilities/capture-gameplay.d.ts +72 -7
  10. package/dist/core/capabilities/capture-gameplay.js +386 -25
  11. package/dist/core/capabilities/export-engine-version.d.ts +7 -0
  12. package/dist/core/capabilities/export-engine-version.js +9 -0
  13. package/dist/core/capabilities/export-game.d.ts +4 -0
  14. package/dist/core/capabilities/export-game.js +50 -7
  15. package/dist/core/capabilities/image-generation.d.ts +12 -2
  16. package/dist/core/capabilities/image-generation.js +31 -2
  17. package/dist/core/capabilities/publish-build.d.ts +4 -0
  18. package/dist/core/capabilities/publish-build.js +84 -37
  19. package/dist/core/capabilities/runtime-control.d.ts +2 -2
  20. package/dist/core/capabilities/summer-bundle.d.ts +9 -0
  21. package/dist/core/capabilities/summer-bundle.js +19 -0
  22. package/dist/core/capabilities/tool-dispatch.js +13 -2
  23. package/dist/core/library-read.d.ts +7 -0
  24. package/dist/core/library-read.js +59 -7
  25. package/dist/core/library-search.d.ts +2 -0
  26. package/dist/installer/agent-config.d.ts +26 -1
  27. package/dist/installer/agent-config.js +180 -5
  28. package/dist/installer/agent-table.d.ts +13 -0
  29. package/dist/installer/agent-table.js +120 -0
  30. package/dist/mcp/tools/creator-tools.js +32 -3
  31. package/dist/mcp/tools/generate-tools.js +11 -30
  32. package/dist/mcp/tools/library-tools.d.ts +2 -1
  33. package/dist/mcp/tools/library-tools.js +1 -0
  34. package/gemini-extension.json +5 -1
  35. package/integrations/README.md +6 -0
  36. package/library/references/mcp-tools-reference/mcp-tools-reference.md +1 -1
  37. package/library/skills/README.md +11 -1
  38. package/library/skills/export-and-ship/SKILL.md +1 -1
  39. package/library/skills/export-and-ship/resource.yaml +2 -1
  40. package/library/skills/grow-analytics/SKILL.md +21 -0
  41. package/library/skills/grow-analytics/resource.yaml +27 -0
  42. package/library/skills/publish-your-game/SKILL.md +21 -0
  43. package/library/skills/publish-your-game/resource.yaml +29 -0
  44. package/library/skills/store-art/SKILL.md +23 -0
  45. package/library/skills/store-art/resource.yaml +30 -0
  46. package/library/skills/store-listing/SKILL.md +21 -0
  47. package/library/skills/store-listing/resource.yaml +25 -0
  48. package/library/skills/summer-analytics/resource.yaml +1 -0
  49. package/library/tools/capture-gameplay/resource.yaml +66 -1
  50. package/library/tools/export-game/resource.yaml +3 -0
  51. package/library/tools/generate-image/resource.yaml +4 -2
  52. package/package.json +1 -1
  53. package/registry/generated/counts.json +2 -2
  54. package/registry/generated/gemini-extension.json +5 -1
  55. package/registry/generated/index.json +153 -10
  56. package/registry/generated/marketplace.claude.json +1 -1
  57. package/registry/generated/plugin.claude.json +5 -1
  58. package/registry/generated/plugin.codex.json +5 -1
  59. package/registry/generated/plugin.cursor.json +5 -1
  60. package/registry/generated/plugin.factory.json +5 -1
  61. package/registry/generated/skills-index.md +11 -1
  62. package/registry/generated/skills-registry.json +52 -0
  63. package/registry/schemas/skill.schema.json +5 -0
@@ -73,6 +73,180 @@ export async function configureAgentMcp(options) {
73
73
  nextSteps: [`Updated ${target.path}.`, spec.restart],
74
74
  };
75
75
  }
76
+ /** The Summer Games player MCP: friends, parties, messages and the player's
77
+ * inbox, as the signed-in player. A remote server; the agent signs in to it
78
+ * with OAuth in the browser, so setup writes no credential. */
79
+ export const SUMMER_GAMES_MCP_SERVER_NAME = "summer-games";
80
+ export const SUMMER_GAMES_MCP_URL = "https://mcp.summer.games/mcp";
81
+ /**
82
+ * Adds the Summer Games MCP next to summer-engine in the same config file, in
83
+ * the agent's own entry shape (remoteMcp in agent-table.ts). An entry that
84
+ * already points at the same URL, under any name, counts as added and is left
85
+ * as it is. Returns null for agents without remoteMcp.
86
+ */
87
+ export async function configureSummerGamesMcp(options) {
88
+ const spec = agentSpec(options.agent);
89
+ if (!spec.remoteMcp)
90
+ return null;
91
+ const env = options.env ?? process.env;
92
+ const target = resolveConfigTarget(spec, options.scope, resolve(options.cwd ?? process.cwd()), env);
93
+ const write = !options.dryRun;
94
+ const url = SUMMER_GAMES_MCP_URL;
95
+ const name = SUMMER_GAMES_MCP_SERVER_NAME;
96
+ const entry = spec.remoteMcp.entry(url);
97
+ const key = topLevelKey(spec.format);
98
+ let existing;
99
+ let changed = false;
100
+ switch (spec.format) {
101
+ case "toml":
102
+ case "toml-array": {
103
+ const current = await readTextFileIfExists(target.path);
104
+ existing = spec.format === "toml" ? findTomlServerByUrl(current, url) : findTomlArrayServerByUrl(current, url);
105
+ if (existing)
106
+ break;
107
+ const next = spec.format === "toml"
108
+ ? upsertTomlTable(current, renderTomlTable(`[mcp_servers.${name}]`, entry), name)
109
+ : upsertTomlArrayBlock(current, renderTomlTable("[[mcp_servers]]", entry), name);
110
+ changed = current !== next;
111
+ if (write && changed)
112
+ await writeTextFile(target.path, next);
113
+ break;
114
+ }
115
+ case "yaml-goose":
116
+ case "yaml-hermes": {
117
+ const current = await readTextFileIfExists(target.path);
118
+ const parsed = current.trim() === "" ? {} : parseDocument(current).toJS();
119
+ existing = isJsonObject(parsed) ? findServerByUrl([parsed[key]], url) : undefined;
120
+ if (existing)
121
+ break;
122
+ ({ changed } = await upsertYamlConfig(target.path, [key, name], entry, write));
123
+ break;
124
+ }
125
+ case "json-gemini":
126
+ throw new Error("The Gemini extension manifest has no Summer Games entry.");
127
+ default: {
128
+ const current = await readJsonConfig(target.path);
129
+ const maps = [current[key]];
130
+ // Claude Code's ~/.claude.json also keeps per-project servers (`claude
131
+ // mcp add` without --scope user) under projects.<dir>.mcpServers.
132
+ if (isJsonObject(current.projects)) {
133
+ for (const project of Object.values(current.projects)) {
134
+ if (isJsonObject(project))
135
+ maps.push(project.mcpServers);
136
+ }
137
+ }
138
+ existing = findServerByUrl(maps, url);
139
+ if (existing)
140
+ break;
141
+ const next = copyJsonObject(current);
142
+ next[key] = mergeNamedObject(next[key], name, entry, key);
143
+ const nextRendered = renderJsonFile(next);
144
+ changed = renderJsonFile(current) !== nextRendered;
145
+ if (write && changed)
146
+ await writeTextFile(target.path, nextRendered);
147
+ }
148
+ }
149
+ const serverName = existing ?? name;
150
+ return {
151
+ serverName,
152
+ url,
153
+ path: target.path,
154
+ changed,
155
+ wrote: write && changed,
156
+ ...(existing && existing !== name ? { existingName: existing } : {}),
157
+ signIn: spec.remoteMcp.signIn(serverName),
158
+ };
159
+ }
160
+ function sameMcpUrl(a, b) {
161
+ if (typeof a !== "string")
162
+ return false;
163
+ const normalize = (value) => {
164
+ try {
165
+ const parsed = new URL(value.trim());
166
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname.replace(/\/+$/, "")}`.toLowerCase();
167
+ }
168
+ catch {
169
+ return value.trim().toLowerCase();
170
+ }
171
+ };
172
+ return normalize(a) === normalize(b);
173
+ }
174
+ /** Agents name a remote server's address url, serverUrl (Windsurf,
175
+ * Antigravity), httpUrl (Qwen Code) or uri (Goose). */
176
+ const REMOTE_URL_FIELDS = ["url", "serverUrl", "httpUrl", "uri"];
177
+ /** The name of a server with this URL in any of these name -> entry maps. */
178
+ function findServerByUrl(maps, url) {
179
+ for (const map of maps) {
180
+ if (!isJsonObject(map))
181
+ continue;
182
+ for (const [name, entry] of Object.entries(map)) {
183
+ if (isJsonObject(entry) && REMOTE_URL_FIELDS.some((field) => sameMcpUrl(entry[field], url)))
184
+ return name;
185
+ }
186
+ }
187
+ return undefined;
188
+ }
189
+ /** The name of a [[mcp_servers]] block (Mistral Vibe) whose url is this URL. */
190
+ function findTomlArrayServerByUrl(content, url) {
191
+ let block;
192
+ const done = () => (block?.matches ? block.name : undefined);
193
+ for (const line of content.split(/\r?\n/)) {
194
+ if (/^\s*\[/.test(line)) {
195
+ if (done())
196
+ return done();
197
+ block = /^\s*\[\[mcp_servers\]\]\s*(?:#.*)?$/.test(line) ? { matches: false } : undefined;
198
+ continue;
199
+ }
200
+ if (!block)
201
+ continue;
202
+ const field = /^\s*(name|url)\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(line);
203
+ if (!field)
204
+ continue;
205
+ const value = field[2] ?? field[3];
206
+ if (field[1] === "name")
207
+ block.name = value;
208
+ else if (sameMcpUrl(value, url))
209
+ block.matches = true;
210
+ }
211
+ return done();
212
+ }
213
+ /** A TOML table: the header line, then key = value for each entry field. */
214
+ function renderTomlTable(header, entry) {
215
+ return `${[header, ...Object.entries(entry).map(([field, value]) => `${field} = ${tomlValue(value)}`)].join("\n")}\n`;
216
+ }
217
+ function tomlValue(value) {
218
+ if (typeof value === "string")
219
+ return tomlString(value);
220
+ if (typeof value === "number" || typeof value === "boolean")
221
+ return String(value);
222
+ if (Array.isArray(value))
223
+ return `[${value.map(tomlValue).join(", ")}]`;
224
+ if (isJsonObject(value)) {
225
+ const fields = Object.entries(value).map(([field, inner]) => `${field} = ${tomlValue(inner)}`);
226
+ return fields.length === 0 ? "{}" : `{ ${fields.join(", ")} }`;
227
+ }
228
+ throw new Error(`Cannot write ${JSON.stringify(value)} as TOML.`);
229
+ }
230
+ /** The name of a [mcp_servers.<name>] table whose url is this URL. */
231
+ function findTomlServerByUrl(content, url) {
232
+ let table;
233
+ for (const line of content.split(/\r?\n/)) {
234
+ const header = /^\s*\[([^\[\]]+)\]\s*(?:#.*)?$/.exec(line);
235
+ if (header) {
236
+ const server = /^mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))$/.exec(header[1].trim());
237
+ table = server ? (server[1] ?? server[2]) : undefined;
238
+ continue;
239
+ }
240
+ if (/^\s*\[/.test(line)) {
241
+ table = undefined;
242
+ continue;
243
+ }
244
+ const value = /^\s*url\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(line);
245
+ if (table && value && sameMcpUrl(value[1] ?? value[2], url))
246
+ return table;
247
+ }
248
+ return undefined;
249
+ }
76
250
  async function upsertConfig(format, path, server, write) {
77
251
  switch (format) {
78
252
  case "toml":
@@ -320,10 +494,10 @@ async function upsertTomlArrayConfig(path, server, write) {
320
494
  }
321
495
  return { changed };
322
496
  }
323
- export function upsertTomlArrayBlock(content, block) {
497
+ export function upsertTomlArrayBlock(content, block, name = SUMMER_MCP_SERVER_NAME) {
324
498
  const lines = content.split(/\r?\n/);
325
499
  const header = /^\s*\[\[mcp_servers\]\]\s*(?:#.*)?$/;
326
- const nameLine = new RegExp(`^\\s*name\\s*=\\s*"${SUMMER_MCP_SERVER_NAME}"\\s*(?:#.*)?$`);
500
+ const nameLine = new RegExp(`^\\s*name\\s*=\\s*"${name}"\\s*(?:#.*)?$`);
327
501
  let start = -1;
328
502
  let end = lines.length;
329
503
  for (let index = 0; index < lines.length; index += 1) {
@@ -470,10 +644,11 @@ async function readTextFileIfExists(path) {
470
644
  throw new Error(`Could not read ${path}: ${formatError(error)}`);
471
645
  }
472
646
  }
473
- function upsertTomlTable(content, table) {
647
+ function upsertTomlTable(content, table, name = SUMMER_MCP_SERVER_NAME) {
474
648
  const lines = content.split(/\r?\n/);
475
- const serverHeader = /^\s*\[mcp_servers\.(?:"summer-engine"|summer-engine)(?:\.|\])/;
476
- const start = lines.findIndex((line) => /^\s*\[mcp_servers\.(?:"summer-engine"|summer-engine)\]\s*(?:#.*)?$/.test(line));
649
+ const key = name.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
650
+ const serverHeader = new RegExp(`^\\s*\\[mcp_servers\\.(?:"${key}"|${key})(?:\\.|\\])`);
651
+ const start = lines.findIndex((line) => new RegExp(`^\\s*\\[mcp_servers\\.(?:"${key}"|${key})\\]\\s*(?:#.*)?$`).test(line));
477
652
  if (start === -1) {
478
653
  const trimmed = content.endsWith("\n") || content === "" ? content : `${content}\n`;
479
654
  return `${trimmed}${trimmed === "" ? "" : "\n"}${table}`;
@@ -70,6 +70,19 @@ export interface AgentSpec {
70
70
  /** Env var that overrides the MCP config path (tests, unusual installs). */
71
71
  envOverride: string;
72
72
  format: McpFormat;
73
+ /**
74
+ * The Summer Games player MCP (a remote server that signs in with OAuth on
75
+ * its own) in this agent's file, under the same key as summer-engine.
76
+ * entry is the server's value; for TOML it is written as key = value lines.
77
+ * signIn tells the person how to sign in once, for the entry's name.
78
+ * Absent: setup links the engine only (no OAuth sign-in for remote servers,
79
+ * no file entry for them, or a sign-in api.summer.games has not been checked
80
+ * to accept).
81
+ */
82
+ remoteMcp?: {
83
+ entry: (url: string) => Record<string, unknown>;
84
+ signIn: (name: string) => string;
85
+ };
73
86
  mcp: {
74
87
  /** null: user-level servers are managed in the app's UI; a user request writes the project file with a warning. */
75
88
  user: ((ctx: PathContext) => string) | null;
@@ -108,6 +108,10 @@ const SPECS = [
108
108
  status: "active",
109
109
  envOverride: "SUMMER_CLAUDE_CONFIG_FILE",
110
110
  format: "json-stdio",
111
+ remoteMcp: {
112
+ entry: (url) => ({ type: "http", url }),
113
+ signIn: (name) => `In Claude Code, run /mcp, choose ${name} and sign in.`,
114
+ },
111
115
  mcp: { user: home(".claude.json"), project: project(".mcp.json") },
112
116
  restart: "Restart Claude Code or run /mcp in a new session.",
113
117
  skills: skillDirs(home(".claude", "skills"), project(".claude", "skills")),
@@ -119,6 +123,7 @@ const SPECS = [
119
123
  status: "active",
120
124
  envOverride: "SUMMER_CLAUDE_DESKTOP_CONFIG_FILE",
121
125
  format: "json",
126
+ // No Summer Games entry: Claude Desktop's file takes local servers only; remote ones are added as Connectors in its settings.
122
127
  mcp: {
123
128
  user: (ctx) => join(appConfigRoot(ctx), "Claude", "claude_desktop_config.json"),
124
129
  project: null,
@@ -134,6 +139,10 @@ const SPECS = [
134
139
  status: "active",
135
140
  envOverride: "SUMMER_CODEX_CONFIG_FILE",
136
141
  format: "toml",
142
+ remoteMcp: {
143
+ entry: (url) => ({ url }),
144
+ signIn: (name) => `In Codex, run /mcp login ${name} (or \`codex mcp login ${name}\` in a terminal) and sign in.`,
145
+ },
137
146
  mcp: {
138
147
  user: home(".codex", "config.toml"),
139
148
  project: project(".codex", "config.toml"),
@@ -149,6 +158,10 @@ const SPECS = [
149
158
  status: "active",
150
159
  envOverride: "SUMMER_CURSOR_MCP_CONFIG_FILE",
151
160
  format: "json-stdio",
161
+ remoteMcp: {
162
+ entry: (url) => ({ url }),
163
+ signIn: (name) => `In Cursor Settings, open MCP and click Connect (or Authenticate) next to ${name}.`,
164
+ },
152
165
  mcp: { user: home(".cursor", "mcp.json"), project: project(".cursor", "mcp.json") },
153
166
  restart: "Restart Cursor and enable the summer-engine MCP server if prompted.",
154
167
  skills: sharedSkills({
@@ -164,6 +177,10 @@ const SPECS = [
164
177
  status: "active",
165
178
  envOverride: "SUMMER_WINDSURF_MCP_CONFIG_FILE",
166
179
  format: "json",
180
+ remoteMcp: {
181
+ entry: (url) => ({ serverUrl: url }),
182
+ signIn: (name) => `Devin Desktop opens summer.games in your browser the first time ${name} connects; sign in there.`,
183
+ },
167
184
  mcp: {
168
185
  user: home(".codeium", "windsurf", "mcp_config.json"),
169
186
  project: null,
@@ -178,6 +195,10 @@ const SPECS = [
178
195
  status: "active",
179
196
  envOverride: "SUMMER_ANTIGRAVITY_CONFIG_FILE",
180
197
  format: "json",
198
+ remoteMcp: {
199
+ entry: (url) => ({ serverUrl: url }),
200
+ signIn: (name) => `In Antigravity, open Settings > Customizations, choose Authenticate next to ${name}, sign in on summer.games, then paste the code back and choose Submit.`,
201
+ },
181
202
  mcp: {
182
203
  user: home(".gemini", "config", "mcp_config.json"),
183
204
  project: project(".agents", "mcp_config.json"),
@@ -193,6 +214,7 @@ const SPECS = [
193
214
  legacyNote: "Gemini CLI was retired for individual accounts on 2026-06-18 and replaced by Antigravity. Use `summer setup antigravity` unless you are on a Workspace/enterprise Gemini CLI.",
194
215
  envOverride: "SUMMER_GEMINI_CONFIG_FILE",
195
216
  format: "json-gemini",
217
+ // No Summer Games entry: legacy target; use antigravity.
196
218
  mcp: {
197
219
  user: home(".gemini", "extensions", "summer-engine", "gemini-extension.json"),
198
220
  project: null,
@@ -207,6 +229,10 @@ const SPECS = [
207
229
  status: "active",
208
230
  envOverride: "SUMMER_CLINE_CONFIG_FILE",
209
231
  format: "json",
232
+ remoteMcp: {
233
+ entry: (url) => ({ type: "streamableHttp", url }),
234
+ signIn: (name) => `In Cline, open MCP Servers and click Authenticate next to ${name}.`,
235
+ },
210
236
  mcp: {
211
237
  user: (ctx) => vsCodeGlobalStorage(ctx, "saoudrizwan.claude-dev", "cline_mcp_settings.json"),
212
238
  project: null,
@@ -221,6 +247,10 @@ const SPECS = [
221
247
  status: "active",
222
248
  envOverride: "SUMMER_CLINE_CLI_CONFIG_FILE",
223
249
  format: "json",
250
+ remoteMcp: {
251
+ entry: (url) => ({ type: "streamableHttp", url }),
252
+ signIn: (name) => `Run \`cline mcp\`, pick ${name} and choose Authorize OAuth.`,
253
+ },
224
254
  mcp: { user: home(".cline", "data", "settings", "cline_mcp_settings.json"), project: null },
225
255
  restart: "Restart the Cline CLI so it reloads its MCP settings.",
226
256
  skills: skillDirs(home(".cline", "skills"), project(".cline", "skills")),
@@ -233,6 +263,7 @@ const SPECS = [
233
263
  legacyNote: "Roo Code shut down on 2026-05-15. The extension may still run, but it is no longer maintained; consider Cline or Kilo Code.",
234
264
  envOverride: "SUMMER_ROO_CODE_CONFIG_FILE",
235
265
  format: "json",
266
+ // No Summer Games entry: Roo Code has no OAuth sign-in for remote MCP servers.
236
267
  mcp: {
237
268
  user: (ctx) => vsCodeGlobalStorage(ctx, "rooveterinaryinc.roo-cline", "cline_mcp_settings.json"),
238
269
  project: null,
@@ -252,6 +283,10 @@ const SPECS = [
252
283
  status: "active",
253
284
  envOverride: "SUMMER_KILO_CODE_CONFIG_FILE",
254
285
  format: "json-opencode",
286
+ remoteMcp: {
287
+ entry: (url) => ({ type: "remote", url }),
288
+ signIn: (name) => `In Kilo, click the sign-in notice for ${name} (Kilo CLI: \`kilo mcp auth ${name}\`).`,
289
+ },
255
290
  mcp: {
256
291
  user: (ctx) => join(xdgOrAppData(ctx), "kilo", "kilo.json"),
257
292
  project: project("kilo.json"),
@@ -266,6 +301,10 @@ const SPECS = [
266
301
  status: "active",
267
302
  envOverride: "SUMMER_GITHUB_COPILOT_CONFIG_FILE",
268
303
  format: "json-copilot",
304
+ remoteMcp: {
305
+ entry: (url) => ({ type: "http", url, tools: ["*"] }),
306
+ signIn: (name) => `Copilot CLI opens summer.games to sign in when ${name} first connects; /mcp signs in again.`,
307
+ },
269
308
  mcp: { user: home(".copilot", "mcp-config.json"), project: project(".mcp.json") },
270
309
  restart: "Restart Copilot CLI, or run /mcp reload and /skills reload in the active session.",
271
310
  skills: skillDirs(home(".copilot", "skills"), project(".github", "skills")),
@@ -277,6 +316,10 @@ const SPECS = [
277
316
  status: "active",
278
317
  envOverride: "SUMMER_VSCODE_COPILOT_CONFIG_FILE",
279
318
  format: "json-vscode",
319
+ remoteMcp: {
320
+ entry: (url) => ({ type: "http", url }),
321
+ signIn: (name) => `In VS Code, click Allow when ${name} asks to authenticate, then sign in on summer.games.`,
322
+ },
280
323
  mcp: { user: (ctx) => join(vsCodeUserDir(ctx), "mcp.json"), project: project(".vscode", "mcp.json") },
281
324
  restart: "Restart VS Code or run MCP: List Servers, then start summer-engine in Copilot Agent mode.",
282
325
  skills: sharedSkills({ legacyDirs: { user: home(".copilot", "skills"), project: project(".github", "skills") } }),
@@ -288,6 +331,7 @@ const SPECS = [
288
331
  status: "active",
289
332
  envOverride: "SUMMER_VISUAL_STUDIO_CONFIG_FILE",
290
333
  format: "json-vscode",
334
+ // No Summer Games entry: Visual Studio's OAuth redirect for MCP is not documented; not checked against api.summer.games.
291
335
  mcp: { user: home(".mcp.json"), project: project(".mcp.json") },
292
336
  restart: "Restart Visual Studio, open Copilot Chat in Agent mode, and enable summer-engine in the tools picker.",
293
337
  skills: null,
@@ -300,6 +344,10 @@ const SPECS = [
300
344
  status: "active",
301
345
  envOverride: "SUMMER_COPILOT_JETBRAINS_CONFIG_FILE",
302
346
  format: "json-servers",
347
+ remoteMcp: {
348
+ entry: (url) => ({ url }),
349
+ signIn: (name) => `In your JetBrains IDE, click OK when ${name} asks to authenticate, then sign in on summer.games.`,
350
+ },
303
351
  mcp: {
304
352
  user: (ctx) => join(xdgOrAppData(ctx), "github-copilot", "intellij", "mcp.json"),
305
353
  project: null,
@@ -315,6 +363,10 @@ const SPECS = [
315
363
  status: "active",
316
364
  envOverride: "SUMMER_OPENCODE_CONFIG_FILE",
317
365
  format: "json-opencode",
366
+ remoteMcp: {
367
+ entry: (url) => ({ type: "remote", url }),
368
+ signIn: (name) => `Run \`opencode mcp auth ${name}\`; it opens summer.games to sign in.`,
369
+ },
318
370
  mcp: {
319
371
  user: (ctx) => join(xdgOrAppData(ctx), "opencode", "opencode.json"),
320
372
  project: project("opencode.json"),
@@ -333,6 +385,10 @@ const SPECS = [
333
385
  status: "active",
334
386
  envOverride: "SUMMER_ZED_CONFIG_FILE",
335
387
  format: "json-zed",
388
+ remoteMcp: {
389
+ entry: (url) => ({ url }),
390
+ signIn: (name) => `In Zed, open the agent settings and click Authenticate next to ${name}.`,
391
+ },
336
392
  mcp: {
337
393
  user: (ctx) => join(xdgOrAppData(ctx), "zed", "settings.json"),
338
394
  project: null,
@@ -347,6 +403,10 @@ const SPECS = [
347
403
  status: "active",
348
404
  envOverride: "SUMMER_KIRO_CONFIG_FILE",
349
405
  format: "json",
406
+ remoteMcp: {
407
+ entry: (url) => ({ url }),
408
+ signIn: (name) => `Kiro opens summer.games to sign in when ${name} connects (Kiro CLI: /mcp auth).`,
409
+ },
350
410
  mcp: { user: home(".kiro", "settings", "mcp.json"), project: project(".kiro", "settings", "mcp.json") },
351
411
  restart: "Kiro reloads MCP config on save; open the MCP Servers view to confirm summer-engine is connected.",
352
412
  skills: skillDirs(home(".kiro", "skills"), project(".kiro", "skills")),
@@ -358,6 +418,19 @@ const SPECS = [
358
418
  status: "active",
359
419
  envOverride: "SUMMER_GOOSE_CONFIG_FILE",
360
420
  format: "yaml-goose",
421
+ remoteMcp: {
422
+ entry: (url) => ({
423
+ type: "streamable_http",
424
+ name: "summer-games",
425
+ description: "Summer Games: friends, parties and messages as you",
426
+ uri: url,
427
+ enabled: true,
428
+ timeout: 300,
429
+ env_keys: [],
430
+ envs: {},
431
+ }),
432
+ signIn: (name) => `Goose opens summer.games to sign in when the ${name} extension starts.`,
433
+ },
361
434
  mcp: {
362
435
  user: (ctx) => ctx.os === "win32"
363
436
  ? join(appData(ctx), "Block", "goose", "config", "config.yaml")
@@ -374,6 +447,10 @@ const SPECS = [
374
447
  status: "active",
375
448
  envOverride: "SUMMER_HERMES_CONFIG_FILE",
376
449
  format: "yaml-hermes",
450
+ remoteMcp: {
451
+ entry: (url) => ({ url, auth: "oauth" }),
452
+ signIn: (name) => `Run \`hermes mcp login ${name}\`, or sign in on summer.games when Hermes first connects.`,
453
+ },
377
454
  mcp: { user: home(".hermes", "config.yaml"), project: null },
378
455
  restart: "Run /reload-mcp in Hermes Agent (or restart it).",
379
456
  skills: skillDirs(home(".hermes", "skills"), SHARED_SKILLS_PROJECT, {
@@ -388,6 +465,10 @@ const SPECS = [
388
465
  status: "active",
389
466
  envOverride: "SUMMER_TRAE_CONFIG_FILE",
390
467
  format: "json",
468
+ remoteMcp: {
469
+ entry: (url) => ({ url }),
470
+ signIn: (name) => `Trae opens summer.games to sign in when ${name} connects.`,
471
+ },
391
472
  mcp: {
392
473
  user: null,
393
474
  project: project(".trae", "mcp.json"),
@@ -404,6 +485,10 @@ const SPECS = [
404
485
  status: "active",
405
486
  envOverride: "SUMMER_QWEN_CODE_CONFIG_FILE",
406
487
  format: "json",
488
+ remoteMcp: {
489
+ entry: (url) => ({ httpUrl: url }),
490
+ signIn: (name) => `In Qwen Code, run /mcp auth ${name} and sign in.`,
491
+ },
407
492
  mcp: { user: home(".qwen", "settings.json"), project: project(".qwen", "settings.json") },
408
493
  restart: "Restart Qwen Code or run /mcp in a new session.",
409
494
  skills: skillDirs(home(".qwen", "skills"), project(".qwen", "skills")),
@@ -415,6 +500,10 @@ const SPECS = [
415
500
  status: "active",
416
501
  envOverride: "SUMMER_KIMI_CODE_CONFIG_FILE",
417
502
  format: "json",
503
+ remoteMcp: {
504
+ entry: (url) => ({ url, auth: "oauth" }),
505
+ signIn: (name) => `Run \`kimi mcp auth ${name}\`; it opens summer.games to sign in.`,
506
+ },
418
507
  mcp: { user: home(".kimi-code", "mcp.json"), project: project(".kimi-code", "mcp.json") },
419
508
  restart: "Restart Kimi Code CLI so it reconnects its MCP servers.",
420
509
  skills: sharedSkills({ legacyDirs: { user: home(".kimi-code", "skills"), project: project(".kimi-code", "skills") } }),
@@ -426,6 +515,10 @@ const SPECS = [
426
515
  status: "active",
427
516
  envOverride: "SUMMER_CRUSH_CONFIG_FILE",
428
517
  format: "json-crush",
518
+ remoteMcp: {
519
+ entry: (url) => ({ type: "http", url, oauth: true }),
520
+ signIn: (name) => `Crush opens summer.games to sign in when ${name} connects.`,
521
+ },
429
522
  mcp: {
430
523
  user: home(".config", "crush", "crushrc"),
431
524
  project: project(".crushrc"),
@@ -440,6 +533,10 @@ const SPECS = [
440
533
  status: "active",
441
534
  envOverride: "SUMMER_AMP_CONFIG_FILE",
442
535
  format: "json-amp",
536
+ remoteMcp: {
537
+ entry: (url) => ({ url }),
538
+ signIn: (name) => `Amp opens summer.games to sign in for ${name} when it starts.`,
539
+ },
443
540
  mcp: { user: (ctx) => join(xdgOrAppData(ctx), "amp", "settings.json"), project: null },
444
541
  restart: "Restart Amp so it reloads settings.json.",
445
542
  skills: sharedSkills({ legacyDirs: { user: (ctx) => join(xdgOrAppData(ctx), "amp", "skills"), project: null } }),
@@ -451,6 +548,10 @@ const SPECS = [
451
548
  status: "active",
452
549
  envOverride: "SUMMER_FACTORY_CONFIG_FILE",
453
550
  format: "json-stdio",
551
+ remoteMcp: {
552
+ entry: (url) => ({ type: "http", url }),
553
+ signIn: (name) => `In Droid, run /mcp, choose ${name} and Authenticate.`,
554
+ },
454
555
  mcp: { user: home(".factory", "mcp.json"), project: project(".factory", "mcp.json") },
455
556
  restart: "Restart droid or run /mcp in the active session.",
456
557
  skills: sharedSkills({ legacyDirs: { user: home(".factory", "skills"), project: project(".factory", "skills") } }),
@@ -462,6 +563,10 @@ const SPECS = [
462
563
  status: "active",
463
564
  envOverride: "SUMMER_JUNIE_CONFIG_FILE",
464
565
  format: "json",
566
+ remoteMcp: {
567
+ entry: (url) => ({ url }),
568
+ signIn: (name) => `In Junie, run /mcp, open ${name} and choose Authorize.`,
569
+ },
465
570
  mcp: { user: home(".junie", "mcp", "mcp.json"), project: project(".junie", "mcp", "mcp.json") },
466
571
  restart: "Restart the JetBrains IDE so Junie reloads its MCP config.",
467
572
  skills: null,
@@ -474,6 +579,10 @@ const SPECS = [
474
579
  status: "active",
475
580
  envOverride: "SUMMER_WARP_CONFIG_FILE",
476
581
  format: "json",
582
+ remoteMcp: {
583
+ entry: (url) => ({ url }),
584
+ signIn: (name) => `Warp opens summer.games to sign in when ${name} starts.`,
585
+ },
477
586
  mcp: { user: home(".warp", ".mcp.json"), project: project(".warp", ".mcp.json") },
478
587
  restart: "Warp detects the file and spawns the server; check Settings > AI > MCP servers.",
479
588
  skills: sharedSkills({ legacyDirs: { user: home(".warp", "skills"), project: project(".warp", "skills") } }),
@@ -485,6 +594,7 @@ const SPECS = [
485
594
  status: "active",
486
595
  envOverride: "SUMMER_ROVO_DEV_CONFIG_FILE",
487
596
  format: "json-transport",
597
+ // No Summer Games entry: Rovo Dev CLI has no OAuth sign-in for remote MCP servers (headers only).
488
598
  mcp: { user: home(".rovodev", "mcp.json"), project: null },
489
599
  restart: "Restart Rovo Dev CLI so it reconnects its MCP servers.",
490
600
  skills: sharedSkills({ legacyDirs: { user: home(".rovodev", "skills"), project: project(".rovodev", "skills") } }),
@@ -496,6 +606,7 @@ const SPECS = [
496
606
  status: "active",
497
607
  envOverride: "SUMMER_QODER_CONFIG_FILE",
498
608
  format: "json",
609
+ // No Summer Games entry: Qoder's remote-server fields in settings.json are not documented.
499
610
  mcp: { user: home(".qoder", "settings.json"), project: project(".mcp.json") },
500
611
  restart: "Restart Qoder CLI so it reloads its MCP settings (the Qoder IDE manages servers in its UI).",
501
612
  skills: skillDirs(home(".qoder", "skills"), project(".qoder", "skills")),
@@ -507,6 +618,10 @@ const SPECS = [
507
618
  status: "active",
508
619
  envOverride: "SUMMER_GROK_BUILD_CONFIG_FILE",
509
620
  format: "toml",
621
+ remoteMcp: {
622
+ entry: (url) => ({ url }),
623
+ signIn: (name) => `In Grok Build, run /mcps, select ${name} and press i to sign in.`,
624
+ },
510
625
  mcp: { user: home(".grok", "config.toml"), project: project(".grok", "config.toml") },
511
626
  restart: "Restart Grok Build so it reloads config.toml (it also reads ~/.claude.json and .cursor/mcp.json).",
512
627
  skills: sharedSkills({ legacyDirs: { user: home(".grok", "skills"), project: project(".grok", "skills") } }),
@@ -518,6 +633,10 @@ const SPECS = [
518
633
  status: "active",
519
634
  envOverride: "SUMMER_MISTRAL_VIBE_CONFIG_FILE",
520
635
  format: "toml-array",
636
+ remoteMcp: {
637
+ entry: (url) => ({ name: "summer-games", transport: "streamable-http", url, auth: { type: "oauth", scopes: [] } }),
638
+ signIn: (name) => `In Vibe, run /mcp login ${name}.`,
639
+ },
521
640
  mcp: { user: home(".vibe", "config.toml"), project: project(".vibe", "config.toml") },
522
641
  restart: "Restart Vibe so it reloads config.toml.",
523
642
  skills: skillDirs(home(".vibe", "skills"), SHARED_SKILLS_PROJECT, { legacyDirs: { user: home(".vibe", "skills"), project: project(".vibe", "skills") } }),
@@ -529,6 +648,7 @@ const SPECS = [
529
648
  status: "active",
530
649
  envOverride: "SUMMER_LM_STUDIO_CONFIG_FILE",
531
650
  format: "json",
651
+ // No Summer Games entry: LM Studio's MCP client registration is closed and unverified.
532
652
  mcp: { user: home(".lmstudio", "mcp.json"), project: null },
533
653
  restart: "Open LM Studio, toggle on the summer-engine MCP server in the Program tab, and raise the loaded model's context length to 32k or higher.",
534
654
  skills: null,
@@ -40,7 +40,7 @@ async function creatorResult(operation) {
40
40
  }
41
41
  }
42
42
  export function registerCreatorTools(server) {
43
- server.tool("summer_export_game", "Export the game in the exact file the Summer Games store takes, with the installed Summer Engine headless (no window). format \"bundle\" (default): the summer.games .zip the Summer apps run on iPhone, Android, macOS and Windows; targets picks the platforms (a game without a server: ios and android only); upload it with summer_publish_build. format \"download\" with one target: web (an HTML5 .zip on Summer's WebGPU Forward+ web template, played on summer.games), macos (.app zip, macos-universal) or windows (.exe zip, windows-x64); these need the export template from summer_export_templates and are uploaded as store versions. Needs Summer Engine 0.7.0 or newer: an older engine is refused before anything is written (code engine_too_old). Works without the editor running. Returns path, sha256, size, the manifest or store platform, and warnings.", {
43
+ server.tool("summer_export_game", "Export the game in the exact file the Summer Games store takes, with the installed Summer Engine headless (no window). format \"bundle\" (default): the summer.games .zip the Summer apps run on iPhone, Android, macOS and Windows; targets picks the platforms (a game without a server: ios and android only); upload it with summer_publish_build. format \"download\" with one target: web (an HTML5 .zip on Summer's WebGPU Forward+ web template, played on summer.games), macos (.app zip, macos-universal) or windows (.exe zip, windows-x64); these need the export template from summer_export_templates and are uploaded as store versions. Needs Summer Engine 0.7.0 or newer: an older engine is refused before anything is written (code engine_too_old). A target the installed engine cannot build (android needs 0.7.1) is left out: the other targets export, and skippedTargets plus a warning name it. A game with a server declares its platforms in summer.build.json; when they differ from the exported targets a warning says so (summer_publish_build would be refused), and alignDeclaration:true aligns them. Works without the editor running. Returns path, sha256, size, the manifest or store platform, and warnings.", {
44
44
  project: z.string().optional().describe("Project folder with project.godot. Defaults to the MCP's bound project, then the working directory."),
45
45
  out: z.string().optional().describe("Output .zip path. Defaults to <project>/.summer/exports/."),
46
46
  targets: z
@@ -53,7 +53,11 @@ export function registerCreatorTools(server) {
53
53
  .describe("bundle (default): the summer.games store build. download: a web build or native download; the default when targets is [\"web\"]."),
54
54
  debug: z.boolean().default(false).describe("Export a debug build (--export-debug)."),
55
55
  timeoutSeconds: z.number().int().min(10).max(7200).optional().describe("Stop the engine after this many seconds (default 900; a first export imports every asset)."),
56
- }, async ({ project, out, targets, format, debug, timeoutSeconds }) => creatorResult(() => exportGame({ project, out, targets, format, debug, ...(timeoutSeconds ? { timeoutMs: timeoutSeconds * 1000 } : {}) })));
56
+ alignDeclaration: z
57
+ .boolean()
58
+ .optional()
59
+ .describe("A game with a server: set summer.build.json targetPlatforms to targets before exporting. The store refuses an upload whose declaration differs from the exported platforms (declaration_mismatch) and that spends the version."),
60
+ }, async ({ project, out, targets, format, debug, timeoutSeconds, alignDeclaration }) => creatorResult(() => exportGame({ project, out, targets, format, debug, alignDeclaration, ...(timeoutSeconds ? { timeoutMs: timeoutSeconds * 1000 } : {}) })));
57
61
  server.tool("summer_export_templates", "List, download and install Summer Engine export templates from Summer's CDN into the folder the installed engine reads, checking each file's sha256. Needed only for summer_export_game format \"download\": web (Summer's WebGPU Forward+ template), macos, windows. Store bundles (format \"bundle\") need no template. action list shows what is installed and published; action install fetches the platforms asked (default web).", {
58
62
  action: z.enum(["list", "install"]).describe("list: installed and published templates. install: download and install."),
59
63
  platforms: z
@@ -66,7 +70,7 @@ export function registerCreatorTools(server) {
66
70
  .optional()
67
71
  .describe("The installed Summer Engine version (Help > About, e.g. 0.7.1). Read automatically on macOS; needed on Windows and Linux."),
68
72
  }, async ({ action, platforms, includeDebug, summerVersion }) => creatorResult(() => exportTemplates({ action, platforms, includeDebug, summerVersion })));
69
- server.tool("summer_capture_gameplay", "Capture real gameplay frames without a running editor: starts the game in Summer Engine's offscreen verify instance (a real renderer, a window parked offscreen with no focus, never on screen), lets it run waitSeconds, and saves PNG frames of what the game draws, HUD included. Use for store screenshots (1920x1080 landscape or 1080x1920 portrait). With an editor open, summer_screenshot target game also works. Returns each frame's path, width and height; look at them before using them.", {
73
+ server.tool("summer_capture_gameplay", "Capture real gameplay frames without a running editor: starts the game in Summer Engine's offscreen verify instance (--summer-offscreen: a real renderer, a window parked offscreen with no focus, never on screen), lets it run waitSeconds, runs steps, and saves PNG frames of what the game draws, HUD included. Use for store screenshots (1920x1080 landscape or 1080x1920 portrait). The main scene is often a title menu: pass the game's own flags in args (after \"--\", e.g. [\"--autostart\"]), a gameplay scene, or steps that play like a player: [{press:\"Play\"}, {wait:3000}, {key:\"Space\"}, {action:\"jump\"}, {click:[960,540]}, {drag:{from:[400,800],to:[1400,800]}}, {shot:true}]. With a shot step, only shot steps save frames; without one, frames are saved after the steps. A step that cannot run (no such button, key or action) becomes a warning. With an editor open, summer_screenshot target game also works. Returns each frame's path, width and height; look at them before using them.", {
70
74
  project: z.string().optional().describe("Project folder with project.godot. Defaults to the MCP's bound project, then the working directory."),
71
75
  scene: z.string().optional().describe("Scene to start (res://... or uid://...). Default: the project's main scene, which is often a title menu; pass the gameplay scene for gameplay frames."),
72
76
  resolution: z.string().optional().describe("Window size WIDTHxHEIGHT (default 1920x1080; 1080x1920 for portrait)."),
@@ -74,6 +78,31 @@ export function registerCreatorTools(server) {
74
78
  waitSeconds: z.number().min(0).max(120).optional().describe("Seconds the game runs before the first frame (default 3)."),
75
79
  intervalSeconds: z.number().min(0.1).max(60).optional().describe("Seconds between frames (default 1)."),
76
80
  out: z.string().optional().describe("Output folder. Defaults to <project>/.summer/captures/<time>/ (ignored by git)."),
81
+ args: z
82
+ .array(z.string())
83
+ .max(32)
84
+ .optional()
85
+ .describe('The game\'s own command-line flags, passed after "--" (OS.get_cmdline_user_args()), e.g. ["--autostart", "--solo"] to skip the title screen.'),
86
+ steps: z
87
+ .array(z
88
+ .object({
89
+ press: z.string().optional().describe("Press the visible button with this text (or node name); waits up to timeoutSeconds (default 10) for it."),
90
+ timeoutSeconds: z.number().min(0).max(60).optional(),
91
+ key: z.string().optional().describe("Press a key by Godot name: Space, Enter, Escape, A, Up."),
92
+ action: z.string().optional().describe("Press an input action from the project's InputMap, e.g. jump."),
93
+ holdMs: z.number().min(0).max(10000).optional().describe("How long key or action is held (default 100)."),
94
+ click: z.tuple([z.number(), z.number()]).optional().describe("Left-click at [x, y] in frame pixels."),
95
+ drag: z
96
+ .object({ from: z.tuple([z.number(), z.number()]), to: z.tuple([z.number(), z.number()]), ms: z.number().min(0).max(10000).optional() })
97
+ .optional()
98
+ .describe("Drag with the left button from [x, y] to [x, y] over ms (default 300)."),
99
+ wait: z.number().min(0).max(60000).optional().describe("Wait this many milliseconds."),
100
+ shot: z.boolean().optional().describe("true: save a frame now."),
101
+ })
102
+ .strict())
103
+ .max(50)
104
+ .optional()
105
+ .describe("What to do after waitSeconds, in order; exactly one of press, key, action, click, drag, wait, shot per step."),
77
106
  }, async (args) => creatorResult(() => captureGameplay(args)));
78
107
  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).", {
79
108
  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."),