skedyul 1.2.44 → 1.2.48

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 (54) hide show
  1. package/dist/cli/commands/workflows.d.ts +1 -0
  2. package/dist/cli/index.js +3670 -9073
  3. package/dist/cli/utils/auth.d.ts +4 -1
  4. package/dist/cli/utils/auth.js +32 -8
  5. package/dist/cli/utils/config.d.ts +23 -0
  6. package/dist/cli/utils/env-sync.d.ts +46 -0
  7. package/dist/cli/utils/mcp-http-client.d.ts +74 -0
  8. package/dist/cli/utils/migration-approval.d.ts +39 -0
  9. package/dist/cli/utils/mock-context.d.ts +19 -9
  10. package/dist/cli/utils/sse.d.ts +33 -0
  11. package/dist/cli/utils.d.ts +5 -1
  12. package/dist/compiler/types.d.ts +11 -9
  13. package/dist/config/schema-loader.d.ts +15 -2
  14. package/dist/config/types/model.d.ts +2 -0
  15. package/dist/config/types/page.d.ts +9 -0
  16. package/dist/context/index.d.ts +2 -2
  17. package/dist/context/resolver.d.ts +29 -28
  18. package/dist/context/types.d.ts +195 -37
  19. package/dist/core/client.d.ts +189 -233
  20. package/dist/dedicated/server.js +252 -163
  21. package/dist/esm/index.mjs +1149 -7671
  22. package/dist/index.d.ts +11 -6
  23. package/dist/index.js +1194 -7669
  24. package/dist/scheduling/calculateWaitTime.d.ts +16 -0
  25. package/dist/scheduling/index.d.ts +11 -0
  26. package/dist/scheduling/index.js +334 -0
  27. package/dist/scheduling/index.mjs +305 -0
  28. package/dist/scheduling/isTimeInWindow.d.ts +15 -0
  29. package/dist/scheduling/types-workflow.d.ts +54 -0
  30. package/dist/scheduling/types.d.ts +166 -0
  31. package/dist/schemas/agent-schema-v3.d.ts +406 -60
  32. package/dist/schemas/agent-schema-v3.js +248 -75
  33. package/dist/schemas/agent-schema-v3.mjs +234 -73
  34. package/dist/schemas/agent-schema.js +3 -7295
  35. package/dist/schemas/agent-schema.mjs +3 -7323
  36. package/dist/schemas/crm-schema.d.ts +53 -19
  37. package/dist/schemas/index.d.ts +1 -1
  38. package/dist/schemas.d.ts +128 -40
  39. package/dist/server/route-handlers/handlers.d.ts +7 -0
  40. package/dist/server/utils/env.d.ts +9 -0
  41. package/dist/server/utils/index.d.ts +1 -0
  42. package/dist/server/utils/mcp-response.d.ts +11 -0
  43. package/dist/server.js +252 -163
  44. package/dist/serverless/server.mjs +252 -163
  45. package/dist/skills/index.d.ts +1 -1
  46. package/dist/skills/types.d.ts +34 -23
  47. package/dist/skills/types.js +8 -17
  48. package/dist/skills/types.mjs +7 -16
  49. package/dist/types/index.d.ts +3 -3
  50. package/dist/types/server.d.ts +1 -0
  51. package/dist/types/tool-context.d.ts +31 -4
  52. package/dist/types/tool-response.d.ts +2 -0
  53. package/dist/types/tool.d.ts +33 -1
  54. package/package.json +8 -1
@@ -149,10 +149,24 @@ function parseNumberEnv(value) {
149
149
  const parsed = Number.parseInt(value, 10);
150
150
  return Number.isNaN(parsed) ? null : parsed;
151
151
  }
152
+ function getBakedExecutableEnv() {
153
+ return {
154
+ ...parseJsonRecord(process.env.MCP_ENV_JSON),
155
+ ...parseJsonRecord(process.env.MCP_ENV)
156
+ };
157
+ }
158
+ function buildToolExecutionEnv(requestEnv = {}) {
159
+ const bakedEnv = getBakedExecutableEnv();
160
+ const merged = { ...bakedEnv };
161
+ for (const [key, value] of Object.entries(requestEnv)) {
162
+ if (value !== void 0) {
163
+ merged[key] = value;
164
+ }
165
+ }
166
+ return merged;
167
+ }
152
168
  function mergeRuntimeEnv() {
153
- const bakedEnv = parseJsonRecord(process.env.MCP_ENV_JSON);
154
- const runtimeEnv = parseJsonRecord(process.env.MCP_ENV);
155
- const merged = { ...bakedEnv, ...runtimeEnv };
169
+ const merged = getBakedExecutableEnv();
156
170
  Object.assign(process.env, merged);
157
171
  }
158
172
 
@@ -197,6 +211,48 @@ function getListeningPort(config) {
197
211
  return config.defaultPort ?? 3e3;
198
212
  }
199
213
 
214
+ // src/server/utils/mcp-response.ts
215
+ function serializeMcpContentText(value) {
216
+ return JSON.stringify(value ?? null);
217
+ }
218
+ function isToolCallFailure(result) {
219
+ const isNewShapeFailure = "success" in result && result.success === false;
220
+ const isLegacyErrorFailure = "error" in result && result.error != null;
221
+ const isLegacyMetaFailure = "meta" in result && result.meta != null && typeof result.meta === "object" && "success" in result.meta && result.meta.success === false;
222
+ return isNewShapeFailure || isLegacyErrorFailure || isLegacyMetaFailure;
223
+ }
224
+ function buildToolCallErrorOutput(result) {
225
+ const isNewShapeFailure = "success" in result && result.success === false;
226
+ const isLegacyErrorFailure = "error" in result && result.error != null;
227
+ const isLegacyMetaFailure = "meta" in result && result.meta != null && typeof result.meta === "object" && "success" in result.meta && result.meta.success === false;
228
+ if (isNewShapeFailure && "error" in result) {
229
+ return {
230
+ error: result.error,
231
+ retry: "retry" in result ? result.retry : void 0
232
+ };
233
+ }
234
+ if (isLegacyErrorFailure && "error" in result) {
235
+ return { error: result.error };
236
+ }
237
+ if (isLegacyMetaFailure && "meta" in result && result.meta) {
238
+ const meta = result.meta;
239
+ return {
240
+ error: {
241
+ code: "TOOL_FAILED",
242
+ message: meta.message ?? "Tool execution failed",
243
+ category: "internal"
244
+ }
245
+ };
246
+ }
247
+ return {
248
+ error: {
249
+ code: "TOOL_FAILED",
250
+ message: "Tool execution failed",
251
+ category: "internal"
252
+ }
253
+ };
254
+ }
255
+
200
256
  // src/core/client.ts
201
257
  var import_async_hooks = require("async_hooks");
202
258
  var import_v4 = require("zod/v4");
@@ -320,14 +376,19 @@ function createContextLogger() {
320
376
  function buildToolMetadata(registry) {
321
377
  return Object.values(registry).map((tool) => {
322
378
  const toolConfig = tool.config ?? {};
323
- const timeout = typeof toolConfig.timeout === "number" && toolConfig.timeout > 0 ? toolConfig.timeout : 1e4;
324
- const retries = typeof toolConfig.retries === "number" && toolConfig.retries >= 1 ? toolConfig.retries : 1;
379
+ const rawTimeout = tool.timeout ?? toolConfig.timeout;
380
+ const rawRetries = tool.retries ?? toolConfig.retries;
381
+ const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 1e4;
382
+ const retries = typeof rawRetries === "number" && rawRetries >= 1 ? rawRetries : 1;
325
383
  return {
326
384
  name: tool.name,
327
385
  displayName: tool.label || tool.name,
328
386
  description: tool.description,
329
387
  inputSchema: getJsonSchemaFromToolSchema(tool.inputSchema),
330
388
  outputSchema: getJsonSchemaFromToolSchema(tool.outputSchema),
389
+ // Include timeout/retries at top-level for tools/list response (used by syncExecutableTools)
390
+ timeout,
391
+ retries,
331
392
  config: {
332
393
  timeout,
333
394
  retries,
@@ -381,8 +442,9 @@ function createCallToolHandler(registry, state, onMaxRequests) {
381
442
  }
382
443
  }
383
444
  const requestEnv = args.env ?? {};
445
+ const toolEnv = buildToolExecutionEnv(requestEnv);
384
446
  const originalEnv = { ...process.env };
385
- Object.assign(process.env, requestEnv);
447
+ Object.assign(process.env, toolEnv);
386
448
  const invocation = args.invocation;
387
449
  try {
388
450
  const inputs = args.inputs ?? {};
@@ -395,7 +457,16 @@ function createCallToolHandler(registry, state, onMaxRequests) {
395
457
  executionContext = {
396
458
  trigger: "provision",
397
459
  app,
398
- env: process.env,
460
+ env: toolEnv,
461
+ mode: estimateMode ? "estimate" : "execute",
462
+ invocation,
463
+ log
464
+ };
465
+ } else if (trigger === "developer_page_action" || trigger === "developer_form_submit") {
466
+ executionContext = {
467
+ trigger,
468
+ app,
469
+ env: toolEnv,
399
470
  mode: estimateMode ? "estimate" : "execute",
400
471
  invocation,
401
472
  log
@@ -404,7 +475,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
404
475
  const workplace = rawContext.workplace;
405
476
  const request = rawContext.request;
406
477
  const appInstallationId = rawContext.appInstallationId;
407
- const envVars = process.env;
478
+ const envVars = toolEnv;
408
479
  const modeValue = estimateMode ? "estimate" : "execute";
409
480
  if (trigger === "field_change") {
410
481
  const field = rawContext.field;
@@ -424,8 +495,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
424
495
  }
425
496
  }
426
497
  const requestConfig = {
427
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
428
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
498
+ baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
499
+ apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
429
500
  };
430
501
  const functionResult = await runWithConfig(requestConfig, async () => {
431
502
  return await runWithLogContext({ invocation }, async () => {
@@ -433,7 +504,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
433
504
  });
434
505
  });
435
506
  const billing = normalizeBilling(functionResult.billing);
507
+ if ("success" in functionResult && functionResult.success === false) {
508
+ return {
509
+ success: false,
510
+ output: null,
511
+ billing,
512
+ error: "error" in functionResult ? functionResult.error : void 0,
513
+ effect: "effect" in functionResult ? functionResult.effect : void 0
514
+ };
515
+ }
436
516
  return {
517
+ success: true,
437
518
  output: functionResult.output,
438
519
  billing,
439
520
  meta: functionResult.meta ?? {
@@ -442,7 +523,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
442
523
  toolName
443
524
  },
444
525
  effect: functionResult.effect,
445
- dataBlocks: functionResult.dataBlocks
526
+ dataBlocks: functionResult.dataBlocks,
527
+ cursor: functionResult.cursor
446
528
  };
447
529
  } catch (error) {
448
530
  if (error instanceof AppAuthInvalidError) {
@@ -805,8 +887,9 @@ function serializeConfig(config) {
805
887
  tools: registry ? Object.entries(registry).map(([key, tool]) => ({
806
888
  name: tool.name || key,
807
889
  description: tool.description,
808
- timeout: tool.config?.timeout,
809
- retries: tool.config?.retries
890
+ // Read timeout/retries from top-level first, then fallback to config
891
+ timeout: tool.timeout ?? tool.config?.timeout,
892
+ retries: tool.retries ?? tool.config?.retries
810
893
  })) : [],
811
894
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
812
895
  name: w.name,
@@ -1225,6 +1308,34 @@ function isMethodAllowed(webhookRegistry, handle, method) {
1225
1308
  function getConfigFilePath() {
1226
1309
  return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1227
1310
  }
1311
+ function findToolInRegistry(registry, toolName) {
1312
+ for (const [key, t] of Object.entries(registry)) {
1313
+ if (t.name === toolName || key === toolName) {
1314
+ return { toolKey: key, tool: t };
1315
+ }
1316
+ }
1317
+ const inputParts = toolName.split(":");
1318
+ const inputShortName = inputParts[inputParts.length - 1];
1319
+ if (inputParts.length > 1) {
1320
+ for (const [key, t] of Object.entries(registry)) {
1321
+ if (t.name === inputShortName || key === inputShortName) {
1322
+ return { toolKey: key, tool: t };
1323
+ }
1324
+ }
1325
+ }
1326
+ const matches = [];
1327
+ for (const [key, t] of Object.entries(registry)) {
1328
+ const registryParts = t.name.split(":");
1329
+ const registryShortName = registryParts[registryParts.length - 1];
1330
+ if (registryShortName === toolName) {
1331
+ matches.push({ toolKey: key, tool: t });
1332
+ }
1333
+ }
1334
+ if (matches.length === 1) {
1335
+ return matches[0];
1336
+ }
1337
+ return null;
1338
+ }
1228
1339
  function handleHealthRoute(ctx) {
1229
1340
  return {
1230
1341
  status: 200,
@@ -1234,34 +1345,14 @@ function handleHealthRoute(ctx) {
1234
1345
  function handleConfigRoute(ctx) {
1235
1346
  const configFilePath = getConfigFilePath();
1236
1347
  try {
1237
- console.log(`[/config] Checking for config file at: ${configFilePath}`);
1238
1348
  if (fs.existsSync(configFilePath)) {
1239
1349
  const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
1240
- console.log(
1241
- `[/config] Loaded config from file: tools=${fileConfig.tools?.length ?? 0}, webhooks=${fileConfig.webhooks?.length ?? 0}`
1242
- );
1243
- console.log(
1244
- `[/config] SENDING config with keys: ${Object.keys(fileConfig).join(", ")}`
1245
- );
1246
- console.log(
1247
- `[/config] SENDING full config: ${JSON.stringify(fileConfig).substring(0, 2e3)}...`
1248
- );
1249
1350
  return { status: 200, body: fileConfig };
1250
1351
  }
1251
- console.log("[/config] Config file not found, falling back to runtime serialization");
1252
1352
  } catch (err) {
1253
1353
  console.warn("[/config] Failed to read config file, falling back to runtime serialization:", err);
1254
1354
  }
1255
1355
  const serialized = serializeConfig(ctx.config);
1256
- console.log(
1257
- `[/config] Runtime serialization: tools=${serialized.tools?.length ?? 0}, webhooks=${serialized.webhooks?.length ?? 0}`
1258
- );
1259
- console.log(
1260
- `[/config] SENDING serialized config with keys: ${Object.keys(serialized).join(", ")}`
1261
- );
1262
- console.log(
1263
- `[/config] SENDING full serialized config: ${JSON.stringify(serialized).substring(0, 2e3)}...`
1264
- );
1265
1356
  return { status: 200, body: serialized };
1266
1357
  }
1267
1358
  async function handleCoreRoute(req, ctx) {
@@ -1316,16 +1407,8 @@ async function handleEstimateRoute(req, ctx) {
1316
1407
  try {
1317
1408
  const toolName = estimateBody.name;
1318
1409
  const toolArgs = estimateBody.inputs ?? {};
1319
- let toolKey = null;
1320
- let tool = null;
1321
- for (const [key, t] of Object.entries(ctx.registry)) {
1322
- if (t.name === toolName || key === toolName) {
1323
- toolKey = key;
1324
- tool = t;
1325
- break;
1326
- }
1327
- }
1328
- if (!tool || !toolKey) {
1410
+ const found = findToolInRegistry(ctx.registry, toolName);
1411
+ if (!found) {
1329
1412
  return {
1330
1413
  status: 400,
1331
1414
  body: {
@@ -1336,6 +1419,7 @@ async function handleEstimateRoute(req, ctx) {
1336
1419
  }
1337
1420
  };
1338
1421
  }
1422
+ const { toolKey, tool } = found;
1339
1423
  const inputSchema = getZodSchema(tool.inputSchema);
1340
1424
  const validatedArgs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
1341
1425
  const estimateResponse = await ctx.callTool(toolKey, {
@@ -1498,35 +1582,13 @@ async function handleMcpRoute(req, ctx) {
1498
1582
  async function handleMcpToolsCall(params, id, ctx) {
1499
1583
  const toolName = params?.name;
1500
1584
  const rawArgs = params?.arguments ?? {};
1501
- console.log("[route-handlers /mcp] Received tools/call request:", JSON.stringify({
1502
- toolName,
1503
- hasArguments: !!params?.arguments,
1504
- argumentKeys: rawArgs ? Object.keys(rawArgs) : [],
1505
- hasEnv: "env" in rawArgs,
1506
- envKeys: rawArgs.env ? Object.keys(rawArgs.env) : [],
1507
- hasApiToken: !!rawArgs.env?.SKEDYUL_API_TOKEN
1508
- }, null, 2));
1509
1585
  const hasSkedyulFormat = "inputs" in rawArgs || "env" in rawArgs || "context" in rawArgs || "invocation" in rawArgs;
1510
1586
  const toolInputs = hasSkedyulFormat ? rawArgs.inputs ?? {} : rawArgs;
1511
1587
  const toolContext = hasSkedyulFormat ? rawArgs.context : void 0;
1512
1588
  const toolEnv = hasSkedyulFormat ? rawArgs.env : void 0;
1513
1589
  const toolInvocation = hasSkedyulFormat ? rawArgs.invocation : void 0;
1514
- console.log("[route-handlers /mcp] Extracted env:", JSON.stringify({
1515
- hasSkedyulFormat,
1516
- hasToolEnv: !!toolEnv,
1517
- toolEnvKeys: toolEnv ? Object.keys(toolEnv) : [],
1518
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1519
- }, null, 2));
1520
- let toolKey = null;
1521
- let tool = null;
1522
- for (const [key, t] of Object.entries(ctx.registry)) {
1523
- if (t.name === toolName || key === toolName) {
1524
- toolKey = key;
1525
- tool = t;
1526
- break;
1527
- }
1528
- }
1529
- if (!tool || !toolKey) {
1590
+ const found = findToolInRegistry(ctx.registry, toolName);
1591
+ if (!found) {
1530
1592
  return {
1531
1593
  status: 200,
1532
1594
  body: {
@@ -1539,6 +1601,7 @@ async function handleMcpToolsCall(params, id, ctx) {
1539
1601
  }
1540
1602
  };
1541
1603
  }
1604
+ const { toolKey, tool } = found;
1542
1605
  try {
1543
1606
  const inputSchema = getZodSchema(tool.inputSchema);
1544
1607
  const outputSchema = getZodSchema(tool.outputSchema);
@@ -1551,68 +1614,45 @@ async function handleMcpToolsCall(params, id, ctx) {
1551
1614
  invocation: toolInvocation
1552
1615
  });
1553
1616
  let result;
1554
- const isNewShapeFailure = "success" in toolResult && toolResult.success === false;
1555
- const isLegacyErrorFailure = "error" in toolResult && toolResult.error != null;
1556
- const isLegacyMetaFailure = "meta" in toolResult && toolResult.meta != null && typeof toolResult.meta === "object" && "success" in toolResult.meta && toolResult.meta.success === false;
1557
- const isFailure = isNewShapeFailure || isLegacyErrorFailure || isLegacyMetaFailure;
1617
+ const isFailure = isToolCallFailure(toolResult);
1558
1618
  if (isFailure) {
1559
- let errorOutput;
1560
- if (isNewShapeFailure && "error" in toolResult) {
1561
- errorOutput = {
1562
- error: toolResult.error,
1563
- retry: "retry" in toolResult ? toolResult.retry : void 0
1564
- };
1565
- } else if (isLegacyErrorFailure && "error" in toolResult) {
1566
- errorOutput = { error: toolResult.error };
1567
- } else if (isLegacyMetaFailure && "meta" in toolResult && toolResult.meta) {
1568
- const meta = toolResult.meta;
1569
- errorOutput = {
1570
- error: {
1571
- code: "TOOL_FAILED",
1572
- message: meta.message ?? "Tool execution failed",
1573
- category: "internal"
1574
- }
1575
- };
1576
- } else {
1577
- errorOutput = {
1578
- error: {
1579
- code: "TOOL_FAILED",
1580
- message: "Tool execution failed",
1581
- category: "internal"
1582
- }
1583
- };
1584
- }
1619
+ const errorOutput = buildToolCallErrorOutput(toolResult);
1585
1620
  result = {
1586
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
1621
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
1587
1622
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
1588
1623
  isError: true,
1589
1624
  billing: "billing" in toolResult ? toolResult.billing : void 0
1590
1625
  };
1591
1626
  } else {
1592
- const outputData = "output" in toolResult ? toolResult.output : null;
1627
+ const rawOutput = "output" in toolResult ? toolResult.output : null;
1628
+ const outputData = rawOutput ?? null;
1593
1629
  const effect = "effect" in toolResult ? toolResult.effect : void 0;
1594
1630
  const warnings = "warnings" in toolResult ? toolResult.warnings : void 0;
1595
1631
  const pagination = "pagination" in toolResult ? toolResult.pagination : void 0;
1632
+ const cursor = "cursor" in toolResult ? toolResult.cursor : void 0;
1596
1633
  let structuredContent;
1597
1634
  if (outputData) {
1598
1635
  structuredContent = {
1599
1636
  ...outputData,
1600
1637
  __effect: effect,
1601
1638
  __warnings: warnings,
1602
- __pagination: pagination
1639
+ __pagination: pagination,
1640
+ __cursor: cursor
1603
1641
  };
1604
- } else if (effect || warnings || pagination) {
1642
+ } else if (effect || warnings || pagination || cursor) {
1605
1643
  structuredContent = {
1606
1644
  __effect: effect,
1607
1645
  __warnings: warnings,
1608
- __pagination: pagination
1646
+ __pagination: pagination,
1647
+ __cursor: cursor
1609
1648
  };
1610
1649
  } else if (hasOutputSchema) {
1611
1650
  structuredContent = {};
1612
1651
  }
1613
1652
  result = {
1614
- content: [{ type: "text", text: JSON.stringify(outputData) }],
1653
+ content: [{ type: "text", text: serializeMcpContentText(outputData) }],
1615
1654
  structuredContent,
1655
+ cursor,
1616
1656
  billing: "billing" in toolResult ? toolResult.billing : void 0
1617
1657
  };
1618
1658
  }
@@ -1638,6 +1678,94 @@ async function handleMcpToolsCall(params, id, ctx) {
1638
1678
  };
1639
1679
  }
1640
1680
  }
1681
+ async function handleMcpBatchRoute(req, ctx) {
1682
+ const parseResult = parseJsonBody(req);
1683
+ if (!parseResult.success) {
1684
+ return parseResult.error;
1685
+ }
1686
+ const { calls, deadline, env: batchEnv, context: batchContext } = parseResult.data;
1687
+ if (!calls || !Array.isArray(calls) || calls.length === 0) {
1688
+ return {
1689
+ status: 400,
1690
+ body: {
1691
+ error: {
1692
+ code: -32602,
1693
+ message: "Missing or invalid calls array"
1694
+ }
1695
+ }
1696
+ };
1697
+ }
1698
+ const perCallTimeoutMs = deadline ? Math.min(deadline, 25e3) : 25e3;
1699
+ const executeWithTimeout = async (call, timeoutMs) => {
1700
+ return new Promise(async (resolve) => {
1701
+ const timeoutHandle = setTimeout(() => {
1702
+ resolve({
1703
+ id: call.id,
1704
+ success: false,
1705
+ error: "Timeout",
1706
+ timedOut: true
1707
+ });
1708
+ }, timeoutMs);
1709
+ try {
1710
+ const toolName = call.name;
1711
+ const toolArgs = call.arguments ?? {};
1712
+ const found = findToolInRegistry(ctx.registry, toolName);
1713
+ if (!found) {
1714
+ clearTimeout(timeoutHandle);
1715
+ resolve({
1716
+ id: call.id,
1717
+ success: false,
1718
+ error: `Tool "${toolName}" not found`
1719
+ });
1720
+ return;
1721
+ }
1722
+ const { toolKey, tool } = found;
1723
+ const inputSchema = getZodSchema(tool.inputSchema);
1724
+ const validatedInputs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
1725
+ const toolResult = await ctx.callTool(toolKey, {
1726
+ inputs: validatedInputs,
1727
+ context: batchContext,
1728
+ env: batchEnv
1729
+ });
1730
+ clearTimeout(timeoutHandle);
1731
+ const isFailure = "success" in toolResult && toolResult.success === false || "error" in toolResult && toolResult.error != null;
1732
+ if (isFailure) {
1733
+ resolve({
1734
+ id: call.id,
1735
+ success: false,
1736
+ error: "error" in toolResult && toolResult.error ? typeof toolResult.error === "string" ? toolResult.error : JSON.stringify(toolResult.error) : "Tool execution failed"
1737
+ });
1738
+ } else {
1739
+ resolve({
1740
+ id: call.id,
1741
+ success: true,
1742
+ result: "output" in toolResult ? toolResult.output : toolResult
1743
+ });
1744
+ }
1745
+ } catch (err) {
1746
+ clearTimeout(timeoutHandle);
1747
+ resolve({
1748
+ id: call.id,
1749
+ success: false,
1750
+ error: err instanceof Error ? err.message : String(err)
1751
+ });
1752
+ }
1753
+ });
1754
+ };
1755
+ const results = await Promise.all(
1756
+ calls.map((call) => executeWithTimeout(call, perCallTimeoutMs))
1757
+ );
1758
+ return {
1759
+ status: 200,
1760
+ body: {
1761
+ results,
1762
+ totalCalls: calls.length,
1763
+ successCount: results.filter((r) => r.success).length,
1764
+ failedCount: results.filter((r) => !r.success).length,
1765
+ timedOutCount: results.filter((r) => r.timedOut).length
1766
+ }
1767
+ };
1768
+ }
1641
1769
  function createNotFoundResponse() {
1642
1770
  return {
1643
1771
  status: 404,
@@ -1711,6 +1839,9 @@ async function routeRequest(req, ctx) {
1711
1839
  if (req.path === "/mcp" && req.method === "POST") {
1712
1840
  return handleMcpRoute(req, ctx);
1713
1841
  }
1842
+ if (req.path === "/mcp/batch" && req.method === "POST") {
1843
+ return handleMcpBatchRoute(req, ctx);
1844
+ }
1714
1845
  return createNotFoundResponse();
1715
1846
  } catch (err) {
1716
1847
  return createErrorResponse(err);
@@ -1807,30 +1938,22 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1807
1938
  httpServer.once("error", reject);
1808
1939
  });
1809
1940
  },
1941
+ async close() {
1942
+ return new Promise((resolve, reject) => {
1943
+ const closable = httpServer;
1944
+ closable.closeAllConnections?.();
1945
+ httpServer.close((err) => {
1946
+ if (err) reject(err);
1947
+ else resolve();
1948
+ });
1949
+ });
1950
+ },
1810
1951
  getHealthStatus: () => state.getHealthStatus()
1811
1952
  };
1812
1953
  }
1813
1954
  async function handleMcpWithSdkTransport(req, res, url, ctx, mcpServer) {
1814
1955
  try {
1815
1956
  const body = await parseJSONBody(req);
1816
- if (body?.method === "tools/call") {
1817
- console.log(
1818
- "[dedicated.ts /mcp] Received tools/call request:",
1819
- JSON.stringify(
1820
- {
1821
- method: body.method,
1822
- toolName: body.params?.name,
1823
- hasArguments: !!body.params?.arguments,
1824
- argumentKeys: body.params?.arguments ? Object.keys(body.params.arguments) : [],
1825
- hasEnv: !!body.params?.arguments?.env,
1826
- envKeys: body.params?.arguments?.env ? Object.keys(body.params.arguments.env) : [],
1827
- hasApiToken: !!body.params?.arguments?.env?.SKEDYUL_API_TOKEN
1828
- },
1829
- null,
1830
- 2
1831
- )
1832
- );
1833
- }
1834
1957
  if (body?.method === "tools/list") {
1835
1958
  sendJSON(res, 200, {
1836
1959
  jsonrpc: "2.0",
@@ -1905,65 +2028,46 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1905
2028
  }
1906
2029
 
1907
2030
  // src/server/index.ts
1908
- console.log("[skedyul-node/server] Module loading - imports done");
1909
- console.log("[skedyul-node/server] All imports complete");
1910
- console.log("[skedyul-node/server] Installing context logger...");
1911
2031
  installContextLogger();
1912
- console.log("[skedyul-node/server] Context logger installed");
1913
2032
  function createSkedyulServer(config) {
1914
- console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
1915
2033
  mergeRuntimeEnv();
1916
2034
  const registry = config.tools;
1917
2035
  const webhookRegistry = config.webhooks;
1918
- console.log("[createSkedyulServer] Step 2: coreApi setup");
1919
2036
  if (config.coreApi?.service) {
1920
2037
  coreApiService.register(config.coreApi.service);
1921
2038
  if (config.coreApi.webhookHandler) {
1922
2039
  coreApiService.setWebhookHandler(config.coreApi.webhookHandler);
1923
2040
  }
1924
2041
  }
1925
- console.log("[createSkedyulServer] Step 3: buildToolMetadata()");
1926
2042
  const tools = buildToolMetadata(registry);
1927
- console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
1928
2043
  const toolNames = Object.values(registry).map((tool) => tool.name);
1929
2044
  const runtimeLabel = config.computeLayer ?? "serverless";
1930
2045
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
1931
2046
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
1932
- console.log("[createSkedyulServer] Step 4: createRequestState()");
1933
2047
  const state = createRequestState(
1934
2048
  maxRequests,
1935
2049
  ttlExtendSeconds,
1936
2050
  runtimeLabel,
1937
2051
  toolNames
1938
2052
  );
1939
- console.log("[createSkedyulServer] Step 4 done");
1940
- console.log("[createSkedyulServer] Step 5: new McpServer()");
1941
2053
  const mcpServer = new import_mcp.McpServer({
1942
2054
  name: config.name,
1943
2055
  version: config.version ?? "0.0.0"
1944
2056
  });
1945
- console.log("[createSkedyulServer] Step 5 done");
1946
2057
  const dedicatedShutdown = () => {
1947
2058
  console.log("Max requests reached, shutting down...");
1948
2059
  setTimeout(() => process.exit(0), 1e3);
1949
2060
  };
1950
- console.log("[createSkedyulServer] Step 6: createCallToolHandler()");
1951
2061
  const callTool = createCallToolHandler(
1952
2062
  registry,
1953
2063
  state,
1954
2064
  config.computeLayer === "dedicated" ? dedicatedShutdown : void 0
1955
2065
  );
1956
- console.log("[createSkedyulServer] Step 6 done");
1957
- console.log("[createSkedyulServer] Step 7: Registering tools...");
1958
2066
  for (const [toolKey, tool] of Object.entries(registry)) {
1959
- console.log(`[createSkedyulServer] Registering tool: ${toolKey}`);
1960
2067
  const toolName = tool.name || toolKey;
1961
2068
  const toolDisplayName = tool.label || toolName;
1962
- console.log(`[createSkedyulServer] Getting input schema for ${toolKey}`);
1963
2069
  const inputZodSchema = getZodSchema(tool.inputSchema);
1964
- console.log(`[createSkedyulServer] Getting output schema for ${toolKey}`);
1965
2070
  const outputZodSchema = getZodSchema(tool.outputSchema);
1966
- console.log(`[createSkedyulServer] Creating wrapped schema for ${toolKey}`);
1967
2071
  const wrappedInputSchema = z3.object({
1968
2072
  inputs: inputZodSchema ?? z3.record(z3.string(), z3.unknown()).optional(),
1969
2073
  context: z3.record(z3.string(), z3.unknown()).optional(),
@@ -1971,7 +2075,6 @@ function createSkedyulServer(config) {
1971
2075
  invocation: z3.record(z3.string(), z3.unknown()).optional(),
1972
2076
  estimate: z3.boolean().optional()
1973
2077
  }).passthrough();
1974
- console.log(`[createSkedyulServer] Calling mcpServer.registerTool for ${toolKey}`);
1975
2078
  mcpServer.registerTool(
1976
2079
  toolName,
1977
2080
  {
@@ -1983,20 +2086,11 @@ function createSkedyulServer(config) {
1983
2086
  // outputSchema: outputZodSchema,
1984
2087
  },
1985
2088
  async (args) => {
1986
- console.log(`[mcpServer.registerTool] Tool ${toolName} received raw args:`, JSON.stringify(args, null, 2));
1987
2089
  const rawArgs = args;
1988
2090
  const toolInputs = rawArgs.inputs ?? {};
1989
2091
  const toolContext = rawArgs.context;
1990
2092
  const toolEnv = rawArgs.env;
1991
2093
  const toolInvocation = rawArgs.invocation;
1992
- console.log(`[mcpServer.registerTool] Tool ${toolName} extracted:`, {
1993
- hasInputs: !!rawArgs.inputs,
1994
- hasContext: !!rawArgs.context,
1995
- hasEnv: !!rawArgs.env,
1996
- hasInvocation: !!rawArgs.invocation,
1997
- envKeys: toolEnv ? Object.keys(toolEnv) : [],
1998
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1999
- });
2000
2094
  let validatedInputs = toolInputs;
2001
2095
  if (inputZodSchema) {
2002
2096
  try {
@@ -2030,10 +2124,10 @@ function createSkedyulServer(config) {
2030
2124
  invocation: toolInvocation
2031
2125
  });
2032
2126
  const hasOutputSchema = Boolean(outputZodSchema);
2033
- if (result.error) {
2034
- const errorOutput = { error: result.error };
2127
+ if (isToolCallFailure(result)) {
2128
+ const errorOutput = buildToolCallErrorOutput(result);
2035
2129
  return {
2036
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
2130
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
2037
2131
  // Don't provide structuredContent for error responses when tool has outputSchema
2038
2132
  // because the error response won't match the success schema and MCP SDK validates it
2039
2133
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
@@ -2041,7 +2135,8 @@ function createSkedyulServer(config) {
2041
2135
  billing: result.billing
2042
2136
  };
2043
2137
  }
2044
- const outputData = result.output;
2138
+ const rawOutput = "output" in result ? result.output : null;
2139
+ const outputData = rawOutput ?? null;
2045
2140
  const dataBlocks = result.dataBlocks;
2046
2141
  let structuredContent;
2047
2142
  if (outputData) {
@@ -2059,18 +2154,15 @@ function createSkedyulServer(config) {
2059
2154
  structuredContent = {};
2060
2155
  }
2061
2156
  return {
2062
- content: [{ type: "text", text: JSON.stringify(result.output) }],
2157
+ // Ensure text is always a string - JSON.stringify(undefined) returns undefined, not a string
2158
+ content: [{ type: "text", text: serializeMcpContentText(outputData) }],
2063
2159
  structuredContent,
2064
2160
  billing: result.billing
2065
2161
  };
2066
2162
  }
2067
2163
  );
2068
- console.log(`[createSkedyulServer] Tool ${toolKey} registered successfully`);
2069
2164
  }
2070
- console.log("[createSkedyulServer] Step 7 done - all tools registered");
2071
- console.log("[createSkedyulServer] Step 8: Creating server instance");
2072
2165
  if (config.computeLayer === "dedicated") {
2073
- console.log("[createSkedyulServer] Creating dedicated instance");
2074
2166
  return createDedicatedServerInstance(
2075
2167
  config,
2076
2168
  tools,
@@ -2079,10 +2171,7 @@ function createSkedyulServer(config) {
2079
2171
  mcpServer
2080
2172
  );
2081
2173
  }
2082
- console.log("[createSkedyulServer] Creating serverless instance");
2083
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
2084
- console.log("[createSkedyulServer] Serverless instance created successfully");
2085
- return serverlessInstance;
2174
+ return createServerlessInstance(config, tools, callTool, state, mcpServer);
2086
2175
  }
2087
2176
  var server = {
2088
2177
  create: createSkedyulServer