skedyul 0.2.151 → 0.2.153
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/.build-stamp +1 -1
- package/dist/server.js +109 -61
- package/dist/types.d.ts +15 -1
- package/package.json +1 -1
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1770699014045
|
package/dist/server.js
CHANGED
|
@@ -690,6 +690,79 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
690
690
|
}
|
|
691
691
|
return createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
|
|
692
692
|
}
|
|
693
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
694
|
+
// Shared Handler Helpers (used by both webhooks and OAuth callbacks)
|
|
695
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
696
|
+
/**
|
|
697
|
+
* Parses a handler envelope from the request body.
|
|
698
|
+
* Detects envelope format: { env: {...}, request: {...}, context?: {...} }
|
|
699
|
+
* Returns the extracted env and request, or null if not an envelope.
|
|
700
|
+
*/
|
|
701
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
702
|
+
// Check if parsedBody is an object with env and request properties
|
|
703
|
+
if (typeof parsedBody !== 'object' ||
|
|
704
|
+
parsedBody === null ||
|
|
705
|
+
Array.isArray(parsedBody) ||
|
|
706
|
+
!('env' in parsedBody) ||
|
|
707
|
+
!('request' in parsedBody)) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
const envelope = parsedBody;
|
|
711
|
+
// Validate env is an object (not null, not array)
|
|
712
|
+
if (typeof envelope.env !== 'object' ||
|
|
713
|
+
envelope.env === null ||
|
|
714
|
+
Array.isArray(envelope.env)) {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
// Validate request is an object (structure validation happens in buildRequestFromRaw)
|
|
718
|
+
if (typeof envelope.request !== 'object' ||
|
|
719
|
+
envelope.request === null ||
|
|
720
|
+
Array.isArray(envelope.request)) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
env: envelope.env,
|
|
725
|
+
request: envelope.request,
|
|
726
|
+
context: envelope.context,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Converts a raw HandlerRawRequest (wire format) to a rich WebhookRequest.
|
|
731
|
+
* Parses JSON body if content-type is application/json, creates Buffer rawBody.
|
|
732
|
+
*/
|
|
733
|
+
function buildRequestFromRaw(raw) {
|
|
734
|
+
// Parse the original request body
|
|
735
|
+
let parsedBody = raw.body;
|
|
736
|
+
const contentType = raw.headers['content-type'] ?? '';
|
|
737
|
+
if (contentType.includes('application/json')) {
|
|
738
|
+
try {
|
|
739
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
// Keep as string if JSON parsing fails
|
|
743
|
+
parsedBody = raw.body;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
method: raw.method,
|
|
748
|
+
url: raw.url,
|
|
749
|
+
path: raw.path,
|
|
750
|
+
headers: raw.headers,
|
|
751
|
+
query: raw.query,
|
|
752
|
+
body: parsedBody,
|
|
753
|
+
rawBody: raw.body ? Buffer.from(raw.body, 'utf-8') : undefined,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Builds request-scoped config by merging env from envelope with process.env fallbacks.
|
|
758
|
+
* Used for SKEDYUL_API_TOKEN and SKEDYUL_API_URL overrides.
|
|
759
|
+
*/
|
|
760
|
+
function buildRequestScopedConfig(env) {
|
|
761
|
+
return {
|
|
762
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
|
|
763
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
|
|
764
|
+
};
|
|
765
|
+
}
|
|
693
766
|
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, webhookRegistry) {
|
|
694
767
|
const port = getListeningPort(config);
|
|
695
768
|
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
@@ -742,49 +815,27 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
742
815
|
}
|
|
743
816
|
// Check if this is an envelope format from the platform
|
|
744
817
|
// Envelope format: { env: {...}, request: {...}, context: {...} }
|
|
745
|
-
const
|
|
746
|
-
parsedBody !== null &&
|
|
747
|
-
'env' in parsedBody &&
|
|
748
|
-
'request' in parsedBody &&
|
|
749
|
-
'context' in parsedBody);
|
|
818
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
750
819
|
let webhookRequest;
|
|
751
820
|
let webhookContext;
|
|
752
821
|
let requestEnv = {};
|
|
753
|
-
if (
|
|
754
|
-
// Platform envelope format -
|
|
755
|
-
const
|
|
756
|
-
requestEnv = envelope.env
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
try {
|
|
762
|
-
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
763
|
-
}
|
|
764
|
-
catch {
|
|
765
|
-
// Keep as string if JSON parsing fails
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
webhookRequest = {
|
|
769
|
-
method: envelope.request.method,
|
|
770
|
-
url: envelope.request.url,
|
|
771
|
-
path: envelope.request.path,
|
|
772
|
-
headers: envelope.request.headers,
|
|
773
|
-
query: envelope.request.query,
|
|
774
|
-
body: originalParsedBody,
|
|
775
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, 'utf-8') : undefined,
|
|
776
|
-
};
|
|
777
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
778
|
-
const app = envelope.context.app;
|
|
822
|
+
if (envelope && 'context' in envelope && envelope.context) {
|
|
823
|
+
// Platform envelope format - use shared helpers
|
|
824
|
+
const context = envelope.context;
|
|
825
|
+
requestEnv = envelope.env;
|
|
826
|
+
// Convert raw request to rich request using shared helper
|
|
827
|
+
webhookRequest = buildRequestFromRaw(envelope.request);
|
|
828
|
+
const envVars = { ...process.env, ...envelope.env };
|
|
829
|
+
const app = context.app;
|
|
779
830
|
// Build webhook context based on whether we have installation context
|
|
780
|
-
if (
|
|
831
|
+
if (context.appInstallationId && context.workplace) {
|
|
781
832
|
// Runtime webhook context
|
|
782
833
|
webhookContext = {
|
|
783
834
|
env: envVars,
|
|
784
835
|
app,
|
|
785
|
-
appInstallationId:
|
|
786
|
-
workplace:
|
|
787
|
-
registration:
|
|
836
|
+
appInstallationId: context.appInstallationId,
|
|
837
|
+
workplace: context.workplace,
|
|
838
|
+
registration: context.registration ?? {},
|
|
788
839
|
};
|
|
789
840
|
}
|
|
790
841
|
else {
|
|
@@ -823,10 +874,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
823
874
|
Object.assign(process.env, requestEnv);
|
|
824
875
|
// Build request-scoped config for the skedyul client
|
|
825
876
|
// This uses AsyncLocalStorage to override the global config (same pattern as tools)
|
|
826
|
-
const requestConfig =
|
|
827
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
|
|
828
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
|
|
829
|
-
};
|
|
877
|
+
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
830
878
|
// Invoke the handler with request-scoped config
|
|
831
879
|
let webhookResponse;
|
|
832
880
|
try {
|
|
@@ -905,9 +953,9 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
905
953
|
sendJSON(res, 404, { error: 'OAuth callback handler not configured' });
|
|
906
954
|
return;
|
|
907
955
|
}
|
|
908
|
-
let
|
|
956
|
+
let parsedBody;
|
|
909
957
|
try {
|
|
910
|
-
|
|
958
|
+
parsedBody = await parseJSONBody(req);
|
|
911
959
|
}
|
|
912
960
|
catch {
|
|
913
961
|
sendJSON(res, 400, {
|
|
@@ -915,20 +963,20 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
915
963
|
});
|
|
916
964
|
return;
|
|
917
965
|
}
|
|
918
|
-
|
|
966
|
+
// Parse envelope using shared helper
|
|
967
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
968
|
+
if (!envelope) {
|
|
919
969
|
sendJSON(res, 400, {
|
|
920
|
-
error: { code: -32602, message: 'Missing
|
|
970
|
+
error: { code: -32602, message: 'Missing envelope format: expected { env, request }' },
|
|
921
971
|
});
|
|
922
972
|
return;
|
|
923
973
|
}
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
apiToken: oauthCallbackBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
|
|
929
|
-
};
|
|
974
|
+
// Convert raw request to rich request using shared helper
|
|
975
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
976
|
+
// Build request-scoped config using shared helper
|
|
977
|
+
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
930
978
|
const oauthCallbackContext = {
|
|
931
|
-
|
|
979
|
+
request: oauthRequest,
|
|
932
980
|
};
|
|
933
981
|
try {
|
|
934
982
|
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
@@ -1576,24 +1624,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
1576
1624
|
if (!config.hooks?.oauth_callback) {
|
|
1577
1625
|
return createResponse(404, { error: 'OAuth callback handler not configured' }, headers);
|
|
1578
1626
|
}
|
|
1579
|
-
let
|
|
1627
|
+
let parsedBody;
|
|
1580
1628
|
try {
|
|
1581
|
-
|
|
1629
|
+
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
1582
1630
|
}
|
|
1583
1631
|
catch {
|
|
1584
1632
|
return createResponse(400, { error: { code: -32700, message: 'Parse error' } }, headers);
|
|
1585
1633
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1634
|
+
// Parse envelope using shared helper
|
|
1635
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
1636
|
+
if (!envelope) {
|
|
1637
|
+
return createResponse(400, { error: { code: -32602, message: 'Missing envelope format: expected { env, request }' } }, headers);
|
|
1588
1638
|
}
|
|
1589
|
-
//
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
apiToken: oauthCallbackBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
|
|
1594
|
-
};
|
|
1639
|
+
// Convert raw request to rich request using shared helper
|
|
1640
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
1641
|
+
// Build request-scoped config using shared helper
|
|
1642
|
+
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
1595
1643
|
const oauthCallbackContext = {
|
|
1596
|
-
|
|
1644
|
+
request: oauthRequest,
|
|
1597
1645
|
};
|
|
1598
1646
|
try {
|
|
1599
1647
|
const oauthCallbackHook = config.hooks.oauth_callback;
|
package/dist/types.d.ts
CHANGED
|
@@ -274,7 +274,8 @@ export type HasOAuthCallback<Hooks extends ServerHooks> = Hooks extends {
|
|
|
274
274
|
export type InstallHandlerResult<Hooks extends ServerHooks = ServerHooks> = HasOAuthCallback<Hooks> extends true ? InstallHandlerResponseOAuth : InstallHandlerResponseStandard;
|
|
275
275
|
export type InstallHandler<Hooks extends ServerHooks = ServerHooks> = (ctx: InstallHandlerContext) => Promise<InstallHandlerResult<Hooks>>;
|
|
276
276
|
export interface OAuthCallbackContext {
|
|
277
|
-
|
|
277
|
+
/** Full HTTP request from the OAuth provider */
|
|
278
|
+
request: WebhookRequest;
|
|
278
279
|
}
|
|
279
280
|
export interface OAuthCallbackResult {
|
|
280
281
|
env?: Record<string, string>;
|
|
@@ -322,6 +323,19 @@ export interface ServerlessServerInstance {
|
|
|
322
323
|
getHealthStatus(): HealthStatus;
|
|
323
324
|
}
|
|
324
325
|
export type SkedyulServerInstance = DedicatedServerInstance | ServerlessServerInstance;
|
|
326
|
+
/**
|
|
327
|
+
* Raw HTTP request shape sent over the wire in handler envelopes.
|
|
328
|
+
* This is the wire format used by both webhooks and OAuth callbacks.
|
|
329
|
+
* It gets converted to the rich WebhookRequest type at parse time.
|
|
330
|
+
*/
|
|
331
|
+
export interface HandlerRawRequest {
|
|
332
|
+
method: string;
|
|
333
|
+
url: string;
|
|
334
|
+
path: string;
|
|
335
|
+
headers: Record<string, string>;
|
|
336
|
+
query: Record<string, string>;
|
|
337
|
+
body: string;
|
|
338
|
+
}
|
|
325
339
|
/** Raw HTTP request received by webhooks */
|
|
326
340
|
export interface WebhookRequest {
|
|
327
341
|
method: string;
|