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
|
@@ -142,10 +142,6 @@ function sendJSON(res, statusCode, data) {
|
|
|
142
142
|
res.writeHead(statusCode, { "Content-Type": "application/json" });
|
|
143
143
|
res.end(JSON.stringify(data));
|
|
144
144
|
}
|
|
145
|
-
function sendHTML(res, statusCode, html) {
|
|
146
|
-
res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
|
|
147
|
-
res.end(html);
|
|
148
|
-
}
|
|
149
145
|
function getDefaultHeaders(options) {
|
|
150
146
|
return {
|
|
151
147
|
"Content-Type": "application/json",
|
|
@@ -331,8 +327,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
331
327
|
};
|
|
332
328
|
}
|
|
333
329
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
334
|
-
return async function callTool(
|
|
335
|
-
const toolName = String(
|
|
330
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
331
|
+
const toolName = String(toolNameInput);
|
|
336
332
|
const tool = registry[toolName];
|
|
337
333
|
if (!tool) {
|
|
338
334
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -341,7 +337,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
341
337
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
342
338
|
}
|
|
343
339
|
const fn = tool.handler;
|
|
344
|
-
const args =
|
|
340
|
+
const args = toolArgsInput ?? {};
|
|
345
341
|
const estimateMode = args.estimate === true;
|
|
346
342
|
if (!estimateMode) {
|
|
347
343
|
state.incrementRequestCount();
|
|
@@ -596,51 +592,6 @@ async function handleCoreMethod(method, params) {
|
|
|
596
592
|
};
|
|
597
593
|
}
|
|
598
594
|
|
|
599
|
-
// src/server/handler-helpers.ts
|
|
600
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
601
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
const envelope = parsedBody;
|
|
605
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
609
|
-
return null;
|
|
610
|
-
}
|
|
611
|
-
return {
|
|
612
|
-
env: envelope.env,
|
|
613
|
-
request: envelope.request,
|
|
614
|
-
context: envelope.context
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
function buildRequestFromRaw(raw) {
|
|
618
|
-
let parsedBody = raw.body;
|
|
619
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
620
|
-
if (contentType.includes("application/json")) {
|
|
621
|
-
try {
|
|
622
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
623
|
-
} catch {
|
|
624
|
-
parsedBody = raw.body;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
method: raw.method,
|
|
629
|
-
url: raw.url,
|
|
630
|
-
path: raw.path,
|
|
631
|
-
headers: raw.headers,
|
|
632
|
-
query: raw.query,
|
|
633
|
-
body: parsedBody,
|
|
634
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function buildRequestScopedConfig(env) {
|
|
638
|
-
return {
|
|
639
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
640
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
595
|
// src/server/startup-logger.ts
|
|
645
596
|
function padEnd(str, length) {
|
|
646
597
|
if (str.length >= length) {
|
|
@@ -648,10 +599,11 @@ function padEnd(str, length) {
|
|
|
648
599
|
}
|
|
649
600
|
return str + " ".repeat(length - str.length);
|
|
650
601
|
}
|
|
651
|
-
function printStartupLog(config, tools,
|
|
602
|
+
function printStartupLog(config, tools, port) {
|
|
652
603
|
if (process.env.NODE_ENV === "test") {
|
|
653
604
|
return;
|
|
654
605
|
}
|
|
606
|
+
const webhookRegistry = config.webhooks;
|
|
655
607
|
const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
|
|
656
608
|
const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
|
|
657
609
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
@@ -664,9 +616,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
664
616
|
console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
|
|
665
617
|
console.log(`\u2560${divider}\u2563`);
|
|
666
618
|
console.log(`\u2551 \u2551`);
|
|
667
|
-
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.
|
|
668
|
-
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.
|
|
669
|
-
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
|
|
619
|
+
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
|
|
620
|
+
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
|
|
621
|
+
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
|
|
670
622
|
if (port) {
|
|
671
623
|
console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
|
|
672
624
|
}
|
|
@@ -708,67 +660,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
708
660
|
console.log("");
|
|
709
661
|
}
|
|
710
662
|
|
|
711
|
-
// src/config
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
663
|
+
// src/server/config-serializer.ts
|
|
664
|
+
function serializeConfig(config) {
|
|
665
|
+
const registry = config.tools;
|
|
666
|
+
const webhookRegistry = config.webhooks;
|
|
667
|
+
return {
|
|
668
|
+
name: config.name,
|
|
669
|
+
version: config.version,
|
|
670
|
+
description: config.description,
|
|
671
|
+
computeLayer: config.computeLayer,
|
|
672
|
+
tools: registry ? Object.entries(registry).map(([key, tool]) => ({
|
|
673
|
+
name: tool.name || key,
|
|
674
|
+
description: tool.description,
|
|
675
|
+
timeout: tool.timeout,
|
|
676
|
+
retries: tool.retries
|
|
677
|
+
})) : [],
|
|
678
|
+
webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
|
|
679
|
+
name: w.name,
|
|
680
|
+
description: w.description,
|
|
681
|
+
methods: w.methods ?? ["POST"],
|
|
682
|
+
type: w.type ?? "WEBHOOK"
|
|
683
|
+
})) : [],
|
|
684
|
+
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
685
|
+
agents: config.agents
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function isProvisionConfig(value) {
|
|
689
|
+
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/server/handlers/install-handler.ts
|
|
693
|
+
async function handleInstall(body, hooks) {
|
|
694
|
+
if (!hooks?.install) {
|
|
695
|
+
return {
|
|
696
|
+
status: 404,
|
|
697
|
+
body: { error: "Install handler not configured" }
|
|
698
|
+
};
|
|
715
699
|
}
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
700
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
701
|
+
return {
|
|
702
|
+
status: 400,
|
|
703
|
+
body: {
|
|
704
|
+
error: {
|
|
705
|
+
code: -32602,
|
|
706
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const installContext = {
|
|
712
|
+
env: body.env ?? {},
|
|
713
|
+
workplace: body.context.workplace,
|
|
714
|
+
appInstallationId: body.context.appInstallationId,
|
|
715
|
+
app: body.context.app,
|
|
716
|
+
invocation: body.invocation,
|
|
717
|
+
log: createContextLogger()
|
|
718
|
+
};
|
|
719
|
+
const requestConfig = {
|
|
720
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
721
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
722
|
+
};
|
|
723
|
+
try {
|
|
724
|
+
const installHook = hooks.install;
|
|
725
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
726
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
727
|
+
return await runWithConfig(requestConfig, async () => {
|
|
728
|
+
return await installHandler(installContext);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
return {
|
|
732
|
+
status: 200,
|
|
733
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
734
|
+
};
|
|
735
|
+
} catch (err) {
|
|
736
|
+
if (err instanceof InstallError) {
|
|
737
|
+
return {
|
|
738
|
+
status: 400,
|
|
739
|
+
body: {
|
|
740
|
+
error: {
|
|
741
|
+
code: err.code,
|
|
742
|
+
message: err.message,
|
|
743
|
+
field: err.field
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
};
|
|
720
747
|
}
|
|
721
|
-
return
|
|
748
|
+
return {
|
|
749
|
+
status: 500,
|
|
750
|
+
body: {
|
|
751
|
+
error: {
|
|
752
|
+
code: -32603,
|
|
753
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
722
757
|
}
|
|
723
|
-
return value;
|
|
724
758
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
759
|
+
|
|
760
|
+
// src/server/handlers/uninstall-handler.ts
|
|
761
|
+
async function handleUninstall(body, hooks) {
|
|
762
|
+
if (!hooks?.uninstall) {
|
|
763
|
+
return {
|
|
764
|
+
status: 404,
|
|
765
|
+
body: { error: "Uninstall handler not configured" }
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
769
|
+
return {
|
|
770
|
+
status: 400,
|
|
771
|
+
body: {
|
|
772
|
+
error: {
|
|
773
|
+
code: -32602,
|
|
774
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const uninstallContext = {
|
|
780
|
+
env: body.env ?? {},
|
|
781
|
+
workplace: body.context.workplace,
|
|
782
|
+
appInstallationId: body.context.appInstallationId,
|
|
783
|
+
app: body.context.app,
|
|
784
|
+
invocation: body.invocation,
|
|
785
|
+
log: createContextLogger()
|
|
786
|
+
};
|
|
787
|
+
const requestConfig = {
|
|
788
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
789
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
const uninstallHook = hooks.uninstall;
|
|
793
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
794
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
795
|
+
return await runWithConfig(requestConfig, async () => {
|
|
796
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
status: 200,
|
|
801
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
802
|
+
};
|
|
803
|
+
} catch (err) {
|
|
804
|
+
return {
|
|
805
|
+
status: 500,
|
|
806
|
+
body: {
|
|
807
|
+
error: {
|
|
808
|
+
code: -32603,
|
|
809
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
}
|
|
733
814
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
815
|
+
|
|
816
|
+
// src/server/handlers/provision-handler.ts
|
|
817
|
+
async function handleProvision(body, hooks) {
|
|
818
|
+
if (!hooks?.provision) {
|
|
819
|
+
return {
|
|
820
|
+
status: 404,
|
|
821
|
+
body: { error: "Provision handler not configured" }
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (!body.context?.app) {
|
|
825
|
+
return {
|
|
826
|
+
status: 400,
|
|
827
|
+
body: {
|
|
828
|
+
error: {
|
|
829
|
+
code: -32602,
|
|
830
|
+
message: "Missing context (app required)"
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
const mergedEnv = {};
|
|
836
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
837
|
+
if (value !== void 0) {
|
|
838
|
+
mergedEnv[key] = value;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
842
|
+
const provisionContext = {
|
|
843
|
+
env: mergedEnv,
|
|
844
|
+
app: body.context.app,
|
|
845
|
+
invocation: body.invocation,
|
|
846
|
+
log: createContextLogger()
|
|
847
|
+
};
|
|
848
|
+
const requestConfig = {
|
|
849
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
850
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
851
|
+
};
|
|
852
|
+
try {
|
|
853
|
+
const provisionHook = hooks.provision;
|
|
854
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
855
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
856
|
+
return await runWithConfig(requestConfig, async () => {
|
|
857
|
+
return await provisionHandler(provisionContext);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
status: 200,
|
|
862
|
+
body: result
|
|
863
|
+
};
|
|
864
|
+
} catch (err) {
|
|
865
|
+
return {
|
|
866
|
+
status: 500,
|
|
867
|
+
body: {
|
|
868
|
+
error: {
|
|
869
|
+
code: -32603,
|
|
870
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
741
875
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
)
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
876
|
+
|
|
877
|
+
// src/server/handler-helpers.ts
|
|
878
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
879
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
const envelope = parsedBody;
|
|
883
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
751
889
|
return {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
890
|
+
env: envelope.env,
|
|
891
|
+
request: envelope.request,
|
|
892
|
+
context: envelope.context
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function buildRequestFromRaw(raw) {
|
|
896
|
+
let parsedBody = raw.body;
|
|
897
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
898
|
+
if (contentType.includes("application/json")) {
|
|
899
|
+
try {
|
|
900
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
901
|
+
} catch {
|
|
902
|
+
parsedBody = raw.body;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
method: raw.method,
|
|
907
|
+
url: raw.url,
|
|
908
|
+
path: raw.path,
|
|
909
|
+
headers: raw.headers,
|
|
910
|
+
query: raw.query,
|
|
911
|
+
body: parsedBody,
|
|
912
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function buildRequestScopedConfig(env) {
|
|
916
|
+
return {
|
|
917
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
918
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
923
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
924
|
+
if (!hooks?.oauth_callback) {
|
|
925
|
+
return {
|
|
926
|
+
status: 404,
|
|
927
|
+
body: { error: "OAuth callback handler not configured" }
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
931
|
+
if (!envelope) {
|
|
932
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
933
|
+
return {
|
|
934
|
+
status: 400,
|
|
935
|
+
body: {
|
|
936
|
+
error: {
|
|
937
|
+
code: -32602,
|
|
938
|
+
message: "Missing envelope format: expected { env, request }"
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
const invocation = parsedBody.invocation;
|
|
944
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
945
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
946
|
+
const oauthCallbackContext = {
|
|
947
|
+
request: oauthRequest,
|
|
948
|
+
invocation,
|
|
949
|
+
log: createContextLogger()
|
|
950
|
+
};
|
|
951
|
+
try {
|
|
952
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
953
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
954
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
955
|
+
return await runWithConfig(requestConfig, async () => {
|
|
956
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
return {
|
|
960
|
+
status: 200,
|
|
961
|
+
body: {
|
|
962
|
+
appInstallationId: result.appInstallationId,
|
|
963
|
+
env: result.env ?? {}
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
} catch (err) {
|
|
967
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
968
|
+
return {
|
|
969
|
+
status: 500,
|
|
970
|
+
body: {
|
|
971
|
+
error: {
|
|
972
|
+
code: -32603,
|
|
973
|
+
message: errorMessage
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/server/handlers/webhook-handler.ts
|
|
981
|
+
function parseWebhookRequest(parsedBody, method, url, path, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
982
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
983
|
+
if (isEnvelope) {
|
|
984
|
+
const envelope = parsedBody;
|
|
985
|
+
const requestEnv = envelope.env ?? {};
|
|
986
|
+
const invocation = envelope.invocation;
|
|
987
|
+
let originalParsedBody = envelope.request.body;
|
|
988
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
989
|
+
if (originalContentType.includes("application/json")) {
|
|
990
|
+
try {
|
|
991
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const webhookRequest2 = {
|
|
996
|
+
method: envelope.request.method,
|
|
997
|
+
url: envelope.request.url,
|
|
998
|
+
path: envelope.request.path,
|
|
999
|
+
headers: envelope.request.headers,
|
|
1000
|
+
query: envelope.request.query,
|
|
1001
|
+
body: originalParsedBody,
|
|
1002
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1003
|
+
};
|
|
1004
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
1005
|
+
const app = envelope.context.app;
|
|
1006
|
+
let webhookContext2;
|
|
1007
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1008
|
+
webhookContext2 = {
|
|
1009
|
+
env: envVars,
|
|
1010
|
+
app,
|
|
1011
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
1012
|
+
workplace: envelope.context.workplace,
|
|
1013
|
+
registration: envelope.context.registration ?? {},
|
|
1014
|
+
invocation,
|
|
1015
|
+
log: createContextLogger()
|
|
1016
|
+
};
|
|
1017
|
+
} else {
|
|
1018
|
+
webhookContext2 = {
|
|
1019
|
+
env: envVars,
|
|
1020
|
+
app,
|
|
1021
|
+
invocation,
|
|
1022
|
+
log: createContextLogger()
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
1026
|
+
}
|
|
1027
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
1028
|
+
return {
|
|
1029
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
const webhookRequest = {
|
|
1033
|
+
method,
|
|
1034
|
+
url,
|
|
1035
|
+
path,
|
|
1036
|
+
headers,
|
|
1037
|
+
query,
|
|
1038
|
+
body: parsedBody,
|
|
1039
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1040
|
+
};
|
|
1041
|
+
const webhookContext = {
|
|
1042
|
+
env: process.env,
|
|
1043
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
1044
|
+
log: createContextLogger()
|
|
760
1045
|
};
|
|
1046
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
761
1047
|
}
|
|
762
|
-
function
|
|
1048
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
1049
|
+
const webhookDef = webhookRegistry[handle];
|
|
1050
|
+
if (!webhookDef) {
|
|
1051
|
+
return {
|
|
1052
|
+
status: 404,
|
|
1053
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const originalEnv = { ...process.env };
|
|
1057
|
+
Object.assign(process.env, data.requestEnv);
|
|
1058
|
+
const requestConfig = {
|
|
1059
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1060
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1061
|
+
};
|
|
1062
|
+
let webhookResponse;
|
|
1063
|
+
try {
|
|
1064
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
1065
|
+
return await runWithConfig(requestConfig, async () => {
|
|
1066
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1071
|
+
return {
|
|
1072
|
+
status: 500,
|
|
1073
|
+
body: { error: "Webhook handler error" }
|
|
1074
|
+
};
|
|
1075
|
+
} finally {
|
|
1076
|
+
process.env = originalEnv;
|
|
1077
|
+
}
|
|
763
1078
|
return {
|
|
764
|
-
|
|
765
|
-
|
|
1079
|
+
status: webhookResponse.status ?? 200,
|
|
1080
|
+
body: webhookResponse.body,
|
|
1081
|
+
headers: webhookResponse.headers
|
|
766
1082
|
};
|
|
767
1083
|
}
|
|
1084
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
1085
|
+
const webhookDef = webhookRegistry[handle];
|
|
1086
|
+
if (!webhookDef) return false;
|
|
1087
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
1088
|
+
return allowedMethods.includes(method);
|
|
1089
|
+
}
|
|
768
1090
|
|
|
769
1091
|
// src/server/dedicated.ts
|
|
770
|
-
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
1092
|
+
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
771
1093
|
const port = getListeningPort(config);
|
|
1094
|
+
const registry = config.tools;
|
|
1095
|
+
const webhookRegistry = config.webhooks;
|
|
772
1096
|
const httpServer = http.createServer(
|
|
773
1097
|
async (req, res) => {
|
|
774
1098
|
function sendCoreResult(result) {
|
|
@@ -785,30 +1109,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
785
1109
|
return;
|
|
786
1110
|
}
|
|
787
1111
|
if (pathname === "/config" && req.method === "GET") {
|
|
788
|
-
|
|
789
|
-
if (!appConfig && config.appConfigLoader) {
|
|
790
|
-
const loaded = await config.appConfigLoader();
|
|
791
|
-
appConfig = loaded.default;
|
|
792
|
-
}
|
|
793
|
-
if (!appConfig) {
|
|
794
|
-
appConfig = createMinimalConfig(
|
|
795
|
-
config.metadata.name,
|
|
796
|
-
config.metadata.version
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
800
|
-
sendJSON(res, 200, serializedConfig);
|
|
1112
|
+
sendJSON(res, 200, serializeConfig(config));
|
|
801
1113
|
return;
|
|
802
1114
|
}
|
|
803
1115
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
804
1116
|
const handle = pathname.slice("/webhooks/".length);
|
|
805
|
-
|
|
806
|
-
if (!webhookDef) {
|
|
1117
|
+
if (!webhookRegistry[handle]) {
|
|
807
1118
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
808
1119
|
return;
|
|
809
1120
|
}
|
|
810
|
-
|
|
811
|
-
if (!allowedMethods.includes(req.method)) {
|
|
1121
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
812
1122
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
813
1123
|
return;
|
|
814
1124
|
}
|
|
@@ -830,87 +1140,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
830
1140
|
} else {
|
|
831
1141
|
parsedBody = rawBody;
|
|
832
1142
|
}
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
webhookContext = {
|
|
847
|
-
env: envVars,
|
|
848
|
-
app,
|
|
849
|
-
appInstallationId: context.appInstallationId,
|
|
850
|
-
workplace: context.workplace,
|
|
851
|
-
registration: context.registration ?? {},
|
|
852
|
-
invocation,
|
|
853
|
-
log: createContextLogger()
|
|
854
|
-
};
|
|
855
|
-
} else {
|
|
856
|
-
webhookContext = {
|
|
857
|
-
env: envVars,
|
|
858
|
-
app,
|
|
859
|
-
invocation,
|
|
860
|
-
log: createContextLogger()
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
} else {
|
|
864
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
865
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
866
|
-
if (!appId || !appVersionId) {
|
|
867
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
868
|
-
}
|
|
869
|
-
webhookRequest = {
|
|
870
|
-
method: req.method ?? "POST",
|
|
871
|
-
url: url.toString(),
|
|
872
|
-
path: pathname,
|
|
873
|
-
headers: req.headers,
|
|
874
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
875
|
-
body: parsedBody,
|
|
876
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
877
|
-
};
|
|
878
|
-
webhookContext = {
|
|
879
|
-
env: process.env,
|
|
880
|
-
app: { id: appId, versionId: appVersionId },
|
|
881
|
-
log: createContextLogger()
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
const originalEnv = { ...process.env };
|
|
885
|
-
Object.assign(process.env, requestEnv);
|
|
886
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
887
|
-
let webhookResponse;
|
|
888
|
-
try {
|
|
889
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
890
|
-
return await runWithConfig(requestConfig, async () => {
|
|
891
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
892
|
-
});
|
|
893
|
-
});
|
|
894
|
-
} catch (err) {
|
|
895
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
896
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
1143
|
+
const parseResult = parseWebhookRequest(
|
|
1144
|
+
parsedBody,
|
|
1145
|
+
req.method ?? "POST",
|
|
1146
|
+
url.toString(),
|
|
1147
|
+
pathname,
|
|
1148
|
+
req.headers,
|
|
1149
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
1150
|
+
rawBody,
|
|
1151
|
+
req.headers["x-skedyul-app-id"],
|
|
1152
|
+
req.headers["x-skedyul-app-version-id"]
|
|
1153
|
+
);
|
|
1154
|
+
if ("error" in parseResult) {
|
|
1155
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
897
1156
|
return;
|
|
898
|
-
} finally {
|
|
899
|
-
process.env = originalEnv;
|
|
900
1157
|
}
|
|
901
|
-
const
|
|
1158
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
902
1159
|
const responseHeaders = {
|
|
903
|
-
...
|
|
1160
|
+
...result.headers
|
|
904
1161
|
};
|
|
905
1162
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
906
1163
|
responseHeaders["Content-Type"] = "application/json";
|
|
907
1164
|
}
|
|
908
|
-
res.writeHead(status, responseHeaders);
|
|
909
|
-
if (
|
|
910
|
-
if (typeof
|
|
911
|
-
res.end(
|
|
1165
|
+
res.writeHead(result.status, responseHeaders);
|
|
1166
|
+
if (result.body !== void 0) {
|
|
1167
|
+
if (typeof result.body === "string") {
|
|
1168
|
+
res.end(result.body);
|
|
912
1169
|
} else {
|
|
913
|
-
res.end(JSON.stringify(
|
|
1170
|
+
res.end(JSON.stringify(result.body));
|
|
914
1171
|
}
|
|
915
1172
|
} else {
|
|
916
1173
|
res.end();
|
|
@@ -949,10 +1206,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
949
1206
|
return;
|
|
950
1207
|
}
|
|
951
1208
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
952
|
-
if (!config.hooks?.oauth_callback) {
|
|
953
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
1209
|
let parsedBody;
|
|
957
1210
|
try {
|
|
958
1211
|
parsedBody = await parseJSONBody(req);
|
|
@@ -963,50 +1216,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
963
1216
|
});
|
|
964
1217
|
return;
|
|
965
1218
|
}
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
969
|
-
sendJSON(res, 400, {
|
|
970
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
971
|
-
});
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
const invocation = parsedBody.invocation;
|
|
975
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
976
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
977
|
-
const oauthCallbackContext = {
|
|
978
|
-
request: oauthRequest,
|
|
979
|
-
invocation,
|
|
980
|
-
log: createContextLogger()
|
|
981
|
-
};
|
|
982
|
-
try {
|
|
983
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
984
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
985
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
986
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
987
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
988
|
-
});
|
|
989
|
-
});
|
|
990
|
-
sendJSON(res, 200, {
|
|
991
|
-
appInstallationId: result.appInstallationId,
|
|
992
|
-
env: result.env ?? {}
|
|
993
|
-
});
|
|
994
|
-
} catch (err) {
|
|
995
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
996
|
-
sendJSON(res, 500, {
|
|
997
|
-
error: {
|
|
998
|
-
code: -32603,
|
|
999
|
-
message: errorMessage
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1219
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1220
|
+
sendJSON(res, result.status, result.body);
|
|
1003
1221
|
return;
|
|
1004
1222
|
}
|
|
1005
1223
|
if (pathname === "/install" && req.method === "POST") {
|
|
1006
|
-
if (!config.hooks?.install) {
|
|
1007
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
1224
|
let installBody;
|
|
1011
1225
|
try {
|
|
1012
1226
|
installBody = await parseJSONBody(req);
|
|
@@ -1016,61 +1230,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1016
1230
|
});
|
|
1017
1231
|
return;
|
|
1018
1232
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
1022
|
-
});
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
const installContext = {
|
|
1026
|
-
env: installBody.env ?? {},
|
|
1027
|
-
workplace: installBody.context.workplace,
|
|
1028
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
1029
|
-
app: installBody.context.app,
|
|
1030
|
-
invocation: installBody.invocation,
|
|
1031
|
-
log: createContextLogger()
|
|
1032
|
-
};
|
|
1033
|
-
const installRequestConfig = {
|
|
1034
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1035
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1036
|
-
};
|
|
1037
|
-
try {
|
|
1038
|
-
const installHook = config.hooks.install;
|
|
1039
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1040
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1041
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1042
|
-
return await installHandler(installContext);
|
|
1043
|
-
});
|
|
1044
|
-
});
|
|
1045
|
-
sendJSON(res, 200, {
|
|
1046
|
-
env: result.env ?? {},
|
|
1047
|
-
redirect: result.redirect
|
|
1048
|
-
});
|
|
1049
|
-
} catch (err) {
|
|
1050
|
-
if (err instanceof InstallError) {
|
|
1051
|
-
sendJSON(res, 400, {
|
|
1052
|
-
error: {
|
|
1053
|
-
code: err.code,
|
|
1054
|
-
message: err.message,
|
|
1055
|
-
field: err.field
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
} else {
|
|
1059
|
-
sendJSON(res, 500, {
|
|
1060
|
-
error: {
|
|
1061
|
-
code: -32603,
|
|
1062
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1233
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1234
|
+
sendJSON(res, result.status, result.body);
|
|
1067
1235
|
return;
|
|
1068
1236
|
}
|
|
1069
1237
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
1070
|
-
if (!config.hooks?.uninstall) {
|
|
1071
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
1238
|
let uninstallBody;
|
|
1075
1239
|
try {
|
|
1076
1240
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -1080,53 +1244,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1080
1244
|
});
|
|
1081
1245
|
return;
|
|
1082
1246
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
error: {
|
|
1086
|
-
code: -32602,
|
|
1087
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1088
|
-
}
|
|
1089
|
-
});
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
const uninstallContext = {
|
|
1093
|
-
env: uninstallBody.env ?? {},
|
|
1094
|
-
workplace: uninstallBody.context.workplace,
|
|
1095
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1096
|
-
app: uninstallBody.context.app,
|
|
1097
|
-
invocation: uninstallBody.invocation,
|
|
1098
|
-
log: createContextLogger()
|
|
1099
|
-
};
|
|
1100
|
-
const uninstallRequestConfig = {
|
|
1101
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1102
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1103
|
-
};
|
|
1104
|
-
try {
|
|
1105
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1106
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1107
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1108
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1109
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1110
|
-
});
|
|
1111
|
-
});
|
|
1112
|
-
sendJSON(res, 200, {
|
|
1113
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
1114
|
-
});
|
|
1115
|
-
} catch (err) {
|
|
1116
|
-
sendJSON(res, 500, {
|
|
1117
|
-
error: {
|
|
1118
|
-
code: -32603,
|
|
1119
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1120
|
-
}
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1247
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1248
|
+
sendJSON(res, result.status, result.body);
|
|
1123
1249
|
return;
|
|
1124
1250
|
}
|
|
1125
1251
|
if (pathname === "/provision" && req.method === "POST") {
|
|
1126
|
-
if (!config.hooks?.provision) {
|
|
1127
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
1252
|
let provisionBody;
|
|
1131
1253
|
try {
|
|
1132
1254
|
provisionBody = await parseJSONBody(req);
|
|
@@ -1136,46 +1258,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1136
1258
|
});
|
|
1137
1259
|
return;
|
|
1138
1260
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
1142
|
-
});
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
const mergedEnv = {};
|
|
1146
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1147
|
-
if (value !== void 0) {
|
|
1148
|
-
mergedEnv[key] = value;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1152
|
-
const provisionContext = {
|
|
1153
|
-
env: mergedEnv,
|
|
1154
|
-
app: provisionBody.context.app,
|
|
1155
|
-
invocation: provisionBody.invocation,
|
|
1156
|
-
log: createContextLogger()
|
|
1157
|
-
};
|
|
1158
|
-
const provisionRequestConfig = {
|
|
1159
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1160
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1161
|
-
};
|
|
1162
|
-
try {
|
|
1163
|
-
const provisionHook = config.hooks.provision;
|
|
1164
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1165
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1166
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1167
|
-
return await provisionHandler(provisionContext);
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
sendJSON(res, 200, result);
|
|
1171
|
-
} catch (err) {
|
|
1172
|
-
sendJSON(res, 500, {
|
|
1173
|
-
error: {
|
|
1174
|
-
code: -32603,
|
|
1175
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1261
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1262
|
+
sendJSON(res, result.status, result.body);
|
|
1179
1263
|
return;
|
|
1180
1264
|
}
|
|
1181
1265
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -1326,7 +1410,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1326
1410
|
const finalPort = listenPort ?? port;
|
|
1327
1411
|
return new Promise((resolve, reject) => {
|
|
1328
1412
|
httpServer.listen(finalPort, () => {
|
|
1329
|
-
printStartupLog(config, tools,
|
|
1413
|
+
printStartupLog(config, tools, finalPort);
|
|
1330
1414
|
resolve();
|
|
1331
1415
|
});
|
|
1332
1416
|
httpServer.once("error", reject);
|
|
@@ -1337,13 +1421,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1337
1421
|
}
|
|
1338
1422
|
|
|
1339
1423
|
// src/server/serverless.ts
|
|
1340
|
-
function createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
1424
|
+
function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
1341
1425
|
const headers = getDefaultHeaders(config.cors);
|
|
1426
|
+
const registry = config.tools;
|
|
1427
|
+
const webhookRegistry = config.webhooks;
|
|
1342
1428
|
let hasLoggedStartup = false;
|
|
1343
1429
|
return {
|
|
1344
1430
|
async handler(event) {
|
|
1345
1431
|
if (!hasLoggedStartup) {
|
|
1346
|
-
printStartupLog(config, tools
|
|
1432
|
+
printStartupLog(config, tools);
|
|
1347
1433
|
hasLoggedStartup = true;
|
|
1348
1434
|
}
|
|
1349
1435
|
try {
|
|
@@ -1354,12 +1440,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1354
1440
|
}
|
|
1355
1441
|
if (path.startsWith("/webhooks/") && webhookRegistry) {
|
|
1356
1442
|
const handle = path.slice("/webhooks/".length);
|
|
1357
|
-
|
|
1358
|
-
if (!webhookDef) {
|
|
1443
|
+
if (!webhookRegistry[handle]) {
|
|
1359
1444
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
1360
1445
|
}
|
|
1361
|
-
|
|
1362
|
-
if (!allowedMethods.includes(method)) {
|
|
1446
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
1363
1447
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
1364
1448
|
}
|
|
1365
1449
|
const rawBody = event.body ?? "";
|
|
@@ -1374,107 +1458,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1374
1458
|
} else {
|
|
1375
1459
|
parsedBody = rawBody;
|
|
1376
1460
|
}
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
method: envelope.request.method,
|
|
1396
|
-
url: envelope.request.url,
|
|
1397
|
-
path: envelope.request.path,
|
|
1398
|
-
headers: envelope.request.headers,
|
|
1399
|
-
query: envelope.request.query,
|
|
1400
|
-
body: originalParsedBody,
|
|
1401
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1402
|
-
};
|
|
1403
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
1404
|
-
const app = envelope.context.app;
|
|
1405
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1406
|
-
webhookContext = {
|
|
1407
|
-
env: envVars,
|
|
1408
|
-
app,
|
|
1409
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
1410
|
-
workplace: envelope.context.workplace,
|
|
1411
|
-
registration: envelope.context.registration ?? {},
|
|
1412
|
-
invocation,
|
|
1413
|
-
log: createContextLogger()
|
|
1414
|
-
};
|
|
1415
|
-
} else {
|
|
1416
|
-
webhookContext = {
|
|
1417
|
-
env: envVars,
|
|
1418
|
-
app,
|
|
1419
|
-
invocation,
|
|
1420
|
-
log: createContextLogger()
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
} else {
|
|
1424
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
1425
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
1426
|
-
if (!appId || !appVersionId) {
|
|
1427
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
1428
|
-
}
|
|
1429
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1430
|
-
const protocol = forwardedProto ?? "https";
|
|
1431
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1432
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1433
|
-
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1434
|
-
webhookRequest = {
|
|
1435
|
-
method,
|
|
1436
|
-
url: webhookUrl,
|
|
1437
|
-
path,
|
|
1438
|
-
headers: event.headers,
|
|
1439
|
-
query: event.queryStringParameters ?? {},
|
|
1440
|
-
body: parsedBody,
|
|
1441
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1442
|
-
};
|
|
1443
|
-
webhookContext = {
|
|
1444
|
-
env: process.env,
|
|
1445
|
-
app: { id: appId, versionId: appVersionId },
|
|
1446
|
-
log: createContextLogger()
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
const originalEnv = { ...process.env };
|
|
1450
|
-
Object.assign(process.env, requestEnv);
|
|
1451
|
-
const requestConfig = {
|
|
1452
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1453
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1454
|
-
};
|
|
1455
|
-
let webhookResponse;
|
|
1456
|
-
try {
|
|
1457
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
1458
|
-
return await runWithConfig(requestConfig, async () => {
|
|
1459
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
1460
|
-
});
|
|
1461
|
-
});
|
|
1462
|
-
} catch (err) {
|
|
1463
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1464
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
1465
|
-
} finally {
|
|
1466
|
-
process.env = originalEnv;
|
|
1461
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1462
|
+
const protocol = forwardedProto ?? "https";
|
|
1463
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1464
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1465
|
+
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1466
|
+
const parseResult = parseWebhookRequest(
|
|
1467
|
+
parsedBody,
|
|
1468
|
+
method,
|
|
1469
|
+
webhookUrl,
|
|
1470
|
+
path,
|
|
1471
|
+
event.headers,
|
|
1472
|
+
event.queryStringParameters ?? {},
|
|
1473
|
+
rawBody,
|
|
1474
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
1475
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
1476
|
+
);
|
|
1477
|
+
if ("error" in parseResult) {
|
|
1478
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
1467
1479
|
}
|
|
1480
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
1468
1481
|
const responseHeaders = {
|
|
1469
1482
|
...headers,
|
|
1470
|
-
...
|
|
1483
|
+
...result.headers
|
|
1471
1484
|
};
|
|
1472
|
-
const status = webhookResponse.status ?? 200;
|
|
1473
|
-
const body = webhookResponse.body;
|
|
1474
1485
|
return {
|
|
1475
|
-
statusCode: status,
|
|
1486
|
+
statusCode: result.status,
|
|
1476
1487
|
headers: responseHeaders,
|
|
1477
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
1488
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
1478
1489
|
};
|
|
1479
1490
|
}
|
|
1480
1491
|
if (path === "/core" && method === "POST") {
|
|
@@ -1610,9 +1621,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1610
1621
|
}
|
|
1611
1622
|
}
|
|
1612
1623
|
if (path === "/install" && method === "POST") {
|
|
1613
|
-
if (!config.hooks?.install) {
|
|
1614
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
1615
|
-
}
|
|
1616
1624
|
let installBody;
|
|
1617
1625
|
try {
|
|
1618
1626
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1623,68 +1631,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1623
1631
|
headers
|
|
1624
1632
|
);
|
|
1625
1633
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
400,
|
|
1629
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
1630
|
-
headers
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
const installContext = {
|
|
1634
|
-
env: installBody.env ?? {},
|
|
1635
|
-
workplace: installBody.context.workplace,
|
|
1636
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
1637
|
-
app: installBody.context.app,
|
|
1638
|
-
invocation: installBody.invocation,
|
|
1639
|
-
log: createContextLogger()
|
|
1640
|
-
};
|
|
1641
|
-
const installRequestConfig = {
|
|
1642
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1643
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1644
|
-
};
|
|
1645
|
-
try {
|
|
1646
|
-
const installHook = config.hooks.install;
|
|
1647
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1648
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1649
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1650
|
-
return await installHandler(installContext);
|
|
1651
|
-
});
|
|
1652
|
-
});
|
|
1653
|
-
return createResponse(
|
|
1654
|
-
200,
|
|
1655
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
1656
|
-
headers
|
|
1657
|
-
);
|
|
1658
|
-
} catch (err) {
|
|
1659
|
-
if (err instanceof InstallError) {
|
|
1660
|
-
return createResponse(
|
|
1661
|
-
400,
|
|
1662
|
-
{
|
|
1663
|
-
error: {
|
|
1664
|
-
code: err.code,
|
|
1665
|
-
message: err.message,
|
|
1666
|
-
field: err.field
|
|
1667
|
-
}
|
|
1668
|
-
},
|
|
1669
|
-
headers
|
|
1670
|
-
);
|
|
1671
|
-
}
|
|
1672
|
-
return createResponse(
|
|
1673
|
-
500,
|
|
1674
|
-
{
|
|
1675
|
-
error: {
|
|
1676
|
-
code: -32603,
|
|
1677
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1678
|
-
}
|
|
1679
|
-
},
|
|
1680
|
-
headers
|
|
1681
|
-
);
|
|
1682
|
-
}
|
|
1634
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1635
|
+
return createResponse(result.status, result.body, headers);
|
|
1683
1636
|
}
|
|
1684
1637
|
if (path === "/uninstall" && method === "POST") {
|
|
1685
|
-
if (!config.hooks?.uninstall) {
|
|
1686
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
1687
|
-
}
|
|
1688
1638
|
let uninstallBody;
|
|
1689
1639
|
try {
|
|
1690
1640
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1695,137 +1645,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1695
1645
|
headers
|
|
1696
1646
|
);
|
|
1697
1647
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
400,
|
|
1701
|
-
{
|
|
1702
|
-
error: {
|
|
1703
|
-
code: -32602,
|
|
1704
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1705
|
-
}
|
|
1706
|
-
},
|
|
1707
|
-
headers
|
|
1708
|
-
);
|
|
1709
|
-
}
|
|
1710
|
-
const uninstallContext = {
|
|
1711
|
-
env: uninstallBody.env ?? {},
|
|
1712
|
-
workplace: uninstallBody.context.workplace,
|
|
1713
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1714
|
-
app: uninstallBody.context.app,
|
|
1715
|
-
invocation: uninstallBody.invocation,
|
|
1716
|
-
log: createContextLogger()
|
|
1717
|
-
};
|
|
1718
|
-
const uninstallRequestConfig = {
|
|
1719
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1720
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1721
|
-
};
|
|
1722
|
-
try {
|
|
1723
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1724
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1725
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1726
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1727
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1728
|
-
});
|
|
1729
|
-
});
|
|
1730
|
-
return createResponse(
|
|
1731
|
-
200,
|
|
1732
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
1733
|
-
headers
|
|
1734
|
-
);
|
|
1735
|
-
} catch (err) {
|
|
1736
|
-
return createResponse(
|
|
1737
|
-
500,
|
|
1738
|
-
{
|
|
1739
|
-
error: {
|
|
1740
|
-
code: -32603,
|
|
1741
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1742
|
-
}
|
|
1743
|
-
},
|
|
1744
|
-
headers
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1648
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1649
|
+
return createResponse(result.status, result.body, headers);
|
|
1747
1650
|
}
|
|
1748
1651
|
if (path === "/provision" && method === "POST") {
|
|
1749
|
-
console.log("[serverless] /provision endpoint called");
|
|
1750
|
-
if (!config.hooks?.provision) {
|
|
1751
|
-
console.log("[serverless] No provision handler configured");
|
|
1752
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
1753
|
-
}
|
|
1754
1652
|
let provisionBody;
|
|
1755
1653
|
try {
|
|
1756
1654
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
1757
|
-
console.log("[serverless] Provision body parsed:", {
|
|
1758
|
-
hasEnv: !!provisionBody.env,
|
|
1759
|
-
hasContext: !!provisionBody.context,
|
|
1760
|
-
appId: provisionBody.context?.app?.id,
|
|
1761
|
-
versionId: provisionBody.context?.app?.versionId
|
|
1762
|
-
});
|
|
1763
1655
|
} catch {
|
|
1764
|
-
console.log("[serverless] Failed to parse provision body");
|
|
1765
1656
|
return createResponse(
|
|
1766
1657
|
400,
|
|
1767
1658
|
{ error: { code: -32700, message: "Parse error" } },
|
|
1768
1659
|
headers
|
|
1769
1660
|
);
|
|
1770
1661
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
return createResponse(
|
|
1774
|
-
400,
|
|
1775
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
1776
|
-
headers
|
|
1777
|
-
);
|
|
1778
|
-
}
|
|
1779
|
-
const mergedEnv = {};
|
|
1780
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1781
|
-
if (value !== void 0) {
|
|
1782
|
-
mergedEnv[key] = value;
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1786
|
-
const provisionContext = {
|
|
1787
|
-
env: mergedEnv,
|
|
1788
|
-
app: provisionBody.context.app,
|
|
1789
|
-
invocation: provisionBody.invocation,
|
|
1790
|
-
log: createContextLogger()
|
|
1791
|
-
};
|
|
1792
|
-
const provisionRequestConfig = {
|
|
1793
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1794
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1795
|
-
};
|
|
1796
|
-
console.log("[serverless] Calling provision handler...");
|
|
1797
|
-
try {
|
|
1798
|
-
const provisionHook = config.hooks.provision;
|
|
1799
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1800
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1801
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1802
|
-
return await provisionHandler(provisionContext);
|
|
1803
|
-
});
|
|
1804
|
-
});
|
|
1805
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
1806
|
-
return createResponse(200, result, headers);
|
|
1807
|
-
} catch (err) {
|
|
1808
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
1809
|
-
return createResponse(
|
|
1810
|
-
500,
|
|
1811
|
-
{
|
|
1812
|
-
error: {
|
|
1813
|
-
code: -32603,
|
|
1814
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1815
|
-
}
|
|
1816
|
-
},
|
|
1817
|
-
headers
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1662
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1663
|
+
return createResponse(result.status, result.body, headers);
|
|
1820
1664
|
}
|
|
1821
1665
|
if (path === "/oauth_callback" && method === "POST") {
|
|
1822
|
-
if (!config.hooks?.oauth_callback) {
|
|
1823
|
-
return createResponse(
|
|
1824
|
-
404,
|
|
1825
|
-
{ error: "OAuth callback handler not configured" },
|
|
1826
|
-
headers
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
1666
|
let parsedBody;
|
|
1830
1667
|
try {
|
|
1831
1668
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1837,70 +1674,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1837
1674
|
headers
|
|
1838
1675
|
);
|
|
1839
1676
|
}
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
1843
|
-
return createResponse(
|
|
1844
|
-
400,
|
|
1845
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
1846
|
-
headers
|
|
1847
|
-
);
|
|
1848
|
-
}
|
|
1849
|
-
const invocation = parsedBody.invocation;
|
|
1850
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
1851
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
1852
|
-
const oauthCallbackContext = {
|
|
1853
|
-
request: oauthRequest,
|
|
1854
|
-
invocation,
|
|
1855
|
-
log: createContextLogger()
|
|
1856
|
-
};
|
|
1857
|
-
try {
|
|
1858
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
1859
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
1860
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
1861
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
1862
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
1863
|
-
});
|
|
1864
|
-
});
|
|
1865
|
-
return createResponse(
|
|
1866
|
-
200,
|
|
1867
|
-
{
|
|
1868
|
-
appInstallationId: result.appInstallationId,
|
|
1869
|
-
env: result.env ?? {}
|
|
1870
|
-
},
|
|
1871
|
-
headers
|
|
1872
|
-
);
|
|
1873
|
-
} catch (err) {
|
|
1874
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
1875
|
-
return createResponse(
|
|
1876
|
-
500,
|
|
1877
|
-
{
|
|
1878
|
-
error: {
|
|
1879
|
-
code: -32603,
|
|
1880
|
-
message: errorMessage
|
|
1881
|
-
}
|
|
1882
|
-
},
|
|
1883
|
-
headers
|
|
1884
|
-
);
|
|
1885
|
-
}
|
|
1677
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1678
|
+
return createResponse(result.status, result.body, headers);
|
|
1886
1679
|
}
|
|
1887
1680
|
if (path === "/health" && method === "GET") {
|
|
1888
1681
|
return createResponse(200, state.getHealthStatus(), headers);
|
|
1889
1682
|
}
|
|
1890
1683
|
if (path === "/config" && method === "GET") {
|
|
1891
|
-
|
|
1892
|
-
if (!appConfig && config.appConfigLoader) {
|
|
1893
|
-
const loaded = await config.appConfigLoader();
|
|
1894
|
-
appConfig = loaded.default;
|
|
1895
|
-
}
|
|
1896
|
-
if (!appConfig) {
|
|
1897
|
-
appConfig = createMinimalConfig(
|
|
1898
|
-
config.metadata.name,
|
|
1899
|
-
config.metadata.version
|
|
1900
|
-
);
|
|
1901
|
-
}
|
|
1902
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
1903
|
-
return createResponse(200, serializedConfig, headers);
|
|
1684
|
+
return createResponse(200, serializeConfig(config), headers);
|
|
1904
1685
|
}
|
|
1905
1686
|
if (path === "/mcp" && method === "POST") {
|
|
1906
1687
|
let body;
|
|
@@ -2118,9 +1899,11 @@ console.log("[skedyul-node/server] All imports complete");
|
|
|
2118
1899
|
console.log("[skedyul-node/server] Installing context logger...");
|
|
2119
1900
|
installContextLogger();
|
|
2120
1901
|
console.log("[skedyul-node/server] Context logger installed");
|
|
2121
|
-
function createSkedyulServer(config
|
|
1902
|
+
function createSkedyulServer(config) {
|
|
2122
1903
|
console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
|
|
2123
1904
|
mergeRuntimeEnv();
|
|
1905
|
+
const registry = config.tools;
|
|
1906
|
+
const webhookRegistry = config.webhooks;
|
|
2124
1907
|
console.log("[createSkedyulServer] Step 2: coreApi setup");
|
|
2125
1908
|
if (config.coreApi?.service) {
|
|
2126
1909
|
coreApiService.register(config.coreApi.service);
|
|
@@ -2132,7 +1915,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2132
1915
|
const tools = buildToolMetadata(registry);
|
|
2133
1916
|
console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
|
|
2134
1917
|
const toolNames = Object.values(registry).map((tool) => tool.name);
|
|
2135
|
-
const runtimeLabel = config.computeLayer;
|
|
1918
|
+
const runtimeLabel = config.computeLayer ?? "serverless";
|
|
2136
1919
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
2137
1920
|
const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
|
|
2138
1921
|
console.log("[createSkedyulServer] Step 4: createRequestState()");
|
|
@@ -2145,8 +1928,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2145
1928
|
console.log("[createSkedyulServer] Step 4 done");
|
|
2146
1929
|
console.log("[createSkedyulServer] Step 5: new McpServer()");
|
|
2147
1930
|
const mcpServer = new McpServer({
|
|
2148
|
-
name: config.
|
|
2149
|
-
version: config.
|
|
1931
|
+
name: config.name,
|
|
1932
|
+
version: config.version ?? "0.0.0"
|
|
2150
1933
|
});
|
|
2151
1934
|
console.log("[createSkedyulServer] Step 5 done");
|
|
2152
1935
|
const dedicatedShutdown = () => {
|
|
@@ -2274,13 +2057,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
2274
2057
|
tools,
|
|
2275
2058
|
callTool,
|
|
2276
2059
|
state,
|
|
2277
|
-
mcpServer
|
|
2278
|
-
registry,
|
|
2279
|
-
webhookRegistry
|
|
2060
|
+
mcpServer
|
|
2280
2061
|
);
|
|
2281
2062
|
}
|
|
2282
2063
|
console.log("[createSkedyulServer] Creating serverless instance");
|
|
2283
|
-
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
2064
|
+
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
|
|
2284
2065
|
console.log("[createSkedyulServer] Serverless instance created successfully");
|
|
2285
2066
|
return serverlessInstance;
|
|
2286
2067
|
}
|
|
@@ -2288,32 +2069,10 @@ var server = {
|
|
|
2288
2069
|
create: createSkedyulServer
|
|
2289
2070
|
};
|
|
2290
2071
|
export {
|
|
2291
|
-
buildRequestFromRaw,
|
|
2292
|
-
buildRequestScopedConfig,
|
|
2293
|
-
buildToolMetadata,
|
|
2294
|
-
createCallToolHandler,
|
|
2295
|
-
createDedicatedServerInstance,
|
|
2296
|
-
createRequestState,
|
|
2297
|
-
createResponse,
|
|
2298
|
-
createServerlessInstance,
|
|
2299
2072
|
createSkedyulServer,
|
|
2300
|
-
getDefaultHeaders,
|
|
2301
2073
|
getJsonSchemaFromToolSchema,
|
|
2302
|
-
getListeningPort,
|
|
2303
2074
|
getZodSchema,
|
|
2304
|
-
handleCoreMethod,
|
|
2305
2075
|
isToolSchemaWithJson,
|
|
2306
|
-
mergeRuntimeEnv,
|
|
2307
|
-
normalizeBilling,
|
|
2308
|
-
padEnd,
|
|
2309
|
-
parseHandlerEnvelope,
|
|
2310
|
-
parseJSONBody,
|
|
2311
|
-
parseJsonRecord,
|
|
2312
|
-
parseNumberEnv,
|
|
2313
|
-
printStartupLog,
|
|
2314
|
-
readRawRequestBody,
|
|
2315
|
-
sendHTML,
|
|
2316
|
-
sendJSON,
|
|
2317
2076
|
server,
|
|
2318
2077
|
toJsonSchema
|
|
2319
2078
|
};
|