@skilder-ai/runtime 0.9.24 → 0.9.25

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/README.md CHANGED
@@ -45,7 +45,9 @@ When `REMOTE_PORT` is set, the runtime exposes `GET /health`:
45
45
  - **`200 { status: 'ok' }`** — NATS connected, an active round-trip probe (`flush`, 2s timeout) succeeded, and the runtime is authenticated
46
46
  - **`503 { status: 'error', message: '...' }`** — NATS disconnected, NATS unreachable (flush timed out), not authenticated, or an unexpected error occurred
47
47
 
48
- > **Note:** The health endpoint is only available when `REMOTE_PORT` is set. CLI/standalone mode (no `REMOTE_PORT`) does not start an HTTP server this is intentional.
48
+ Health is independent of the remote MCP endpoints: a runtime that serves no MCP endpoint (no `MCP_REMOTE_ENABLED`) is still healthcheckable. The container image defaults `REMOTE_PORT` to `3001`, so every runtime container has a working healthcheck out of the box.
49
+
50
+ > **Note:** `npx`-installed runtimes bind no port unless you set `REMOTE_PORT` yourself, and stdio mode (`USER_KEY`) never starts an HTTP server — it is spawned per client, so a shared port would collide.
49
51
 
50
52
  ### Docker Compose setup
51
53
 
@@ -54,7 +56,8 @@ services:
54
56
  runtime:
55
57
  image: your-runtime-image
56
58
  environment:
57
- REMOTE_PORT: "3001"
59
+ REMOTE_PORT: "3001" # web service (health); the image defaults this
60
+ MCP_REMOTE_ENABLED: "true" # also serve the remote MCP endpoints on it
58
61
  RUNTIME_KEY: "<RUNTIME_API_KEY>"
59
62
  NATS_SERVERS: "nats:4222"
60
63
  healthcheck:
package/dist/index.js CHANGED
@@ -87816,6 +87816,23 @@ function parsePositiveIntEnv(name21, fallback) {
87816
87816
  }
87817
87817
  return parsed;
87818
87818
  }
87819
+ function parseOptionalPositiveIntEnv(name21) {
87820
+ const raw = process.env[name21];
87821
+ if (raw === void 0 || raw === "") return void 0;
87822
+ return parsePositiveIntEnv(name21, 0);
87823
+ }
87824
+ var TRUE_VALUES = ["true", "1", "yes", "on"];
87825
+ var FALSE_VALUES = ["false", "0", "no", "off"];
87826
+ function parseBooleanEnv(name21, fallback) {
87827
+ const raw = process.env[name21];
87828
+ if (raw === void 0 || raw === "") return fallback;
87829
+ const normalized = raw.trim().toLowerCase();
87830
+ if (TRUE_VALUES.includes(normalized)) return true;
87831
+ if (FALSE_VALUES.includes(normalized)) return false;
87832
+ throw new Error(
87833
+ `Invalid ${name21}: "${raw}". Must be one of ${[...TRUE_VALUES, ...FALSE_VALUES].join(", ")}.`
87834
+ );
87835
+ }
87819
87836
 
87820
87837
  // ../common/src/constants.node.ts
87821
87838
  var MCP_TOOL_EXECUTION_TIMEOUT = parseInt(process.env.MCP_TOOL_EXECUTION_TIMEOUT ?? "60000", 10);
@@ -152767,6 +152784,16 @@ McpStdioService = __decorate15([
152767
152784
  // src/services/fastify.manager.service.ts
152768
152785
  var import_fastify = __toESM(require_fastify());
152769
152786
  var import_cors = __toESM(require_cors());
152787
+
152788
+ // src/config/server.ts
152789
+ function remotePort() {
152790
+ return parseOptionalPositiveIntEnv("REMOTE_PORT");
152791
+ }
152792
+ function mcpRemoteEnabled() {
152793
+ return parseBooleanEnv("MCP_REMOTE_ENABLED", false);
152794
+ }
152795
+
152796
+ // src/services/fastify.manager.service.ts
152770
152797
  var __decorate16 = function(decorators, target, key, desc) {
152771
152798
  var c3 = arguments.length, r2 = c3 < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d2;
152772
152799
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r2 = Reflect.decorate(decorators, target, key, desc);
@@ -152796,14 +152823,14 @@ var FastifyManagerService = class FastifyManagerService2 extends LifecycleServic
152796
152823
  this.logger = this.loggerService.getLogger(this.name);
152797
152824
  }
152798
152825
  async initialize() {
152799
- const remotePort = process.env.REMOTE_PORT;
152800
- if (!remotePort) {
152826
+ const port = remotePort();
152827
+ if (!port) {
152801
152828
  this.logger.warn("REMOTE_PORT is not set, Fastify manager will not start");
152802
152829
  return;
152803
152830
  }
152804
152831
  this.logger.info("Starting Fastify manager service");
152805
152832
  await this.setupFastifyInstance();
152806
- this.port = parseInt(remotePort);
152833
+ this.port = port;
152807
152834
  }
152808
152835
  /**
152809
152836
  * Start listening on the configured port.
@@ -152825,6 +152852,10 @@ var FastifyManagerService = class FastifyManagerService2 extends LifecycleServic
152825
152852
  this.isListening = true;
152826
152853
  this.logger.info(`Fastify server listening on port ${this.port}`);
152827
152854
  } catch (error48) {
152855
+ if (error48?.code === "EADDRINUSE") {
152856
+ this.logger.error({ event: "fastify_listen_failed", reason: "port_in_use", port: this.port }, `Port ${this.port} is already in use. Set REMOTE_PORT to a free port, or unset it to run without a web service.`);
152857
+ throw error48;
152858
+ }
152828
152859
  this.logger.error(`Failed to start Fastify server: ${error48}`);
152829
152860
  throw error48;
152830
152861
  }
@@ -152835,6 +152866,7 @@ var FastifyManagerService = class FastifyManagerService2 extends LifecycleServic
152835
152866
  await this.fastifyInstance.close();
152836
152867
  this.fastifyInstance = void 0;
152837
152868
  }
152869
+ this.isListening = false;
152838
152870
  }
152839
152871
  /**
152840
152872
  * Returns the shared Fastify instance for route registration
@@ -162087,7 +162119,8 @@ function validateAndDetectMode() {
162087
162119
  const userKey = process.env.USER_KEY;
162088
162120
  const runtimeKey = process.env.RUNTIME_KEY;
162089
162121
  const runtimeName = process.env.RUNTIME_NAME;
162090
- const remotePort = process.env.REMOTE_PORT;
162122
+ const mcpRemote = mcpRemoteEnabled();
162123
+ const port = userKey ? void 0 : remotePort();
162091
162124
  const keyVariables = [systemKey, userKey, runtimeKey];
162092
162125
  const keyVariablesSet = keyVariables.filter((key) => !!key);
162093
162126
  if (keyVariablesSet.length > 1) {
@@ -162100,26 +162133,32 @@ function validateAndDetectMode() {
162100
162133
  throw new Error("Invalid configuration: SYSTEM_KEY requires RUNTIME_NAME");
162101
162134
  }
162102
162135
  }
162103
- if (remotePort && userKey) {
162136
+ if (mcpRemote && userKey) {
162104
162137
  throw new Error(
162105
- "Invalid configuration: REMOTE_PORT is mutually exclusive with USER_KEY. Please use only REMOTE_PORT for edge runtimes"
162138
+ "Invalid configuration: MCP_REMOTE_ENABLED is mutually exclusive with USER_KEY. USER_KEY serves MCP over stdio; use RUNTIME_KEY or RUNTIME_NAME for remote MCP."
162106
162139
  );
162107
162140
  }
162141
+ if (mcpRemote && !port) {
162142
+ throw new Error("Invalid configuration: MCP_REMOTE_ENABLED requires REMOTE_PORT, which is where the MCP endpoints are served.");
162143
+ }
162108
162144
  if ((runtimeName || runtimeKey) && userKey) {
162109
162145
  throw new Error("Invalid configuration: trying to start both a runtime and a user, this is not supported");
162110
162146
  }
162147
+ if (userKey && process.env.REMOTE_PORT) {
162148
+ console.warn("REMOTE_PORT is ignored in stdio mode (USER_KEY): stdio runtimes do not start a web service");
162149
+ }
162111
162150
  if (userKey) {
162112
162151
  return "MCP_STDIO";
162113
162152
  } else if (runtimeName || runtimeKey) {
162114
- if (remotePort) {
162153
+ if (mcpRemote) {
162115
162154
  return "EDGE_MCP_STREAM";
162116
162155
  }
162117
162156
  return "EDGE";
162118
- } else if (remotePort) {
162157
+ } else if (mcpRemote) {
162119
162158
  return "STANDALONE_MCP_STREAM";
162120
162159
  } else {
162121
162160
  throw new Error(
162122
- "Invalid configuration: At least one of USER_KEY, RUNTIME_NAME, RUNTIME_KEY, or REMOTE_PORT must be set. See documentation for valid operational modes."
162161
+ "Invalid configuration: At least one of USER_KEY, RUNTIME_NAME, RUNTIME_KEY, or MCP_REMOTE_ENABLED must be set. See documentation for valid operational modes."
162123
162162
  );
162124
162163
  }
162125
162164
  }
@@ -162129,19 +162168,22 @@ var start = () => {
162129
162168
  const runtimeExecutionId = (0, import_crypto3.randomBytes)(16).toString("hex");
162130
162169
  container.bind(RUNTIME_EXECUTION_ID).toConstantValue(runtimeExecutionId);
162131
162170
  container.bind(AuthService).toSelf().inSingletonScope();
162171
+ const webService = mode !== "MCP_STDIO" && remotePort() !== void 0;
162172
+ if (webService) {
162173
+ container.bind(FastifyManagerService).toSelf().inSingletonScope();
162174
+ } else {
162175
+ container.bind(FastifyManagerService).toConstantValue(void 0);
162176
+ }
162132
162177
  if (mode === "MCP_STDIO") {
162133
162178
  container.bind(McpStdioService).toSelf().inSingletonScope();
162134
- container.bind(FastifyManagerService).toConstantValue(void 0);
162135
162179
  container.bind(McpSseService).toConstantValue(void 0);
162136
162180
  container.bind(McpStreamableService).toConstantValue(void 0);
162137
162181
  } else if (mode === "EDGE_MCP_STREAM" || mode === "STANDALONE_MCP_STREAM") {
162138
- container.bind(FastifyManagerService).toSelf().inSingletonScope();
162139
162182
  container.bind(McpSseService).toSelf().inSingletonScope();
162140
162183
  container.bind(McpStreamableService).toSelf().inSingletonScope();
162141
162184
  container.bind(McpStdioService).toConstantValue(void 0);
162142
162185
  } else {
162143
162186
  container.bind(McpStdioService).toConstantValue(void 0);
162144
- container.bind(FastifyManagerService).toConstantValue(void 0);
162145
162187
  container.bind(McpSseService).toConstantValue(void 0);
162146
162188
  container.bind(McpStreamableService).toConstantValue(void 0);
162147
162189
  }