@agiflowai/one-mcp 0.3.14 → 0.3.16

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.
@@ -1524,23 +1524,11 @@ var McpClientManagerService = class {
1524
1524
  clients = /* @__PURE__ */ new Map();
1525
1525
  serverConfigs = /* @__PURE__ */ new Map();
1526
1526
  connectionPromises = /* @__PURE__ */ new Map();
1527
- constructor() {
1528
- process.on("exit", () => {
1529
- this.cleanupOnExit();
1530
- });
1531
- process.on("SIGINT", () => {
1532
- this.cleanupOnExit();
1533
- process.exit(0);
1534
- });
1535
- process.on("SIGTERM", () => {
1536
- this.cleanupOnExit();
1537
- process.exit(0);
1538
- });
1539
- }
1540
1527
  /**
1541
- * Cleanup all resources on exit (child processes)
1528
+ * Synchronously kill all stdio MCP server child processes.
1529
+ * Must be called by the owner (e.g. transport/command layer) during shutdown.
1542
1530
  */
1543
- cleanupOnExit() {
1531
+ cleanupChildProcesses() {
1544
1532
  for (const [serverName, client] of this.clients) try {
1545
1533
  const childProcess = client["childProcess"];
1546
1534
  if (childProcess && !childProcess.killed) {
@@ -2808,7 +2796,7 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
2808
2796
 
2809
2797
  //#endregion
2810
2798
  //#region package.json
2811
- var version = "0.3.13";
2799
+ var version = "0.3.15";
2812
2800
 
2813
2801
  //#endregion
2814
2802
  //#region src/server/index.ts
@@ -2819,6 +2807,7 @@ var version = "0.3.13";
2819
2807
  * - Factory pattern for server creation
2820
2808
  * - Tool registration pattern
2821
2809
  * - Dependency injection for services
2810
+ * - Shared services pattern for multi-session HTTP transport
2822
2811
  *
2823
2812
  * CODING STANDARDS:
2824
2813
  * - Register all tools, resources, and prompts here
@@ -2922,7 +2911,13 @@ function buildProxyInstructions(serverDefinitions, mode, includeSkillsTool) {
2922
2911
  "Use describe_tools to inspect capabilities and use_tool to execute them."
2923
2912
  ].join("\n\n");
2924
2913
  }
2925
- async function createServer(options) {
2914
+ /**
2915
+ * Initialize shared services and tools once for use across multiple sessions.
2916
+ * Use with createSessionServer() for HTTP transport where multiple agents
2917
+ * connect concurrently. This avoids duplicating downstream connections,
2918
+ * file watchers, caches, and tool instances per session.
2919
+ */
2920
+ async function initializeSharedServices(options) {
2926
2921
  const clientManager = new McpClientManagerService();
2927
2922
  let configSkills;
2928
2923
  let configId;
@@ -2995,13 +2990,47 @@ async function createServer(options) {
2995
2990
  definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
2996
2991
  }
2997
2992
  else definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
2993
+ const proxyMode = options?.proxyMode || "meta";
2998
2994
  const describeTools = new DescribeToolsTool(clientManager, skillService, serverId, definitionsCacheService);
2999
- const useToolWithCache = new UseToolTool(clientManager, skillService, serverId, definitionsCacheService);
2995
+ const useTool = new UseToolTool(clientManager, skillService, serverId, definitionsCacheService);
3000
2996
  const searchListTools = new SearchListToolsTool(clientManager, definitionsCacheService);
3001
2997
  toolsRef.describeTools = describeTools;
3002
- const serverDefinitions = await definitionsCacheService.getServerDefinitions();
3003
- const includeSkillsTool = await hasAnySkills(definitionsCacheService, skillService);
3004
- const proxyMode = options?.proxyMode || "meta";
2998
+ if (skillService) skillService.startWatching().catch((error) => {
2999
+ console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
3000
+ });
3001
+ if (!shouldStartFromCache && effectiveDefinitionsCachePath && options?.configFilePath) definitionsCacheService.collectForCache({
3002
+ configPath: options.configFilePath,
3003
+ configHash,
3004
+ oneMcpVersion: version,
3005
+ serverId
3006
+ }).then((definitionsCache) => DefinitionsCacheService.writeToFile(effectiveDefinitionsCachePath, definitionsCache)).then(() => {
3007
+ console.error(`[definitions-cache] Wrote ${effectiveDefinitionsCachePath}`);
3008
+ }).catch((error) => {
3009
+ console.error(`[definitions-cache] Failed to persist ${effectiveDefinitionsCachePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
3010
+ });
3011
+ const dispose = async () => {
3012
+ await clientManager.disconnectAll();
3013
+ if (skillService) skillService.stopWatching();
3014
+ };
3015
+ return {
3016
+ clientManager,
3017
+ definitionsCacheService,
3018
+ skillService,
3019
+ describeTools,
3020
+ useTool,
3021
+ searchListTools,
3022
+ serverId,
3023
+ proxyMode,
3024
+ dispose
3025
+ };
3026
+ }
3027
+ /**
3028
+ * Create a lightweight per-session MCP Server instance that delegates
3029
+ * to shared services and tools. Use with initializeSharedServices()
3030
+ * for multi-session HTTP transport.
3031
+ */
3032
+ async function createSessionServer(shared) {
3033
+ const { clientManager, definitionsCacheService, skillService, describeTools, useTool: useToolWithCache, searchListTools, serverId, proxyMode } = shared;
3005
3034
  const server = new __modelcontextprotocol_sdk_server_index_js.Server({
3006
3035
  name: "@agiflowai/one-mcp",
3007
3036
  version: "0.1.0"
@@ -3011,10 +3040,7 @@ async function createServer(options) {
3011
3040
  resources: {},
3012
3041
  prompts: {}
3013
3042
  },
3014
- instructions: buildProxyInstructions(serverDefinitions, proxyMode, includeSkillsTool)
3015
- });
3016
- if (skillService) skillService.startWatching().catch((error) => {
3017
- console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
3043
+ instructions: buildProxyInstructions(await definitionsCacheService.getServerDefinitions(), proxyMode, await hasAnySkills(definitionsCacheService, skillService))
3018
3044
  });
3019
3045
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: proxyMode === "flat" ? await (async () => {
3020
3046
  const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
@@ -3051,14 +3077,14 @@ async function createServer(options) {
3051
3077
  throw new Error(`Unknown tool: ${name}`);
3052
3078
  });
3053
3079
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListResourcesRequestSchema, async () => {
3054
- const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
3080
+ const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
3055
3081
  const resourceToServers = /* @__PURE__ */ new Map();
3056
- for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
3082
+ for (const serverDefinition of currentServerDefinitions) for (const resource of serverDefinition.resources) {
3057
3083
  if (!resourceToServers.has(resource.uri)) resourceToServers.set(resource.uri, []);
3058
3084
  resourceToServers.get(resource.uri)?.push(serverDefinition.serverName);
3059
3085
  }
3060
3086
  const resources = [];
3061
- for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
3087
+ for (const serverDefinition of currentServerDefinitions) for (const resource of serverDefinition.resources) {
3062
3088
  const hasClash = (resourceToServers.get(resource.uri) || []).length > 1;
3063
3089
  resources.push({
3064
3090
  ...resource,
@@ -3077,10 +3103,10 @@ async function createServer(options) {
3077
3103
  return await (await clientManager.ensureConnected(matchingServers[0])).readResource(actualUri);
3078
3104
  });
3079
3105
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListPromptsRequestSchema, async () => {
3080
- const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
3106
+ const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
3081
3107
  const promptToServers = /* @__PURE__ */ new Map();
3082
3108
  const serverPromptsMap = /* @__PURE__ */ new Map();
3083
- for (const serverDefinition of serverDefinitions$1) {
3109
+ for (const serverDefinition of currentServerDefinitions) {
3084
3110
  serverPromptsMap.set(serverDefinition.serverName, serverDefinition.prompts);
3085
3111
  for (const prompt of serverDefinition.prompts) {
3086
3112
  if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
@@ -3088,7 +3114,7 @@ async function createServer(options) {
3088
3114
  }
3089
3115
  }
3090
3116
  const aggregatedPrompts = [];
3091
- for (const serverDefinition of serverDefinitions$1) {
3117
+ for (const serverDefinition of currentServerDefinitions) {
3092
3118
  const prompts = serverPromptsMap.get(serverDefinition.serverName) || [];
3093
3119
  for (const prompt of prompts) {
3094
3120
  const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
@@ -3103,29 +3129,26 @@ async function createServer(options) {
3103
3129
  });
3104
3130
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.GetPromptRequestSchema, async (request) => {
3105
3131
  const { name, arguments: args } = request.params;
3106
- const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
3132
+ const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
3107
3133
  const { serverName, actualToolName: actualPromptName } = parseToolName(name);
3108
3134
  if (serverName) return await (await clientManager.ensureConnected(serverName)).getPrompt(actualPromptName, args);
3109
3135
  const serversWithPrompt = [];
3110
- for (const serverDefinition of serverDefinitions$1) if (serverDefinition.prompts.some((prompt) => prompt.name === name)) serversWithPrompt.push(serverDefinition.serverName);
3136
+ for (const serverDefinition of currentServerDefinitions) if (serverDefinition.prompts.some((prompt) => prompt.name === name)) serversWithPrompt.push(serverDefinition.serverName);
3111
3137
  if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
3112
3138
  if (serversWithPrompt.length > 1) throw new Error(`Prompt "${name}" exists on multiple servers: ${serversWithPrompt.join(", ")}. Use the prefixed format (e.g., "${serversWithPrompt[0]}__${name}") to specify which server to use.`);
3113
3139
  const client = clientManager.getClient(serversWithPrompt[0]);
3114
3140
  if (!client) return await (await clientManager.ensureConnected(serversWithPrompt[0])).getPrompt(name, args);
3115
3141
  return await client.getPrompt(name, args);
3116
3142
  });
3117
- if (!shouldStartFromCache && effectiveDefinitionsCachePath && options?.configFilePath) definitionsCacheService.collectForCache({
3118
- configPath: options.configFilePath,
3119
- configHash,
3120
- oneMcpVersion: version,
3121
- serverId
3122
- }).then((definitionsCache) => DefinitionsCacheService.writeToFile(effectiveDefinitionsCachePath, definitionsCache)).then(() => {
3123
- console.error(`[definitions-cache] Wrote ${effectiveDefinitionsCachePath}`);
3124
- }).catch((error) => {
3125
- console.error(`[definitions-cache] Failed to persist ${effectiveDefinitionsCachePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
3126
- });
3127
3143
  return server;
3128
3144
  }
3145
+ /**
3146
+ * Create a single MCP server instance (backward-compatible wrapper).
3147
+ * For multi-session HTTP transport, use initializeSharedServices() + createSessionServer() instead.
3148
+ */
3149
+ async function createServer(options) {
3150
+ return createSessionServer(await initializeSharedServices(options));
3151
+ }
3129
3152
 
3130
3153
  //#endregion
3131
3154
  //#region src/types/index.ts
@@ -3176,16 +3199,26 @@ var HttpFullSessionManager = class {
3176
3199
  server
3177
3200
  });
3178
3201
  }
3179
- deleteSession(sessionId) {
3202
+ async deleteSession(sessionId) {
3180
3203
  const session = this.sessions.get(sessionId);
3181
- if (session) session.server.close();
3204
+ if (session) try {
3205
+ await session.server.close();
3206
+ } catch (error) {
3207
+ throw new Error(`Failed to close MCP server for session '${sessionId}': ${toErrorMessage$2(error)}`);
3208
+ }
3182
3209
  this.sessions.delete(sessionId);
3183
3210
  }
3184
3211
  hasSession(sessionId) {
3185
3212
  return this.sessions.has(sessionId);
3186
3213
  }
3187
- clear() {
3188
- for (const session of this.sessions.values()) session.server.close();
3214
+ async clear() {
3215
+ try {
3216
+ await Promise.all(Array.from(this.sessions.values()).map(async (session) => {
3217
+ await session.server.close();
3218
+ }));
3219
+ } catch (error) {
3220
+ throw new Error(`Failed to clear sessions: ${toErrorMessage$2(error)}`);
3221
+ }
3189
3222
  this.sessions.clear();
3190
3223
  }
3191
3224
  };
@@ -3226,7 +3259,7 @@ var HttpTransportHandler = class {
3226
3259
  adminOptions;
3227
3260
  adminRateLimiter = new AdminRateLimiter();
3228
3261
  constructor(serverFactory, config, adminOptions) {
3229
- this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
3262
+ this.serverFactory = serverFactory;
3230
3263
  this.app = (0, express.default)();
3231
3264
  this.sessionManager = new HttpFullSessionManager();
3232
3265
  this.config = {
@@ -3349,7 +3382,7 @@ var HttpTransportHandler = class {
3349
3382
  let transport;
3350
3383
  if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
3351
3384
  else if (!sessionId && (0, __modelcontextprotocol_sdk_types_js.isInitializeRequest)(req.body)) {
3352
- const mcpServer = this.serverFactory();
3385
+ const mcpServer = await this.serverFactory();
3353
3386
  transport = new __modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({
3354
3387
  sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
3355
3388
  enableJsonResponse: true,
@@ -3357,8 +3390,12 @@ var HttpTransportHandler = class {
3357
3390
  this.sessionManager.setSession(initializedSessionId, transport, mcpServer);
3358
3391
  }
3359
3392
  });
3360
- transport.onclose = () => {
3361
- if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
3393
+ transport.onclose = async () => {
3394
+ if (transport.sessionId) try {
3395
+ await this.sessionManager.deleteSession(transport.sessionId);
3396
+ } catch (error) {
3397
+ console.error(`Failed to clean up session '${transport.sessionId}': ${toErrorMessage$2(error)}`);
3398
+ }
3362
3399
  };
3363
3400
  try {
3364
3401
  await mcpServer.connect(transport);
@@ -3407,7 +3444,7 @@ var HttpTransportHandler = class {
3407
3444
  } catch (error) {
3408
3445
  throw new Error(`Failed handling MCP DELETE request for session '${sessionId}': ${toErrorMessage$2(error)}`);
3409
3446
  }
3410
- this.sessionManager.deleteSession(sessionId);
3447
+ await this.sessionManager.deleteSession(sessionId);
3411
3448
  }
3412
3449
  async start() {
3413
3450
  try {
@@ -3430,7 +3467,11 @@ var HttpTransportHandler = class {
3430
3467
  }
3431
3468
  async stop() {
3432
3469
  if (!this.server) return;
3433
- this.sessionManager.clear();
3470
+ try {
3471
+ await this.sessionManager.clear();
3472
+ } catch (error) {
3473
+ throw new Error(`Failed to clear sessions during HTTP transport stop: ${toErrorMessage$2(error)}`);
3474
+ }
3434
3475
  const closeServer = (0, node_util.promisify)(this.server.close.bind(this.server));
3435
3476
  try {
3436
3477
  await closeServer();
@@ -3767,11 +3808,11 @@ var StdioHttpTransportHandler = class {
3767
3808
  */
3768
3809
  const RUNTIME_DIR_NAME = "runtimes";
3769
3810
  const RUNTIME_FILE_SUFFIX = ".runtime.json";
3770
- function isObject$1(value) {
3811
+ function isObject(value) {
3771
3812
  return typeof value === "object" && value !== null;
3772
3813
  }
3773
3814
  function isRuntimeStateRecord(value) {
3774
- if (!isObject$1(value)) return false;
3815
+ if (!isObject(value)) return false;
3775
3816
  return typeof value.serverId === "string" && typeof value.host === "string" && typeof value.port === "number" && value.transport === "http" && typeof value.shutdownToken === "string" && typeof value.startedAt === "string" && typeof value.pid === "number" && (value.configPath === void 0 || typeof value.configPath === "string");
3776
3817
  }
3777
3818
  function toErrorMessage$1(error) {
@@ -3821,7 +3862,7 @@ var RuntimeStateService = class RuntimeStateService {
3821
3862
  const parsed = JSON.parse(content);
3822
3863
  return isRuntimeStateRecord(parsed) ? parsed : null;
3823
3864
  } catch (error) {
3824
- if (isObject$1(error) && "code" in error && error.code === "ENOENT") return null;
3865
+ if (isObject(error) && "code" in error && error.code === "ENOENT") return null;
3825
3866
  throw new Error(`Failed to read runtime state for server '${serverId}' from '${filePath}': ${toErrorMessage$1(error)}`);
3826
3867
  }
3827
3868
  }
@@ -3842,7 +3883,7 @@ var RuntimeStateService = class RuntimeStateService {
3842
3883
  }
3843
3884
  }))).filter((record) => record !== null);
3844
3885
  } catch (error) {
3845
- if (isObject$1(error) && "code" in error && error.code === "ENOENT") return [];
3886
+ if (isObject(error) && "code" in error && error.code === "ENOENT") return [];
3846
3887
  throw new Error(`Failed to list runtime states from '${this.runtimeDir}': ${toErrorMessage$1(error)}`);
3847
3888
  }
3848
3889
  }
@@ -3857,18 +3898,105 @@ var RuntimeStateService = class RuntimeStateService {
3857
3898
  };
3858
3899
 
3859
3900
  //#endregion
3860
- //#region src/services/StopServerService.ts
3861
- function toErrorMessage(error) {
3862
- return error instanceof Error ? error.message : String(error);
3863
- }
3864
- function isObject(value) {
3865
- return typeof value === "object" && value !== null;
3901
+ //#region src/services/StopServerService/constants.ts
3902
+ /**
3903
+ * StopServerService constants.
3904
+ */
3905
+ /** Maximum time in milliseconds to wait for a shutdown to complete. */
3906
+ const DEFAULT_STOP_TIMEOUT_MS = 5e3;
3907
+ /** Minimum timeout in milliseconds for individual health check requests. */
3908
+ const HEALTH_REQUEST_TIMEOUT_FLOOR_MS = 250;
3909
+ /** Delay in milliseconds between shutdown polling attempts. */
3910
+ const SHUTDOWN_POLL_INTERVAL_MS = 200;
3911
+ /** Path for the runtime health check endpoint. */
3912
+ const HEALTH_CHECK_PATH = "/health";
3913
+ /** Path for the authenticated admin shutdown endpoint. */
3914
+ const ADMIN_SHUTDOWN_PATH = "/admin/shutdown";
3915
+ /** HTTP GET method identifier. */
3916
+ const HTTP_METHOD_GET = "GET";
3917
+ /** HTTP POST method identifier. */
3918
+ const HTTP_METHOD_POST = "POST";
3919
+ /** HTTP header name for bearer token authorization. */
3920
+ const AUTHORIZATION_HEADER_NAME = "Authorization";
3921
+ /** Prefix for bearer token values in the Authorization header. */
3922
+ const BEARER_TOKEN_PREFIX = "Bearer ";
3923
+ /** HTTP protocol scheme prefix for URL construction. */
3924
+ const HTTP_PROTOCOL = "http://";
3925
+ /** Separator between host and port in URL construction. */
3926
+ const URL_PORT_SEPARATOR = ":";
3927
+ /** Loopback hostname. */
3928
+ const LOOPBACK_HOST_LOCALHOST = "localhost";
3929
+ /** IPv4 loopback address. */
3930
+ const LOOPBACK_HOST_IPV4 = "127.0.0.1";
3931
+ /** IPv6 loopback address. */
3932
+ const LOOPBACK_HOST_IPV6 = "::1";
3933
+ /** Hosts that are safe to send admin requests to (loopback only). */
3934
+ const ALLOWED_HOSTS = new Set([
3935
+ LOOPBACK_HOST_LOCALHOST,
3936
+ LOOPBACK_HOST_IPV4,
3937
+ LOOPBACK_HOST_IPV6
3938
+ ]);
3939
+ /** Expected status value in a healthy runtime response. */
3940
+ const HEALTH_STATUS_OK = "ok";
3941
+ /** Expected transport value in a healthy runtime response. */
3942
+ const HEALTH_TRANSPORT_HTTP = "http";
3943
+ /** Property key for status field in health responses. */
3944
+ const KEY_STATUS = "status";
3945
+ /** Property key for transport field in health responses. */
3946
+ const KEY_TRANSPORT = "transport";
3947
+ /** Property key for serverId field in runtime responses. */
3948
+ const KEY_SERVER_ID = "serverId";
3949
+ /** Property key for ok field in shutdown responses. */
3950
+ const KEY_OK = "ok";
3951
+ /** Property key for message field in shutdown responses. */
3952
+ const KEY_MESSAGE = "message";
3953
+
3954
+ //#endregion
3955
+ //#region src/services/StopServerService/types.ts
3956
+ /**
3957
+ * Safely cast a non-null object to a string-keyed record for property access.
3958
+ * @param value - Object value already verified as non-null
3959
+ * @returns The same value typed as a record
3960
+ */
3961
+ function toRecord(value) {
3962
+ return value;
3866
3963
  }
3964
+ /**
3965
+ * Type guard for health responses.
3966
+ * @param value - Candidate payload to validate
3967
+ * @returns True when payload matches health response shape
3968
+ */
3867
3969
  function isHealthResponse(value) {
3868
- return isObject(value) && value.status === "ok" && value.transport === "http" && (value.serverId === void 0 || typeof value.serverId === "string");
3970
+ if (typeof value !== "object" || value === null) return false;
3971
+ const record = toRecord(value);
3972
+ return KEY_STATUS in record && record[KEY_STATUS] === HEALTH_STATUS_OK && KEY_TRANSPORT in record && record[KEY_TRANSPORT] === HEALTH_TRANSPORT_HTTP && (!(KEY_SERVER_ID in record) || record[KEY_SERVER_ID] === void 0 || typeof record[KEY_SERVER_ID] === "string");
3869
3973
  }
3974
+ /**
3975
+ * Type guard for shutdown responses.
3976
+ * @param value - Candidate payload to validate
3977
+ * @returns True when payload matches shutdown response shape
3978
+ */
3870
3979
  function isShutdownResponse(value) {
3871
- return isObject(value) && typeof value.ok === "boolean" && typeof value.message === "string" && (value.serverId === void 0 || typeof value.serverId === "string");
3980
+ if (typeof value !== "object" || value === null) return false;
3981
+ const record = toRecord(value);
3982
+ return KEY_OK in record && typeof record[KEY_OK] === "boolean" && KEY_MESSAGE in record && typeof record[KEY_MESSAGE] === "string" && (!(KEY_SERVER_ID in record) || record[KEY_SERVER_ID] === void 0 || typeof record[KEY_SERVER_ID] === "string");
3983
+ }
3984
+
3985
+ //#endregion
3986
+ //#region src/services/StopServerService/StopServerService.ts
3987
+ /**
3988
+ * Format runtime endpoint URL after validating the host is a loopback address.
3989
+ * Rejects non-loopback hosts to prevent SSRF via tampered runtime state files.
3990
+ * @param runtime - Runtime record to format
3991
+ * @param path - Request path to append
3992
+ * @returns Full runtime URL
3993
+ */
3994
+ function buildRuntimeUrl(runtime, path) {
3995
+ if (!ALLOWED_HOSTS.has(runtime.host)) throw new Error(`Refusing to connect to non-loopback host '${runtime.host}'. Only ${Array.from(ALLOWED_HOSTS).join(", ")} are allowed.`);
3996
+ return `${HTTP_PROTOCOL}${runtime.host}${URL_PORT_SEPARATOR}${runtime.port}${path}`;
3997
+ }
3998
+ function toErrorMessage(error) {
3999
+ return error instanceof Error ? error.message : String(error);
3872
4000
  }
3873
4001
  function sleep(delayMs) {
3874
4002
  return new Promise((resolve$2) => {
@@ -3889,7 +4017,7 @@ var StopServerService = class {
3889
4017
  * @returns Stop result payload
3890
4018
  */
3891
4019
  async stop(request) {
3892
- const timeoutMs = request.timeoutMs ?? 5e3;
4020
+ const timeoutMs = request.timeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
3893
4021
  const runtime = await this.resolveRuntime(request);
3894
4022
  const health = await this.fetchHealth(runtime, timeoutMs);
3895
4023
  if (!health.reachable) {
@@ -3935,7 +4063,7 @@ var StopServerService = class {
3935
4063
  */
3936
4064
  async fetchHealth(runtime, timeoutMs) {
3937
4065
  try {
3938
- const response = await this.fetchWithTimeout(`http://${runtime.host}:${runtime.port}/health`, { method: "GET" }, timeoutMs);
4066
+ const response = await this.fetchWithTimeout(buildRuntimeUrl(runtime, HEALTH_CHECK_PATH), { method: HTTP_METHOD_GET }, timeoutMs);
3939
4067
  if (!response.ok) return { reachable: false };
3940
4068
  const payload = await response.json();
3941
4069
  if (!isHealthResponse(payload)) throw new Error("Received invalid health response payload.");
@@ -3955,9 +4083,9 @@ var StopServerService = class {
3955
4083
  * @returns Parsed shutdown response payload
3956
4084
  */
3957
4085
  async requestShutdown(runtime, shutdownToken, timeoutMs) {
3958
- const response = await this.fetchWithTimeout(`http://${runtime.host}:${runtime.port}/admin/shutdown`, {
3959
- method: "POST",
3960
- headers: { Authorization: `Bearer ${shutdownToken}` }
4086
+ const response = await this.fetchWithTimeout(buildRuntimeUrl(runtime, ADMIN_SHUTDOWN_PATH), {
4087
+ method: HTTP_METHOD_POST,
4088
+ headers: { [AUTHORIZATION_HEADER_NAME]: `${BEARER_TOKEN_PREFIX}${shutdownToken}` }
3961
4089
  }, timeoutMs);
3962
4090
  const payload = await response.json();
3963
4091
  if (!isShutdownResponse(payload)) throw new Error("Received invalid shutdown response payload.");
@@ -3973,8 +4101,8 @@ var StopServerService = class {
3973
4101
  async waitForShutdown(runtime, timeoutMs) {
3974
4102
  const deadline = Date.now() + timeoutMs;
3975
4103
  while (Date.now() < deadline) {
3976
- if (!(await this.fetchHealth(runtime, Math.max(250, deadline - Date.now()))).reachable) return;
3977
- await sleep(200);
4104
+ if (!(await this.fetchHealth(runtime, Math.max(HEALTH_REQUEST_TIMEOUT_FLOOR_MS, deadline - Date.now()))).reachable) return;
4105
+ await sleep(SHUTDOWN_POLL_INTERVAL_MS);
3978
4106
  }
3979
4107
  throw new Error(`Timed out waiting for runtime '${runtime.serverId}' to stop at http://${runtime.host}:${runtime.port}.`);
3980
4108
  }
@@ -4100,6 +4228,12 @@ Object.defineProperty(exports, 'createServer', {
4100
4228
  return createServer;
4101
4229
  }
4102
4230
  });
4231
+ Object.defineProperty(exports, 'createSessionServer', {
4232
+ enumerable: true,
4233
+ get: function () {
4234
+ return createSessionServer;
4235
+ }
4236
+ });
4103
4237
  Object.defineProperty(exports, 'findConfigFile', {
4104
4238
  enumerable: true,
4105
4239
  get: function () {
@@ -4112,6 +4246,12 @@ Object.defineProperty(exports, 'generateServerId', {
4112
4246
  return generateServerId;
4113
4247
  }
4114
4248
  });
4249
+ Object.defineProperty(exports, 'initializeSharedServices', {
4250
+ enumerable: true,
4251
+ get: function () {
4252
+ return initializeSharedServices;
4253
+ }
4254
+ });
4115
4255
  Object.defineProperty(exports, 'version', {
4116
4256
  enumerable: true,
4117
4257
  get: function () {