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/index.js CHANGED
@@ -136,7 +136,6 @@ __export(index_exports, {
136
136
  communicationChannel: () => communicationChannel,
137
137
  configure: () => configure,
138
138
  createContextLogger: () => createContextLogger,
139
- createMinimalConfig: () => createMinimalConfig,
140
139
  createServerHookContext: () => createServerHookContext,
141
140
  createToolCallContext: () => createToolCallContext,
142
141
  createWebhookContext: () => createWebhookContext,
@@ -163,7 +162,6 @@ __export(index_exports, {
163
162
  isWorkflowDependency: () => isWorkflowDependency,
164
163
  loadConfig: () => loadConfig,
165
164
  report: () => report,
166
- resolveConfig: () => resolveConfig,
167
165
  resource: () => resource,
168
166
  runWithConfig: () => runWithConfig,
169
167
  safeParseConfig: () => safeParseConfig,
@@ -2246,8 +2244,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
2246
2244
  };
2247
2245
  }
2248
2246
  function createCallToolHandler(registry, state, onMaxRequests) {
2249
- return async function callTool(nameRaw, argsRaw) {
2250
- const toolName = String(nameRaw);
2247
+ return async function callTool(toolNameInput, toolArgsInput) {
2248
+ const toolName = String(toolNameInput);
2251
2249
  const tool = registry[toolName];
2252
2250
  if (!tool) {
2253
2251
  throw new Error(`Tool "${toolName}" not found in registry`);
@@ -2256,7 +2254,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
2256
2254
  throw new Error(`Tool "${toolName}" handler is not a function`);
2257
2255
  }
2258
2256
  const fn = tool.handler;
2259
- const args = argsRaw ?? {};
2257
+ const args = toolArgsInput ?? {};
2260
2258
  const estimateMode = args.estimate === true;
2261
2259
  if (!estimateMode) {
2262
2260
  state.incrementRequestCount();
@@ -2511,51 +2509,6 @@ async function handleCoreMethod(method, params) {
2511
2509
  };
2512
2510
  }
2513
2511
 
2514
- // src/server/handler-helpers.ts
2515
- function parseHandlerEnvelope(parsedBody) {
2516
- if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
2517
- return null;
2518
- }
2519
- const envelope = parsedBody;
2520
- if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
2521
- return null;
2522
- }
2523
- if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
2524
- return null;
2525
- }
2526
- return {
2527
- env: envelope.env,
2528
- request: envelope.request,
2529
- context: envelope.context
2530
- };
2531
- }
2532
- function buildRequestFromRaw(raw) {
2533
- let parsedBody = raw.body;
2534
- const contentType = raw.headers["content-type"] ?? "";
2535
- if (contentType.includes("application/json")) {
2536
- try {
2537
- parsedBody = raw.body ? JSON.parse(raw.body) : {};
2538
- } catch {
2539
- parsedBody = raw.body;
2540
- }
2541
- }
2542
- return {
2543
- method: raw.method,
2544
- url: raw.url,
2545
- path: raw.path,
2546
- headers: raw.headers,
2547
- query: raw.query,
2548
- body: parsedBody,
2549
- rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
2550
- };
2551
- }
2552
- function buildRequestScopedConfig(env) {
2553
- return {
2554
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2555
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2556
- };
2557
- }
2558
-
2559
2512
  // src/server/startup-logger.ts
2560
2513
  function padEnd(str, length) {
2561
2514
  if (str.length >= length) {
@@ -2563,10 +2516,11 @@ function padEnd(str, length) {
2563
2516
  }
2564
2517
  return str + " ".repeat(length - str.length);
2565
2518
  }
2566
- function printStartupLog(config, tools, webhookRegistry, port) {
2519
+ function printStartupLog(config, tools, port) {
2567
2520
  if (process.env.NODE_ENV === "test") {
2568
2521
  return;
2569
2522
  }
2523
+ const webhookRegistry = config.webhooks;
2570
2524
  const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
2571
2525
  const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
2572
2526
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
@@ -2579,9 +2533,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
2579
2533
  console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
2580
2534
  console.log(`\u2560${divider}\u2563`);
2581
2535
  console.log(`\u2551 \u2551`);
2582
- console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.metadata.name, 49)}\u2551`);
2583
- console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.metadata.version, 49)}\u2551`);
2584
- console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
2536
+ console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
2537
+ console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
2538
+ console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
2585
2539
  if (port) {
2586
2540
  console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
2587
2541
  }
@@ -2623,67 +2577,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
2623
2577
  console.log("");
2624
2578
  }
2625
2579
 
2626
- // src/config/resolve.ts
2627
- async function resolveDynamicImport(value) {
2628
- if (value === void 0 || value === null) {
2629
- return void 0;
2580
+ // src/server/config-serializer.ts
2581
+ function serializeConfig(config) {
2582
+ const registry = config.tools;
2583
+ const webhookRegistry = config.webhooks;
2584
+ return {
2585
+ name: config.name,
2586
+ version: config.version,
2587
+ description: config.description,
2588
+ computeLayer: config.computeLayer,
2589
+ tools: registry ? Object.entries(registry).map(([key, tool]) => ({
2590
+ name: tool.name || key,
2591
+ description: tool.description,
2592
+ timeout: tool.timeout,
2593
+ retries: tool.retries
2594
+ })) : [],
2595
+ webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
2596
+ name: w.name,
2597
+ description: w.description,
2598
+ methods: w.methods ?? ["POST"],
2599
+ type: w.type ?? "WEBHOOK"
2600
+ })) : [],
2601
+ provision: isProvisionConfig(config.provision) ? config.provision : void 0,
2602
+ agents: config.agents
2603
+ };
2604
+ }
2605
+ function isProvisionConfig(value) {
2606
+ return value !== void 0 && value !== null && !(value instanceof Promise);
2607
+ }
2608
+
2609
+ // src/server/handlers/install-handler.ts
2610
+ async function handleInstall(body, hooks) {
2611
+ if (!hooks?.install) {
2612
+ return {
2613
+ status: 404,
2614
+ body: { error: "Install handler not configured" }
2615
+ };
2616
+ }
2617
+ if (!body.context?.appInstallationId || !body.context?.workplace) {
2618
+ return {
2619
+ status: 400,
2620
+ body: {
2621
+ error: {
2622
+ code: -32602,
2623
+ message: "Missing context (appInstallationId and workplace required)"
2624
+ }
2625
+ }
2626
+ };
2627
+ }
2628
+ const installContext = {
2629
+ env: body.env ?? {},
2630
+ workplace: body.context.workplace,
2631
+ appInstallationId: body.context.appInstallationId,
2632
+ app: body.context.app,
2633
+ invocation: body.invocation,
2634
+ log: createContextLogger()
2635
+ };
2636
+ const requestConfig = {
2637
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2638
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2639
+ };
2640
+ try {
2641
+ const installHook = hooks.install;
2642
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
2643
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
2644
+ return await runWithConfig(requestConfig, async () => {
2645
+ return await installHandler(installContext);
2646
+ });
2647
+ });
2648
+ return {
2649
+ status: 200,
2650
+ body: { env: result.env ?? {}, redirect: result.redirect }
2651
+ };
2652
+ } catch (err) {
2653
+ if (err instanceof InstallError) {
2654
+ return {
2655
+ status: 400,
2656
+ body: {
2657
+ error: {
2658
+ code: err.code,
2659
+ message: err.message,
2660
+ field: err.field
2661
+ }
2662
+ }
2663
+ };
2664
+ }
2665
+ return {
2666
+ status: 500,
2667
+ body: {
2668
+ error: {
2669
+ code: -32603,
2670
+ message: err instanceof Error ? err.message : String(err ?? "")
2671
+ }
2672
+ }
2673
+ };
2674
+ }
2675
+ }
2676
+
2677
+ // src/server/handlers/uninstall-handler.ts
2678
+ async function handleUninstall(body, hooks) {
2679
+ if (!hooks?.uninstall) {
2680
+ return {
2681
+ status: 404,
2682
+ body: { error: "Uninstall handler not configured" }
2683
+ };
2684
+ }
2685
+ if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
2686
+ return {
2687
+ status: 400,
2688
+ body: {
2689
+ error: {
2690
+ code: -32602,
2691
+ message: "Missing context (appInstallationId, workplace and app required)"
2692
+ }
2693
+ }
2694
+ };
2695
+ }
2696
+ const uninstallContext = {
2697
+ env: body.env ?? {},
2698
+ workplace: body.context.workplace,
2699
+ appInstallationId: body.context.appInstallationId,
2700
+ app: body.context.app,
2701
+ invocation: body.invocation,
2702
+ log: createContextLogger()
2703
+ };
2704
+ const requestConfig = {
2705
+ baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2706
+ apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2707
+ };
2708
+ try {
2709
+ const uninstallHook = hooks.uninstall;
2710
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
2711
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
2712
+ return await runWithConfig(requestConfig, async () => {
2713
+ return await uninstallHandlerFn(uninstallContext);
2714
+ });
2715
+ });
2716
+ return {
2717
+ status: 200,
2718
+ body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
2719
+ };
2720
+ } catch (err) {
2721
+ return {
2722
+ status: 500,
2723
+ body: {
2724
+ error: {
2725
+ code: -32603,
2726
+ message: err instanceof Error ? err.message : String(err ?? "")
2727
+ }
2728
+ }
2729
+ };
2730
+ }
2731
+ }
2732
+
2733
+ // src/server/handlers/provision-handler.ts
2734
+ async function handleProvision(body, hooks) {
2735
+ if (!hooks?.provision) {
2736
+ return {
2737
+ status: 404,
2738
+ body: { error: "Provision handler not configured" }
2739
+ };
2740
+ }
2741
+ if (!body.context?.app) {
2742
+ return {
2743
+ status: 400,
2744
+ body: {
2745
+ error: {
2746
+ code: -32602,
2747
+ message: "Missing context (app required)"
2748
+ }
2749
+ }
2750
+ };
2630
2751
  }
2631
- if (value instanceof Promise) {
2632
- const resolved = await value;
2633
- if (resolved && typeof resolved === "object" && "default" in resolved) {
2634
- return resolved.default;
2752
+ const mergedEnv = {};
2753
+ for (const [key, value] of Object.entries(process.env)) {
2754
+ if (value !== void 0) {
2755
+ mergedEnv[key] = value;
2635
2756
  }
2636
- return resolved;
2637
2757
  }
2638
- return value;
2758
+ Object.assign(mergedEnv, body.env ?? {});
2759
+ const provisionContext = {
2760
+ env: mergedEnv,
2761
+ app: body.context.app,
2762
+ invocation: body.invocation,
2763
+ log: createContextLogger()
2764
+ };
2765
+ const requestConfig = {
2766
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
2767
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
2768
+ };
2769
+ try {
2770
+ const provisionHook = hooks.provision;
2771
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
2772
+ const result = await runWithLogContext({ invocation: body.invocation }, async () => {
2773
+ return await runWithConfig(requestConfig, async () => {
2774
+ return await provisionHandler(provisionContext);
2775
+ });
2776
+ });
2777
+ return {
2778
+ status: 200,
2779
+ body: result
2780
+ };
2781
+ } catch (err) {
2782
+ return {
2783
+ status: 500,
2784
+ body: {
2785
+ error: {
2786
+ code: -32603,
2787
+ message: err instanceof Error ? err.message : String(err ?? "")
2788
+ }
2789
+ }
2790
+ };
2791
+ }
2639
2792
  }
2640
- function serializeTools(registry) {
2641
- return Object.entries(registry).map(([key, tool]) => ({
2642
- name: tool.name || key,
2643
- displayName: tool.label,
2644
- description: tool.description,
2645
- timeout: tool.timeout,
2646
- retries: tool.retries
2647
- }));
2793
+
2794
+ // src/server/handler-helpers.ts
2795
+ function parseHandlerEnvelope(parsedBody) {
2796
+ if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
2797
+ return null;
2798
+ }
2799
+ const envelope = parsedBody;
2800
+ if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
2801
+ return null;
2802
+ }
2803
+ if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
2804
+ return null;
2805
+ }
2806
+ return {
2807
+ env: envelope.env,
2808
+ request: envelope.request,
2809
+ context: envelope.context
2810
+ };
2648
2811
  }
2649
- function serializeWebhooks(registry) {
2650
- return Object.values(registry).map((webhook2) => ({
2651
- name: webhook2.name,
2652
- description: webhook2.description,
2653
- methods: webhook2.methods ?? ["POST"],
2654
- type: webhook2.type ?? "WEBHOOK"
2655
- }));
2812
+ function buildRequestFromRaw(raw) {
2813
+ let parsedBody = raw.body;
2814
+ const contentType = raw.headers["content-type"] ?? "";
2815
+ if (contentType.includes("application/json")) {
2816
+ try {
2817
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
2818
+ } catch {
2819
+ parsedBody = raw.body;
2820
+ }
2821
+ }
2822
+ return {
2823
+ method: raw.method,
2824
+ url: raw.url,
2825
+ path: raw.path,
2826
+ headers: raw.headers,
2827
+ query: raw.query,
2828
+ body: parsedBody,
2829
+ rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
2830
+ };
2656
2831
  }
2657
- async function resolveConfig(config, registry, webhookRegistry) {
2658
- const provision = await resolveDynamicImport(
2659
- config.provision
2660
- );
2661
- const install = await resolveDynamicImport(
2662
- config.install
2663
- );
2664
- const tools = serializeTools(registry);
2665
- const webhooks = webhookRegistry ? serializeWebhooks(webhookRegistry) : [];
2832
+ function buildRequestScopedConfig(env) {
2666
2833
  return {
2667
- name: config.name,
2668
- version: config.version,
2669
- description: config.description,
2670
- computeLayer: config.computeLayer,
2671
- tools,
2672
- webhooks,
2673
- provision,
2674
- agents: config.agents
2834
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2835
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2836
+ };
2837
+ }
2838
+
2839
+ // src/server/handlers/oauth-callback-handler.ts
2840
+ async function handleOAuthCallback(parsedBody, hooks) {
2841
+ if (!hooks?.oauth_callback) {
2842
+ return {
2843
+ status: 404,
2844
+ body: { error: "OAuth callback handler not configured" }
2845
+ };
2846
+ }
2847
+ const envelope = parseHandlerEnvelope(parsedBody);
2848
+ if (!envelope) {
2849
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
2850
+ return {
2851
+ status: 400,
2852
+ body: {
2853
+ error: {
2854
+ code: -32602,
2855
+ message: "Missing envelope format: expected { env, request }"
2856
+ }
2857
+ }
2858
+ };
2859
+ }
2860
+ const invocation = parsedBody.invocation;
2861
+ const oauthRequest = buildRequestFromRaw(envelope.request);
2862
+ const requestConfig = buildRequestScopedConfig(envelope.env);
2863
+ const oauthCallbackContext = {
2864
+ request: oauthRequest,
2865
+ invocation,
2866
+ log: createContextLogger()
2867
+ };
2868
+ try {
2869
+ const oauthCallbackHook = hooks.oauth_callback;
2870
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
2871
+ const result = await runWithLogContext({ invocation }, async () => {
2872
+ return await runWithConfig(requestConfig, async () => {
2873
+ return await oauthCallbackHandler(oauthCallbackContext);
2874
+ });
2875
+ });
2876
+ return {
2877
+ status: 200,
2878
+ body: {
2879
+ appInstallationId: result.appInstallationId,
2880
+ env: result.env ?? {}
2881
+ }
2882
+ };
2883
+ } catch (err) {
2884
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
2885
+ return {
2886
+ status: 500,
2887
+ body: {
2888
+ error: {
2889
+ code: -32603,
2890
+ message: errorMessage
2891
+ }
2892
+ }
2893
+ };
2894
+ }
2895
+ }
2896
+
2897
+ // src/server/handlers/webhook-handler.ts
2898
+ function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
2899
+ const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
2900
+ if (isEnvelope) {
2901
+ const envelope = parsedBody;
2902
+ const requestEnv = envelope.env ?? {};
2903
+ const invocation = envelope.invocation;
2904
+ let originalParsedBody = envelope.request.body;
2905
+ const originalContentType = envelope.request.headers["content-type"] ?? "";
2906
+ if (originalContentType.includes("application/json")) {
2907
+ try {
2908
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
2909
+ } catch {
2910
+ }
2911
+ }
2912
+ const webhookRequest2 = {
2913
+ method: envelope.request.method,
2914
+ url: envelope.request.url,
2915
+ path: envelope.request.path,
2916
+ headers: envelope.request.headers,
2917
+ query: envelope.request.query,
2918
+ body: originalParsedBody,
2919
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
2920
+ };
2921
+ const envVars = { ...process.env, ...requestEnv };
2922
+ const app = envelope.context.app;
2923
+ let webhookContext2;
2924
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
2925
+ webhookContext2 = {
2926
+ env: envVars,
2927
+ app,
2928
+ appInstallationId: envelope.context.appInstallationId,
2929
+ workplace: envelope.context.workplace,
2930
+ registration: envelope.context.registration ?? {},
2931
+ invocation,
2932
+ log: createContextLogger()
2933
+ };
2934
+ } else {
2935
+ webhookContext2 = {
2936
+ env: envVars,
2937
+ app,
2938
+ invocation,
2939
+ log: createContextLogger()
2940
+ };
2941
+ }
2942
+ return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
2943
+ }
2944
+ if (!appIdHeader || !appVersionIdHeader) {
2945
+ return {
2946
+ error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
2947
+ };
2948
+ }
2949
+ const webhookRequest = {
2950
+ method,
2951
+ url,
2952
+ path: path2,
2953
+ headers,
2954
+ query,
2955
+ body: parsedBody,
2956
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
2957
+ };
2958
+ const webhookContext = {
2959
+ env: process.env,
2960
+ app: { id: appIdHeader, versionId: appVersionIdHeader },
2961
+ log: createContextLogger()
2675
2962
  };
2963
+ return { webhookRequest, webhookContext, requestEnv: {} };
2676
2964
  }
2677
- function createMinimalConfig(name, version) {
2965
+ async function executeWebhookHandler(handle, webhookRegistry, data) {
2966
+ const webhookDef = webhookRegistry[handle];
2967
+ if (!webhookDef) {
2968
+ return {
2969
+ status: 404,
2970
+ body: { error: `Webhook handler '${handle}' not found` }
2971
+ };
2972
+ }
2973
+ const originalEnv = { ...process.env };
2974
+ Object.assign(process.env, data.requestEnv);
2975
+ const requestConfig = {
2976
+ baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2977
+ apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2978
+ };
2979
+ let webhookResponse;
2980
+ try {
2981
+ webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
2982
+ return await runWithConfig(requestConfig, async () => {
2983
+ return await webhookDef.handler(data.webhookRequest, data.webhookContext);
2984
+ });
2985
+ });
2986
+ } catch (err) {
2987
+ console.error(`Webhook handler '${handle}' error:`, err);
2988
+ return {
2989
+ status: 500,
2990
+ body: { error: "Webhook handler error" }
2991
+ };
2992
+ } finally {
2993
+ process.env = originalEnv;
2994
+ }
2678
2995
  return {
2679
- name,
2680
- version
2996
+ status: webhookResponse.status ?? 200,
2997
+ body: webhookResponse.body,
2998
+ headers: webhookResponse.headers
2681
2999
  };
2682
3000
  }
3001
+ function isMethodAllowed(webhookRegistry, handle, method) {
3002
+ const webhookDef = webhookRegistry[handle];
3003
+ if (!webhookDef) return false;
3004
+ const allowedMethods = webhookDef.methods ?? ["POST"];
3005
+ return allowedMethods.includes(method);
3006
+ }
2683
3007
 
2684
3008
  // src/server/dedicated.ts
2685
- function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
3009
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
2686
3010
  const port = getListeningPort(config);
3011
+ const registry = config.tools;
3012
+ const webhookRegistry = config.webhooks;
2687
3013
  const httpServer = import_http2.default.createServer(
2688
3014
  async (req, res) => {
2689
3015
  function sendCoreResult(result) {
@@ -2700,30 +3026,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2700
3026
  return;
2701
3027
  }
2702
3028
  if (pathname === "/config" && req.method === "GET") {
2703
- let appConfig = config.appConfig;
2704
- if (!appConfig && config.appConfigLoader) {
2705
- const loaded = await config.appConfigLoader();
2706
- appConfig = loaded.default;
2707
- }
2708
- if (!appConfig) {
2709
- appConfig = createMinimalConfig(
2710
- config.metadata.name,
2711
- config.metadata.version
2712
- );
2713
- }
2714
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
2715
- sendJSON(res, 200, serializedConfig);
3029
+ sendJSON(res, 200, serializeConfig(config));
2716
3030
  return;
2717
3031
  }
2718
3032
  if (pathname.startsWith("/webhooks/") && webhookRegistry) {
2719
3033
  const handle = pathname.slice("/webhooks/".length);
2720
- const webhookDef = webhookRegistry[handle];
2721
- if (!webhookDef) {
3034
+ if (!webhookRegistry[handle]) {
2722
3035
  sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
2723
3036
  return;
2724
3037
  }
2725
- const allowedMethods = webhookDef.methods ?? ["POST"];
2726
- if (!allowedMethods.includes(req.method)) {
3038
+ if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
2727
3039
  sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
2728
3040
  return;
2729
3041
  }
@@ -2745,87 +3057,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2745
3057
  } else {
2746
3058
  parsedBody = rawBody;
2747
3059
  }
2748
- const envelope = parseHandlerEnvelope(parsedBody);
2749
- let webhookRequest;
2750
- let webhookContext;
2751
- let requestEnv = {};
2752
- let invocation;
2753
- if (envelope && "context" in envelope && envelope.context) {
2754
- const context = envelope.context;
2755
- requestEnv = envelope.env;
2756
- invocation = parsedBody.invocation;
2757
- webhookRequest = buildRequestFromRaw(envelope.request);
2758
- const envVars = { ...process.env, ...envelope.env };
2759
- const app = context.app;
2760
- if (context.appInstallationId && context.workplace) {
2761
- webhookContext = {
2762
- env: envVars,
2763
- app,
2764
- appInstallationId: context.appInstallationId,
2765
- workplace: context.workplace,
2766
- registration: context.registration ?? {},
2767
- invocation,
2768
- log: createContextLogger()
2769
- };
2770
- } else {
2771
- webhookContext = {
2772
- env: envVars,
2773
- app,
2774
- invocation,
2775
- log: createContextLogger()
2776
- };
2777
- }
2778
- } else {
2779
- const appId = req.headers["x-skedyul-app-id"];
2780
- const appVersionId = req.headers["x-skedyul-app-version-id"];
2781
- if (!appId || !appVersionId) {
2782
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
2783
- }
2784
- webhookRequest = {
2785
- method: req.method ?? "POST",
2786
- url: url.toString(),
2787
- path: pathname,
2788
- headers: req.headers,
2789
- query: Object.fromEntries(url.searchParams.entries()),
2790
- body: parsedBody,
2791
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
2792
- };
2793
- webhookContext = {
2794
- env: process.env,
2795
- app: { id: appId, versionId: appVersionId },
2796
- log: createContextLogger()
2797
- };
2798
- }
2799
- const originalEnv = { ...process.env };
2800
- Object.assign(process.env, requestEnv);
2801
- const requestConfig = buildRequestScopedConfig(requestEnv);
2802
- let webhookResponse;
2803
- try {
2804
- webhookResponse = await runWithLogContext({ invocation }, async () => {
2805
- return await runWithConfig(requestConfig, async () => {
2806
- return await webhookDef.handler(webhookRequest, webhookContext);
2807
- });
2808
- });
2809
- } catch (err) {
2810
- console.error(`Webhook handler '${handle}' error:`, err);
2811
- sendJSON(res, 500, { error: "Webhook handler error" });
3060
+ const parseResult = parseWebhookRequest(
3061
+ parsedBody,
3062
+ req.method ?? "POST",
3063
+ url.toString(),
3064
+ pathname,
3065
+ req.headers,
3066
+ Object.fromEntries(url.searchParams.entries()),
3067
+ rawBody,
3068
+ req.headers["x-skedyul-app-id"],
3069
+ req.headers["x-skedyul-app-version-id"]
3070
+ );
3071
+ if ("error" in parseResult) {
3072
+ sendJSON(res, 400, { error: parseResult.error });
2812
3073
  return;
2813
- } finally {
2814
- process.env = originalEnv;
2815
3074
  }
2816
- const status = webhookResponse.status ?? 200;
3075
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
2817
3076
  const responseHeaders = {
2818
- ...webhookResponse.headers
3077
+ ...result.headers
2819
3078
  };
2820
3079
  if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
2821
3080
  responseHeaders["Content-Type"] = "application/json";
2822
3081
  }
2823
- res.writeHead(status, responseHeaders);
2824
- if (webhookResponse.body !== void 0) {
2825
- if (typeof webhookResponse.body === "string") {
2826
- res.end(webhookResponse.body);
3082
+ res.writeHead(result.status, responseHeaders);
3083
+ if (result.body !== void 0) {
3084
+ if (typeof result.body === "string") {
3085
+ res.end(result.body);
2827
3086
  } else {
2828
- res.end(JSON.stringify(webhookResponse.body));
3087
+ res.end(JSON.stringify(result.body));
2829
3088
  }
2830
3089
  } else {
2831
3090
  res.end();
@@ -2864,10 +3123,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2864
3123
  return;
2865
3124
  }
2866
3125
  if (pathname === "/oauth_callback" && req.method === "POST") {
2867
- if (!config.hooks?.oauth_callback) {
2868
- sendJSON(res, 404, { error: "OAuth callback handler not configured" });
2869
- return;
2870
- }
2871
3126
  let parsedBody;
2872
3127
  try {
2873
3128
  parsedBody = await parseJSONBody(req);
@@ -2878,50 +3133,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2878
3133
  });
2879
3134
  return;
2880
3135
  }
2881
- const envelope = parseHandlerEnvelope(parsedBody);
2882
- if (!envelope) {
2883
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
2884
- sendJSON(res, 400, {
2885
- error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
2886
- });
2887
- return;
2888
- }
2889
- const invocation = parsedBody.invocation;
2890
- const oauthRequest = buildRequestFromRaw(envelope.request);
2891
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
2892
- const oauthCallbackContext = {
2893
- request: oauthRequest,
2894
- invocation,
2895
- log: createContextLogger()
2896
- };
2897
- try {
2898
- const oauthCallbackHook = config.hooks.oauth_callback;
2899
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
2900
- const result = await runWithLogContext({ invocation }, async () => {
2901
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
2902
- return await oauthCallbackHandler(oauthCallbackContext);
2903
- });
2904
- });
2905
- sendJSON(res, 200, {
2906
- appInstallationId: result.appInstallationId,
2907
- env: result.env ?? {}
2908
- });
2909
- } catch (err) {
2910
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
2911
- sendJSON(res, 500, {
2912
- error: {
2913
- code: -32603,
2914
- message: errorMessage
2915
- }
2916
- });
2917
- }
3136
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
3137
+ sendJSON(res, result.status, result.body);
2918
3138
  return;
2919
3139
  }
2920
3140
  if (pathname === "/install" && req.method === "POST") {
2921
- if (!config.hooks?.install) {
2922
- sendJSON(res, 404, { error: "Install handler not configured" });
2923
- return;
2924
- }
2925
3141
  let installBody;
2926
3142
  try {
2927
3143
  installBody = await parseJSONBody(req);
@@ -2931,61 +3147,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2931
3147
  });
2932
3148
  return;
2933
3149
  }
2934
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
2935
- sendJSON(res, 400, {
2936
- error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
2937
- });
2938
- return;
2939
- }
2940
- const installContext = {
2941
- env: installBody.env ?? {},
2942
- workplace: installBody.context.workplace,
2943
- appInstallationId: installBody.context.appInstallationId,
2944
- app: installBody.context.app,
2945
- invocation: installBody.invocation,
2946
- log: createContextLogger()
2947
- };
2948
- const installRequestConfig = {
2949
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2950
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2951
- };
2952
- try {
2953
- const installHook = config.hooks.install;
2954
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
2955
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
2956
- return await runWithConfig(installRequestConfig, async () => {
2957
- return await installHandler(installContext);
2958
- });
2959
- });
2960
- sendJSON(res, 200, {
2961
- env: result.env ?? {},
2962
- redirect: result.redirect
2963
- });
2964
- } catch (err) {
2965
- if (err instanceof InstallError) {
2966
- sendJSON(res, 400, {
2967
- error: {
2968
- code: err.code,
2969
- message: err.message,
2970
- field: err.field
2971
- }
2972
- });
2973
- } else {
2974
- sendJSON(res, 500, {
2975
- error: {
2976
- code: -32603,
2977
- message: err instanceof Error ? err.message : String(err ?? "")
2978
- }
2979
- });
2980
- }
2981
- }
3150
+ const result = await handleInstall(installBody, config.hooks);
3151
+ sendJSON(res, result.status, result.body);
2982
3152
  return;
2983
3153
  }
2984
3154
  if (pathname === "/uninstall" && req.method === "POST") {
2985
- if (!config.hooks?.uninstall) {
2986
- sendJSON(res, 404, { error: "Uninstall handler not configured" });
2987
- return;
2988
- }
2989
3155
  let uninstallBody;
2990
3156
  try {
2991
3157
  uninstallBody = await parseJSONBody(req);
@@ -2995,53 +3161,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
2995
3161
  });
2996
3162
  return;
2997
3163
  }
2998
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
2999
- sendJSON(res, 400, {
3000
- error: {
3001
- code: -32602,
3002
- message: "Missing context (appInstallationId, workplace and app required)"
3003
- }
3004
- });
3005
- return;
3006
- }
3007
- const uninstallContext = {
3008
- env: uninstallBody.env ?? {},
3009
- workplace: uninstallBody.context.workplace,
3010
- appInstallationId: uninstallBody.context.appInstallationId,
3011
- app: uninstallBody.context.app,
3012
- invocation: uninstallBody.invocation,
3013
- log: createContextLogger()
3014
- };
3015
- const uninstallRequestConfig = {
3016
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3017
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3018
- };
3019
- try {
3020
- const uninstallHook = config.hooks.uninstall;
3021
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3022
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
3023
- return await runWithConfig(uninstallRequestConfig, async () => {
3024
- return await uninstallHandlerFn(uninstallContext);
3025
- });
3026
- });
3027
- sendJSON(res, 200, {
3028
- cleanedWebhookIds: result.cleanedWebhookIds ?? []
3029
- });
3030
- } catch (err) {
3031
- sendJSON(res, 500, {
3032
- error: {
3033
- code: -32603,
3034
- message: err instanceof Error ? err.message : String(err ?? "")
3035
- }
3036
- });
3037
- }
3164
+ const result = await handleUninstall(uninstallBody, config.hooks);
3165
+ sendJSON(res, result.status, result.body);
3038
3166
  return;
3039
3167
  }
3040
3168
  if (pathname === "/provision" && req.method === "POST") {
3041
- if (!config.hooks?.provision) {
3042
- sendJSON(res, 404, { error: "Provision handler not configured" });
3043
- return;
3044
- }
3045
3169
  let provisionBody;
3046
3170
  try {
3047
3171
  provisionBody = await parseJSONBody(req);
@@ -3051,46 +3175,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3051
3175
  });
3052
3176
  return;
3053
3177
  }
3054
- if (!provisionBody.context?.app) {
3055
- sendJSON(res, 400, {
3056
- error: { code: -32602, message: "Missing context (app required)" }
3057
- });
3058
- return;
3059
- }
3060
- const mergedEnv = {};
3061
- for (const [key, value] of Object.entries(process.env)) {
3062
- if (value !== void 0) {
3063
- mergedEnv[key] = value;
3064
- }
3065
- }
3066
- Object.assign(mergedEnv, provisionBody.env ?? {});
3067
- const provisionContext = {
3068
- env: mergedEnv,
3069
- app: provisionBody.context.app,
3070
- invocation: provisionBody.invocation,
3071
- log: createContextLogger()
3072
- };
3073
- const provisionRequestConfig = {
3074
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
3075
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
3076
- };
3077
- try {
3078
- const provisionHook = config.hooks.provision;
3079
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
3080
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
3081
- return await runWithConfig(provisionRequestConfig, async () => {
3082
- return await provisionHandler(provisionContext);
3083
- });
3084
- });
3085
- sendJSON(res, 200, result);
3086
- } catch (err) {
3087
- sendJSON(res, 500, {
3088
- error: {
3089
- code: -32603,
3090
- message: err instanceof Error ? err.message : String(err ?? "")
3091
- }
3092
- });
3093
- }
3178
+ const result = await handleProvision(provisionBody, config.hooks);
3179
+ sendJSON(res, result.status, result.body);
3094
3180
  return;
3095
3181
  }
3096
3182
  if (pathname === "/core" && req.method === "POST") {
@@ -3241,7 +3327,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3241
3327
  const finalPort = listenPort ?? port;
3242
3328
  return new Promise((resolve2, reject) => {
3243
3329
  httpServer.listen(finalPort, () => {
3244
- printStartupLog(config, tools, webhookRegistry, finalPort);
3330
+ printStartupLog(config, tools, finalPort);
3245
3331
  resolve2();
3246
3332
  });
3247
3333
  httpServer.once("error", reject);
@@ -3252,13 +3338,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
3252
3338
  }
3253
3339
 
3254
3340
  // src/server/serverless.ts
3255
- function createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
3341
+ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
3256
3342
  const headers = getDefaultHeaders(config.cors);
3343
+ const registry = config.tools;
3344
+ const webhookRegistry = config.webhooks;
3257
3345
  let hasLoggedStartup = false;
3258
3346
  return {
3259
3347
  async handler(event) {
3260
3348
  if (!hasLoggedStartup) {
3261
- printStartupLog(config, tools, webhookRegistry);
3349
+ printStartupLog(config, tools);
3262
3350
  hasLoggedStartup = true;
3263
3351
  }
3264
3352
  try {
@@ -3269,12 +3357,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3269
3357
  }
3270
3358
  if (path2.startsWith("/webhooks/") && webhookRegistry) {
3271
3359
  const handle = path2.slice("/webhooks/".length);
3272
- const webhookDef = webhookRegistry[handle];
3273
- if (!webhookDef) {
3360
+ if (!webhookRegistry[handle]) {
3274
3361
  return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
3275
3362
  }
3276
- const allowedMethods = webhookDef.methods ?? ["POST"];
3277
- if (!allowedMethods.includes(method)) {
3363
+ if (!isMethodAllowed(webhookRegistry, handle, method)) {
3278
3364
  return createResponse(405, { error: `Method ${method} not allowed` }, headers);
3279
3365
  }
3280
3366
  const rawBody = event.body ?? "";
@@ -3289,107 +3375,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3289
3375
  } else {
3290
3376
  parsedBody = rawBody;
3291
3377
  }
3292
- const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
3293
- let webhookRequest;
3294
- let webhookContext;
3295
- let requestEnv = {};
3296
- let invocation;
3297
- if (isEnvelope) {
3298
- const envelope = parsedBody;
3299
- requestEnv = envelope.env ?? {};
3300
- invocation = envelope.invocation;
3301
- let originalParsedBody = envelope.request.body;
3302
- const originalContentType = envelope.request.headers["content-type"] ?? "";
3303
- if (originalContentType.includes("application/json")) {
3304
- try {
3305
- originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
3306
- } catch {
3307
- }
3308
- }
3309
- webhookRequest = {
3310
- method: envelope.request.method,
3311
- url: envelope.request.url,
3312
- path: envelope.request.path,
3313
- headers: envelope.request.headers,
3314
- query: envelope.request.query,
3315
- body: originalParsedBody,
3316
- rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
3317
- };
3318
- const envVars = { ...process.env, ...requestEnv };
3319
- const app = envelope.context.app;
3320
- if (envelope.context.appInstallationId && envelope.context.workplace) {
3321
- webhookContext = {
3322
- env: envVars,
3323
- app,
3324
- appInstallationId: envelope.context.appInstallationId,
3325
- workplace: envelope.context.workplace,
3326
- registration: envelope.context.registration ?? {},
3327
- invocation,
3328
- log: createContextLogger()
3329
- };
3330
- } else {
3331
- webhookContext = {
3332
- env: envVars,
3333
- app,
3334
- invocation,
3335
- log: createContextLogger()
3336
- };
3337
- }
3338
- } else {
3339
- const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
3340
- const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
3341
- if (!appId || !appVersionId) {
3342
- throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
3343
- }
3344
- const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3345
- const protocol = forwardedProto ?? "https";
3346
- const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3347
- const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
3348
- const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
3349
- webhookRequest = {
3350
- method,
3351
- url: webhookUrl,
3352
- path: path2,
3353
- headers: event.headers,
3354
- query: event.queryStringParameters ?? {},
3355
- body: parsedBody,
3356
- rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
3357
- };
3358
- webhookContext = {
3359
- env: process.env,
3360
- app: { id: appId, versionId: appVersionId },
3361
- log: createContextLogger()
3362
- };
3363
- }
3364
- const originalEnv = { ...process.env };
3365
- Object.assign(process.env, requestEnv);
3366
- const requestConfig = {
3367
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3368
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3369
- };
3370
- let webhookResponse;
3371
- try {
3372
- webhookResponse = await runWithLogContext({ invocation }, async () => {
3373
- return await runWithConfig(requestConfig, async () => {
3374
- return await webhookDef.handler(webhookRequest, webhookContext);
3375
- });
3376
- });
3377
- } catch (err) {
3378
- console.error(`Webhook handler '${handle}' error:`, err);
3379
- return createResponse(500, { error: "Webhook handler error" }, headers);
3380
- } finally {
3381
- process.env = originalEnv;
3378
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3379
+ const protocol = forwardedProto ?? "https";
3380
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3381
+ const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
3382
+ const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
3383
+ const parseResult = parseWebhookRequest(
3384
+ parsedBody,
3385
+ method,
3386
+ webhookUrl,
3387
+ path2,
3388
+ event.headers,
3389
+ event.queryStringParameters ?? {},
3390
+ rawBody,
3391
+ event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
3392
+ event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
3393
+ );
3394
+ if ("error" in parseResult) {
3395
+ return createResponse(400, { error: parseResult.error }, headers);
3382
3396
  }
3397
+ const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
3383
3398
  const responseHeaders = {
3384
3399
  ...headers,
3385
- ...webhookResponse.headers
3400
+ ...result.headers
3386
3401
  };
3387
- const status = webhookResponse.status ?? 200;
3388
- const body = webhookResponse.body;
3389
3402
  return {
3390
- statusCode: status,
3403
+ statusCode: result.status,
3391
3404
  headers: responseHeaders,
3392
- body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
3405
+ body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
3393
3406
  };
3394
3407
  }
3395
3408
  if (path2 === "/core" && method === "POST") {
@@ -3525,9 +3538,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3525
3538
  }
3526
3539
  }
3527
3540
  if (path2 === "/install" && method === "POST") {
3528
- if (!config.hooks?.install) {
3529
- return createResponse(404, { error: "Install handler not configured" }, headers);
3530
- }
3531
3541
  let installBody;
3532
3542
  try {
3533
3543
  installBody = event.body ? JSON.parse(event.body) : {};
@@ -3538,68 +3548,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3538
3548
  headers
3539
3549
  );
3540
3550
  }
3541
- if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
3542
- return createResponse(
3543
- 400,
3544
- { error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
3545
- headers
3546
- );
3547
- }
3548
- const installContext = {
3549
- env: installBody.env ?? {},
3550
- workplace: installBody.context.workplace,
3551
- appInstallationId: installBody.context.appInstallationId,
3552
- app: installBody.context.app,
3553
- invocation: installBody.invocation,
3554
- log: createContextLogger()
3555
- };
3556
- const installRequestConfig = {
3557
- baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3558
- apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3559
- };
3560
- try {
3561
- const installHook = config.hooks.install;
3562
- const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
3563
- const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
3564
- return await runWithConfig(installRequestConfig, async () => {
3565
- return await installHandler(installContext);
3566
- });
3567
- });
3568
- return createResponse(
3569
- 200,
3570
- { env: result.env ?? {}, redirect: result.redirect },
3571
- headers
3572
- );
3573
- } catch (err) {
3574
- if (err instanceof InstallError) {
3575
- return createResponse(
3576
- 400,
3577
- {
3578
- error: {
3579
- code: err.code,
3580
- message: err.message,
3581
- field: err.field
3582
- }
3583
- },
3584
- headers
3585
- );
3586
- }
3587
- return createResponse(
3588
- 500,
3589
- {
3590
- error: {
3591
- code: -32603,
3592
- message: err instanceof Error ? err.message : String(err ?? "")
3593
- }
3594
- },
3595
- headers
3596
- );
3597
- }
3551
+ const result = await handleInstall(installBody, config.hooks);
3552
+ return createResponse(result.status, result.body, headers);
3598
3553
  }
3599
3554
  if (path2 === "/uninstall" && method === "POST") {
3600
- if (!config.hooks?.uninstall) {
3601
- return createResponse(404, { error: "Uninstall handler not configured" }, headers);
3602
- }
3603
3555
  let uninstallBody;
3604
3556
  try {
3605
3557
  uninstallBody = event.body ? JSON.parse(event.body) : {};
@@ -3610,137 +3562,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3610
3562
  headers
3611
3563
  );
3612
3564
  }
3613
- if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
3614
- return createResponse(
3615
- 400,
3616
- {
3617
- error: {
3618
- code: -32602,
3619
- message: "Missing context (appInstallationId, workplace and app required)"
3620
- }
3621
- },
3622
- headers
3623
- );
3624
- }
3625
- const uninstallContext = {
3626
- env: uninstallBody.env ?? {},
3627
- workplace: uninstallBody.context.workplace,
3628
- appInstallationId: uninstallBody.context.appInstallationId,
3629
- app: uninstallBody.context.app,
3630
- invocation: uninstallBody.invocation,
3631
- log: createContextLogger()
3632
- };
3633
- const uninstallRequestConfig = {
3634
- baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3635
- apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3636
- };
3637
- try {
3638
- const uninstallHook = config.hooks.uninstall;
3639
- const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3640
- const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
3641
- return await runWithConfig(uninstallRequestConfig, async () => {
3642
- return await uninstallHandlerFn(uninstallContext);
3643
- });
3644
- });
3645
- return createResponse(
3646
- 200,
3647
- { cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
3648
- headers
3649
- );
3650
- } catch (err) {
3651
- return createResponse(
3652
- 500,
3653
- {
3654
- error: {
3655
- code: -32603,
3656
- message: err instanceof Error ? err.message : String(err ?? "")
3657
- }
3658
- },
3659
- headers
3660
- );
3661
- }
3565
+ const result = await handleUninstall(uninstallBody, config.hooks);
3566
+ return createResponse(result.status, result.body, headers);
3662
3567
  }
3663
3568
  if (path2 === "/provision" && method === "POST") {
3664
- console.log("[serverless] /provision endpoint called");
3665
- if (!config.hooks?.provision) {
3666
- console.log("[serverless] No provision handler configured");
3667
- return createResponse(404, { error: "Provision handler not configured" }, headers);
3668
- }
3669
3569
  let provisionBody;
3670
3570
  try {
3671
3571
  provisionBody = event.body ? JSON.parse(event.body) : {};
3672
- console.log("[serverless] Provision body parsed:", {
3673
- hasEnv: !!provisionBody.env,
3674
- hasContext: !!provisionBody.context,
3675
- appId: provisionBody.context?.app?.id,
3676
- versionId: provisionBody.context?.app?.versionId
3677
- });
3678
3572
  } catch {
3679
- console.log("[serverless] Failed to parse provision body");
3680
3573
  return createResponse(
3681
3574
  400,
3682
3575
  { error: { code: -32700, message: "Parse error" } },
3683
3576
  headers
3684
3577
  );
3685
3578
  }
3686
- if (!provisionBody.context?.app) {
3687
- console.log("[serverless] Missing app context in provision body");
3688
- return createResponse(
3689
- 400,
3690
- { error: { code: -32602, message: "Missing context (app required)" } },
3691
- headers
3692
- );
3693
- }
3694
- const mergedEnv = {};
3695
- for (const [key, value] of Object.entries(process.env)) {
3696
- if (value !== void 0) {
3697
- mergedEnv[key] = value;
3698
- }
3699
- }
3700
- Object.assign(mergedEnv, provisionBody.env ?? {});
3701
- const provisionContext = {
3702
- env: mergedEnv,
3703
- app: provisionBody.context.app,
3704
- invocation: provisionBody.invocation,
3705
- log: createContextLogger()
3706
- };
3707
- const provisionRequestConfig = {
3708
- baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
3709
- apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
3710
- };
3711
- console.log("[serverless] Calling provision handler...");
3712
- try {
3713
- const provisionHook = config.hooks.provision;
3714
- const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
3715
- const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
3716
- return await runWithConfig(provisionRequestConfig, async () => {
3717
- return await provisionHandler(provisionContext);
3718
- });
3719
- });
3720
- console.log("[serverless] Provision handler completed successfully");
3721
- return createResponse(200, result, headers);
3722
- } catch (err) {
3723
- console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
3724
- return createResponse(
3725
- 500,
3726
- {
3727
- error: {
3728
- code: -32603,
3729
- message: err instanceof Error ? err.message : String(err ?? "")
3730
- }
3731
- },
3732
- headers
3733
- );
3734
- }
3579
+ const result = await handleProvision(provisionBody, config.hooks);
3580
+ return createResponse(result.status, result.body, headers);
3735
3581
  }
3736
3582
  if (path2 === "/oauth_callback" && method === "POST") {
3737
- if (!config.hooks?.oauth_callback) {
3738
- return createResponse(
3739
- 404,
3740
- { error: "OAuth callback handler not configured" },
3741
- headers
3742
- );
3743
- }
3744
3583
  let parsedBody;
3745
3584
  try {
3746
3585
  parsedBody = event.body ? JSON.parse(event.body) : {};
@@ -3752,70 +3591,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
3752
3591
  headers
3753
3592
  );
3754
3593
  }
3755
- const envelope = parseHandlerEnvelope(parsedBody);
3756
- if (!envelope) {
3757
- console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
3758
- return createResponse(
3759
- 400,
3760
- { error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
3761
- headers
3762
- );
3763
- }
3764
- const invocation = parsedBody.invocation;
3765
- const oauthRequest = buildRequestFromRaw(envelope.request);
3766
- const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
3767
- const oauthCallbackContext = {
3768
- request: oauthRequest,
3769
- invocation,
3770
- log: createContextLogger()
3771
- };
3772
- try {
3773
- const oauthCallbackHook = config.hooks.oauth_callback;
3774
- const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
3775
- const result = await runWithLogContext({ invocation }, async () => {
3776
- return await runWithConfig(oauthCallbackRequestConfig, async () => {
3777
- return await oauthCallbackHandler(oauthCallbackContext);
3778
- });
3779
- });
3780
- return createResponse(
3781
- 200,
3782
- {
3783
- appInstallationId: result.appInstallationId,
3784
- env: result.env ?? {}
3785
- },
3786
- headers
3787
- );
3788
- } catch (err) {
3789
- const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
3790
- return createResponse(
3791
- 500,
3792
- {
3793
- error: {
3794
- code: -32603,
3795
- message: errorMessage
3796
- }
3797
- },
3798
- headers
3799
- );
3800
- }
3594
+ const result = await handleOAuthCallback(parsedBody, config.hooks);
3595
+ return createResponse(result.status, result.body, headers);
3801
3596
  }
3802
3597
  if (path2 === "/health" && method === "GET") {
3803
3598
  return createResponse(200, state.getHealthStatus(), headers);
3804
3599
  }
3805
3600
  if (path2 === "/config" && method === "GET") {
3806
- let appConfig = config.appConfig;
3807
- if (!appConfig && config.appConfigLoader) {
3808
- const loaded = await config.appConfigLoader();
3809
- appConfig = loaded.default;
3810
- }
3811
- if (!appConfig) {
3812
- appConfig = createMinimalConfig(
3813
- config.metadata.name,
3814
- config.metadata.version
3815
- );
3816
- }
3817
- const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
3818
- return createResponse(200, serializedConfig, headers);
3601
+ return createResponse(200, serializeConfig(config), headers);
3819
3602
  }
3820
3603
  if (path2 === "/mcp" && method === "POST") {
3821
3604
  let body;
@@ -4033,9 +3816,11 @@ console.log("[skedyul-node/server] All imports complete");
4033
3816
  console.log("[skedyul-node/server] Installing context logger...");
4034
3817
  installContextLogger();
4035
3818
  console.log("[skedyul-node/server] Context logger installed");
4036
- function createSkedyulServer(config, registry, webhookRegistry) {
3819
+ function createSkedyulServer(config) {
4037
3820
  console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
4038
3821
  mergeRuntimeEnv();
3822
+ const registry = config.tools;
3823
+ const webhookRegistry = config.webhooks;
4039
3824
  console.log("[createSkedyulServer] Step 2: coreApi setup");
4040
3825
  if (config.coreApi?.service) {
4041
3826
  coreApiService.register(config.coreApi.service);
@@ -4047,7 +3832,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4047
3832
  const tools = buildToolMetadata(registry);
4048
3833
  console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
4049
3834
  const toolNames = Object.values(registry).map((tool) => tool.name);
4050
- const runtimeLabel = config.computeLayer;
3835
+ const runtimeLabel = config.computeLayer ?? "serverless";
4051
3836
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
4052
3837
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
4053
3838
  console.log("[createSkedyulServer] Step 4: createRequestState()");
@@ -4060,8 +3845,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4060
3845
  console.log("[createSkedyulServer] Step 4 done");
4061
3846
  console.log("[createSkedyulServer] Step 5: new McpServer()");
4062
3847
  const mcpServer = new import_mcp.McpServer({
4063
- name: config.metadata.name,
4064
- version: config.metadata.version
3848
+ name: config.name,
3849
+ version: config.version ?? "0.0.0"
4065
3850
  });
4066
3851
  console.log("[createSkedyulServer] Step 5 done");
4067
3852
  const dedicatedShutdown = () => {
@@ -4189,13 +3974,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
4189
3974
  tools,
4190
3975
  callTool,
4191
3976
  state,
4192
- mcpServer,
4193
- registry,
4194
- webhookRegistry
3977
+ mcpServer
4195
3978
  );
4196
3979
  }
4197
3980
  console.log("[createSkedyulServer] Creating serverless instance");
4198
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
3981
+ const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
4199
3982
  console.log("[createSkedyulServer] Serverless instance created successfully");
4200
3983
  return serverlessInstance;
4201
3984
  }
@@ -4517,7 +4300,6 @@ var index_default = { z: import_v44.z };
4517
4300
  communicationChannel,
4518
4301
  configure,
4519
4302
  createContextLogger,
4520
- createMinimalConfig,
4521
4303
  createServerHookContext,
4522
4304
  createToolCallContext,
4523
4305
  createWebhookContext,
@@ -4543,7 +4325,6 @@ var index_default = { z: import_v44.z };
4543
4325
  isWorkflowDependency,
4544
4326
  loadConfig,
4545
4327
  report,
4546
- resolveConfig,
4547
4328
  resource,
4548
4329
  runWithConfig,
4549
4330
  safeParseConfig,