@agiflowai/one-mcp 0.3.12 → 0.3.14
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 +9 -3
- package/dist/cli.cjs +579 -241
- package/dist/cli.mjs +541 -203
- package/dist/index.cjs +18 -12
- package/dist/index.d.cts +332 -23
- package/dist/index.d.mts +333 -23
- package/dist/index.mjs +2 -2
- package/dist/{http-ClRbCldm.mjs → src-D7Yq1bTx.mjs} +700 -167
- package/dist/{http-B4_JfY6t.cjs → src-iTE9Cero.cjs} +723 -166
- package/package.json +2 -2
|
@@ -12,10 +12,12 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
12
12
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
13
13
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
14
14
|
import { Liquid } from "liquidjs";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import express from "express";
|
|
15
|
+
import { once } from "node:events";
|
|
16
|
+
import { promisify } from "node:util";
|
|
18
17
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
18
|
+
import express from "express";
|
|
19
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
21
|
|
|
20
22
|
//#region src/utils/mcpConfigSchema.ts
|
|
21
23
|
/**
|
|
@@ -1137,7 +1139,7 @@ const DEFAULT_SERVER_ID = "unknown";
|
|
|
1137
1139
|
function isYamlPath(filePath) {
|
|
1138
1140
|
return filePath.endsWith(".yaml") || filePath.endsWith(".yml");
|
|
1139
1141
|
}
|
|
1140
|
-
function toErrorMessage(error) {
|
|
1142
|
+
function toErrorMessage$3(error) {
|
|
1141
1143
|
return error instanceof Error ? error.message : String(error);
|
|
1142
1144
|
}
|
|
1143
1145
|
function sanitizeConfigPathForFilename(configFilePath) {
|
|
@@ -1328,7 +1330,7 @@ var DefinitionsCacheService = class {
|
|
|
1328
1330
|
} catch (error) {
|
|
1329
1331
|
failures.push({
|
|
1330
1332
|
serverName: client.serverName,
|
|
1331
|
-
error: toErrorMessage(error)
|
|
1333
|
+
error: toErrorMessage$3(error)
|
|
1332
1334
|
});
|
|
1333
1335
|
return null;
|
|
1334
1336
|
}
|
|
@@ -1366,7 +1368,7 @@ var DefinitionsCacheService = class {
|
|
|
1366
1368
|
arguments: prompt.arguments?.map((arg) => ({ ...arg }))
|
|
1367
1369
|
}));
|
|
1368
1370
|
} catch (error) {
|
|
1369
|
-
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1371
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage$3(error)}`);
|
|
1370
1372
|
return [];
|
|
1371
1373
|
}
|
|
1372
1374
|
}
|
|
@@ -1379,7 +1381,7 @@ var DefinitionsCacheService = class {
|
|
|
1379
1381
|
mimeType: resource.mimeType
|
|
1380
1382
|
}));
|
|
1381
1383
|
} catch (error) {
|
|
1382
|
-
console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1384
|
+
console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage$3(error)}`);
|
|
1383
1385
|
return [];
|
|
1384
1386
|
}
|
|
1385
1387
|
}
|
|
@@ -1408,7 +1410,7 @@ var DefinitionsCacheService = class {
|
|
|
1408
1410
|
autoDetected: true
|
|
1409
1411
|
};
|
|
1410
1412
|
} catch (error) {
|
|
1411
|
-
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1413
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage$3(error)}`);
|
|
1412
1414
|
return null;
|
|
1413
1415
|
}
|
|
1414
1416
|
}));
|
|
@@ -2777,7 +2779,7 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2777
2779
|
|
|
2778
2780
|
//#endregion
|
|
2779
2781
|
//#region package.json
|
|
2780
|
-
var version = "0.3.
|
|
2782
|
+
var version = "0.3.13";
|
|
2781
2783
|
|
|
2782
2784
|
//#endregion
|
|
2783
2785
|
//#region src/server/index.ts
|
|
@@ -3097,28 +3099,323 @@ async function createServer(options) {
|
|
|
3097
3099
|
}
|
|
3098
3100
|
|
|
3099
3101
|
//#endregion
|
|
3100
|
-
//#region src/
|
|
3102
|
+
//#region src/types/index.ts
|
|
3101
3103
|
/**
|
|
3102
|
-
*
|
|
3103
|
-
* Used for command-line and direct integrations
|
|
3104
|
+
* Transport mode constants
|
|
3104
3105
|
*/
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3106
|
+
const TRANSPORT_MODE = {
|
|
3107
|
+
STDIO: "stdio",
|
|
3108
|
+
HTTP: "http",
|
|
3109
|
+
SSE: "sse"
|
|
3110
|
+
};
|
|
3111
|
+
|
|
3112
|
+
//#endregion
|
|
3113
|
+
//#region src/transports/http.ts
|
|
3114
|
+
/**
|
|
3115
|
+
* HTTP Transport Handler
|
|
3116
|
+
*
|
|
3117
|
+
* DESIGN PATTERNS:
|
|
3118
|
+
* - Transport handler pattern implementing TransportHandler interface
|
|
3119
|
+
* - Session management for stateful connections
|
|
3120
|
+
* - Streamable HTTP protocol (2025-03-26) with resumability support
|
|
3121
|
+
* - Factory pattern for creating MCP server instances per session
|
|
3122
|
+
*
|
|
3123
|
+
* CODING STANDARDS:
|
|
3124
|
+
* - Use async/await for all asynchronous operations
|
|
3125
|
+
* - Implement proper session lifecycle management
|
|
3126
|
+
* - Handle errors gracefully with appropriate HTTP status codes
|
|
3127
|
+
* - Provide health check endpoint for monitoring
|
|
3128
|
+
* - Clean up resources on shutdown
|
|
3129
|
+
*
|
|
3130
|
+
* AVOID:
|
|
3131
|
+
* - Sharing MCP server instances across sessions (use factory pattern)
|
|
3132
|
+
* - Forgetting to clean up sessions on disconnect
|
|
3133
|
+
* - Missing error handling for request processing
|
|
3134
|
+
* - Hardcoded configuration (use TransportConfig)
|
|
3135
|
+
*/
|
|
3136
|
+
/**
|
|
3137
|
+
* HTTP session manager
|
|
3138
|
+
*/
|
|
3139
|
+
var HttpFullSessionManager = class {
|
|
3140
|
+
sessions = /* @__PURE__ */ new Map();
|
|
3141
|
+
getSession(sessionId) {
|
|
3142
|
+
return this.sessions.get(sessionId);
|
|
3143
|
+
}
|
|
3144
|
+
setSession(sessionId, transport, server) {
|
|
3145
|
+
this.sessions.set(sessionId, {
|
|
3146
|
+
transport,
|
|
3147
|
+
server
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
deleteSession(sessionId) {
|
|
3151
|
+
const session = this.sessions.get(sessionId);
|
|
3152
|
+
if (session) session.server.close();
|
|
3153
|
+
this.sessions.delete(sessionId);
|
|
3154
|
+
}
|
|
3155
|
+
hasSession(sessionId) {
|
|
3156
|
+
return this.sessions.has(sessionId);
|
|
3157
|
+
}
|
|
3158
|
+
clear() {
|
|
3159
|
+
for (const session of this.sessions.values()) session.server.close();
|
|
3160
|
+
this.sessions.clear();
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
function toErrorMessage$2(error) {
|
|
3164
|
+
return error instanceof Error ? error.message : String(error);
|
|
3165
|
+
}
|
|
3166
|
+
const ADMIN_RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3167
|
+
const ADMIN_RATE_LIMIT_MAX_REQUESTS = 5;
|
|
3168
|
+
/**
|
|
3169
|
+
* Simple in-memory rate limiter for the admin shutdown endpoint.
|
|
3170
|
+
* Tracks request timestamps per IP within a sliding window.
|
|
3171
|
+
*/
|
|
3172
|
+
var AdminRateLimiter = class {
|
|
3173
|
+
requests = /* @__PURE__ */ new Map();
|
|
3174
|
+
isAllowed(ip) {
|
|
3175
|
+
const now = Date.now();
|
|
3176
|
+
const windowStart = now - ADMIN_RATE_LIMIT_WINDOW_MS;
|
|
3177
|
+
const timestamps = (this.requests.get(ip) ?? []).filter((t) => t > windowStart);
|
|
3178
|
+
if (timestamps.length >= ADMIN_RATE_LIMIT_MAX_REQUESTS) {
|
|
3179
|
+
this.requests.set(ip, timestamps);
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
timestamps.push(now);
|
|
3183
|
+
this.requests.set(ip, timestamps);
|
|
3184
|
+
return true;
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
/**
|
|
3188
|
+
* HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
|
|
3189
|
+
* Provides stateful session management with resumability support
|
|
3190
|
+
*/
|
|
3191
|
+
var HttpTransportHandler = class {
|
|
3192
|
+
serverFactory;
|
|
3193
|
+
app;
|
|
3194
|
+
server = null;
|
|
3195
|
+
sessionManager;
|
|
3196
|
+
config;
|
|
3197
|
+
adminOptions;
|
|
3198
|
+
adminRateLimiter = new AdminRateLimiter();
|
|
3199
|
+
constructor(serverFactory, config, adminOptions) {
|
|
3200
|
+
this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
|
|
3201
|
+
this.app = express();
|
|
3202
|
+
this.sessionManager = new HttpFullSessionManager();
|
|
3203
|
+
this.config = {
|
|
3204
|
+
mode: config.mode,
|
|
3205
|
+
port: config.port ?? 3e3,
|
|
3206
|
+
host: config.host ?? "localhost"
|
|
3207
|
+
};
|
|
3208
|
+
this.adminOptions = adminOptions;
|
|
3209
|
+
this.setupMiddleware();
|
|
3210
|
+
this.setupRoutes();
|
|
3211
|
+
}
|
|
3212
|
+
setupMiddleware() {
|
|
3213
|
+
this.app.use(express.json());
|
|
3214
|
+
}
|
|
3215
|
+
setupRoutes() {
|
|
3216
|
+
this.app.post("/mcp", async (req, res) => {
|
|
3217
|
+
try {
|
|
3218
|
+
await this.handlePostRequest(req, res);
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
console.error(`Failed to handle MCP POST request: ${toErrorMessage$2(error)}`);
|
|
3221
|
+
res.status(500).json({
|
|
3222
|
+
jsonrpc: "2.0",
|
|
3223
|
+
error: {
|
|
3224
|
+
code: -32603,
|
|
3225
|
+
message: "Failed to handle MCP POST request."
|
|
3226
|
+
},
|
|
3227
|
+
id: null
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
});
|
|
3231
|
+
this.app.get("/mcp", async (req, res) => {
|
|
3232
|
+
try {
|
|
3233
|
+
await this.handleGetRequest(req, res);
|
|
3234
|
+
} catch (error) {
|
|
3235
|
+
console.error(`Failed to handle MCP GET request: ${toErrorMessage$2(error)}`);
|
|
3236
|
+
res.status(500).send("Failed to handle MCP GET request.");
|
|
3237
|
+
}
|
|
3238
|
+
});
|
|
3239
|
+
this.app.delete("/mcp", async (req, res) => {
|
|
3240
|
+
try {
|
|
3241
|
+
await this.handleDeleteRequest(req, res);
|
|
3242
|
+
} catch (error) {
|
|
3243
|
+
console.error(`Failed to handle MCP DELETE request: ${toErrorMessage$2(error)}`);
|
|
3244
|
+
res.status(500).send("Failed to handle MCP DELETE request.");
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
this.app.get("/health", (_req, res) => {
|
|
3248
|
+
const payload = {
|
|
3249
|
+
status: "ok",
|
|
3250
|
+
transport: "http",
|
|
3251
|
+
serverId: this.adminOptions?.serverId
|
|
3252
|
+
};
|
|
3253
|
+
res.json(payload);
|
|
3254
|
+
});
|
|
3255
|
+
this.app.post("/admin/shutdown", async (req, res) => {
|
|
3256
|
+
try {
|
|
3257
|
+
const clientIp = req.ip ?? req.socket.remoteAddress ?? "unknown";
|
|
3258
|
+
if (!this.adminRateLimiter.isAllowed(clientIp)) {
|
|
3259
|
+
const payload = {
|
|
3260
|
+
ok: false,
|
|
3261
|
+
message: "Too many shutdown requests. Try again later.",
|
|
3262
|
+
serverId: this.adminOptions?.serverId
|
|
3263
|
+
};
|
|
3264
|
+
res.status(429).json(payload);
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
await this.handleAdminShutdownRequest(req, res);
|
|
3268
|
+
} catch (error) {
|
|
3269
|
+
console.error(`Failed to process shutdown request: ${toErrorMessage$2(error)}`);
|
|
3270
|
+
const payload = {
|
|
3271
|
+
ok: false,
|
|
3272
|
+
message: "Failed to process shutdown request.",
|
|
3273
|
+
serverId: this.adminOptions?.serverId
|
|
3274
|
+
};
|
|
3275
|
+
res.status(500).json(payload);
|
|
3276
|
+
}
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
isAuthorizedShutdownRequest(req) {
|
|
3280
|
+
const expectedToken = this.adminOptions?.shutdownToken;
|
|
3281
|
+
if (!expectedToken) return false;
|
|
3282
|
+
const authHeader = req.headers.authorization;
|
|
3283
|
+
if (typeof authHeader === "string" && authHeader.startsWith("Bearer ")) return authHeader.slice(7) === expectedToken;
|
|
3284
|
+
const tokenHeader = req.headers["x-one-mcp-shutdown-token"];
|
|
3285
|
+
return typeof tokenHeader === "string" && tokenHeader === expectedToken;
|
|
3286
|
+
}
|
|
3287
|
+
async handleAdminShutdownRequest(req, res) {
|
|
3288
|
+
try {
|
|
3289
|
+
if (!this.adminOptions?.onShutdownRequested) {
|
|
3290
|
+
const payload$1 = {
|
|
3291
|
+
ok: false,
|
|
3292
|
+
message: "Shutdown endpoint is not enabled for this server instance.",
|
|
3293
|
+
serverId: this.adminOptions?.serverId
|
|
3294
|
+
};
|
|
3295
|
+
res.status(404).json(payload$1);
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (!this.isAuthorizedShutdownRequest(req)) {
|
|
3299
|
+
const payload$1 = {
|
|
3300
|
+
ok: false,
|
|
3301
|
+
message: "Unauthorized shutdown request: invalid or missing shutdown token.",
|
|
3302
|
+
serverId: this.adminOptions?.serverId
|
|
3303
|
+
};
|
|
3304
|
+
res.status(401).json(payload$1);
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
const payload = {
|
|
3308
|
+
ok: true,
|
|
3309
|
+
message: "Shutdown request accepted. Stopping server gracefully.",
|
|
3310
|
+
serverId: this.adminOptions?.serverId
|
|
3311
|
+
};
|
|
3312
|
+
res.json(payload);
|
|
3313
|
+
await this.adminOptions.onShutdownRequested();
|
|
3314
|
+
} catch (error) {
|
|
3315
|
+
throw new Error(`Failed to handle admin shutdown request: ${toErrorMessage$2(error)}`);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
async handlePostRequest(req, res) {
|
|
3319
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
3320
|
+
let transport;
|
|
3321
|
+
if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
|
|
3322
|
+
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
3323
|
+
const mcpServer = this.serverFactory();
|
|
3324
|
+
transport = new StreamableHTTPServerTransport({
|
|
3325
|
+
sessionIdGenerator: () => randomUUID(),
|
|
3326
|
+
enableJsonResponse: true,
|
|
3327
|
+
onsessioninitialized: (initializedSessionId) => {
|
|
3328
|
+
this.sessionManager.setSession(initializedSessionId, transport, mcpServer);
|
|
3329
|
+
}
|
|
3330
|
+
});
|
|
3331
|
+
transport.onclose = () => {
|
|
3332
|
+
if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
|
|
3333
|
+
};
|
|
3334
|
+
try {
|
|
3335
|
+
await mcpServer.connect(transport);
|
|
3336
|
+
} catch (error) {
|
|
3337
|
+
throw new Error(`Failed to connect MCP server transport for initialization request: ${toErrorMessage$2(error)}`);
|
|
3338
|
+
}
|
|
3339
|
+
} else {
|
|
3340
|
+
res.status(400).json({
|
|
3341
|
+
jsonrpc: "2.0",
|
|
3342
|
+
error: {
|
|
3343
|
+
code: -32e3,
|
|
3344
|
+
message: sessionId === void 0 ? "Bad Request: missing session ID and request body is not an initialize request." : `Bad Request: unknown session ID '${sessionId}'.`
|
|
3345
|
+
},
|
|
3346
|
+
id: null
|
|
3347
|
+
});
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
try {
|
|
3351
|
+
await transport.handleRequest(req, res, req.body);
|
|
3352
|
+
} catch (error) {
|
|
3353
|
+
throw new Error(`Failed handling MCP transport request: ${toErrorMessage$2(error)}`);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
async handleGetRequest(req, res) {
|
|
3357
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
3358
|
+
if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
|
|
3359
|
+
res.status(400).send("Invalid or missing session ID");
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
3363
|
+
try {
|
|
3364
|
+
await session.transport.handleRequest(req, res);
|
|
3365
|
+
} catch (error) {
|
|
3366
|
+
throw new Error(`Failed handling MCP GET request for session '${sessionId}': ${toErrorMessage$2(error)}`);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
async handleDeleteRequest(req, res) {
|
|
3370
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
3371
|
+
if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
|
|
3372
|
+
res.status(400).send("Invalid or missing session ID");
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
3376
|
+
try {
|
|
3377
|
+
await session.transport.handleRequest(req, res);
|
|
3378
|
+
} catch (error) {
|
|
3379
|
+
throw new Error(`Failed handling MCP DELETE request for session '${sessionId}': ${toErrorMessage$2(error)}`);
|
|
3380
|
+
}
|
|
3381
|
+
this.sessionManager.deleteSession(sessionId);
|
|
3110
3382
|
}
|
|
3111
3383
|
async start() {
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3384
|
+
try {
|
|
3385
|
+
const server = this.app.listen(this.config.port, this.config.host);
|
|
3386
|
+
this.server = server;
|
|
3387
|
+
const listeningPromise = (async () => {
|
|
3388
|
+
await once(server, "listening");
|
|
3389
|
+
})();
|
|
3390
|
+
const errorPromise = (async () => {
|
|
3391
|
+
const [error] = await once(server, "error");
|
|
3392
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
3393
|
+
})();
|
|
3394
|
+
await Promise.race([listeningPromise, errorPromise]);
|
|
3395
|
+
console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
|
|
3396
|
+
console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
|
|
3397
|
+
} catch (error) {
|
|
3398
|
+
this.server = null;
|
|
3399
|
+
throw new Error(`Failed to start HTTP transport: ${toErrorMessage$2(error)}`);
|
|
3400
|
+
}
|
|
3115
3401
|
}
|
|
3116
3402
|
async stop() {
|
|
3117
|
-
if (this.
|
|
3118
|
-
|
|
3119
|
-
|
|
3403
|
+
if (!this.server) return;
|
|
3404
|
+
this.sessionManager.clear();
|
|
3405
|
+
const closeServer = promisify(this.server.close.bind(this.server));
|
|
3406
|
+
try {
|
|
3407
|
+
await closeServer();
|
|
3408
|
+
this.server = null;
|
|
3409
|
+
} catch (error) {
|
|
3410
|
+
throw new Error(`Failed to stop HTTP transport: ${toErrorMessage$2(error)}`);
|
|
3120
3411
|
}
|
|
3121
3412
|
}
|
|
3413
|
+
getPort() {
|
|
3414
|
+
return this.config.port;
|
|
3415
|
+
}
|
|
3416
|
+
getHost() {
|
|
3417
|
+
return this.config.host;
|
|
3418
|
+
}
|
|
3122
3419
|
};
|
|
3123
3420
|
|
|
3124
3421
|
//#endregion
|
|
@@ -3264,182 +3561,418 @@ var SseTransportHandler = class {
|
|
|
3264
3561
|
};
|
|
3265
3562
|
|
|
3266
3563
|
//#endregion
|
|
3267
|
-
//#region src/transports/
|
|
3564
|
+
//#region src/transports/stdio.ts
|
|
3268
3565
|
/**
|
|
3269
|
-
*
|
|
3566
|
+
* Stdio transport handler for MCP server
|
|
3567
|
+
* Used for command-line and direct integrations
|
|
3568
|
+
*/
|
|
3569
|
+
var StdioTransportHandler = class {
|
|
3570
|
+
server;
|
|
3571
|
+
transport = null;
|
|
3572
|
+
constructor(server) {
|
|
3573
|
+
this.server = server;
|
|
3574
|
+
}
|
|
3575
|
+
async start() {
|
|
3576
|
+
this.transport = new StdioServerTransport();
|
|
3577
|
+
await this.server.connect(this.transport);
|
|
3578
|
+
console.error("@agiflowai/one-mcp MCP server started on stdio");
|
|
3579
|
+
}
|
|
3580
|
+
async stop() {
|
|
3581
|
+
if (this.transport) {
|
|
3582
|
+
await this.transport.close();
|
|
3583
|
+
this.transport = null;
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
//#endregion
|
|
3589
|
+
//#region src/transports/stdio-http.ts
|
|
3590
|
+
/**
|
|
3591
|
+
* STDIO-HTTP Proxy Transport
|
|
3270
3592
|
*
|
|
3271
3593
|
* DESIGN PATTERNS:
|
|
3272
3594
|
* - Transport handler pattern implementing TransportHandler interface
|
|
3273
|
-
* -
|
|
3274
|
-
* -
|
|
3275
|
-
* - Factory pattern for creating MCP server instances per session
|
|
3595
|
+
* - STDIO transport with MCP request forwarding to HTTP backend
|
|
3596
|
+
* - Graceful cleanup with error isolation
|
|
3276
3597
|
*
|
|
3277
3598
|
* CODING STANDARDS:
|
|
3278
|
-
* - Use
|
|
3279
|
-
* -
|
|
3280
|
-
* -
|
|
3281
|
-
* - Provide health check endpoint for monitoring
|
|
3282
|
-
* - Clean up resources on shutdown
|
|
3599
|
+
* - Use StdioServerTransport for stdio communication
|
|
3600
|
+
* - Reuse a single StreamableHTTP client connection
|
|
3601
|
+
* - Wrap async operations with try-catch and descriptive errors
|
|
3283
3602
|
*
|
|
3284
3603
|
* AVOID:
|
|
3285
|
-
* -
|
|
3286
|
-
* -
|
|
3287
|
-
* -
|
|
3288
|
-
* - Hardcoded configuration (use TransportConfig)
|
|
3604
|
+
* - Starting HTTP server lifecycle in this transport entry point
|
|
3605
|
+
* - Recreating HTTP client per request
|
|
3606
|
+
* - Swallowing cleanup failures silently
|
|
3289
3607
|
*/
|
|
3290
3608
|
/**
|
|
3291
|
-
* HTTP
|
|
3609
|
+
* Transport that serves MCP over stdio and forwards MCP requests to an HTTP endpoint.
|
|
3292
3610
|
*/
|
|
3293
|
-
var
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3611
|
+
var StdioHttpTransportHandler = class {
|
|
3612
|
+
endpoint;
|
|
3613
|
+
stdioProxyServer = null;
|
|
3614
|
+
stdioTransport = null;
|
|
3615
|
+
httpClient = null;
|
|
3616
|
+
constructor(config) {
|
|
3617
|
+
this.endpoint = config.endpoint;
|
|
3297
3618
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3619
|
+
async start() {
|
|
3620
|
+
try {
|
|
3621
|
+
const httpClientTransport = new StreamableHTTPClientTransport(this.endpoint);
|
|
3622
|
+
const client = new Client({
|
|
3623
|
+
name: "@agiflowai/one-mcp-stdio-http-proxy",
|
|
3624
|
+
version: "0.1.0"
|
|
3625
|
+
}, { capabilities: {} });
|
|
3626
|
+
await client.connect(httpClientTransport);
|
|
3627
|
+
this.httpClient = client;
|
|
3628
|
+
this.stdioProxyServer = this.createProxyServer(client);
|
|
3629
|
+
this.stdioTransport = new StdioServerTransport();
|
|
3630
|
+
await this.stdioProxyServer.connect(this.stdioTransport);
|
|
3631
|
+
console.error(`@agiflowai/one-mcp MCP stdio proxy connected to ${this.endpoint.toString()}`);
|
|
3632
|
+
} catch (error) {
|
|
3633
|
+
await this.stop();
|
|
3634
|
+
throw new Error(`Failed to start stdio-http proxy transport: ${error instanceof Error ? error.message : String(error)}`);
|
|
3635
|
+
}
|
|
3308
3636
|
}
|
|
3309
|
-
|
|
3310
|
-
|
|
3637
|
+
async stop() {
|
|
3638
|
+
const stdioTransport = this.stdioTransport;
|
|
3639
|
+
const stdioProxyServer = this.stdioProxyServer;
|
|
3640
|
+
const httpClient = this.httpClient;
|
|
3641
|
+
this.stdioTransport = null;
|
|
3642
|
+
this.stdioProxyServer = null;
|
|
3643
|
+
this.httpClient = null;
|
|
3644
|
+
const cleanupErrors = [];
|
|
3645
|
+
await Promise.all([
|
|
3646
|
+
(async () => {
|
|
3647
|
+
try {
|
|
3648
|
+
if (stdioTransport) await stdioTransport.close();
|
|
3649
|
+
} catch (error) {
|
|
3650
|
+
cleanupErrors.push(`failed closing stdio transport: ${error instanceof Error ? error.message : String(error)}`);
|
|
3651
|
+
}
|
|
3652
|
+
})(),
|
|
3653
|
+
(async () => {
|
|
3654
|
+
try {
|
|
3655
|
+
if (stdioProxyServer) await stdioProxyServer.close();
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
cleanupErrors.push(`failed closing stdio proxy server: ${error instanceof Error ? error.message : String(error)}`);
|
|
3658
|
+
}
|
|
3659
|
+
})(),
|
|
3660
|
+
(async () => {
|
|
3661
|
+
try {
|
|
3662
|
+
if (httpClient) await httpClient.close();
|
|
3663
|
+
} catch (error) {
|
|
3664
|
+
cleanupErrors.push(`failed closing http client: ${error instanceof Error ? error.message : String(error)}`);
|
|
3665
|
+
}
|
|
3666
|
+
})()
|
|
3667
|
+
]);
|
|
3668
|
+
if (cleanupErrors.length > 0) throw new Error(`Failed to stop stdio-http proxy transport: ${cleanupErrors.join("; ")}`);
|
|
3311
3669
|
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3670
|
+
createProxyServer(client) {
|
|
3671
|
+
const proxyServer = new Server({
|
|
3672
|
+
name: "@agiflowai/one-mcp-stdio-http-proxy",
|
|
3673
|
+
version: "0.1.0"
|
|
3674
|
+
}, { capabilities: {
|
|
3675
|
+
tools: {},
|
|
3676
|
+
resources: {},
|
|
3677
|
+
prompts: {}
|
|
3678
|
+
} });
|
|
3679
|
+
proxyServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
3680
|
+
try {
|
|
3681
|
+
return await client.listTools();
|
|
3682
|
+
} catch (error) {
|
|
3683
|
+
throw new Error(`Failed forwarding tools/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
proxyServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
3687
|
+
try {
|
|
3688
|
+
return await client.callTool({
|
|
3689
|
+
name: request.params.name,
|
|
3690
|
+
arguments: request.params.arguments
|
|
3691
|
+
});
|
|
3692
|
+
} catch (error) {
|
|
3693
|
+
throw new Error(`Failed forwarding tools/call (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3694
|
+
}
|
|
3695
|
+
});
|
|
3696
|
+
proxyServer.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
3697
|
+
try {
|
|
3698
|
+
return await client.listResources();
|
|
3699
|
+
} catch (error) {
|
|
3700
|
+
throw new Error(`Failed forwarding resources/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3701
|
+
}
|
|
3702
|
+
});
|
|
3703
|
+
proxyServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
3704
|
+
try {
|
|
3705
|
+
return await client.readResource({ uri: request.params.uri });
|
|
3706
|
+
} catch (error) {
|
|
3707
|
+
throw new Error(`Failed forwarding resources/read (${request.params.uri}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3708
|
+
}
|
|
3709
|
+
});
|
|
3710
|
+
proxyServer.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
3711
|
+
try {
|
|
3712
|
+
return await client.listPrompts();
|
|
3713
|
+
} catch (error) {
|
|
3714
|
+
throw new Error(`Failed forwarding prompts/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3715
|
+
}
|
|
3716
|
+
});
|
|
3717
|
+
proxyServer.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
3718
|
+
try {
|
|
3719
|
+
return await client.getPrompt({
|
|
3720
|
+
name: request.params.name,
|
|
3721
|
+
arguments: request.params.arguments
|
|
3722
|
+
});
|
|
3723
|
+
} catch (error) {
|
|
3724
|
+
throw new Error(`Failed forwarding prompts/get (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
3725
|
+
}
|
|
3726
|
+
});
|
|
3727
|
+
return proxyServer;
|
|
3315
3728
|
}
|
|
3316
3729
|
};
|
|
3730
|
+
|
|
3731
|
+
//#endregion
|
|
3732
|
+
//#region src/services/RuntimeStateService.ts
|
|
3317
3733
|
/**
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3734
|
+
* RuntimeStateService
|
|
3735
|
+
*
|
|
3736
|
+
* Persists runtime metadata for HTTP one-mcp instances so external commands
|
|
3737
|
+
* (for example `one-mcp stop`) can discover and target the correct server.
|
|
3320
3738
|
*/
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3739
|
+
const RUNTIME_DIR_NAME = "runtimes";
|
|
3740
|
+
const RUNTIME_FILE_SUFFIX = ".runtime.json";
|
|
3741
|
+
function isObject$1(value) {
|
|
3742
|
+
return typeof value === "object" && value !== null;
|
|
3743
|
+
}
|
|
3744
|
+
function isRuntimeStateRecord(value) {
|
|
3745
|
+
if (!isObject$1(value)) return false;
|
|
3746
|
+
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");
|
|
3747
|
+
}
|
|
3748
|
+
function toErrorMessage$1(error) {
|
|
3749
|
+
return error instanceof Error ? error.message : String(error);
|
|
3750
|
+
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Runtime state persistence implementation.
|
|
3753
|
+
*/
|
|
3754
|
+
var RuntimeStateService = class RuntimeStateService {
|
|
3755
|
+
runtimeDir;
|
|
3756
|
+
constructor(runtimeDir) {
|
|
3757
|
+
this.runtimeDir = runtimeDir ?? RuntimeStateService.getDefaultRuntimeDir();
|
|
3338
3758
|
}
|
|
3339
|
-
|
|
3340
|
-
|
|
3759
|
+
/**
|
|
3760
|
+
* Resolve default runtime directory under the user's home cache path.
|
|
3761
|
+
* @returns Absolute runtime directory path
|
|
3762
|
+
*/
|
|
3763
|
+
static getDefaultRuntimeDir() {
|
|
3764
|
+
return join(homedir(), ".aicode-toolkit", "one-mcp", RUNTIME_DIR_NAME);
|
|
3341
3765
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
});
|
|
3349
|
-
this.app.delete("/mcp", async (req, res) => {
|
|
3350
|
-
await this.handleDeleteRequest(req, res);
|
|
3351
|
-
});
|
|
3352
|
-
this.app.get("/health", (_req, res) => {
|
|
3353
|
-
res.json({
|
|
3354
|
-
status: "ok",
|
|
3355
|
-
transport: "http"
|
|
3356
|
-
});
|
|
3357
|
-
});
|
|
3766
|
+
/**
|
|
3767
|
+
* Build runtime state file path for a given server ID.
|
|
3768
|
+
* @param serverId - Target one-mcp server identifier
|
|
3769
|
+
* @returns Absolute runtime file path
|
|
3770
|
+
*/
|
|
3771
|
+
getRecordPath(serverId) {
|
|
3772
|
+
return join(this.runtimeDir, `${serverId}${RUNTIME_FILE_SUFFIX}`);
|
|
3358
3773
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3774
|
+
/**
|
|
3775
|
+
* Persist a runtime state record.
|
|
3776
|
+
* @param record - Runtime metadata to persist
|
|
3777
|
+
* @returns Promise that resolves when write completes
|
|
3778
|
+
*/
|
|
3779
|
+
async write(record) {
|
|
3780
|
+
await mkdir(this.runtimeDir, { recursive: true });
|
|
3781
|
+
await writeFile(this.getRecordPath(record.serverId), JSON.stringify(record, null, 2), "utf-8");
|
|
3782
|
+
}
|
|
3783
|
+
/**
|
|
3784
|
+
* Read a runtime state record by server ID.
|
|
3785
|
+
* @param serverId - Target one-mcp server identifier
|
|
3786
|
+
* @returns Matching runtime record, or null when no record exists
|
|
3787
|
+
*/
|
|
3788
|
+
async read(serverId) {
|
|
3789
|
+
const filePath = this.getRecordPath(serverId);
|
|
3790
|
+
try {
|
|
3791
|
+
const content = await readFile(filePath, "utf-8");
|
|
3792
|
+
const parsed = JSON.parse(content);
|
|
3793
|
+
return isRuntimeStateRecord(parsed) ? parsed : null;
|
|
3794
|
+
} catch (error) {
|
|
3795
|
+
if (isObject$1(error) && "code" in error && error.code === "ENOENT") return null;
|
|
3796
|
+
throw new Error(`Failed to read runtime state for server '${serverId}' from '${filePath}': ${toErrorMessage$1(error)}`);
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
/**
|
|
3800
|
+
* List all persisted runtime records.
|
|
3801
|
+
* @returns Array of runtime records
|
|
3802
|
+
*/
|
|
3803
|
+
async list() {
|
|
3804
|
+
try {
|
|
3805
|
+
const files = (await readdir(this.runtimeDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(RUNTIME_FILE_SUFFIX));
|
|
3806
|
+
return (await Promise.all(files.map(async (file) => {
|
|
3807
|
+
try {
|
|
3808
|
+
const content = await readFile(join(this.runtimeDir, file.name), "utf-8");
|
|
3809
|
+
const parsed = JSON.parse(content);
|
|
3810
|
+
return isRuntimeStateRecord(parsed) ? parsed : null;
|
|
3811
|
+
} catch {
|
|
3812
|
+
return null;
|
|
3370
3813
|
}
|
|
3371
|
-
});
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
};
|
|
3375
|
-
await mcpServer.connect(transport);
|
|
3376
|
-
} else {
|
|
3377
|
-
res.status(400).json({
|
|
3378
|
-
jsonrpc: "2.0",
|
|
3379
|
-
error: {
|
|
3380
|
-
code: -32e3,
|
|
3381
|
-
message: "Bad Request: No valid session ID provided"
|
|
3382
|
-
},
|
|
3383
|
-
id: null
|
|
3384
|
-
});
|
|
3385
|
-
return;
|
|
3814
|
+
}))).filter((record) => record !== null);
|
|
3815
|
+
} catch (error) {
|
|
3816
|
+
if (isObject$1(error) && "code" in error && error.code === "ENOENT") return [];
|
|
3817
|
+
throw new Error(`Failed to list runtime states from '${this.runtimeDir}': ${toErrorMessage$1(error)}`);
|
|
3386
3818
|
}
|
|
3387
|
-
await transport.handleRequest(req, res, req.body);
|
|
3388
3819
|
}
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3820
|
+
/**
|
|
3821
|
+
* Remove a runtime state record by server ID.
|
|
3822
|
+
* @param serverId - Target one-mcp server identifier
|
|
3823
|
+
* @returns Promise that resolves when delete completes
|
|
3824
|
+
*/
|
|
3825
|
+
async remove(serverId) {
|
|
3826
|
+
await rm(this.getRecordPath(serverId), { force: true });
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
|
|
3830
|
+
//#endregion
|
|
3831
|
+
//#region src/services/StopServerService.ts
|
|
3832
|
+
function toErrorMessage(error) {
|
|
3833
|
+
return error instanceof Error ? error.message : String(error);
|
|
3834
|
+
}
|
|
3835
|
+
function isObject(value) {
|
|
3836
|
+
return typeof value === "object" && value !== null;
|
|
3837
|
+
}
|
|
3838
|
+
function isHealthResponse(value) {
|
|
3839
|
+
return isObject(value) && value.status === "ok" && value.transport === "http" && (value.serverId === void 0 || typeof value.serverId === "string");
|
|
3840
|
+
}
|
|
3841
|
+
function isShutdownResponse(value) {
|
|
3842
|
+
return isObject(value) && typeof value.ok === "boolean" && typeof value.message === "string" && (value.serverId === void 0 || typeof value.serverId === "string");
|
|
3843
|
+
}
|
|
3844
|
+
function sleep(delayMs) {
|
|
3845
|
+
return new Promise((resolve$1) => {
|
|
3846
|
+
setTimeout(resolve$1, delayMs);
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Service for resolving runtime targets and stopping them safely.
|
|
3851
|
+
*/
|
|
3852
|
+
var StopServerService = class {
|
|
3853
|
+
runtimeStateService;
|
|
3854
|
+
constructor(runtimeStateService = new RuntimeStateService()) {
|
|
3855
|
+
this.runtimeStateService = runtimeStateService;
|
|
3856
|
+
}
|
|
3857
|
+
/**
|
|
3858
|
+
* Resolve a target runtime and stop it cooperatively.
|
|
3859
|
+
* @param request - Stop request options
|
|
3860
|
+
* @returns Stop result payload
|
|
3861
|
+
*/
|
|
3862
|
+
async stop(request) {
|
|
3863
|
+
const timeoutMs = request.timeoutMs ?? 5e3;
|
|
3864
|
+
const runtime = await this.resolveRuntime(request);
|
|
3865
|
+
const health = await this.fetchHealth(runtime, timeoutMs);
|
|
3866
|
+
if (!health.reachable) {
|
|
3867
|
+
await this.runtimeStateService.remove(runtime.serverId);
|
|
3868
|
+
throw new Error(`Runtime '${runtime.serverId}' is not reachable at http://${runtime.host}:${runtime.port}. Removed stale runtime record.`);
|
|
3394
3869
|
}
|
|
3395
|
-
|
|
3870
|
+
if (!request.force && health.payload?.serverId && health.payload.serverId !== runtime.serverId) throw new Error(`Refusing to stop runtime at http://${runtime.host}:${runtime.port}: expected server ID '${runtime.serverId}' but health endpoint reported '${health.payload.serverId}'. Use --force to override.`);
|
|
3871
|
+
const shutdownToken = request.token ?? runtime.shutdownToken;
|
|
3872
|
+
if (!shutdownToken) throw new Error(`No shutdown token available for runtime '${runtime.serverId}'.`);
|
|
3873
|
+
const shutdownResponse = await this.requestShutdown(runtime, shutdownToken, timeoutMs);
|
|
3874
|
+
await this.waitForShutdown(runtime, timeoutMs);
|
|
3875
|
+
await this.runtimeStateService.remove(runtime.serverId);
|
|
3876
|
+
return {
|
|
3877
|
+
ok: true,
|
|
3878
|
+
serverId: runtime.serverId,
|
|
3879
|
+
host: runtime.host,
|
|
3880
|
+
port: runtime.port,
|
|
3881
|
+
message: shutdownResponse.message
|
|
3882
|
+
};
|
|
3396
3883
|
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3884
|
+
/**
|
|
3885
|
+
* Resolve a runtime record from explicit ID or a unique host/port pair.
|
|
3886
|
+
* @param request - Stop request options
|
|
3887
|
+
* @returns Matching runtime record
|
|
3888
|
+
*/
|
|
3889
|
+
async resolveRuntime(request) {
|
|
3890
|
+
if (request.serverId) {
|
|
3891
|
+
const runtime = await this.runtimeStateService.read(request.serverId);
|
|
3892
|
+
if (!runtime) throw new Error(`No runtime record found for server ID '${request.serverId}'. Start the server with 'one-mcp mcp-serve --type http' first.`);
|
|
3893
|
+
return runtime;
|
|
3402
3894
|
}
|
|
3403
|
-
|
|
3404
|
-
this.
|
|
3895
|
+
if (request.host === void 0 || request.port === void 0) throw new Error("Provide --id or both --host and --port to select a runtime.");
|
|
3896
|
+
const matches = (await this.runtimeStateService.list()).filter((runtime) => runtime.host === request.host && runtime.port === request.port);
|
|
3897
|
+
if (matches.length === 0) throw new Error(`No runtime record found for http://${request.host}:${request.port}. Start the server with 'one-mcp mcp-serve --type http' first.`);
|
|
3898
|
+
if (matches.length > 1) throw new Error(`Multiple runtime records match http://${request.host}:${request.port}. Retry with --id to avoid stopping the wrong server.`);
|
|
3899
|
+
return matches[0];
|
|
3405
3900
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3901
|
+
/**
|
|
3902
|
+
* Read the runtime health payload.
|
|
3903
|
+
* @param runtime - Runtime to query
|
|
3904
|
+
* @param timeoutMs - Request timeout in milliseconds
|
|
3905
|
+
* @returns Reachability status and optional payload
|
|
3906
|
+
*/
|
|
3907
|
+
async fetchHealth(runtime, timeoutMs) {
|
|
3908
|
+
try {
|
|
3909
|
+
const response = await this.fetchWithTimeout(`http://${runtime.host}:${runtime.port}/health`, { method: "GET" }, timeoutMs);
|
|
3910
|
+
if (!response.ok) return { reachable: false };
|
|
3911
|
+
const payload = await response.json();
|
|
3912
|
+
if (!isHealthResponse(payload)) throw new Error("Received invalid health response payload.");
|
|
3913
|
+
return {
|
|
3914
|
+
reachable: true,
|
|
3915
|
+
payload
|
|
3916
|
+
};
|
|
3917
|
+
} catch {
|
|
3918
|
+
return { reachable: false };
|
|
3919
|
+
}
|
|
3421
3920
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3921
|
+
/**
|
|
3922
|
+
* Send authenticated shutdown request to the admin endpoint.
|
|
3923
|
+
* @param runtime - Runtime to stop
|
|
3924
|
+
* @param shutdownToken - Bearer token for the admin endpoint
|
|
3925
|
+
* @param timeoutMs - Request timeout in milliseconds
|
|
3926
|
+
* @returns Parsed shutdown response payload
|
|
3927
|
+
*/
|
|
3928
|
+
async requestShutdown(runtime, shutdownToken, timeoutMs) {
|
|
3929
|
+
const response = await this.fetchWithTimeout(`http://${runtime.host}:${runtime.port}/admin/shutdown`, {
|
|
3930
|
+
method: "POST",
|
|
3931
|
+
headers: { Authorization: `Bearer ${shutdownToken}` }
|
|
3932
|
+
}, timeoutMs);
|
|
3933
|
+
const payload = await response.json();
|
|
3934
|
+
if (!isShutdownResponse(payload)) throw new Error("Received invalid shutdown response payload.");
|
|
3935
|
+
if (!response.ok || !payload.ok) throw new Error(payload.message);
|
|
3936
|
+
return payload;
|
|
3435
3937
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3938
|
+
/**
|
|
3939
|
+
* Poll until the target runtime is no longer reachable.
|
|
3940
|
+
* @param runtime - Runtime expected to stop
|
|
3941
|
+
* @param timeoutMs - Maximum wait time in milliseconds
|
|
3942
|
+
* @returns Promise that resolves when shutdown is observed
|
|
3943
|
+
*/
|
|
3944
|
+
async waitForShutdown(runtime, timeoutMs) {
|
|
3945
|
+
const deadline = Date.now() + timeoutMs;
|
|
3946
|
+
while (Date.now() < deadline) {
|
|
3947
|
+
if (!(await this.fetchHealth(runtime, Math.max(250, deadline - Date.now()))).reachable) return;
|
|
3948
|
+
await sleep(200);
|
|
3949
|
+
}
|
|
3950
|
+
throw new Error(`Timed out waiting for runtime '${runtime.serverId}' to stop at http://${runtime.host}:${runtime.port}.`);
|
|
3438
3951
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3952
|
+
/**
|
|
3953
|
+
* Perform a fetch with an abort timeout.
|
|
3954
|
+
* @param url - Target URL
|
|
3955
|
+
* @param init - Fetch options
|
|
3956
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
3957
|
+
* @returns Fetch response
|
|
3958
|
+
*/
|
|
3959
|
+
async fetchWithTimeout(url, init, timeoutMs) {
|
|
3960
|
+
const controller = new AbortController();
|
|
3961
|
+
const timeoutId = setTimeout(() => {
|
|
3962
|
+
controller.abort();
|
|
3963
|
+
}, timeoutMs);
|
|
3964
|
+
try {
|
|
3965
|
+
return await fetch(url, {
|
|
3966
|
+
...init,
|
|
3967
|
+
signal: controller.signal
|
|
3968
|
+
});
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
throw new Error(`Request to '${url}' failed: ${toErrorMessage(error)}`);
|
|
3971
|
+
} finally {
|
|
3972
|
+
clearTimeout(timeoutId);
|
|
3973
|
+
}
|
|
3441
3974
|
}
|
|
3442
3975
|
};
|
|
3443
3976
|
|
|
3444
3977
|
//#endregion
|
|
3445
|
-
export {
|
|
3978
|
+
export { findConfigFile as _, SseTransportHandler as a, createServer as c, SearchListToolsTool as d, DescribeToolsTool as f, generateServerId as g, DefinitionsCacheService as h, StdioTransportHandler as i, version as l, McpClientManagerService as m, RuntimeStateService as n, HttpTransportHandler as o, SkillService as p, StdioHttpTransportHandler as r, TRANSPORT_MODE as s, StopServerService as t, UseToolTool as u, ConfigFetcherService as v };
|