gameye-mcp 0.3.2 → 0.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/dist/env.js CHANGED
@@ -3,10 +3,20 @@
3
3
  // The Gameye environment is a property of the server process, never of a
4
4
  // single tool call: an agent cannot switch to production mid-conversation.
5
5
  // Users who need both environments run two named server entries.
6
+ import { parseDurationMs } from "./limits.js";
6
7
  export const KNOWN_HOSTS = {
7
8
  sandbox: "https://api.sandbox-gameye.gameye.net",
8
9
  production: "https://api.production-gameye.gameye.net",
9
10
  };
11
+ export const DEFAULT_LIMITS = {
12
+ maxSessions: 5,
13
+ maxTtl: "4h",
14
+ maxTtlMs: 4 * 3_600_000,
15
+ maxLaunches: 20,
16
+ stopOwnOnly: false,
17
+ };
18
+ /** Shortest GAMEYE_MAX_TTL: the default TTL a launch without one gets (DEFAULT_SESSION_TTL). */
19
+ const MIN_MAX_TTL_MS = 30 * 60_000;
10
20
  /** Per-request timeout used when GAMEYE_TIMEOUT_MS is not set. */
11
21
  export const DEFAULT_TIMEOUT_MS = 30_000;
12
22
  export class ConfigError extends Error {
@@ -20,6 +30,20 @@ function nonEmpty(value) {
20
30
  const trimmed = value?.trim();
21
31
  return trimmed ? trimmed : undefined;
22
32
  }
33
+ /** A whole value like `${user_config.api_token}`: a host placeholder that was never filled in. */
34
+ const UNEXPANDED_PLACEHOLDER = /^\$\{[^}]*\}$/;
35
+ /**
36
+ * The configured token, or undefined when it is missing, blank or an unexpanded
37
+ * host placeholder. Any of those starts the server in no-token mode.
38
+ */
39
+ function resolveToken(raw) {
40
+ const value = nonEmpty(raw);
41
+ if (value === undefined || UNEXPANDED_PLACEHOLDER.test(value))
42
+ return undefined;
43
+ return value;
44
+ }
45
+ /** The one stderr line printed at start in no-token mode. Never mentions a token value. */
46
+ export const NO_TOKEN_NOTICE = "No GAMEYE_API_TOKEN set: account tools will refuse until a token is set and the server is restarted.";
23
47
  function parseTimeoutMs(raw) {
24
48
  const value = nonEmpty(raw);
25
49
  if (value === undefined)
@@ -30,6 +54,35 @@ function parseTimeoutMs(raw) {
30
54
  }
31
55
  return ms;
32
56
  }
57
+ function parsePositiveInt(name, raw, fallback) {
58
+ const value = nonEmpty(raw);
59
+ if (value === undefined)
60
+ return fallback;
61
+ const n = /^\d+$/.test(value) ? Number(value) : NaN;
62
+ if (!Number.isSafeInteger(n) || n <= 0) {
63
+ throw new ConfigError(`${name} must be a positive whole number (got "${value}"). Leave it unset for ${fallback}.`);
64
+ }
65
+ return n;
66
+ }
67
+ function parseLimits(env) {
68
+ const maxSessions = parsePositiveInt("GAMEYE_MAX_SESSIONS", env.GAMEYE_MAX_SESSIONS, DEFAULT_LIMITS.maxSessions);
69
+ const maxLaunches = parsePositiveInt("GAMEYE_MAX_LAUNCHES", env.GAMEYE_MAX_LAUNCHES, DEFAULT_LIMITS.maxLaunches);
70
+ let { maxTtl, maxTtlMs } = DEFAULT_LIMITS;
71
+ const rawTtl = nonEmpty(env.GAMEYE_MAX_TTL);
72
+ if (rawTtl !== undefined) {
73
+ const ms = parseDurationMs(rawTtl);
74
+ if (ms === undefined || ms < MIN_MAX_TTL_MS) {
75
+ throw new ConfigError(`GAMEYE_MAX_TTL must be a duration of at least 30m, like 2h or 90m (got "${rawTtl}"). Leave it unset for ${DEFAULT_LIMITS.maxTtl}.`);
76
+ }
77
+ maxTtl = rawTtl;
78
+ maxTtlMs = ms;
79
+ }
80
+ const rawOwn = nonEmpty(env.GAMEYE_STOP_OWN_ONLY);
81
+ if (rawOwn !== undefined && rawOwn !== "1" && rawOwn !== "0") {
82
+ throw new ConfigError(`GAMEYE_STOP_OWN_ONLY must be 1 or 0 (got "${rawOwn}"). Leave it unset to allow stopping any session the token can see.`);
83
+ }
84
+ return { maxSessions, maxTtl, maxTtlMs, maxLaunches, stopOwnOnly: rawOwn === "1" };
85
+ }
33
86
  /** Lowercase a hostname and drop a trailing root dot (`example.com.` is the same host as `example.com`). */
34
87
  function normalizeHostname(hostname) {
35
88
  return hostname.toLowerCase().replace(/\.$/, "");
@@ -48,18 +101,17 @@ function matchKnownTier(url) {
48
101
  /**
49
102
  * Resolve the server configuration from an environment object.
50
103
  * Pure: throws ConfigError on any invalid combination and never touches process state.
104
+ * A missing token is not an error: it resolves with `hasToken: false`.
51
105
  */
52
106
  export function resolveConfig(env) {
53
- const token = nonEmpty(env.GAMEYE_API_TOKEN);
54
- if (!token) {
55
- throw new ConfigError("GAMEYE_API_TOKEN is required. Set it in your MCP server configuration:\n" +
56
- ' "env": { "GAMEYE_API_TOKEN": "<your token>" }');
57
- }
107
+ const token = resolveToken(env.GAMEYE_API_TOKEN);
108
+ const auth = token === undefined ? { hasToken: false } : { token, hasToken: true };
58
109
  if (nonEmpty(env.GAMEYE_API_ENDPOINT)) {
59
110
  throw new ConfigError("GAMEYE_API_ENDPOINT is no longer supported. Use GAMEYE_ENV=sandbox|production " +
60
111
  "(default sandbox), or GAMEYE_API_URL for a non-standard https host.");
61
112
  }
62
113
  const timeoutMs = parseTimeoutMs(env.GAMEYE_TIMEOUT_MS);
114
+ const limits = parseLimits(env);
63
115
  const rawEnv = nonEmpty(env.GAMEYE_ENV);
64
116
  let tier = "sandbox";
65
117
  if (rawEnv !== undefined) {
@@ -71,7 +123,7 @@ export function resolveConfig(env) {
71
123
  const rawUrl = nonEmpty(env.GAMEYE_API_URL);
72
124
  if (rawUrl === undefined) {
73
125
  const baseUrl = KNOWN_HOSTS[tier];
74
- return { environment: tier, baseUrl, host: new URL(baseUrl).host, token, timeoutMs };
126
+ return { environment: tier, baseUrl, host: new URL(baseUrl).host, ...auth, timeoutMs, limits };
75
127
  }
76
128
  let url;
77
129
  try {
@@ -90,10 +142,10 @@ export function resolveConfig(env) {
90
142
  `Set GAMEYE_ENV=${knownTier} to use ${knownTier}, or remove GAMEYE_API_URL.`);
91
143
  }
92
144
  const baseUrl = KNOWN_HOSTS[knownTier];
93
- return { environment: knownTier, baseUrl, host: new URL(baseUrl).host, token, timeoutMs };
145
+ return { environment: knownTier, baseUrl, host: new URL(baseUrl).host, ...auth, timeoutMs, limits };
94
146
  }
95
147
  const baseUrl = `${url.origin}${url.pathname}`.replace(/\/+$/, "");
96
- return { environment: "custom", baseUrl, host: url.host, token, timeoutMs };
148
+ return { environment: "custom", baseUrl, host: url.host, ...auth, timeoutMs, limits };
97
149
  }
98
150
  /** Resolve from process.env, printing a clear message and exiting 1 on any error. */
99
151
  export function loadConfigOrExit(env = process.env) {
@@ -110,5 +162,5 @@ export function loadConfigOrExit(env = process.env) {
110
162
  }
111
163
  /** The line every tool result starts with, e.g. `[sandbox · api.sandbox-gameye.gameye.net]`. */
112
164
  export function resultPrefix(config) {
113
- return `[${config.environment} · ${config.host}]`;
165
+ return `[${config.environment} · ${config.host}${config.hasToken === false ? " · no token" : ""}]`;
114
166
  }
@@ -0,0 +1,39 @@
1
+ export interface BundledSkill {
2
+ /** Directory and frontmatter name, e.g. "gameye-containerize". */
3
+ name: string;
4
+ description: string;
5
+ /** The skill's own metadata.version. */
6
+ version: string;
7
+ /** "SKILL.md" and "references/<file>.md", keyed by path relative to the skill. */
8
+ files: Readonly<Record<string, string>>;
9
+ }
10
+ export interface ContainerContractItem {
11
+ /** The item's number in gameye-containerize "The container contract". */
12
+ number: number;
13
+ text: string;
14
+ }
15
+ export declare const TEMPLATE_ENGINES: readonly ["unreal", "unity", "godot", "web"];
16
+ export declare const TEMPLATE_KINDS: readonly ["dockerfile", "shutdown-hook"];
17
+ export type TemplateEngine = (typeof TEMPLATE_ENGINES)[number];
18
+ export type TemplateKind = (typeof TEMPLATE_KINDS)[number];
19
+ export interface BundledTemplate {
20
+ engine: TemplateEngine;
21
+ kind: TemplateKind;
22
+ /** The engine skill that publishes it, e.g. "gameye-godot-server". */
23
+ skill: string;
24
+ /** The heading of the section the markers sit in. */
25
+ section: string;
26
+ /** The fence's info string, e.g. "dockerfile". */
27
+ language: string;
28
+ /** The fenced block's content exactly as published, without the fence lines. */
29
+ code: string;
30
+ /** Prose inside the marker pair beside the fence, e.g. the engine version it was tested on. */
31
+ note?: string;
32
+ }
33
+ export declare const BUNDLED_SKILLS_PACK_VERSION = "1.7.0";
34
+ export declare const BUNDLED_SKILLS_TAG = "v1.7.0";
35
+ export declare const BUNDLED_SKILLS_COMMIT = "aeb5b8fbe1251b89b6368de8fa48a39850de93a1";
36
+ export declare const BUNDLED_SKILLS: readonly BundledSkill[];
37
+ export declare const CONTAINER_CONTRACT: readonly ContainerContractItem[];
38
+ /** Template blocks marked <!-- gameye-template: engine/kind --> in the pinned engine skills. */
39
+ export declare const BUNDLED_TEMPLATES: readonly BundledTemplate[];