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
package/dist/cli/index.js CHANGED
@@ -2574,8 +2574,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
2574
2574
  };
2575
2575
  }
2576
2576
  function createCallToolHandler(registry, state, onMaxRequests) {
2577
- return async function callTool(nameRaw, argsRaw) {
2578
- const toolName = String(nameRaw);
2577
+ return async function callTool(toolNameInput, toolArgsInput) {
2578
+ const toolName = String(toolNameInput);
2579
2579
  const tool = registry[toolName];
2580
2580
  if (!tool) {
2581
2581
  throw new Error(`Tool "${toolName}" not found in registry`);
@@ -2584,7 +2584,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
2584
2584
  throw new Error(`Tool "${toolName}" handler is not a function`);
2585
2585
  }
2586
2586
  const fn = tool.handler;
2587
- const args2 = argsRaw ?? {};
2587
+ const args2 = toolArgsInput ?? {};
2588
2588
  const estimateMode = args2.estimate === true;
2589
2589
  if (!estimateMode) {
2590
2590
  state.incrementRequestCount();
@@ -2839,51 +2839,6 @@ async function handleCoreMethod(method, params) {
2839
2839
  };
2840
2840
  }
2841
2841
 
2842
- // src/server/handler-helpers.ts
2843
- function parseHandlerEnvelope(parsedBody) {
2844
- if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
2845
- return null;
2846
- }
2847
- const envelope = parsedBody;
2848
- if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
2849
- return null;
2850
- }
2851
- if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
2852
- return null;
2853
- }
2854
- return {
2855
- env: envelope.env,
2856
- request: envelope.request,
2857
- context: envelope.context
2858
- };
2859
- }
2860
- function buildRequestFromRaw(raw) {
2861
- let parsedBody = raw.body;
2862
- const contentType = raw.headers["content-type"] ?? "";
2863
- if (contentType.includes("application/json")) {
2864
- try {
2865
- parsedBody = raw.body ? JSON.parse(raw.body) : {};
2866
- } catch {
2867
- parsedBody = raw.body;
2868
- }
2869
- }
2870
- return {
2871
- method: raw.method,
2872
- url: raw.url,
2873
- path: raw.path,
2874
- headers: raw.headers,
2875
- query: raw.query,
2876
- body: parsedBody,
2877
- rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
2878
- };
2879
- }
2880
- function buildRequestScopedConfig(env) {
2881
- return {
2882
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2883
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2884
- };
2885
- }
2886
-
2887
2842
  // src/server/startup-logger.ts
2888
2843
  function padEnd(str, length) {
2889
2844
  if (str.length >= length) {
@@ -2891,10 +2846,11 @@ function padEnd(str, length) {
2891
2846
  }
2892
2847
  return str + " ".repeat(length - str.length);
2893
2848
  }
2894
- function printStartupLog(config, tools, webhookRegistry, port) {
2849
+ function printStartupLog(config, tools, port) {
2895
2850
  if (process.env.NODE_ENV === "test") {
2896
2851
  return;
2897
2852
  }
2853
+ const webhookRegistry = config.webhooks;
2898
2854
  const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
2899
2855
  const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
2900
2856
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
@@ -2907,9 +2863,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
2907
2863
  console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
2908
2864
  console.log(`\u2560${divider}\u2563`);
2909
2865
  console.log(`\u2551 \u2551`);
2910
- console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.metadata.name, 49)}\u2551`);
2911
- console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.metadata.version, 49)}\u2551`);
2912
- console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
2866
+ console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
2867
+ console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
2868
+ console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
2913
2869
  if (port) {
2914
2870
  console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
2915
2871
  }
@@ -2951,67 +2907,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
2951
2907
  console.log("");
2952
2908
  }
2953
2909
 
2954
- // src/config/resolve.ts
2955
- async function resolveDynamicImport(value) {
2956
- if (value === void 0 || value === null) {
2957
- return void 0;
2958
- }
2959
- if (value instanceof Promise) {
2960
- const resolved = await value;
2961
- if (resolved && typeof resolved === "object" && "default" in resolved) {
2962
- return resolved.default;
2963
- }
2964
- return resolved;
2965
- }
2966
- return value;
2967
- }
2968
- function serializeTools(registry) {
2969
- return Object.entries(registry).map(([key, tool]) => ({
2970
- name: tool.name || key,
2971
- displayName: tool.label,
2972
- description: tool.description,
2973
- timeout: tool.timeout,
2974
- retries: tool.retries
2975
- }));
2976
- }
2977
- function serializeWebhooks(registry) {
2978
- return Object.values(registry).map((webhook) => ({
2979
- name: webhook.name,
2980
- description: webhook.description,
2981
- methods: webhook.methods ?? ["POST"],
2982
- type: webhook.type ?? "WEBHOOK"
2983
- }));
2984
- }
2985
- async function resolveConfig(config, registry, webhookRegistry) {
2986
- const provision = await resolveDynamicImport(
2987
- config.provision
2988
- );
2989
- const install = await resolveDynamicImport(
2990
- config.install
2991
- );
2992
- const tools = serializeTools(registry);
2993
- const webhooks = webhookRegistry ? serializeWebhooks(webhookRegistry) : [];
2910
+ // src/server/config-serializer.ts
2911
+ function serializeConfig(config) {
2912
+ const registry = config.tools;
2913
+ const webhookRegistry = config.webhooks;
2994
2914
  return {
2995
2915
  name: config.name,
2996
2916
  version: config.version,
2997
2917
  description: config.description,
2998
2918
  computeLayer: config.computeLayer,
2999
- tools,
3000
- webhooks,
3001
- provision,
2919
+ tools: registry ? Object.entries(registry).map(([key, tool]) => ({
2920
+ name: tool.name || key,
2921
+ description: tool.description,
2922
+ timeout: tool.timeout,
2923
+ retries: tool.retries
2924
+ })) : [],
2925
+ webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
2926
+ name: w.name,
2927
+ description: w.description,
2928
+ methods: w.methods ?? ["POST"],
2929
+ type: w.type ?? "WEBHOOK"
2930
+ })) : [],
2931
+ provision: isProvisionConfig(config.provision) ? config.provision : void 0,
3002
2932
  agents: config.agents
3003
2933
  };
3004
2934
  }
3005
- function createMinimalConfig(name, version) {
2935
+ function isProvisionConfig(value) {
2936
+ return value !== void 0 && value !== null && !(value instanceof Promise);
2937
+ }
2938
+
2939
+ // src/server/handlers/install-handler.ts
2940
+ async function handleInstall(body, hooks) {
2941
+ if (!hooks?.install) {
2942
+ return {
2943
+ status: 404,
2944
+ body: { error: "Install handler not configured" }
2945
+ };
2946
+ }
2947
+ if (!body.context?.appInstallationId || !body.context?.workplace) {
2948
+ return {
2949
+ status: 400,
2950
+ body: {
2951
+ error: {
2952
+ code: -32602,
2953
+ message: "Missing context (appInstallationId and workplace required)"
2954
+ }
2955
+ }
2956
+ };
2957
+ }
2958
+ const installContext = {
2959
+ env: body.env ?? {},
2960
+ workplace: body.context.workplace,
2961
+ appInstallationId: body.context.appInstallationId,
2962
+ app: body.context.app,
2963
+ invocation: body.invocation,
2964
+ log: createContextLogger()
2965
+ };
2966
+ const requestConfig = {
2967
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2968
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2969
+ };
2970
+ try {
2971
+ const installHook = hooks.install;
2972
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
2973
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
2974
+ return await runWithConfig(requestConfig, async () => {
2975
+ return await installHandler(installContext);
2976
+ });
2977
+ });
2978
+ return {
2979
+ status: 200,
2980
+ body: { env: result.env ?? {}, redirect: result.redirect }
2981
+ };
2982
+ } catch (err) {
2983
+ if (err instanceof InstallError) {
2984
+ return {
2985
+ status: 400,
2986
+ body: {
2987
+ error: {
2988
+ code: err.code,
2989
+ message: err.message,
2990
+ field: err.field
2991
+ }
2992
+ }
2993
+ };
2994
+ }
2995
+ return {
2996
+ status: 500,
2997
+ body: {
2998
+ error: {
2999
+ code: -32603,
3000
+ message: err instanceof Error ? err.message : String(err ?? "")
3001
+ }
3002
+ }
3003
+ };
3004
+ }
3005
+ }
3006
+
3007
+ // src/server/handlers/uninstall-handler.ts
3008
+ async function handleUninstall(body, hooks) {
3009
+ if (!hooks?.uninstall) {
3010
+ return {
3011
+ status: 404,
3012
+ body: { error: "Uninstall handler not configured" }
3013
+ };
3014
+ }
3015
+ if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
3016
+ return {
3017
+ status: 400,
3018
+ body: {
3019
+ error: {
3020
+ code: -32602,
3021
+ message: "Missing context (appInstallationId, workplace and app required)"
3022
+ }
3023
+ }
3024
+ };
3025
+ }
3026
+ const uninstallContext = {
3027
+ env: body.env ?? {},
3028
+ workplace: body.context.workplace,
3029
+ appInstallationId: body.context.appInstallationId,
3030
+ app: body.context.app,
3031
+ invocation: body.invocation,
3032
+ log: createContextLogger()
3033
+ };
3034
+ const requestConfig = {
3035
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3036
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3037
+ };
3038
+ try {
3039
+ const uninstallHook = hooks.uninstall;
3040
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3041
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
3042
+ return await runWithConfig(requestConfig, async () => {
3043
+ return await uninstallHandlerFn(uninstallContext);
3044
+ });
3045
+ });
3046
+ return {
3047
+ status: 200,
3048
+ body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
3049
+ };
3050
+ } catch (err) {
3051
+ return {
3052
+ status: 500,
3053
+ body: {
3054
+ error: {
3055
+ code: -32603,
3056
+ message: err instanceof Error ? err.message : String(err ?? "")
3057
+ }
3058
+ }
3059
+ };
3060
+ }
3061
+ }
3062
+
3063
+ // src/server/handlers/provision-handler.ts
3064
+ async function handleProvision(body, hooks) {
3065
+ if (!hooks?.provision) {
3066
+ return {
3067
+ status: 404,
3068
+ body: { error: "Provision handler not configured" }
3069
+ };
3070
+ }
3071
+ if (!body.context?.app) {
3072
+ return {
3073
+ status: 400,
3074
+ body: {
3075
+ error: {
3076
+ code: -32602,
3077
+ message: "Missing context (app required)"
3078
+ }
3079
+ }
3080
+ };
3081
+ }
3082
+ const mergedEnv = {};
3083
+ for (const [key, value] of Object.entries(process.env)) {
3084
+ if (value !== void 0) {
3085
+ mergedEnv[key] = value;
3086
+ }
3087
+ }
3088
+ Object.assign(mergedEnv, body.env ?? {});
3089
+ const provisionContext = {
3090
+ env: mergedEnv,
3091
+ app: body.context.app,
3092
+ invocation: body.invocation,
3093
+ log: createContextLogger()
3094
+ };
3095
+ const requestConfig = {
3096
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
3097
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
3098
+ };
3099
+ try {
3100
+ const provisionHook = hooks.provision;
3101
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
3102
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
3103
+ return await runWithConfig(requestConfig, async () => {
3104
+ return await provisionHandler(provisionContext);
3105
+ });
3106
+ });
3107
+ return {
3108
+ status: 200,
3109
+ body: result
3110
+ };
3111
+ } catch (err) {
3112
+ return {
3113
+ status: 500,
3114
+ body: {
3115
+ error: {
3116
+ code: -32603,
3117
+ message: err instanceof Error ? err.message : String(err ?? "")
3118
+ }
3119
+ }
3120
+ };
3121
+ }
3122
+ }
3123
+
3124
+ // src/server/handler-helpers.ts
3125
+ function parseHandlerEnvelope(parsedBody) {
3126
+ if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
3127
+ return null;
3128
+ }
3129
+ const envelope = parsedBody;
3130
+ if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
3131
+ return null;
3132
+ }
3133
+ if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
3134
+ return null;
3135
+ }
3006
3136
  return {
3007
- name,
3008
- version
3137
+ env: envelope.env,
3138
+ request: envelope.request,
3139
+ context: envelope.context
3140
+ };
3141
+ }
3142
+ function buildRequestFromRaw(raw) {
3143
+ let parsedBody = raw.body;
3144
+ const contentType = raw.headers["content-type"] ?? "";
3145
+ if (contentType.includes("application/json")) {
3146
+ try {
3147
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
3148
+ } catch {
3149
+ parsedBody = raw.body;
3150
+ }
3151
+ }
3152
+ return {
3153
+ method: raw.method,
3154
+ url: raw.url,
3155
+ path: raw.path,
3156
+ headers: raw.headers,
3157
+ query: raw.query,
3158
+ body: parsedBody,
3159
+ rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
3160
+ };
3161
+ }
3162
+ function buildRequestScopedConfig(env) {
3163
+ return {
3164
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3165
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3009
3166
  };
3010
3167
  }
3011
3168
 
3169
+ // src/server/handlers/oauth-callback-handler.ts
3170
+ async function handleOAuthCallback(parsedBody, hooks) {
3171
+ if (!hooks?.oauth_callback) {
3172
+ return {
3173
+ status: 404,
3174
+ body: { error: "OAuth callback handler not configured" }
3175
+ };
3176
+ }
3177
+ const envelope = parseHandlerEnvelope(parsedBody);
3178
+ if (!envelope) {
3179
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
3180
+ return {
3181
+ status: 400,
3182
+ body: {
3183
+ error: {
3184
+ code: -32602,
3185
+ message: "Missing envelope format: expected { env, request }"
3186
+ }
3187
+ }
3188
+ };
3189
+ }
3190
+ const invocation = parsedBody.invocation;
3191
+ const oauthRequest = buildRequestFromRaw(envelope.request);
3192
+ const requestConfig = buildRequestScopedConfig(envelope.env);
3193
+ const oauthCallbackContext = {
3194
+ request: oauthRequest,
3195
+ invocation,
3196
+ log: createContextLogger()
3197
+ };
3198
+ try {
3199
+ const oauthCallbackHook = hooks.oauth_callback;
3200
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
3201
+ const result = await runWithLogContext({ invocation }, async () => {
3202
+ return await runWithConfig(requestConfig, async () => {
3203
+ return await oauthCallbackHandler(oauthCallbackContext);
3204
+ });
3205
+ });
3206
+ return {
3207
+ status: 200,
3208
+ body: {
3209
+ appInstallationId: result.appInstallationId,
3210
+ env: result.env ?? {}
3211
+ }
3212
+ };
3213
+ } catch (err) {
3214
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
3215
+ return {
3216
+ status: 500,
3217
+ body: {
3218
+ error: {
3219
+ code: -32603,
3220
+ message: errorMessage
3221
+ }
3222
+ }
3223
+ };
3224
+ }
3225
+ }
3226
+
3227
+ // src/server/handlers/webhook-handler.ts
3228
+ function parseWebhookRequest(parsedBody, method, url, path14, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
3229
+ const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
3230
+ if (isEnvelope) {
3231
+ const envelope = parsedBody;
3232
+ const requestEnv = envelope.env ?? {};
3233
+ const invocation = envelope.invocation;
3234
+ let originalParsedBody = envelope.request.body;
3235
+ const originalContentType = envelope.request.headers["content-type"] ?? "";
3236
+ if (originalContentType.includes("application/json")) {
3237
+ try {
3238
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
3239
+ } catch {
3240
+ }
3241
+ }
3242
+ const webhookRequest2 = {
3243
+ method: envelope.request.method,
3244
+ url: envelope.request.url,
3245
+ path: envelope.request.path,
3246
+ headers: envelope.request.headers,
3247
+ query: envelope.request.query,
3248
+ body: originalParsedBody,
3249
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
3250
+ };
3251
+ const envVars = { ...process.env, ...requestEnv };
3252
+ const app = envelope.context.app;
3253
+ let webhookContext2;
3254
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
3255
+ webhookContext2 = {
3256
+ env: envVars,
3257
+ app,
3258
+ appInstallationId: envelope.context.appInstallationId,
3259
+ workplace: envelope.context.workplace,
3260
+ registration: envelope.context.registration ?? {},
3261
+ invocation,
3262
+ log: createContextLogger()
3263
+ };
3264
+ } else {
3265
+ webhookContext2 = {
3266
+ env: envVars,
3267
+ app,
3268
+ invocation,
3269
+ log: createContextLogger()
3270
+ };
3271
+ }
3272
+ return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
3273
+ }
3274
+ if (!appIdHeader || !appVersionIdHeader) {
3275
+ return {
3276
+ error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
3277
+ };
3278
+ }
3279
+ const webhookRequest = {
3280
+ method,
3281
+ url,
3282
+ path: path14,
3283
+ headers,
3284
+ query,
3285
+ body: parsedBody,
3286
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
3287
+ };
3288
+ const webhookContext = {
3289
+ env: process.env,
3290
+ app: { id: appIdHeader, versionId: appVersionIdHeader },
3291
+ log: createContextLogger()
3292
+ };
3293
+ return { webhookRequest, webhookContext, requestEnv: {} };
3294
+ }
3295
+ async function executeWebhookHandler(handle, webhookRegistry, data) {
3296
+ const webhookDef = webhookRegistry[handle];
3297
+ if (!webhookDef) {
3298
+ return {
3299
+ status: 404,
3300
+ body: { error: `Webhook handler '${handle}' not found` }
3301
+ };
3302
+ }
3303
+ const originalEnv = { ...process.env };
3304
+ Object.assign(process.env, data.requestEnv);
3305
+ const requestConfig = {
3306
+ baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3307
+ apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3308
+ };
3309
+ let webhookResponse;
3310
+ try {
3311
+ webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
3312
+ return await runWithConfig(requestConfig, async () => {
3313
+ return await webhookDef.handler(data.webhookRequest, data.webhookContext);
3314
+ });
3315
+ });
3316
+ } catch (err) {
3317
+ console.error(`Webhook handler '${handle}' error:`, err);
3318
+ return {
3319
+ status: 500,
3320
+ body: { error: "Webhook handler error" }
3321
+ };
3322
+ } finally {
3323
+ process.env = originalEnv;
3324
+ }
3325
+ return {
3326
+ status: webhookResponse.status ?? 200,
3327
+ body: webhookResponse.body,
3328
+ headers: webhookResponse.headers
3329
+ };
3330
+ }
3331
+ function isMethodAllowed(webhookRegistry, handle, method) {
3332
+ const webhookDef = webhookRegistry[handle];
3333
+ if (!webhookDef) return false;
3334
+ const allowedMethods = webhookDef.methods ?? ["POST"];
3335
+ return allowedMethods.includes(method);
3336
+ }
3337
+
3012
3338
  // src/server/dedicated.ts
3013
- function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
3339
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
3014
3340
  const port = getListeningPort(config);
3341
+ const registry = config.tools;
3342
+ const webhookRegistry = config.webhooks;
3015
3343
  const httpServer = import_http2.default.createServer(
3016
3344
  async (req, res) => {
3017
3345
  function sendCoreResult(result) {
@@ -3028,30 +3356,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3028
3356
  return;
3029
3357
  }
3030
3358
  if (pathname === "/config" && req.method === "GET") {
3031
- let appConfig = config.appConfig;
3032
- if (!appConfig && config.appConfigLoader) {
3033
- const loaded = await config.appConfigLoader();
3034
- appConfig = loaded.default;
3035
- }
3036
- if (!appConfig) {
3037
- appConfig = createMinimalConfig(
3038
- config.metadata.name,
3039
- config.metadata.version
3040
- );
3041
- }
3042
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
3043
- sendJSON(res, 200, serializedConfig);
3359
+ sendJSON(res, 200, serializeConfig(config));
3044
3360
  return;
3045
3361
  }
3046
3362
  if (pathname.startsWith("/webhooks/") && webhookRegistry) {
3047
3363
  const handle = pathname.slice("/webhooks/".length);
3048
- const webhookDef = webhookRegistry[handle];
3049
- if (!webhookDef) {
3364
+ if (!webhookRegistry[handle]) {
3050
3365
  sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
3051
3366
  return;
3052
3367
  }
3053
- const allowedMethods = webhookDef.methods ?? ["POST"];
3054
- if (!allowedMethods.includes(req.method)) {
3368
+ if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
3055
3369
  sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
3056
3370
  return;
3057
3371
  }
@@ -3073,87 +3387,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3073
3387
  } else {
3074
3388
  parsedBody = rawBody;
3075
3389
  }
3076
- const envelope = parseHandlerEnvelope(parsedBody);
3077
- let webhookRequest;
3078
- let webhookContext;
3079
- let requestEnv = {};
3080
- let invocation;
3081
- if (envelope && "context" in envelope && envelope.context) {
3082
- const context = envelope.context;
3083
- requestEnv = envelope.env;
3084
- invocation = parsedBody.invocation;
3085
- webhookRequest = buildRequestFromRaw(envelope.request);
3086
- const envVars = { ...process.env, ...envelope.env };
3087
- const app = context.app;
3088
- if (context.appInstallationId && context.workplace) {
3089
- webhookContext = {
3090
- env: envVars,
3091
- app,
3092
- appInstallationId: context.appInstallationId,
3093
- workplace: context.workplace,
3094
- registration: context.registration ?? {},
3095
- invocation,
3096
- log: createContextLogger()
3097
- };
3098
- } else {
3099
- webhookContext = {
3100
- env: envVars,
3101
- app,
3102
- invocation,
3103
- log: createContextLogger()
3104
- };
3105
- }
3106
- } else {
3107
- const appId = req.headers["x-skedyul-app-id"];
3108
- const appVersionId = req.headers["x-skedyul-app-version-id"];
3109
- if (!appId || !appVersionId) {
3110
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
3111
- }
3112
- webhookRequest = {
3113
- method: req.method ?? "POST",
3114
- url: url.toString(),
3115
- path: pathname,
3116
- headers: req.headers,
3117
- query: Object.fromEntries(url.searchParams.entries()),
3118
- body: parsedBody,
3119
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
3120
- };
3121
- webhookContext = {
3122
- env: process.env,
3123
- app: { id: appId, versionId: appVersionId },
3124
- log: createContextLogger()
3125
- };
3126
- }
3127
- const originalEnv = { ...process.env };
3128
- Object.assign(process.env, requestEnv);
3129
- const requestConfig = buildRequestScopedConfig(requestEnv);
3130
- let webhookResponse;
3131
- try {
3132
- webhookResponse = await runWithLogContext({ invocation }, async () => {
3133
- return await runWithConfig(requestConfig, async () => {
3134
- return await webhookDef.handler(webhookRequest, webhookContext);
3135
- });
3136
- });
3137
- } catch (err) {
3138
- console.error(`Webhook handler '${handle}' error:`, err);
3139
- sendJSON(res, 500, { error: "Webhook handler error" });
3390
+ const parseResult = parseWebhookRequest(
3391
+ parsedBody,
3392
+ req.method ?? "POST",
3393
+ url.toString(),
3394
+ pathname,
3395
+ req.headers,
3396
+ Object.fromEntries(url.searchParams.entries()),
3397
+ rawBody,
3398
+ req.headers["x-skedyul-app-id"],
3399
+ req.headers["x-skedyul-app-version-id"]
3400
+ );
3401
+ if ("error" in parseResult) {
3402
+ sendJSON(res, 400, { error: parseResult.error });
3140
3403
  return;
3141
- } finally {
3142
- process.env = originalEnv;
3143
3404
  }
3144
- const status = webhookResponse.status ?? 200;
3405
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
3145
3406
  const responseHeaders = {
3146
- ...webhookResponse.headers
3407
+ ...result.headers
3147
3408
  };
3148
3409
  if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
3149
3410
  responseHeaders["Content-Type"] = "application/json";
3150
3411
  }
3151
- res.writeHead(status, responseHeaders);
3152
- if (webhookResponse.body !== void 0) {
3153
- if (typeof webhookResponse.body === "string") {
3154
- res.end(webhookResponse.body);
3412
+ res.writeHead(result.status, responseHeaders);
3413
+ if (result.body !== void 0) {
3414
+ if (typeof result.body === "string") {
3415
+ res.end(result.body);
3155
3416
  } else {
3156
- res.end(JSON.stringify(webhookResponse.body));
3417
+ res.end(JSON.stringify(result.body));
3157
3418
  }
3158
3419
  } else {
3159
3420
  res.end();
@@ -3192,10 +3453,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3192
3453
  return;
3193
3454
  }
3194
3455
  if (pathname === "/oauth_callback" && req.method === "POST") {
3195
- if (!config.hooks?.oauth_callback) {
3196
- sendJSON(res, 404, { error: "OAuth callback handler not configured" });
3197
- return;
3198
- }
3199
3456
  let parsedBody;
3200
3457
  try {
3201
3458
  parsedBody = await parseJSONBody(req);
@@ -3206,50 +3463,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3206
3463
  });
3207
3464
  return;
3208
3465
  }
3209
- const envelope = parseHandlerEnvelope(parsedBody);
3210
- if (!envelope) {
3211
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
3212
- sendJSON(res, 400, {
3213
- error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
3214
- });
3215
- return;
3216
- }
3217
- const invocation = parsedBody.invocation;
3218
- const oauthRequest = buildRequestFromRaw(envelope.request);
3219
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
3220
- const oauthCallbackContext = {
3221
- request: oauthRequest,
3222
- invocation,
3223
- log: createContextLogger()
3224
- };
3225
- try {
3226
- const oauthCallbackHook = config.hooks.oauth_callback;
3227
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
3228
- const result = await runWithLogContext({ invocation }, async () => {
3229
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
3230
- return await oauthCallbackHandler(oauthCallbackContext);
3231
- });
3232
- });
3233
- sendJSON(res, 200, {
3234
- appInstallationId: result.appInstallationId,
3235
- env: result.env ?? {}
3236
- });
3237
- } catch (err) {
3238
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
3239
- sendJSON(res, 500, {
3240
- error: {
3241
- code: -32603,
3242
- message: errorMessage
3243
- }
3244
- });
3245
- }
3466
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
3467
+ sendJSON(res, result.status, result.body);
3246
3468
  return;
3247
3469
  }
3248
3470
  if (pathname === "/install" && req.method === "POST") {
3249
- if (!config.hooks?.install) {
3250
- sendJSON(res, 404, { error: "Install handler not configured" });
3251
- return;
3252
- }
3253
3471
  let installBody;
3254
3472
  try {
3255
3473
  installBody = await parseJSONBody(req);
@@ -3259,61 +3477,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3259
3477
  });
3260
3478
  return;
3261
3479
  }
3262
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
3263
- sendJSON(res, 400, {
3264
- error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
3265
- });
3266
- return;
3267
- }
3268
- const installContext = {
3269
- env: installBody.env ?? {},
3270
- workplace: installBody.context.workplace,
3271
- appInstallationId: installBody.context.appInstallationId,
3272
- app: installBody.context.app,
3273
- invocation: installBody.invocation,
3274
- log: createContextLogger()
3275
- };
3276
- const installRequestConfig = {
3277
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3278
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3279
- };
3280
- try {
3281
- const installHook = config.hooks.install;
3282
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
3283
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
3284
- return await runWithConfig(installRequestConfig, async () => {
3285
- return await installHandler(installContext);
3286
- });
3287
- });
3288
- sendJSON(res, 200, {
3289
- env: result.env ?? {},
3290
- redirect: result.redirect
3291
- });
3292
- } catch (err) {
3293
- if (err instanceof InstallError) {
3294
- sendJSON(res, 400, {
3295
- error: {
3296
- code: err.code,
3297
- message: err.message,
3298
- field: err.field
3299
- }
3300
- });
3301
- } else {
3302
- sendJSON(res, 500, {
3303
- error: {
3304
- code: -32603,
3305
- message: err instanceof Error ? err.message : String(err ?? "")
3306
- }
3307
- });
3308
- }
3309
- }
3480
+ const result = await handleInstall(installBody, config.hooks);
3481
+ sendJSON(res, result.status, result.body);
3310
3482
  return;
3311
3483
  }
3312
3484
  if (pathname === "/uninstall" && req.method === "POST") {
3313
- if (!config.hooks?.uninstall) {
3314
- sendJSON(res, 404, { error: "Uninstall handler not configured" });
3315
- return;
3316
- }
3317
3485
  let uninstallBody;
3318
3486
  try {
3319
3487
  uninstallBody = await parseJSONBody(req);
@@ -3323,53 +3491,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3323
3491
  });
3324
3492
  return;
3325
3493
  }
3326
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
3327
- sendJSON(res, 400, {
3328
- error: {
3329
- code: -32602,
3330
- message: "Missing context (appInstallationId, workplace and app required)"
3331
- }
3332
- });
3333
- return;
3334
- }
3335
- const uninstallContext = {
3336
- env: uninstallBody.env ?? {},
3337
- workplace: uninstallBody.context.workplace,
3338
- appInstallationId: uninstallBody.context.appInstallationId,
3339
- app: uninstallBody.context.app,
3340
- invocation: uninstallBody.invocation,
3341
- log: createContextLogger()
3342
- };
3343
- const uninstallRequestConfig = {
3344
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3345
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3346
- };
3347
- try {
3348
- const uninstallHook = config.hooks.uninstall;
3349
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3350
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
3351
- return await runWithConfig(uninstallRequestConfig, async () => {
3352
- return await uninstallHandlerFn(uninstallContext);
3353
- });
3354
- });
3355
- sendJSON(res, 200, {
3356
- cleanedWebhookIds: result.cleanedWebhookIds ?? []
3357
- });
3358
- } catch (err) {
3359
- sendJSON(res, 500, {
3360
- error: {
3361
- code: -32603,
3362
- message: err instanceof Error ? err.message : String(err ?? "")
3363
- }
3364
- });
3365
- }
3494
+ const result = await handleUninstall(uninstallBody, config.hooks);
3495
+ sendJSON(res, result.status, result.body);
3366
3496
  return;
3367
3497
  }
3368
3498
  if (pathname === "/provision" && req.method === "POST") {
3369
- if (!config.hooks?.provision) {
3370
- sendJSON(res, 404, { error: "Provision handler not configured" });
3371
- return;
3372
- }
3373
3499
  let provisionBody;
3374
3500
  try {
3375
3501
  provisionBody = await parseJSONBody(req);
@@ -3379,46 +3505,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3379
3505
  });
3380
3506
  return;
3381
3507
  }
3382
- if (!provisionBody.context?.app) {
3383
- sendJSON(res, 400, {
3384
- error: { code: -32602, message: "Missing context (app required)" }
3385
- });
3386
- return;
3387
- }
3388
- const mergedEnv = {};
3389
- for (const [key, value] of Object.entries(process.env)) {
3390
- if (value !== void 0) {
3391
- mergedEnv[key] = value;
3392
- }
3393
- }
3394
- Object.assign(mergedEnv, provisionBody.env ?? {});
3395
- const provisionContext = {
3396
- env: mergedEnv,
3397
- app: provisionBody.context.app,
3398
- invocation: provisionBody.invocation,
3399
- log: createContextLogger()
3400
- };
3401
- const provisionRequestConfig = {
3402
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
3403
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
3404
- };
3405
- try {
3406
- const provisionHook = config.hooks.provision;
3407
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
3408
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
3409
- return await runWithConfig(provisionRequestConfig, async () => {
3410
- return await provisionHandler(provisionContext);
3411
- });
3412
- });
3413
- sendJSON(res, 200, result);
3414
- } catch (err) {
3415
- sendJSON(res, 500, {
3416
- error: {
3417
- code: -32603,
3418
- message: err instanceof Error ? err.message : String(err ?? "")
3419
- }
3420
- });
3421
- }
3508
+ const result = await handleProvision(provisionBody, config.hooks);
3509
+ sendJSON(res, result.status, result.body);
3422
3510
  return;
3423
3511
  }
3424
3512
  if (pathname === "/core" && req.method === "POST") {
@@ -3569,7 +3657,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3569
3657
  const finalPort = listenPort ?? port;
3570
3658
  return new Promise((resolve8, reject) => {
3571
3659
  httpServer.listen(finalPort, () => {
3572
- printStartupLog(config, tools, webhookRegistry, finalPort);
3660
+ printStartupLog(config, tools, finalPort);
3573
3661
  resolve8();
3574
3662
  });
3575
3663
  httpServer.once("error", reject);
@@ -3580,13 +3668,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3580
3668
  }
3581
3669
 
3582
3670
  // src/server/serverless.ts
3583
- function createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
3671
+ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
3584
3672
  const headers = getDefaultHeaders(config.cors);
3673
+ const registry = config.tools;
3674
+ const webhookRegistry = config.webhooks;
3585
3675
  let hasLoggedStartup = false;
3586
3676
  return {
3587
3677
  async handler(event) {
3588
3678
  if (!hasLoggedStartup) {
3589
- printStartupLog(config, tools, webhookRegistry);
3679
+ printStartupLog(config, tools);
3590
3680
  hasLoggedStartup = true;
3591
3681
  }
3592
3682
  try {
@@ -3597,12 +3687,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3597
3687
  }
3598
3688
  if (path14.startsWith("/webhooks/") && webhookRegistry) {
3599
3689
  const handle = path14.slice("/webhooks/".length);
3600
- const webhookDef = webhookRegistry[handle];
3601
- if (!webhookDef) {
3690
+ if (!webhookRegistry[handle]) {
3602
3691
  return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
3603
3692
  }
3604
- const allowedMethods = webhookDef.methods ?? ["POST"];
3605
- if (!allowedMethods.includes(method)) {
3693
+ if (!isMethodAllowed(webhookRegistry, handle, method)) {
3606
3694
  return createResponse(405, { error: `Method ${method} not allowed` }, headers);
3607
3695
  }
3608
3696
  const rawBody = event.body ?? "";
@@ -3617,107 +3705,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3617
3705
  } else {
3618
3706
  parsedBody = rawBody;
3619
3707
  }
3620
- const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
3621
- let webhookRequest;
3622
- let webhookContext;
3623
- let requestEnv = {};
3624
- let invocation;
3625
- if (isEnvelope) {
3626
- const envelope = parsedBody;
3627
- requestEnv = envelope.env ?? {};
3628
- invocation = envelope.invocation;
3629
- let originalParsedBody = envelope.request.body;
3630
- const originalContentType = envelope.request.headers["content-type"] ?? "";
3631
- if (originalContentType.includes("application/json")) {
3632
- try {
3633
- originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
3634
- } catch {
3635
- }
3636
- }
3637
- webhookRequest = {
3638
- method: envelope.request.method,
3639
- url: envelope.request.url,
3640
- path: envelope.request.path,
3641
- headers: envelope.request.headers,
3642
- query: envelope.request.query,
3643
- body: originalParsedBody,
3644
- rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
3645
- };
3646
- const envVars = { ...process.env, ...requestEnv };
3647
- const app = envelope.context.app;
3648
- if (envelope.context.appInstallationId && envelope.context.workplace) {
3649
- webhookContext = {
3650
- env: envVars,
3651
- app,
3652
- appInstallationId: envelope.context.appInstallationId,
3653
- workplace: envelope.context.workplace,
3654
- registration: envelope.context.registration ?? {},
3655
- invocation,
3656
- log: createContextLogger()
3657
- };
3658
- } else {
3659
- webhookContext = {
3660
- env: envVars,
3661
- app,
3662
- invocation,
3663
- log: createContextLogger()
3664
- };
3665
- }
3666
- } else {
3667
- const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
3668
- const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
3669
- if (!appId || !appVersionId) {
3670
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
3671
- }
3672
- const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3673
- const protocol = forwardedProto ?? "https";
3674
- const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3675
- const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
3676
- const webhookUrl = `${protocol}://${host}${path14}${queryString}`;
3677
- webhookRequest = {
3678
- method,
3679
- url: webhookUrl,
3680
- path: path14,
3681
- headers: event.headers,
3682
- query: event.queryStringParameters ?? {},
3683
- body: parsedBody,
3684
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
3685
- };
3686
- webhookContext = {
3687
- env: process.env,
3688
- app: { id: appId, versionId: appVersionId },
3689
- log: createContextLogger()
3690
- };
3691
- }
3692
- const originalEnv = { ...process.env };
3693
- Object.assign(process.env, requestEnv);
3694
- const requestConfig = {
3695
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3696
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3697
- };
3698
- let webhookResponse;
3699
- try {
3700
- webhookResponse = await runWithLogContext({ invocation }, async () => {
3701
- return await runWithConfig(requestConfig, async () => {
3702
- return await webhookDef.handler(webhookRequest, webhookContext);
3703
- });
3704
- });
3705
- } catch (err) {
3706
- console.error(`Webhook handler '${handle}' error:`, err);
3707
- return createResponse(500, { error: "Webhook handler error" }, headers);
3708
- } finally {
3709
- process.env = originalEnv;
3708
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3709
+ const protocol = forwardedProto ?? "https";
3710
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3711
+ const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
3712
+ const webhookUrl = `${protocol}://${host}${path14}${queryString}`;
3713
+ const parseResult = parseWebhookRequest(
3714
+ parsedBody,
3715
+ method,
3716
+ webhookUrl,
3717
+ path14,
3718
+ event.headers,
3719
+ event.queryStringParameters ?? {},
3720
+ rawBody,
3721
+ event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
3722
+ event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
3723
+ );
3724
+ if ("error" in parseResult) {
3725
+ return createResponse(400, { error: parseResult.error }, headers);
3710
3726
  }
3727
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
3711
3728
  const responseHeaders = {
3712
3729
  ...headers,
3713
- ...webhookResponse.headers
3730
+ ...result.headers
3714
3731
  };
3715
- const status = webhookResponse.status ?? 200;
3716
- const body = webhookResponse.body;
3717
3732
  return {
3718
- statusCode: status,
3733
+ statusCode: result.status,
3719
3734
  headers: responseHeaders,
3720
- body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
3735
+ body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
3721
3736
  };
3722
3737
  }
3723
3738
  if (path14 === "/core" && method === "POST") {
@@ -3853,9 +3868,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3853
3868
  }
3854
3869
  }
3855
3870
  if (path14 === "/install" && method === "POST") {
3856
- if (!config.hooks?.install) {
3857
- return createResponse(404, { error: "Install handler not configured" }, headers);
3858
- }
3859
3871
  let installBody;
3860
3872
  try {
3861
3873
  installBody = event.body ? JSON.parse(event.body) : {};
@@ -3866,68 +3878,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3866
3878
  headers
3867
3879
  );
3868
3880
  }
3869
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
3870
- return createResponse(
3871
- 400,
3872
- { error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
3873
- headers
3874
- );
3875
- }
3876
- const installContext = {
3877
- env: installBody.env ?? {},
3878
- workplace: installBody.context.workplace,
3879
- appInstallationId: installBody.context.appInstallationId,
3880
- app: installBody.context.app,
3881
- invocation: installBody.invocation,
3882
- log: createContextLogger()
3883
- };
3884
- const installRequestConfig = {
3885
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3886
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3887
- };
3888
- try {
3889
- const installHook = config.hooks.install;
3890
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
3891
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
3892
- return await runWithConfig(installRequestConfig, async () => {
3893
- return await installHandler(installContext);
3894
- });
3895
- });
3896
- return createResponse(
3897
- 200,
3898
- { env: result.env ?? {}, redirect: result.redirect },
3899
- headers
3900
- );
3901
- } catch (err) {
3902
- if (err instanceof InstallError) {
3903
- return createResponse(
3904
- 400,
3905
- {
3906
- error: {
3907
- code: err.code,
3908
- message: err.message,
3909
- field: err.field
3910
- }
3911
- },
3912
- headers
3913
- );
3914
- }
3915
- return createResponse(
3916
- 500,
3917
- {
3918
- error: {
3919
- code: -32603,
3920
- message: err instanceof Error ? err.message : String(err ?? "")
3921
- }
3922
- },
3923
- headers
3924
- );
3925
- }
3881
+ const result = await handleInstall(installBody, config.hooks);
3882
+ return createResponse(result.status, result.body, headers);
3926
3883
  }
3927
3884
  if (path14 === "/uninstall" && method === "POST") {
3928
- if (!config.hooks?.uninstall) {
3929
- return createResponse(404, { error: "Uninstall handler not configured" }, headers);
3930
- }
3931
3885
  let uninstallBody;
3932
3886
  try {
3933
3887
  uninstallBody = event.body ? JSON.parse(event.body) : {};
@@ -3938,137 +3892,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3938
3892
  headers
3939
3893
  );
3940
3894
  }
3941
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
3942
- return createResponse(
3943
- 400,
3944
- {
3945
- error: {
3946
- code: -32602,
3947
- message: "Missing context (appInstallationId, workplace and app required)"
3948
- }
3949
- },
3950
- headers
3951
- );
3952
- }
3953
- const uninstallContext = {
3954
- env: uninstallBody.env ?? {},
3955
- workplace: uninstallBody.context.workplace,
3956
- appInstallationId: uninstallBody.context.appInstallationId,
3957
- app: uninstallBody.context.app,
3958
- invocation: uninstallBody.invocation,
3959
- log: createContextLogger()
3960
- };
3961
- const uninstallRequestConfig = {
3962
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3963
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3964
- };
3965
- try {
3966
- const uninstallHook = config.hooks.uninstall;
3967
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3968
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
3969
- return await runWithConfig(uninstallRequestConfig, async () => {
3970
- return await uninstallHandlerFn(uninstallContext);
3971
- });
3972
- });
3973
- return createResponse(
3974
- 200,
3975
- { cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
3976
- headers
3977
- );
3978
- } catch (err) {
3979
- return createResponse(
3980
- 500,
3981
- {
3982
- error: {
3983
- code: -32603,
3984
- message: err instanceof Error ? err.message : String(err ?? "")
3985
- }
3986
- },
3987
- headers
3988
- );
3989
- }
3895
+ const result = await handleUninstall(uninstallBody, config.hooks);
3896
+ return createResponse(result.status, result.body, headers);
3990
3897
  }
3991
3898
  if (path14 === "/provision" && method === "POST") {
3992
- console.log("[serverless] /provision endpoint called");
3993
- if (!config.hooks?.provision) {
3994
- console.log("[serverless] No provision handler configured");
3995
- return createResponse(404, { error: "Provision handler not configured" }, headers);
3996
- }
3997
3899
  let provisionBody;
3998
3900
  try {
3999
3901
  provisionBody = event.body ? JSON.parse(event.body) : {};
4000
- console.log("[serverless] Provision body parsed:", {
4001
- hasEnv: !!provisionBody.env,
4002
- hasContext: !!provisionBody.context,
4003
- appId: provisionBody.context?.app?.id,
4004
- versionId: provisionBody.context?.app?.versionId
4005
- });
4006
3902
  } catch {
4007
- console.log("[serverless] Failed to parse provision body");
4008
3903
  return createResponse(
4009
3904
  400,
4010
3905
  { error: { code: -32700, message: "Parse error" } },
4011
3906
  headers
4012
3907
  );
4013
3908
  }
4014
- if (!provisionBody.context?.app) {
4015
- console.log("[serverless] Missing app context in provision body");
4016
- return createResponse(
4017
- 400,
4018
- { error: { code: -32602, message: "Missing context (app required)" } },
4019
- headers
4020
- );
4021
- }
4022
- const mergedEnv = {};
4023
- for (const [key, value] of Object.entries(process.env)) {
4024
- if (value !== void 0) {
4025
- mergedEnv[key] = value;
4026
- }
4027
- }
4028
- Object.assign(mergedEnv, provisionBody.env ?? {});
4029
- const provisionContext = {
4030
- env: mergedEnv,
4031
- app: provisionBody.context.app,
4032
- invocation: provisionBody.invocation,
4033
- log: createContextLogger()
4034
- };
4035
- const provisionRequestConfig = {
4036
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
4037
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
4038
- };
4039
- console.log("[serverless] Calling provision handler...");
4040
- try {
4041
- const provisionHook = config.hooks.provision;
4042
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
4043
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
4044
- return await runWithConfig(provisionRequestConfig, async () => {
4045
- return await provisionHandler(provisionContext);
4046
- });
4047
- });
4048
- console.log("[serverless] Provision handler completed successfully");
4049
- return createResponse(200, result, headers);
4050
- } catch (err) {
4051
- console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
4052
- return createResponse(
4053
- 500,
4054
- {
4055
- error: {
4056
- code: -32603,
4057
- message: err instanceof Error ? err.message : String(err ?? "")
4058
- }
4059
- },
4060
- headers
4061
- );
4062
- }
3909
+ const result = await handleProvision(provisionBody, config.hooks);
3910
+ return createResponse(result.status, result.body, headers);
4063
3911
  }
4064
3912
  if (path14 === "/oauth_callback" && method === "POST") {
4065
- if (!config.hooks?.oauth_callback) {
4066
- return createResponse(
4067
- 404,
4068
- { error: "OAuth callback handler not configured" },
4069
- headers
4070
- );
4071
- }
4072
3913
  let parsedBody;
4073
3914
  try {
4074
3915
  parsedBody = event.body ? JSON.parse(event.body) : {};
@@ -4080,70 +3921,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
4080
3921
  headers
4081
3922
  );
4082
3923
  }
4083
- const envelope = parseHandlerEnvelope(parsedBody);
4084
- if (!envelope) {
4085
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
4086
- return createResponse(
4087
- 400,
4088
- { error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
4089
- headers
4090
- );
4091
- }
4092
- const invocation = parsedBody.invocation;
4093
- const oauthRequest = buildRequestFromRaw(envelope.request);
4094
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
4095
- const oauthCallbackContext = {
4096
- request: oauthRequest,
4097
- invocation,
4098
- log: createContextLogger()
4099
- };
4100
- try {
4101
- const oauthCallbackHook = config.hooks.oauth_callback;
4102
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
4103
- const result = await runWithLogContext({ invocation }, async () => {
4104
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
4105
- return await oauthCallbackHandler(oauthCallbackContext);
4106
- });
4107
- });
4108
- return createResponse(
4109
- 200,
4110
- {
4111
- appInstallationId: result.appInstallationId,
4112
- env: result.env ?? {}
4113
- },
4114
- headers
4115
- );
4116
- } catch (err) {
4117
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
4118
- return createResponse(
4119
- 500,
4120
- {
4121
- error: {
4122
- code: -32603,
4123
- message: errorMessage
4124
- }
4125
- },
4126
- headers
4127
- );
4128
- }
3924
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
3925
+ return createResponse(result.status, result.body, headers);
4129
3926
  }
4130
3927
  if (path14 === "/health" && method === "GET") {
4131
3928
  return createResponse(200, state.getHealthStatus(), headers);
4132
3929
  }
4133
3930
  if (path14 === "/config" && method === "GET") {
4134
- let appConfig = config.appConfig;
4135
- if (!appConfig && config.appConfigLoader) {
4136
- const loaded = await config.appConfigLoader();
4137
- appConfig = loaded.default;
4138
- }
4139
- if (!appConfig) {
4140
- appConfig = createMinimalConfig(
4141
- config.metadata.name,
4142
- config.metadata.version
4143
- );
4144
- }
4145
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
4146
- return createResponse(200, serializedConfig, headers);
3931
+ return createResponse(200, serializeConfig(config), headers);
4147
3932
  }
4148
3933
  if (path14 === "/mcp" && method === "POST") {
4149
3934
  let body;
@@ -4361,9 +4146,11 @@ console.log("[skedyul-node/server] All imports complete");
4361
4146
  console.log("[skedyul-node/server] Installing context logger...");
4362
4147
  installContextLogger();
4363
4148
  console.log("[skedyul-node/server] Context logger installed");
4364
- function createSkedyulServer(config, registry, webhookRegistry) {
4149
+ function createSkedyulServer(config) {
4365
4150
  console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
4366
4151
  mergeRuntimeEnv();
4152
+ const registry = config.tools;
4153
+ const webhookRegistry = config.webhooks;
4367
4154
  console.log("[createSkedyulServer] Step 2: coreApi setup");
4368
4155
  if (config.coreApi?.service) {
4369
4156
  coreApiService.register(config.coreApi.service);
@@ -4375,7 +4162,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4375
4162
  const tools = buildToolMetadata(registry);
4376
4163
  console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
4377
4164
  const toolNames = Object.values(registry).map((tool) => tool.name);
4378
- const runtimeLabel = config.computeLayer;
4165
+ const runtimeLabel = config.computeLayer ?? "serverless";
4379
4166
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
4380
4167
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
4381
4168
  console.log("[createSkedyulServer] Step 4: createRequestState()");
@@ -4388,8 +4175,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4388
4175
  console.log("[createSkedyulServer] Step 4 done");
4389
4176
  console.log("[createSkedyulServer] Step 5: new McpServer()");
4390
4177
  const mcpServer = new import_mcp.McpServer({
4391
- name: config.metadata.name,
4392
- version: config.metadata.version
4178
+ name: config.name,
4179
+ version: config.version ?? "0.0.0"
4393
4180
  });
4394
4181
  console.log("[createSkedyulServer] Step 5 done");
4395
4182
  const dedicatedShutdown = () => {
@@ -4517,13 +4304,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4517
4304
  tools,
4518
4305
  callTool,
4519
4306
  state,
4520
- mcpServer,
4521
- registry,
4522
- webhookRegistry
4307
+ mcpServer
4523
4308
  );
4524
4309
  }
4525
4310
  console.log("[createSkedyulServer] Creating serverless instance");
4526
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
4311
+ const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
4527
4312
  console.log("[createSkedyulServer] Serverless instance created successfully");
4528
4313
  return serverlessInstance;
4529
4314
  }
@@ -4922,18 +4707,14 @@ Built config for sync:`);
4922
4707
  } catch (error) {
4923
4708
  console.warn(`Could not load install handler: ${error instanceof Error ? error.message : String(error)}`);
4924
4709
  }
4925
- const server2 = createSkedyulServer(
4926
- {
4927
- computeLayer: "dedicated",
4928
- metadata: {
4929
- name: serverName,
4930
- version: serverVersion
4931
- },
4932
- defaultPort: port,
4933
- hooks: installHandler ? { install: installHandler } : void 0
4934
- },
4935
- registry
4936
- );
4710
+ const server2 = createSkedyulServer({
4711
+ name: serverName,
4712
+ version: serverVersion,
4713
+ computeLayer: "dedicated",
4714
+ defaultPort: port,
4715
+ tools: registry,
4716
+ hooks: installHandler ? { install: installHandler } : void 0
4717
+ });
4937
4718
  const dedicatedServer = server2;
4938
4719
  try {
4939
4720
  await dedicatedServer.listen(port);