@agiflowai/one-mcp 0.3.13 → 0.3.15

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.
@@ -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 { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
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.12";
2782
+ var version = "0.3.14";
2781
2783
 
2782
2784
  //#endregion
2783
2785
  //#region src/server/index.ts
@@ -3097,28 +3099,341 @@ async function createServer(options) {
3097
3099
  }
3098
3100
 
3099
3101
  //#endregion
3100
- //#region src/transports/stdio.ts
3102
+ //#region src/types/index.ts
3101
3103
  /**
3102
- * Stdio transport handler for MCP server
3103
- * Used for command-line and direct integrations
3104
+ * Transport mode constants
3104
3105
  */
3105
- var StdioTransportHandler = class {
3106
- server;
3107
- transport = null;
3108
- constructor(server) {
3109
- this.server = server;
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
+ async deleteSession(sessionId) {
3151
+ const session = this.sessions.get(sessionId);
3152
+ if (session) try {
3153
+ await session.server.close();
3154
+ } catch (error) {
3155
+ throw new Error(`Failed to close MCP server for session '${sessionId}': ${toErrorMessage$2(error)}`);
3156
+ }
3157
+ this.sessions.delete(sessionId);
3158
+ }
3159
+ hasSession(sessionId) {
3160
+ return this.sessions.has(sessionId);
3161
+ }
3162
+ async clear() {
3163
+ try {
3164
+ await Promise.all(Array.from(this.sessions.values()).map(async (session) => {
3165
+ await session.server.close();
3166
+ }));
3167
+ } catch (error) {
3168
+ throw new Error(`Failed to clear sessions: ${toErrorMessage$2(error)}`);
3169
+ }
3170
+ this.sessions.clear();
3171
+ }
3172
+ };
3173
+ function toErrorMessage$2(error) {
3174
+ return error instanceof Error ? error.message : String(error);
3175
+ }
3176
+ const ADMIN_RATE_LIMIT_WINDOW_MS = 6e4;
3177
+ const ADMIN_RATE_LIMIT_MAX_REQUESTS = 5;
3178
+ /**
3179
+ * Simple in-memory rate limiter for the admin shutdown endpoint.
3180
+ * Tracks request timestamps per IP within a sliding window.
3181
+ */
3182
+ var AdminRateLimiter = class {
3183
+ requests = /* @__PURE__ */ new Map();
3184
+ isAllowed(ip) {
3185
+ const now = Date.now();
3186
+ const windowStart = now - ADMIN_RATE_LIMIT_WINDOW_MS;
3187
+ const timestamps = (this.requests.get(ip) ?? []).filter((t) => t > windowStart);
3188
+ if (timestamps.length >= ADMIN_RATE_LIMIT_MAX_REQUESTS) {
3189
+ this.requests.set(ip, timestamps);
3190
+ return false;
3191
+ }
3192
+ timestamps.push(now);
3193
+ this.requests.set(ip, timestamps);
3194
+ return true;
3195
+ }
3196
+ };
3197
+ /**
3198
+ * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
3199
+ * Provides stateful session management with resumability support
3200
+ */
3201
+ var HttpTransportHandler = class {
3202
+ serverFactory;
3203
+ app;
3204
+ server = null;
3205
+ sessionManager;
3206
+ config;
3207
+ adminOptions;
3208
+ adminRateLimiter = new AdminRateLimiter();
3209
+ constructor(serverFactory, config, adminOptions) {
3210
+ this.serverFactory = serverFactory;
3211
+ this.app = express();
3212
+ this.sessionManager = new HttpFullSessionManager();
3213
+ this.config = {
3214
+ mode: config.mode,
3215
+ port: config.port ?? 3e3,
3216
+ host: config.host ?? "localhost"
3217
+ };
3218
+ this.adminOptions = adminOptions;
3219
+ this.setupMiddleware();
3220
+ this.setupRoutes();
3221
+ }
3222
+ setupMiddleware() {
3223
+ this.app.use(express.json());
3224
+ }
3225
+ setupRoutes() {
3226
+ this.app.post("/mcp", async (req, res) => {
3227
+ try {
3228
+ await this.handlePostRequest(req, res);
3229
+ } catch (error) {
3230
+ console.error(`Failed to handle MCP POST request: ${toErrorMessage$2(error)}`);
3231
+ res.status(500).json({
3232
+ jsonrpc: "2.0",
3233
+ error: {
3234
+ code: -32603,
3235
+ message: "Failed to handle MCP POST request."
3236
+ },
3237
+ id: null
3238
+ });
3239
+ }
3240
+ });
3241
+ this.app.get("/mcp", async (req, res) => {
3242
+ try {
3243
+ await this.handleGetRequest(req, res);
3244
+ } catch (error) {
3245
+ console.error(`Failed to handle MCP GET request: ${toErrorMessage$2(error)}`);
3246
+ res.status(500).send("Failed to handle MCP GET request.");
3247
+ }
3248
+ });
3249
+ this.app.delete("/mcp", async (req, res) => {
3250
+ try {
3251
+ await this.handleDeleteRequest(req, res);
3252
+ } catch (error) {
3253
+ console.error(`Failed to handle MCP DELETE request: ${toErrorMessage$2(error)}`);
3254
+ res.status(500).send("Failed to handle MCP DELETE request.");
3255
+ }
3256
+ });
3257
+ this.app.get("/health", (_req, res) => {
3258
+ const payload = {
3259
+ status: "ok",
3260
+ transport: "http",
3261
+ serverId: this.adminOptions?.serverId
3262
+ };
3263
+ res.json(payload);
3264
+ });
3265
+ this.app.post("/admin/shutdown", async (req, res) => {
3266
+ try {
3267
+ const clientIp = req.ip ?? req.socket.remoteAddress ?? "unknown";
3268
+ if (!this.adminRateLimiter.isAllowed(clientIp)) {
3269
+ const payload = {
3270
+ ok: false,
3271
+ message: "Too many shutdown requests. Try again later.",
3272
+ serverId: this.adminOptions?.serverId
3273
+ };
3274
+ res.status(429).json(payload);
3275
+ return;
3276
+ }
3277
+ await this.handleAdminShutdownRequest(req, res);
3278
+ } catch (error) {
3279
+ console.error(`Failed to process shutdown request: ${toErrorMessage$2(error)}`);
3280
+ const payload = {
3281
+ ok: false,
3282
+ message: "Failed to process shutdown request.",
3283
+ serverId: this.adminOptions?.serverId
3284
+ };
3285
+ res.status(500).json(payload);
3286
+ }
3287
+ });
3288
+ }
3289
+ isAuthorizedShutdownRequest(req) {
3290
+ const expectedToken = this.adminOptions?.shutdownToken;
3291
+ if (!expectedToken) return false;
3292
+ const authHeader = req.headers.authorization;
3293
+ if (typeof authHeader === "string" && authHeader.startsWith("Bearer ")) return authHeader.slice(7) === expectedToken;
3294
+ const tokenHeader = req.headers["x-one-mcp-shutdown-token"];
3295
+ return typeof tokenHeader === "string" && tokenHeader === expectedToken;
3296
+ }
3297
+ async handleAdminShutdownRequest(req, res) {
3298
+ try {
3299
+ if (!this.adminOptions?.onShutdownRequested) {
3300
+ const payload$1 = {
3301
+ ok: false,
3302
+ message: "Shutdown endpoint is not enabled for this server instance.",
3303
+ serverId: this.adminOptions?.serverId
3304
+ };
3305
+ res.status(404).json(payload$1);
3306
+ return;
3307
+ }
3308
+ if (!this.isAuthorizedShutdownRequest(req)) {
3309
+ const payload$1 = {
3310
+ ok: false,
3311
+ message: "Unauthorized shutdown request: invalid or missing shutdown token.",
3312
+ serverId: this.adminOptions?.serverId
3313
+ };
3314
+ res.status(401).json(payload$1);
3315
+ return;
3316
+ }
3317
+ const payload = {
3318
+ ok: true,
3319
+ message: "Shutdown request accepted. Stopping server gracefully.",
3320
+ serverId: this.adminOptions?.serverId
3321
+ };
3322
+ res.json(payload);
3323
+ await this.adminOptions.onShutdownRequested();
3324
+ } catch (error) {
3325
+ throw new Error(`Failed to handle admin shutdown request: ${toErrorMessage$2(error)}`);
3326
+ }
3327
+ }
3328
+ async handlePostRequest(req, res) {
3329
+ const sessionId = req.headers["mcp-session-id"];
3330
+ let transport;
3331
+ if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
3332
+ else if (!sessionId && isInitializeRequest(req.body)) {
3333
+ const mcpServer = await this.serverFactory();
3334
+ transport = new StreamableHTTPServerTransport({
3335
+ sessionIdGenerator: () => randomUUID(),
3336
+ enableJsonResponse: true,
3337
+ onsessioninitialized: (initializedSessionId) => {
3338
+ this.sessionManager.setSession(initializedSessionId, transport, mcpServer);
3339
+ }
3340
+ });
3341
+ transport.onclose = async () => {
3342
+ if (transport.sessionId) try {
3343
+ await this.sessionManager.deleteSession(transport.sessionId);
3344
+ } catch (error) {
3345
+ console.error(`Failed to clean up session '${transport.sessionId}': ${toErrorMessage$2(error)}`);
3346
+ }
3347
+ };
3348
+ try {
3349
+ await mcpServer.connect(transport);
3350
+ } catch (error) {
3351
+ throw new Error(`Failed to connect MCP server transport for initialization request: ${toErrorMessage$2(error)}`);
3352
+ }
3353
+ } else {
3354
+ res.status(400).json({
3355
+ jsonrpc: "2.0",
3356
+ error: {
3357
+ code: -32e3,
3358
+ message: sessionId === void 0 ? "Bad Request: missing session ID and request body is not an initialize request." : `Bad Request: unknown session ID '${sessionId}'.`
3359
+ },
3360
+ id: null
3361
+ });
3362
+ return;
3363
+ }
3364
+ try {
3365
+ await transport.handleRequest(req, res, req.body);
3366
+ } catch (error) {
3367
+ throw new Error(`Failed handling MCP transport request: ${toErrorMessage$2(error)}`);
3368
+ }
3369
+ }
3370
+ async handleGetRequest(req, res) {
3371
+ const sessionId = req.headers["mcp-session-id"];
3372
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3373
+ res.status(400).send("Invalid or missing session ID");
3374
+ return;
3375
+ }
3376
+ const session = this.sessionManager.getSession(sessionId);
3377
+ try {
3378
+ await session.transport.handleRequest(req, res);
3379
+ } catch (error) {
3380
+ throw new Error(`Failed handling MCP GET request for session '${sessionId}': ${toErrorMessage$2(error)}`);
3381
+ }
3382
+ }
3383
+ async handleDeleteRequest(req, res) {
3384
+ const sessionId = req.headers["mcp-session-id"];
3385
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3386
+ res.status(400).send("Invalid or missing session ID");
3387
+ return;
3388
+ }
3389
+ const session = this.sessionManager.getSession(sessionId);
3390
+ try {
3391
+ await session.transport.handleRequest(req, res);
3392
+ } catch (error) {
3393
+ throw new Error(`Failed handling MCP DELETE request for session '${sessionId}': ${toErrorMessage$2(error)}`);
3394
+ }
3395
+ await this.sessionManager.deleteSession(sessionId);
3110
3396
  }
3111
3397
  async start() {
3112
- this.transport = new StdioServerTransport();
3113
- await this.server.connect(this.transport);
3114
- console.error("@agiflowai/one-mcp MCP server started on stdio");
3398
+ try {
3399
+ const server = this.app.listen(this.config.port, this.config.host);
3400
+ this.server = server;
3401
+ const listeningPromise = (async () => {
3402
+ await once(server, "listening");
3403
+ })();
3404
+ const errorPromise = (async () => {
3405
+ const [error] = await once(server, "error");
3406
+ throw error instanceof Error ? error : new Error(String(error));
3407
+ })();
3408
+ await Promise.race([listeningPromise, errorPromise]);
3409
+ console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
3410
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
3411
+ } catch (error) {
3412
+ this.server = null;
3413
+ throw new Error(`Failed to start HTTP transport: ${toErrorMessage$2(error)}`);
3414
+ }
3115
3415
  }
3116
3416
  async stop() {
3117
- if (this.transport) {
3118
- await this.transport.close();
3119
- this.transport = null;
3417
+ if (!this.server) return;
3418
+ try {
3419
+ await this.sessionManager.clear();
3420
+ } catch (error) {
3421
+ throw new Error(`Failed to clear sessions during HTTP transport stop: ${toErrorMessage$2(error)}`);
3422
+ }
3423
+ const closeServer = promisify(this.server.close.bind(this.server));
3424
+ try {
3425
+ await closeServer();
3426
+ this.server = null;
3427
+ } catch (error) {
3428
+ throw new Error(`Failed to stop HTTP transport: ${toErrorMessage$2(error)}`);
3120
3429
  }
3121
3430
  }
3431
+ getPort() {
3432
+ return this.config.port;
3433
+ }
3434
+ getHost() {
3435
+ return this.config.host;
3436
+ }
3122
3437
  };
3123
3438
 
3124
3439
  //#endregion
@@ -3264,182 +3579,505 @@ var SseTransportHandler = class {
3264
3579
  };
3265
3580
 
3266
3581
  //#endregion
3267
- //#region src/transports/http.ts
3582
+ //#region src/transports/stdio.ts
3268
3583
  /**
3269
- * HTTP Transport Handler
3584
+ * Stdio transport handler for MCP server
3585
+ * Used for command-line and direct integrations
3586
+ */
3587
+ var StdioTransportHandler = class {
3588
+ server;
3589
+ transport = null;
3590
+ constructor(server) {
3591
+ this.server = server;
3592
+ }
3593
+ async start() {
3594
+ this.transport = new StdioServerTransport();
3595
+ await this.server.connect(this.transport);
3596
+ console.error("@agiflowai/one-mcp MCP server started on stdio");
3597
+ }
3598
+ async stop() {
3599
+ if (this.transport) {
3600
+ await this.transport.close();
3601
+ this.transport = null;
3602
+ }
3603
+ }
3604
+ };
3605
+
3606
+ //#endregion
3607
+ //#region src/transports/stdio-http.ts
3608
+ /**
3609
+ * STDIO-HTTP Proxy Transport
3270
3610
  *
3271
3611
  * DESIGN PATTERNS:
3272
3612
  * - Transport handler pattern implementing TransportHandler interface
3273
- * - Session management for stateful connections
3274
- * - Streamable HTTP protocol (2025-03-26) with resumability support
3275
- * - Factory pattern for creating MCP server instances per session
3613
+ * - STDIO transport with MCP request forwarding to HTTP backend
3614
+ * - Graceful cleanup with error isolation
3276
3615
  *
3277
3616
  * CODING STANDARDS:
3278
- * - Use async/await for all asynchronous operations
3279
- * - Implement proper session lifecycle management
3280
- * - Handle errors gracefully with appropriate HTTP status codes
3281
- * - Provide health check endpoint for monitoring
3282
- * - Clean up resources on shutdown
3617
+ * - Use StdioServerTransport for stdio communication
3618
+ * - Reuse a single StreamableHTTP client connection
3619
+ * - Wrap async operations with try-catch and descriptive errors
3283
3620
  *
3284
3621
  * AVOID:
3285
- * - Sharing MCP server instances across sessions (use factory pattern)
3286
- * - Forgetting to clean up sessions on disconnect
3287
- * - Missing error handling for request processing
3288
- * - Hardcoded configuration (use TransportConfig)
3622
+ * - Starting HTTP server lifecycle in this transport entry point
3623
+ * - Recreating HTTP client per request
3624
+ * - Swallowing cleanup failures silently
3289
3625
  */
3290
3626
  /**
3291
- * HTTP session manager
3627
+ * Transport that serves MCP over stdio and forwards MCP requests to an HTTP endpoint.
3292
3628
  */
3293
- var HttpFullSessionManager = class {
3294
- sessions = /* @__PURE__ */ new Map();
3295
- getSession(sessionId) {
3296
- return this.sessions.get(sessionId);
3297
- }
3298
- setSession(sessionId, transport, server) {
3299
- this.sessions.set(sessionId, {
3300
- transport,
3301
- server
3302
- });
3629
+ var StdioHttpTransportHandler = class {
3630
+ endpoint;
3631
+ stdioProxyServer = null;
3632
+ stdioTransport = null;
3633
+ httpClient = null;
3634
+ constructor(config) {
3635
+ this.endpoint = config.endpoint;
3303
3636
  }
3304
- deleteSession(sessionId) {
3305
- const session = this.sessions.get(sessionId);
3306
- if (session) session.server.close();
3307
- this.sessions.delete(sessionId);
3637
+ async start() {
3638
+ try {
3639
+ const httpClientTransport = new StreamableHTTPClientTransport(this.endpoint);
3640
+ const client = new Client({
3641
+ name: "@agiflowai/one-mcp-stdio-http-proxy",
3642
+ version: "0.1.0"
3643
+ }, { capabilities: {} });
3644
+ await client.connect(httpClientTransport);
3645
+ this.httpClient = client;
3646
+ this.stdioProxyServer = this.createProxyServer(client);
3647
+ this.stdioTransport = new StdioServerTransport();
3648
+ await this.stdioProxyServer.connect(this.stdioTransport);
3649
+ console.error(`@agiflowai/one-mcp MCP stdio proxy connected to ${this.endpoint.toString()}`);
3650
+ } catch (error) {
3651
+ await this.stop();
3652
+ throw new Error(`Failed to start stdio-http proxy transport: ${error instanceof Error ? error.message : String(error)}`);
3653
+ }
3308
3654
  }
3309
- hasSession(sessionId) {
3310
- return this.sessions.has(sessionId);
3655
+ async stop() {
3656
+ const stdioTransport = this.stdioTransport;
3657
+ const stdioProxyServer = this.stdioProxyServer;
3658
+ const httpClient = this.httpClient;
3659
+ this.stdioTransport = null;
3660
+ this.stdioProxyServer = null;
3661
+ this.httpClient = null;
3662
+ const cleanupErrors = [];
3663
+ await Promise.all([
3664
+ (async () => {
3665
+ try {
3666
+ if (stdioTransport) await stdioTransport.close();
3667
+ } catch (error) {
3668
+ cleanupErrors.push(`failed closing stdio transport: ${error instanceof Error ? error.message : String(error)}`);
3669
+ }
3670
+ })(),
3671
+ (async () => {
3672
+ try {
3673
+ if (stdioProxyServer) await stdioProxyServer.close();
3674
+ } catch (error) {
3675
+ cleanupErrors.push(`failed closing stdio proxy server: ${error instanceof Error ? error.message : String(error)}`);
3676
+ }
3677
+ })(),
3678
+ (async () => {
3679
+ try {
3680
+ if (httpClient) await httpClient.close();
3681
+ } catch (error) {
3682
+ cleanupErrors.push(`failed closing http client: ${error instanceof Error ? error.message : String(error)}`);
3683
+ }
3684
+ })()
3685
+ ]);
3686
+ if (cleanupErrors.length > 0) throw new Error(`Failed to stop stdio-http proxy transport: ${cleanupErrors.join("; ")}`);
3311
3687
  }
3312
- clear() {
3313
- for (const session of this.sessions.values()) session.server.close();
3314
- this.sessions.clear();
3688
+ createProxyServer(client) {
3689
+ const proxyServer = new Server({
3690
+ name: "@agiflowai/one-mcp-stdio-http-proxy",
3691
+ version: "0.1.0"
3692
+ }, { capabilities: {
3693
+ tools: {},
3694
+ resources: {},
3695
+ prompts: {}
3696
+ } });
3697
+ proxyServer.setRequestHandler(ListToolsRequestSchema, async () => {
3698
+ try {
3699
+ return await client.listTools();
3700
+ } catch (error) {
3701
+ throw new Error(`Failed forwarding tools/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3702
+ }
3703
+ });
3704
+ proxyServer.setRequestHandler(CallToolRequestSchema, async (request) => {
3705
+ try {
3706
+ return await client.callTool({
3707
+ name: request.params.name,
3708
+ arguments: request.params.arguments
3709
+ });
3710
+ } catch (error) {
3711
+ throw new Error(`Failed forwarding tools/call (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3712
+ }
3713
+ });
3714
+ proxyServer.setRequestHandler(ListResourcesRequestSchema, async () => {
3715
+ try {
3716
+ return await client.listResources();
3717
+ } catch (error) {
3718
+ throw new Error(`Failed forwarding resources/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3719
+ }
3720
+ });
3721
+ proxyServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3722
+ try {
3723
+ return await client.readResource({ uri: request.params.uri });
3724
+ } catch (error) {
3725
+ throw new Error(`Failed forwarding resources/read (${request.params.uri}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3726
+ }
3727
+ });
3728
+ proxyServer.setRequestHandler(ListPromptsRequestSchema, async () => {
3729
+ try {
3730
+ return await client.listPrompts();
3731
+ } catch (error) {
3732
+ throw new Error(`Failed forwarding prompts/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3733
+ }
3734
+ });
3735
+ proxyServer.setRequestHandler(GetPromptRequestSchema, async (request) => {
3736
+ try {
3737
+ return await client.getPrompt({
3738
+ name: request.params.name,
3739
+ arguments: request.params.arguments
3740
+ });
3741
+ } catch (error) {
3742
+ throw new Error(`Failed forwarding prompts/get (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
3743
+ }
3744
+ });
3745
+ return proxyServer;
3315
3746
  }
3316
3747
  };
3748
+
3749
+ //#endregion
3750
+ //#region src/services/RuntimeStateService.ts
3317
3751
  /**
3318
- * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
3319
- * Provides stateful session management with resumability support
3752
+ * RuntimeStateService
3753
+ *
3754
+ * Persists runtime metadata for HTTP one-mcp instances so external commands
3755
+ * (for example `one-mcp stop`) can discover and target the correct server.
3320
3756
  */
3321
- var HttpTransportHandler = class {
3322
- serverFactory;
3323
- app;
3324
- server = null;
3325
- sessionManager;
3326
- config;
3327
- constructor(serverFactory, config) {
3328
- this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
3329
- this.app = express();
3330
- this.sessionManager = new HttpFullSessionManager();
3331
- this.config = {
3332
- mode: config.mode,
3333
- port: config.port ?? 3e3,
3334
- host: config.host ?? "localhost"
3335
- };
3336
- this.setupMiddleware();
3337
- this.setupRoutes();
3757
+ const RUNTIME_DIR_NAME = "runtimes";
3758
+ const RUNTIME_FILE_SUFFIX = ".runtime.json";
3759
+ function isObject(value) {
3760
+ return typeof value === "object" && value !== null;
3761
+ }
3762
+ function isRuntimeStateRecord(value) {
3763
+ if (!isObject(value)) return false;
3764
+ 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");
3765
+ }
3766
+ function toErrorMessage$1(error) {
3767
+ return error instanceof Error ? error.message : String(error);
3768
+ }
3769
+ /**
3770
+ * Runtime state persistence implementation.
3771
+ */
3772
+ var RuntimeStateService = class RuntimeStateService {
3773
+ runtimeDir;
3774
+ constructor(runtimeDir) {
3775
+ this.runtimeDir = runtimeDir ?? RuntimeStateService.getDefaultRuntimeDir();
3338
3776
  }
3339
- setupMiddleware() {
3340
- this.app.use(express.json());
3777
+ /**
3778
+ * Resolve default runtime directory under the user's home cache path.
3779
+ * @returns Absolute runtime directory path
3780
+ */
3781
+ static getDefaultRuntimeDir() {
3782
+ return join(homedir(), ".aicode-toolkit", "one-mcp", RUNTIME_DIR_NAME);
3341
3783
  }
3342
- setupRoutes() {
3343
- this.app.post("/mcp", async (req, res) => {
3344
- await this.handlePostRequest(req, res);
3345
- });
3346
- this.app.get("/mcp", async (req, res) => {
3347
- await this.handleGetRequest(req, res);
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
- });
3784
+ /**
3785
+ * Build runtime state file path for a given server ID.
3786
+ * @param serverId - Target one-mcp server identifier
3787
+ * @returns Absolute runtime file path
3788
+ */
3789
+ getRecordPath(serverId) {
3790
+ return join(this.runtimeDir, `${serverId}${RUNTIME_FILE_SUFFIX}`);
3358
3791
  }
3359
- async handlePostRequest(req, res) {
3360
- const sessionId = req.headers["mcp-session-id"];
3361
- let transport;
3362
- if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
3363
- else if (!sessionId && isInitializeRequest(req.body)) {
3364
- const mcpServer = this.serverFactory();
3365
- transport = new StreamableHTTPServerTransport({
3366
- sessionIdGenerator: () => randomUUID(),
3367
- enableJsonResponse: true,
3368
- onsessioninitialized: (sessionId$1) => {
3369
- this.sessionManager.setSession(sessionId$1, transport, mcpServer);
3792
+ /**
3793
+ * Persist a runtime state record.
3794
+ * @param record - Runtime metadata to persist
3795
+ * @returns Promise that resolves when write completes
3796
+ */
3797
+ async write(record) {
3798
+ await mkdir(this.runtimeDir, { recursive: true });
3799
+ await writeFile(this.getRecordPath(record.serverId), JSON.stringify(record, null, 2), "utf-8");
3800
+ }
3801
+ /**
3802
+ * Read a runtime state record by server ID.
3803
+ * @param serverId - Target one-mcp server identifier
3804
+ * @returns Matching runtime record, or null when no record exists
3805
+ */
3806
+ async read(serverId) {
3807
+ const filePath = this.getRecordPath(serverId);
3808
+ try {
3809
+ const content = await readFile(filePath, "utf-8");
3810
+ const parsed = JSON.parse(content);
3811
+ return isRuntimeStateRecord(parsed) ? parsed : null;
3812
+ } catch (error) {
3813
+ if (isObject(error) && "code" in error && error.code === "ENOENT") return null;
3814
+ throw new Error(`Failed to read runtime state for server '${serverId}' from '${filePath}': ${toErrorMessage$1(error)}`);
3815
+ }
3816
+ }
3817
+ /**
3818
+ * List all persisted runtime records.
3819
+ * @returns Array of runtime records
3820
+ */
3821
+ async list() {
3822
+ try {
3823
+ const files = (await readdir(this.runtimeDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(RUNTIME_FILE_SUFFIX));
3824
+ return (await Promise.all(files.map(async (file) => {
3825
+ try {
3826
+ const content = await readFile(join(this.runtimeDir, file.name), "utf-8");
3827
+ const parsed = JSON.parse(content);
3828
+ return isRuntimeStateRecord(parsed) ? parsed : null;
3829
+ } catch {
3830
+ return null;
3370
3831
  }
3371
- });
3372
- transport.onclose = () => {
3373
- if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
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;
3832
+ }))).filter((record) => record !== null);
3833
+ } catch (error) {
3834
+ if (isObject(error) && "code" in error && error.code === "ENOENT") return [];
3835
+ throw new Error(`Failed to list runtime states from '${this.runtimeDir}': ${toErrorMessage$1(error)}`);
3386
3836
  }
3387
- await transport.handleRequest(req, res, req.body);
3388
3837
  }
3389
- async handleGetRequest(req, res) {
3390
- const sessionId = req.headers["mcp-session-id"];
3391
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3392
- res.status(400).send("Invalid or missing session ID");
3393
- return;
3838
+ /**
3839
+ * Remove a runtime state record by server ID.
3840
+ * @param serverId - Target one-mcp server identifier
3841
+ * @returns Promise that resolves when delete completes
3842
+ */
3843
+ async remove(serverId) {
3844
+ await rm(this.getRecordPath(serverId), { force: true });
3845
+ }
3846
+ };
3847
+
3848
+ //#endregion
3849
+ //#region src/services/StopServerService/constants.ts
3850
+ /**
3851
+ * StopServerService constants.
3852
+ */
3853
+ /** Maximum time in milliseconds to wait for a shutdown to complete. */
3854
+ const DEFAULT_STOP_TIMEOUT_MS = 5e3;
3855
+ /** Minimum timeout in milliseconds for individual health check requests. */
3856
+ const HEALTH_REQUEST_TIMEOUT_FLOOR_MS = 250;
3857
+ /** Delay in milliseconds between shutdown polling attempts. */
3858
+ const SHUTDOWN_POLL_INTERVAL_MS = 200;
3859
+ /** Path for the runtime health check endpoint. */
3860
+ const HEALTH_CHECK_PATH = "/health";
3861
+ /** Path for the authenticated admin shutdown endpoint. */
3862
+ const ADMIN_SHUTDOWN_PATH = "/admin/shutdown";
3863
+ /** HTTP GET method identifier. */
3864
+ const HTTP_METHOD_GET = "GET";
3865
+ /** HTTP POST method identifier. */
3866
+ const HTTP_METHOD_POST = "POST";
3867
+ /** HTTP header name for bearer token authorization. */
3868
+ const AUTHORIZATION_HEADER_NAME = "Authorization";
3869
+ /** Prefix for bearer token values in the Authorization header. */
3870
+ const BEARER_TOKEN_PREFIX = "Bearer ";
3871
+ /** HTTP protocol scheme prefix for URL construction. */
3872
+ const HTTP_PROTOCOL = "http://";
3873
+ /** Separator between host and port in URL construction. */
3874
+ const URL_PORT_SEPARATOR = ":";
3875
+ /** Loopback hostname. */
3876
+ const LOOPBACK_HOST_LOCALHOST = "localhost";
3877
+ /** IPv4 loopback address. */
3878
+ const LOOPBACK_HOST_IPV4 = "127.0.0.1";
3879
+ /** IPv6 loopback address. */
3880
+ const LOOPBACK_HOST_IPV6 = "::1";
3881
+ /** Hosts that are safe to send admin requests to (loopback only). */
3882
+ const ALLOWED_HOSTS = new Set([
3883
+ LOOPBACK_HOST_LOCALHOST,
3884
+ LOOPBACK_HOST_IPV4,
3885
+ LOOPBACK_HOST_IPV6
3886
+ ]);
3887
+ /** Expected status value in a healthy runtime response. */
3888
+ const HEALTH_STATUS_OK = "ok";
3889
+ /** Expected transport value in a healthy runtime response. */
3890
+ const HEALTH_TRANSPORT_HTTP = "http";
3891
+ /** Property key for status field in health responses. */
3892
+ const KEY_STATUS = "status";
3893
+ /** Property key for transport field in health responses. */
3894
+ const KEY_TRANSPORT = "transport";
3895
+ /** Property key for serverId field in runtime responses. */
3896
+ const KEY_SERVER_ID = "serverId";
3897
+ /** Property key for ok field in shutdown responses. */
3898
+ const KEY_OK = "ok";
3899
+ /** Property key for message field in shutdown responses. */
3900
+ const KEY_MESSAGE = "message";
3901
+
3902
+ //#endregion
3903
+ //#region src/services/StopServerService/types.ts
3904
+ /**
3905
+ * Safely cast a non-null object to a string-keyed record for property access.
3906
+ * @param value - Object value already verified as non-null
3907
+ * @returns The same value typed as a record
3908
+ */
3909
+ function toRecord(value) {
3910
+ return value;
3911
+ }
3912
+ /**
3913
+ * Type guard for health responses.
3914
+ * @param value - Candidate payload to validate
3915
+ * @returns True when payload matches health response shape
3916
+ */
3917
+ function isHealthResponse(value) {
3918
+ if (typeof value !== "object" || value === null) return false;
3919
+ const record = toRecord(value);
3920
+ 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");
3921
+ }
3922
+ /**
3923
+ * Type guard for shutdown responses.
3924
+ * @param value - Candidate payload to validate
3925
+ * @returns True when payload matches shutdown response shape
3926
+ */
3927
+ function isShutdownResponse(value) {
3928
+ if (typeof value !== "object" || value === null) return false;
3929
+ const record = toRecord(value);
3930
+ 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");
3931
+ }
3932
+
3933
+ //#endregion
3934
+ //#region src/services/StopServerService/StopServerService.ts
3935
+ /**
3936
+ * Format runtime endpoint URL after validating the host is a loopback address.
3937
+ * Rejects non-loopback hosts to prevent SSRF via tampered runtime state files.
3938
+ * @param runtime - Runtime record to format
3939
+ * @param path - Request path to append
3940
+ * @returns Full runtime URL
3941
+ */
3942
+ function buildRuntimeUrl(runtime, path) {
3943
+ 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.`);
3944
+ return `${HTTP_PROTOCOL}${runtime.host}${URL_PORT_SEPARATOR}${runtime.port}${path}`;
3945
+ }
3946
+ function toErrorMessage(error) {
3947
+ return error instanceof Error ? error.message : String(error);
3948
+ }
3949
+ function sleep(delayMs) {
3950
+ return new Promise((resolve$1) => {
3951
+ setTimeout(resolve$1, delayMs);
3952
+ });
3953
+ }
3954
+ /**
3955
+ * Service for resolving runtime targets and stopping them safely.
3956
+ */
3957
+ var StopServerService = class {
3958
+ runtimeStateService;
3959
+ constructor(runtimeStateService = new RuntimeStateService()) {
3960
+ this.runtimeStateService = runtimeStateService;
3961
+ }
3962
+ /**
3963
+ * Resolve a target runtime and stop it cooperatively.
3964
+ * @param request - Stop request options
3965
+ * @returns Stop result payload
3966
+ */
3967
+ async stop(request) {
3968
+ const timeoutMs = request.timeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
3969
+ const runtime = await this.resolveRuntime(request);
3970
+ const health = await this.fetchHealth(runtime, timeoutMs);
3971
+ if (!health.reachable) {
3972
+ await this.runtimeStateService.remove(runtime.serverId);
3973
+ throw new Error(`Runtime '${runtime.serverId}' is not reachable at http://${runtime.host}:${runtime.port}. Removed stale runtime record.`);
3394
3974
  }
3395
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
3975
+ 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.`);
3976
+ const shutdownToken = request.token ?? runtime.shutdownToken;
3977
+ if (!shutdownToken) throw new Error(`No shutdown token available for runtime '${runtime.serverId}'.`);
3978
+ const shutdownResponse = await this.requestShutdown(runtime, shutdownToken, timeoutMs);
3979
+ await this.waitForShutdown(runtime, timeoutMs);
3980
+ await this.runtimeStateService.remove(runtime.serverId);
3981
+ return {
3982
+ ok: true,
3983
+ serverId: runtime.serverId,
3984
+ host: runtime.host,
3985
+ port: runtime.port,
3986
+ message: shutdownResponse.message
3987
+ };
3396
3988
  }
3397
- async handleDeleteRequest(req, res) {
3398
- const sessionId = req.headers["mcp-session-id"];
3399
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
3400
- res.status(400).send("Invalid or missing session ID");
3401
- return;
3989
+ /**
3990
+ * Resolve a runtime record from explicit ID or a unique host/port pair.
3991
+ * @param request - Stop request options
3992
+ * @returns Matching runtime record
3993
+ */
3994
+ async resolveRuntime(request) {
3995
+ if (request.serverId) {
3996
+ const runtime = await this.runtimeStateService.read(request.serverId);
3997
+ 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.`);
3998
+ return runtime;
3402
3999
  }
3403
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
3404
- this.sessionManager.deleteSession(sessionId);
4000
+ if (request.host === void 0 || request.port === void 0) throw new Error("Provide --id or both --host and --port to select a runtime.");
4001
+ const matches = (await this.runtimeStateService.list()).filter((runtime) => runtime.host === request.host && runtime.port === request.port);
4002
+ 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.`);
4003
+ 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.`);
4004
+ return matches[0];
3405
4005
  }
3406
- async start() {
3407
- return new Promise((resolve$1, reject) => {
3408
- try {
3409
- this.server = this.app.listen(this.config.port, this.config.host, () => {
3410
- console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
3411
- console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
3412
- resolve$1();
3413
- });
3414
- this.server.on("error", (error) => {
3415
- reject(error);
3416
- });
3417
- } catch (error) {
3418
- reject(error);
3419
- }
3420
- });
4006
+ /**
4007
+ * Read the runtime health payload.
4008
+ * @param runtime - Runtime to query
4009
+ * @param timeoutMs - Request timeout in milliseconds
4010
+ * @returns Reachability status and optional payload
4011
+ */
4012
+ async fetchHealth(runtime, timeoutMs) {
4013
+ try {
4014
+ const response = await this.fetchWithTimeout(buildRuntimeUrl(runtime, HEALTH_CHECK_PATH), { method: HTTP_METHOD_GET }, timeoutMs);
4015
+ if (!response.ok) return { reachable: false };
4016
+ const payload = await response.json();
4017
+ if (!isHealthResponse(payload)) throw new Error("Received invalid health response payload.");
4018
+ return {
4019
+ reachable: true,
4020
+ payload
4021
+ };
4022
+ } catch {
4023
+ return { reachable: false };
4024
+ }
3421
4025
  }
3422
- async stop() {
3423
- return new Promise((resolve$1, reject) => {
3424
- if (this.server) {
3425
- this.sessionManager.clear();
3426
- this.server.close((err) => {
3427
- if (err) reject(err);
3428
- else {
3429
- this.server = null;
3430
- resolve$1();
3431
- }
3432
- });
3433
- } else resolve$1();
3434
- });
4026
+ /**
4027
+ * Send authenticated shutdown request to the admin endpoint.
4028
+ * @param runtime - Runtime to stop
4029
+ * @param shutdownToken - Bearer token for the admin endpoint
4030
+ * @param timeoutMs - Request timeout in milliseconds
4031
+ * @returns Parsed shutdown response payload
4032
+ */
4033
+ async requestShutdown(runtime, shutdownToken, timeoutMs) {
4034
+ const response = await this.fetchWithTimeout(buildRuntimeUrl(runtime, ADMIN_SHUTDOWN_PATH), {
4035
+ method: HTTP_METHOD_POST,
4036
+ headers: { [AUTHORIZATION_HEADER_NAME]: `${BEARER_TOKEN_PREFIX}${shutdownToken}` }
4037
+ }, timeoutMs);
4038
+ const payload = await response.json();
4039
+ if (!isShutdownResponse(payload)) throw new Error("Received invalid shutdown response payload.");
4040
+ if (!response.ok || !payload.ok) throw new Error(payload.message);
4041
+ return payload;
3435
4042
  }
3436
- getPort() {
3437
- return this.config.port;
4043
+ /**
4044
+ * Poll until the target runtime is no longer reachable.
4045
+ * @param runtime - Runtime expected to stop
4046
+ * @param timeoutMs - Maximum wait time in milliseconds
4047
+ * @returns Promise that resolves when shutdown is observed
4048
+ */
4049
+ async waitForShutdown(runtime, timeoutMs) {
4050
+ const deadline = Date.now() + timeoutMs;
4051
+ while (Date.now() < deadline) {
4052
+ if (!(await this.fetchHealth(runtime, Math.max(HEALTH_REQUEST_TIMEOUT_FLOOR_MS, deadline - Date.now()))).reachable) return;
4053
+ await sleep(SHUTDOWN_POLL_INTERVAL_MS);
4054
+ }
4055
+ throw new Error(`Timed out waiting for runtime '${runtime.serverId}' to stop at http://${runtime.host}:${runtime.port}.`);
3438
4056
  }
3439
- getHost() {
3440
- return this.config.host;
4057
+ /**
4058
+ * Perform a fetch with an abort timeout.
4059
+ * @param url - Target URL
4060
+ * @param init - Fetch options
4061
+ * @param timeoutMs - Timeout in milliseconds
4062
+ * @returns Fetch response
4063
+ */
4064
+ async fetchWithTimeout(url, init, timeoutMs) {
4065
+ const controller = new AbortController();
4066
+ const timeoutId = setTimeout(() => {
4067
+ controller.abort();
4068
+ }, timeoutMs);
4069
+ try {
4070
+ return await fetch(url, {
4071
+ ...init,
4072
+ signal: controller.signal
4073
+ });
4074
+ } catch (error) {
4075
+ throw new Error(`Request to '${url}' failed: ${toErrorMessage(error)}`);
4076
+ } finally {
4077
+ clearTimeout(timeoutId);
4078
+ }
3441
4079
  }
3442
4080
  };
3443
4081
 
3444
4082
  //#endregion
3445
- export { version as a, DescribeToolsTool as c, DefinitionsCacheService as d, generateServerId as f, createServer as i, SkillService as l, ConfigFetcherService as m, SseTransportHandler as n, UseToolTool as o, findConfigFile as p, StdioTransportHandler as r, SearchListToolsTool as s, HttpTransportHandler as t, McpClientManagerService as u };
4083
+ 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 };