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.
- package/dist/cli/index.js +512 -731
- package/dist/config/app-config.d.ts +26 -2
- package/dist/config/index.d.ts +1 -2
- package/dist/config/types/env.d.ts +8 -2
- package/dist/config/types/form.d.ts +10 -6
- package/dist/config/types/index.d.ts +0 -1
- package/dist/config/types/page.d.ts +2 -2
- package/dist/config/types/webhook.d.ts +2 -1
- package/dist/dedicated/server.js +503 -766
- package/dist/esm/index.mjs +503 -720
- package/dist/index.d.ts +2 -3
- package/dist/index.js +503 -722
- package/dist/server/config-serializer.d.ts +12 -0
- package/dist/server/dedicated.d.ts +3 -2
- package/dist/server/handlers/index.d.ts +12 -0
- package/dist/server/handlers/install-handler.d.ts +9 -0
- package/dist/server/handlers/oauth-callback-handler.d.ts +9 -0
- package/dist/server/handlers/provision-handler.d.ts +9 -0
- package/dist/server/handlers/types.d.ts +101 -0
- package/dist/server/handlers/uninstall-handler.d.ts +9 -0
- package/dist/server/handlers/webhook-handler.d.ts +28 -0
- package/dist/server/index.d.ts +15 -6
- package/dist/server/serverless.d.ts +3 -2
- package/dist/server/startup-logger.d.ts +3 -2
- package/dist/server/tool-handler.d.ts +1 -1
- package/dist/server/utils/http.d.ts +3 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +503 -766
- package/dist/serverless/server.mjs +503 -744
- package/dist/types/handlers.d.ts +6 -24
- package/dist/types/index.d.ts +4 -4
- package/dist/types/server.d.ts +1 -34
- package/dist/types/shared.d.ts +5 -0
- package/dist/types/tool.d.ts +5 -7
- package/dist/types/webhook.d.ts +14 -6
- package/package.json +1 -1
- package/dist/config/resolve.d.ts +0 -27
- 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(
|
|
2250
|
-
const toolName = String(
|
|
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 =
|
|
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,
|
|
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.
|
|
2583
|
-
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.
|
|
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
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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
|
-
|
|
2632
|
-
|
|
2633
|
-
if (
|
|
2634
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
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
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
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
|
|
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
|
-
|
|
2680
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2721
|
-
if (!webhookDef) {
|
|
3034
|
+
if (!webhookRegistry[handle]) {
|
|
2722
3035
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
2723
3036
|
return;
|
|
2724
3037
|
}
|
|
2725
|
-
|
|
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
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
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
|
|
3075
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
2817
3076
|
const responseHeaders = {
|
|
2818
|
-
...
|
|
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 (
|
|
2825
|
-
if (typeof
|
|
2826
|
-
res.end(
|
|
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(
|
|
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
|
|
2882
|
-
|
|
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
|
-
|
|
2935
|
-
|
|
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
|
-
|
|
2999
|
-
|
|
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
|
-
|
|
3055
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
3273
|
-
if (!webhookDef) {
|
|
3360
|
+
if (!webhookRegistry[handle]) {
|
|
3274
3361
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
3275
3362
|
}
|
|
3276
|
-
|
|
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
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
3542
|
-
|
|
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
|
-
|
|
3614
|
-
|
|
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
|
-
|
|
3687
|
-
|
|
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
|
|
3756
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
4064
|
-
version: config.
|
|
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
|
|
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,
|