skedyul 1.2.21 → 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.
@@ -142,10 +142,6 @@ function sendJSON(res, statusCode, data) {
142
142
  res.writeHead(statusCode, { "Content-Type": "application/json" });
143
143
  res.end(JSON.stringify(data));
144
144
  }
145
- function sendHTML(res, statusCode, html) {
146
- res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
147
- res.end(html);
148
- }
149
145
  function getDefaultHeaders(options) {
150
146
  return {
151
147
  "Content-Type": "application/json",
@@ -331,8 +327,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
331
327
  };
332
328
  }
333
329
  function createCallToolHandler(registry, state, onMaxRequests) {
334
- return async function callTool(nameRaw, argsRaw) {
335
- const toolName = String(nameRaw);
330
+ return async function callTool(toolNameInput, toolArgsInput) {
331
+ const toolName = String(toolNameInput);
336
332
  const tool = registry[toolName];
337
333
  if (!tool) {
338
334
  throw new Error(`Tool "${toolName}" not found in registry`);
@@ -341,7 +337,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
341
337
  throw new Error(`Tool "${toolName}" handler is not a function`);
342
338
  }
343
339
  const fn = tool.handler;
344
- const args = argsRaw ?? {};
340
+ const args = toolArgsInput ?? {};
345
341
  const estimateMode = args.estimate === true;
346
342
  if (!estimateMode) {
347
343
  state.incrementRequestCount();
@@ -596,51 +592,6 @@ async function handleCoreMethod(method, params) {
596
592
  };
597
593
  }
598
594
 
599
- // src/server/handler-helpers.ts
600
- function parseHandlerEnvelope(parsedBody) {
601
- if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
602
- return null;
603
- }
604
- const envelope = parsedBody;
605
- if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
606
- return null;
607
- }
608
- if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
609
- return null;
610
- }
611
- return {
612
- env: envelope.env,
613
- request: envelope.request,
614
- context: envelope.context
615
- };
616
- }
617
- function buildRequestFromRaw(raw) {
618
- let parsedBody = raw.body;
619
- const contentType = raw.headers["content-type"] ?? "";
620
- if (contentType.includes("application/json")) {
621
- try {
622
- parsedBody = raw.body ? JSON.parse(raw.body) : {};
623
- } catch {
624
- parsedBody = raw.body;
625
- }
626
- }
627
- return {
628
- method: raw.method,
629
- url: raw.url,
630
- path: raw.path,
631
- headers: raw.headers,
632
- query: raw.query,
633
- body: parsedBody,
634
- rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
635
- };
636
- }
637
- function buildRequestScopedConfig(env) {
638
- return {
639
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
640
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
641
- };
642
- }
643
-
644
595
  // src/server/startup-logger.ts
645
596
  function padEnd(str, length) {
646
597
  if (str.length >= length) {
@@ -738,6 +689,405 @@ function isProvisionConfig(value) {
738
689
  return value !== void 0 && value !== null && !(value instanceof Promise);
739
690
  }
740
691
 
692
+ // src/server/handlers/install-handler.ts
693
+ async function handleInstall(body, hooks) {
694
+ if (!hooks?.install) {
695
+ return {
696
+ status: 404,
697
+ body: { error: "Install handler not configured" }
698
+ };
699
+ }
700
+ if (!body.context?.appInstallationId || !body.context?.workplace) {
701
+ return {
702
+ status: 400,
703
+ body: {
704
+ error: {
705
+ code: -32602,
706
+ message: "Missing context (appInstallationId and workplace required)"
707
+ }
708
+ }
709
+ };
710
+ }
711
+ const installContext = {
712
+ env: body.env ?? {},
713
+ workplace: body.context.workplace,
714
+ appInstallationId: body.context.appInstallationId,
715
+ app: body.context.app,
716
+ invocation: body.invocation,
717
+ log: createContextLogger()
718
+ };
719
+ const requestConfig = {
720
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
721
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
722
+ };
723
+ try {
724
+ const installHook = hooks.install;
725
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
726
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
727
+ return await runWithConfig(requestConfig, async () => {
728
+ return await installHandler(installContext);
729
+ });
730
+ });
731
+ return {
732
+ status: 200,
733
+ body: { env: result.env ?? {}, redirect: result.redirect }
734
+ };
735
+ } catch (err) {
736
+ if (err instanceof InstallError) {
737
+ return {
738
+ status: 400,
739
+ body: {
740
+ error: {
741
+ code: err.code,
742
+ message: err.message,
743
+ field: err.field
744
+ }
745
+ }
746
+ };
747
+ }
748
+ return {
749
+ status: 500,
750
+ body: {
751
+ error: {
752
+ code: -32603,
753
+ message: err instanceof Error ? err.message : String(err ?? "")
754
+ }
755
+ }
756
+ };
757
+ }
758
+ }
759
+
760
+ // src/server/handlers/uninstall-handler.ts
761
+ async function handleUninstall(body, hooks) {
762
+ if (!hooks?.uninstall) {
763
+ return {
764
+ status: 404,
765
+ body: { error: "Uninstall handler not configured" }
766
+ };
767
+ }
768
+ if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
769
+ return {
770
+ status: 400,
771
+ body: {
772
+ error: {
773
+ code: -32602,
774
+ message: "Missing context (appInstallationId, workplace and app required)"
775
+ }
776
+ }
777
+ };
778
+ }
779
+ const uninstallContext = {
780
+ env: body.env ?? {},
781
+ workplace: body.context.workplace,
782
+ appInstallationId: body.context.appInstallationId,
783
+ app: body.context.app,
784
+ invocation: body.invocation,
785
+ log: createContextLogger()
786
+ };
787
+ const requestConfig = {
788
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
789
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
790
+ };
791
+ try {
792
+ const uninstallHook = hooks.uninstall;
793
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
794
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
795
+ return await runWithConfig(requestConfig, async () => {
796
+ return await uninstallHandlerFn(uninstallContext);
797
+ });
798
+ });
799
+ return {
800
+ status: 200,
801
+ body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
802
+ };
803
+ } catch (err) {
804
+ return {
805
+ status: 500,
806
+ body: {
807
+ error: {
808
+ code: -32603,
809
+ message: err instanceof Error ? err.message : String(err ?? "")
810
+ }
811
+ }
812
+ };
813
+ }
814
+ }
815
+
816
+ // src/server/handlers/provision-handler.ts
817
+ async function handleProvision(body, hooks) {
818
+ if (!hooks?.provision) {
819
+ return {
820
+ status: 404,
821
+ body: { error: "Provision handler not configured" }
822
+ };
823
+ }
824
+ if (!body.context?.app) {
825
+ return {
826
+ status: 400,
827
+ body: {
828
+ error: {
829
+ code: -32602,
830
+ message: "Missing context (app required)"
831
+ }
832
+ }
833
+ };
834
+ }
835
+ const mergedEnv = {};
836
+ for (const [key, value] of Object.entries(process.env)) {
837
+ if (value !== void 0) {
838
+ mergedEnv[key] = value;
839
+ }
840
+ }
841
+ Object.assign(mergedEnv, body.env ?? {});
842
+ const provisionContext = {
843
+ env: mergedEnv,
844
+ app: body.context.app,
845
+ invocation: body.invocation,
846
+ log: createContextLogger()
847
+ };
848
+ const requestConfig = {
849
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
850
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
851
+ };
852
+ try {
853
+ const provisionHook = hooks.provision;
854
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
855
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
856
+ return await runWithConfig(requestConfig, async () => {
857
+ return await provisionHandler(provisionContext);
858
+ });
859
+ });
860
+ return {
861
+ status: 200,
862
+ body: result
863
+ };
864
+ } catch (err) {
865
+ return {
866
+ status: 500,
867
+ body: {
868
+ error: {
869
+ code: -32603,
870
+ message: err instanceof Error ? err.message : String(err ?? "")
871
+ }
872
+ }
873
+ };
874
+ }
875
+ }
876
+
877
+ // src/server/handler-helpers.ts
878
+ function parseHandlerEnvelope(parsedBody) {
879
+ if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
880
+ return null;
881
+ }
882
+ const envelope = parsedBody;
883
+ if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
884
+ return null;
885
+ }
886
+ if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
887
+ return null;
888
+ }
889
+ return {
890
+ env: envelope.env,
891
+ request: envelope.request,
892
+ context: envelope.context
893
+ };
894
+ }
895
+ function buildRequestFromRaw(raw) {
896
+ let parsedBody = raw.body;
897
+ const contentType = raw.headers["content-type"] ?? "";
898
+ if (contentType.includes("application/json")) {
899
+ try {
900
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
901
+ } catch {
902
+ parsedBody = raw.body;
903
+ }
904
+ }
905
+ return {
906
+ method: raw.method,
907
+ url: raw.url,
908
+ path: raw.path,
909
+ headers: raw.headers,
910
+ query: raw.query,
911
+ body: parsedBody,
912
+ rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
913
+ };
914
+ }
915
+ function buildRequestScopedConfig(env) {
916
+ return {
917
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
918
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
919
+ };
920
+ }
921
+
922
+ // src/server/handlers/oauth-callback-handler.ts
923
+ async function handleOAuthCallback(parsedBody, hooks) {
924
+ if (!hooks?.oauth_callback) {
925
+ return {
926
+ status: 404,
927
+ body: { error: "OAuth callback handler not configured" }
928
+ };
929
+ }
930
+ const envelope = parseHandlerEnvelope(parsedBody);
931
+ if (!envelope) {
932
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
933
+ return {
934
+ status: 400,
935
+ body: {
936
+ error: {
937
+ code: -32602,
938
+ message: "Missing envelope format: expected { env, request }"
939
+ }
940
+ }
941
+ };
942
+ }
943
+ const invocation = parsedBody.invocation;
944
+ const oauthRequest = buildRequestFromRaw(envelope.request);
945
+ const requestConfig = buildRequestScopedConfig(envelope.env);
946
+ const oauthCallbackContext = {
947
+ request: oauthRequest,
948
+ invocation,
949
+ log: createContextLogger()
950
+ };
951
+ try {
952
+ const oauthCallbackHook = hooks.oauth_callback;
953
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
954
+ const result = await runWithLogContext({ invocation }, async () => {
955
+ return await runWithConfig(requestConfig, async () => {
956
+ return await oauthCallbackHandler(oauthCallbackContext);
957
+ });
958
+ });
959
+ return {
960
+ status: 200,
961
+ body: {
962
+ appInstallationId: result.appInstallationId,
963
+ env: result.env ?? {}
964
+ }
965
+ };
966
+ } catch (err) {
967
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
968
+ return {
969
+ status: 500,
970
+ body: {
971
+ error: {
972
+ code: -32603,
973
+ message: errorMessage
974
+ }
975
+ }
976
+ };
977
+ }
978
+ }
979
+
980
+ // src/server/handlers/webhook-handler.ts
981
+ function parseWebhookRequest(parsedBody, method, url, path, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
982
+ const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
983
+ if (isEnvelope) {
984
+ const envelope = parsedBody;
985
+ const requestEnv = envelope.env ?? {};
986
+ const invocation = envelope.invocation;
987
+ let originalParsedBody = envelope.request.body;
988
+ const originalContentType = envelope.request.headers["content-type"] ?? "";
989
+ if (originalContentType.includes("application/json")) {
990
+ try {
991
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
992
+ } catch {
993
+ }
994
+ }
995
+ const webhookRequest2 = {
996
+ method: envelope.request.method,
997
+ url: envelope.request.url,
998
+ path: envelope.request.path,
999
+ headers: envelope.request.headers,
1000
+ query: envelope.request.query,
1001
+ body: originalParsedBody,
1002
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
1003
+ };
1004
+ const envVars = { ...process.env, ...requestEnv };
1005
+ const app = envelope.context.app;
1006
+ let webhookContext2;
1007
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
1008
+ webhookContext2 = {
1009
+ env: envVars,
1010
+ app,
1011
+ appInstallationId: envelope.context.appInstallationId,
1012
+ workplace: envelope.context.workplace,
1013
+ registration: envelope.context.registration ?? {},
1014
+ invocation,
1015
+ log: createContextLogger()
1016
+ };
1017
+ } else {
1018
+ webhookContext2 = {
1019
+ env: envVars,
1020
+ app,
1021
+ invocation,
1022
+ log: createContextLogger()
1023
+ };
1024
+ }
1025
+ return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
1026
+ }
1027
+ if (!appIdHeader || !appVersionIdHeader) {
1028
+ return {
1029
+ error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
1030
+ };
1031
+ }
1032
+ const webhookRequest = {
1033
+ method,
1034
+ url,
1035
+ path,
1036
+ headers,
1037
+ query,
1038
+ body: parsedBody,
1039
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
1040
+ };
1041
+ const webhookContext = {
1042
+ env: process.env,
1043
+ app: { id: appIdHeader, versionId: appVersionIdHeader },
1044
+ log: createContextLogger()
1045
+ };
1046
+ return { webhookRequest, webhookContext, requestEnv: {} };
1047
+ }
1048
+ async function executeWebhookHandler(handle, webhookRegistry, data) {
1049
+ const webhookDef = webhookRegistry[handle];
1050
+ if (!webhookDef) {
1051
+ return {
1052
+ status: 404,
1053
+ body: { error: `Webhook handler '${handle}' not found` }
1054
+ };
1055
+ }
1056
+ const originalEnv = { ...process.env };
1057
+ Object.assign(process.env, data.requestEnv);
1058
+ const requestConfig = {
1059
+ baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1060
+ apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1061
+ };
1062
+ let webhookResponse;
1063
+ try {
1064
+ webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
1065
+ return await runWithConfig(requestConfig, async () => {
1066
+ return await webhookDef.handler(data.webhookRequest, data.webhookContext);
1067
+ });
1068
+ });
1069
+ } catch (err) {
1070
+ console.error(`Webhook handler '${handle}' error:`, err);
1071
+ return {
1072
+ status: 500,
1073
+ body: { error: "Webhook handler error" }
1074
+ };
1075
+ } finally {
1076
+ process.env = originalEnv;
1077
+ }
1078
+ return {
1079
+ status: webhookResponse.status ?? 200,
1080
+ body: webhookResponse.body,
1081
+ headers: webhookResponse.headers
1082
+ };
1083
+ }
1084
+ function isMethodAllowed(webhookRegistry, handle, method) {
1085
+ const webhookDef = webhookRegistry[handle];
1086
+ if (!webhookDef) return false;
1087
+ const allowedMethods = webhookDef.methods ?? ["POST"];
1088
+ return allowedMethods.includes(method);
1089
+ }
1090
+
741
1091
  // src/server/dedicated.ts
742
1092
  function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
743
1093
  const port = getListeningPort(config);
@@ -764,13 +1114,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
764
1114
  }
765
1115
  if (pathname.startsWith("/webhooks/") && webhookRegistry) {
766
1116
  const handle = pathname.slice("/webhooks/".length);
767
- const webhookDef = webhookRegistry[handle];
768
- if (!webhookDef) {
1117
+ if (!webhookRegistry[handle]) {
769
1118
  sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
770
1119
  return;
771
1120
  }
772
- const allowedMethods = webhookDef.methods ?? ["POST"];
773
- if (!allowedMethods.includes(req.method)) {
1121
+ if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
774
1122
  sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
775
1123
  return;
776
1124
  }
@@ -792,87 +1140,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
792
1140
  } else {
793
1141
  parsedBody = rawBody;
794
1142
  }
795
- const envelope = parseHandlerEnvelope(parsedBody);
796
- let webhookRequest;
797
- let webhookContext;
798
- let requestEnv = {};
799
- let invocation;
800
- if (envelope && "context" in envelope && envelope.context) {
801
- const context = envelope.context;
802
- requestEnv = envelope.env;
803
- invocation = parsedBody.invocation;
804
- webhookRequest = buildRequestFromRaw(envelope.request);
805
- const envVars = { ...process.env, ...envelope.env };
806
- const app = context.app;
807
- if (context.appInstallationId && context.workplace) {
808
- webhookContext = {
809
- env: envVars,
810
- app,
811
- appInstallationId: context.appInstallationId,
812
- workplace: context.workplace,
813
- registration: context.registration ?? {},
814
- invocation,
815
- log: createContextLogger()
816
- };
817
- } else {
818
- webhookContext = {
819
- env: envVars,
820
- app,
821
- invocation,
822
- log: createContextLogger()
823
- };
824
- }
825
- } else {
826
- const appId = req.headers["x-skedyul-app-id"];
827
- const appVersionId = req.headers["x-skedyul-app-version-id"];
828
- if (!appId || !appVersionId) {
829
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
830
- }
831
- webhookRequest = {
832
- method: req.method ?? "POST",
833
- url: url.toString(),
834
- path: pathname,
835
- headers: req.headers,
836
- query: Object.fromEntries(url.searchParams.entries()),
837
- body: parsedBody,
838
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
839
- };
840
- webhookContext = {
841
- env: process.env,
842
- app: { id: appId, versionId: appVersionId },
843
- log: createContextLogger()
844
- };
845
- }
846
- const originalEnv = { ...process.env };
847
- Object.assign(process.env, requestEnv);
848
- const requestConfig = buildRequestScopedConfig(requestEnv);
849
- let webhookResponse;
850
- try {
851
- webhookResponse = await runWithLogContext({ invocation }, async () => {
852
- return await runWithConfig(requestConfig, async () => {
853
- return await webhookDef.handler(webhookRequest, webhookContext);
854
- });
855
- });
856
- } catch (err) {
857
- console.error(`Webhook handler '${handle}' error:`, err);
858
- sendJSON(res, 500, { error: "Webhook handler error" });
1143
+ const parseResult = parseWebhookRequest(
1144
+ parsedBody,
1145
+ req.method ?? "POST",
1146
+ url.toString(),
1147
+ pathname,
1148
+ req.headers,
1149
+ Object.fromEntries(url.searchParams.entries()),
1150
+ rawBody,
1151
+ req.headers["x-skedyul-app-id"],
1152
+ req.headers["x-skedyul-app-version-id"]
1153
+ );
1154
+ if ("error" in parseResult) {
1155
+ sendJSON(res, 400, { error: parseResult.error });
859
1156
  return;
860
- } finally {
861
- process.env = originalEnv;
862
1157
  }
863
- const status = webhookResponse.status ?? 200;
1158
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
864
1159
  const responseHeaders = {
865
- ...webhookResponse.headers
1160
+ ...result.headers
866
1161
  };
867
1162
  if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
868
1163
  responseHeaders["Content-Type"] = "application/json";
869
1164
  }
870
- res.writeHead(status, responseHeaders);
871
- if (webhookResponse.body !== void 0) {
872
- if (typeof webhookResponse.body === "string") {
873
- res.end(webhookResponse.body);
1165
+ res.writeHead(result.status, responseHeaders);
1166
+ if (result.body !== void 0) {
1167
+ if (typeof result.body === "string") {
1168
+ res.end(result.body);
874
1169
  } else {
875
- res.end(JSON.stringify(webhookResponse.body));
1170
+ res.end(JSON.stringify(result.body));
876
1171
  }
877
1172
  } else {
878
1173
  res.end();
@@ -911,10 +1206,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
911
1206
  return;
912
1207
  }
913
1208
  if (pathname === "/oauth_callback" && req.method === "POST") {
914
- if (!config.hooks?.oauth_callback) {
915
- sendJSON(res, 404, { error: "OAuth callback handler not configured" });
916
- return;
917
- }
918
1209
  let parsedBody;
919
1210
  try {
920
1211
  parsedBody = await parseJSONBody(req);
@@ -925,50 +1216,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
925
1216
  });
926
1217
  return;
927
1218
  }
928
- const envelope = parseHandlerEnvelope(parsedBody);
929
- if (!envelope) {
930
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
931
- sendJSON(res, 400, {
932
- error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
933
- });
934
- return;
935
- }
936
- const invocation = parsedBody.invocation;
937
- const oauthRequest = buildRequestFromRaw(envelope.request);
938
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
939
- const oauthCallbackContext = {
940
- request: oauthRequest,
941
- invocation,
942
- log: createContextLogger()
943
- };
944
- try {
945
- const oauthCallbackHook = config.hooks.oauth_callback;
946
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
947
- const result = await runWithLogContext({ invocation }, async () => {
948
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
949
- return await oauthCallbackHandler(oauthCallbackContext);
950
- });
951
- });
952
- sendJSON(res, 200, {
953
- appInstallationId: result.appInstallationId,
954
- env: result.env ?? {}
955
- });
956
- } catch (err) {
957
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
958
- sendJSON(res, 500, {
959
- error: {
960
- code: -32603,
961
- message: errorMessage
962
- }
963
- });
964
- }
1219
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
1220
+ sendJSON(res, result.status, result.body);
965
1221
  return;
966
1222
  }
967
1223
  if (pathname === "/install" && req.method === "POST") {
968
- if (!config.hooks?.install) {
969
- sendJSON(res, 404, { error: "Install handler not configured" });
970
- return;
971
- }
972
1224
  let installBody;
973
1225
  try {
974
1226
  installBody = await parseJSONBody(req);
@@ -978,61 +1230,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
978
1230
  });
979
1231
  return;
980
1232
  }
981
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
982
- sendJSON(res, 400, {
983
- error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
984
- });
985
- return;
986
- }
987
- const installContext = {
988
- env: installBody.env ?? {},
989
- workplace: installBody.context.workplace,
990
- appInstallationId: installBody.context.appInstallationId,
991
- app: installBody.context.app,
992
- invocation: installBody.invocation,
993
- log: createContextLogger()
994
- };
995
- const installRequestConfig = {
996
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
997
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
998
- };
999
- try {
1000
- const installHook = config.hooks.install;
1001
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
1002
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
1003
- return await runWithConfig(installRequestConfig, async () => {
1004
- return await installHandler(installContext);
1005
- });
1006
- });
1007
- sendJSON(res, 200, {
1008
- env: result.env ?? {},
1009
- redirect: result.redirect
1010
- });
1011
- } catch (err) {
1012
- if (err instanceof InstallError) {
1013
- sendJSON(res, 400, {
1014
- error: {
1015
- code: err.code,
1016
- message: err.message,
1017
- field: err.field
1018
- }
1019
- });
1020
- } else {
1021
- sendJSON(res, 500, {
1022
- error: {
1023
- code: -32603,
1024
- message: err instanceof Error ? err.message : String(err ?? "")
1025
- }
1026
- });
1027
- }
1028
- }
1233
+ const result = await handleInstall(installBody, config.hooks);
1234
+ sendJSON(res, result.status, result.body);
1029
1235
  return;
1030
1236
  }
1031
1237
  if (pathname === "/uninstall" && req.method === "POST") {
1032
- if (!config.hooks?.uninstall) {
1033
- sendJSON(res, 404, { error: "Uninstall handler not configured" });
1034
- return;
1035
- }
1036
1238
  let uninstallBody;
1037
1239
  try {
1038
1240
  uninstallBody = await parseJSONBody(req);
@@ -1042,53 +1244,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1042
1244
  });
1043
1245
  return;
1044
1246
  }
1045
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
1046
- sendJSON(res, 400, {
1047
- error: {
1048
- code: -32602,
1049
- message: "Missing context (appInstallationId, workplace and app required)"
1050
- }
1051
- });
1052
- return;
1053
- }
1054
- const uninstallContext = {
1055
- env: uninstallBody.env ?? {},
1056
- workplace: uninstallBody.context.workplace,
1057
- appInstallationId: uninstallBody.context.appInstallationId,
1058
- app: uninstallBody.context.app,
1059
- invocation: uninstallBody.invocation,
1060
- log: createContextLogger()
1061
- };
1062
- const uninstallRequestConfig = {
1063
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1064
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1065
- };
1066
- try {
1067
- const uninstallHook = config.hooks.uninstall;
1068
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
1069
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
1070
- return await runWithConfig(uninstallRequestConfig, async () => {
1071
- return await uninstallHandlerFn(uninstallContext);
1072
- });
1073
- });
1074
- sendJSON(res, 200, {
1075
- cleanedWebhookIds: result.cleanedWebhookIds ?? []
1076
- });
1077
- } catch (err) {
1078
- sendJSON(res, 500, {
1079
- error: {
1080
- code: -32603,
1081
- message: err instanceof Error ? err.message : String(err ?? "")
1082
- }
1083
- });
1084
- }
1247
+ const result = await handleUninstall(uninstallBody, config.hooks);
1248
+ sendJSON(res, result.status, result.body);
1085
1249
  return;
1086
1250
  }
1087
1251
  if (pathname === "/provision" && req.method === "POST") {
1088
- if (!config.hooks?.provision) {
1089
- sendJSON(res, 404, { error: "Provision handler not configured" });
1090
- return;
1091
- }
1092
1252
  let provisionBody;
1093
1253
  try {
1094
1254
  provisionBody = await parseJSONBody(req);
@@ -1098,46 +1258,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1098
1258
  });
1099
1259
  return;
1100
1260
  }
1101
- if (!provisionBody.context?.app) {
1102
- sendJSON(res, 400, {
1103
- error: { code: -32602, message: "Missing context (app required)" }
1104
- });
1105
- return;
1106
- }
1107
- const mergedEnv = {};
1108
- for (const [key, value] of Object.entries(process.env)) {
1109
- if (value !== void 0) {
1110
- mergedEnv[key] = value;
1111
- }
1112
- }
1113
- Object.assign(mergedEnv, provisionBody.env ?? {});
1114
- const provisionContext = {
1115
- env: mergedEnv,
1116
- app: provisionBody.context.app,
1117
- invocation: provisionBody.invocation,
1118
- log: createContextLogger()
1119
- };
1120
- const provisionRequestConfig = {
1121
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
1122
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
1123
- };
1124
- try {
1125
- const provisionHook = config.hooks.provision;
1126
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
1127
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
1128
- return await runWithConfig(provisionRequestConfig, async () => {
1129
- return await provisionHandler(provisionContext);
1130
- });
1131
- });
1132
- sendJSON(res, 200, result);
1133
- } catch (err) {
1134
- sendJSON(res, 500, {
1135
- error: {
1136
- code: -32603,
1137
- message: err instanceof Error ? err.message : String(err ?? "")
1138
- }
1139
- });
1140
- }
1261
+ const result = await handleProvision(provisionBody, config.hooks);
1262
+ sendJSON(res, result.status, result.body);
1141
1263
  return;
1142
1264
  }
1143
1265
  if (pathname === "/core" && req.method === "POST") {
@@ -1318,12 +1440,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1318
1440
  }
1319
1441
  if (path.startsWith("/webhooks/") && webhookRegistry) {
1320
1442
  const handle = path.slice("/webhooks/".length);
1321
- const webhookDef = webhookRegistry[handle];
1322
- if (!webhookDef) {
1443
+ if (!webhookRegistry[handle]) {
1323
1444
  return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
1324
1445
  }
1325
- const allowedMethods = webhookDef.methods ?? ["POST"];
1326
- if (!allowedMethods.includes(method)) {
1446
+ if (!isMethodAllowed(webhookRegistry, handle, method)) {
1327
1447
  return createResponse(405, { error: `Method ${method} not allowed` }, headers);
1328
1448
  }
1329
1449
  const rawBody = event.body ?? "";
@@ -1338,107 +1458,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1338
1458
  } else {
1339
1459
  parsedBody = rawBody;
1340
1460
  }
1341
- const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
1342
- let webhookRequest;
1343
- let webhookContext;
1344
- let requestEnv = {};
1345
- let invocation;
1346
- if (isEnvelope) {
1347
- const envelope = parsedBody;
1348
- requestEnv = envelope.env ?? {};
1349
- invocation = envelope.invocation;
1350
- let originalParsedBody = envelope.request.body;
1351
- const originalContentType = envelope.request.headers["content-type"] ?? "";
1352
- if (originalContentType.includes("application/json")) {
1353
- try {
1354
- originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
1355
- } catch {
1356
- }
1357
- }
1358
- webhookRequest = {
1359
- method: envelope.request.method,
1360
- url: envelope.request.url,
1361
- path: envelope.request.path,
1362
- headers: envelope.request.headers,
1363
- query: envelope.request.query,
1364
- body: originalParsedBody,
1365
- rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
1366
- };
1367
- const envVars = { ...process.env, ...requestEnv };
1368
- const app = envelope.context.app;
1369
- if (envelope.context.appInstallationId && envelope.context.workplace) {
1370
- webhookContext = {
1371
- env: envVars,
1372
- app,
1373
- appInstallationId: envelope.context.appInstallationId,
1374
- workplace: envelope.context.workplace,
1375
- registration: envelope.context.registration ?? {},
1376
- invocation,
1377
- log: createContextLogger()
1378
- };
1379
- } else {
1380
- webhookContext = {
1381
- env: envVars,
1382
- app,
1383
- invocation,
1384
- log: createContextLogger()
1385
- };
1386
- }
1387
- } else {
1388
- const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
1389
- const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
1390
- if (!appId || !appVersionId) {
1391
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
1392
- }
1393
- const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
1394
- const protocol = forwardedProto ?? "https";
1395
- const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
1396
- const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
1397
- const webhookUrl = `${protocol}://${host}${path}${queryString}`;
1398
- webhookRequest = {
1399
- method,
1400
- url: webhookUrl,
1401
- path,
1402
- headers: event.headers,
1403
- query: event.queryStringParameters ?? {},
1404
- body: parsedBody,
1405
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
1406
- };
1407
- webhookContext = {
1408
- env: process.env,
1409
- app: { id: appId, versionId: appVersionId },
1410
- log: createContextLogger()
1411
- };
1412
- }
1413
- const originalEnv = { ...process.env };
1414
- Object.assign(process.env, requestEnv);
1415
- const requestConfig = {
1416
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1417
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1418
- };
1419
- let webhookResponse;
1420
- try {
1421
- webhookResponse = await runWithLogContext({ invocation }, async () => {
1422
- return await runWithConfig(requestConfig, async () => {
1423
- return await webhookDef.handler(webhookRequest, webhookContext);
1424
- });
1425
- });
1426
- } catch (err) {
1427
- console.error(`Webhook handler '${handle}' error:`, err);
1428
- return createResponse(500, { error: "Webhook handler error" }, headers);
1429
- } finally {
1430
- process.env = originalEnv;
1461
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
1462
+ const protocol = forwardedProto ?? "https";
1463
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
1464
+ const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
1465
+ const webhookUrl = `${protocol}://${host}${path}${queryString}`;
1466
+ const parseResult = parseWebhookRequest(
1467
+ parsedBody,
1468
+ method,
1469
+ webhookUrl,
1470
+ path,
1471
+ event.headers,
1472
+ event.queryStringParameters ?? {},
1473
+ rawBody,
1474
+ event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
1475
+ event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
1476
+ );
1477
+ if ("error" in parseResult) {
1478
+ return createResponse(400, { error: parseResult.error }, headers);
1431
1479
  }
1480
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
1432
1481
  const responseHeaders = {
1433
1482
  ...headers,
1434
- ...webhookResponse.headers
1483
+ ...result.headers
1435
1484
  };
1436
- const status = webhookResponse.status ?? 200;
1437
- const body = webhookResponse.body;
1438
1485
  return {
1439
- statusCode: status,
1486
+ statusCode: result.status,
1440
1487
  headers: responseHeaders,
1441
- body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
1488
+ body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
1442
1489
  };
1443
1490
  }
1444
1491
  if (path === "/core" && method === "POST") {
@@ -1574,9 +1621,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1574
1621
  }
1575
1622
  }
1576
1623
  if (path === "/install" && method === "POST") {
1577
- if (!config.hooks?.install) {
1578
- return createResponse(404, { error: "Install handler not configured" }, headers);
1579
- }
1580
1624
  let installBody;
1581
1625
  try {
1582
1626
  installBody = event.body ? JSON.parse(event.body) : {};
@@ -1587,68 +1631,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1587
1631
  headers
1588
1632
  );
1589
1633
  }
1590
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
1591
- return createResponse(
1592
- 400,
1593
- { error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
1594
- headers
1595
- );
1596
- }
1597
- const installContext = {
1598
- env: installBody.env ?? {},
1599
- workplace: installBody.context.workplace,
1600
- appInstallationId: installBody.context.appInstallationId,
1601
- app: installBody.context.app,
1602
- invocation: installBody.invocation,
1603
- log: createContextLogger()
1604
- };
1605
- const installRequestConfig = {
1606
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1607
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1608
- };
1609
- try {
1610
- const installHook = config.hooks.install;
1611
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
1612
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
1613
- return await runWithConfig(installRequestConfig, async () => {
1614
- return await installHandler(installContext);
1615
- });
1616
- });
1617
- return createResponse(
1618
- 200,
1619
- { env: result.env ?? {}, redirect: result.redirect },
1620
- headers
1621
- );
1622
- } catch (err) {
1623
- if (err instanceof InstallError) {
1624
- return createResponse(
1625
- 400,
1626
- {
1627
- error: {
1628
- code: err.code,
1629
- message: err.message,
1630
- field: err.field
1631
- }
1632
- },
1633
- headers
1634
- );
1635
- }
1636
- return createResponse(
1637
- 500,
1638
- {
1639
- error: {
1640
- code: -32603,
1641
- message: err instanceof Error ? err.message : String(err ?? "")
1642
- }
1643
- },
1644
- headers
1645
- );
1646
- }
1634
+ const result = await handleInstall(installBody, config.hooks);
1635
+ return createResponse(result.status, result.body, headers);
1647
1636
  }
1648
1637
  if (path === "/uninstall" && method === "POST") {
1649
- if (!config.hooks?.uninstall) {
1650
- return createResponse(404, { error: "Uninstall handler not configured" }, headers);
1651
- }
1652
1638
  let uninstallBody;
1653
1639
  try {
1654
1640
  uninstallBody = event.body ? JSON.parse(event.body) : {};
@@ -1659,137 +1645,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1659
1645
  headers
1660
1646
  );
1661
1647
  }
1662
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
1663
- return createResponse(
1664
- 400,
1665
- {
1666
- error: {
1667
- code: -32602,
1668
- message: "Missing context (appInstallationId, workplace and app required)"
1669
- }
1670
- },
1671
- headers
1672
- );
1673
- }
1674
- const uninstallContext = {
1675
- env: uninstallBody.env ?? {},
1676
- workplace: uninstallBody.context.workplace,
1677
- appInstallationId: uninstallBody.context.appInstallationId,
1678
- app: uninstallBody.context.app,
1679
- invocation: uninstallBody.invocation,
1680
- log: createContextLogger()
1681
- };
1682
- const uninstallRequestConfig = {
1683
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
1684
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
1685
- };
1686
- try {
1687
- const uninstallHook = config.hooks.uninstall;
1688
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
1689
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
1690
- return await runWithConfig(uninstallRequestConfig, async () => {
1691
- return await uninstallHandlerFn(uninstallContext);
1692
- });
1693
- });
1694
- return createResponse(
1695
- 200,
1696
- { cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
1697
- headers
1698
- );
1699
- } catch (err) {
1700
- return createResponse(
1701
- 500,
1702
- {
1703
- error: {
1704
- code: -32603,
1705
- message: err instanceof Error ? err.message : String(err ?? "")
1706
- }
1707
- },
1708
- headers
1709
- );
1710
- }
1648
+ const result = await handleUninstall(uninstallBody, config.hooks);
1649
+ return createResponse(result.status, result.body, headers);
1711
1650
  }
1712
1651
  if (path === "/provision" && method === "POST") {
1713
- console.log("[serverless] /provision endpoint called");
1714
- if (!config.hooks?.provision) {
1715
- console.log("[serverless] No provision handler configured");
1716
- return createResponse(404, { error: "Provision handler not configured" }, headers);
1717
- }
1718
1652
  let provisionBody;
1719
1653
  try {
1720
1654
  provisionBody = event.body ? JSON.parse(event.body) : {};
1721
- console.log("[serverless] Provision body parsed:", {
1722
- hasEnv: !!provisionBody.env,
1723
- hasContext: !!provisionBody.context,
1724
- appId: provisionBody.context?.app?.id,
1725
- versionId: provisionBody.context?.app?.versionId
1726
- });
1727
1655
  } catch {
1728
- console.log("[serverless] Failed to parse provision body");
1729
1656
  return createResponse(
1730
1657
  400,
1731
1658
  { error: { code: -32700, message: "Parse error" } },
1732
1659
  headers
1733
1660
  );
1734
1661
  }
1735
- if (!provisionBody.context?.app) {
1736
- console.log("[serverless] Missing app context in provision body");
1737
- return createResponse(
1738
- 400,
1739
- { error: { code: -32602, message: "Missing context (app required)" } },
1740
- headers
1741
- );
1742
- }
1743
- const mergedEnv = {};
1744
- for (const [key, value] of Object.entries(process.env)) {
1745
- if (value !== void 0) {
1746
- mergedEnv[key] = value;
1747
- }
1748
- }
1749
- Object.assign(mergedEnv, provisionBody.env ?? {});
1750
- const provisionContext = {
1751
- env: mergedEnv,
1752
- app: provisionBody.context.app,
1753
- invocation: provisionBody.invocation,
1754
- log: createContextLogger()
1755
- };
1756
- const provisionRequestConfig = {
1757
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
1758
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
1759
- };
1760
- console.log("[serverless] Calling provision handler...");
1761
- try {
1762
- const provisionHook = config.hooks.provision;
1763
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
1764
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
1765
- return await runWithConfig(provisionRequestConfig, async () => {
1766
- return await provisionHandler(provisionContext);
1767
- });
1768
- });
1769
- console.log("[serverless] Provision handler completed successfully");
1770
- return createResponse(200, result, headers);
1771
- } catch (err) {
1772
- console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
1773
- return createResponse(
1774
- 500,
1775
- {
1776
- error: {
1777
- code: -32603,
1778
- message: err instanceof Error ? err.message : String(err ?? "")
1779
- }
1780
- },
1781
- headers
1782
- );
1783
- }
1662
+ const result = await handleProvision(provisionBody, config.hooks);
1663
+ return createResponse(result.status, result.body, headers);
1784
1664
  }
1785
1665
  if (path === "/oauth_callback" && method === "POST") {
1786
- if (!config.hooks?.oauth_callback) {
1787
- return createResponse(
1788
- 404,
1789
- { error: "OAuth callback handler not configured" },
1790
- headers
1791
- );
1792
- }
1793
1666
  let parsedBody;
1794
1667
  try {
1795
1668
  parsedBody = event.body ? JSON.parse(event.body) : {};
@@ -1801,52 +1674,8 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1801
1674
  headers
1802
1675
  );
1803
1676
  }
1804
- const envelope = parseHandlerEnvelope(parsedBody);
1805
- if (!envelope) {
1806
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
1807
- return createResponse(
1808
- 400,
1809
- { error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
1810
- headers
1811
- );
1812
- }
1813
- const invocation = parsedBody.invocation;
1814
- const oauthRequest = buildRequestFromRaw(envelope.request);
1815
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
1816
- const oauthCallbackContext = {
1817
- request: oauthRequest,
1818
- invocation,
1819
- log: createContextLogger()
1820
- };
1821
- try {
1822
- const oauthCallbackHook = config.hooks.oauth_callback;
1823
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
1824
- const result = await runWithLogContext({ invocation }, async () => {
1825
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
1826
- return await oauthCallbackHandler(oauthCallbackContext);
1827
- });
1828
- });
1829
- return createResponse(
1830
- 200,
1831
- {
1832
- appInstallationId: result.appInstallationId,
1833
- env: result.env ?? {}
1834
- },
1835
- headers
1836
- );
1837
- } catch (err) {
1838
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
1839
- return createResponse(
1840
- 500,
1841
- {
1842
- error: {
1843
- code: -32603,
1844
- message: errorMessage
1845
- }
1846
- },
1847
- headers
1848
- );
1849
- }
1677
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
1678
+ return createResponse(result.status, result.body, headers);
1850
1679
  }
1851
1680
  if (path === "/health" && method === "GET") {
1852
1681
  return createResponse(200, state.getHealthStatus(), headers);
@@ -2240,32 +2069,10 @@ var server = {
2240
2069
  create: createSkedyulServer
2241
2070
  };
2242
2071
  export {
2243
- buildRequestFromRaw,
2244
- buildRequestScopedConfig,
2245
- buildToolMetadata,
2246
- createCallToolHandler,
2247
- createDedicatedServerInstance,
2248
- createRequestState,
2249
- createResponse,
2250
- createServerlessInstance,
2251
2072
  createSkedyulServer,
2252
- getDefaultHeaders,
2253
2073
  getJsonSchemaFromToolSchema,
2254
- getListeningPort,
2255
2074
  getZodSchema,
2256
- handleCoreMethod,
2257
2075
  isToolSchemaWithJson,
2258
- mergeRuntimeEnv,
2259
- normalizeBilling,
2260
- padEnd,
2261
- parseHandlerEnvelope,
2262
- parseJSONBody,
2263
- parseJsonRecord,
2264
- parseNumberEnv,
2265
- printStartupLog,
2266
- readRawRequestBody,
2267
- sendHTML,
2268
- sendJSON,
2269
2076
  server,
2270
2077
  toJsonSchema
2271
2078
  };