@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.
- package/dist/cli.cjs +15 -5
- package/dist/cli.mjs +15 -5
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +196 -156
- package/dist/index.d.mts +196 -156
- package/dist/index.mjs +2 -2
- package/dist/{src-iTE9Cero.cjs → src-BRqEdbha.cjs} +212 -72
- package/dist/{src-D7Yq1bTx.mjs → src-Dn6vMZIk.mjs} +201 -73
- package/package.json +2 -2
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
2995
|
+
const useTool = new UseToolTool(clientManager, skillService, serverId, definitionsCacheService);
|
|
3000
2996
|
const searchListTools = new SearchListToolsTool(clientManager, definitionsCacheService);
|
|
3001
2997
|
toolsRef.describeTools = describeTools;
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
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(
|
|
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
|
|
3080
|
+
const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
|
|
3055
3081
|
const resourceToServers = /* @__PURE__ */ new Map();
|
|
3056
|
-
for (const serverDefinition of
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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
|
|
3811
|
+
function isObject(value) {
|
|
3771
3812
|
return typeof value === "object" && value !== null;
|
|
3772
3813
|
}
|
|
3773
3814
|
function isRuntimeStateRecord(value) {
|
|
3774
|
-
if (!isObject
|
|
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
|
|
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
|
|
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
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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(
|
|
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(
|
|
3959
|
-
method:
|
|
3960
|
-
headers: {
|
|
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(
|
|
3977
|
-
await sleep(
|
|
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 () {
|