skedyul 1.2.19 → 1.2.23

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.
Files changed (38) hide show
  1. package/dist/cli/index.js +512 -731
  2. package/dist/config/app-config.d.ts +26 -2
  3. package/dist/config/index.d.ts +1 -2
  4. package/dist/config/types/env.d.ts +8 -2
  5. package/dist/config/types/form.d.ts +10 -6
  6. package/dist/config/types/index.d.ts +0 -1
  7. package/dist/config/types/page.d.ts +2 -2
  8. package/dist/config/types/webhook.d.ts +2 -1
  9. package/dist/dedicated/server.js +503 -766
  10. package/dist/esm/index.mjs +503 -720
  11. package/dist/index.d.ts +2 -3
  12. package/dist/index.js +503 -722
  13. package/dist/server/config-serializer.d.ts +12 -0
  14. package/dist/server/dedicated.d.ts +3 -2
  15. package/dist/server/handlers/index.d.ts +12 -0
  16. package/dist/server/handlers/install-handler.d.ts +9 -0
  17. package/dist/server/handlers/oauth-callback-handler.d.ts +9 -0
  18. package/dist/server/handlers/provision-handler.d.ts +9 -0
  19. package/dist/server/handlers/types.d.ts +101 -0
  20. package/dist/server/handlers/uninstall-handler.d.ts +9 -0
  21. package/dist/server/handlers/webhook-handler.d.ts +28 -0
  22. package/dist/server/index.d.ts +15 -6
  23. package/dist/server/serverless.d.ts +3 -2
  24. package/dist/server/startup-logger.d.ts +3 -2
  25. package/dist/server/tool-handler.d.ts +1 -1
  26. package/dist/server/utils/http.d.ts +3 -2
  27. package/dist/server.d.ts +1 -1
  28. package/dist/server.js +503 -766
  29. package/dist/serverless/server.mjs +503 -744
  30. package/dist/types/handlers.d.ts +6 -24
  31. package/dist/types/index.d.ts +4 -4
  32. package/dist/types/server.d.ts +1 -34
  33. package/dist/types/shared.d.ts +5 -0
  34. package/dist/types/tool.d.ts +5 -7
  35. package/dist/types/webhook.d.ts +14 -6
  36. package/package.json +1 -1
  37. package/dist/config/resolve.d.ts +0 -27
  38. package/dist/config/types/compute.d.ts +0 -9
@@ -30,32 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/server.ts
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
- buildRequestFromRaw: () => buildRequestFromRaw,
34
- buildRequestScopedConfig: () => buildRequestScopedConfig,
35
- buildToolMetadata: () => buildToolMetadata,
36
- createCallToolHandler: () => createCallToolHandler,
37
- createDedicatedServerInstance: () => createDedicatedServerInstance,
38
- createRequestState: () => createRequestState,
39
- createResponse: () => createResponse,
40
- createServerlessInstance: () => createServerlessInstance,
41
33
  createSkedyulServer: () => createSkedyulServer,
42
- getDefaultHeaders: () => getDefaultHeaders,
43
34
  getJsonSchemaFromToolSchema: () => getJsonSchemaFromToolSchema,
44
- getListeningPort: () => getListeningPort,
45
35
  getZodSchema: () => getZodSchema,
46
- handleCoreMethod: () => handleCoreMethod,
47
36
  isToolSchemaWithJson: () => isToolSchemaWithJson,
48
- mergeRuntimeEnv: () => mergeRuntimeEnv,
49
- normalizeBilling: () => normalizeBilling,
50
- padEnd: () => padEnd,
51
- parseHandlerEnvelope: () => parseHandlerEnvelope,
52
- parseJSONBody: () => parseJSONBody,
53
- parseJsonRecord: () => parseJsonRecord,
54
- parseNumberEnv: () => parseNumberEnv,
55
- printStartupLog: () => printStartupLog,
56
- readRawRequestBody: () => readRawRequestBody,
57
- sendHTML: () => sendHTML,
58
- sendJSON: () => sendJSON,
59
37
  server: () => server,
60
38
  toJsonSchema: () => toJsonSchema
61
39
  });
@@ -203,10 +181,6 @@ function sendJSON(res, statusCode, data) {
203
181
  res.writeHead(statusCode, { "Content-Type": "application/json" });
204
182
  res.end(JSON.stringify(data));
205
183
  }
206
- function sendHTML(res, statusCode, html) {
207
- res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
208
- res.end(html);
209
- }
210
184
  function getDefaultHeaders(options) {
211
185
  return {
212
186
  "Content-Type": "application/json",
@@ -392,8 +366,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
392
366
  };
393
367
  }
394
368
  function createCallToolHandler(registry, state, onMaxRequests) {
395
- return async function callTool(nameRaw, argsRaw) {
396
- const toolName = String(nameRaw);
369
+ return async function callTool(toolNameInput, toolArgsInput) {
370
+ const toolName = String(toolNameInput);
397
371
  const tool = registry[toolName];
398
372
  if (!tool) {
399
373
  throw new Error(`Tool "${toolName}" not found in registry`);
@@ -402,7 +376,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
402
376
  throw new Error(`Tool "${toolName}" handler is not a function`);
403
377
  }
404
378
  const fn = tool.handler;
405
- const args = argsRaw ?? {};
379
+ const args = toolArgsInput ?? {};
406
380
  const estimateMode = args.estimate === true;
407
381
  if (!estimateMode) {
408
382
  state.incrementRequestCount();
@@ -657,51 +631,6 @@ async function handleCoreMethod(method, params) {
657
631
  };
658
632
  }
659
633
 
660
- // src/server/handler-helpers.ts
661
- function parseHandlerEnvelope(parsedBody) {
662
- if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
663
- return null;
664
- }
665
- const envelope = parsedBody;
666
- if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
667
- return null;
668
- }
669
- if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
670
- return null;
671
- }
672
- return {
673
- env: envelope.env,
674
- request: envelope.request,
675
- context: envelope.context
676
- };
677
- }
678
- function buildRequestFromRaw(raw) {
679
- let parsedBody = raw.body;
680
- const contentType = raw.headers["content-type"] ?? "";
681
- if (contentType.includes("application/json")) {
682
- try {
683
- parsedBody = raw.body ? JSON.parse(raw.body) : {};
684
- } catch {
685
- parsedBody = raw.body;
686
- }
687
- }
688
- return {
689
- method: raw.method,
690
- url: raw.url,
691
- path: raw.path,
692
- headers: raw.headers,
693
- query: raw.query,
694
- body: parsedBody,
695
- rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
696
- };
697
- }
698
- function buildRequestScopedConfig(env) {
699
- return {
700
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
701
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
702
- };
703
- }
704
-
705
634
  // src/server/startup-logger.ts
706
635
  function padEnd(str, length) {
707
636
  if (str.length >= length) {
@@ -709,10 +638,11 @@ function padEnd(str, length) {
709
638
  }
710
639
  return str + " ".repeat(length - str.length);
711
640
  }
712
- function printStartupLog(config, tools, webhookRegistry, port) {
641
+ function printStartupLog(config, tools, port) {
713
642
  if (process.env.NODE_ENV === "test") {
714
643
  return;
715
644
  }
645
+ const webhookRegistry = config.webhooks;
716
646
  const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
717
647
  const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
718
648
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
@@ -725,9 +655,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
725
655
  console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
726
656
  console.log(`\u2560${divider}\u2563`);
727
657
  console.log(`\u2551 \u2551`);
728
- console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.metadata.name, 49)}\u2551`);
729
- console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.metadata.version, 49)}\u2551`);
730
- console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
658
+ console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
659
+ console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
660
+ console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
731
661
  if (port) {
732
662
  console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
733
663
  }
@@ -769,67 +699,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
769
699
  console.log("");
770
700
  }
771
701
 
772
- // src/config/resolve.ts
773
- async function resolveDynamicImport(value) {
774
- if (value === void 0 || value === null) {
775
- return void 0;
702
+ // src/server/config-serializer.ts
703
+ function serializeConfig(config) {
704
+ const registry = config.tools;
705
+ const webhookRegistry = config.webhooks;
706
+ return {
707
+ name: config.name,
708
+ version: config.version,
709
+ description: config.description,
710
+ computeLayer: config.computeLayer,
711
+ tools: registry ? Object.entries(registry).map(([key, tool]) => ({
712
+ name: tool.name || key,
713
+ description: tool.description,
714
+ timeout: tool.timeout,
715
+ retries: tool.retries
716
+ })) : [],
717
+ webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
718
+ name: w.name,
719
+ description: w.description,
720
+ methods: w.methods ?? ["POST"],
721
+ type: w.type ?? "WEBHOOK"
722
+ })) : [],
723
+ provision: isProvisionConfig(config.provision) ? config.provision : void 0,
724
+ agents: config.agents
725
+ };
726
+ }
727
+ function isProvisionConfig(value) {
728
+ return value !== void 0 && value !== null && !(value instanceof Promise);
729
+ }
730
+
731
+ // src/server/handlers/install-handler.ts
732
+ async function handleInstall(body, hooks) {
733
+ if (!hooks?.install) {
734
+ return {
735
+ status: 404,
736
+ body: { error: "Install handler not configured" }
737
+ };
776
738
  }
777
- if (value instanceof Promise) {
778
- const resolved = await value;
779
- if (resolved && typeof resolved === "object" && "default" in resolved) {
780
- return resolved.default;
739
+ if (!body.context?.appInstallationId || !body.context?.workplace) {
740
+ return {
741
+ status: 400,
742
+ body: {
743
+ error: {
744
+ code: -32602,
745
+ message: "Missing context (appInstallationId and workplace required)"
746
+ }
747
+ }
748
+ };
749
+ }
750
+ const installContext = {
751
+ env: body.env ?? {},
752
+ workplace: body.context.workplace,
753
+ appInstallationId: body.context.appInstallationId,
754
+ app: body.context.app,
755
+ invocation: body.invocation,
756
+ log: createContextLogger()
757
+ };
758
+ const requestConfig = {
759
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
760
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
761
+ };
762
+ try {
763
+ const installHook = hooks.install;
764
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
765
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
766
+ return await runWithConfig(requestConfig, async () => {
767
+ return await installHandler(installContext);
768
+ });
769
+ });
770
+ return {
771
+ status: 200,
772
+ body: { env: result.env ?? {}, redirect: result.redirect }
773
+ };
774
+ } catch (err) {
775
+ if (err instanceof InstallError) {
776
+ return {
777
+ status: 400,
778
+ body: {
779
+ error: {
780
+ code: err.code,
781
+ message: err.message,
782
+ field: err.field
783
+ }
784
+ }
785
+ };
781
786
  }
782
- return resolved;
787
+ return {
788
+ status: 500,
789
+ body: {
790
+ error: {
791
+ code: -32603,
792
+ message: err instanceof Error ? err.message : String(err ?? "")
793
+ }
794
+ }
795
+ };
783
796
  }
784
- return value;
785
797
  }
786
- function serializeTools(registry) {
787
- return Object.entries(registry).map(([key, tool]) => ({
788
- name: tool.name || key,
789
- displayName: tool.label,
790
- description: tool.description,
791
- timeout: tool.timeout,
792
- retries: tool.retries
793
- }));
798
+
799
+ // src/server/handlers/uninstall-handler.ts
800
+ async function handleUninstall(body, hooks) {
801
+ if (!hooks?.uninstall) {
802
+ return {
803
+ status: 404,
804
+ body: { error: "Uninstall handler not configured" }
805
+ };
806
+ }
807
+ if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
808
+ return {
809
+ status: 400,
810
+ body: {
811
+ error: {
812
+ code: -32602,
813
+ message: "Missing context (appInstallationId, workplace and app required)"
814
+ }
815
+ }
816
+ };
817
+ }
818
+ const uninstallContext = {
819
+ env: body.env ?? {},
820
+ workplace: body.context.workplace,
821
+ appInstallationId: body.context.appInstallationId,
822
+ app: body.context.app,
823
+ invocation: body.invocation,
824
+ log: createContextLogger()
825
+ };
826
+ const requestConfig = {
827
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
828
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
829
+ };
830
+ try {
831
+ const uninstallHook = hooks.uninstall;
832
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
833
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
834
+ return await runWithConfig(requestConfig, async () => {
835
+ return await uninstallHandlerFn(uninstallContext);
836
+ });
837
+ });
838
+ return {
839
+ status: 200,
840
+ body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
841
+ };
842
+ } catch (err) {
843
+ return {
844
+ status: 500,
845
+ body: {
846
+ error: {
847
+ code: -32603,
848
+ message: err instanceof Error ? err.message : String(err ?? "")
849
+ }
850
+ }
851
+ };
852
+ }
794
853
  }
795
- function serializeWebhooks(registry) {
796
- return Object.values(registry).map((webhook) => ({
797
- name: webhook.name,
798
- description: webhook.description,
799
- methods: webhook.methods ?? ["POST"],
800
- type: webhook.type ?? "WEBHOOK"
801
- }));
854
+
855
+ // src/server/handlers/provision-handler.ts
856
+ async function handleProvision(body, hooks) {
857
+ if (!hooks?.provision) {
858
+ return {
859
+ status: 404,
860
+ body: { error: "Provision handler not configured" }
861
+ };
862
+ }
863
+ if (!body.context?.app) {
864
+ return {
865
+ status: 400,
866
+ body: {
867
+ error: {
868
+ code: -32602,
869
+ message: "Missing context (app required)"
870
+ }
871
+ }
872
+ };
873
+ }
874
+ const mergedEnv = {};
875
+ for (const [key, value] of Object.entries(process.env)) {
876
+ if (value !== void 0) {
877
+ mergedEnv[key] = value;
878
+ }
879
+ }
880
+ Object.assign(mergedEnv, body.env ?? {});
881
+ const provisionContext = {
882
+ env: mergedEnv,
883
+ app: body.context.app,
884
+ invocation: body.invocation,
885
+ log: createContextLogger()
886
+ };
887
+ const requestConfig = {
888
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
889
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
890
+ };
891
+ try {
892
+ const provisionHook = hooks.provision;
893
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
894
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
895
+ return await runWithConfig(requestConfig, async () => {
896
+ return await provisionHandler(provisionContext);
897
+ });
898
+ });
899
+ return {
900
+ status: 200,
901
+ body: result
902
+ };
903
+ } catch (err) {
904
+ return {
905
+ status: 500,
906
+ body: {
907
+ error: {
908
+ code: -32603,
909
+ message: err instanceof Error ? err.message : String(err ?? "")
910
+ }
911
+ }
912
+ };
913
+ }
802
914
  }
803
- async function resolveConfig(config, registry, webhookRegistry) {
804
- const provision = await resolveDynamicImport(
805
- config.provision
806
- );
807
- const install = await resolveDynamicImport(
808
- config.install
809
- );
810
- const tools = serializeTools(registry);
811
- const webhooks = webhookRegistry ? serializeWebhooks(webhookRegistry) : [];
915
+
916
+ // src/server/handler-helpers.ts
917
+ function parseHandlerEnvelope(parsedBody) {
918
+ if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
919
+ return null;
920
+ }
921
+ const envelope = parsedBody;
922
+ if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
923
+ return null;
924
+ }
925
+ if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
926
+ return null;
927
+ }
812
928
  return {
813
- name: config.name,
814
- version: config.version,
815
- description: config.description,
816
- computeLayer: config.computeLayer,
817
- tools,
818
- webhooks,
819
- provision,
820
- agents: config.agents
929
+ env: envelope.env,
930
+ request: envelope.request,
931
+ context: envelope.context
932
+ };
933
+ }
934
+ function buildRequestFromRaw(raw) {
935
+ let parsedBody = raw.body;
936
+ const contentType = raw.headers["content-type"] ?? "";
937
+ if (contentType.includes("application/json")) {
938
+ try {
939
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
940
+ } catch {
941
+ parsedBody = raw.body;
942
+ }
943
+ }
944
+ return {
945
+ method: raw.method,
946
+ url: raw.url,
947
+ path: raw.path,
948
+ headers: raw.headers,
949
+ query: raw.query,
950
+ body: parsedBody,
951
+ rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
952
+ };
953
+ }
954
+ function buildRequestScopedConfig(env) {
955
+ return {
956
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
957
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
958
+ };
959
+ }
960
+
961
+ // src/server/handlers/oauth-callback-handler.ts
962
+ async function handleOAuthCallback(parsedBody, hooks) {
963
+ if (!hooks?.oauth_callback) {
964
+ return {
965
+ status: 404,
966
+ body: { error: "OAuth callback handler not configured" }
967
+ };
968
+ }
969
+ const envelope = parseHandlerEnvelope(parsedBody);
970
+ if (!envelope) {
971
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
972
+ return {
973
+ status: 400,
974
+ body: {
975
+ error: {
976
+ code: -32602,
977
+ message: "Missing envelope format: expected { env, request }"
978
+ }
979
+ }
980
+ };
981
+ }
982
+ const invocation = parsedBody.invocation;
983
+ const oauthRequest = buildRequestFromRaw(envelope.request);
984
+ const requestConfig = buildRequestScopedConfig(envelope.env);
985
+ const oauthCallbackContext = {
986
+ request: oauthRequest,
987
+ invocation,
988
+ log: createContextLogger()
989
+ };
990
+ try {
991
+ const oauthCallbackHook = hooks.oauth_callback;
992
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
993
+ const result = await runWithLogContext({ invocation }, async () => {
994
+ return await runWithConfig(requestConfig, async () => {
995
+ return await oauthCallbackHandler(oauthCallbackContext);
996
+ });
997
+ });
998
+ return {
999
+ status: 200,
1000
+ body: {
1001
+ appInstallationId: result.appInstallationId,
1002
+ env: result.env ?? {}
1003
+ }
1004
+ };
1005
+ } catch (err) {
1006
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
1007
+ return {
1008
+ status: 500,
1009
+ body: {
1010
+ error: {
1011
+ code: -32603,
1012
+ message: errorMessage
1013
+ }
1014
+ }
1015
+ };
1016
+ }
1017
+ }
1018
+
1019
+ // src/server/handlers/webhook-handler.ts
1020
+ function parseWebhookRequest(parsedBody, method, url, path, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
1021
+ const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
1022
+ if (isEnvelope) {
1023
+ const envelope = parsedBody;
1024
+ const requestEnv = envelope.env ?? {};
1025
+ const invocation = envelope.invocation;
1026
+ let originalParsedBody = envelope.request.body;
1027
+ const originalContentType = envelope.request.headers["content-type"] ?? "";
1028
+ if (originalContentType.includes("application/json")) {
1029
+ try {
1030
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ const webhookRequest2 = {
1035
+ method: envelope.request.method,
1036
+ url: envelope.request.url,
1037
+ path: envelope.request.path,
1038
+ headers: envelope.request.headers,
1039
+ query: envelope.request.query,
1040
+ body: originalParsedBody,
1041
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
1042
+ };
1043
+ const envVars = { ...process.env, ...requestEnv };
1044
+ const app = envelope.context.app;
1045
+ let webhookContext2;
1046
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
1047
+ webhookContext2 = {
1048
+ env: envVars,
1049
+ app,
1050
+ appInstallationId: envelope.context.appInstallationId,
1051
+ workplace: envelope.context.workplace,
1052
+ registration: envelope.context.registration ?? {},
1053
+ invocation,
1054
+ log: createContextLogger()
1055
+ };
1056
+ } else {
1057
+ webhookContext2 = {
1058
+ env: envVars,
1059
+ app,
1060
+ invocation,
1061
+ log: createContextLogger()
1062
+ };
1063
+ }
1064
+ return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
1065
+ }
1066
+ if (!appIdHeader || !appVersionIdHeader) {
1067
+ return {
1068
+ error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
1069
+ };
1070
+ }
1071
+ const webhookRequest = {
1072
+ method,
1073
+ url,
1074
+ path,
1075
+ headers,
1076
+ query,
1077
+ body: parsedBody,
1078
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
1079
+ };
1080
+ const webhookContext = {
1081
+ env: process.env,
1082
+ app: { id: appIdHeader, versionId: appVersionIdHeader },
1083
+ log: createContextLogger()
821
1084
  };
1085
+ return { webhookRequest, webhookContext, requestEnv: {} };
822
1086
  }
823
- function createMinimalConfig(name, version) {
1087
+ async function executeWebhookHandler(handle, webhookRegistry, data) {
1088
+ const webhookDef = webhookRegistry[handle];
1089
+ if (!webhookDef) {
1090
+ return {
1091
+ status: 404,
1092
+ body: { error: `Webhook handler '${handle}' not found` }
1093
+ };
1094
+ }
1095
+ const originalEnv = { ...process.env };
1096
+ Object.assign(process.env, data.requestEnv);
1097
+ const requestConfig = {
1098
+ baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1099
+ apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1100
+ };
1101
+ let webhookResponse;
1102
+ try {
1103
+ webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
1104
+ return await runWithConfig(requestConfig, async () => {
1105
+ return await webhookDef.handler(data.webhookRequest, data.webhookContext);
1106
+ });
1107
+ });
1108
+ } catch (err) {
1109
+ console.error(`Webhook handler '${handle}' error:`, err);
1110
+ return {
1111
+ status: 500,
1112
+ body: { error: "Webhook handler error" }
1113
+ };
1114
+ } finally {
1115
+ process.env = originalEnv;
1116
+ }
824
1117
  return {
825
- name,
826
- version
1118
+ status: webhookResponse.status ?? 200,
1119
+ body: webhookResponse.body,
1120
+ headers: webhookResponse.headers
827
1121
  };
828
1122
  }
1123
+ function isMethodAllowed(webhookRegistry, handle, method) {
1124
+ const webhookDef = webhookRegistry[handle];
1125
+ if (!webhookDef) return false;
1126
+ const allowedMethods = webhookDef.methods ?? ["POST"];
1127
+ return allowedMethods.includes(method);
1128
+ }
829
1129
 
830
1130
  // src/server/dedicated.ts
831
- function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
1131
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
832
1132
  const port = getListeningPort(config);
1133
+ const registry = config.tools;
1134
+ const webhookRegistry = config.webhooks;
833
1135
  const httpServer = import_http2.default.createServer(
834
1136
  async (req, res) => {
835
1137
  function sendCoreResult(result) {
@@ -846,30 +1148,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
846
1148
  return;
847
1149
  }
848
1150
  if (pathname === "/config" && req.method === "GET") {
849
- let appConfig = config.appConfig;
850
- if (!appConfig && config.appConfigLoader) {
851
- const loaded = await config.appConfigLoader();
852
- appConfig = loaded.default;
853
- }
854
- if (!appConfig) {
855
- appConfig = createMinimalConfig(
856
- config.metadata.name,
857
- config.metadata.version
858
- );
859
- }
860
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
861
- sendJSON(res, 200, serializedConfig);
1151
+ sendJSON(res, 200, serializeConfig(config));
862
1152
  return;
863
1153
  }
864
1154
  if (pathname.startsWith("/webhooks/") && webhookRegistry) {
865
1155
  const handle = pathname.slice("/webhooks/".length);
866
- const webhookDef = webhookRegistry[handle];
867
- if (!webhookDef) {
1156
+ if (!webhookRegistry[handle]) {
868
1157
  sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
869
1158
  return;
870
1159
  }
871
- const allowedMethods = webhookDef.methods ?? ["POST"];
872
- if (!allowedMethods.includes(req.method)) {
1160
+ if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
873
1161
  sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
874
1162
  return;
875
1163
  }
@@ -891,87 +1179,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
891
1179
  } else {
892
1180
  parsedBody = rawBody;
893
1181
  }
894
- const envelope = parseHandlerEnvelope(parsedBody);
895
- let webhookRequest;
896
- let webhookContext;
897
- let requestEnv = {};
898
- let invocation;
899
- if (envelope && "context" in envelope && envelope.context) {
900
- const context = envelope.context;
901
- requestEnv = envelope.env;
902
- invocation = parsedBody.invocation;
903
- webhookRequest = buildRequestFromRaw(envelope.request);
904
- const envVars = { ...process.env, ...envelope.env };
905
- const app = context.app;
906
- if (context.appInstallationId && context.workplace) {
907
- webhookContext = {
908
- env: envVars,
909
- app,
910
- appInstallationId: context.appInstallationId,
911
- workplace: context.workplace,
912
- registration: context.registration ?? {},
913
- invocation,
914
- log: createContextLogger()
915
- };
916
- } else {
917
- webhookContext = {
918
- env: envVars,
919
- app,
920
- invocation,
921
- log: createContextLogger()
922
- };
923
- }
924
- } else {
925
- const appId = req.headers["x-skedyul-app-id"];
926
- const appVersionId = req.headers["x-skedyul-app-version-id"];
927
- if (!appId || !appVersionId) {
928
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
929
- }
930
- webhookRequest = {
931
- method: req.method ?? "POST",
932
- url: url.toString(),
933
- path: pathname,
934
- headers: req.headers,
935
- query: Object.fromEntries(url.searchParams.entries()),
936
- body: parsedBody,
937
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
938
- };
939
- webhookContext = {
940
- env: process.env,
941
- app: { id: appId, versionId: appVersionId },
942
- log: createContextLogger()
943
- };
944
- }
945
- const originalEnv = { ...process.env };
946
- Object.assign(process.env, requestEnv);
947
- const requestConfig = buildRequestScopedConfig(requestEnv);
948
- let webhookResponse;
949
- try {
950
- webhookResponse = await runWithLogContext({ invocation }, async () => {
951
- return await runWithConfig(requestConfig, async () => {
952
- return await webhookDef.handler(webhookRequest, webhookContext);
953
- });
954
- });
955
- } catch (err) {
956
- console.error(`Webhook handler '${handle}' error:`, err);
957
- sendJSON(res, 500, { error: "Webhook handler error" });
1182
+ const parseResult = parseWebhookRequest(
1183
+ parsedBody,
1184
+ req.method ?? "POST",
1185
+ url.toString(),
1186
+ pathname,
1187
+ req.headers,
1188
+ Object.fromEntries(url.searchParams.entries()),
1189
+ rawBody,
1190
+ req.headers["x-skedyul-app-id"],
1191
+ req.headers["x-skedyul-app-version-id"]
1192
+ );
1193
+ if ("error" in parseResult) {
1194
+ sendJSON(res, 400, { error: parseResult.error });
958
1195
  return;
959
- } finally {
960
- process.env = originalEnv;
961
1196
  }
962
- const status = webhookResponse.status ?? 200;
1197
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
963
1198
  const responseHeaders = {
964
- ...webhookResponse.headers
1199
+ ...result.headers
965
1200
  };
966
1201
  if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
967
1202
  responseHeaders["Content-Type"] = "application/json";
968
1203
  }
969
- res.writeHead(status, responseHeaders);
970
- if (webhookResponse.body !== void 0) {
971
- if (typeof webhookResponse.body === "string") {
972
- res.end(webhookResponse.body);
1204
+ res.writeHead(result.status, responseHeaders);
1205
+ if (result.body !== void 0) {
1206
+ if (typeof result.body === "string") {
1207
+ res.end(result.body);
973
1208
  } else {
974
- res.end(JSON.stringify(webhookResponse.body));
1209
+ res.end(JSON.stringify(result.body));
975
1210
  }
976
1211
  } else {
977
1212
  res.end();
@@ -1010,10 +1245,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1010
1245
  return;
1011
1246
  }
1012
1247
  if (pathname === "/oauth_callback" && req.method === "POST") {
1013
- if (!config.hooks?.oauth_callback) {
1014
- sendJSON(res, 404, { error: "OAuth callback handler not configured" });
1015
- return;
1016
- }
1017
1248
  let parsedBody;
1018
1249
  try {
1019
1250
  parsedBody = await parseJSONBody(req);
@@ -1024,50 +1255,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1024
1255
  });
1025
1256
  return;
1026
1257
  }
1027
- const envelope = parseHandlerEnvelope(parsedBody);
1028
- if (!envelope) {
1029
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
1030
- sendJSON(res, 400, {
1031
- error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
1032
- });
1033
- return;
1034
- }
1035
- const invocation = parsedBody.invocation;
1036
- const oauthRequest = buildRequestFromRaw(envelope.request);
1037
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
1038
- const oauthCallbackContext = {
1039
- request: oauthRequest,
1040
- invocation,
1041
- log: createContextLogger()
1042
- };
1043
- try {
1044
- const oauthCallbackHook = config.hooks.oauth_callback;
1045
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
1046
- const result = await runWithLogContext({ invocation }, async () => {
1047
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
1048
- return await oauthCallbackHandler(oauthCallbackContext);
1049
- });
1050
- });
1051
- sendJSON(res, 200, {
1052
- appInstallationId: result.appInstallationId,
1053
- env: result.env ?? {}
1054
- });
1055
- } catch (err) {
1056
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
1057
- sendJSON(res, 500, {
1058
- error: {
1059
- code: -32603,
1060
- message: errorMessage
1061
- }
1062
- });
1063
- }
1258
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
1259
+ sendJSON(res, result.status, result.body);
1064
1260
  return;
1065
1261
  }
1066
1262
  if (pathname === "/install" && req.method === "POST") {
1067
- if (!config.hooks?.install) {
1068
- sendJSON(res, 404, { error: "Install handler not configured" });
1069
- return;
1070
- }
1071
1263
  let installBody;
1072
1264
  try {
1073
1265
  installBody = await parseJSONBody(req);
@@ -1077,61 +1269,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1077
1269
  });
1078
1270
  return;
1079
1271
  }
1080
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
1081
- sendJSON(res, 400, {
1082
- error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
1083
- });
1084
- return;
1085
- }
1086
- const installContext = {
1087
- env: installBody.env ?? {},
1088
- workplace: installBody.context.workplace,
1089
- appInstallationId: installBody.context.appInstallationId,
1090
- app: installBody.context.app,
1091
- invocation: installBody.invocation,
1092
- log: createContextLogger()
1093
- };
1094
- const installRequestConfig = {
1095
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1096
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1097
- };
1098
- try {
1099
- const installHook = config.hooks.install;
1100
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
1101
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
1102
- return await runWithConfig(installRequestConfig, async () => {
1103
- return await installHandler(installContext);
1104
- });
1105
- });
1106
- sendJSON(res, 200, {
1107
- env: result.env ?? {},
1108
- redirect: result.redirect
1109
- });
1110
- } catch (err) {
1111
- if (err instanceof InstallError) {
1112
- sendJSON(res, 400, {
1113
- error: {
1114
- code: err.code,
1115
- message: err.message,
1116
- field: err.field
1117
- }
1118
- });
1119
- } else {
1120
- sendJSON(res, 500, {
1121
- error: {
1122
- code: -32603,
1123
- message: err instanceof Error ? err.message : String(err ?? "")
1124
- }
1125
- });
1126
- }
1127
- }
1272
+ const result = await handleInstall(installBody, config.hooks);
1273
+ sendJSON(res, result.status, result.body);
1128
1274
  return;
1129
1275
  }
1130
1276
  if (pathname === "/uninstall" && req.method === "POST") {
1131
- if (!config.hooks?.uninstall) {
1132
- sendJSON(res, 404, { error: "Uninstall handler not configured" });
1133
- return;
1134
- }
1135
1277
  let uninstallBody;
1136
1278
  try {
1137
1279
  uninstallBody = await parseJSONBody(req);
@@ -1141,53 +1283,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1141
1283
  });
1142
1284
  return;
1143
1285
  }
1144
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
1145
- sendJSON(res, 400, {
1146
- error: {
1147
- code: -32602,
1148
- message: "Missing context (appInstallationId, workplace and app required)"
1149
- }
1150
- });
1151
- return;
1152
- }
1153
- const uninstallContext = {
1154
- env: uninstallBody.env ?? {},
1155
- workplace: uninstallBody.context.workplace,
1156
- appInstallationId: uninstallBody.context.appInstallationId,
1157
- app: uninstallBody.context.app,
1158
- invocation: uninstallBody.invocation,
1159
- log: createContextLogger()
1160
- };
1161
- const uninstallRequestConfig = {
1162
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1163
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1164
- };
1165
- try {
1166
- const uninstallHook = config.hooks.uninstall;
1167
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
1168
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
1169
- return await runWithConfig(uninstallRequestConfig, async () => {
1170
- return await uninstallHandlerFn(uninstallContext);
1171
- });
1172
- });
1173
- sendJSON(res, 200, {
1174
- cleanedWebhookIds: result.cleanedWebhookIds ?? []
1175
- });
1176
- } catch (err) {
1177
- sendJSON(res, 500, {
1178
- error: {
1179
- code: -32603,
1180
- message: err instanceof Error ? err.message : String(err ?? "")
1181
- }
1182
- });
1183
- }
1286
+ const result = await handleUninstall(uninstallBody, config.hooks);
1287
+ sendJSON(res, result.status, result.body);
1184
1288
  return;
1185
1289
  }
1186
1290
  if (pathname === "/provision" && req.method === "POST") {
1187
- if (!config.hooks?.provision) {
1188
- sendJSON(res, 404, { error: "Provision handler not configured" });
1189
- return;
1190
- }
1191
1291
  let provisionBody;
1192
1292
  try {
1193
1293
  provisionBody = await parseJSONBody(req);
@@ -1197,46 +1297,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1197
1297
  });
1198
1298
  return;
1199
1299
  }
1200
- if (!provisionBody.context?.app) {
1201
- sendJSON(res, 400, {
1202
- error: { code: -32602, message: "Missing context (app required)" }
1203
- });
1204
- return;
1205
- }
1206
- const mergedEnv = {};
1207
- for (const [key, value] of Object.entries(process.env)) {
1208
- if (value !== void 0) {
1209
- mergedEnv[key] = value;
1210
- }
1211
- }
1212
- Object.assign(mergedEnv, provisionBody.env ?? {});
1213
- const provisionContext = {
1214
- env: mergedEnv,
1215
- app: provisionBody.context.app,
1216
- invocation: provisionBody.invocation,
1217
- log: createContextLogger()
1218
- };
1219
- const provisionRequestConfig = {
1220
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
1221
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
1222
- };
1223
- try {
1224
- const provisionHook = config.hooks.provision;
1225
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
1226
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
1227
- return await runWithConfig(provisionRequestConfig, async () => {
1228
- return await provisionHandler(provisionContext);
1229
- });
1230
- });
1231
- sendJSON(res, 200, result);
1232
- } catch (err) {
1233
- sendJSON(res, 500, {
1234
- error: {
1235
- code: -32603,
1236
- message: err instanceof Error ? err.message : String(err ?? "")
1237
- }
1238
- });
1239
- }
1300
+ const result = await handleProvision(provisionBody, config.hooks);
1301
+ sendJSON(res, result.status, result.body);
1240
1302
  return;
1241
1303
  }
1242
1304
  if (pathname === "/core" && req.method === "POST") {
@@ -1387,7 +1449,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1387
1449
  const finalPort = listenPort ?? port;
1388
1450
  return new Promise((resolve, reject) => {
1389
1451
  httpServer.listen(finalPort, () => {
1390
- printStartupLog(config, tools, webhookRegistry, finalPort);
1452
+ printStartupLog(config, tools, finalPort);
1391
1453
  resolve();
1392
1454
  });
1393
1455
  httpServer.once("error", reject);
@@ -1398,13 +1460,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1398
1460
  }
1399
1461
 
1400
1462
  // src/server/serverless.ts
1401
- function createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
1463
+ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1402
1464
  const headers = getDefaultHeaders(config.cors);
1465
+ const registry = config.tools;
1466
+ const webhookRegistry = config.webhooks;
1403
1467
  let hasLoggedStartup = false;
1404
1468
  return {
1405
1469
  async handler(event) {
1406
1470
  if (!hasLoggedStartup) {
1407
- printStartupLog(config, tools, webhookRegistry);
1471
+ printStartupLog(config, tools);
1408
1472
  hasLoggedStartup = true;
1409
1473
  }
1410
1474
  try {
@@ -1415,12 +1479,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1415
1479
  }
1416
1480
  if (path.startsWith("/webhooks/") && webhookRegistry) {
1417
1481
  const handle = path.slice("/webhooks/".length);
1418
- const webhookDef = webhookRegistry[handle];
1419
- if (!webhookDef) {
1482
+ if (!webhookRegistry[handle]) {
1420
1483
  return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
1421
1484
  }
1422
- const allowedMethods = webhookDef.methods ?? ["POST"];
1423
- if (!allowedMethods.includes(method)) {
1485
+ if (!isMethodAllowed(webhookRegistry, handle, method)) {
1424
1486
  return createResponse(405, { error: `Method ${method} not allowed` }, headers);
1425
1487
  }
1426
1488
  const rawBody = event.body ?? "";
@@ -1435,107 +1497,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1435
1497
  } else {
1436
1498
  parsedBody = rawBody;
1437
1499
  }
1438
- const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
1439
- let webhookRequest;
1440
- let webhookContext;
1441
- let requestEnv = {};
1442
- let invocation;
1443
- if (isEnvelope) {
1444
- const envelope = parsedBody;
1445
- requestEnv = envelope.env ?? {};
1446
- invocation = envelope.invocation;
1447
- let originalParsedBody = envelope.request.body;
1448
- const originalContentType = envelope.request.headers["content-type"] ?? "";
1449
- if (originalContentType.includes("application/json")) {
1450
- try {
1451
- originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
1452
- } catch {
1453
- }
1454
- }
1455
- webhookRequest = {
1456
- method: envelope.request.method,
1457
- url: envelope.request.url,
1458
- path: envelope.request.path,
1459
- headers: envelope.request.headers,
1460
- query: envelope.request.query,
1461
- body: originalParsedBody,
1462
- rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
1463
- };
1464
- const envVars = { ...process.env, ...requestEnv };
1465
- const app = envelope.context.app;
1466
- if (envelope.context.appInstallationId && envelope.context.workplace) {
1467
- webhookContext = {
1468
- env: envVars,
1469
- app,
1470
- appInstallationId: envelope.context.appInstallationId,
1471
- workplace: envelope.context.workplace,
1472
- registration: envelope.context.registration ?? {},
1473
- invocation,
1474
- log: createContextLogger()
1475
- };
1476
- } else {
1477
- webhookContext = {
1478
- env: envVars,
1479
- app,
1480
- invocation,
1481
- log: createContextLogger()
1482
- };
1483
- }
1484
- } else {
1485
- const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
1486
- const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
1487
- if (!appId || !appVersionId) {
1488
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
1489
- }
1490
- const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
1491
- const protocol = forwardedProto ?? "https";
1492
- const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
1493
- const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
1494
- const webhookUrl = `${protocol}://${host}${path}${queryString}`;
1495
- webhookRequest = {
1496
- method,
1497
- url: webhookUrl,
1498
- path,
1499
- headers: event.headers,
1500
- query: event.queryStringParameters ?? {},
1501
- body: parsedBody,
1502
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
1503
- };
1504
- webhookContext = {
1505
- env: process.env,
1506
- app: { id: appId, versionId: appVersionId },
1507
- log: createContextLogger()
1508
- };
1509
- }
1510
- const originalEnv = { ...process.env };
1511
- Object.assign(process.env, requestEnv);
1512
- const requestConfig = {
1513
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1514
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1515
- };
1516
- let webhookResponse;
1517
- try {
1518
- webhookResponse = await runWithLogContext({ invocation }, async () => {
1519
- return await runWithConfig(requestConfig, async () => {
1520
- return await webhookDef.handler(webhookRequest, webhookContext);
1521
- });
1522
- });
1523
- } catch (err) {
1524
- console.error(`Webhook handler '${handle}' error:`, err);
1525
- return createResponse(500, { error: "Webhook handler error" }, headers);
1526
- } finally {
1527
- process.env = originalEnv;
1500
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
1501
+ const protocol = forwardedProto ?? "https";
1502
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
1503
+ const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
1504
+ const webhookUrl = `${protocol}://${host}${path}${queryString}`;
1505
+ const parseResult = parseWebhookRequest(
1506
+ parsedBody,
1507
+ method,
1508
+ webhookUrl,
1509
+ path,
1510
+ event.headers,
1511
+ event.queryStringParameters ?? {},
1512
+ rawBody,
1513
+ event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
1514
+ event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
1515
+ );
1516
+ if ("error" in parseResult) {
1517
+ return createResponse(400, { error: parseResult.error }, headers);
1528
1518
  }
1519
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
1529
1520
  const responseHeaders = {
1530
1521
  ...headers,
1531
- ...webhookResponse.headers
1522
+ ...result.headers
1532
1523
  };
1533
- const status = webhookResponse.status ?? 200;
1534
- const body = webhookResponse.body;
1535
1524
  return {
1536
- statusCode: status,
1525
+ statusCode: result.status,
1537
1526
  headers: responseHeaders,
1538
- body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
1527
+ body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
1539
1528
  };
1540
1529
  }
1541
1530
  if (path === "/core" && method === "POST") {
@@ -1671,9 +1660,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1671
1660
  }
1672
1661
  }
1673
1662
  if (path === "/install" && method === "POST") {
1674
- if (!config.hooks?.install) {
1675
- return createResponse(404, { error: "Install handler not configured" }, headers);
1676
- }
1677
1663
  let installBody;
1678
1664
  try {
1679
1665
  installBody = event.body ? JSON.parse(event.body) : {};
@@ -1684,68 +1670,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1684
1670
  headers
1685
1671
  );
1686
1672
  }
1687
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
1688
- return createResponse(
1689
- 400,
1690
- { error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
1691
- headers
1692
- );
1693
- }
1694
- const installContext = {
1695
- env: installBody.env ?? {},
1696
- workplace: installBody.context.workplace,
1697
- appInstallationId: installBody.context.appInstallationId,
1698
- app: installBody.context.app,
1699
- invocation: installBody.invocation,
1700
- log: createContextLogger()
1701
- };
1702
- const installRequestConfig = {
1703
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1704
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1705
- };
1706
- try {
1707
- const installHook = config.hooks.install;
1708
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
1709
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
1710
- return await runWithConfig(installRequestConfig, async () => {
1711
- return await installHandler(installContext);
1712
- });
1713
- });
1714
- return createResponse(
1715
- 200,
1716
- { env: result.env ?? {}, redirect: result.redirect },
1717
- headers
1718
- );
1719
- } catch (err) {
1720
- if (err instanceof InstallError) {
1721
- return createResponse(
1722
- 400,
1723
- {
1724
- error: {
1725
- code: err.code,
1726
- message: err.message,
1727
- field: err.field
1728
- }
1729
- },
1730
- headers
1731
- );
1732
- }
1733
- return createResponse(
1734
- 500,
1735
- {
1736
- error: {
1737
- code: -32603,
1738
- message: err instanceof Error ? err.message : String(err ?? "")
1739
- }
1740
- },
1741
- headers
1742
- );
1743
- }
1673
+ const result = await handleInstall(installBody, config.hooks);
1674
+ return createResponse(result.status, result.body, headers);
1744
1675
  }
1745
1676
  if (path === "/uninstall" && method === "POST") {
1746
- if (!config.hooks?.uninstall) {
1747
- return createResponse(404, { error: "Uninstall handler not configured" }, headers);
1748
- }
1749
1677
  let uninstallBody;
1750
1678
  try {
1751
1679
  uninstallBody = event.body ? JSON.parse(event.body) : {};
@@ -1756,137 +1684,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1756
1684
  headers
1757
1685
  );
1758
1686
  }
1759
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
1760
- return createResponse(
1761
- 400,
1762
- {
1763
- error: {
1764
- code: -32602,
1765
- message: "Missing context (appInstallationId, workplace and app required)"
1766
- }
1767
- },
1768
- headers
1769
- );
1770
- }
1771
- const uninstallContext = {
1772
- env: uninstallBody.env ?? {},
1773
- workplace: uninstallBody.context.workplace,
1774
- appInstallationId: uninstallBody.context.appInstallationId,
1775
- app: uninstallBody.context.app,
1776
- invocation: uninstallBody.invocation,
1777
- log: createContextLogger()
1778
- };
1779
- const uninstallRequestConfig = {
1780
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1781
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1782
- };
1783
- try {
1784
- const uninstallHook = config.hooks.uninstall;
1785
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
1786
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
1787
- return await runWithConfig(uninstallRequestConfig, async () => {
1788
- return await uninstallHandlerFn(uninstallContext);
1789
- });
1790
- });
1791
- return createResponse(
1792
- 200,
1793
- { cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
1794
- headers
1795
- );
1796
- } catch (err) {
1797
- return createResponse(
1798
- 500,
1799
- {
1800
- error: {
1801
- code: -32603,
1802
- message: err instanceof Error ? err.message : String(err ?? "")
1803
- }
1804
- },
1805
- headers
1806
- );
1807
- }
1687
+ const result = await handleUninstall(uninstallBody, config.hooks);
1688
+ return createResponse(result.status, result.body, headers);
1808
1689
  }
1809
1690
  if (path === "/provision" && method === "POST") {
1810
- console.log("[serverless] /provision endpoint called");
1811
- if (!config.hooks?.provision) {
1812
- console.log("[serverless] No provision handler configured");
1813
- return createResponse(404, { error: "Provision handler not configured" }, headers);
1814
- }
1815
1691
  let provisionBody;
1816
1692
  try {
1817
1693
  provisionBody = event.body ? JSON.parse(event.body) : {};
1818
- console.log("[serverless] Provision body parsed:", {
1819
- hasEnv: !!provisionBody.env,
1820
- hasContext: !!provisionBody.context,
1821
- appId: provisionBody.context?.app?.id,
1822
- versionId: provisionBody.context?.app?.versionId
1823
- });
1824
1694
  } catch {
1825
- console.log("[serverless] Failed to parse provision body");
1826
1695
  return createResponse(
1827
1696
  400,
1828
1697
  { error: { code: -32700, message: "Parse error" } },
1829
1698
  headers
1830
1699
  );
1831
1700
  }
1832
- if (!provisionBody.context?.app) {
1833
- console.log("[serverless] Missing app context in provision body");
1834
- return createResponse(
1835
- 400,
1836
- { error: { code: -32602, message: "Missing context (app required)" } },
1837
- headers
1838
- );
1839
- }
1840
- const mergedEnv = {};
1841
- for (const [key, value] of Object.entries(process.env)) {
1842
- if (value !== void 0) {
1843
- mergedEnv[key] = value;
1844
- }
1845
- }
1846
- Object.assign(mergedEnv, provisionBody.env ?? {});
1847
- const provisionContext = {
1848
- env: mergedEnv,
1849
- app: provisionBody.context.app,
1850
- invocation: provisionBody.invocation,
1851
- log: createContextLogger()
1852
- };
1853
- const provisionRequestConfig = {
1854
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
1855
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
1856
- };
1857
- console.log("[serverless] Calling provision handler...");
1858
- try {
1859
- const provisionHook = config.hooks.provision;
1860
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
1861
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
1862
- return await runWithConfig(provisionRequestConfig, async () => {
1863
- return await provisionHandler(provisionContext);
1864
- });
1865
- });
1866
- console.log("[serverless] Provision handler completed successfully");
1867
- return createResponse(200, result, headers);
1868
- } catch (err) {
1869
- console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
1870
- return createResponse(
1871
- 500,
1872
- {
1873
- error: {
1874
- code: -32603,
1875
- message: err instanceof Error ? err.message : String(err ?? "")
1876
- }
1877
- },
1878
- headers
1879
- );
1880
- }
1701
+ const result = await handleProvision(provisionBody, config.hooks);
1702
+ return createResponse(result.status, result.body, headers);
1881
1703
  }
1882
1704
  if (path === "/oauth_callback" && method === "POST") {
1883
- if (!config.hooks?.oauth_callback) {
1884
- return createResponse(
1885
- 404,
1886
- { error: "OAuth callback handler not configured" },
1887
- headers
1888
- );
1889
- }
1890
1705
  let parsedBody;
1891
1706
  try {
1892
1707
  parsedBody = event.body ? JSON.parse(event.body) : {};
@@ -1898,70 +1713,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1898
1713
  headers
1899
1714
  );
1900
1715
  }
1901
- const envelope = parseHandlerEnvelope(parsedBody);
1902
- if (!envelope) {
1903
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
1904
- return createResponse(
1905
- 400,
1906
- { error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
1907
- headers
1908
- );
1909
- }
1910
- const invocation = parsedBody.invocation;
1911
- const oauthRequest = buildRequestFromRaw(envelope.request);
1912
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
1913
- const oauthCallbackContext = {
1914
- request: oauthRequest,
1915
- invocation,
1916
- log: createContextLogger()
1917
- };
1918
- try {
1919
- const oauthCallbackHook = config.hooks.oauth_callback;
1920
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
1921
- const result = await runWithLogContext({ invocation }, async () => {
1922
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
1923
- return await oauthCallbackHandler(oauthCallbackContext);
1924
- });
1925
- });
1926
- return createResponse(
1927
- 200,
1928
- {
1929
- appInstallationId: result.appInstallationId,
1930
- env: result.env ?? {}
1931
- },
1932
- headers
1933
- );
1934
- } catch (err) {
1935
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
1936
- return createResponse(
1937
- 500,
1938
- {
1939
- error: {
1940
- code: -32603,
1941
- message: errorMessage
1942
- }
1943
- },
1944
- headers
1945
- );
1946
- }
1716
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
1717
+ return createResponse(result.status, result.body, headers);
1947
1718
  }
1948
1719
  if (path === "/health" && method === "GET") {
1949
1720
  return createResponse(200, state.getHealthStatus(), headers);
1950
1721
  }
1951
1722
  if (path === "/config" && method === "GET") {
1952
- let appConfig = config.appConfig;
1953
- if (!appConfig && config.appConfigLoader) {
1954
- const loaded = await config.appConfigLoader();
1955
- appConfig = loaded.default;
1956
- }
1957
- if (!appConfig) {
1958
- appConfig = createMinimalConfig(
1959
- config.metadata.name,
1960
- config.metadata.version
1961
- );
1962
- }
1963
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
1964
- return createResponse(200, serializedConfig, headers);
1723
+ return createResponse(200, serializeConfig(config), headers);
1965
1724
  }
1966
1725
  if (path === "/mcp" && method === "POST") {
1967
1726
  let body;
@@ -2179,9 +1938,11 @@ console.log("[skedyul-node/server] All imports complete");
2179
1938
  console.log("[skedyul-node/server] Installing context logger...");
2180
1939
  installContextLogger();
2181
1940
  console.log("[skedyul-node/server] Context logger installed");
2182
- function createSkedyulServer(config, registry, webhookRegistry) {
1941
+ function createSkedyulServer(config) {
2183
1942
  console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
2184
1943
  mergeRuntimeEnv();
1944
+ const registry = config.tools;
1945
+ const webhookRegistry = config.webhooks;
2185
1946
  console.log("[createSkedyulServer] Step 2: coreApi setup");
2186
1947
  if (config.coreApi?.service) {
2187
1948
  coreApiService.register(config.coreApi.service);
@@ -2193,7 +1954,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
2193
1954
  const tools = buildToolMetadata(registry);
2194
1955
  console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
2195
1956
  const toolNames = Object.values(registry).map((tool) => tool.name);
2196
- const runtimeLabel = config.computeLayer;
1957
+ const runtimeLabel = config.computeLayer ?? "serverless";
2197
1958
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
2198
1959
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
2199
1960
  console.log("[createSkedyulServer] Step 4: createRequestState()");
@@ -2206,8 +1967,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
2206
1967
  console.log("[createSkedyulServer] Step 4 done");
2207
1968
  console.log("[createSkedyulServer] Step 5: new McpServer()");
2208
1969
  const mcpServer = new import_mcp.McpServer({
2209
- name: config.metadata.name,
2210
- version: config.metadata.version
1970
+ name: config.name,
1971
+ version: config.version ?? "0.0.0"
2211
1972
  });
2212
1973
  console.log("[createSkedyulServer] Step 5 done");
2213
1974
  const dedicatedShutdown = () => {
@@ -2335,13 +2096,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
2335
2096
  tools,
2336
2097
  callTool,
2337
2098
  state,
2338
- mcpServer,
2339
- registry,
2340
- webhookRegistry
2099
+ mcpServer
2341
2100
  );
2342
2101
  }
2343
2102
  console.log("[createSkedyulServer] Creating serverless instance");
2344
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
2103
+ const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
2345
2104
  console.log("[createSkedyulServer] Serverless instance created successfully");
2346
2105
  return serverlessInstance;
2347
2106
  }
@@ -2350,32 +2109,10 @@ var server = {
2350
2109
  };
2351
2110
  // Annotate the CommonJS export names for ESM import in node:
2352
2111
  0 && (module.exports = {
2353
- buildRequestFromRaw,
2354
- buildRequestScopedConfig,
2355
- buildToolMetadata,
2356
- createCallToolHandler,
2357
- createDedicatedServerInstance,
2358
- createRequestState,
2359
- createResponse,
2360
- createServerlessInstance,
2361
2112
  createSkedyulServer,
2362
- getDefaultHeaders,
2363
2113
  getJsonSchemaFromToolSchema,
2364
- getListeningPort,
2365
2114
  getZodSchema,
2366
- handleCoreMethod,
2367
2115
  isToolSchemaWithJson,
2368
- mergeRuntimeEnv,
2369
- normalizeBilling,
2370
- padEnd,
2371
- parseHandlerEnvelope,
2372
- parseJSONBody,
2373
- parseJsonRecord,
2374
- parseNumberEnv,
2375
- printStartupLog,
2376
- readRawRequestBody,
2377
- sendHTML,
2378
- sendJSON,
2379
2116
  server,
2380
2117
  toJsonSchema
2381
2118
  });