skedyul 0.3.0 → 0.3.2

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.
Files changed (77) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/config/app-config.d.ts +73 -0
  3. package/dist/config/app-config.js +12 -0
  4. package/dist/config/index.d.ts +9 -0
  5. package/dist/config/index.js +33 -0
  6. package/dist/config/loader.d.ts +7 -0
  7. package/dist/config/loader.js +119 -0
  8. package/dist/config/types/agent.d.ts +29 -0
  9. package/dist/config/types/agent.js +5 -0
  10. package/dist/config/types/channel.d.ts +46 -0
  11. package/dist/config/types/channel.js +2 -0
  12. package/dist/config/types/compute.d.ts +1 -0
  13. package/dist/config/types/compute.js +5 -0
  14. package/dist/config/types/env.d.ts +16 -0
  15. package/dist/config/types/env.js +5 -0
  16. package/dist/config/types/index.d.ts +9 -0
  17. package/dist/config/types/index.js +26 -0
  18. package/dist/config/types/model.d.ts +62 -0
  19. package/dist/config/types/model.js +2 -0
  20. package/dist/config/types/page.d.ts +436 -0
  21. package/dist/config/types/page.js +5 -0
  22. package/dist/config/types/resource.d.ts +30 -0
  23. package/dist/config/types/resource.js +5 -0
  24. package/dist/config/types/webhook.d.ts +35 -0
  25. package/dist/config/types/webhook.js +5 -0
  26. package/dist/config/types/workflow.d.ts +24 -0
  27. package/dist/config/types/workflow.js +2 -0
  28. package/dist/config/utils.d.ts +16 -0
  29. package/dist/config/utils.js +37 -0
  30. package/dist/config.d.ts +5 -767
  31. package/dist/config.js +11 -151
  32. package/dist/schemas.d.ts +43 -43
  33. package/dist/server/core-api-handler.d.ts +8 -0
  34. package/dist/server/core-api-handler.js +148 -0
  35. package/dist/server/dedicated.d.ts +7 -0
  36. package/dist/server/dedicated.js +610 -0
  37. package/dist/server/handler-helpers.d.ts +24 -0
  38. package/dist/server/handler-helpers.js +75 -0
  39. package/dist/server/index.d.ts +19 -0
  40. package/dist/server/index.js +196 -0
  41. package/dist/server/serverless.d.ts +7 -0
  42. package/dist/server/serverless.js +629 -0
  43. package/dist/server/startup-logger.d.ts +9 -0
  44. package/dist/server/startup-logger.js +113 -0
  45. package/dist/server/tool-handler.d.ts +14 -0
  46. package/dist/server/tool-handler.js +189 -0
  47. package/dist/server/types.d.ts +22 -0
  48. package/dist/server/types.js +2 -0
  49. package/dist/server/utils/env.d.ts +12 -0
  50. package/dist/server/utils/env.js +38 -0
  51. package/dist/server/utils/http.d.ts +30 -0
  52. package/dist/server/utils/http.js +81 -0
  53. package/dist/server/utils/index.d.ts +3 -0
  54. package/dist/server/utils/index.js +24 -0
  55. package/dist/server/utils/schema.d.ts +22 -0
  56. package/dist/server/utils/schema.js +102 -0
  57. package/dist/server.d.ts +7 -11
  58. package/dist/server.js +39 -2026
  59. package/dist/types/aws.d.ts +15 -0
  60. package/dist/types/aws.js +5 -0
  61. package/dist/types/handlers.d.ts +122 -0
  62. package/dist/types/handlers.js +2 -0
  63. package/dist/types/index.d.ts +16 -0
  64. package/dist/types/index.js +16 -0
  65. package/dist/types/server.d.ts +43 -0
  66. package/dist/types/server.js +2 -0
  67. package/dist/types/shared.d.ts +16 -0
  68. package/dist/types/shared.js +5 -0
  69. package/dist/types/tool-context.d.ts +64 -0
  70. package/dist/types/tool-context.js +12 -0
  71. package/dist/types/tool.d.ts +96 -0
  72. package/dist/types/tool.js +19 -0
  73. package/dist/types/webhook.d.ts +116 -0
  74. package/dist/types/webhook.js +7 -0
  75. package/dist/types.d.ts +4 -461
  76. package/dist/types.js +21 -31
  77. package/package.json +2 -2
@@ -0,0 +1,610 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDedicatedServerInstance = createDedicatedServerInstance;
7
+ const http_1 = __importDefault(require("http"));
8
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
9
+ const service_1 = require("../core/service");
10
+ const client_1 = require("../core/client");
11
+ const errors_1 = require("../errors");
12
+ const core_api_handler_1 = require("./core-api-handler");
13
+ const handler_helpers_1 = require("./handler-helpers");
14
+ const startup_logger_1 = require("./startup-logger");
15
+ const utils_1 = require("./utils");
16
+ /**
17
+ * Creates a dedicated (long-running HTTP) server instance
18
+ */
19
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, webhookRegistry) {
20
+ const port = (0, utils_1.getListeningPort)(config);
21
+ const httpServer = http_1.default.createServer(async (req, res) => {
22
+ function sendCoreResult(result) {
23
+ (0, utils_1.sendJSON)(res, result.status, result.payload);
24
+ }
25
+ try {
26
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
27
+ const pathname = url.pathname;
28
+ if (pathname === '/health' && req.method === 'GET') {
29
+ (0, utils_1.sendJSON)(res, 200, state.getHealthStatus());
30
+ return;
31
+ }
32
+ // Handle webhook requests: /webhooks/{handle}
33
+ if (pathname.startsWith('/webhooks/') && webhookRegistry) {
34
+ const handle = pathname.slice('/webhooks/'.length);
35
+ const webhookDef = webhookRegistry[handle];
36
+ if (!webhookDef) {
37
+ (0, utils_1.sendJSON)(res, 404, { error: `Webhook handler '${handle}' not found` });
38
+ return;
39
+ }
40
+ // Check if HTTP method is allowed
41
+ const allowedMethods = webhookDef.methods ?? ['POST'];
42
+ if (!allowedMethods.includes(req.method)) {
43
+ (0, utils_1.sendJSON)(res, 405, { error: `Method ${req.method} not allowed` });
44
+ return;
45
+ }
46
+ // Read raw request body
47
+ let rawBody;
48
+ try {
49
+ rawBody = await (0, utils_1.readRawRequestBody)(req);
50
+ }
51
+ catch {
52
+ (0, utils_1.sendJSON)(res, 400, { error: 'Failed to read request body' });
53
+ return;
54
+ }
55
+ // Parse body based on content type
56
+ let parsedBody;
57
+ const contentType = req.headers['content-type'] ?? '';
58
+ if (contentType.includes('application/json')) {
59
+ try {
60
+ parsedBody = rawBody ? JSON.parse(rawBody) : {};
61
+ }
62
+ catch {
63
+ parsedBody = rawBody;
64
+ }
65
+ }
66
+ else {
67
+ parsedBody = rawBody;
68
+ }
69
+ // Check if this is an envelope format from the platform
70
+ // Envelope format: { env: {...}, request: {...}, context: {...} }
71
+ const envelope = (0, handler_helpers_1.parseHandlerEnvelope)(parsedBody);
72
+ let webhookRequest;
73
+ let webhookContext;
74
+ let requestEnv = {};
75
+ if (envelope && 'context' in envelope && envelope.context) {
76
+ // Platform envelope format - use shared helpers
77
+ const context = envelope.context;
78
+ requestEnv = envelope.env;
79
+ // Convert raw request to rich request using shared helper
80
+ webhookRequest = (0, handler_helpers_1.buildRequestFromRaw)(envelope.request);
81
+ const envVars = { ...process.env, ...envelope.env };
82
+ const app = context.app;
83
+ // Build webhook context based on whether we have installation context
84
+ if (context.appInstallationId && context.workplace) {
85
+ // Runtime webhook context
86
+ webhookContext = {
87
+ env: envVars,
88
+ app,
89
+ appInstallationId: context.appInstallationId,
90
+ workplace: context.workplace,
91
+ registration: context.registration ?? {},
92
+ };
93
+ }
94
+ else {
95
+ // Provision webhook context
96
+ webhookContext = {
97
+ env: envVars,
98
+ app,
99
+ };
100
+ }
101
+ }
102
+ else {
103
+ // Direct request format (legacy or direct calls) - requires app info from headers or fail
104
+ const appId = req.headers['x-skedyul-app-id'];
105
+ const appVersionId = req.headers['x-skedyul-app-version-id'];
106
+ if (!appId || !appVersionId) {
107
+ throw new Error('Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)');
108
+ }
109
+ webhookRequest = {
110
+ method: req.method ?? 'POST',
111
+ url: url.toString(),
112
+ path: pathname,
113
+ headers: req.headers,
114
+ query: Object.fromEntries(url.searchParams.entries()),
115
+ body: parsedBody,
116
+ rawBody: rawBody ? Buffer.from(rawBody, 'utf-8') : undefined,
117
+ };
118
+ // Direct calls are provision-level (no installation context)
119
+ webhookContext = {
120
+ env: process.env,
121
+ app: { id: appId, versionId: appVersionId },
122
+ };
123
+ }
124
+ // Temporarily inject env into process.env for skedyul client to use
125
+ // (same pattern as tool handler)
126
+ const originalEnv = { ...process.env };
127
+ Object.assign(process.env, requestEnv);
128
+ // Build request-scoped config for the skedyul client
129
+ // This uses AsyncLocalStorage to override the global config (same pattern as tools)
130
+ const requestConfig = (0, handler_helpers_1.buildRequestScopedConfig)(requestEnv);
131
+ // Invoke the handler with request-scoped config
132
+ let webhookResponse;
133
+ try {
134
+ webhookResponse = await (0, client_1.runWithConfig)(requestConfig, async () => {
135
+ return await webhookDef.handler(webhookRequest, webhookContext);
136
+ });
137
+ }
138
+ catch (err) {
139
+ console.error(`Webhook handler '${handle}' error:`, err);
140
+ (0, utils_1.sendJSON)(res, 500, { error: 'Webhook handler error' });
141
+ return;
142
+ }
143
+ finally {
144
+ // Restore original env
145
+ process.env = originalEnv;
146
+ }
147
+ // Send response
148
+ const status = webhookResponse.status ?? 200;
149
+ const responseHeaders = {
150
+ ...webhookResponse.headers,
151
+ };
152
+ // Default to JSON content type if not specified
153
+ if (!responseHeaders['Content-Type'] && !responseHeaders['content-type']) {
154
+ responseHeaders['Content-Type'] = 'application/json';
155
+ }
156
+ res.writeHead(status, responseHeaders);
157
+ if (webhookResponse.body !== undefined) {
158
+ if (typeof webhookResponse.body === 'string') {
159
+ res.end(webhookResponse.body);
160
+ }
161
+ else {
162
+ res.end(JSON.stringify(webhookResponse.body));
163
+ }
164
+ }
165
+ else {
166
+ res.end();
167
+ }
168
+ return;
169
+ }
170
+ if (pathname === '/estimate' && req.method === 'POST') {
171
+ let estimateBody;
172
+ try {
173
+ estimateBody = (await (0, utils_1.parseJSONBody)(req));
174
+ }
175
+ catch {
176
+ (0, utils_1.sendJSON)(res, 400, {
177
+ error: {
178
+ code: -32700,
179
+ message: 'Parse error',
180
+ },
181
+ });
182
+ return;
183
+ }
184
+ try {
185
+ const estimateResponse = await callTool(estimateBody.name, {
186
+ inputs: estimateBody.inputs,
187
+ estimate: true,
188
+ });
189
+ (0, utils_1.sendJSON)(res, 200, {
190
+ billing: estimateResponse.billing ?? { credits: 0 },
191
+ });
192
+ }
193
+ catch (err) {
194
+ (0, utils_1.sendJSON)(res, 500, {
195
+ error: {
196
+ code: -32603,
197
+ message: err instanceof Error ? err.message : String(err ?? ''),
198
+ },
199
+ });
200
+ }
201
+ return;
202
+ }
203
+ // Handle /oauth_callback endpoint for OAuth callbacks (called by Temporal workflow)
204
+ if (pathname === '/oauth_callback' && req.method === 'POST') {
205
+ if (!config.hooks?.oauth_callback) {
206
+ (0, utils_1.sendJSON)(res, 404, { error: 'OAuth callback handler not configured' });
207
+ return;
208
+ }
209
+ let parsedBody;
210
+ try {
211
+ parsedBody = await (0, utils_1.parseJSONBody)(req);
212
+ }
213
+ catch (err) {
214
+ console.error('[OAuth Callback] Failed to parse JSON body:', err);
215
+ (0, utils_1.sendJSON)(res, 400, {
216
+ error: { code: -32700, message: 'Parse error' },
217
+ });
218
+ return;
219
+ }
220
+ // Parse envelope using shared helper
221
+ const envelope = (0, handler_helpers_1.parseHandlerEnvelope)(parsedBody);
222
+ if (!envelope) {
223
+ console.error('[OAuth Callback] Failed to parse envelope. Body:', JSON.stringify(parsedBody, null, 2));
224
+ (0, utils_1.sendJSON)(res, 400, {
225
+ error: { code: -32602, message: 'Missing envelope format: expected { env, request }' },
226
+ });
227
+ return;
228
+ }
229
+ // Convert raw request to rich request using shared helper
230
+ const oauthRequest = (0, handler_helpers_1.buildRequestFromRaw)(envelope.request);
231
+ // Build request-scoped config using shared helper
232
+ const oauthCallbackRequestConfig = (0, handler_helpers_1.buildRequestScopedConfig)(envelope.env);
233
+ const oauthCallbackContext = {
234
+ request: oauthRequest,
235
+ };
236
+ try {
237
+ const oauthCallbackHook = config.hooks.oauth_callback;
238
+ const oauthCallbackHandler = typeof oauthCallbackHook === 'function'
239
+ ? oauthCallbackHook
240
+ : oauthCallbackHook.handler;
241
+ const result = await (0, client_1.runWithConfig)(oauthCallbackRequestConfig, async () => {
242
+ return await oauthCallbackHandler(oauthCallbackContext);
243
+ });
244
+ (0, utils_1.sendJSON)(res, 200, {
245
+ appInstallationId: result.appInstallationId,
246
+ env: result.env ?? {},
247
+ });
248
+ }
249
+ catch (err) {
250
+ const errorMessage = err instanceof Error ? err.message : String(err ?? 'Unknown error');
251
+ (0, utils_1.sendJSON)(res, 500, {
252
+ error: {
253
+ code: -32603,
254
+ message: errorMessage,
255
+ },
256
+ });
257
+ }
258
+ return;
259
+ }
260
+ // Handle /install endpoint for install handlers
261
+ if (pathname === '/install' && req.method === 'POST') {
262
+ if (!config.hooks?.install) {
263
+ (0, utils_1.sendJSON)(res, 404, { error: 'Install handler not configured' });
264
+ return;
265
+ }
266
+ let installBody;
267
+ try {
268
+ installBody = (await (0, utils_1.parseJSONBody)(req));
269
+ }
270
+ catch {
271
+ (0, utils_1.sendJSON)(res, 400, {
272
+ error: { code: -32700, message: 'Parse error' },
273
+ });
274
+ return;
275
+ }
276
+ if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
277
+ (0, utils_1.sendJSON)(res, 400, {
278
+ error: { code: -32602, message: 'Missing context (appInstallationId and workplace required)' },
279
+ });
280
+ return;
281
+ }
282
+ const installContext = {
283
+ env: installBody.env ?? {},
284
+ workplace: installBody.context.workplace,
285
+ appInstallationId: installBody.context.appInstallationId,
286
+ app: installBody.context.app,
287
+ };
288
+ // Build request-scoped config for SDK access
289
+ // Use env from request body (contains generated token from workflow)
290
+ const installRequestConfig = {
291
+ baseUrl: installBody.env?.SKEDYUL_API_URL ??
292
+ process.env.SKEDYUL_API_URL ??
293
+ '',
294
+ apiToken: installBody.env?.SKEDYUL_API_TOKEN ??
295
+ process.env.SKEDYUL_API_TOKEN ??
296
+ '',
297
+ };
298
+ try {
299
+ const installHook = config.hooks.install;
300
+ const installHandler = typeof installHook === 'function'
301
+ ? installHook
302
+ : installHook.handler;
303
+ const result = await (0, client_1.runWithConfig)(installRequestConfig, async () => {
304
+ return await installHandler(installContext);
305
+ });
306
+ (0, utils_1.sendJSON)(res, 200, {
307
+ env: result.env ?? {},
308
+ redirect: result.redirect,
309
+ });
310
+ }
311
+ catch (err) {
312
+ // Check for typed install errors
313
+ if (err instanceof errors_1.InstallError) {
314
+ (0, utils_1.sendJSON)(res, 400, {
315
+ error: {
316
+ code: err.code,
317
+ message: err.message,
318
+ field: err.field,
319
+ },
320
+ });
321
+ }
322
+ else {
323
+ (0, utils_1.sendJSON)(res, 500, {
324
+ error: {
325
+ code: -32603,
326
+ message: err instanceof Error ? err.message : String(err ?? ''),
327
+ },
328
+ });
329
+ }
330
+ }
331
+ return;
332
+ }
333
+ // Handle /uninstall endpoint for uninstall handlers
334
+ if (pathname === '/uninstall' && req.method === 'POST') {
335
+ if (!config.hooks?.uninstall) {
336
+ (0, utils_1.sendJSON)(res, 404, { error: 'Uninstall handler not configured' });
337
+ return;
338
+ }
339
+ let uninstallBody;
340
+ try {
341
+ uninstallBody = (await (0, utils_1.parseJSONBody)(req));
342
+ }
343
+ catch {
344
+ (0, utils_1.sendJSON)(res, 400, {
345
+ error: { code: -32700, message: 'Parse error' },
346
+ });
347
+ return;
348
+ }
349
+ if (!uninstallBody.context?.appInstallationId ||
350
+ !uninstallBody.context?.workplace ||
351
+ !uninstallBody.context?.app) {
352
+ (0, utils_1.sendJSON)(res, 400, {
353
+ error: {
354
+ code: -32602,
355
+ message: 'Missing context (appInstallationId, workplace and app required)',
356
+ },
357
+ });
358
+ return;
359
+ }
360
+ const uninstallContext = {
361
+ env: uninstallBody.env ?? {},
362
+ workplace: uninstallBody.context.workplace,
363
+ appInstallationId: uninstallBody.context.appInstallationId,
364
+ app: uninstallBody.context.app,
365
+ };
366
+ const uninstallRequestConfig = {
367
+ baseUrl: uninstallBody.env?.SKEDYUL_API_URL ??
368
+ process.env.SKEDYUL_API_URL ??
369
+ '',
370
+ apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ??
371
+ process.env.SKEDYUL_API_TOKEN ??
372
+ '',
373
+ };
374
+ try {
375
+ const uninstallHook = config.hooks.uninstall;
376
+ const uninstallHandlerFn = typeof uninstallHook === 'function' ? uninstallHook : uninstallHook.handler;
377
+ const result = await (0, client_1.runWithConfig)(uninstallRequestConfig, async () => {
378
+ return await uninstallHandlerFn(uninstallContext);
379
+ });
380
+ (0, utils_1.sendJSON)(res, 200, {
381
+ cleanedWebhookIds: result.cleanedWebhookIds ?? [],
382
+ });
383
+ }
384
+ catch (err) {
385
+ (0, utils_1.sendJSON)(res, 500, {
386
+ error: {
387
+ code: -32603,
388
+ message: err instanceof Error ? err.message : String(err ?? ''),
389
+ },
390
+ });
391
+ }
392
+ return;
393
+ }
394
+ // Handle /provision endpoint for provision handlers
395
+ if (pathname === '/provision' && req.method === 'POST') {
396
+ if (!config.hooks?.provision) {
397
+ (0, utils_1.sendJSON)(res, 404, { error: 'Provision handler not configured' });
398
+ return;
399
+ }
400
+ let provisionBody;
401
+ try {
402
+ provisionBody = (await (0, utils_1.parseJSONBody)(req));
403
+ }
404
+ catch {
405
+ (0, utils_1.sendJSON)(res, 400, {
406
+ error: { code: -32700, message: 'Parse error' },
407
+ });
408
+ return;
409
+ }
410
+ if (!provisionBody.context?.app) {
411
+ (0, utils_1.sendJSON)(res, 400, {
412
+ error: { code: -32602, message: 'Missing context (app required)' },
413
+ });
414
+ return;
415
+ }
416
+ // SECURITY: Merge process.env (baked-in secrets) with request env (API token).
417
+ // This ensures secrets like MAILGUN_API_KEY come from the container,
418
+ // while runtime values like SKEDYUL_API_TOKEN come from the request.
419
+ const mergedEnv = {};
420
+ for (const [key, value] of Object.entries(process.env)) {
421
+ if (value !== undefined) {
422
+ mergedEnv[key] = value;
423
+ }
424
+ }
425
+ // Request env overrides process.env (e.g., for SKEDYUL_API_TOKEN)
426
+ Object.assign(mergedEnv, provisionBody.env ?? {});
427
+ const provisionContext = {
428
+ env: mergedEnv,
429
+ app: provisionBody.context.app,
430
+ };
431
+ // Build request-scoped config for SDK access
432
+ // Use merged env for consistency
433
+ const provisionRequestConfig = {
434
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? '',
435
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? '',
436
+ };
437
+ try {
438
+ const provisionHook = config.hooks.provision;
439
+ const provisionHandler = typeof provisionHook === 'function'
440
+ ? provisionHook
441
+ : provisionHook.handler;
442
+ const result = await (0, client_1.runWithConfig)(provisionRequestConfig, async () => {
443
+ return await provisionHandler(provisionContext);
444
+ });
445
+ (0, utils_1.sendJSON)(res, 200, result);
446
+ }
447
+ catch (err) {
448
+ (0, utils_1.sendJSON)(res, 500, {
449
+ error: {
450
+ code: -32603,
451
+ message: err instanceof Error ? err.message : String(err ?? ''),
452
+ },
453
+ });
454
+ }
455
+ return;
456
+ }
457
+ if (pathname === '/core' && req.method === 'POST') {
458
+ let coreBody;
459
+ try {
460
+ coreBody = (await (0, utils_1.parseJSONBody)(req));
461
+ }
462
+ catch {
463
+ (0, utils_1.sendJSON)(res, 400, {
464
+ error: {
465
+ code: -32700,
466
+ message: 'Parse error',
467
+ },
468
+ });
469
+ return;
470
+ }
471
+ if (!coreBody?.method) {
472
+ (0, utils_1.sendJSON)(res, 400, {
473
+ error: {
474
+ code: -32602,
475
+ message: 'Missing method',
476
+ },
477
+ });
478
+ return;
479
+ }
480
+ const method = coreBody.method;
481
+ const result = await (0, core_api_handler_1.handleCoreMethod)(method, coreBody.params);
482
+ sendCoreResult(result);
483
+ return;
484
+ }
485
+ if (pathname === '/core/webhook' && req.method === 'POST') {
486
+ let rawWebhookBody;
487
+ try {
488
+ rawWebhookBody = await (0, utils_1.readRawRequestBody)(req);
489
+ }
490
+ catch {
491
+ (0, utils_1.sendJSON)(res, 400, { status: 'parse-error' });
492
+ return;
493
+ }
494
+ let webhookBody;
495
+ try {
496
+ webhookBody = rawWebhookBody ? JSON.parse(rawWebhookBody) : {};
497
+ }
498
+ catch {
499
+ (0, utils_1.sendJSON)(res, 400, { status: 'parse-error' });
500
+ return;
501
+ }
502
+ const normalizedHeaders = Object.fromEntries(Object.entries(req.headers).map(([key, value]) => [
503
+ key,
504
+ typeof value === 'string' ? value : value?.[0] ?? '',
505
+ ]));
506
+ const coreWebhookRequest = {
507
+ method: req.method ?? 'POST',
508
+ headers: normalizedHeaders,
509
+ body: webhookBody,
510
+ query: Object.fromEntries(url.searchParams.entries()),
511
+ url: url.toString(),
512
+ path: url.pathname,
513
+ rawBody: rawWebhookBody
514
+ ? Buffer.from(rawWebhookBody, 'utf-8')
515
+ : undefined,
516
+ };
517
+ const webhookResponse = await service_1.coreApiService.dispatchWebhook(coreWebhookRequest);
518
+ res.writeHead(webhookResponse.status, {
519
+ 'Content-Type': 'application/json',
520
+ });
521
+ res.end(JSON.stringify(webhookResponse.body ?? {}));
522
+ return;
523
+ }
524
+ if (pathname === '/mcp' && req.method === 'POST') {
525
+ try {
526
+ const body = await (0, utils_1.parseJSONBody)(req);
527
+ // Handle tools/list directly to include custom metadata (timeout, displayName, outputSchema)
528
+ // The MCP SDK only returns standard fields, so we intercept and return the full metadata
529
+ if (body?.method === 'tools/list') {
530
+ (0, utils_1.sendJSON)(res, 200, {
531
+ jsonrpc: '2.0',
532
+ id: body.id ?? null,
533
+ result: { tools },
534
+ });
535
+ return;
536
+ }
537
+ // Handle webhooks/list before passing to MCP SDK transport
538
+ if (body?.method === 'webhooks/list') {
539
+ const webhooks = webhookRegistry
540
+ ? Object.values(webhookRegistry).map((w) => ({
541
+ name: w.name,
542
+ description: w.description,
543
+ methods: w.methods ?? ['POST'],
544
+ type: w.type ?? 'WEBHOOK',
545
+ }))
546
+ : [];
547
+ (0, utils_1.sendJSON)(res, 200, {
548
+ jsonrpc: '2.0',
549
+ id: body.id ?? null,
550
+ result: { webhooks },
551
+ });
552
+ return;
553
+ }
554
+ // Pass to MCP SDK transport for standard MCP methods (tools/call, etc.)
555
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
556
+ sessionIdGenerator: undefined,
557
+ enableJsonResponse: true,
558
+ });
559
+ res.on('close', () => {
560
+ transport.close();
561
+ });
562
+ await mcpServer.connect(transport);
563
+ await transport.handleRequest(req, res, body);
564
+ }
565
+ catch (err) {
566
+ (0, utils_1.sendJSON)(res, 500, {
567
+ jsonrpc: '2.0',
568
+ id: null,
569
+ error: {
570
+ code: -32603,
571
+ message: err instanceof Error ? err.message : String(err ?? ''),
572
+ },
573
+ });
574
+ }
575
+ return;
576
+ }
577
+ (0, utils_1.sendJSON)(res, 404, {
578
+ jsonrpc: '2.0',
579
+ id: null,
580
+ error: {
581
+ code: -32601,
582
+ message: 'Not Found',
583
+ },
584
+ });
585
+ }
586
+ catch (err) {
587
+ (0, utils_1.sendJSON)(res, 500, {
588
+ jsonrpc: '2.0',
589
+ id: null,
590
+ error: {
591
+ code: -32603,
592
+ message: err instanceof Error ? err.message : String(err ?? ''),
593
+ },
594
+ });
595
+ }
596
+ });
597
+ return {
598
+ async listen(listenPort) {
599
+ const finalPort = listenPort ?? port;
600
+ return new Promise((resolve, reject) => {
601
+ httpServer.listen(finalPort, () => {
602
+ (0, startup_logger_1.printStartupLog)(config, tools, webhookRegistry, finalPort);
603
+ resolve();
604
+ });
605
+ httpServer.once('error', reject);
606
+ });
607
+ },
608
+ getHealthStatus: () => state.getHealthStatus(),
609
+ };
610
+ }
@@ -0,0 +1,24 @@
1
+ import type { HandlerRawRequest, WebhookRequest } from '../types';
2
+ /**
3
+ * Parses a handler envelope from the request body.
4
+ * Detects envelope format: { env: {...}, request: {...}, context?: {...} }
5
+ * Returns the extracted env and request, or null if not an envelope.
6
+ */
7
+ export declare function parseHandlerEnvelope(parsedBody: unknown): {
8
+ env: Record<string, string>;
9
+ request: HandlerRawRequest;
10
+ context?: unknown;
11
+ } | null;
12
+ /**
13
+ * Converts a raw HandlerRawRequest (wire format) to a rich WebhookRequest.
14
+ * Parses JSON body if content-type is application/json, creates Buffer rawBody.
15
+ */
16
+ export declare function buildRequestFromRaw(raw: HandlerRawRequest): WebhookRequest;
17
+ /**
18
+ * Builds request-scoped config by merging env from envelope with process.env fallbacks.
19
+ * Used for SKEDYUL_API_TOKEN and SKEDYUL_API_URL overrides.
20
+ */
21
+ export declare function buildRequestScopedConfig(env: Record<string, string>): {
22
+ baseUrl: string;
23
+ apiToken: string;
24
+ };