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
@@ -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 ?? {
@@ -403,7 +484,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
403
484
  toolName
404
485
  },
405
486
  effect: functionResult.effect,
406
- dataBlocks: functionResult.dataBlocks
487
+ dataBlocks: functionResult.dataBlocks,
488
+ cursor: functionResult.cursor
407
489
  };
408
490
  } catch (error) {
409
491
  if (error instanceof AppAuthInvalidError) {
@@ -766,8 +848,9 @@ function serializeConfig(config) {
766
848
  tools: registry ? Object.entries(registry).map(([key, tool]) => ({
767
849
  name: tool.name || key,
768
850
  description: tool.description,
769
- timeout: tool.config?.timeout,
770
- 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
771
854
  })) : [],
772
855
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
773
856
  name: w.name,
@@ -1186,6 +1269,34 @@ function isMethodAllowed(webhookRegistry, handle, method) {
1186
1269
  function getConfigFilePath() {
1187
1270
  return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1188
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
+ }
1189
1300
  function handleHealthRoute(ctx) {
1190
1301
  return {
1191
1302
  status: 200,
@@ -1195,34 +1306,14 @@ function handleHealthRoute(ctx) {
1195
1306
  function handleConfigRoute(ctx) {
1196
1307
  const configFilePath = getConfigFilePath();
1197
1308
  try {
1198
- console.log(`[/config] Checking for config file at: ${configFilePath}`);
1199
1309
  if (fs.existsSync(configFilePath)) {
1200
1310
  const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
1201
- console.log(
1202
- `[/config] Loaded config from file: tools=${fileConfig.tools?.length ?? 0}, webhooks=${fileConfig.webhooks?.length ?? 0}`
1203
- );
1204
- console.log(
1205
- `[/config] SENDING config with keys: ${Object.keys(fileConfig).join(", ")}`
1206
- );
1207
- console.log(
1208
- `[/config] SENDING full config: ${JSON.stringify(fileConfig).substring(0, 2e3)}...`
1209
- );
1210
1311
  return { status: 200, body: fileConfig };
1211
1312
  }
1212
- console.log("[/config] Config file not found, falling back to runtime serialization");
1213
1313
  } catch (err) {
1214
1314
  console.warn("[/config] Failed to read config file, falling back to runtime serialization:", err);
1215
1315
  }
1216
1316
  const serialized = serializeConfig(ctx.config);
1217
- console.log(
1218
- `[/config] Runtime serialization: tools=${serialized.tools?.length ?? 0}, webhooks=${serialized.webhooks?.length ?? 0}`
1219
- );
1220
- console.log(
1221
- `[/config] SENDING serialized config with keys: ${Object.keys(serialized).join(", ")}`
1222
- );
1223
- console.log(
1224
- `[/config] SENDING full serialized config: ${JSON.stringify(serialized).substring(0, 2e3)}...`
1225
- );
1226
1317
  return { status: 200, body: serialized };
1227
1318
  }
1228
1319
  async function handleCoreRoute(req, ctx) {
@@ -1277,16 +1368,8 @@ async function handleEstimateRoute(req, ctx) {
1277
1368
  try {
1278
1369
  const toolName = estimateBody.name;
1279
1370
  const toolArgs = estimateBody.inputs ?? {};
1280
- let toolKey = null;
1281
- let tool = null;
1282
- for (const [key, t] of Object.entries(ctx.registry)) {
1283
- if (t.name === toolName || key === toolName) {
1284
- toolKey = key;
1285
- tool = t;
1286
- break;
1287
- }
1288
- }
1289
- if (!tool || !toolKey) {
1371
+ const found = findToolInRegistry(ctx.registry, toolName);
1372
+ if (!found) {
1290
1373
  return {
1291
1374
  status: 400,
1292
1375
  body: {
@@ -1297,6 +1380,7 @@ async function handleEstimateRoute(req, ctx) {
1297
1380
  }
1298
1381
  };
1299
1382
  }
1383
+ const { toolKey, tool } = found;
1300
1384
  const inputSchema = getZodSchema(tool.inputSchema);
1301
1385
  const validatedArgs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
1302
1386
  const estimateResponse = await ctx.callTool(toolKey, {
@@ -1459,35 +1543,13 @@ async function handleMcpRoute(req, ctx) {
1459
1543
  async function handleMcpToolsCall(params, id, ctx) {
1460
1544
  const toolName = params?.name;
1461
1545
  const rawArgs = params?.arguments ?? {};
1462
- console.log("[route-handlers /mcp] Received tools/call request:", JSON.stringify({
1463
- toolName,
1464
- hasArguments: !!params?.arguments,
1465
- argumentKeys: rawArgs ? Object.keys(rawArgs) : [],
1466
- hasEnv: "env" in rawArgs,
1467
- envKeys: rawArgs.env ? Object.keys(rawArgs.env) : [],
1468
- hasApiToken: !!rawArgs.env?.SKEDYUL_API_TOKEN
1469
- }, null, 2));
1470
1546
  const hasSkedyulFormat = "inputs" in rawArgs || "env" in rawArgs || "context" in rawArgs || "invocation" in rawArgs;
1471
1547
  const toolInputs = hasSkedyulFormat ? rawArgs.inputs ?? {} : rawArgs;
1472
1548
  const toolContext = hasSkedyulFormat ? rawArgs.context : void 0;
1473
1549
  const toolEnv = hasSkedyulFormat ? rawArgs.env : void 0;
1474
1550
  const toolInvocation = hasSkedyulFormat ? rawArgs.invocation : void 0;
1475
- console.log("[route-handlers /mcp] Extracted env:", JSON.stringify({
1476
- hasSkedyulFormat,
1477
- hasToolEnv: !!toolEnv,
1478
- toolEnvKeys: toolEnv ? Object.keys(toolEnv) : [],
1479
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1480
- }, null, 2));
1481
- let toolKey = null;
1482
- let tool = null;
1483
- for (const [key, t] of Object.entries(ctx.registry)) {
1484
- if (t.name === toolName || key === toolName) {
1485
- toolKey = key;
1486
- tool = t;
1487
- break;
1488
- }
1489
- }
1490
- if (!tool || !toolKey) {
1551
+ const found = findToolInRegistry(ctx.registry, toolName);
1552
+ if (!found) {
1491
1553
  return {
1492
1554
  status: 200,
1493
1555
  body: {
@@ -1500,6 +1562,7 @@ async function handleMcpToolsCall(params, id, ctx) {
1500
1562
  }
1501
1563
  };
1502
1564
  }
1565
+ const { toolKey, tool } = found;
1503
1566
  try {
1504
1567
  const inputSchema = getZodSchema(tool.inputSchema);
1505
1568
  const outputSchema = getZodSchema(tool.outputSchema);
@@ -1512,68 +1575,45 @@ async function handleMcpToolsCall(params, id, ctx) {
1512
1575
  invocation: toolInvocation
1513
1576
  });
1514
1577
  let result;
1515
- const isNewShapeFailure = "success" in toolResult && toolResult.success === false;
1516
- const isLegacyErrorFailure = "error" in toolResult && toolResult.error != null;
1517
- const isLegacyMetaFailure = "meta" in toolResult && toolResult.meta != null && typeof toolResult.meta === "object" && "success" in toolResult.meta && toolResult.meta.success === false;
1518
- const isFailure = isNewShapeFailure || isLegacyErrorFailure || isLegacyMetaFailure;
1578
+ const isFailure = isToolCallFailure(toolResult);
1519
1579
  if (isFailure) {
1520
- let errorOutput;
1521
- if (isNewShapeFailure && "error" in toolResult) {
1522
- errorOutput = {
1523
- error: toolResult.error,
1524
- retry: "retry" in toolResult ? toolResult.retry : void 0
1525
- };
1526
- } else if (isLegacyErrorFailure && "error" in toolResult) {
1527
- errorOutput = { error: toolResult.error };
1528
- } else if (isLegacyMetaFailure && "meta" in toolResult && toolResult.meta) {
1529
- const meta = toolResult.meta;
1530
- errorOutput = {
1531
- error: {
1532
- code: "TOOL_FAILED",
1533
- message: meta.message ?? "Tool execution failed",
1534
- category: "internal"
1535
- }
1536
- };
1537
- } else {
1538
- errorOutput = {
1539
- error: {
1540
- code: "TOOL_FAILED",
1541
- message: "Tool execution failed",
1542
- category: "internal"
1543
- }
1544
- };
1545
- }
1580
+ const errorOutput = buildToolCallErrorOutput(toolResult);
1546
1581
  result = {
1547
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
1582
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
1548
1583
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
1549
1584
  isError: true,
1550
1585
  billing: "billing" in toolResult ? toolResult.billing : void 0
1551
1586
  };
1552
1587
  } else {
1553
- const outputData = "output" in toolResult ? toolResult.output : null;
1588
+ const rawOutput = "output" in toolResult ? toolResult.output : null;
1589
+ const outputData = rawOutput ?? null;
1554
1590
  const effect = "effect" in toolResult ? toolResult.effect : void 0;
1555
1591
  const warnings = "warnings" in toolResult ? toolResult.warnings : void 0;
1556
1592
  const pagination = "pagination" in toolResult ? toolResult.pagination : void 0;
1593
+ const cursor = "cursor" in toolResult ? toolResult.cursor : void 0;
1557
1594
  let structuredContent;
1558
1595
  if (outputData) {
1559
1596
  structuredContent = {
1560
1597
  ...outputData,
1561
1598
  __effect: effect,
1562
1599
  __warnings: warnings,
1563
- __pagination: pagination
1600
+ __pagination: pagination,
1601
+ __cursor: cursor
1564
1602
  };
1565
- } else if (effect || warnings || pagination) {
1603
+ } else if (effect || warnings || pagination || cursor) {
1566
1604
  structuredContent = {
1567
1605
  __effect: effect,
1568
1606
  __warnings: warnings,
1569
- __pagination: pagination
1607
+ __pagination: pagination,
1608
+ __cursor: cursor
1570
1609
  };
1571
1610
  } else if (hasOutputSchema) {
1572
1611
  structuredContent = {};
1573
1612
  }
1574
1613
  result = {
1575
- content: [{ type: "text", text: JSON.stringify(outputData) }],
1614
+ content: [{ type: "text", text: serializeMcpContentText(outputData) }],
1576
1615
  structuredContent,
1616
+ cursor,
1577
1617
  billing: "billing" in toolResult ? toolResult.billing : void 0
1578
1618
  };
1579
1619
  }
@@ -1599,6 +1639,94 @@ async function handleMcpToolsCall(params, id, ctx) {
1599
1639
  };
1600
1640
  }
1601
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
+ }
1602
1730
  function createNotFoundResponse() {
1603
1731
  return {
1604
1732
  status: 404,
@@ -1672,6 +1800,9 @@ async function routeRequest(req, ctx) {
1672
1800
  if (req.path === "/mcp" && req.method === "POST") {
1673
1801
  return handleMcpRoute(req, ctx);
1674
1802
  }
1803
+ if (req.path === "/mcp/batch" && req.method === "POST") {
1804
+ return handleMcpBatchRoute(req, ctx);
1805
+ }
1675
1806
  return createNotFoundResponse();
1676
1807
  } catch (err) {
1677
1808
  return createErrorResponse(err);
@@ -1768,30 +1899,22 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
1768
1899
  httpServer.once("error", reject);
1769
1900
  });
1770
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
+ },
1771
1912
  getHealthStatus: () => state.getHealthStatus()
1772
1913
  };
1773
1914
  }
1774
1915
  async function handleMcpWithSdkTransport(req, res, url, ctx, mcpServer) {
1775
1916
  try {
1776
1917
  const body = await parseJSONBody(req);
1777
- if (body?.method === "tools/call") {
1778
- console.log(
1779
- "[dedicated.ts /mcp] Received tools/call request:",
1780
- JSON.stringify(
1781
- {
1782
- method: body.method,
1783
- toolName: body.params?.name,
1784
- hasArguments: !!body.params?.arguments,
1785
- argumentKeys: body.params?.arguments ? Object.keys(body.params.arguments) : [],
1786
- hasEnv: !!body.params?.arguments?.env,
1787
- envKeys: body.params?.arguments?.env ? Object.keys(body.params.arguments.env) : [],
1788
- hasApiToken: !!body.params?.arguments?.env?.SKEDYUL_API_TOKEN
1789
- },
1790
- null,
1791
- 2
1792
- )
1793
- );
1794
- }
1795
1918
  if (body?.method === "tools/list") {
1796
1919
  sendJSON(res, 200, {
1797
1920
  jsonrpc: "2.0",
@@ -1866,65 +1989,46 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1866
1989
  }
1867
1990
 
1868
1991
  // src/server/index.ts
1869
- console.log("[skedyul-node/server] Module loading - imports done");
1870
- console.log("[skedyul-node/server] All imports complete");
1871
- console.log("[skedyul-node/server] Installing context logger...");
1872
1992
  installContextLogger();
1873
- console.log("[skedyul-node/server] Context logger installed");
1874
1993
  function createSkedyulServer(config) {
1875
- console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
1876
1994
  mergeRuntimeEnv();
1877
1995
  const registry = config.tools;
1878
1996
  const webhookRegistry = config.webhooks;
1879
- console.log("[createSkedyulServer] Step 2: coreApi setup");
1880
1997
  if (config.coreApi?.service) {
1881
1998
  coreApiService.register(config.coreApi.service);
1882
1999
  if (config.coreApi.webhookHandler) {
1883
2000
  coreApiService.setWebhookHandler(config.coreApi.webhookHandler);
1884
2001
  }
1885
2002
  }
1886
- console.log("[createSkedyulServer] Step 3: buildToolMetadata()");
1887
2003
  const tools = buildToolMetadata(registry);
1888
- console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
1889
2004
  const toolNames = Object.values(registry).map((tool) => tool.name);
1890
2005
  const runtimeLabel = config.computeLayer ?? "serverless";
1891
2006
  const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
1892
2007
  const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
1893
- console.log("[createSkedyulServer] Step 4: createRequestState()");
1894
2008
  const state = createRequestState(
1895
2009
  maxRequests,
1896
2010
  ttlExtendSeconds,
1897
2011
  runtimeLabel,
1898
2012
  toolNames
1899
2013
  );
1900
- console.log("[createSkedyulServer] Step 4 done");
1901
- console.log("[createSkedyulServer] Step 5: new McpServer()");
1902
2014
  const mcpServer = new McpServer({
1903
2015
  name: config.name,
1904
2016
  version: config.version ?? "0.0.0"
1905
2017
  });
1906
- console.log("[createSkedyulServer] Step 5 done");
1907
2018
  const dedicatedShutdown = () => {
1908
2019
  console.log("Max requests reached, shutting down...");
1909
2020
  setTimeout(() => process.exit(0), 1e3);
1910
2021
  };
1911
- console.log("[createSkedyulServer] Step 6: createCallToolHandler()");
1912
2022
  const callTool = createCallToolHandler(
1913
2023
  registry,
1914
2024
  state,
1915
2025
  config.computeLayer === "dedicated" ? dedicatedShutdown : void 0
1916
2026
  );
1917
- console.log("[createSkedyulServer] Step 6 done");
1918
- console.log("[createSkedyulServer] Step 7: Registering tools...");
1919
2027
  for (const [toolKey, tool] of Object.entries(registry)) {
1920
- console.log(`[createSkedyulServer] Registering tool: ${toolKey}`);
1921
2028
  const toolName = tool.name || toolKey;
1922
2029
  const toolDisplayName = tool.label || toolName;
1923
- console.log(`[createSkedyulServer] Getting input schema for ${toolKey}`);
1924
2030
  const inputZodSchema = getZodSchema(tool.inputSchema);
1925
- console.log(`[createSkedyulServer] Getting output schema for ${toolKey}`);
1926
2031
  const outputZodSchema = getZodSchema(tool.outputSchema);
1927
- console.log(`[createSkedyulServer] Creating wrapped schema for ${toolKey}`);
1928
2032
  const wrappedInputSchema = z3.object({
1929
2033
  inputs: inputZodSchema ?? z3.record(z3.string(), z3.unknown()).optional(),
1930
2034
  context: z3.record(z3.string(), z3.unknown()).optional(),
@@ -1932,7 +2036,6 @@ function createSkedyulServer(config) {
1932
2036
  invocation: z3.record(z3.string(), z3.unknown()).optional(),
1933
2037
  estimate: z3.boolean().optional()
1934
2038
  }).passthrough();
1935
- console.log(`[createSkedyulServer] Calling mcpServer.registerTool for ${toolKey}`);
1936
2039
  mcpServer.registerTool(
1937
2040
  toolName,
1938
2041
  {
@@ -1944,20 +2047,11 @@ function createSkedyulServer(config) {
1944
2047
  // outputSchema: outputZodSchema,
1945
2048
  },
1946
2049
  async (args) => {
1947
- console.log(`[mcpServer.registerTool] Tool ${toolName} received raw args:`, JSON.stringify(args, null, 2));
1948
2050
  const rawArgs = args;
1949
2051
  const toolInputs = rawArgs.inputs ?? {};
1950
2052
  const toolContext = rawArgs.context;
1951
2053
  const toolEnv = rawArgs.env;
1952
2054
  const toolInvocation = rawArgs.invocation;
1953
- console.log(`[mcpServer.registerTool] Tool ${toolName} extracted:`, {
1954
- hasInputs: !!rawArgs.inputs,
1955
- hasContext: !!rawArgs.context,
1956
- hasEnv: !!rawArgs.env,
1957
- hasInvocation: !!rawArgs.invocation,
1958
- envKeys: toolEnv ? Object.keys(toolEnv) : [],
1959
- hasApiToken: toolEnv?.SKEDYUL_API_TOKEN ? `yes (${toolEnv.SKEDYUL_API_TOKEN.length} chars)` : "no"
1960
- });
1961
2055
  let validatedInputs = toolInputs;
1962
2056
  if (inputZodSchema) {
1963
2057
  try {
@@ -1991,10 +2085,10 @@ function createSkedyulServer(config) {
1991
2085
  invocation: toolInvocation
1992
2086
  });
1993
2087
  const hasOutputSchema = Boolean(outputZodSchema);
1994
- if (result.error) {
1995
- const errorOutput = { error: result.error };
2088
+ if (isToolCallFailure(result)) {
2089
+ const errorOutput = buildToolCallErrorOutput(result);
1996
2090
  return {
1997
- content: [{ type: "text", text: JSON.stringify(errorOutput) }],
2091
+ content: [{ type: "text", text: serializeMcpContentText(errorOutput) }],
1998
2092
  // Don't provide structuredContent for error responses when tool has outputSchema
1999
2093
  // because the error response won't match the success schema and MCP SDK validates it
2000
2094
  structuredContent: hasOutputSchema ? void 0 : errorOutput,
@@ -2002,7 +2096,8 @@ function createSkedyulServer(config) {
2002
2096
  billing: result.billing
2003
2097
  };
2004
2098
  }
2005
- const outputData = result.output;
2099
+ const rawOutput = "output" in result ? result.output : null;
2100
+ const outputData = rawOutput ?? null;
2006
2101
  const dataBlocks = result.dataBlocks;
2007
2102
  let structuredContent;
2008
2103
  if (outputData) {
@@ -2020,18 +2115,15 @@ function createSkedyulServer(config) {
2020
2115
  structuredContent = {};
2021
2116
  }
2022
2117
  return {
2023
- 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) }],
2024
2120
  structuredContent,
2025
2121
  billing: result.billing
2026
2122
  };
2027
2123
  }
2028
2124
  );
2029
- console.log(`[createSkedyulServer] Tool ${toolKey} registered successfully`);
2030
2125
  }
2031
- console.log("[createSkedyulServer] Step 7 done - all tools registered");
2032
- console.log("[createSkedyulServer] Step 8: Creating server instance");
2033
2126
  if (config.computeLayer === "dedicated") {
2034
- console.log("[createSkedyulServer] Creating dedicated instance");
2035
2127
  return createDedicatedServerInstance(
2036
2128
  config,
2037
2129
  tools,
@@ -2040,10 +2132,7 @@ function createSkedyulServer(config) {
2040
2132
  mcpServer
2041
2133
  );
2042
2134
  }
2043
- console.log("[createSkedyulServer] Creating serverless instance");
2044
- const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
2045
- console.log("[createSkedyulServer] Serverless instance created successfully");
2046
- return serverlessInstance;
2135
+ return createServerlessInstance(config, tools, callTool, state, mcpServer);
2047
2136
  }
2048
2137
  var server = {
2049
2138
  create: createSkedyulServer