skedyul 1.2.43 → 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 +3682 -9076
  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 +264 -166
  21. package/dist/esm/index.mjs +1161 -7674
  22. package/dist/index.d.ts +11 -6
  23. package/dist/index.js +1206 -7672
  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 +264 -166
  44. package/dist/serverless/server.mjs +264 -166
  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 +35 -1
  54. package/package.json +8 -1
@@ -110,10 +110,24 @@ function parseNumberEnv(value) {
110
110
  const parsed = Number.parseInt(value, 10);
111
111
  return Number.isNaN(parsed) ? null : parsed;
112
112
  }
113
+ function getBakedExecutableEnv() {
114
+ return {
115
+ ...parseJsonRecord(process.env.MCP_ENV_JSON),
116
+ ...parseJsonRecord(process.env.MCP_ENV)
117
+ };
118
+ }
119
+ function buildToolExecutionEnv(requestEnv = {}) {
120
+ const bakedEnv = getBakedExecutableEnv();
121
+ const merged = { ...bakedEnv };
122
+ for (const [key, value] of Object.entries(requestEnv)) {
123
+ if (value !== void 0) {
124
+ merged[key] = value;
125
+ }
126
+ }
127
+ return merged;
128
+ }
113
129
  function mergeRuntimeEnv() {
114
- const bakedEnv = parseJsonRecord(process.env.MCP_ENV_JSON);
115
- const runtimeEnv = parseJsonRecord(process.env.MCP_ENV);
116
- const merged = { ...bakedEnv, ...runtimeEnv };
130
+ const merged = getBakedExecutableEnv();
117
131
  Object.assign(process.env, merged);
118
132
  }
119
133
 
@@ -158,6 +172,48 @@ function getListeningPort(config) {
158
172
  return config.defaultPort ?? 3e3;
159
173
  }
160
174
 
175
+ // src/server/utils/mcp-response.ts
176
+ function serializeMcpContentText(value) {
177
+ return JSON.stringify(value ?? null);
178
+ }
179
+ function isToolCallFailure(result) {
180
+ const isNewShapeFailure = "success" in result && result.success === false;
181
+ const isLegacyErrorFailure = "error" in result && result.error != null;
182
+ const isLegacyMetaFailure = "meta" in result && result.meta != null && typeof result.meta === "object" && "success" in result.meta && result.meta.success === false;
183
+ return isNewShapeFailure || isLegacyErrorFailure || isLegacyMetaFailure;
184
+ }
185
+ function buildToolCallErrorOutput(result) {
186
+ const isNewShapeFailure = "success" in result && result.success === false;
187
+ const isLegacyErrorFailure = "error" in result && result.error != null;
188
+ const isLegacyMetaFailure = "meta" in result && result.meta != null && typeof result.meta === "object" && "success" in result.meta && result.meta.success === false;
189
+ if (isNewShapeFailure && "error" in result) {
190
+ return {
191
+ error: result.error,
192
+ retry: "retry" in result ? result.retry : void 0
193
+ };
194
+ }
195
+ if (isLegacyErrorFailure && "error" in result) {
196
+ return { error: result.error };
197
+ }
198
+ if (isLegacyMetaFailure && "meta" in result && result.meta) {
199
+ const meta = result.meta;
200
+ return {
201
+ error: {
202
+ code: "TOOL_FAILED",
203
+ message: meta.message ?? "Tool execution failed",
204
+ category: "internal"
205
+ }
206
+ };
207
+ }
208
+ return {
209
+ error: {
210
+ code: "TOOL_FAILED",
211
+ message: "Tool execution failed",
212
+ category: "internal"
213
+ }
214
+ };
215
+ }
216
+
161
217
  // src/core/client.ts
162
218
  import { AsyncLocalStorage } from "async_hooks";
163
219
  import { z as z2 } from "zod/v4";
@@ -281,14 +337,19 @@ function createContextLogger() {
281
337
  function buildToolMetadata(registry) {
282
338
  return Object.values(registry).map((tool) => {
283
339
  const toolConfig = tool.config ?? {};
284
- const timeout = typeof toolConfig.timeout === "number" && toolConfig.timeout > 0 ? toolConfig.timeout : 1e4;
285
- const retries = typeof toolConfig.retries === "number" && toolConfig.retries >= 1 ? toolConfig.retries : 1;
340
+ const rawTimeout = tool.timeout ?? toolConfig.timeout;
341
+ const rawRetries = tool.retries ?? toolConfig.retries;
342
+ const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 1e4;
343
+ const retries = typeof rawRetries === "number" && rawRetries >= 1 ? rawRetries : 1;
286
344
  return {
287
345
  name: tool.name,
288
346
  displayName: tool.label || tool.name,
289
347
  description: tool.description,
290
348
  inputSchema: getJsonSchemaFromToolSchema(tool.inputSchema),
291
349
  outputSchema: getJsonSchemaFromToolSchema(tool.outputSchema),
350
+ // Include timeout/retries at top-level for tools/list response (used by syncExecutableTools)
351
+ timeout,
352
+ retries,
292
353
  config: {
293
354
  timeout,
294
355
  retries,
@@ -342,8 +403,9 @@ function createCallToolHandler(registry, state, onMaxRequests) {
342
403
  }
343
404
  }
344
405
  const requestEnv = args.env ?? {};
406
+ const toolEnv = buildToolExecutionEnv(requestEnv);
345
407
  const originalEnv = { ...process.env };
346
- Object.assign(process.env, requestEnv);
408
+ Object.assign(process.env, toolEnv);
347
409
  const invocation = args.invocation;
348
410
  try {
349
411
  const inputs = args.inputs ?? {};
@@ -356,7 +418,16 @@ function createCallToolHandler(registry, state, onMaxRequests) {
356
418
  executionContext = {
357
419
  trigger: "provision",
358
420
  app,
359
- env: process.env,
421
+ env: toolEnv,
422
+ mode: estimateMode ? "estimate" : "execute",
423
+ invocation,
424
+ log
425
+ };
426
+ } else if (trigger === "developer_page_action" || trigger === "developer_form_submit") {
427
+ executionContext = {
428
+ trigger,
429
+ app,
430
+ env: toolEnv,
360
431
  mode: estimateMode ? "estimate" : "execute",
361
432
  invocation,
362
433
  log
@@ -365,7 +436,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
365
436
  const workplace = rawContext.workplace;
366
437
  const request = rawContext.request;
367
438
  const appInstallationId = rawContext.appInstallationId;
368
- const envVars = process.env;
439
+ const envVars = toolEnv;
369
440
  const modeValue = estimateMode ? "estimate" : "execute";
370
441
  if (trigger === "field_change") {
371
442
  const field = rawContext.field;
@@ -385,8 +456,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
385
456
  }
386
457
  }
387
458
  const requestConfig = {
388
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
389
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
459
+ baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
460
+ apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
390
461
  };
391
462
  const functionResult = await runWithConfig(requestConfig, async () => {
392
463
  return await runWithLogContext({ invocation }, async () => {
@@ -394,7 +465,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
394
465
  });
395
466
  });
396
467
  const billing = normalizeBilling(functionResult.billing);
468
+ if ("success" in functionResult && functionResult.success === false) {
469
+ return {
470
+ success: false,
471
+ output: null,
472
+ billing,
473
+ error: "error" in functionResult ? functionResult.error : void 0,
474
+ effect: "effect" in functionResult ? functionResult.effect : void 0
475
+ };
476
+ }
397
477
  return {
478
+ success: true,
398
479
  output: functionResult.output,
399
480
  billing,
400
481
  meta: functionResult.meta ?? {
@@ -402,7 +483,9 @@ function createCallToolHandler(registry, state, onMaxRequests) {
402
483
  message: "OK",
403
484
  toolName
404
485
  },
405
- effect: functionResult.effect
486
+ effect: functionResult.effect,
487
+ dataBlocks: functionResult.dataBlocks,
488
+ cursor: functionResult.cursor
406
489
  };
407
490
  } catch (error) {
408
491
  if (error instanceof AppAuthInvalidError) {
@@ -765,8 +848,9 @@ function serializeConfig(config) {
765
848
  tools: registry ? Object.entries(registry).map(([key, tool]) => ({
766
849
  name: tool.name || key,
767
850
  description: tool.description,
768
- timeout: tool.config?.timeout,
769
- retries: tool.config?.retries
851
+ // Read timeout/retries from top-level first, then fallback to config
852
+ timeout: tool.timeout ?? tool.config?.timeout,
853
+ retries: tool.retries ?? tool.config?.retries
770
854
  })) : [],
771
855
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
772
856
  name: w.name,
@@ -1185,6 +1269,34 @@ function isMethodAllowed(webhookRegistry, handle, method) {
1185
1269
  function getConfigFilePath() {
1186
1270
  return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1187
1271
  }
1272
+ function findToolInRegistry(registry, toolName) {
1273
+ for (const [key, t] of Object.entries(registry)) {
1274
+ if (t.name === toolName || key === toolName) {
1275
+ return { toolKey: key, tool: t };
1276
+ }
1277
+ }
1278
+ const inputParts = toolName.split(":");
1279
+ const inputShortName = inputParts[inputParts.length - 1];
1280
+ if (inputParts.length > 1) {
1281
+ for (const [key, t] of Object.entries(registry)) {
1282
+ if (t.name === inputShortName || key === inputShortName) {
1283
+ return { toolKey: key, tool: t };
1284
+ }
1285
+ }
1286
+ }
1287
+ const matches = [];
1288
+ for (const [key, t] of Object.entries(registry)) {
1289
+ const registryParts = t.name.split(":");
1290
+ const registryShortName = registryParts[registryParts.length - 1];
1291
+ if (registryShortName === toolName) {
1292
+ matches.push({ toolKey: key, tool: t });
1293
+ }
1294
+ }
1295
+ if (matches.length === 1) {
1296
+ return matches[0];
1297
+ }
1298
+ return null;
1299
+ }
1188
1300
  function handleHealthRoute(ctx) {
1189
1301
  return {
1190
1302
  status: 200,
@@ -1194,34 +1306,14 @@ function handleHealthRoute(ctx) {
1194
1306
  function handleConfigRoute(ctx) {
1195
1307
  const configFilePath = getConfigFilePath();
1196
1308
  try {
1197
- console.log(`[/config] Checking for config file at: ${configFilePath}`);
1198
1309
  if (fs.existsSync(configFilePath)) {
1199
1310
  const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
1200
- console.log(
1201
- `[/config] Loaded config from file: tools=${fileConfig.tools?.length ?? 0}, webhooks=${fileConfig.webhooks?.length ?? 0}`
1202
- );
1203
- console.log(
1204
- `[/config] SENDING config with keys: ${Object.keys(fileConfig).join(", ")}`
1205
- );
1206
- console.log(
1207
- `[/config] SENDING full config: ${JSON.stringify(fileConfig).substring(0, 2e3)}...`
1208
- );
1209
1311
  return { status: 200, body: fileConfig };
1210
1312
  }
1211
- console.log("[/config] Config file not found, falling back to runtime serialization");
1212
1313
  } catch (err) {
1213
1314
  console.warn("[/config] Failed to read config file, falling back to runtime serialization:", err);
1214
1315
  }
1215
1316
  const serialized = serializeConfig(ctx.config);
1216
- console.log(
1217
- `[/config] Runtime serialization: tools=${serialized.tools?.length ?? 0}, webhooks=${serialized.webhooks?.length ?? 0}`
1218
- );
1219
- console.log(
1220
- `[/config] SENDING serialized config with keys: ${Object.keys(serialized).join(", ")}`
1221
- );
1222
- console.log(
1223
- `[/config] SENDING full serialized config: ${JSON.stringify(serialized).substring(0, 2e3)}...`
1224
- );
1225
1317
  return { status: 200, body: serialized };
1226
1318
  }
1227
1319
  async function handleCoreRoute(req, ctx) {
@@ -1276,16 +1368,8 @@ async function handleEstimateRoute(req, ctx) {
1276
1368
  try {
1277
1369
  const toolName = estimateBody.name;
1278
1370
  const toolArgs = estimateBody.inputs ?? {};
1279
- let toolKey = null;
1280
- let tool = null;
1281
- for (const [key, t] of Object.entries(ctx.registry)) {
1282
- if (t.name === toolName || key === toolName) {
1283
- toolKey = key;
1284
- tool = t;
1285
- break;
1286
- }
1287
- }
1288
- if (!tool || !toolKey) {
1371
+ const found = findToolInRegistry(ctx.registry, toolName);
1372
+ if (!found) {
1289
1373
  return {
1290
1374
  status: 400,
1291
1375
  body: {
@@ -1296,6 +1380,7 @@ async function handleEstimateRoute(req, ctx) {
1296
1380
  }
1297
1381
  };
1298
1382
  }
1383
+ const { toolKey, tool } = found;
1299
1384
  const inputSchema = getZodSchema(tool.inputSchema);
1300
1385
  const validatedArgs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
1301
1386
  const estimateResponse = await ctx.callTool(toolKey, {
@@ -1458,35 +1543,13 @@ async function handleMcpRoute(req, ctx) {
1458
1543
  async function handleMcpToolsCall(params, id, ctx) {
1459
1544
  const toolName = params?.name;
1460
1545
  const rawArgs = params?.arguments ?? {};
1461
- console.log("[route-handlers /mcp] Received tools/call request:", JSON.stringify({
1462
- toolName,
1463
- hasArguments: !!params?.arguments,
1464
- argumentKeys: rawArgs ? Object.keys(rawArgs) : [],
1465
- hasEnv: "env" in rawArgs,
1466
- envKeys: rawArgs.env ? Object.keys(rawArgs.env) : [],
1467
- hasApiToken: !!rawArgs.env?.SKEDYUL_API_TOKEN
1468
- }, null, 2));
1469
1546
  const hasSkedyulFormat = "inputs" in rawArgs || "env" in rawArgs || "context" in rawArgs || "invocation" in rawArgs;
1470
1547
  const toolInputs = hasSkedyulFormat ? rawArgs.inputs ?? {} : rawArgs;
1471
1548
  const toolContext = hasSkedyulFormat ? rawArgs.context : void 0;
1472
1549
  const toolEnv = hasSkedyulFormat ? rawArgs.env : void 0;
1473
1550
  const toolInvocation = hasSkedyulFormat ? rawArgs.invocation : void 0;
1474
- console.log("[route-handlers /mcp] Extracted env:", JSON.stringify({
1475
- hasSkedyulFormat,
1476
- hasToolEnv: !!toolEnv,
1477
- toolEnvKeys: toolEnv ? Object.keys(toolEnv) : [],
1478
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1479
- }, null, 2));
1480
- let toolKey = null;
1481
- let tool = null;
1482
- for (const [key, t] of Object.entries(ctx.registry)) {
1483
- if (t.name === toolName || key === toolName) {
1484
- toolKey = key;
1485
- tool = t;
1486
- break;
1487
- }
1488
- }
1489
- if (!tool || !toolKey) {
1551
+ const found = findToolInRegistry(ctx.registry, toolName);
1552
+ if (!found) {
1490
1553
  return {
1491
1554
  status: 200,
1492
1555
  body: {
@@ -1499,6 +1562,7 @@ async function handleMcpToolsCall(params, id, ctx) {
1499
1562
  }
1500
1563
  };
1501
1564
  }
1565
+ const { toolKey, tool } = found;
1502
1566
  try {
1503
1567
  const inputSchema = getZodSchema(tool.inputSchema);
1504
1568
  const outputSchema = getZodSchema(tool.outputSchema);
@@ -1511,68 +1575,45 @@ async function handleMcpToolsCall(params, id, ctx) {
1511
1575
  invocation: toolInvocation
1512
1576
  });
1513
1577
  let result;
1514
- const isNewShapeFailure = "success" in toolResult && toolResult.success === false;
1515
- const isLegacyErrorFailure = "error" in toolResult && toolResult.error != null;
1516
- const isLegacyMetaFailure = "meta" in toolResult && toolResult.meta != null && typeof toolResult.meta === "object" && "success" in toolResult.meta && toolResult.meta.success === false;
1517
- const isFailure = isNewShapeFailure || isLegacyErrorFailure || isLegacyMetaFailure;
1578
+ const isFailure = isToolCallFailure(toolResult);
1518
1579
  if (isFailure) {
1519
- let errorOutput;
1520
- if (isNewShapeFailure && "error" in toolResult) {
1521
- errorOutput = {
1522
- error: toolResult.error,
1523
- retry: "retry" in toolResult ? toolResult.retry : void 0
1524
- };
1525
- } else if (isLegacyErrorFailure && "error" in toolResult) {
1526
- errorOutput = { error: toolResult.error };
1527
- } else if (isLegacyMetaFailure && "meta" in toolResult && toolResult.meta) {
1528
- const meta = toolResult.meta;
1529
- errorOutput = {
1530
- error: {
1531
- code: "TOOL_FAILED",
1532
- message: meta.message ?? "Tool execution failed",
1533
- category: "internal"
1534
- }
1535
- };
1536
- } else {
1537
- errorOutput = {
1538
- error: {
1539
- code: "TOOL_FAILED",
1540
- message: "Tool execution failed",
1541
- category: "internal"
1542
- }
1543
- };
1544
- }
1580
+ const errorOutput = buildToolCallErrorOutput(toolResult);
1545
1581
  result = {
1546
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
1582
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
1547
1583
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
1548
1584
  isError: true,
1549
1585
  billing: "billing" in toolResult ? toolResult.billing : void 0
1550
1586
  };
1551
1587
  } else {
1552
- const outputData = "output" in toolResult ? toolResult.output : null;
1588
+ const rawOutput = "output" in toolResult ? toolResult.output : null;
1589
+ const outputData = rawOutput ?? null;
1553
1590
  const effect = "effect" in toolResult ? toolResult.effect : void 0;
1554
1591
  const warnings = "warnings" in toolResult ? toolResult.warnings : void 0;
1555
1592
  const pagination = "pagination" in toolResult ? toolResult.pagination : void 0;
1593
+ const cursor = "cursor" in toolResult ? toolResult.cursor : void 0;
1556
1594
  let structuredContent;
1557
1595
  if (outputData) {
1558
1596
  structuredContent = {
1559
1597
  ...outputData,
1560
1598
  __effect: effect,
1561
1599
  __warnings: warnings,
1562
- __pagination: pagination
1600
+ __pagination: pagination,
1601
+ __cursor: cursor
1563
1602
  };
1564
- } else if (effect || warnings || pagination) {
1603
+ } else if (effect || warnings || pagination || cursor) {
1565
1604
  structuredContent = {
1566
1605
  __effect: effect,
1567
1606
  __warnings: warnings,
1568
- __pagination: pagination
1607
+ __pagination: pagination,
1608
+ __cursor: cursor
1569
1609
  };
1570
1610
  } else if (hasOutputSchema) {
1571
1611
  structuredContent = {};
1572
1612
  }
1573
1613
  result = {
1574
- content: [{ type: "text", text: JSON.stringify(outputData) }],
1614
+ content: [{ type: "text", text: serializeMcpContentText(outputData) }],
1575
1615
  structuredContent,
1616
+ cursor,
1576
1617
  billing: "billing" in toolResult ? toolResult.billing : void 0
1577
1618
  };
1578
1619
  }
@@ -1598,6 +1639,94 @@ async function handleMcpToolsCall(params, id, ctx) {
1598
1639
  };
1599
1640
  }
1600
1641
  }
1642
+ async function handleMcpBatchRoute(req, ctx) {
1643
+ const parseResult = parseJsonBody(req);
1644
+ if (!parseResult.success) {
1645
+ return parseResult.error;
1646
+ }
1647
+ const { calls, deadline, env: batchEnv, context: batchContext } = parseResult.data;
1648
+ if (!calls || !Array.isArray(calls) || calls.length === 0) {
1649
+ return {
1650
+ status: 400,
1651
+ body: {
1652
+ error: {
1653
+ code: -32602,
1654
+ message: "Missing or invalid calls array"
1655
+ }
1656
+ }
1657
+ };
1658
+ }
1659
+ const perCallTimeoutMs = deadline ? Math.min(deadline, 25e3) : 25e3;
1660
+ const executeWithTimeout = async (call, timeoutMs) => {
1661
+ return new Promise(async (resolve) => {
1662
+ const timeoutHandle = setTimeout(() => {
1663
+ resolve({
1664
+ id: call.id,
1665
+ success: false,
1666
+ error: "Timeout",
1667
+ timedOut: true
1668
+ });
1669
+ }, timeoutMs);
1670
+ try {
1671
+ const toolName = call.name;
1672
+ const toolArgs = call.arguments ?? {};
1673
+ const found = findToolInRegistry(ctx.registry, toolName);
1674
+ if (!found) {
1675
+ clearTimeout(timeoutHandle);
1676
+ resolve({
1677
+ id: call.id,
1678
+ success: false,
1679
+ error: `Tool "${toolName}" not found`
1680
+ });
1681
+ return;
1682
+ }
1683
+ const { toolKey, tool } = found;
1684
+ const inputSchema = getZodSchema(tool.inputSchema);
1685
+ const validatedInputs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
1686
+ const toolResult = await ctx.callTool(toolKey, {
1687
+ inputs: validatedInputs,
1688
+ context: batchContext,
1689
+ env: batchEnv
1690
+ });
1691
+ clearTimeout(timeoutHandle);
1692
+ const isFailure = "success" in toolResult && toolResult.success === false || "error" in toolResult && toolResult.error != null;
1693
+ if (isFailure) {
1694
+ resolve({
1695
+ id: call.id,
1696
+ success: false,
1697
+ error: "error" in toolResult && toolResult.error ? typeof toolResult.error === "string" ? toolResult.error : JSON.stringify(toolResult.error) : "Tool execution failed"
1698
+ });
1699
+ } else {
1700
+ resolve({
1701
+ id: call.id,
1702
+ success: true,
1703
+ result: "output" in toolResult ? toolResult.output : toolResult
1704
+ });
1705
+ }
1706
+ } catch (err) {
1707
+ clearTimeout(timeoutHandle);
1708
+ resolve({
1709
+ id: call.id,
1710
+ success: false,
1711
+ error: err instanceof Error ? err.message : String(err)
1712
+ });
1713
+ }
1714
+ });
1715
+ };
1716
+ const results = await Promise.all(
1717
+ calls.map((call) => executeWithTimeout(call, perCallTimeoutMs))
1718
+ );
1719
+ return {
1720
+ status: 200,
1721
+ body: {
1722
+ results,
1723
+ totalCalls: calls.length,
1724
+ successCount: results.filter((r) => r.success).length,
1725
+ failedCount: results.filter((r) => !r.success).length,
1726
+ timedOutCount: results.filter((r) => r.timedOut).length
1727
+ }
1728
+ };
1729
+ }
1601
1730
  function createNotFoundResponse() {
1602
1731
  return {
1603
1732
  status: 404,
@@ -1671,6 +1800,9 @@ async function routeRequest(req, ctx) {
1671
1800
  if (req.path === "/mcp" && req.method === "POST") {
1672
1801
  return handleMcpRoute(req, ctx);
1673
1802
  }
1803
+ if (req.path === "/mcp/batch" && req.method === "POST") {
1804
+ return handleMcpBatchRoute(req, ctx);
1805
+ }
1674
1806
  return createNotFoundResponse();
1675
1807
  } catch (err) {
1676
1808
  return createErrorResponse(err);
@@ -1767,30 +1899,22 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1767
1899
  httpServer.once("error", reject);
1768
1900
  });
1769
1901
  },
1902
+ async close() {
1903
+ return new Promise((resolve, reject) => {
1904
+ const closable = httpServer;
1905
+ closable.closeAllConnections?.();
1906
+ httpServer.close((err) => {
1907
+ if (err) reject(err);
1908
+ else resolve();
1909
+ });
1910
+ });
1911
+ },
1770
1912
  getHealthStatus: () => state.getHealthStatus()
1771
1913
  };
1772
1914
  }
1773
1915
  async function handleMcpWithSdkTransport(req, res, url, ctx, mcpServer) {
1774
1916
  try {
1775
1917
  const body = await parseJSONBody(req);
1776
- if (body?.method === "tools/call") {
1777
- console.log(
1778
- "[dedicated.ts /mcp] Received tools/call request:",
1779
- JSON.stringify(
1780
- {
1781
- method: body.method,
1782
- toolName: body.params?.name,
1783
- hasArguments: !!body.params?.arguments,
1784
- argumentKeys: body.params?.arguments ? Object.keys(body.params.arguments) : [],
1785
- hasEnv: !!body.params?.arguments?.env,
1786
- envKeys: body.params?.arguments?.env ? Object.keys(body.params.arguments.env) : [],
1787
- hasApiToken: !!body.params?.arguments?.env?.SKEDYUL_API_TOKEN
1788
- },
1789
- null,
1790
- 2
1791
- )
1792
- );
1793
- }
1794
1918
  if (body?.method === "tools/list") {
1795
1919
  sendJSON(res, 200, {
1796
1920
  jsonrpc: "2.0",
@@ -1865,65 +1989,46 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1865
1989
  }
1866
1990
 
1867
1991
  // src/server/index.ts
1868
- console.log("[skedyul-node/server] Module loading - imports done");
1869
- console.log("[skedyul-node/server] All imports complete");
1870
- console.log("[skedyul-node/server] Installing context logger...");
1871
1992
  installContextLogger();
1872
- console.log("[skedyul-node/server] Context logger installed");
1873
1993
  function createSkedyulServer(config) {
1874
- console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
1875
1994
  mergeRuntimeEnv();
1876
1995
  const registry = config.tools;
1877
1996
  const webhookRegistry = config.webhooks;
1878
- console.log("[createSkedyulServer] Step 2: coreApi setup");
1879
1997
  if (config.coreApi?.service) {
1880
1998
  coreApiService.register(config.coreApi.service);
1881
1999
  if (config.coreApi.webhookHandler) {
1882
2000
  coreApiService.setWebhookHandler(config.coreApi.webhookHandler);
1883
2001
  }
1884
2002
  }
1885
- console.log("[createSkedyulServer] Step 3: buildToolMetadata()");
1886
2003
  const tools = buildToolMetadata(registry);
1887
- console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
1888
2004
  const toolNames = Object.values(registry).map((tool) => tool.name);
1889
2005
  const runtimeLabel = config.computeLayer ?? "serverless";
1890
2006
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
1891
2007
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
1892
- console.log("[createSkedyulServer] Step 4: createRequestState()");
1893
2008
  const state = createRequestState(
1894
2009
  maxRequests,
1895
2010
  ttlExtendSeconds,
1896
2011
  runtimeLabel,
1897
2012
  toolNames
1898
2013
  );
1899
- console.log("[createSkedyulServer] Step 4 done");
1900
- console.log("[createSkedyulServer] Step 5: new McpServer()");
1901
2014
  const mcpServer = new McpServer({
1902
2015
  name: config.name,
1903
2016
  version: config.version ?? "0.0.0"
1904
2017
  });
1905
- console.log("[createSkedyulServer] Step 5 done");
1906
2018
  const dedicatedShutdown = () => {
1907
2019
  console.log("Max requests reached, shutting down...");
1908
2020
  setTimeout(() => process.exit(0), 1e3);
1909
2021
  };
1910
- console.log("[createSkedyulServer] Step 6: createCallToolHandler()");
1911
2022
  const callTool = createCallToolHandler(
1912
2023
  registry,
1913
2024
  state,
1914
2025
  config.computeLayer === "dedicated" ? dedicatedShutdown : void 0
1915
2026
  );
1916
- console.log("[createSkedyulServer] Step 6 done");
1917
- console.log("[createSkedyulServer] Step 7: Registering tools...");
1918
2027
  for (const [toolKey, tool] of Object.entries(registry)) {
1919
- console.log(`[createSkedyulServer] Registering tool: ${toolKey}`);
1920
2028
  const toolName = tool.name || toolKey;
1921
2029
  const toolDisplayName = tool.label || toolName;
1922
- console.log(`[createSkedyulServer] Getting input schema for ${toolKey}`);
1923
2030
  const inputZodSchema = getZodSchema(tool.inputSchema);
1924
- console.log(`[createSkedyulServer] Getting output schema for ${toolKey}`);
1925
2031
  const outputZodSchema = getZodSchema(tool.outputSchema);
1926
- console.log(`[createSkedyulServer] Creating wrapped schema for ${toolKey}`);
1927
2032
  const wrappedInputSchema = z3.object({
1928
2033
  inputs: inputZodSchema ?? z3.record(z3.string(), z3.unknown()).optional(),
1929
2034
  context: z3.record(z3.string(), z3.unknown()).optional(),
@@ -1931,7 +2036,6 @@ function createSkedyulServer(config) {
1931
2036
  invocation: z3.record(z3.string(), z3.unknown()).optional(),
1932
2037
  estimate: z3.boolean().optional()
1933
2038
  }).passthrough();
1934
- console.log(`[createSkedyulServer] Calling mcpServer.registerTool for ${toolKey}`);
1935
2039
  mcpServer.registerTool(
1936
2040
  toolName,
1937
2041
  {
@@ -1943,20 +2047,11 @@ function createSkedyulServer(config) {
1943
2047
  // outputSchema: outputZodSchema,
1944
2048
  },
1945
2049
  async (args) => {
1946
- console.log(`[mcpServer.registerTool] Tool ${toolName} received raw args:`, JSON.stringify(args, null, 2));
1947
2050
  const rawArgs = args;
1948
2051
  const toolInputs = rawArgs.inputs ?? {};
1949
2052
  const toolContext = rawArgs.context;
1950
2053
  const toolEnv = rawArgs.env;
1951
2054
  const toolInvocation = rawArgs.invocation;
1952
- console.log(`[mcpServer.registerTool] Tool ${toolName} extracted:`, {
1953
- hasInputs: !!rawArgs.inputs,
1954
- hasContext: !!rawArgs.context,
1955
- hasEnv: !!rawArgs.env,
1956
- hasInvocation: !!rawArgs.invocation,
1957
- envKeys: toolEnv ? Object.keys(toolEnv) : [],
1958
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1959
- });
1960
2055
  let validatedInputs = toolInputs;
1961
2056
  if (inputZodSchema) {
1962
2057
  try {
@@ -1990,10 +2085,10 @@ function createSkedyulServer(config) {
1990
2085
  invocation: toolInvocation
1991
2086
  });
1992
2087
  const hasOutputSchema = Boolean(outputZodSchema);
1993
- if (result.error) {
1994
- const errorOutput = { error: result.error };
2088
+ if (isToolCallFailure(result)) {
2089
+ const errorOutput = buildToolCallErrorOutput(result);
1995
2090
  return {
1996
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
2091
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
1997
2092
  // Don't provide structuredContent for error responses when tool has outputSchema
1998
2093
  // because the error response won't match the success schema and MCP SDK validates it
1999
2094
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
@@ -2001,28 +2096,34 @@ function createSkedyulServer(config) {
2001
2096
  billing: result.billing
2002
2097
  };
2003
2098
  }
2004
- const outputData = result.output;
2099
+ const rawOutput = "output" in result ? result.output : null;
2100
+ const outputData = rawOutput ?? null;
2101
+ const dataBlocks = result.dataBlocks;
2005
2102
  let structuredContent;
2006
2103
  if (outputData) {
2007
- structuredContent = { ...outputData, __effect: result.effect };
2008
- } else if (result.effect) {
2009
- structuredContent = { __effect: result.effect };
2104
+ structuredContent = {
2105
+ ...outputData,
2106
+ __effect: result.effect,
2107
+ __dataBlocks: dataBlocks
2108
+ };
2109
+ } else if (result.effect || dataBlocks) {
2110
+ structuredContent = {
2111
+ __effect: result.effect,
2112
+ __dataBlocks: dataBlocks
2113
+ };
2010
2114
  } else if (hasOutputSchema) {
2011
2115
  structuredContent = {};
2012
2116
  }
2013
2117
  return {
2014
- content: [{ type: "text", text: JSON.stringify(result.output) }],
2118
+ // Ensure text is always a string - JSON.stringify(undefined) returns undefined, not a string
2119
+ content: [{ type: "text", text: serializeMcpContentText(outputData) }],
2015
2120
  structuredContent,
2016
2121
  billing: result.billing
2017
2122
  };
2018
2123
  }
2019
2124
  );
2020
- console.log(`[createSkedyulServer] Tool ${toolKey} registered successfully`);
2021
2125
  }
2022
- console.log("[createSkedyulServer] Step 7 done - all tools registered");
2023
- console.log("[createSkedyulServer] Step 8: Creating server instance");
2024
2126
  if (config.computeLayer === "dedicated") {
2025
- console.log("[createSkedyulServer] Creating dedicated instance");
2026
2127
  return createDedicatedServerInstance(
2027
2128
  config,
2028
2129
  tools,
@@ -2031,10 +2132,7 @@ function createSkedyulServer(config) {
2031
2132
  mcpServer
2032
2133
  );
2033
2134
  }
2034
- console.log("[createSkedyulServer] Creating serverless instance");
2035
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
2036
- console.log("[createSkedyulServer] Serverless instance created successfully");
2037
- return serverlessInstance;
2135
+ return createServerlessInstance(config, tools, callTool, state, mcpServer);
2038
2136
  }
2039
2137
  var server = {
2040
2138
  create: createSkedyulServer