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.
- package/dist/cli/commands/workflows.d.ts +1 -0
- package/dist/cli/index.js +3670 -9073
- package/dist/cli/utils/auth.d.ts +4 -1
- package/dist/cli/utils/auth.js +32 -8
- package/dist/cli/utils/config.d.ts +23 -0
- package/dist/cli/utils/env-sync.d.ts +46 -0
- package/dist/cli/utils/mcp-http-client.d.ts +74 -0
- package/dist/cli/utils/migration-approval.d.ts +39 -0
- package/dist/cli/utils/mock-context.d.ts +19 -9
- package/dist/cli/utils/sse.d.ts +33 -0
- package/dist/cli/utils.d.ts +5 -1
- package/dist/compiler/types.d.ts +11 -9
- package/dist/config/schema-loader.d.ts +15 -2
- package/dist/config/types/model.d.ts +2 -0
- package/dist/config/types/page.d.ts +9 -0
- package/dist/context/index.d.ts +2 -2
- package/dist/context/resolver.d.ts +29 -28
- package/dist/context/types.d.ts +195 -37
- package/dist/core/client.d.ts +189 -233
- package/dist/dedicated/server.js +252 -163
- package/dist/esm/index.mjs +1149 -7671
- package/dist/index.d.ts +11 -6
- package/dist/index.js +1194 -7669
- package/dist/scheduling/calculateWaitTime.d.ts +16 -0
- package/dist/scheduling/index.d.ts +11 -0
- package/dist/scheduling/index.js +334 -0
- package/dist/scheduling/index.mjs +305 -0
- package/dist/scheduling/isTimeInWindow.d.ts +15 -0
- package/dist/scheduling/types-workflow.d.ts +54 -0
- package/dist/scheduling/types.d.ts +166 -0
- package/dist/schemas/agent-schema-v3.d.ts +406 -60
- package/dist/schemas/agent-schema-v3.js +248 -75
- package/dist/schemas/agent-schema-v3.mjs +234 -73
- package/dist/schemas/agent-schema.js +3 -7295
- package/dist/schemas/agent-schema.mjs +3 -7323
- package/dist/schemas/crm-schema.d.ts +53 -19
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas.d.ts +128 -40
- package/dist/server/route-handlers/handlers.d.ts +7 -0
- package/dist/server/utils/env.d.ts +9 -0
- package/dist/server/utils/index.d.ts +1 -0
- package/dist/server/utils/mcp-response.d.ts +11 -0
- package/dist/server.js +252 -163
- package/dist/serverless/server.mjs +252 -163
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/types.d.ts +34 -23
- package/dist/skills/types.js +8 -17
- package/dist/skills/types.mjs +7 -16
- package/dist/types/index.d.ts +3 -3
- package/dist/types/server.d.ts +1 -0
- package/dist/types/tool-context.d.ts +31 -4
- package/dist/types/tool-response.d.ts +2 -0
- package/dist/types/tool.d.ts +33 -1
- 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
|
|
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
|
|
285
|
-
const
|
|
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,
|
|
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:
|
|
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 =
|
|
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:
|
|
389
|
-
apiToken:
|
|
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
|
|
770
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
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
|
-
|
|
1476
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
1995
|
-
const errorOutput =
|
|
2088
|
+
if (isToolCallFailure(result)) {
|
|
2089
|
+
const errorOutput = buildToolCallErrorOutput(result);
|
|
1996
2090
|
return {
|
|
1997
|
-
content: [{ type: "text", text:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|