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,629 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServerlessInstance = createServerlessInstance;
4
+ const service_1 = require("../core/service");
5
+ const client_1 = require("../core/client");
6
+ const errors_1 = require("../errors");
7
+ const core_api_handler_1 = require("./core-api-handler");
8
+ const handler_helpers_1 = require("./handler-helpers");
9
+ const startup_logger_1 = require("./startup-logger");
10
+ const utils_1 = require("./utils");
11
+ /**
12
+ * Creates a serverless (Lambda-style) server instance
13
+ */
14
+ function createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
15
+ const headers = (0, utils_1.getDefaultHeaders)(config.cors);
16
+ // Print startup log once on cold start
17
+ let hasLoggedStartup = false;
18
+ return {
19
+ async handler(event) {
20
+ // Log startup info on first invocation (cold start)
21
+ if (!hasLoggedStartup) {
22
+ (0, startup_logger_1.printStartupLog)(config, tools, webhookRegistry);
23
+ hasLoggedStartup = true;
24
+ }
25
+ try {
26
+ const path = event.path;
27
+ const method = event.httpMethod;
28
+ if (method === 'OPTIONS') {
29
+ return (0, utils_1.createResponse)(200, { message: 'OK' }, headers);
30
+ }
31
+ // Handle webhook requests: /webhooks/{handle}
32
+ if (path.startsWith('/webhooks/') && webhookRegistry) {
33
+ const handle = path.slice('/webhooks/'.length);
34
+ const webhookDef = webhookRegistry[handle];
35
+ if (!webhookDef) {
36
+ return (0, utils_1.createResponse)(404, { error: `Webhook handler '${handle}' not found` }, headers);
37
+ }
38
+ // Check if HTTP method is allowed
39
+ const allowedMethods = webhookDef.methods ?? ['POST'];
40
+ if (!allowedMethods.includes(method)) {
41
+ return (0, utils_1.createResponse)(405, { error: `Method ${method} not allowed` }, headers);
42
+ }
43
+ // Get raw body
44
+ const rawBody = event.body ?? '';
45
+ // Parse body based on content type
46
+ let parsedBody;
47
+ const contentType = event.headers?.['content-type'] ?? event.headers?.['Content-Type'] ?? '';
48
+ if (contentType.includes('application/json')) {
49
+ try {
50
+ parsedBody = rawBody ? JSON.parse(rawBody) : {};
51
+ }
52
+ catch {
53
+ parsedBody = rawBody;
54
+ }
55
+ }
56
+ else {
57
+ parsedBody = rawBody;
58
+ }
59
+ // Check if this is an envelope format from the platform
60
+ // Envelope format: { env: {...}, request: {...}, context: {...} }
61
+ const isEnvelope = (typeof parsedBody === 'object' &&
62
+ parsedBody !== null &&
63
+ 'env' in parsedBody &&
64
+ 'request' in parsedBody &&
65
+ 'context' in parsedBody);
66
+ let webhookRequest;
67
+ let webhookContext;
68
+ let requestEnv = {};
69
+ if (isEnvelope) {
70
+ // Platform envelope format - extract env, request, and context
71
+ const envelope = parsedBody;
72
+ requestEnv = envelope.env ?? {};
73
+ // Parse the original request body
74
+ let originalParsedBody = envelope.request.body;
75
+ const originalContentType = envelope.request.headers['content-type'] ?? '';
76
+ if (originalContentType.includes('application/json')) {
77
+ try {
78
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
79
+ }
80
+ catch {
81
+ // Keep as string if JSON parsing fails
82
+ }
83
+ }
84
+ webhookRequest = {
85
+ method: envelope.request.method,
86
+ url: envelope.request.url,
87
+ path: envelope.request.path,
88
+ headers: envelope.request.headers,
89
+ query: envelope.request.query,
90
+ body: originalParsedBody,
91
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, 'utf-8') : undefined,
92
+ };
93
+ const envVars = { ...process.env, ...requestEnv };
94
+ const app = envelope.context.app;
95
+ // Build webhook context based on whether we have installation context
96
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
97
+ // Runtime webhook context
98
+ webhookContext = {
99
+ env: envVars,
100
+ app,
101
+ appInstallationId: envelope.context.appInstallationId,
102
+ workplace: envelope.context.workplace,
103
+ registration: envelope.context.registration ?? {},
104
+ };
105
+ }
106
+ else {
107
+ // Provision webhook context
108
+ webhookContext = {
109
+ env: envVars,
110
+ app,
111
+ };
112
+ }
113
+ }
114
+ else {
115
+ // Direct request format (legacy or direct calls) - requires app info from headers or fail
116
+ const appId = event.headers?.['x-skedyul-app-id'] ?? event.headers?.['X-Skedyul-App-Id'];
117
+ const appVersionId = event.headers?.['x-skedyul-app-version-id'] ?? event.headers?.['X-Skedyul-App-Version-Id'];
118
+ if (!appId || !appVersionId) {
119
+ throw new Error('Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)');
120
+ }
121
+ const forwardedProto = event.headers?.['x-forwarded-proto'] ??
122
+ event.headers?.['X-Forwarded-Proto'];
123
+ const protocol = forwardedProto ?? 'https';
124
+ const host = event.headers?.host ?? event.headers?.Host ?? 'localhost';
125
+ const queryString = event.queryStringParameters
126
+ ? '?' + new URLSearchParams(event.queryStringParameters).toString()
127
+ : '';
128
+ const webhookUrl = `${protocol}://${host}${path}${queryString}`;
129
+ webhookRequest = {
130
+ method,
131
+ url: webhookUrl,
132
+ path,
133
+ headers: event.headers,
134
+ query: event.queryStringParameters ?? {},
135
+ body: parsedBody,
136
+ rawBody: rawBody ? Buffer.from(rawBody, 'utf-8') : undefined,
137
+ };
138
+ // Direct calls are provision-level (no installation context)
139
+ webhookContext = {
140
+ env: process.env,
141
+ app: { id: appId, versionId: appVersionId },
142
+ };
143
+ }
144
+ // Temporarily inject env into process.env for skedyul client to use
145
+ // (same pattern as tool handler)
146
+ const originalEnv = { ...process.env };
147
+ Object.assign(process.env, requestEnv);
148
+ // Build request-scoped config for the skedyul client
149
+ // This uses AsyncLocalStorage to override the global config (same pattern as tools)
150
+ const requestConfig = {
151
+ baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
152
+ apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
153
+ };
154
+ // Invoke the handler with request-scoped config
155
+ let webhookResponse;
156
+ try {
157
+ webhookResponse = await (0, client_1.runWithConfig)(requestConfig, async () => {
158
+ return await webhookDef.handler(webhookRequest, webhookContext);
159
+ });
160
+ }
161
+ catch (err) {
162
+ console.error(`Webhook handler '${handle}' error:`, err);
163
+ return (0, utils_1.createResponse)(500, { error: 'Webhook handler error' }, headers);
164
+ }
165
+ finally {
166
+ // Restore original env
167
+ process.env = originalEnv;
168
+ }
169
+ // Build response headers
170
+ const responseHeaders = {
171
+ ...headers,
172
+ ...webhookResponse.headers,
173
+ };
174
+ const status = webhookResponse.status ?? 200;
175
+ const body = webhookResponse.body;
176
+ return {
177
+ statusCode: status,
178
+ headers: responseHeaders,
179
+ body: body !== undefined
180
+ ? (typeof body === 'string' ? body : JSON.stringify(body))
181
+ : '',
182
+ };
183
+ }
184
+ if (path === '/core' && method === 'POST') {
185
+ let coreBody;
186
+ try {
187
+ coreBody = event.body ? JSON.parse(event.body) : {};
188
+ }
189
+ catch {
190
+ return (0, utils_1.createResponse)(400, {
191
+ error: {
192
+ code: -32700,
193
+ message: 'Parse error',
194
+ },
195
+ }, headers);
196
+ }
197
+ if (!coreBody?.method) {
198
+ return (0, utils_1.createResponse)(400, {
199
+ error: {
200
+ code: -32602,
201
+ message: 'Missing method',
202
+ },
203
+ }, headers);
204
+ }
205
+ const coreMethod = coreBody.method;
206
+ const result = await (0, core_api_handler_1.handleCoreMethod)(coreMethod, coreBody.params);
207
+ return (0, utils_1.createResponse)(result.status, result.payload, headers);
208
+ }
209
+ if (path === '/core/webhook' && method === 'POST') {
210
+ const rawWebhookBody = event.body ?? '';
211
+ let webhookBody;
212
+ try {
213
+ webhookBody = rawWebhookBody ? JSON.parse(rawWebhookBody) : {};
214
+ }
215
+ catch {
216
+ return (0, utils_1.createResponse)(400, { status: 'parse-error' }, headers);
217
+ }
218
+ const forwardedProto = event.headers?.['x-forwarded-proto'] ??
219
+ event.headers?.['X-Forwarded-Proto'];
220
+ const protocol = forwardedProto ?? 'https';
221
+ const host = event.headers?.host ?? event.headers?.Host ?? 'localhost';
222
+ const webhookUrl = `${protocol}://${host}${event.path}`;
223
+ const coreWebhookRequest = {
224
+ method,
225
+ headers: (event.headers ?? {}),
226
+ body: webhookBody,
227
+ query: event.queryStringParameters ?? {},
228
+ url: webhookUrl,
229
+ path: event.path,
230
+ rawBody: rawWebhookBody
231
+ ? Buffer.from(rawWebhookBody, 'utf-8')
232
+ : undefined,
233
+ };
234
+ const webhookResponse = await service_1.coreApiService.dispatchWebhook(coreWebhookRequest);
235
+ return (0, utils_1.createResponse)(webhookResponse.status, webhookResponse.body ?? {}, headers);
236
+ }
237
+ if (path === '/estimate' && method === 'POST') {
238
+ let estimateBody;
239
+ try {
240
+ estimateBody = event.body ? JSON.parse(event.body) : {};
241
+ }
242
+ catch {
243
+ return (0, utils_1.createResponse)(400, {
244
+ error: {
245
+ code: -32700,
246
+ message: 'Parse error',
247
+ },
248
+ }, headers);
249
+ }
250
+ try {
251
+ const toolName = estimateBody.name;
252
+ const toolArgs = estimateBody.inputs ?? {};
253
+ // Find tool by name
254
+ let toolKey = null;
255
+ let tool = null;
256
+ for (const [key, t] of Object.entries(registry)) {
257
+ if (t.name === toolName || key === toolName) {
258
+ toolKey = key;
259
+ tool = t;
260
+ break;
261
+ }
262
+ }
263
+ if (!tool || !toolKey) {
264
+ return (0, utils_1.createResponse)(400, {
265
+ error: {
266
+ code: -32602,
267
+ message: `Tool "${toolName}" not found`,
268
+ },
269
+ }, headers);
270
+ }
271
+ const inputSchema = (0, utils_1.getZodSchema)(tool.inputSchema);
272
+ // Validate arguments against Zod schema
273
+ const validatedArgs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
274
+ const estimateResponse = await callTool(toolKey, {
275
+ inputs: validatedArgs,
276
+ estimate: true,
277
+ });
278
+ return (0, utils_1.createResponse)(200, {
279
+ billing: estimateResponse.billing ?? { credits: 0 },
280
+ }, headers);
281
+ }
282
+ catch (err) {
283
+ return (0, utils_1.createResponse)(500, {
284
+ error: {
285
+ code: -32603,
286
+ message: err instanceof Error ? err.message : String(err ?? ''),
287
+ },
288
+ }, headers);
289
+ }
290
+ }
291
+ // Handle /install endpoint for install handlers
292
+ if (path === '/install' && method === 'POST') {
293
+ if (!config.hooks?.install) {
294
+ return (0, utils_1.createResponse)(404, { error: 'Install handler not configured' }, headers);
295
+ }
296
+ let installBody;
297
+ try {
298
+ installBody = event.body ? JSON.parse(event.body) : {};
299
+ }
300
+ catch {
301
+ return (0, utils_1.createResponse)(400, { error: { code: -32700, message: 'Parse error' } }, headers);
302
+ }
303
+ if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
304
+ return (0, utils_1.createResponse)(400, { error: { code: -32602, message: 'Missing context (appInstallationId and workplace required)' } }, headers);
305
+ }
306
+ const installContext = {
307
+ env: installBody.env ?? {},
308
+ workplace: installBody.context.workplace,
309
+ appInstallationId: installBody.context.appInstallationId,
310
+ app: installBody.context.app,
311
+ };
312
+ // Build request-scoped config for SDK access
313
+ // Use env from request body (contains generated token from workflow)
314
+ const installRequestConfig = {
315
+ baseUrl: installBody.env?.SKEDYUL_API_URL ??
316
+ process.env.SKEDYUL_API_URL ??
317
+ '',
318
+ apiToken: installBody.env?.SKEDYUL_API_TOKEN ??
319
+ process.env.SKEDYUL_API_TOKEN ??
320
+ '',
321
+ };
322
+ try {
323
+ const installHook = config.hooks.install;
324
+ const installHandler = typeof installHook === 'function'
325
+ ? installHook
326
+ : installHook.handler;
327
+ const result = await (0, client_1.runWithConfig)(installRequestConfig, async () => {
328
+ return await installHandler(installContext);
329
+ });
330
+ return (0, utils_1.createResponse)(200, { env: result.env ?? {}, redirect: result.redirect }, headers);
331
+ }
332
+ catch (err) {
333
+ // Check for typed install errors
334
+ if (err instanceof errors_1.InstallError) {
335
+ return (0, utils_1.createResponse)(400, {
336
+ error: {
337
+ code: err.code,
338
+ message: err.message,
339
+ field: err.field,
340
+ },
341
+ }, headers);
342
+ }
343
+ return (0, utils_1.createResponse)(500, {
344
+ error: {
345
+ code: -32603,
346
+ message: err instanceof Error ? err.message : String(err ?? ''),
347
+ },
348
+ }, headers);
349
+ }
350
+ }
351
+ // Handle /uninstall endpoint for uninstall handlers
352
+ if (path === '/uninstall' && method === 'POST') {
353
+ if (!config.hooks?.uninstall) {
354
+ return (0, utils_1.createResponse)(404, { error: 'Uninstall handler not configured' }, headers);
355
+ }
356
+ let uninstallBody;
357
+ try {
358
+ uninstallBody = event.body ? JSON.parse(event.body) : {};
359
+ }
360
+ catch {
361
+ return (0, utils_1.createResponse)(400, { error: { code: -32700, message: 'Parse error' } }, headers);
362
+ }
363
+ if (!uninstallBody.context?.appInstallationId ||
364
+ !uninstallBody.context?.workplace ||
365
+ !uninstallBody.context?.app) {
366
+ return (0, utils_1.createResponse)(400, {
367
+ error: {
368
+ code: -32602,
369
+ message: 'Missing context (appInstallationId, workplace and app required)',
370
+ },
371
+ }, headers);
372
+ }
373
+ const uninstallContext = {
374
+ env: uninstallBody.env ?? {},
375
+ workplace: uninstallBody.context.workplace,
376
+ appInstallationId: uninstallBody.context.appInstallationId,
377
+ app: uninstallBody.context.app,
378
+ };
379
+ const uninstallRequestConfig = {
380
+ baseUrl: uninstallBody.env?.SKEDYUL_API_URL ??
381
+ process.env.SKEDYUL_API_URL ??
382
+ '',
383
+ apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ??
384
+ process.env.SKEDYUL_API_TOKEN ??
385
+ '',
386
+ };
387
+ try {
388
+ const uninstallHook = config.hooks.uninstall;
389
+ const uninstallHandlerFn = typeof uninstallHook === 'function' ? uninstallHook : uninstallHook.handler;
390
+ const result = await (0, client_1.runWithConfig)(uninstallRequestConfig, async () => {
391
+ return await uninstallHandlerFn(uninstallContext);
392
+ });
393
+ return (0, utils_1.createResponse)(200, { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }, headers);
394
+ }
395
+ catch (err) {
396
+ return (0, utils_1.createResponse)(500, {
397
+ error: {
398
+ code: -32603,
399
+ message: err instanceof Error ? err.message : String(err ?? ''),
400
+ },
401
+ }, headers);
402
+ }
403
+ }
404
+ // Handle /oauth_callback endpoint for OAuth callbacks (called by platform route)
405
+ if (path === '/oauth_callback' && method === 'POST') {
406
+ if (!config.hooks?.oauth_callback) {
407
+ return (0, utils_1.createResponse)(404, { error: 'OAuth callback handler not configured' }, headers);
408
+ }
409
+ let parsedBody;
410
+ try {
411
+ parsedBody = event.body ? JSON.parse(event.body) : {};
412
+ }
413
+ catch (err) {
414
+ console.error('[OAuth Callback] Failed to parse JSON body:', err);
415
+ return (0, utils_1.createResponse)(400, { error: { code: -32700, message: 'Parse error' } }, headers);
416
+ }
417
+ // Parse envelope using shared helper
418
+ const envelope = (0, handler_helpers_1.parseHandlerEnvelope)(parsedBody);
419
+ if (!envelope) {
420
+ console.error('[OAuth Callback] Failed to parse envelope. Body:', JSON.stringify(parsedBody, null, 2));
421
+ return (0, utils_1.createResponse)(400, { error: { code: -32602, message: 'Missing envelope format: expected { env, request }' } }, headers);
422
+ }
423
+ // Convert raw request to rich request using shared helper
424
+ const oauthRequest = (0, handler_helpers_1.buildRequestFromRaw)(envelope.request);
425
+ // Build request-scoped config using shared helper
426
+ const oauthCallbackRequestConfig = (0, handler_helpers_1.buildRequestScopedConfig)(envelope.env);
427
+ const oauthCallbackContext = {
428
+ request: oauthRequest,
429
+ };
430
+ try {
431
+ const oauthCallbackHook = config.hooks.oauth_callback;
432
+ const oauthCallbackHandler = typeof oauthCallbackHook === 'function'
433
+ ? oauthCallbackHook
434
+ : oauthCallbackHook.handler;
435
+ const result = await (0, client_1.runWithConfig)(oauthCallbackRequestConfig, async () => {
436
+ return await oauthCallbackHandler(oauthCallbackContext);
437
+ });
438
+ return (0, utils_1.createResponse)(200, {
439
+ appInstallationId: result.appInstallationId,
440
+ env: result.env ?? {},
441
+ }, headers);
442
+ }
443
+ catch (err) {
444
+ const errorMessage = err instanceof Error ? err.message : String(err ?? 'Unknown error');
445
+ return (0, utils_1.createResponse)(500, {
446
+ error: {
447
+ code: -32603,
448
+ message: errorMessage,
449
+ },
450
+ }, headers);
451
+ }
452
+ }
453
+ if (path === '/health' && method === 'GET') {
454
+ return (0, utils_1.createResponse)(200, state.getHealthStatus(), headers);
455
+ }
456
+ if (path === '/mcp' && method === 'POST') {
457
+ let body;
458
+ try {
459
+ body = event.body ? JSON.parse(event.body) : {};
460
+ }
461
+ catch {
462
+ return (0, utils_1.createResponse)(400, {
463
+ jsonrpc: '2.0',
464
+ id: null,
465
+ error: {
466
+ code: -32700,
467
+ message: 'Parse error',
468
+ },
469
+ }, headers);
470
+ }
471
+ try {
472
+ const { jsonrpc, id, method: rpcMethod, params } = body;
473
+ if (jsonrpc !== '2.0') {
474
+ return (0, utils_1.createResponse)(400, {
475
+ jsonrpc: '2.0',
476
+ id,
477
+ error: {
478
+ code: -32600,
479
+ message: 'Invalid Request',
480
+ },
481
+ }, headers);
482
+ }
483
+ let result;
484
+ if (rpcMethod === 'tools/list') {
485
+ result = { tools };
486
+ }
487
+ else if (rpcMethod === 'tools/call') {
488
+ const toolName = params?.name;
489
+ // Support both formats:
490
+ // 1. Skedyul format: { inputs: {...}, context: {...}, env: {...} }
491
+ // 2. Standard MCP format: { ...directArgs }
492
+ const rawArgs = (params?.arguments ?? {});
493
+ const hasSkedyulFormat = 'inputs' in rawArgs || 'env' in rawArgs || 'context' in rawArgs;
494
+ const toolInputs = hasSkedyulFormat ? (rawArgs.inputs ?? {}) : rawArgs;
495
+ const toolContext = hasSkedyulFormat ? rawArgs.context : undefined;
496
+ const toolEnv = hasSkedyulFormat ? rawArgs.env : undefined;
497
+ // Find tool by name (check both registry key and tool.name)
498
+ let toolKey = null;
499
+ let tool = null;
500
+ for (const [key, t] of Object.entries(registry)) {
501
+ if (t.name === toolName || key === toolName) {
502
+ toolKey = key;
503
+ tool = t;
504
+ break;
505
+ }
506
+ }
507
+ if (!tool || !toolKey) {
508
+ return (0, utils_1.createResponse)(200, {
509
+ jsonrpc: '2.0',
510
+ id,
511
+ error: {
512
+ code: -32602,
513
+ message: `Tool "${toolName}" not found`,
514
+ },
515
+ }, headers);
516
+ }
517
+ try {
518
+ const inputSchema = (0, utils_1.getZodSchema)(tool.inputSchema);
519
+ const outputSchema = (0, utils_1.getZodSchema)(tool.outputSchema);
520
+ const hasOutputSchema = Boolean(outputSchema);
521
+ const validatedInputs = inputSchema
522
+ ? inputSchema.parse(toolInputs)
523
+ : toolInputs;
524
+ const toolResult = await callTool(toolKey, {
525
+ inputs: validatedInputs,
526
+ context: toolContext,
527
+ env: toolEnv,
528
+ });
529
+ // Transform internal format to MCP protocol format
530
+ // Note: effect is embedded in structuredContent as __effect
531
+ // for consistency with dedicated mode (MCP SDK strips custom fields)
532
+ if (toolResult.error) {
533
+ const errorOutput = { error: toolResult.error };
534
+ result = {
535
+ content: [{ type: 'text', text: JSON.stringify(errorOutput) }],
536
+ structuredContent: errorOutput,
537
+ isError: true,
538
+ billing: toolResult.billing,
539
+ };
540
+ }
541
+ else {
542
+ const outputData = toolResult.output;
543
+ const structuredContent = outputData
544
+ ? { ...outputData, __effect: toolResult.effect }
545
+ : toolResult.effect
546
+ ? { __effect: toolResult.effect }
547
+ : undefined;
548
+ result = {
549
+ content: [{ type: 'text', text: JSON.stringify(toolResult.output) }],
550
+ structuredContent,
551
+ billing: toolResult.billing,
552
+ };
553
+ }
554
+ }
555
+ catch (validationError) {
556
+ return (0, utils_1.createResponse)(200, {
557
+ jsonrpc: '2.0',
558
+ id,
559
+ error: {
560
+ code: -32602,
561
+ message: validationError instanceof Error
562
+ ? validationError.message
563
+ : 'Invalid arguments',
564
+ },
565
+ }, headers);
566
+ }
567
+ }
568
+ else if (rpcMethod === 'webhooks/list') {
569
+ // Return registered webhooks with their metadata
570
+ const webhooks = webhookRegistry
571
+ ? Object.values(webhookRegistry).map((w) => ({
572
+ name: w.name,
573
+ description: w.description,
574
+ methods: w.methods ?? ['POST'],
575
+ type: w.type ?? 'WEBHOOK',
576
+ }))
577
+ : [];
578
+ result = { webhooks };
579
+ }
580
+ else {
581
+ return (0, utils_1.createResponse)(200, {
582
+ jsonrpc: '2.0',
583
+ id,
584
+ error: {
585
+ code: -32601,
586
+ message: `Method not found: ${rpcMethod}`,
587
+ },
588
+ }, headers);
589
+ }
590
+ return (0, utils_1.createResponse)(200, {
591
+ jsonrpc: '2.0',
592
+ id,
593
+ result,
594
+ }, headers);
595
+ }
596
+ catch (err) {
597
+ return (0, utils_1.createResponse)(500, {
598
+ jsonrpc: '2.0',
599
+ id: body?.id ?? null,
600
+ error: {
601
+ code: -32603,
602
+ message: err instanceof Error ? err.message : String(err ?? ''),
603
+ },
604
+ }, headers);
605
+ }
606
+ }
607
+ return (0, utils_1.createResponse)(404, {
608
+ jsonrpc: '2.0',
609
+ id: null,
610
+ error: {
611
+ code: -32601,
612
+ message: 'Not Found',
613
+ },
614
+ }, headers);
615
+ }
616
+ catch (err) {
617
+ return (0, utils_1.createResponse)(500, {
618
+ jsonrpc: '2.0',
619
+ id: null,
620
+ error: {
621
+ code: -32603,
622
+ message: err instanceof Error ? err.message : String(err ?? ''),
623
+ },
624
+ }, headers);
625
+ }
626
+ },
627
+ getHealthStatus: () => state.getHealthStatus(),
628
+ };
629
+ }
@@ -0,0 +1,9 @@
1
+ import type { SkedyulServerConfig, ToolMetadata, WebhookRegistry } from '../types';
2
+ /**
3
+ * Pad a string to the right with spaces
4
+ */
5
+ export declare function padEnd(str: string, length: number): string;
6
+ /**
7
+ * Prints a styled startup log showing server configuration
8
+ */
9
+ export declare function printStartupLog(config: SkedyulServerConfig, tools: ToolMetadata[], webhookRegistry?: WebhookRegistry, port?: number): void;