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