@forklaunch/core 0.9.22 → 0.10.0

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.
@@ -15,6 +15,13 @@ function cors(corsOptions) {
15
15
  };
16
16
  }
17
17
 
18
+ // src/http/router/expressLikeRouter.ts
19
+ import {
20
+ sanitizePathSlashes,
21
+ toPrettyCamelCase,
22
+ toRecord
23
+ } from "@forklaunch/common";
24
+
18
25
  // src/http/guards/isForklaunchRouter.ts
19
26
  function isForklaunchRouter(maybeForklaunchRouter) {
20
27
  return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
@@ -33,7 +40,7 @@ function isExpressLikeSchemaHandler(middleware2) {
33
40
  (argumentName) => argumentName.toLowerCase()
34
41
  )
35
42
  );
36
- return extractedArgumentNames && extractedArgumentNames.size <= 3;
43
+ return extractedArgumentNames && extractedArgumentNames.size <= 4;
37
44
  }
38
45
 
39
46
  // src/http/guards/isForklaunchExpressLikeRouter.ts
@@ -617,6 +624,12 @@ function discriminateBody(schemaValidator, body) {
617
624
  parserType: "text",
618
625
  schema: maybeTypedBody
619
626
  };
627
+ } else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
628
+ return {
629
+ contentType: "application/octet-stream",
630
+ parserType: "file",
631
+ schema: maybeTypedBody
632
+ };
620
633
  } else {
621
634
  return {
622
635
  contentType: "application/json",
@@ -668,6 +681,12 @@ function discriminateResponseBodies(schemaValidator, responses) {
668
681
  parserType: "text",
669
682
  schema: response
670
683
  };
684
+ } else if (schemaValidator.openapi(response).format === "binary") {
685
+ discriminatedResponses[Number(statusCode)] = {
686
+ contentType: "application/octet-stream",
687
+ parserType: "file",
688
+ schema: response
689
+ };
671
690
  } else {
672
691
  discriminatedResponses[Number(statusCode)] = {
673
692
  contentType: "application/json",
@@ -687,37 +706,38 @@ function discriminateResponseBodies(schemaValidator, responses) {
687
706
  }
688
707
 
689
708
  // src/http/router/expressLikeRouter.ts
690
- var ForklaunchExpressLikeRouter = class {
691
- constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector) {
709
+ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
710
+ constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, sdkName) {
711
+ this.basePath = basePath;
692
712
  this.schemaValidator = schemaValidator;
693
713
  this.internal = internal;
694
714
  this.postEnrichMiddleware = postEnrichMiddleware;
695
715
  this.openTelemetryCollector = openTelemetryCollector;
696
- this.basePath = basePath;
697
- process.on("uncaughtException", (err) => {
698
- this.openTelemetryCollector.log("error", `Uncaught exception: ${err}`);
699
- process.exit(1);
700
- });
701
- process.on("unhandledRejection", (reason) => {
702
- this.openTelemetryCollector.log(
703
- "error",
704
- `Unhandled rejection: ${reason}`
705
- );
706
- process.exit(1);
707
- });
708
- process.on("exit", () => {
709
- this.openTelemetryCollector.log("info", "Shutting down application");
710
- });
711
- process.on("SIGINT", () => {
712
- this.openTelemetryCollector.log("info", "Shutting down application");
713
- process.exit(0);
714
- });
716
+ this.sdkName = sdkName;
717
+ if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
718
+ process.on("uncaughtException", (err) => {
719
+ this.openTelemetryCollector.error(`Uncaught exception: ${err}`);
720
+ process.exit(1);
721
+ });
722
+ process.on("unhandledRejection", (reason) => {
723
+ this.openTelemetryCollector.error(`Unhandled rejection: ${reason}`);
724
+ process.exit(1);
725
+ });
726
+ process.on("exit", () => {
727
+ this.openTelemetryCollector.info("Shutting down application");
728
+ });
729
+ process.on("SIGINT", () => {
730
+ this.openTelemetryCollector.info("Shutting down application");
731
+ process.exit(0);
732
+ });
733
+ }
715
734
  this.internal.use(createContext(this.schemaValidator));
716
735
  }
717
736
  requestHandler;
718
737
  routers = [];
719
738
  routes = [];
720
- basePath;
739
+ fetchMap = {};
740
+ sdk = {};
721
741
  /**
722
742
  * Resolves middlewares based on the contract details.
723
743
  *
@@ -836,6 +856,20 @@ var ForklaunchExpressLikeRouter = class {
836
856
  responseSchemas
837
857
  };
838
858
  }
859
+ /**
860
+ * Fetches a route from the route map and executes it with the given parameters.
861
+ *
862
+ * @template Path - The path type that extends keyof fetchMap and string.
863
+ * @param {Path} path - The route path
864
+ * @param {Parameters<fetchMap[Path]>[1]} [requestInit] - Optional request initialization parameters.
865
+ * @returns {Promise<ReturnType<fetchMap[Path]>>} - The result of executing the route handler.
866
+ */
867
+ fetch = async (path, ...reqInit) => {
868
+ return this.fetchMap[path](
869
+ path,
870
+ reqInit[0]
871
+ );
872
+ };
839
873
  /**
840
874
  * Executes request locally, applying parameters
841
875
  *
@@ -855,24 +889,6 @@ var ForklaunchExpressLikeRouter = class {
855
889
  body: discriminateBody(this.schemaValidator, request?.body)?.schema ?? {},
856
890
  path: route
857
891
  };
858
- function remapFileBody(body) {
859
- if (body instanceof File) {
860
- return (name, contentType) => {
861
- return new File([body], name, { type: contentType });
862
- };
863
- }
864
- Object.entries(body).forEach(([key, value]) => {
865
- if (value instanceof File) {
866
- body[key] = (name, contentType) => {
867
- return new File([value], name, { type: contentType });
868
- };
869
- } else if (typeof value === "object") {
870
- body[key] = remapFileBody(value);
871
- }
872
- });
873
- return body;
874
- }
875
- req.body = remapFileBody(req.body);
876
892
  const res = {
877
893
  status: (code) => {
878
894
  statusCode = code;
@@ -926,24 +942,20 @@ var ForklaunchExpressLikeRouter = class {
926
942
  registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
927
943
  if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
928
944
  const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
929
- return this.registerRoute(
930
- method,
931
- path,
932
- registrationMethod,
933
- contractDetails,
934
- ...handlers
935
- );
945
+ this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
946
+ return this;
936
947
  } else {
937
948
  const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
938
949
  if (isTypedHandler(maybeTypedHandler)) {
939
950
  const { contractDetails, handlers } = maybeTypedHandler;
940
- return this.registerRoute(
951
+ this.registerRoute(
941
952
  method,
942
953
  path,
943
954
  registrationMethod,
944
955
  contractDetails,
945
956
  ...middlewareOrMiddlewareAndTypedHandler.concat(handlers)
946
957
  );
958
+ return this;
947
959
  } else {
948
960
  if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
949
961
  throw new Error("Contract details are not defined");
@@ -975,10 +987,13 @@ var ForklaunchExpressLikeRouter = class {
975
987
  ).concat(handlers),
976
988
  this.#parseAndRunControllerHandler(controllerHandler)
977
989
  );
978
- return this.#localParamRequest(
990
+ const localParamRequest = this.#localParamRequest(
979
991
  handlers,
980
992
  controllerHandler
981
993
  );
994
+ toRecord(this.fetchMap)[sanitizePathSlashes(`${this.basePath}${path}`)] = localParamRequest;
995
+ toRecord(this.sdk)[toPrettyCamelCase(contractDetails.name ?? this.basePath)] = (req) => localParamRequest(`${this.basePath}${path}`, req);
996
+ return this;
982
997
  }
983
998
  }
984
999
  }
@@ -1002,10 +1017,13 @@ var ForklaunchExpressLikeRouter = class {
1002
1017
  return this.#extractHandlers(handlers);
1003
1018
  }
1004
1019
  #extractNestableMiddlewareFromEnrichedTypedHandlerArray(handlers) {
1005
- return this.#extractHandlers(
1006
- handlers,
1007
- (handler) => isForklaunchExpressLikeRouter(handler) ? handler.internal : handler
1008
- );
1020
+ return this.#extractHandlers(handlers, (handler) => {
1021
+ if (isForklaunchExpressLikeRouter(handler)) {
1022
+ this.addRouterToSdk(handler);
1023
+ return handler.internal;
1024
+ }
1025
+ return handler;
1026
+ });
1009
1027
  }
1010
1028
  #processTypedHandlerOrMiddleware(handler, middleware2) {
1011
1029
  if (isTypedHandler(handler)) {
@@ -1029,6 +1047,7 @@ var ForklaunchExpressLikeRouter = class {
1029
1047
  middleware2
1030
1048
  );
1031
1049
  if (isForklaunchExpressLikeRouter(contractDetailsOrMiddlewareOrTypedHandler)) {
1050
+ this.addRouterToSdk(contractDetailsOrMiddlewareOrTypedHandler);
1032
1051
  middleware2.push(contractDetailsOrMiddlewareOrTypedHandler.internal);
1033
1052
  }
1034
1053
  middleware2.push(
@@ -1058,6 +1077,16 @@ var ForklaunchExpressLikeRouter = class {
1058
1077
  }
1059
1078
  return this;
1060
1079
  }
1080
+ addRouterToSdk(router) {
1081
+ Object.entries(router.fetchMap).map(
1082
+ ([key, value]) => toRecord(this.fetchMap)[sanitizePathSlashes(`${this.basePath}${key}`)] = value
1083
+ );
1084
+ const existingSdk = this.sdk[router.sdkName ?? toPrettyCamelCase(router.basePath)];
1085
+ toRecord(this.sdk)[router.sdkName ?? toPrettyCamelCase(router.basePath)] = {
1086
+ ...typeof existingSdk === "object" ? existingSdk : {},
1087
+ ...router.sdk
1088
+ };
1089
+ }
1061
1090
  registerNestableMiddlewareHandler(registrationMethod, pathOrContractDetailsOrMiddlewareOrTypedHandler, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) {
1062
1091
  const middleware2 = [];
1063
1092
  let path;
@@ -1073,7 +1102,9 @@ var ForklaunchExpressLikeRouter = class {
1073
1102
  if (isConstrainedForklaunchRouter(
1074
1103
  pathOrContractDetailsOrMiddlewareOrTypedHandler
1075
1104
  )) {
1076
- path = pathOrContractDetailsOrMiddlewareOrTypedHandler.basePath;
1105
+ const router = pathOrContractDetailsOrMiddlewareOrTypedHandler;
1106
+ this.addRouterToSdk(router);
1107
+ path = router.basePath;
1077
1108
  }
1078
1109
  middleware2.push(
1079
1110
  ...this.#extractNestableMiddlewareAsRouterHandlers(
@@ -1084,6 +1115,11 @@ var ForklaunchExpressLikeRouter = class {
1084
1115
  )
1085
1116
  );
1086
1117
  }
1118
+ if (!path) {
1119
+ path = middleware2.filter(
1120
+ (m) => isForklaunchExpressLikeRouter(m)
1121
+ )[0]?.basePath;
1122
+ }
1087
1123
  if (path) {
1088
1124
  registrationMethod.bind(this.internal)(path, ...middleware2);
1089
1125
  } else {
@@ -1138,15 +1174,13 @@ var ForklaunchExpressLikeRouter = class {
1138
1174
  * @returns {ExpressRouter} - The Express router.
1139
1175
  */
1140
1176
  get = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1141
- return {
1142
- get: this.registerRoute(
1143
- "get",
1144
- path,
1145
- this.internal.get,
1146
- contractDetailsOrMiddlewareOrTypedHandler,
1147
- ...middlewareOrMiddlewareWithTypedHandler
1148
- )
1149
- };
1177
+ return this.registerRoute(
1178
+ "get",
1179
+ path,
1180
+ this.internal.get,
1181
+ contractDetailsOrMiddlewareOrTypedHandler,
1182
+ ...middlewareOrMiddlewareWithTypedHandler
1183
+ );
1150
1184
  };
1151
1185
  /**
1152
1186
  * Registers a POST route with the specified contract details and handler handlers.
@@ -1162,15 +1196,13 @@ var ForklaunchExpressLikeRouter = class {
1162
1196
  * @returns {ExpressRouter} - The Expxwress router.
1163
1197
  */
1164
1198
  post = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1165
- return {
1166
- post: this.registerRoute(
1167
- "post",
1168
- path,
1169
- this.internal.post,
1170
- contractDetailsOrMiddlewareOrTypedHandler,
1171
- ...middlewareOrMiddlewareWithTypedHandler
1172
- )
1173
- };
1199
+ return this.registerRoute(
1200
+ "post",
1201
+ path,
1202
+ this.internal.post,
1203
+ contractDetailsOrMiddlewareOrTypedHandler,
1204
+ ...middlewareOrMiddlewareWithTypedHandler
1205
+ );
1174
1206
  };
1175
1207
  /**
1176
1208
  * Registers a PUT route with the specified contract details and handler handlers.
@@ -1186,15 +1218,13 @@ var ForklaunchExpressLikeRouter = class {
1186
1218
  * @returns {ExpressRouter} - The Express router.
1187
1219
  */
1188
1220
  put = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1189
- return {
1190
- put: this.registerRoute(
1191
- "put",
1192
- path,
1193
- this.internal.put,
1194
- contractDetailsOrMiddlewareOrTypedHandler,
1195
- ...middlewareOrMiddlewareWithTypedHandler
1196
- )
1197
- };
1221
+ return this.registerRoute(
1222
+ "put",
1223
+ path,
1224
+ this.internal.put,
1225
+ contractDetailsOrMiddlewareOrTypedHandler,
1226
+ ...middlewareOrMiddlewareWithTypedHandler
1227
+ );
1198
1228
  };
1199
1229
  /**
1200
1230
  * Registers a PATCH route with the specified contract details and handler handlers.
@@ -1210,15 +1240,13 @@ var ForklaunchExpressLikeRouter = class {
1210
1240
  * @returns {ExpressRouter} - The Express router.
1211
1241
  */
1212
1242
  patch = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1213
- return {
1214
- patch: this.registerRoute(
1215
- "patch",
1216
- path,
1217
- this.internal.patch,
1218
- contractDetailsOrMiddlewareOrTypedHandler,
1219
- ...middlewareOrMiddlewareWithTypedHandler
1220
- )
1221
- };
1243
+ return this.registerRoute(
1244
+ "patch",
1245
+ path,
1246
+ this.internal.patch,
1247
+ contractDetailsOrMiddlewareOrTypedHandler,
1248
+ ...middlewareOrMiddlewareWithTypedHandler
1249
+ );
1222
1250
  };
1223
1251
  /**
1224
1252
  * Registers a DELETE route with the specified contract details and handler handlers.
@@ -1234,15 +1262,13 @@ var ForklaunchExpressLikeRouter = class {
1234
1262
  * @returns {ExpressRouter} - The Express router.
1235
1263
  */
1236
1264
  delete = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1237
- return {
1238
- delete: this.registerRoute(
1239
- "delete",
1240
- path,
1241
- this.internal.delete,
1242
- contractDetailsOrMiddlewareOrTypedHandler,
1243
- ...middlewareOrMiddlewareWithTypedHandler
1244
- )
1245
- };
1265
+ return this.registerRoute(
1266
+ "delete",
1267
+ path,
1268
+ this.internal.delete,
1269
+ contractDetailsOrMiddlewareOrTypedHandler,
1270
+ ...middlewareOrMiddlewareWithTypedHandler
1271
+ );
1246
1272
  };
1247
1273
  /**
1248
1274
  * Registers a OPTIONS route with the specified contract details and handler handlers.
@@ -1258,15 +1284,13 @@ var ForklaunchExpressLikeRouter = class {
1258
1284
  * @returns {ExpressRouter} - The Express router.
1259
1285
  */
1260
1286
  options = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1261
- return {
1262
- options: this.registerRoute(
1263
- "options",
1264
- path,
1265
- this.internal.options,
1266
- contractDetailsOrMiddlewareOrTypedHandler,
1267
- ...middlewareOrMiddlewareWithTypedHandler
1268
- )
1269
- };
1287
+ return this.registerRoute(
1288
+ "options",
1289
+ path,
1290
+ this.internal.options,
1291
+ contractDetailsOrMiddlewareOrTypedHandler,
1292
+ ...middlewareOrMiddlewareWithTypedHandler
1293
+ );
1270
1294
  };
1271
1295
  /**
1272
1296
  * Registers a HEAD route with the specified contract details and handler handlers.
@@ -1282,15 +1306,13 @@ var ForklaunchExpressLikeRouter = class {
1282
1306
  * @returns {ExpressRouter} - The Express router.
1283
1307
  */
1284
1308
  head = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1285
- return {
1286
- head: this.registerRoute(
1287
- "head",
1288
- path,
1289
- this.internal.head,
1290
- contractDetailsOrMiddlewareOrTypedHandler,
1291
- ...middlewareOrMiddlewareWithTypedHandler
1292
- )
1293
- };
1309
+ return this.registerRoute(
1310
+ "head",
1311
+ path,
1312
+ this.internal.head,
1313
+ contractDetailsOrMiddlewareOrTypedHandler,
1314
+ ...middlewareOrMiddlewareWithTypedHandler
1315
+ );
1294
1316
  };
1295
1317
  /**
1296
1318
  * Registers a TRACE route with the specified contract details and handler handlers.
@@ -1306,16 +1328,32 @@ var ForklaunchExpressLikeRouter = class {
1306
1328
  * @returns {ExpressRouter} - The Express router.
1307
1329
  */
1308
1330
  trace = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1309
- return {
1310
- trace: this.registerRoute(
1311
- "trace",
1312
- path,
1313
- this.internal.trace,
1314
- contractDetailsOrMiddlewareOrTypedHandler,
1315
- ...middlewareOrMiddlewareWithTypedHandler
1316
- )
1317
- };
1331
+ return this.registerRoute(
1332
+ "trace",
1333
+ path,
1334
+ this.internal.trace,
1335
+ contractDetailsOrMiddlewareOrTypedHandler,
1336
+ ...middlewareOrMiddlewareWithTypedHandler
1337
+ );
1318
1338
  };
1339
+ cloneInternals(clone) {
1340
+ clone.routers = [...this.routers];
1341
+ clone.routes = [...this.routes];
1342
+ clone.fetchMap = { ...this.fetchMap };
1343
+ clone.sdk = { ...this.sdk };
1344
+ }
1345
+ clone() {
1346
+ const clone = new _ForklaunchExpressLikeRouter(
1347
+ this.basePath,
1348
+ this.schemaValidator,
1349
+ this.internal,
1350
+ this.postEnrichMiddleware,
1351
+ this.openTelemetryCollector,
1352
+ this.sdkName
1353
+ );
1354
+ this.cloneInternals(clone);
1355
+ return clone;
1356
+ }
1319
1357
  };
1320
1358
 
1321
1359
  // src/http/application/expressLikeApplication.ts
@@ -2402,6 +2440,218 @@ var getCodeForStatus = (status) => {
2402
2440
  };
2403
2441
  var httpStatusCodes_default = HTTPStatuses;
2404
2442
 
2443
+ // src/http/mcpGenerator/mcpGenerator.ts
2444
+ import { isNever as isNever2, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2445
+ import { ZodSchemaValidator } from "@forklaunch/validator/zod";
2446
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2447
+
2448
+ // src/http/router/unpackRouters.ts
2449
+ import { toPrettyCamelCase as toPrettyCamelCase2 } from "@forklaunch/common";
2450
+ function unpackRouters(routers, recursiveBasePath = [], recursiveSdkPath = []) {
2451
+ return routers.reduce((acc, router) => {
2452
+ acc.push({
2453
+ fullPath: [...recursiveBasePath, router.basePath].join(""),
2454
+ sdkPath: [
2455
+ ...recursiveSdkPath,
2456
+ toPrettyCamelCase2(router.sdkName ?? router.basePath)
2457
+ ].join("."),
2458
+ router
2459
+ });
2460
+ acc.push(
2461
+ ...unpackRouters(
2462
+ router.routers,
2463
+ [...recursiveBasePath, router.basePath],
2464
+ [
2465
+ ...recursiveSdkPath,
2466
+ toPrettyCamelCase2(router.sdkName ?? router.basePath)
2467
+ ]
2468
+ )
2469
+ );
2470
+ return acc;
2471
+ }, []);
2472
+ }
2473
+
2474
+ // src/http/mcpGenerator/mcpGenerator.ts
2475
+ function generateMcpServer(schemaValidator, protocol, host, port, version, routers, contentTypeMap) {
2476
+ if (!(schemaValidator instanceof ZodSchemaValidator)) {
2477
+ throw new Error(
2478
+ "Schema validator must be an instance of ZodSchemaValidator"
2479
+ );
2480
+ }
2481
+ const mcpServer = new McpServer({
2482
+ name: "example-server",
2483
+ version
2484
+ });
2485
+ unpackRouters(routers).forEach(({ fullPath, router }) => {
2486
+ router.routes.forEach((route) => {
2487
+ let discriminatedBody;
2488
+ if ("body" in route.contractDetails) {
2489
+ discriminatedBody = discriminateBody(
2490
+ schemaValidator,
2491
+ route.contractDetails.body
2492
+ );
2493
+ }
2494
+ const inputSchema = {
2495
+ ...discriminatedBody && "body" in route.contractDetails ? {
2496
+ ..."contentType" in route.contractDetails.body ? { contentType: route.contractDetails.body.contentType } : {},
2497
+ body: schemaValidator.schemify(discriminatedBody.schema)
2498
+ } : {},
2499
+ ...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
2500
+ ...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
2501
+ ...route.contractDetails.requestHeaders ? {
2502
+ headers: schemaValidator.schemify(
2503
+ route.contractDetails.requestHeaders
2504
+ )
2505
+ } : {}
2506
+ // TODO: support auth
2507
+ // ...(route.contractDetails.auth
2508
+ // ? { auth: route.contractDetails.auth }
2509
+ // : {})
2510
+ };
2511
+ mcpServer.tool(
2512
+ route.contractDetails.name,
2513
+ route.contractDetails.summary,
2514
+ inputSchema,
2515
+ async (args) => {
2516
+ const { contentType, body, params, query, headers } = args;
2517
+ let url = `${protocol}://${host}:${port}${fullPath}${route.path}`;
2518
+ if (params) {
2519
+ for (const key in params) {
2520
+ url = url.replace(
2521
+ `:${key}`,
2522
+ encodeURIComponent(params[key])
2523
+ );
2524
+ }
2525
+ }
2526
+ let parsedBody;
2527
+ if (discriminatedBody) {
2528
+ switch (discriminatedBody.parserType) {
2529
+ case "json": {
2530
+ parsedBody = safeStringify2(body);
2531
+ break;
2532
+ }
2533
+ case "text": {
2534
+ parsedBody = body;
2535
+ break;
2536
+ }
2537
+ case "file": {
2538
+ parsedBody = body;
2539
+ break;
2540
+ }
2541
+ case "multipart": {
2542
+ const formData = new FormData();
2543
+ if (isRecord(body)) {
2544
+ for (const key in body) {
2545
+ if (typeof body[key] === "string" || body[key] instanceof Blob) {
2546
+ formData.append(key, body[key]);
2547
+ } else {
2548
+ throw new Error("Body is not a valid multipart object");
2549
+ }
2550
+ }
2551
+ } else {
2552
+ throw new Error("Body is not a valid multipart object");
2553
+ }
2554
+ parsedBody = formData;
2555
+ break;
2556
+ }
2557
+ case "urlEncoded": {
2558
+ if (isRecord(body)) {
2559
+ parsedBody = new URLSearchParams(
2560
+ Object.entries(body).map(([key, value]) => [
2561
+ key,
2562
+ safeStringify2(value)
2563
+ ])
2564
+ );
2565
+ } else {
2566
+ throw new Error("Body is not a valid url encoded object");
2567
+ }
2568
+ break;
2569
+ }
2570
+ default: {
2571
+ isNever2(discriminatedBody.parserType);
2572
+ parsedBody = safeStringify2(body);
2573
+ break;
2574
+ }
2575
+ }
2576
+ }
2577
+ if (query) {
2578
+ const queryString = new URLSearchParams(
2579
+ Object.entries(query).map(([key, value]) => [
2580
+ key,
2581
+ safeStringify2(value)
2582
+ ])
2583
+ ).toString();
2584
+ url += queryString ? `?${queryString}` : "";
2585
+ }
2586
+ const response = await fetch(encodeURI(url), {
2587
+ method: route.method.toUpperCase(),
2588
+ headers: {
2589
+ ...headers,
2590
+ ...discriminatedBody?.contentType != "multipart/form-data" ? {
2591
+ "Content-Type": contentType ?? discriminatedBody?.contentType
2592
+ } : {}
2593
+ },
2594
+ body: parsedBody
2595
+ });
2596
+ if (response.status >= 300) {
2597
+ throw new Error(
2598
+ `Error received while proxying request to ${url}: ${await response.text()}`
2599
+ );
2600
+ }
2601
+ const contractContentType = discriminateResponseBodies(
2602
+ schemaValidator,
2603
+ route.contractDetails.responses
2604
+ )[response.status].contentType;
2605
+ switch (contentTypeMap && contentTypeMap[contractContentType] ? contentTypeMap[contractContentType] : contractContentType) {
2606
+ case "application/json":
2607
+ return {
2608
+ content: [
2609
+ {
2610
+ type: "text",
2611
+ text: safeStringify2(await response.json())
2612
+ }
2613
+ ]
2614
+ };
2615
+ case "text/plain":
2616
+ return {
2617
+ content: [
2618
+ { type: "text", text: await response.text() }
2619
+ ]
2620
+ };
2621
+ case "application/octet-stream":
2622
+ return {
2623
+ content: [
2624
+ {
2625
+ type: "resource",
2626
+ resource: {
2627
+ uri: response.url,
2628
+ blob: Buffer.from(
2629
+ await (await response.blob()).arrayBuffer()
2630
+ ).toString("base64")
2631
+ }
2632
+ }
2633
+ ]
2634
+ };
2635
+ case "text/event-stream":
2636
+ return {
2637
+ content: [
2638
+ { type: "text", text: await response.text() }
2639
+ ]
2640
+ };
2641
+ default:
2642
+ return {
2643
+ content: [
2644
+ { type: "text", text: await response.text() }
2645
+ ]
2646
+ };
2647
+ }
2648
+ }
2649
+ );
2650
+ });
2651
+ });
2652
+ return mcpServer;
2653
+ }
2654
+
2405
2655
  // src/http/middleware/response/parse.middleware.ts
2406
2656
  import {
2407
2657
  prettyPrintParseErrors as prettyPrintParseErrors2
@@ -2468,11 +2718,11 @@ ${parseErrors.join("\n\n")}`
2468
2718
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
2469
2719
  import {
2470
2720
  isAsyncGenerator,
2471
- isNever as isNever2,
2721
+ isNever as isNever3,
2472
2722
  isNodeJsWriteableStream,
2473
- isRecord,
2723
+ isRecord as isRecord2,
2474
2724
  readableStreamToAsyncIterable,
2475
- safeStringify as safeStringify2
2725
+ safeStringify as safeStringify3
2476
2726
  } from "@forklaunch/common";
2477
2727
  import { Readable, Transform } from "stream";
2478
2728
 
@@ -2509,7 +2759,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
2509
2759
  if (res.statusCode === 404) {
2510
2760
  res.type("text/plain");
2511
2761
  res.status(404);
2512
- logger("error").error("Not Found");
2762
+ req.openTelemetryCollector.error("Not Found");
2513
2763
  originalSend.call(instance, "Not Found");
2514
2764
  errorSent = true;
2515
2765
  }
@@ -2552,7 +2802,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
2552
2802
  ------------------
2553
2803
  ${res.locals.errorMessage}`;
2554
2804
  }
2555
- logger("error").error(errorString);
2805
+ req.openTelemetryCollector.error(errorString);
2556
2806
  res.type("text/plain");
2557
2807
  res.status(500);
2558
2808
  originalSend.call(instance, errorString);
@@ -2565,7 +2815,7 @@ ${res.locals.errorMessage}`;
2565
2815
  if (!errorSent) {
2566
2816
  let data2 = "";
2567
2817
  for (const [key, value] of Object.entries(chunk)) {
2568
- data2 += `${key}: ${typeof value === "string" ? value : safeStringify2(value)}
2818
+ data2 += `${key}: ${typeof value === "string" ? value : safeStringify3(value)}
2569
2819
  `;
2570
2820
  }
2571
2821
  data2 += "\n";
@@ -2584,7 +2834,7 @@ ${res.locals.errorMessage}`;
2584
2834
  } else {
2585
2835
  const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
2586
2836
  res.bodyData = data;
2587
- if (isRecord(data)) {
2837
+ if (isRecord2(data)) {
2588
2838
  switch (parserType) {
2589
2839
  case "json":
2590
2840
  res.bodyData = "json" in data ? data.json : data;
@@ -2605,7 +2855,7 @@ ${res.locals.errorMessage}`;
2605
2855
  res.bodyData = data;
2606
2856
  break;
2607
2857
  default:
2608
- isNever2(parserType);
2858
+ isNever3(parserType);
2609
2859
  res.bodyData = data;
2610
2860
  break;
2611
2861
  }
@@ -2618,7 +2868,7 @@ ${res.locals.errorMessage}`;
2618
2868
  ------------------
2619
2869
  ${res.locals.errorMessage}`;
2620
2870
  }
2621
- logger("error").error(errorString);
2871
+ req.openTelemetryCollector.error(errorString);
2622
2872
  res.type("text/plain");
2623
2873
  res.status(500);
2624
2874
  originalSend.call(instance, errorString);
@@ -2641,16 +2891,17 @@ ${res.locals.errorMessage}`;
2641
2891
  }
2642
2892
 
2643
2893
  // src/http/openApiV3Generator/openApiV3Generator.ts
2894
+ import { openApiCompliantPath, toPrettyCamelCase as toPrettyCamelCase3 } from "@forklaunch/common";
2644
2895
  function toUpperCase(str) {
2645
2896
  return str.charAt(0).toUpperCase() + str.slice(1);
2646
2897
  }
2647
2898
  function transformBasePath(basePath) {
2648
2899
  if (basePath.startsWith("/")) {
2649
- return toUpperCase(basePath.slice(1));
2900
+ return basePath.slice(1);
2650
2901
  }
2651
2902
  return `/${basePath}`;
2652
2903
  }
2653
- function generateOpenApiDocument(port, tags, paths) {
2904
+ function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers) {
2654
2905
  return {
2655
2906
  openapi: "3.1.0",
2656
2907
  info: {
@@ -2669,8 +2920,10 @@ function generateOpenApiDocument(port, tags, paths) {
2669
2920
  tags,
2670
2921
  servers: [
2671
2922
  {
2672
- url: `http://localhost:${port}`
2673
- }
2923
+ url: `${protocol}://${host}:${port}`,
2924
+ description: "Main Server"
2925
+ },
2926
+ ...otherServers || []
2674
2927
  ],
2675
2928
  paths
2676
2929
  };
@@ -2691,19 +2944,21 @@ function contentResolver(schemaValidator, body, contentType) {
2691
2944
  }
2692
2945
  };
2693
2946
  }
2694
- function generateSwaggerDocument(schemaValidator, port, routers) {
2947
+ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers, otherServers) {
2695
2948
  const tags = [];
2696
2949
  const paths = {};
2697
- routers.flat(Infinity).forEach((router) => {
2698
- const controllerName = transformBasePath(router.basePath);
2950
+ unpackRouters(routers).forEach(({ fullPath, router, sdkPath }) => {
2951
+ const controllerName = transformBasePath(fullPath);
2699
2952
  tags.push({
2700
2953
  name: controllerName,
2701
- description: `${controllerName} Operations`
2954
+ description: `${toUpperCase(controllerName)} Operations`
2702
2955
  });
2703
2956
  router.routes.forEach((route) => {
2704
- const fullPath = `${router.basePath}${route.path === "/" ? "" : route.path}`.replace(/:(\w+)/g, "{$1}");
2705
- if (!paths[fullPath]) {
2706
- paths[fullPath] = {};
2957
+ const openApiPath = openApiCompliantPath(
2958
+ `${fullPath}${route.path === "/" ? "" : route.path}`
2959
+ );
2960
+ if (!paths[openApiPath]) {
2961
+ paths[openApiPath] = {};
2707
2962
  }
2708
2963
  const { name, summary, query, requestHeaders } = route.contractDetails;
2709
2964
  const responses = {};
@@ -2730,15 +2985,16 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2730
2985
  };
2731
2986
  }
2732
2987
  }
2733
- const pathItemObject = {
2988
+ const operationObject = {
2734
2989
  tags: [controllerName],
2735
2990
  summary: `${name}: ${summary}`,
2736
2991
  parameters: [],
2737
- responses
2992
+ responses,
2993
+ operationId: `${sdkPath}.${toPrettyCamelCase3(name)}`
2738
2994
  };
2739
2995
  if (route.contractDetails.params) {
2740
2996
  for (const key in route.contractDetails.params) {
2741
- pathItemObject.parameters?.push({
2997
+ operationObject.parameters?.push({
2742
2998
  name: key,
2743
2999
  in: "path",
2744
3000
  schema: schemaValidator.openapi(
@@ -2749,7 +3005,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2749
3005
  }
2750
3006
  const discriminatedBodyResult = "body" in route.contractDetails ? discriminateBody(schemaValidator, route.contractDetails.body) : null;
2751
3007
  if (discriminatedBodyResult) {
2752
- pathItemObject.requestBody = {
3008
+ operationObject.requestBody = {
2753
3009
  required: true,
2754
3010
  content: contentResolver(
2755
3011
  schemaValidator,
@@ -2760,7 +3016,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2760
3016
  }
2761
3017
  if (requestHeaders) {
2762
3018
  for (const key in requestHeaders) {
2763
- pathItemObject.parameters?.push({
3019
+ operationObject.parameters?.push({
2764
3020
  name: key,
2765
3021
  in: "header",
2766
3022
  schema: schemaValidator.openapi(
@@ -2771,7 +3027,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2771
3027
  }
2772
3028
  if (query) {
2773
3029
  for (const key in query) {
2774
- pathItemObject.parameters?.push({
3030
+ operationObject.parameters?.push({
2775
3031
  name: key,
2776
3032
  in: "query",
2777
3033
  schema: schemaValidator.openapi(query[key])
@@ -2788,7 +3044,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2788
3044
  content: contentResolver(schemaValidator, schemaValidator.string)
2789
3045
  };
2790
3046
  if (route.contractDetails.auth.method === "jwt") {
2791
- pathItemObject.security = [
3047
+ operationObject.security = [
2792
3048
  {
2793
3049
  bearer: Array.from(
2794
3050
  route.contractDetails.auth.allowedPermissions?.values() || []
@@ -2798,11 +3054,18 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2798
3054
  }
2799
3055
  }
2800
3056
  if (route.method !== "middleware") {
2801
- paths[fullPath][route.method] = pathItemObject;
3057
+ paths[openApiPath][route.method] = operationObject;
2802
3058
  }
2803
3059
  });
2804
3060
  });
2805
- return generateOpenApiDocument(port, tags, paths);
3061
+ return generateOpenApiDocument(
3062
+ protocol,
3063
+ host,
3064
+ port,
3065
+ tags,
3066
+ paths,
3067
+ otherServers
3068
+ );
2806
3069
  }
2807
3070
 
2808
3071
  // src/http/telemetry/evaluateTelemetryOptions.ts
@@ -2841,6 +3104,7 @@ export {
2841
3104
  discriminateResponseBodies,
2842
3105
  enrichExpressLikeSend,
2843
3106
  evaluateTelemetryOptions,
3107
+ generateMcpServer,
2844
3108
  generateSwaggerDocument,
2845
3109
  get,
2846
3110
  getCodeForStatus,