@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.
@@ -40,11 +40,13 @@ let __modelcontextprotocol_sdk_client_stdio_js = require("@modelcontextprotocol/
40
40
  let __modelcontextprotocol_sdk_client_sse_js = require("@modelcontextprotocol/sdk/client/sse.js");
41
41
  let __modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
42
42
  let liquidjs = require("liquidjs");
43
- let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
44
- let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
43
+ let node_events = require("node:events");
44
+ let node_util = require("node:util");
45
+ let __modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
45
46
  let express = require("express");
46
47
  express = __toESM(express);
47
- let __modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
48
+ let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
49
+ let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
48
50
 
49
51
  //#region src/utils/mcpConfigSchema.ts
50
52
  /**
@@ -1166,7 +1168,7 @@ const DEFAULT_SERVER_ID = "unknown";
1166
1168
  function isYamlPath(filePath) {
1167
1169
  return filePath.endsWith(".yaml") || filePath.endsWith(".yml");
1168
1170
  }
1169
- function toErrorMessage(error) {
1171
+ function toErrorMessage$3(error) {
1170
1172
  return error instanceof Error ? error.message : String(error);
1171
1173
  }
1172
1174
  function sanitizeConfigPathForFilename(configFilePath) {
@@ -1357,7 +1359,7 @@ var DefinitionsCacheService = class {
1357
1359
  } catch (error) {
1358
1360
  failures.push({
1359
1361
  serverName: client.serverName,
1360
- error: toErrorMessage(error)
1362
+ error: toErrorMessage$3(error)
1361
1363
  });
1362
1364
  return null;
1363
1365
  }
@@ -1395,7 +1397,7 @@ var DefinitionsCacheService = class {
1395
1397
  arguments: prompt.arguments?.map((arg) => ({ ...arg }))
1396
1398
  }));
1397
1399
  } catch (error) {
1398
- console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage(error)}`);
1400
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage$3(error)}`);
1399
1401
  return [];
1400
1402
  }
1401
1403
  }
@@ -1408,7 +1410,7 @@ var DefinitionsCacheService = class {
1408
1410
  mimeType: resource.mimeType
1409
1411
  }));
1410
1412
  } catch (error) {
1411
- console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage(error)}`);
1413
+ console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage$3(error)}`);
1412
1414
  return [];
1413
1415
  }
1414
1416
  }
@@ -1437,7 +1439,7 @@ var DefinitionsCacheService = class {
1437
1439
  autoDetected: true
1438
1440
  };
1439
1441
  } catch (error) {
1440
- console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage(error)}`);
1442
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage$3(error)}`);
1441
1443
  return null;
1442
1444
  }
1443
1445
  }));
@@ -2806,7 +2808,7 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
2806
2808
 
2807
2809
  //#endregion
2808
2810
  //#region package.json
2809
- var version = "0.3.11";
2811
+ var version = "0.3.13";
2810
2812
 
2811
2813
  //#endregion
2812
2814
  //#region src/server/index.ts
@@ -3126,28 +3128,323 @@ async function createServer(options) {
3126
3128
  }
3127
3129
 
3128
3130
  //#endregion
3129
- //#region src/transports/stdio.ts
3131
+ //#region src/types/index.ts
3130
3132
  /**
3131
- * Stdio transport handler for MCP server
3132
- * Used for command-line and direct integrations
3133
+ * Transport mode constants
3133
3134
  */
3134
- var StdioTransportHandler = class {
3135
- server;
3136
- transport = null;
3137
- constructor(server) {
3138
- this.server = server;
3135
+ const TRANSPORT_MODE = {
3136
+ STDIO: "stdio",
3137
+ HTTP: "http",
3138
+ SSE: "sse"
3139
+ };
3140
+
3141
+ //#endregion
3142
+ //#region src/transports/http.ts
3143
+ /**
3144
+ * HTTP Transport Handler
3145
+ *
3146
+ * DESIGN PATTERNS:
3147
+ * - Transport handler pattern implementing TransportHandler interface
3148
+ * - Session management for stateful connections
3149
+ * - Streamable HTTP protocol (2025-03-26) with resumability support
3150
+ * - Factory pattern for creating MCP server instances per session
3151
+ *
3152
+ * CODING STANDARDS:
3153
+ * - Use async/await for all asynchronous operations
3154
+ * - Implement proper session lifecycle management
3155
+ * - Handle errors gracefully with appropriate HTTP status codes
3156
+ * - Provide health check endpoint for monitoring
3157
+ * - Clean up resources on shutdown
3158
+ *
3159
+ * AVOID:
3160
+ * - Sharing MCP server instances across sessions (use factory pattern)
3161
+ * - Forgetting to clean up sessions on disconnect
3162
+ * - Missing error handling for request processing
3163
+ * - Hardcoded configuration (use TransportConfig)
3164
+ */
3165
+ /**
3166
+ * HTTP session manager
3167
+ */
3168
+ var HttpFullSessionManager = class {
3169
+ sessions = /* @__PURE__ */ new Map();
3170
+ getSession(sessionId) {
3171
+ return this.sessions.get(sessionId);
3172
+ }
3173
+ setSession(sessionId, transport, server) {
3174
+ this.sessions.set(sessionId, {
3175
+ transport,
3176
+ server
3177
+ });
3178
+ }
3179
+ deleteSession(sessionId) {
3180
+ const session = this.sessions.get(sessionId);
3181
+ if (session) session.server.close();
3182
+ this.sessions.delete(sessionId);
3183
+ }
3184
+ hasSession(sessionId) {
3185
+ return this.sessions.has(sessionId);
3186
+ }
3187
+ clear() {
3188
+ for (const session of this.sessions.values()) session.server.close();
3189
+ this.sessions.clear();
3190
+ }
3191
+ };
3192
+ function toErrorMessage$2(error) {
3193
+ return error instanceof Error ? error.message : String(error);
3194
+ }
3195
+ const ADMIN_RATE_LIMIT_WINDOW_MS = 6e4;
3196
+ const ADMIN_RATE_LIMIT_MAX_REQUESTS = 5;
3197
+ /**
3198
+ * Simple in-memory rate limiter for the admin shutdown endpoint.
3199
+ * Tracks request timestamps per IP within a sliding window.
3200
+ */
3201
+ var AdminRateLimiter = class {
3202
+ requests = /* @__PURE__ */ new Map();
3203
+ isAllowed(ip) {
3204
+ const now = Date.now();
3205
+ const windowStart = now - ADMIN_RATE_LIMIT_WINDOW_MS;
3206
+ const timestamps = (this.requests.get(ip) ?? []).filter((t) => t > windowStart);
3207
+ if (timestamps.length >= ADMIN_RATE_LIMIT_MAX_REQUESTS) {
3208
+ this.requests.set(ip, timestamps);
3209
+ return false;
3210
+ }
3211
+ timestamps.push(now);
3212
+ this.requests.set(ip, timestamps);
3213
+ return true;
3214
+ }
3215
+ };
3216
+ /**
3217
+ * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
3218
+ * Provides stateful session management with resumability support
3219
+ */
3220
+ var HttpTransportHandler = class {
3221
+ serverFactory;
3222
+ app;
3223
+ server = null;
3224
+ sessionManager;
3225
+ config;
3226
+ adminOptions;
3227
+ adminRateLimiter = new AdminRateLimiter();
3228
+ constructor(serverFactory, config, adminOptions) {
3229
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
3230
+ this.app = (0, express.default)();
3231
+ this.sessionManager = new HttpFullSessionManager();
3232
+ this.config = {
3233
+ mode: config.mode,
3234
+ port: config.port ?? 3e3,
3235
+ host: config.host ?? "localhost"
3236
+ };
3237
+ this.adminOptions = adminOptions;
3238
+ this.setupMiddleware();
3239
+ this.setupRoutes();
3240
+ }
3241
+ setupMiddleware() {
3242
+ this.app.use(express.default.json());
3243
+ }
3244
+ setupRoutes() {
3245
+ this.app.post("/mcp", async (req, res) => {
3246
+ try {
3247
+ await this.handlePostRequest(req, res);
3248
+ } catch (error) {
3249
+ console.error(`Failed to handle MCP POST request: ${toErrorMessage$2(error)}`);
3250
+ res.status(500).json({
3251
+ jsonrpc: "2.0",
3252
+ error: {
3253
+ code: -32603,
3254
+ message: "Failed to handle MCP POST request."
3255
+ },
3256
+ id: null
3257
+ });
3258
+ }
3259
+ });
3260
+ this.app.get("/mcp", async (req, res) => {
3261
+ try {
3262
+ await this.handleGetRequest(req, res);
3263
+ } catch (error) {
3264
+ console.error(`Failed to handle MCP GET request: ${toErrorMessage$2(error)}`);
3265
+ res.status(500).send("Failed to handle MCP GET request.");
3266
+ }
3267
+ });
3268
+ this.app.delete("/mcp", async (req, res) => {
3269
+ try {
3270
+ await this.handleDeleteRequest(req, res);
3271
+ } catch (error) {
3272
+ console.error(`Failed to handle MCP DELETE request: ${toErrorMessage$2(error)}`);
3273
+ res.status(500).send("Failed to handle MCP DELETE request.");
3274
+ }
3275
+ });
3276
+ this.app.get("/health", (_req, res) => {
3277
+ const payload = {
3278
+ status: "ok",
3279
+ transport: "http",
3280
+ serverId: this.adminOptions?.serverId
3281
+ };
3282
+ res.json(payload);
3283
+ });
3284
+ this.app.post("/admin/shutdown", async (req, res) => {
3285
+ try {
3286
+ const clientIp = req.ip ?? req.socket.remoteAddress ?? "unknown";
3287
+ if (!this.adminRateLimiter.isAllowed(clientIp)) {
3288
+ const payload = {
3289
+ ok: false,
3290
+ message: "Too many shutdown requests. Try again later.",
3291
+ serverId: this.adminOptions?.serverId
3292
+ };
3293
+ res.status(429).json(payload);
3294
+ return;
3295
+ }
3296
+ await this.handleAdminShutdownRequest(req, res);
3297
+ } catch (error) {
3298
+ console.error(`Failed to process shutdown request: ${toErrorMessage$2(error)}`);
3299
+ const payload = {
3300
+ ok: false,
3301
+ message: "Failed to process shutdown request.",
3302
+ serverId: this.adminOptions?.serverId
3303
+ };
3304
+ res.status(500).json(payload);
3305
+ }
3306
+ });
3307
+ }
3308
+ isAuthorizedShutdownRequest(req) {
3309
+ const expectedToken = this.adminOptions?.shutdownToken;
3310
+ if (!expectedToken) return false;
3311
+ const authHeader = req.headers.authorization;
3312
+ if (typeof authHeader === "string" && authHeader.startsWith("Bearer ")) return authHeader.slice(7) === expectedToken;
3313
+ const tokenHeader = req.headers["x-one-mcp-shutdown-token"];
3314
+ return typeof tokenHeader === "string" && tokenHeader === expectedToken;
3315
+ }
3316
+ async handleAdminShutdownRequest(req, res) {
3317
+ try {
3318
+ if (!this.adminOptions?.onShutdownRequested) {
3319
+ const payload$1 = {
3320
+ ok: false,
3321
+ message: "Shutdown endpoint is not enabled for this server instance.",
3322
+ serverId: this.adminOptions?.serverId
3323
+ };
3324
+ res.status(404).json(payload$1);
3325
+ return;
3326
+ }
3327
+ if (!this.isAuthorizedShutdownRequest(req)) {
3328
+ const payload$1 = {
3329
+ ok: false,
3330
+ message: "Unauthorized shutdown request: invalid or missing shutdown token.",
3331
+ serverId: this.adminOptions?.serverId
3332
+ };
3333
+ res.status(401).json(payload$1);
3334
+ return;
3335
+ }
3336
+ const payload = {
3337
+ ok: true,
3338
+ message: "Shutdown request accepted. Stopping server gracefully.",
3339
+ serverId: this.adminOptions?.serverId
3340
+ };
3341
+ res.json(payload);
3342
+ await this.adminOptions.onShutdownRequested();
3343
+ } catch (error) {
3344
+ throw new Error(`Failed to handle admin shutdown request: ${toErrorMessage$2(error)}`);
3345
+ }
3346
+ }
3347
+ async handlePostRequest(req, res) {
3348
+ const sessionId = req.headers["mcp-session-id"];
3349
+ let transport;
3350
+ if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
3351
+ else if (!sessionId && (0, __modelcontextprotocol_sdk_types_js.isInitializeRequest)(req.body)) {
3352
+ const mcpServer = this.serverFactory();
3353
+ transport = new __modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({
3354
+ sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
3355
+ enableJsonResponse: true,
3356
+ onsessioninitialized: (initializedSessionId) => {
3357
+ this.sessionManager.setSession(initializedSessionId, transport, mcpServer);
3358
+ }
3359
+ });
3360
+ transport.onclose = () => {
3361
+ if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
3362
+ };
3363
+ try {
3364
+ await mcpServer.connect(transport);
3365
+ } catch (error) {
3366
+ throw new Error(`Failed to connect MCP server transport for initialization request: ${toErrorMessage$2(error)}`);
3367
+ }
3368
+ } else {
3369
+ res.status(400).json({
3370
+ jsonrpc: "2.0",
3371
+ error: {
3372
+ code: -32e3,
3373
+ message: sessionId === void 0 ? "Bad Request: missing session ID and request body is not an initialize request." : `Bad Request: unknown session ID '${sessionId}'.`
3374
+ },
3375
+ id: null
3376
+ });
3377
+ return;
3378
+ }
3379
+ try {
3380
+ await transport.handleRequest(req, res, req.body);
3381
+ } catch (error) {
3382
+ throw new Error(`Failed handling MCP transport request: ${toErrorMessage$2(error)}`);
3383
+ }
3384
+ }
3385
+ async handleGetRequest(req, res) {
3386
+ const sessionId = req.headers["mcp-session-id"];
3387
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3388
+ res.status(400).send("Invalid or missing session ID");
3389
+ return;
3390
+ }
3391
+ const session = this.sessionManager.getSession(sessionId);
3392
+ try {
3393
+ await session.transport.handleRequest(req, res);
3394
+ } catch (error) {
3395
+ throw new Error(`Failed handling MCP GET request for session '${sessionId}': ${toErrorMessage$2(error)}`);
3396
+ }
3397
+ }
3398
+ async handleDeleteRequest(req, res) {
3399
+ const sessionId = req.headers["mcp-session-id"];
3400
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3401
+ res.status(400).send("Invalid or missing session ID");
3402
+ return;
3403
+ }
3404
+ const session = this.sessionManager.getSession(sessionId);
3405
+ try {
3406
+ await session.transport.handleRequest(req, res);
3407
+ } catch (error) {
3408
+ throw new Error(`Failed handling MCP DELETE request for session '${sessionId}': ${toErrorMessage$2(error)}`);
3409
+ }
3410
+ this.sessionManager.deleteSession(sessionId);
3139
3411
  }
3140
3412
  async start() {
3141
- this.transport = new __modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
3142
- await this.server.connect(this.transport);
3143
- console.error("@agiflowai/one-mcp MCP server started on stdio");
3413
+ try {
3414
+ const server = this.app.listen(this.config.port, this.config.host);
3415
+ this.server = server;
3416
+ const listeningPromise = (async () => {
3417
+ await (0, node_events.once)(server, "listening");
3418
+ })();
3419
+ const errorPromise = (async () => {
3420
+ const [error] = await (0, node_events.once)(server, "error");
3421
+ throw error instanceof Error ? error : new Error(String(error));
3422
+ })();
3423
+ await Promise.race([listeningPromise, errorPromise]);
3424
+ console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
3425
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
3426
+ } catch (error) {
3427
+ this.server = null;
3428
+ throw new Error(`Failed to start HTTP transport: ${toErrorMessage$2(error)}`);
3429
+ }
3144
3430
  }
3145
3431
  async stop() {
3146
- if (this.transport) {
3147
- await this.transport.close();
3148
- this.transport = null;
3432
+ if (!this.server) return;
3433
+ this.sessionManager.clear();
3434
+ const closeServer = (0, node_util.promisify)(this.server.close.bind(this.server));
3435
+ try {
3436
+ await closeServer();
3437
+ this.server = null;
3438
+ } catch (error) {
3439
+ throw new Error(`Failed to stop HTTP transport: ${toErrorMessage$2(error)}`);
3149
3440
  }
3150
3441
  }
3442
+ getPort() {
3443
+ return this.config.port;
3444
+ }
3445
+ getHost() {
3446
+ return this.config.host;
3447
+ }
3151
3448
  };
3152
3449
 
3153
3450
  //#endregion
@@ -3293,180 +3590,416 @@ var SseTransportHandler = class {
3293
3590
  };
3294
3591
 
3295
3592
  //#endregion
3296
- //#region src/transports/http.ts
3593
+ //#region src/transports/stdio.ts
3297
3594
  /**
3298
- * HTTP Transport Handler
3595
+ * Stdio transport handler for MCP server
3596
+ * Used for command-line and direct integrations
3597
+ */
3598
+ var StdioTransportHandler = class {
3599
+ server;
3600
+ transport = null;
3601
+ constructor(server) {
3602
+ this.server = server;
3603
+ }
3604
+ async start() {
3605
+ this.transport = new __modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
3606
+ await this.server.connect(this.transport);
3607
+ console.error("@agiflowai/one-mcp MCP server started on stdio");
3608
+ }
3609
+ async stop() {
3610
+ if (this.transport) {
3611
+ await this.transport.close();
3612
+ this.transport = null;
3613
+ }
3614
+ }
3615
+ };
3616
+
3617
+ //#endregion
3618
+ //#region src/transports/stdio-http.ts
3619
+ /**
3620
+ * STDIO-HTTP Proxy Transport
3299
3621
  *
3300
3622
  * DESIGN PATTERNS:
3301
3623
  * - Transport handler pattern implementing TransportHandler interface
3302
- * - Session management for stateful connections
3303
- * - Streamable HTTP protocol (2025-03-26) with resumability support
3304
- * - Factory pattern for creating MCP server instances per session
3624
+ * - STDIO transport with MCP request forwarding to HTTP backend
3625
+ * - Graceful cleanup with error isolation
3305
3626
  *
3306
3627
  * CODING STANDARDS:
3307
- * - Use async/await for all asynchronous operations
3308
- * - Implement proper session lifecycle management
3309
- * - Handle errors gracefully with appropriate HTTP status codes
3310
- * - Provide health check endpoint for monitoring
3311
- * - Clean up resources on shutdown
3628
+ * - Use StdioServerTransport for stdio communication
3629
+ * - Reuse a single StreamableHTTP client connection
3630
+ * - Wrap async operations with try-catch and descriptive errors
3312
3631
  *
3313
3632
  * AVOID:
3314
- * - Sharing MCP server instances across sessions (use factory pattern)
3315
- * - Forgetting to clean up sessions on disconnect
3316
- * - Missing error handling for request processing
3317
- * - Hardcoded configuration (use TransportConfig)
3633
+ * - Starting HTTP server lifecycle in this transport entry point
3634
+ * - Recreating HTTP client per request
3635
+ * - Swallowing cleanup failures silently
3318
3636
  */
3319
3637
  /**
3320
- * HTTP session manager
3638
+ * Transport that serves MCP over stdio and forwards MCP requests to an HTTP endpoint.
3321
3639
  */
3322
- var HttpFullSessionManager = class {
3323
- sessions = /* @__PURE__ */ new Map();
3324
- getSession(sessionId) {
3325
- return this.sessions.get(sessionId);
3326
- }
3327
- setSession(sessionId, transport, server) {
3328
- this.sessions.set(sessionId, {
3329
- transport,
3330
- server
3331
- });
3640
+ var StdioHttpTransportHandler = class {
3641
+ endpoint;
3642
+ stdioProxyServer = null;
3643
+ stdioTransport = null;
3644
+ httpClient = null;
3645
+ constructor(config) {
3646
+ this.endpoint = config.endpoint;
3332
3647
  }
3333
- deleteSession(sessionId) {
3334
- const session = this.sessions.get(sessionId);
3335
- if (session) session.server.close();
3336
- this.sessions.delete(sessionId);
3648
+ async start() {
3649
+ try {
3650
+ const httpClientTransport = new __modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(this.endpoint);
3651
+ const client = new __modelcontextprotocol_sdk_client_index_js.Client({
3652
+ name: "@agiflowai/one-mcp-stdio-http-proxy",
3653
+ version: "0.1.0"
3654
+ }, { capabilities: {} });
3655
+ await client.connect(httpClientTransport);
3656
+ this.httpClient = client;
3657
+ this.stdioProxyServer = this.createProxyServer(client);
3658
+ this.stdioTransport = new __modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
3659
+ await this.stdioProxyServer.connect(this.stdioTransport);
3660
+ console.error(`@agiflowai/one-mcp MCP stdio proxy connected to ${this.endpoint.toString()}`);
3661
+ } catch (error) {
3662
+ await this.stop();
3663
+ throw new Error(`Failed to start stdio-http proxy transport: ${error instanceof Error ? error.message : String(error)}`);
3664
+ }
3337
3665
  }
3338
- hasSession(sessionId) {
3339
- return this.sessions.has(sessionId);
3666
+ async stop() {
3667
+ const stdioTransport = this.stdioTransport;
3668
+ const stdioProxyServer = this.stdioProxyServer;
3669
+ const httpClient = this.httpClient;
3670
+ this.stdioTransport = null;
3671
+ this.stdioProxyServer = null;
3672
+ this.httpClient = null;
3673
+ const cleanupErrors = [];
3674
+ await Promise.all([
3675
+ (async () => {
3676
+ try {
3677
+ if (stdioTransport) await stdioTransport.close();
3678
+ } catch (error) {
3679
+ cleanupErrors.push(`failed closing stdio transport: ${error instanceof Error ? error.message : String(error)}`);
3680
+ }
3681
+ })(),
3682
+ (async () => {
3683
+ try {
3684
+ if (stdioProxyServer) await stdioProxyServer.close();
3685
+ } catch (error) {
3686
+ cleanupErrors.push(`failed closing stdio proxy server: ${error instanceof Error ? error.message : String(error)}`);
3687
+ }
3688
+ })(),
3689
+ (async () => {
3690
+ try {
3691
+ if (httpClient) await httpClient.close();
3692
+ } catch (error) {
3693
+ cleanupErrors.push(`failed closing http client: ${error instanceof Error ? error.message : String(error)}`);
3694
+ }
3695
+ })()
3696
+ ]);
3697
+ if (cleanupErrors.length > 0) throw new Error(`Failed to stop stdio-http proxy transport: ${cleanupErrors.join("; ")}`);
3340
3698
  }
3341
- clear() {
3342
- for (const session of this.sessions.values()) session.server.close();
3343
- this.sessions.clear();
3699
+ createProxyServer(client) {
3700
+ const proxyServer = new __modelcontextprotocol_sdk_server_index_js.Server({
3701
+ name: "@agiflowai/one-mcp-stdio-http-proxy",
3702
+ version: "0.1.0"
3703
+ }, { capabilities: {
3704
+ tools: {},
3705
+ resources: {},
3706
+ prompts: {}
3707
+ } });
3708
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => {
3709
+ try {
3710
+ return await client.listTools();
3711
+ } catch (error) {
3712
+ throw new Error(`Failed forwarding tools/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3713
+ }
3714
+ });
3715
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
3716
+ try {
3717
+ return await client.callTool({
3718
+ name: request.params.name,
3719
+ arguments: request.params.arguments
3720
+ });
3721
+ } catch (error) {
3722
+ throw new Error(`Failed forwarding tools/call (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3723
+ }
3724
+ });
3725
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListResourcesRequestSchema, async () => {
3726
+ try {
3727
+ return await client.listResources();
3728
+ } catch (error) {
3729
+ throw new Error(`Failed forwarding resources/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3730
+ }
3731
+ });
3732
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.ReadResourceRequestSchema, async (request) => {
3733
+ try {
3734
+ return await client.readResource({ uri: request.params.uri });
3735
+ } catch (error) {
3736
+ throw new Error(`Failed forwarding resources/read (${request.params.uri}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3737
+ }
3738
+ });
3739
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListPromptsRequestSchema, async () => {
3740
+ try {
3741
+ return await client.listPrompts();
3742
+ } catch (error) {
3743
+ throw new Error(`Failed forwarding prompts/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3744
+ }
3745
+ });
3746
+ proxyServer.setRequestHandler(__modelcontextprotocol_sdk_types_js.GetPromptRequestSchema, async (request) => {
3747
+ try {
3748
+ return await client.getPrompt({
3749
+ name: request.params.name,
3750
+ arguments: request.params.arguments
3751
+ });
3752
+ } catch (error) {
3753
+ throw new Error(`Failed forwarding prompts/get (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3754
+ }
3755
+ });
3756
+ return proxyServer;
3344
3757
  }
3345
3758
  };
3759
+
3760
+ //#endregion
3761
+ //#region src/services/RuntimeStateService.ts
3346
3762
  /**
3347
- * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
3348
- * Provides stateful session management with resumability support
3763
+ * RuntimeStateService
3764
+ *
3765
+ * Persists runtime metadata for HTTP one-mcp instances so external commands
3766
+ * (for example `one-mcp stop`) can discover and target the correct server.
3349
3767
  */
3350
- var HttpTransportHandler = class {
3351
- serverFactory;
3352
- app;
3353
- server = null;
3354
- sessionManager;
3355
- config;
3356
- constructor(serverFactory, config) {
3357
- this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
3358
- this.app = (0, express.default)();
3359
- this.sessionManager = new HttpFullSessionManager();
3360
- this.config = {
3361
- mode: config.mode,
3362
- port: config.port ?? 3e3,
3363
- host: config.host ?? "localhost"
3364
- };
3365
- this.setupMiddleware();
3366
- this.setupRoutes();
3768
+ const RUNTIME_DIR_NAME = "runtimes";
3769
+ const RUNTIME_FILE_SUFFIX = ".runtime.json";
3770
+ function isObject$1(value) {
3771
+ return typeof value === "object" && value !== null;
3772
+ }
3773
+ function isRuntimeStateRecord(value) {
3774
+ if (!isObject$1(value)) return false;
3775
+ 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
+ }
3777
+ function toErrorMessage$1(error) {
3778
+ return error instanceof Error ? error.message : String(error);
3779
+ }
3780
+ /**
3781
+ * Runtime state persistence implementation.
3782
+ */
3783
+ var RuntimeStateService = class RuntimeStateService {
3784
+ runtimeDir;
3785
+ constructor(runtimeDir) {
3786
+ this.runtimeDir = runtimeDir ?? RuntimeStateService.getDefaultRuntimeDir();
3367
3787
  }
3368
- setupMiddleware() {
3369
- this.app.use(express.default.json());
3788
+ /**
3789
+ * Resolve default runtime directory under the user's home cache path.
3790
+ * @returns Absolute runtime directory path
3791
+ */
3792
+ static getDefaultRuntimeDir() {
3793
+ return (0, node_path.join)((0, node_os.homedir)(), ".aicode-toolkit", "one-mcp", RUNTIME_DIR_NAME);
3370
3794
  }
3371
- setupRoutes() {
3372
- this.app.post("/mcp", async (req, res) => {
3373
- await this.handlePostRequest(req, res);
3374
- });
3375
- this.app.get("/mcp", async (req, res) => {
3376
- await this.handleGetRequest(req, res);
3377
- });
3378
- this.app.delete("/mcp", async (req, res) => {
3379
- await this.handleDeleteRequest(req, res);
3380
- });
3381
- this.app.get("/health", (_req, res) => {
3382
- res.json({
3383
- status: "ok",
3384
- transport: "http"
3385
- });
3386
- });
3795
+ /**
3796
+ * Build runtime state file path for a given server ID.
3797
+ * @param serverId - Target one-mcp server identifier
3798
+ * @returns Absolute runtime file path
3799
+ */
3800
+ getRecordPath(serverId) {
3801
+ return (0, node_path.join)(this.runtimeDir, `${serverId}${RUNTIME_FILE_SUFFIX}`);
3387
3802
  }
3388
- async handlePostRequest(req, res) {
3389
- const sessionId = req.headers["mcp-session-id"];
3390
- let transport;
3391
- if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
3392
- else if (!sessionId && (0, __modelcontextprotocol_sdk_types_js.isInitializeRequest)(req.body)) {
3393
- const mcpServer = this.serverFactory();
3394
- transport = new __modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({
3395
- sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
3396
- enableJsonResponse: true,
3397
- onsessioninitialized: (sessionId$1) => {
3398
- this.sessionManager.setSession(sessionId$1, transport, mcpServer);
3803
+ /**
3804
+ * Persist a runtime state record.
3805
+ * @param record - Runtime metadata to persist
3806
+ * @returns Promise that resolves when write completes
3807
+ */
3808
+ async write(record) {
3809
+ await (0, node_fs_promises.mkdir)(this.runtimeDir, { recursive: true });
3810
+ await (0, node_fs_promises.writeFile)(this.getRecordPath(record.serverId), JSON.stringify(record, null, 2), "utf-8");
3811
+ }
3812
+ /**
3813
+ * Read a runtime state record by server ID.
3814
+ * @param serverId - Target one-mcp server identifier
3815
+ * @returns Matching runtime record, or null when no record exists
3816
+ */
3817
+ async read(serverId) {
3818
+ const filePath = this.getRecordPath(serverId);
3819
+ try {
3820
+ const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
3821
+ const parsed = JSON.parse(content);
3822
+ return isRuntimeStateRecord(parsed) ? parsed : null;
3823
+ } catch (error) {
3824
+ if (isObject$1(error) && "code" in error && error.code === "ENOENT") return null;
3825
+ throw new Error(`Failed to read runtime state for server '${serverId}' from '${filePath}': ${toErrorMessage$1(error)}`);
3826
+ }
3827
+ }
3828
+ /**
3829
+ * List all persisted runtime records.
3830
+ * @returns Array of runtime records
3831
+ */
3832
+ async list() {
3833
+ try {
3834
+ const files = (await (0, node_fs_promises.readdir)(this.runtimeDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(RUNTIME_FILE_SUFFIX));
3835
+ return (await Promise.all(files.map(async (file) => {
3836
+ try {
3837
+ const content = await (0, node_fs_promises.readFile)((0, node_path.join)(this.runtimeDir, file.name), "utf-8");
3838
+ const parsed = JSON.parse(content);
3839
+ return isRuntimeStateRecord(parsed) ? parsed : null;
3840
+ } catch {
3841
+ return null;
3399
3842
  }
3400
- });
3401
- transport.onclose = () => {
3402
- if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
3403
- };
3404
- await mcpServer.connect(transport);
3405
- } else {
3406
- res.status(400).json({
3407
- jsonrpc: "2.0",
3408
- error: {
3409
- code: -32e3,
3410
- message: "Bad Request: No valid session ID provided"
3411
- },
3412
- id: null
3413
- });
3414
- return;
3843
+ }))).filter((record) => record !== null);
3844
+ } catch (error) {
3845
+ if (isObject$1(error) && "code" in error && error.code === "ENOENT") return [];
3846
+ throw new Error(`Failed to list runtime states from '${this.runtimeDir}': ${toErrorMessage$1(error)}`);
3415
3847
  }
3416
- await transport.handleRequest(req, res, req.body);
3417
3848
  }
3418
- async handleGetRequest(req, res) {
3419
- const sessionId = req.headers["mcp-session-id"];
3420
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3421
- res.status(400).send("Invalid or missing session ID");
3422
- return;
3849
+ /**
3850
+ * Remove a runtime state record by server ID.
3851
+ * @param serverId - Target one-mcp server identifier
3852
+ * @returns Promise that resolves when delete completes
3853
+ */
3854
+ async remove(serverId) {
3855
+ await (0, node_fs_promises.rm)(this.getRecordPath(serverId), { force: true });
3856
+ }
3857
+ };
3858
+
3859
+ //#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;
3866
+ }
3867
+ function isHealthResponse(value) {
3868
+ return isObject(value) && value.status === "ok" && value.transport === "http" && (value.serverId === void 0 || typeof value.serverId === "string");
3869
+ }
3870
+ function isShutdownResponse(value) {
3871
+ return isObject(value) && typeof value.ok === "boolean" && typeof value.message === "string" && (value.serverId === void 0 || typeof value.serverId === "string");
3872
+ }
3873
+ function sleep(delayMs) {
3874
+ return new Promise((resolve$2) => {
3875
+ setTimeout(resolve$2, delayMs);
3876
+ });
3877
+ }
3878
+ /**
3879
+ * Service for resolving runtime targets and stopping them safely.
3880
+ */
3881
+ var StopServerService = class {
3882
+ runtimeStateService;
3883
+ constructor(runtimeStateService = new RuntimeStateService()) {
3884
+ this.runtimeStateService = runtimeStateService;
3885
+ }
3886
+ /**
3887
+ * Resolve a target runtime and stop it cooperatively.
3888
+ * @param request - Stop request options
3889
+ * @returns Stop result payload
3890
+ */
3891
+ async stop(request) {
3892
+ const timeoutMs = request.timeoutMs ?? 5e3;
3893
+ const runtime = await this.resolveRuntime(request);
3894
+ const health = await this.fetchHealth(runtime, timeoutMs);
3895
+ if (!health.reachable) {
3896
+ await this.runtimeStateService.remove(runtime.serverId);
3897
+ throw new Error(`Runtime '${runtime.serverId}' is not reachable at http://${runtime.host}:${runtime.port}. Removed stale runtime record.`);
3423
3898
  }
3424
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
3899
+ 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.`);
3900
+ const shutdownToken = request.token ?? runtime.shutdownToken;
3901
+ if (!shutdownToken) throw new Error(`No shutdown token available for runtime '${runtime.serverId}'.`);
3902
+ const shutdownResponse = await this.requestShutdown(runtime, shutdownToken, timeoutMs);
3903
+ await this.waitForShutdown(runtime, timeoutMs);
3904
+ await this.runtimeStateService.remove(runtime.serverId);
3905
+ return {
3906
+ ok: true,
3907
+ serverId: runtime.serverId,
3908
+ host: runtime.host,
3909
+ port: runtime.port,
3910
+ message: shutdownResponse.message
3911
+ };
3425
3912
  }
3426
- async handleDeleteRequest(req, res) {
3427
- const sessionId = req.headers["mcp-session-id"];
3428
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3429
- res.status(400).send("Invalid or missing session ID");
3430
- return;
3913
+ /**
3914
+ * Resolve a runtime record from explicit ID or a unique host/port pair.
3915
+ * @param request - Stop request options
3916
+ * @returns Matching runtime record
3917
+ */
3918
+ async resolveRuntime(request) {
3919
+ if (request.serverId) {
3920
+ const runtime = await this.runtimeStateService.read(request.serverId);
3921
+ 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.`);
3922
+ return runtime;
3431
3923
  }
3432
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
3433
- this.sessionManager.deleteSession(sessionId);
3924
+ if (request.host === void 0 || request.port === void 0) throw new Error("Provide --id or both --host and --port to select a runtime.");
3925
+ const matches = (await this.runtimeStateService.list()).filter((runtime) => runtime.host === request.host && runtime.port === request.port);
3926
+ 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.`);
3927
+ 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.`);
3928
+ return matches[0];
3434
3929
  }
3435
- async start() {
3436
- return new Promise((resolve$2, reject) => {
3437
- try {
3438
- this.server = this.app.listen(this.config.port, this.config.host, () => {
3439
- console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
3440
- console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
3441
- resolve$2();
3442
- });
3443
- this.server.on("error", (error) => {
3444
- reject(error);
3445
- });
3446
- } catch (error) {
3447
- reject(error);
3448
- }
3449
- });
3930
+ /**
3931
+ * Read the runtime health payload.
3932
+ * @param runtime - Runtime to query
3933
+ * @param timeoutMs - Request timeout in milliseconds
3934
+ * @returns Reachability status and optional payload
3935
+ */
3936
+ async fetchHealth(runtime, timeoutMs) {
3937
+ try {
3938
+ const response = await this.fetchWithTimeout(`http://${runtime.host}:${runtime.port}/health`, { method: "GET" }, timeoutMs);
3939
+ if (!response.ok) return { reachable: false };
3940
+ const payload = await response.json();
3941
+ if (!isHealthResponse(payload)) throw new Error("Received invalid health response payload.");
3942
+ return {
3943
+ reachable: true,
3944
+ payload
3945
+ };
3946
+ } catch {
3947
+ return { reachable: false };
3948
+ }
3450
3949
  }
3451
- async stop() {
3452
- return new Promise((resolve$2, reject) => {
3453
- if (this.server) {
3454
- this.sessionManager.clear();
3455
- this.server.close((err) => {
3456
- if (err) reject(err);
3457
- else {
3458
- this.server = null;
3459
- resolve$2();
3460
- }
3461
- });
3462
- } else resolve$2();
3463
- });
3950
+ /**
3951
+ * Send authenticated shutdown request to the admin endpoint.
3952
+ * @param runtime - Runtime to stop
3953
+ * @param shutdownToken - Bearer token for the admin endpoint
3954
+ * @param timeoutMs - Request timeout in milliseconds
3955
+ * @returns Parsed shutdown response payload
3956
+ */
3957
+ 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}` }
3961
+ }, timeoutMs);
3962
+ const payload = await response.json();
3963
+ if (!isShutdownResponse(payload)) throw new Error("Received invalid shutdown response payload.");
3964
+ if (!response.ok || !payload.ok) throw new Error(payload.message);
3965
+ return payload;
3464
3966
  }
3465
- getPort() {
3466
- return this.config.port;
3967
+ /**
3968
+ * Poll until the target runtime is no longer reachable.
3969
+ * @param runtime - Runtime expected to stop
3970
+ * @param timeoutMs - Maximum wait time in milliseconds
3971
+ * @returns Promise that resolves when shutdown is observed
3972
+ */
3973
+ async waitForShutdown(runtime, timeoutMs) {
3974
+ const deadline = Date.now() + timeoutMs;
3975
+ while (Date.now() < deadline) {
3976
+ if (!(await this.fetchHealth(runtime, Math.max(250, deadline - Date.now()))).reachable) return;
3977
+ await sleep(200);
3978
+ }
3979
+ throw new Error(`Timed out waiting for runtime '${runtime.serverId}' to stop at http://${runtime.host}:${runtime.port}.`);
3467
3980
  }
3468
- getHost() {
3469
- return this.config.host;
3981
+ /**
3982
+ * Perform a fetch with an abort timeout.
3983
+ * @param url - Target URL
3984
+ * @param init - Fetch options
3985
+ * @param timeoutMs - Timeout in milliseconds
3986
+ * @returns Fetch response
3987
+ */
3988
+ async fetchWithTimeout(url, init, timeoutMs) {
3989
+ const controller = new AbortController();
3990
+ const timeoutId = setTimeout(() => {
3991
+ controller.abort();
3992
+ }, timeoutMs);
3993
+ try {
3994
+ return await fetch(url, {
3995
+ ...init,
3996
+ signal: controller.signal
3997
+ });
3998
+ } catch (error) {
3999
+ throw new Error(`Request to '${url}' failed: ${toErrorMessage(error)}`);
4000
+ } finally {
4001
+ clearTimeout(timeoutId);
4002
+ }
3470
4003
  }
3471
4004
  };
3472
4005
 
@@ -3501,6 +4034,12 @@ Object.defineProperty(exports, 'McpClientManagerService', {
3501
4034
  return McpClientManagerService;
3502
4035
  }
3503
4036
  });
4037
+ Object.defineProperty(exports, 'RuntimeStateService', {
4038
+ enumerable: true,
4039
+ get: function () {
4040
+ return RuntimeStateService;
4041
+ }
4042
+ });
3504
4043
  Object.defineProperty(exports, 'SearchListToolsTool', {
3505
4044
  enumerable: true,
3506
4045
  get: function () {
@@ -3519,12 +4058,30 @@ Object.defineProperty(exports, 'SseTransportHandler', {
3519
4058
  return SseTransportHandler;
3520
4059
  }
3521
4060
  });
4061
+ Object.defineProperty(exports, 'StdioHttpTransportHandler', {
4062
+ enumerable: true,
4063
+ get: function () {
4064
+ return StdioHttpTransportHandler;
4065
+ }
4066
+ });
3522
4067
  Object.defineProperty(exports, 'StdioTransportHandler', {
3523
4068
  enumerable: true,
3524
4069
  get: function () {
3525
4070
  return StdioTransportHandler;
3526
4071
  }
3527
4072
  });
4073
+ Object.defineProperty(exports, 'StopServerService', {
4074
+ enumerable: true,
4075
+ get: function () {
4076
+ return StopServerService;
4077
+ }
4078
+ });
4079
+ Object.defineProperty(exports, 'TRANSPORT_MODE', {
4080
+ enumerable: true,
4081
+ get: function () {
4082
+ return TRANSPORT_MODE;
4083
+ }
4084
+ });
3528
4085
  Object.defineProperty(exports, 'UseToolTool', {
3529
4086
  enumerable: true,
3530
4087
  get: function () {