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/server.js
CHANGED
|
@@ -30,32 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/server.ts
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
|
-
buildRequestFromRaw: () => buildRequestFromRaw,
|
|
34
|
-
buildRequestScopedConfig: () => buildRequestScopedConfig,
|
|
35
|
-
buildToolMetadata: () => buildToolMetadata,
|
|
36
|
-
createCallToolHandler: () => createCallToolHandler,
|
|
37
|
-
createDedicatedServerInstance: () => createDedicatedServerInstance,
|
|
38
|
-
createRequestState: () => createRequestState,
|
|
39
|
-
createResponse: () => createResponse,
|
|
40
|
-
createServerlessInstance: () => createServerlessInstance,
|
|
41
33
|
createSkedyulServer: () => createSkedyulServer,
|
|
42
|
-
getDefaultHeaders: () => getDefaultHeaders,
|
|
43
34
|
getJsonSchemaFromToolSchema: () => getJsonSchemaFromToolSchema,
|
|
44
|
-
getListeningPort: () => getListeningPort,
|
|
45
35
|
getZodSchema: () => getZodSchema,
|
|
46
|
-
handleCoreMethod: () => handleCoreMethod,
|
|
47
36
|
isToolSchemaWithJson: () => isToolSchemaWithJson,
|
|
48
|
-
mergeRuntimeEnv: () => mergeRuntimeEnv,
|
|
49
|
-
normalizeBilling: () => normalizeBilling,
|
|
50
|
-
padEnd: () => padEnd,
|
|
51
|
-
parseHandlerEnvelope: () => parseHandlerEnvelope,
|
|
52
|
-
parseJSONBody: () => parseJSONBody,
|
|
53
|
-
parseJsonRecord: () => parseJsonRecord,
|
|
54
|
-
parseNumberEnv: () => parseNumberEnv,
|
|
55
|
-
printStartupLog: () => printStartupLog,
|
|
56
|
-
readRawRequestBody: () => readRawRequestBody,
|
|
57
|
-
sendHTML: () => sendHTML,
|
|
58
|
-
sendJSON: () => sendJSON,
|
|
59
37
|
server: () => server,
|
|
60
38
|
toJsonSchema: () => toJsonSchema
|
|
61
39
|
});
|
|
@@ -203,10 +181,6 @@ function sendJSON(res, statusCode, data) {
|
|
|
203
181
|
res.writeHead(statusCode, { "Content-Type": "application/json" });
|
|
204
182
|
res.end(JSON.stringify(data));
|
|
205
183
|
}
|
|
206
|
-
function sendHTML(res, statusCode, html) {
|
|
207
|
-
res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
|
|
208
|
-
res.end(html);
|
|
209
|
-
}
|
|
210
184
|
function getDefaultHeaders(options) {
|
|
211
185
|
return {
|
|
212
186
|
"Content-Type": "application/json",
|
|
@@ -392,8 +366,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
392
366
|
};
|
|
393
367
|
}
|
|
394
368
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
395
|
-
return async function callTool(
|
|
396
|
-
const toolName = String(
|
|
369
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
370
|
+
const toolName = String(toolNameInput);
|
|
397
371
|
const tool = registry[toolName];
|
|
398
372
|
if (!tool) {
|
|
399
373
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -402,7 +376,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
402
376
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
403
377
|
}
|
|
404
378
|
const fn = tool.handler;
|
|
405
|
-
const args =
|
|
379
|
+
const args = toolArgsInput ?? {};
|
|
406
380
|
const estimateMode = args.estimate === true;
|
|
407
381
|
if (!estimateMode) {
|
|
408
382
|
state.incrementRequestCount();
|
|
@@ -657,51 +631,6 @@ async function handleCoreMethod(method, params) {
|
|
|
657
631
|
};
|
|
658
632
|
}
|
|
659
633
|
|
|
660
|
-
// src/server/handler-helpers.ts
|
|
661
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
662
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
const envelope = parsedBody;
|
|
666
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
667
|
-
return null;
|
|
668
|
-
}
|
|
669
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
670
|
-
return null;
|
|
671
|
-
}
|
|
672
|
-
return {
|
|
673
|
-
env: envelope.env,
|
|
674
|
-
request: envelope.request,
|
|
675
|
-
context: envelope.context
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
function buildRequestFromRaw(raw) {
|
|
679
|
-
let parsedBody = raw.body;
|
|
680
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
681
|
-
if (contentType.includes("application/json")) {
|
|
682
|
-
try {
|
|
683
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
684
|
-
} catch {
|
|
685
|
-
parsedBody = raw.body;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return {
|
|
689
|
-
method: raw.method,
|
|
690
|
-
url: raw.url,
|
|
691
|
-
path: raw.path,
|
|
692
|
-
headers: raw.headers,
|
|
693
|
-
query: raw.query,
|
|
694
|
-
body: parsedBody,
|
|
695
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
function buildRequestScopedConfig(env) {
|
|
699
|
-
return {
|
|
700
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
701
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
634
|
// src/server/startup-logger.ts
|
|
706
635
|
function padEnd(str, length) {
|
|
707
636
|
if (str.length >= length) {
|
|
@@ -709,10 +638,11 @@ function padEnd(str, length) {
|
|
|
709
638
|
}
|
|
710
639
|
return str + " ".repeat(length - str.length);
|
|
711
640
|
}
|
|
712
|
-
function printStartupLog(config, tools,
|
|
641
|
+
function printStartupLog(config, tools, port) {
|
|
713
642
|
if (process.env.NODE_ENV === "test") {
|
|
714
643
|
return;
|
|
715
644
|
}
|
|
645
|
+
const webhookRegistry = config.webhooks;
|
|
716
646
|
const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
|
|
717
647
|
const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
|
|
718
648
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
@@ -725,9 +655,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
725
655
|
console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
|
|
726
656
|
console.log(`\u2560${divider}\u2563`);
|
|
727
657
|
console.log(`\u2551 \u2551`);
|
|
728
|
-
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.
|
|
729
|
-
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.
|
|
730
|
-
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
|
|
658
|
+
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
|
|
659
|
+
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
|
|
660
|
+
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
|
|
731
661
|
if (port) {
|
|
732
662
|
console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
|
|
733
663
|
}
|
|
@@ -769,67 +699,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
769
699
|
console.log("");
|
|
770
700
|
}
|
|
771
701
|
|
|
772
|
-
// src/config
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
702
|
+
// src/server/config-serializer.ts
|
|
703
|
+
function serializeConfig(config) {
|
|
704
|
+
const registry = config.tools;
|
|
705
|
+
const webhookRegistry = config.webhooks;
|
|
706
|
+
return {
|
|
707
|
+
name: config.name,
|
|
708
|
+
version: config.version,
|
|
709
|
+
description: config.description,
|
|
710
|
+
computeLayer: config.computeLayer,
|
|
711
|
+
tools: registry ? Object.entries(registry).map(([key, tool]) => ({
|
|
712
|
+
name: tool.name || key,
|
|
713
|
+
description: tool.description,
|
|
714
|
+
timeout: tool.timeout,
|
|
715
|
+
retries: tool.retries
|
|
716
|
+
})) : [],
|
|
717
|
+
webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
|
|
718
|
+
name: w.name,
|
|
719
|
+
description: w.description,
|
|
720
|
+
methods: w.methods ?? ["POST"],
|
|
721
|
+
type: w.type ?? "WEBHOOK"
|
|
722
|
+
})) : [],
|
|
723
|
+
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
724
|
+
agents: config.agents
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function isProvisionConfig(value) {
|
|
728
|
+
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/server/handlers/install-handler.ts
|
|
732
|
+
async function handleInstall(body, hooks) {
|
|
733
|
+
if (!hooks?.install) {
|
|
734
|
+
return {
|
|
735
|
+
status: 404,
|
|
736
|
+
body: { error: "Install handler not configured" }
|
|
737
|
+
};
|
|
776
738
|
}
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
739
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
740
|
+
return {
|
|
741
|
+
status: 400,
|
|
742
|
+
body: {
|
|
743
|
+
error: {
|
|
744
|
+
code: -32602,
|
|
745
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const installContext = {
|
|
751
|
+
env: body.env ?? {},
|
|
752
|
+
workplace: body.context.workplace,
|
|
753
|
+
appInstallationId: body.context.appInstallationId,
|
|
754
|
+
app: body.context.app,
|
|
755
|
+
invocation: body.invocation,
|
|
756
|
+
log: createContextLogger()
|
|
757
|
+
};
|
|
758
|
+
const requestConfig = {
|
|
759
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
760
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
761
|
+
};
|
|
762
|
+
try {
|
|
763
|
+
const installHook = hooks.install;
|
|
764
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
765
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
766
|
+
return await runWithConfig(requestConfig, async () => {
|
|
767
|
+
return await installHandler(installContext);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
return {
|
|
771
|
+
status: 200,
|
|
772
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
773
|
+
};
|
|
774
|
+
} catch (err) {
|
|
775
|
+
if (err instanceof InstallError) {
|
|
776
|
+
return {
|
|
777
|
+
status: 400,
|
|
778
|
+
body: {
|
|
779
|
+
error: {
|
|
780
|
+
code: err.code,
|
|
781
|
+
message: err.message,
|
|
782
|
+
field: err.field
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
};
|
|
781
786
|
}
|
|
782
|
-
return
|
|
787
|
+
return {
|
|
788
|
+
status: 500,
|
|
789
|
+
body: {
|
|
790
|
+
error: {
|
|
791
|
+
code: -32603,
|
|
792
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
};
|
|
783
796
|
}
|
|
784
|
-
return value;
|
|
785
797
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
798
|
+
|
|
799
|
+
// src/server/handlers/uninstall-handler.ts
|
|
800
|
+
async function handleUninstall(body, hooks) {
|
|
801
|
+
if (!hooks?.uninstall) {
|
|
802
|
+
return {
|
|
803
|
+
status: 404,
|
|
804
|
+
body: { error: "Uninstall handler not configured" }
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
808
|
+
return {
|
|
809
|
+
status: 400,
|
|
810
|
+
body: {
|
|
811
|
+
error: {
|
|
812
|
+
code: -32602,
|
|
813
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
const uninstallContext = {
|
|
819
|
+
env: body.env ?? {},
|
|
820
|
+
workplace: body.context.workplace,
|
|
821
|
+
appInstallationId: body.context.appInstallationId,
|
|
822
|
+
app: body.context.app,
|
|
823
|
+
invocation: body.invocation,
|
|
824
|
+
log: createContextLogger()
|
|
825
|
+
};
|
|
826
|
+
const requestConfig = {
|
|
827
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
828
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
829
|
+
};
|
|
830
|
+
try {
|
|
831
|
+
const uninstallHook = hooks.uninstall;
|
|
832
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
833
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
834
|
+
return await runWithConfig(requestConfig, async () => {
|
|
835
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
status: 200,
|
|
840
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
841
|
+
};
|
|
842
|
+
} catch (err) {
|
|
843
|
+
return {
|
|
844
|
+
status: 500,
|
|
845
|
+
body: {
|
|
846
|
+
error: {
|
|
847
|
+
code: -32603,
|
|
848
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
794
853
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
854
|
+
|
|
855
|
+
// src/server/handlers/provision-handler.ts
|
|
856
|
+
async function handleProvision(body, hooks) {
|
|
857
|
+
if (!hooks?.provision) {
|
|
858
|
+
return {
|
|
859
|
+
status: 404,
|
|
860
|
+
body: { error: "Provision handler not configured" }
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
if (!body.context?.app) {
|
|
864
|
+
return {
|
|
865
|
+
status: 400,
|
|
866
|
+
body: {
|
|
867
|
+
error: {
|
|
868
|
+
code: -32602,
|
|
869
|
+
message: "Missing context (app required)"
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
const mergedEnv = {};
|
|
875
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
876
|
+
if (value !== void 0) {
|
|
877
|
+
mergedEnv[key] = value;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
881
|
+
const provisionContext = {
|
|
882
|
+
env: mergedEnv,
|
|
883
|
+
app: body.context.app,
|
|
884
|
+
invocation: body.invocation,
|
|
885
|
+
log: createContextLogger()
|
|
886
|
+
};
|
|
887
|
+
const requestConfig = {
|
|
888
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
889
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
890
|
+
};
|
|
891
|
+
try {
|
|
892
|
+
const provisionHook = hooks.provision;
|
|
893
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
894
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
895
|
+
return await runWithConfig(requestConfig, async () => {
|
|
896
|
+
return await provisionHandler(provisionContext);
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
return {
|
|
900
|
+
status: 200,
|
|
901
|
+
body: result
|
|
902
|
+
};
|
|
903
|
+
} catch (err) {
|
|
904
|
+
return {
|
|
905
|
+
status: 500,
|
|
906
|
+
body: {
|
|
907
|
+
error: {
|
|
908
|
+
code: -32603,
|
|
909
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
802
914
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
915
|
+
|
|
916
|
+
// src/server/handler-helpers.ts
|
|
917
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
918
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
const envelope = parsedBody;
|
|
922
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
812
928
|
return {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
929
|
+
env: envelope.env,
|
|
930
|
+
request: envelope.request,
|
|
931
|
+
context: envelope.context
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function buildRequestFromRaw(raw) {
|
|
935
|
+
let parsedBody = raw.body;
|
|
936
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
937
|
+
if (contentType.includes("application/json")) {
|
|
938
|
+
try {
|
|
939
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
940
|
+
} catch {
|
|
941
|
+
parsedBody = raw.body;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
method: raw.method,
|
|
946
|
+
url: raw.url,
|
|
947
|
+
path: raw.path,
|
|
948
|
+
headers: raw.headers,
|
|
949
|
+
query: raw.query,
|
|
950
|
+
body: parsedBody,
|
|
951
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function buildRequestScopedConfig(env) {
|
|
955
|
+
return {
|
|
956
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
957
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
962
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
963
|
+
if (!hooks?.oauth_callback) {
|
|
964
|
+
return {
|
|
965
|
+
status: 404,
|
|
966
|
+
body: { error: "OAuth callback handler not configured" }
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
970
|
+
if (!envelope) {
|
|
971
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
972
|
+
return {
|
|
973
|
+
status: 400,
|
|
974
|
+
body: {
|
|
975
|
+
error: {
|
|
976
|
+
code: -32602,
|
|
977
|
+
message: "Missing envelope format: expected { env, request }"
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const invocation = parsedBody.invocation;
|
|
983
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
984
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
985
|
+
const oauthCallbackContext = {
|
|
986
|
+
request: oauthRequest,
|
|
987
|
+
invocation,
|
|
988
|
+
log: createContextLogger()
|
|
989
|
+
};
|
|
990
|
+
try {
|
|
991
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
992
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
993
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
994
|
+
return await runWithConfig(requestConfig, async () => {
|
|
995
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
return {
|
|
999
|
+
status: 200,
|
|
1000
|
+
body: {
|
|
1001
|
+
appInstallationId: result.appInstallationId,
|
|
1002
|
+
env: result.env ?? {}
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
1007
|
+
return {
|
|
1008
|
+
status: 500,
|
|
1009
|
+
body: {
|
|
1010
|
+
error: {
|
|
1011
|
+
code: -32603,
|
|
1012
|
+
message: errorMessage
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/server/handlers/webhook-handler.ts
|
|
1020
|
+
function parseWebhookRequest(parsedBody, method, url, path, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
1021
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
1022
|
+
if (isEnvelope) {
|
|
1023
|
+
const envelope = parsedBody;
|
|
1024
|
+
const requestEnv = envelope.env ?? {};
|
|
1025
|
+
const invocation = envelope.invocation;
|
|
1026
|
+
let originalParsedBody = envelope.request.body;
|
|
1027
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
1028
|
+
if (originalContentType.includes("application/json")) {
|
|
1029
|
+
try {
|
|
1030
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
const webhookRequest2 = {
|
|
1035
|
+
method: envelope.request.method,
|
|
1036
|
+
url: envelope.request.url,
|
|
1037
|
+
path: envelope.request.path,
|
|
1038
|
+
headers: envelope.request.headers,
|
|
1039
|
+
query: envelope.request.query,
|
|
1040
|
+
body: originalParsedBody,
|
|
1041
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1042
|
+
};
|
|
1043
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
1044
|
+
const app = envelope.context.app;
|
|
1045
|
+
let webhookContext2;
|
|
1046
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1047
|
+
webhookContext2 = {
|
|
1048
|
+
env: envVars,
|
|
1049
|
+
app,
|
|
1050
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
1051
|
+
workplace: envelope.context.workplace,
|
|
1052
|
+
registration: envelope.context.registration ?? {},
|
|
1053
|
+
invocation,
|
|
1054
|
+
log: createContextLogger()
|
|
1055
|
+
};
|
|
1056
|
+
} else {
|
|
1057
|
+
webhookContext2 = {
|
|
1058
|
+
env: envVars,
|
|
1059
|
+
app,
|
|
1060
|
+
invocation,
|
|
1061
|
+
log: createContextLogger()
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
1065
|
+
}
|
|
1066
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
1067
|
+
return {
|
|
1068
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
const webhookRequest = {
|
|
1072
|
+
method,
|
|
1073
|
+
url,
|
|
1074
|
+
path,
|
|
1075
|
+
headers,
|
|
1076
|
+
query,
|
|
1077
|
+
body: parsedBody,
|
|
1078
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1079
|
+
};
|
|
1080
|
+
const webhookContext = {
|
|
1081
|
+
env: process.env,
|
|
1082
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
1083
|
+
log: createContextLogger()
|
|
821
1084
|
};
|
|
1085
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
822
1086
|
}
|
|
823
|
-
function
|
|
1087
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
1088
|
+
const webhookDef = webhookRegistry[handle];
|
|
1089
|
+
if (!webhookDef) {
|
|
1090
|
+
return {
|
|
1091
|
+
status: 404,
|
|
1092
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
const originalEnv = { ...process.env };
|
|
1096
|
+
Object.assign(process.env, data.requestEnv);
|
|
1097
|
+
const requestConfig = {
|
|
1098
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1099
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1100
|
+
};
|
|
1101
|
+
let webhookResponse;
|
|
1102
|
+
try {
|
|
1103
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
1104
|
+
return await runWithConfig(requestConfig, async () => {
|
|
1105
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1110
|
+
return {
|
|
1111
|
+
status: 500,
|
|
1112
|
+
body: { error: "Webhook handler error" }
|
|
1113
|
+
};
|
|
1114
|
+
} finally {
|
|
1115
|
+
process.env = originalEnv;
|
|
1116
|
+
}
|
|
824
1117
|
return {
|
|
825
|
-
|
|
826
|
-
|
|
1118
|
+
status: webhookResponse.status ?? 200,
|
|
1119
|
+
body: webhookResponse.body,
|
|
1120
|
+
headers: webhookResponse.headers
|
|
827
1121
|
};
|
|
828
1122
|
}
|
|
1123
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
1124
|
+
const webhookDef = webhookRegistry[handle];
|
|
1125
|
+
if (!webhookDef) return false;
|
|
1126
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
1127
|
+
return allowedMethods.includes(method);
|
|
1128
|
+
}
|
|
829
1129
|
|
|
830
1130
|
// src/server/dedicated.ts
|
|
831
|
-
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
1131
|
+
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
832
1132
|
const port = getListeningPort(config);
|
|
1133
|
+
const registry = config.tools;
|
|
1134
|
+
const webhookRegistry = config.webhooks;
|
|
833
1135
|
const httpServer = import_http2.default.createServer(
|
|
834
1136
|
async (req, res) => {
|
|
835
1137
|
function sendCoreResult(result) {
|
|
@@ -846,30 +1148,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
846
1148
|
return;
|
|
847
1149
|
}
|
|
848
1150
|
if (pathname === "/config" && req.method === "GET") {
|
|
849
|
-
|
|
850
|
-
if (!appConfig && config.appConfigLoader) {
|
|
851
|
-
const loaded = await config.appConfigLoader();
|
|
852
|
-
appConfig = loaded.default;
|
|
853
|
-
}
|
|
854
|
-
if (!appConfig) {
|
|
855
|
-
appConfig = createMinimalConfig(
|
|
856
|
-
config.metadata.name,
|
|
857
|
-
config.metadata.version
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
861
|
-
sendJSON(res, 200, serializedConfig);
|
|
1151
|
+
sendJSON(res, 200, serializeConfig(config));
|
|
862
1152
|
return;
|
|
863
1153
|
}
|
|
864
1154
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
865
1155
|
const handle = pathname.slice("/webhooks/".length);
|
|
866
|
-
|
|
867
|
-
if (!webhookDef) {
|
|
1156
|
+
if (!webhookRegistry[handle]) {
|
|
868
1157
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
869
1158
|
return;
|
|
870
1159
|
}
|
|
871
|
-
|
|
872
|
-
if (!allowedMethods.includes(req.method)) {
|
|
1160
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
873
1161
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
874
1162
|
return;
|
|
875
1163
|
}
|
|
@@ -891,87 +1179,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
891
1179
|
} else {
|
|
892
1180
|
parsedBody = rawBody;
|
|
893
1181
|
}
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
webhookContext = {
|
|
908
|
-
env: envVars,
|
|
909
|
-
app,
|
|
910
|
-
appInstallationId: context.appInstallationId,
|
|
911
|
-
workplace: context.workplace,
|
|
912
|
-
registration: context.registration ?? {},
|
|
913
|
-
invocation,
|
|
914
|
-
log: createContextLogger()
|
|
915
|
-
};
|
|
916
|
-
} else {
|
|
917
|
-
webhookContext = {
|
|
918
|
-
env: envVars,
|
|
919
|
-
app,
|
|
920
|
-
invocation,
|
|
921
|
-
log: createContextLogger()
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
} else {
|
|
925
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
926
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
927
|
-
if (!appId || !appVersionId) {
|
|
928
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
929
|
-
}
|
|
930
|
-
webhookRequest = {
|
|
931
|
-
method: req.method ?? "POST",
|
|
932
|
-
url: url.toString(),
|
|
933
|
-
path: pathname,
|
|
934
|
-
headers: req.headers,
|
|
935
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
936
|
-
body: parsedBody,
|
|
937
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
938
|
-
};
|
|
939
|
-
webhookContext = {
|
|
940
|
-
env: process.env,
|
|
941
|
-
app: { id: appId, versionId: appVersionId },
|
|
942
|
-
log: createContextLogger()
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
const originalEnv = { ...process.env };
|
|
946
|
-
Object.assign(process.env, requestEnv);
|
|
947
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
948
|
-
let webhookResponse;
|
|
949
|
-
try {
|
|
950
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
951
|
-
return await runWithConfig(requestConfig, async () => {
|
|
952
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
953
|
-
});
|
|
954
|
-
});
|
|
955
|
-
} catch (err) {
|
|
956
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
957
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
1182
|
+
const parseResult = parseWebhookRequest(
|
|
1183
|
+
parsedBody,
|
|
1184
|
+
req.method ?? "POST",
|
|
1185
|
+
url.toString(),
|
|
1186
|
+
pathname,
|
|
1187
|
+
req.headers,
|
|
1188
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
1189
|
+
rawBody,
|
|
1190
|
+
req.headers["x-skedyul-app-id"],
|
|
1191
|
+
req.headers["x-skedyul-app-version-id"]
|
|
1192
|
+
);
|
|
1193
|
+
if ("error" in parseResult) {
|
|
1194
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
958
1195
|
return;
|
|
959
|
-
} finally {
|
|
960
|
-
process.env = originalEnv;
|
|
961
1196
|
}
|
|
962
|
-
const
|
|
1197
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
963
1198
|
const responseHeaders = {
|
|
964
|
-
...
|
|
1199
|
+
...result.headers
|
|
965
1200
|
};
|
|
966
1201
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
967
1202
|
responseHeaders["Content-Type"] = "application/json";
|
|
968
1203
|
}
|
|
969
|
-
res.writeHead(status, responseHeaders);
|
|
970
|
-
if (
|
|
971
|
-
if (typeof
|
|
972
|
-
res.end(
|
|
1204
|
+
res.writeHead(result.status, responseHeaders);
|
|
1205
|
+
if (result.body !== void 0) {
|
|
1206
|
+
if (typeof result.body === "string") {
|
|
1207
|
+
res.end(result.body);
|
|
973
1208
|
} else {
|
|
974
|
-
res.end(JSON.stringify(
|
|
1209
|
+
res.end(JSON.stringify(result.body));
|
|
975
1210
|
}
|
|
976
1211
|
} else {
|
|
977
1212
|
res.end();
|
|
@@ -1010,10 +1245,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1010
1245
|
return;
|
|
1011
1246
|
}
|
|
1012
1247
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
1013
|
-
if (!config.hooks?.oauth_callback) {
|
|
1014
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
1248
|
let parsedBody;
|
|
1018
1249
|
try {
|
|
1019
1250
|
parsedBody = await parseJSONBody(req);
|
|
@@ -1024,50 +1255,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1024
1255
|
});
|
|
1025
1256
|
return;
|
|
1026
1257
|
}
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
1030
|
-
sendJSON(res, 400, {
|
|
1031
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
1032
|
-
});
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
const invocation = parsedBody.invocation;
|
|
1036
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
1037
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
1038
|
-
const oauthCallbackContext = {
|
|
1039
|
-
request: oauthRequest,
|
|
1040
|
-
invocation,
|
|
1041
|
-
log: createContextLogger()
|
|
1042
|
-
};
|
|
1043
|
-
try {
|
|
1044
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
1045
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
1046
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
1047
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
1048
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
1049
|
-
});
|
|
1050
|
-
});
|
|
1051
|
-
sendJSON(res, 200, {
|
|
1052
|
-
appInstallationId: result.appInstallationId,
|
|
1053
|
-
env: result.env ?? {}
|
|
1054
|
-
});
|
|
1055
|
-
} catch (err) {
|
|
1056
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
1057
|
-
sendJSON(res, 500, {
|
|
1058
|
-
error: {
|
|
1059
|
-
code: -32603,
|
|
1060
|
-
message: errorMessage
|
|
1061
|
-
}
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1258
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1259
|
+
sendJSON(res, result.status, result.body);
|
|
1064
1260
|
return;
|
|
1065
1261
|
}
|
|
1066
1262
|
if (pathname === "/install" && req.method === "POST") {
|
|
1067
|
-
if (!config.hooks?.install) {
|
|
1068
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
1263
|
let installBody;
|
|
1072
1264
|
try {
|
|
1073
1265
|
installBody = await parseJSONBody(req);
|
|
@@ -1077,61 +1269,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1077
1269
|
});
|
|
1078
1270
|
return;
|
|
1079
1271
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
1083
|
-
});
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
const installContext = {
|
|
1087
|
-
env: installBody.env ?? {},
|
|
1088
|
-
workplace: installBody.context.workplace,
|
|
1089
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
1090
|
-
app: installBody.context.app,
|
|
1091
|
-
invocation: installBody.invocation,
|
|
1092
|
-
log: createContextLogger()
|
|
1093
|
-
};
|
|
1094
|
-
const installRequestConfig = {
|
|
1095
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1096
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1097
|
-
};
|
|
1098
|
-
try {
|
|
1099
|
-
const installHook = config.hooks.install;
|
|
1100
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1101
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1102
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1103
|
-
return await installHandler(installContext);
|
|
1104
|
-
});
|
|
1105
|
-
});
|
|
1106
|
-
sendJSON(res, 200, {
|
|
1107
|
-
env: result.env ?? {},
|
|
1108
|
-
redirect: result.redirect
|
|
1109
|
-
});
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
if (err instanceof InstallError) {
|
|
1112
|
-
sendJSON(res, 400, {
|
|
1113
|
-
error: {
|
|
1114
|
-
code: err.code,
|
|
1115
|
-
message: err.message,
|
|
1116
|
-
field: err.field
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
} else {
|
|
1120
|
-
sendJSON(res, 500, {
|
|
1121
|
-
error: {
|
|
1122
|
-
code: -32603,
|
|
1123
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1124
|
-
}
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1272
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1273
|
+
sendJSON(res, result.status, result.body);
|
|
1128
1274
|
return;
|
|
1129
1275
|
}
|
|
1130
1276
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
1131
|
-
if (!config.hooks?.uninstall) {
|
|
1132
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
1277
|
let uninstallBody;
|
|
1136
1278
|
try {
|
|
1137
1279
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -1141,53 +1283,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1141
1283
|
});
|
|
1142
1284
|
return;
|
|
1143
1285
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
error: {
|
|
1147
|
-
code: -32602,
|
|
1148
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1149
|
-
}
|
|
1150
|
-
});
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
const uninstallContext = {
|
|
1154
|
-
env: uninstallBody.env ?? {},
|
|
1155
|
-
workplace: uninstallBody.context.workplace,
|
|
1156
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1157
|
-
app: uninstallBody.context.app,
|
|
1158
|
-
invocation: uninstallBody.invocation,
|
|
1159
|
-
log: createContextLogger()
|
|
1160
|
-
};
|
|
1161
|
-
const uninstallRequestConfig = {
|
|
1162
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1163
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1164
|
-
};
|
|
1165
|
-
try {
|
|
1166
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1167
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1168
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1169
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1170
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1171
|
-
});
|
|
1172
|
-
});
|
|
1173
|
-
sendJSON(res, 200, {
|
|
1174
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
1175
|
-
});
|
|
1176
|
-
} catch (err) {
|
|
1177
|
-
sendJSON(res, 500, {
|
|
1178
|
-
error: {
|
|
1179
|
-
code: -32603,
|
|
1180
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1181
|
-
}
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1286
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1287
|
+
sendJSON(res, result.status, result.body);
|
|
1184
1288
|
return;
|
|
1185
1289
|
}
|
|
1186
1290
|
if (pathname === "/provision" && req.method === "POST") {
|
|
1187
|
-
if (!config.hooks?.provision) {
|
|
1188
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
1291
|
let provisionBody;
|
|
1192
1292
|
try {
|
|
1193
1293
|
provisionBody = await parseJSONBody(req);
|
|
@@ -1197,46 +1297,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1197
1297
|
});
|
|
1198
1298
|
return;
|
|
1199
1299
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
1203
|
-
});
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
const mergedEnv = {};
|
|
1207
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1208
|
-
if (value !== void 0) {
|
|
1209
|
-
mergedEnv[key] = value;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1213
|
-
const provisionContext = {
|
|
1214
|
-
env: mergedEnv,
|
|
1215
|
-
app: provisionBody.context.app,
|
|
1216
|
-
invocation: provisionBody.invocation,
|
|
1217
|
-
log: createContextLogger()
|
|
1218
|
-
};
|
|
1219
|
-
const provisionRequestConfig = {
|
|
1220
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1221
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1222
|
-
};
|
|
1223
|
-
try {
|
|
1224
|
-
const provisionHook = config.hooks.provision;
|
|
1225
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1226
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1227
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1228
|
-
return await provisionHandler(provisionContext);
|
|
1229
|
-
});
|
|
1230
|
-
});
|
|
1231
|
-
sendJSON(res, 200, result);
|
|
1232
|
-
} catch (err) {
|
|
1233
|
-
sendJSON(res, 500, {
|
|
1234
|
-
error: {
|
|
1235
|
-
code: -32603,
|
|
1236
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1300
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1301
|
+
sendJSON(res, result.status, result.body);
|
|
1240
1302
|
return;
|
|
1241
1303
|
}
|
|
1242
1304
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -1387,7 +1449,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1387
1449
|
const finalPort = listenPort ?? port;
|
|
1388
1450
|
return new Promise((resolve, reject) => {
|
|
1389
1451
|
httpServer.listen(finalPort, () => {
|
|
1390
|
-
printStartupLog(config, tools,
|
|
1452
|
+
printStartupLog(config, tools, finalPort);
|
|
1391
1453
|
resolve();
|
|
1392
1454
|
});
|
|
1393
1455
|
httpServer.once("error", reject);
|
|
@@ -1398,13 +1460,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1398
1460
|
}
|
|
1399
1461
|
|
|
1400
1462
|
// src/server/serverless.ts
|
|
1401
|
-
function createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
1463
|
+
function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
1402
1464
|
const headers = getDefaultHeaders(config.cors);
|
|
1465
|
+
const registry = config.tools;
|
|
1466
|
+
const webhookRegistry = config.webhooks;
|
|
1403
1467
|
let hasLoggedStartup = false;
|
|
1404
1468
|
return {
|
|
1405
1469
|
async handler(event) {
|
|
1406
1470
|
if (!hasLoggedStartup) {
|
|
1407
|
-
printStartupLog(config, tools
|
|
1471
|
+
printStartupLog(config, tools);
|
|
1408
1472
|
hasLoggedStartup = true;
|
|
1409
1473
|
}
|
|
1410
1474
|
try {
|
|
@@ -1415,12 +1479,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1415
1479
|
}
|
|
1416
1480
|
if (path.startsWith("/webhooks/") && webhookRegistry) {
|
|
1417
1481
|
const handle = path.slice("/webhooks/".length);
|
|
1418
|
-
|
|
1419
|
-
if (!webhookDef) {
|
|
1482
|
+
if (!webhookRegistry[handle]) {
|
|
1420
1483
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
1421
1484
|
}
|
|
1422
|
-
|
|
1423
|
-
if (!allowedMethods.includes(method)) {
|
|
1485
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
1424
1486
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
1425
1487
|
}
|
|
1426
1488
|
const rawBody = event.body ?? "";
|
|
@@ -1435,107 +1497,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1435
1497
|
} else {
|
|
1436
1498
|
parsedBody = rawBody;
|
|
1437
1499
|
}
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
method: envelope.request.method,
|
|
1457
|
-
url: envelope.request.url,
|
|
1458
|
-
path: envelope.request.path,
|
|
1459
|
-
headers: envelope.request.headers,
|
|
1460
|
-
query: envelope.request.query,
|
|
1461
|
-
body: originalParsedBody,
|
|
1462
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1463
|
-
};
|
|
1464
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
1465
|
-
const app = envelope.context.app;
|
|
1466
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1467
|
-
webhookContext = {
|
|
1468
|
-
env: envVars,
|
|
1469
|
-
app,
|
|
1470
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
1471
|
-
workplace: envelope.context.workplace,
|
|
1472
|
-
registration: envelope.context.registration ?? {},
|
|
1473
|
-
invocation,
|
|
1474
|
-
log: createContextLogger()
|
|
1475
|
-
};
|
|
1476
|
-
} else {
|
|
1477
|
-
webhookContext = {
|
|
1478
|
-
env: envVars,
|
|
1479
|
-
app,
|
|
1480
|
-
invocation,
|
|
1481
|
-
log: createContextLogger()
|
|
1482
|
-
};
|
|
1483
|
-
}
|
|
1484
|
-
} else {
|
|
1485
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
1486
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
1487
|
-
if (!appId || !appVersionId) {
|
|
1488
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
1489
|
-
}
|
|
1490
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1491
|
-
const protocol = forwardedProto ?? "https";
|
|
1492
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1493
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1494
|
-
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1495
|
-
webhookRequest = {
|
|
1496
|
-
method,
|
|
1497
|
-
url: webhookUrl,
|
|
1498
|
-
path,
|
|
1499
|
-
headers: event.headers,
|
|
1500
|
-
query: event.queryStringParameters ?? {},
|
|
1501
|
-
body: parsedBody,
|
|
1502
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1503
|
-
};
|
|
1504
|
-
webhookContext = {
|
|
1505
|
-
env: process.env,
|
|
1506
|
-
app: { id: appId, versionId: appVersionId },
|
|
1507
|
-
log: createContextLogger()
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
const originalEnv = { ...process.env };
|
|
1511
|
-
Object.assign(process.env, requestEnv);
|
|
1512
|
-
const requestConfig = {
|
|
1513
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1514
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1515
|
-
};
|
|
1516
|
-
let webhookResponse;
|
|
1517
|
-
try {
|
|
1518
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
1519
|
-
return await runWithConfig(requestConfig, async () => {
|
|
1520
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
1521
|
-
});
|
|
1522
|
-
});
|
|
1523
|
-
} catch (err) {
|
|
1524
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1525
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
1526
|
-
} finally {
|
|
1527
|
-
process.env = originalEnv;
|
|
1500
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1501
|
+
const protocol = forwardedProto ?? "https";
|
|
1502
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1503
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1504
|
+
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1505
|
+
const parseResult = parseWebhookRequest(
|
|
1506
|
+
parsedBody,
|
|
1507
|
+
method,
|
|
1508
|
+
webhookUrl,
|
|
1509
|
+
path,
|
|
1510
|
+
event.headers,
|
|
1511
|
+
event.queryStringParameters ?? {},
|
|
1512
|
+
rawBody,
|
|
1513
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
1514
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
1515
|
+
);
|
|
1516
|
+
if ("error" in parseResult) {
|
|
1517
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
1528
1518
|
}
|
|
1519
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
1529
1520
|
const responseHeaders = {
|
|
1530
1521
|
...headers,
|
|
1531
|
-
...
|
|
1522
|
+
...result.headers
|
|
1532
1523
|
};
|
|
1533
|
-
const status = webhookResponse.status ?? 200;
|
|
1534
|
-
const body = webhookResponse.body;
|
|
1535
1524
|
return {
|
|
1536
|
-
statusCode: status,
|
|
1525
|
+
statusCode: result.status,
|
|
1537
1526
|
headers: responseHeaders,
|
|
1538
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
1527
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
1539
1528
|
};
|
|
1540
1529
|
}
|
|
1541
1530
|
if (path === "/core" && method === "POST") {
|
|
@@ -1671,9 +1660,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1671
1660
|
}
|
|
1672
1661
|
}
|
|
1673
1662
|
if (path === "/install" && method === "POST") {
|
|
1674
|
-
if (!config.hooks?.install) {
|
|
1675
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
1676
|
-
}
|
|
1677
1663
|
let installBody;
|
|
1678
1664
|
try {
|
|
1679
1665
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1684,68 +1670,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1684
1670
|
headers
|
|
1685
1671
|
);
|
|
1686
1672
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
400,
|
|
1690
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
1691
|
-
headers
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
const installContext = {
|
|
1695
|
-
env: installBody.env ?? {},
|
|
1696
|
-
workplace: installBody.context.workplace,
|
|
1697
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
1698
|
-
app: installBody.context.app,
|
|
1699
|
-
invocation: installBody.invocation,
|
|
1700
|
-
log: createContextLogger()
|
|
1701
|
-
};
|
|
1702
|
-
const installRequestConfig = {
|
|
1703
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1704
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1705
|
-
};
|
|
1706
|
-
try {
|
|
1707
|
-
const installHook = config.hooks.install;
|
|
1708
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1709
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1710
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1711
|
-
return await installHandler(installContext);
|
|
1712
|
-
});
|
|
1713
|
-
});
|
|
1714
|
-
return createResponse(
|
|
1715
|
-
200,
|
|
1716
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
1717
|
-
headers
|
|
1718
|
-
);
|
|
1719
|
-
} catch (err) {
|
|
1720
|
-
if (err instanceof InstallError) {
|
|
1721
|
-
return createResponse(
|
|
1722
|
-
400,
|
|
1723
|
-
{
|
|
1724
|
-
error: {
|
|
1725
|
-
code: err.code,
|
|
1726
|
-
message: err.message,
|
|
1727
|
-
field: err.field
|
|
1728
|
-
}
|
|
1729
|
-
},
|
|
1730
|
-
headers
|
|
1731
|
-
);
|
|
1732
|
-
}
|
|
1733
|
-
return createResponse(
|
|
1734
|
-
500,
|
|
1735
|
-
{
|
|
1736
|
-
error: {
|
|
1737
|
-
code: -32603,
|
|
1738
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1739
|
-
}
|
|
1740
|
-
},
|
|
1741
|
-
headers
|
|
1742
|
-
);
|
|
1743
|
-
}
|
|
1673
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1674
|
+
return createResponse(result.status, result.body, headers);
|
|
1744
1675
|
}
|
|
1745
1676
|
if (path === "/uninstall" && method === "POST") {
|
|
1746
|
-
if (!config.hooks?.uninstall) {
|
|
1747
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
1748
|
-
}
|
|
1749
1677
|
let uninstallBody;
|
|
1750
1678
|
try {
|
|
1751
1679
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1756,137 +1684,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1756
1684
|
headers
|
|
1757
1685
|
);
|
|
1758
1686
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
400,
|
|
1762
|
-
{
|
|
1763
|
-
error: {
|
|
1764
|
-
code: -32602,
|
|
1765
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1766
|
-
}
|
|
1767
|
-
},
|
|
1768
|
-
headers
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
const uninstallContext = {
|
|
1772
|
-
env: uninstallBody.env ?? {},
|
|
1773
|
-
workplace: uninstallBody.context.workplace,
|
|
1774
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1775
|
-
app: uninstallBody.context.app,
|
|
1776
|
-
invocation: uninstallBody.invocation,
|
|
1777
|
-
log: createContextLogger()
|
|
1778
|
-
};
|
|
1779
|
-
const uninstallRequestConfig = {
|
|
1780
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1781
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1782
|
-
};
|
|
1783
|
-
try {
|
|
1784
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1785
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1786
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1787
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1788
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1789
|
-
});
|
|
1790
|
-
});
|
|
1791
|
-
return createResponse(
|
|
1792
|
-
200,
|
|
1793
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
1794
|
-
headers
|
|
1795
|
-
);
|
|
1796
|
-
} catch (err) {
|
|
1797
|
-
return createResponse(
|
|
1798
|
-
500,
|
|
1799
|
-
{
|
|
1800
|
-
error: {
|
|
1801
|
-
code: -32603,
|
|
1802
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1803
|
-
}
|
|
1804
|
-
},
|
|
1805
|
-
headers
|
|
1806
|
-
);
|
|
1807
|
-
}
|
|
1687
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1688
|
+
return createResponse(result.status, result.body, headers);
|
|
1808
1689
|
}
|
|
1809
1690
|
if (path === "/provision" && method === "POST") {
|
|
1810
|
-
console.log("[serverless] /provision endpoint called");
|
|
1811
|
-
if (!config.hooks?.provision) {
|
|
1812
|
-
console.log("[serverless] No provision handler configured");
|
|
1813
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
1814
|
-
}
|
|
1815
1691
|
let provisionBody;
|
|
1816
1692
|
try {
|
|
1817
1693
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
1818
|
-
console.log("[serverless] Provision body parsed:", {
|
|
1819
|
-
hasEnv: !!provisionBody.env,
|
|
1820
|
-
hasContext: !!provisionBody.context,
|
|
1821
|
-
appId: provisionBody.context?.app?.id,
|
|
1822
|
-
versionId: provisionBody.context?.app?.versionId
|
|
1823
|
-
});
|
|
1824
1694
|
} catch {
|
|
1825
|
-
console.log("[serverless] Failed to parse provision body");
|
|
1826
1695
|
return createResponse(
|
|
1827
1696
|
400,
|
|
1828
1697
|
{ error: { code: -32700, message: "Parse error" } },
|
|
1829
1698
|
headers
|
|
1830
1699
|
);
|
|
1831
1700
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
return createResponse(
|
|
1835
|
-
400,
|
|
1836
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
1837
|
-
headers
|
|
1838
|
-
);
|
|
1839
|
-
}
|
|
1840
|
-
const mergedEnv = {};
|
|
1841
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1842
|
-
if (value !== void 0) {
|
|
1843
|
-
mergedEnv[key] = value;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1847
|
-
const provisionContext = {
|
|
1848
|
-
env: mergedEnv,
|
|
1849
|
-
app: provisionBody.context.app,
|
|
1850
|
-
invocation: provisionBody.invocation,
|
|
1851
|
-
log: createContextLogger()
|
|
1852
|
-
};
|
|
1853
|
-
const provisionRequestConfig = {
|
|
1854
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1855
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1856
|
-
};
|
|
1857
|
-
console.log("[serverless] Calling provision handler...");
|
|
1858
|
-
try {
|
|
1859
|
-
const provisionHook = config.hooks.provision;
|
|
1860
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1861
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1862
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1863
|
-
return await provisionHandler(provisionContext);
|
|
1864
|
-
});
|
|
1865
|
-
});
|
|
1866
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
1867
|
-
return createResponse(200, result, headers);
|
|
1868
|
-
} catch (err) {
|
|
1869
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
1870
|
-
return createResponse(
|
|
1871
|
-
500,
|
|
1872
|
-
{
|
|
1873
|
-
error: {
|
|
1874
|
-
code: -32603,
|
|
1875
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1876
|
-
}
|
|
1877
|
-
},
|
|
1878
|
-
headers
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1701
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1702
|
+
return createResponse(result.status, result.body, headers);
|
|
1881
1703
|
}
|
|
1882
1704
|
if (path === "/oauth_callback" && method === "POST") {
|
|
1883
|
-
if (!config.hooks?.oauth_callback) {
|
|
1884
|
-
return createResponse(
|
|
1885
|
-
404,
|
|
1886
|
-
{ error: "OAuth callback handler not configured" },
|
|
1887
|
-
headers
|
|
1888
|
-
);
|
|
1889
|
-
}
|
|
1890
1705
|
let parsedBody;
|
|
1891
1706
|
try {
|
|
1892
1707
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1898,70 +1713,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1898
1713
|
headers
|
|
1899
1714
|
);
|
|
1900
1715
|
}
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
1904
|
-
return createResponse(
|
|
1905
|
-
400,
|
|
1906
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
1907
|
-
headers
|
|
1908
|
-
);
|
|
1909
|
-
}
|
|
1910
|
-
const invocation = parsedBody.invocation;
|
|
1911
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
1912
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
1913
|
-
const oauthCallbackContext = {
|
|
1914
|
-
request: oauthRequest,
|
|
1915
|
-
invocation,
|
|
1916
|
-
log: createContextLogger()
|
|
1917
|
-
};
|
|
1918
|
-
try {
|
|
1919
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
1920
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
1921
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
1922
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
1923
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
1924
|
-
});
|
|
1925
|
-
});
|
|
1926
|
-
return createResponse(
|
|
1927
|
-
200,
|
|
1928
|
-
{
|
|
1929
|
-
appInstallationId: result.appInstallationId,
|
|
1930
|
-
env: result.env ?? {}
|
|
1931
|
-
},
|
|
1932
|
-
headers
|
|
1933
|
-
);
|
|
1934
|
-
} catch (err) {
|
|
1935
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
1936
|
-
return createResponse(
|
|
1937
|
-
500,
|
|
1938
|
-
{
|
|
1939
|
-
error: {
|
|
1940
|
-
code: -32603,
|
|
1941
|
-
message: errorMessage
|
|
1942
|
-
}
|
|
1943
|
-
},
|
|
1944
|
-
headers
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1716
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1717
|
+
return createResponse(result.status, result.body, headers);
|
|
1947
1718
|
}
|
|
1948
1719
|
if (path === "/health" && method === "GET") {
|
|
1949
1720
|
return createResponse(200, state.getHealthStatus(), headers);
|
|
1950
1721
|
}
|
|
1951
1722
|
if (path === "/config" && method === "GET") {
|
|
1952
|
-
|
|
1953
|
-
if (!appConfig && config.appConfigLoader) {
|
|
1954
|
-
const loaded = await config.appConfigLoader();
|
|
1955
|
-
appConfig = loaded.default;
|
|
1956
|
-
}
|
|
1957
|
-
if (!appConfig) {
|
|
1958
|
-
appConfig = createMinimalConfig(
|
|
1959
|
-
config.metadata.name,
|
|
1960
|
-
config.metadata.version
|
|
1961
|
-
);
|
|
1962
|
-
}
|
|
1963
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
1964
|
-
return createResponse(200, serializedConfig, headers);
|
|
1723
|
+
return createResponse(200, serializeConfig(config), headers);
|
|
1965
1724
|
}
|
|
1966
1725
|
if (path === "/mcp" && method === "POST") {
|
|
1967
1726
|
let body;
|
|
@@ -2179,9 +1938,11 @@ console.log("[skedyul-node/server] All imports complete");
|
|
|
2179
1938
|
console.log("[skedyul-node/server] Installing context logger...");
|
|
2180
1939
|
installContextLogger();
|
|
2181
1940
|
console.log("[skedyul-node/server] Context logger installed");
|
|
2182
|
-
function createSkedyulServer(config
|
|
1941
|
+
function createSkedyulServer(config) {
|
|
2183
1942
|
console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
|
|
2184
1943
|
mergeRuntimeEnv();
|
|
1944
|
+
const registry = config.tools;
|
|
1945
|
+
const webhookRegistry = config.webhooks;
|
|
2185
1946
|
console.log("[createSkedyulServer] Step 2: coreApi setup");
|
|
2186
1947
|
if (config.coreApi?.service) {
|
|
2187
1948
|
coreApiService.register(config.coreApi.service);
|
|
@@ -2193,7 +1954,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2193
1954
|
const tools = buildToolMetadata(registry);
|
|
2194
1955
|
console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
|
|
2195
1956
|
const toolNames = Object.values(registry).map((tool) => tool.name);
|
|
2196
|
-
const runtimeLabel = config.computeLayer;
|
|
1957
|
+
const runtimeLabel = config.computeLayer ?? "serverless";
|
|
2197
1958
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
2198
1959
|
const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
|
|
2199
1960
|
console.log("[createSkedyulServer] Step 4: createRequestState()");
|
|
@@ -2206,8 +1967,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2206
1967
|
console.log("[createSkedyulServer] Step 4 done");
|
|
2207
1968
|
console.log("[createSkedyulServer] Step 5: new McpServer()");
|
|
2208
1969
|
const mcpServer = new import_mcp.McpServer({
|
|
2209
|
-
name: config.
|
|
2210
|
-
version: config.
|
|
1970
|
+
name: config.name,
|
|
1971
|
+
version: config.version ?? "0.0.0"
|
|
2211
1972
|
});
|
|
2212
1973
|
console.log("[createSkedyulServer] Step 5 done");
|
|
2213
1974
|
const dedicatedShutdown = () => {
|
|
@@ -2335,13 +2096,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2335
2096
|
tools,
|
|
2336
2097
|
callTool,
|
|
2337
2098
|
state,
|
|
2338
|
-
mcpServer
|
|
2339
|
-
registry,
|
|
2340
|
-
webhookRegistry
|
|
2099
|
+
mcpServer
|
|
2341
2100
|
);
|
|
2342
2101
|
}
|
|
2343
2102
|
console.log("[createSkedyulServer] Creating serverless instance");
|
|
2344
|
-
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
2103
|
+
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
|
|
2345
2104
|
console.log("[createSkedyulServer] Serverless instance created successfully");
|
|
2346
2105
|
return serverlessInstance;
|
|
2347
2106
|
}
|
|
@@ -2350,32 +2109,10 @@ var server = {
|
|
|
2350
2109
|
};
|
|
2351
2110
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2352
2111
|
0 && (module.exports = {
|
|
2353
|
-
buildRequestFromRaw,
|
|
2354
|
-
buildRequestScopedConfig,
|
|
2355
|
-
buildToolMetadata,
|
|
2356
|
-
createCallToolHandler,
|
|
2357
|
-
createDedicatedServerInstance,
|
|
2358
|
-
createRequestState,
|
|
2359
|
-
createResponse,
|
|
2360
|
-
createServerlessInstance,
|
|
2361
2112
|
createSkedyulServer,
|
|
2362
|
-
getDefaultHeaders,
|
|
2363
2113
|
getJsonSchemaFromToolSchema,
|
|
2364
|
-
getListeningPort,
|
|
2365
2114
|
getZodSchema,
|
|
2366
|
-
handleCoreMethod,
|
|
2367
2115
|
isToolSchemaWithJson,
|
|
2368
|
-
mergeRuntimeEnv,
|
|
2369
|
-
normalizeBilling,
|
|
2370
|
-
padEnd,
|
|
2371
|
-
parseHandlerEnvelope,
|
|
2372
|
-
parseJSONBody,
|
|
2373
|
-
parseJsonRecord,
|
|
2374
|
-
parseNumberEnv,
|
|
2375
|
-
printStartupLog,
|
|
2376
|
-
readRawRequestBody,
|
|
2377
|
-
sendHTML,
|
|
2378
|
-
sendJSON,
|
|
2379
2116
|
server,
|
|
2380
2117
|
toJsonSchema
|
|
2381
2118
|
});
|