skedyul 1.2.19 → 1.2.23
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/index.js +512 -731
- package/dist/config/app-config.d.ts +26 -2
- package/dist/config/index.d.ts +1 -2
- package/dist/config/types/env.d.ts +8 -2
- package/dist/config/types/form.d.ts +10 -6
- package/dist/config/types/index.d.ts +0 -1
- package/dist/config/types/page.d.ts +2 -2
- package/dist/config/types/webhook.d.ts +2 -1
- package/dist/dedicated/server.js +503 -766
- package/dist/esm/index.mjs +503 -720
- package/dist/index.d.ts +2 -3
- package/dist/index.js +503 -722
- package/dist/server/config-serializer.d.ts +12 -0
- package/dist/server/dedicated.d.ts +3 -2
- package/dist/server/handlers/index.d.ts +12 -0
- package/dist/server/handlers/install-handler.d.ts +9 -0
- package/dist/server/handlers/oauth-callback-handler.d.ts +9 -0
- package/dist/server/handlers/provision-handler.d.ts +9 -0
- package/dist/server/handlers/types.d.ts +101 -0
- package/dist/server/handlers/uninstall-handler.d.ts +9 -0
- package/dist/server/handlers/webhook-handler.d.ts +28 -0
- package/dist/server/index.d.ts +15 -6
- package/dist/server/serverless.d.ts +3 -2
- package/dist/server/startup-logger.d.ts +3 -2
- package/dist/server/tool-handler.d.ts +1 -1
- package/dist/server/utils/http.d.ts +3 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +503 -766
- package/dist/serverless/server.mjs +503 -744
- package/dist/types/handlers.d.ts +6 -24
- package/dist/types/index.d.ts +4 -4
- package/dist/types/server.d.ts +1 -34
- package/dist/types/shared.d.ts +5 -0
- package/dist/types/tool.d.ts +5 -7
- package/dist/types/webhook.d.ts +14 -6
- package/package.json +1 -1
- package/dist/config/resolve.d.ts +0 -27
- package/dist/config/types/compute.d.ts +0 -9
package/dist/esm/index.mjs
CHANGED
|
@@ -2078,8 +2078,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
2078
2078
|
};
|
|
2079
2079
|
}
|
|
2080
2080
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
2081
|
-
return async function callTool(
|
|
2082
|
-
const toolName = String(
|
|
2081
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
2082
|
+
const toolName = String(toolNameInput);
|
|
2083
2083
|
const tool = registry[toolName];
|
|
2084
2084
|
if (!tool) {
|
|
2085
2085
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -2088,7 +2088,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
2088
2088
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
2089
2089
|
}
|
|
2090
2090
|
const fn = tool.handler;
|
|
2091
|
-
const args =
|
|
2091
|
+
const args = toolArgsInput ?? {};
|
|
2092
2092
|
const estimateMode = args.estimate === true;
|
|
2093
2093
|
if (!estimateMode) {
|
|
2094
2094
|
state.incrementRequestCount();
|
|
@@ -2343,51 +2343,6 @@ async function handleCoreMethod(method, params) {
|
|
|
2343
2343
|
};
|
|
2344
2344
|
}
|
|
2345
2345
|
|
|
2346
|
-
// src/server/handler-helpers.ts
|
|
2347
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
2348
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2349
|
-
return null;
|
|
2350
|
-
}
|
|
2351
|
-
const envelope = parsedBody;
|
|
2352
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2353
|
-
return null;
|
|
2354
|
-
}
|
|
2355
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2356
|
-
return null;
|
|
2357
|
-
}
|
|
2358
|
-
return {
|
|
2359
|
-
env: envelope.env,
|
|
2360
|
-
request: envelope.request,
|
|
2361
|
-
context: envelope.context
|
|
2362
|
-
};
|
|
2363
|
-
}
|
|
2364
|
-
function buildRequestFromRaw(raw) {
|
|
2365
|
-
let parsedBody = raw.body;
|
|
2366
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
2367
|
-
if (contentType.includes("application/json")) {
|
|
2368
|
-
try {
|
|
2369
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2370
|
-
} catch {
|
|
2371
|
-
parsedBody = raw.body;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return {
|
|
2375
|
-
method: raw.method,
|
|
2376
|
-
url: raw.url,
|
|
2377
|
-
path: raw.path,
|
|
2378
|
-
headers: raw.headers,
|
|
2379
|
-
query: raw.query,
|
|
2380
|
-
body: parsedBody,
|
|
2381
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2382
|
-
};
|
|
2383
|
-
}
|
|
2384
|
-
function buildRequestScopedConfig(env) {
|
|
2385
|
-
return {
|
|
2386
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2387
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2388
|
-
};
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
2346
|
// src/server/startup-logger.ts
|
|
2392
2347
|
function padEnd(str, length) {
|
|
2393
2348
|
if (str.length >= length) {
|
|
@@ -2395,10 +2350,11 @@ function padEnd(str, length) {
|
|
|
2395
2350
|
}
|
|
2396
2351
|
return str + " ".repeat(length - str.length);
|
|
2397
2352
|
}
|
|
2398
|
-
function printStartupLog(config, tools,
|
|
2353
|
+
function printStartupLog(config, tools, port) {
|
|
2399
2354
|
if (process.env.NODE_ENV === "test") {
|
|
2400
2355
|
return;
|
|
2401
2356
|
}
|
|
2357
|
+
const webhookRegistry = config.webhooks;
|
|
2402
2358
|
const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
|
|
2403
2359
|
const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
|
|
2404
2360
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
@@ -2411,9 +2367,9 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
2411
2367
|
console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
|
|
2412
2368
|
console.log(`\u2560${divider}\u2563`);
|
|
2413
2369
|
console.log(`\u2551 \u2551`);
|
|
2414
|
-
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.
|
|
2415
|
-
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.
|
|
2416
|
-
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
|
|
2370
|
+
console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.name, 49)}\u2551`);
|
|
2371
|
+
console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.version ?? "N/A", 49)}\u2551`);
|
|
2372
|
+
console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer ?? "serverless", 49)}\u2551`);
|
|
2417
2373
|
if (port) {
|
|
2418
2374
|
console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
|
|
2419
2375
|
}
|
|
@@ -2455,67 +2411,439 @@ function printStartupLog(config, tools, webhookRegistry, port) {
|
|
|
2455
2411
|
console.log("");
|
|
2456
2412
|
}
|
|
2457
2413
|
|
|
2458
|
-
// src/config
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2414
|
+
// src/server/config-serializer.ts
|
|
2415
|
+
function serializeConfig(config) {
|
|
2416
|
+
const registry = config.tools;
|
|
2417
|
+
const webhookRegistry = config.webhooks;
|
|
2418
|
+
return {
|
|
2419
|
+
name: config.name,
|
|
2420
|
+
version: config.version,
|
|
2421
|
+
description: config.description,
|
|
2422
|
+
computeLayer: config.computeLayer,
|
|
2423
|
+
tools: registry ? Object.entries(registry).map(([key, tool]) => ({
|
|
2424
|
+
name: tool.name || key,
|
|
2425
|
+
description: tool.description,
|
|
2426
|
+
timeout: tool.timeout,
|
|
2427
|
+
retries: tool.retries
|
|
2428
|
+
})) : [],
|
|
2429
|
+
webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
|
|
2430
|
+
name: w.name,
|
|
2431
|
+
description: w.description,
|
|
2432
|
+
methods: w.methods ?? ["POST"],
|
|
2433
|
+
type: w.type ?? "WEBHOOK"
|
|
2434
|
+
})) : [],
|
|
2435
|
+
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
2436
|
+
agents: config.agents
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
function isProvisionConfig(value) {
|
|
2440
|
+
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// src/server/handlers/install-handler.ts
|
|
2444
|
+
async function handleInstall(body, hooks) {
|
|
2445
|
+
if (!hooks?.install) {
|
|
2446
|
+
return {
|
|
2447
|
+
status: 404,
|
|
2448
|
+
body: { error: "Install handler not configured" }
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
2452
|
+
return {
|
|
2453
|
+
status: 400,
|
|
2454
|
+
body: {
|
|
2455
|
+
error: {
|
|
2456
|
+
code: -32602,
|
|
2457
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
const installContext = {
|
|
2463
|
+
env: body.env ?? {},
|
|
2464
|
+
workplace: body.context.workplace,
|
|
2465
|
+
appInstallationId: body.context.appInstallationId,
|
|
2466
|
+
app: body.context.app,
|
|
2467
|
+
invocation: body.invocation,
|
|
2468
|
+
log: createContextLogger()
|
|
2469
|
+
};
|
|
2470
|
+
const requestConfig = {
|
|
2471
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2472
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2473
|
+
};
|
|
2474
|
+
try {
|
|
2475
|
+
const installHook = hooks.install;
|
|
2476
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2477
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2478
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2479
|
+
return await installHandler(installContext);
|
|
2480
|
+
});
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
status: 200,
|
|
2484
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
2485
|
+
};
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
if (err instanceof InstallError) {
|
|
2488
|
+
return {
|
|
2489
|
+
status: 400,
|
|
2490
|
+
body: {
|
|
2491
|
+
error: {
|
|
2492
|
+
code: err.code,
|
|
2493
|
+
message: err.message,
|
|
2494
|
+
field: err.field
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
return {
|
|
2500
|
+
status: 500,
|
|
2501
|
+
body: {
|
|
2502
|
+
error: {
|
|
2503
|
+
code: -32603,
|
|
2504
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/server/handlers/uninstall-handler.ts
|
|
2512
|
+
async function handleUninstall(body, hooks) {
|
|
2513
|
+
if (!hooks?.uninstall) {
|
|
2514
|
+
return {
|
|
2515
|
+
status: 404,
|
|
2516
|
+
body: { error: "Uninstall handler not configured" }
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
2520
|
+
return {
|
|
2521
|
+
status: 400,
|
|
2522
|
+
body: {
|
|
2523
|
+
error: {
|
|
2524
|
+
code: -32602,
|
|
2525
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
const uninstallContext = {
|
|
2531
|
+
env: body.env ?? {},
|
|
2532
|
+
workplace: body.context.workplace,
|
|
2533
|
+
appInstallationId: body.context.appInstallationId,
|
|
2534
|
+
app: body.context.app,
|
|
2535
|
+
invocation: body.invocation,
|
|
2536
|
+
log: createContextLogger()
|
|
2537
|
+
};
|
|
2538
|
+
const requestConfig = {
|
|
2539
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2540
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2541
|
+
};
|
|
2542
|
+
try {
|
|
2543
|
+
const uninstallHook = hooks.uninstall;
|
|
2544
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2545
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2546
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2547
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
2548
|
+
});
|
|
2549
|
+
});
|
|
2550
|
+
return {
|
|
2551
|
+
status: 200,
|
|
2552
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
2553
|
+
};
|
|
2554
|
+
} catch (err) {
|
|
2555
|
+
return {
|
|
2556
|
+
status: 500,
|
|
2557
|
+
body: {
|
|
2558
|
+
error: {
|
|
2559
|
+
code: -32603,
|
|
2560
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// src/server/handlers/provision-handler.ts
|
|
2568
|
+
async function handleProvision(body, hooks) {
|
|
2569
|
+
if (!hooks?.provision) {
|
|
2570
|
+
return {
|
|
2571
|
+
status: 404,
|
|
2572
|
+
body: { error: "Provision handler not configured" }
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
if (!body.context?.app) {
|
|
2576
|
+
return {
|
|
2577
|
+
status: 400,
|
|
2578
|
+
body: {
|
|
2579
|
+
error: {
|
|
2580
|
+
code: -32602,
|
|
2581
|
+
message: "Missing context (app required)"
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2462
2585
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
if (
|
|
2466
|
-
|
|
2586
|
+
const mergedEnv = {};
|
|
2587
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2588
|
+
if (value !== void 0) {
|
|
2589
|
+
mergedEnv[key] = value;
|
|
2467
2590
|
}
|
|
2468
|
-
return resolved;
|
|
2469
2591
|
}
|
|
2470
|
-
|
|
2592
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
2593
|
+
const provisionContext = {
|
|
2594
|
+
env: mergedEnv,
|
|
2595
|
+
app: body.context.app,
|
|
2596
|
+
invocation: body.invocation,
|
|
2597
|
+
log: createContextLogger()
|
|
2598
|
+
};
|
|
2599
|
+
const requestConfig = {
|
|
2600
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
2601
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
2602
|
+
};
|
|
2603
|
+
try {
|
|
2604
|
+
const provisionHook = hooks.provision;
|
|
2605
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
2606
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2607
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2608
|
+
return await provisionHandler(provisionContext);
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
2611
|
+
return {
|
|
2612
|
+
status: 200,
|
|
2613
|
+
body: result
|
|
2614
|
+
};
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
return {
|
|
2617
|
+
status: 500,
|
|
2618
|
+
body: {
|
|
2619
|
+
error: {
|
|
2620
|
+
code: -32603,
|
|
2621
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2471
2626
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2627
|
+
|
|
2628
|
+
// src/server/handler-helpers.ts
|
|
2629
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
2630
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
const envelope = parsedBody;
|
|
2634
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2635
|
+
return null;
|
|
2636
|
+
}
|
|
2637
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
return {
|
|
2641
|
+
env: envelope.env,
|
|
2642
|
+
request: envelope.request,
|
|
2643
|
+
context: envelope.context
|
|
2644
|
+
};
|
|
2480
2645
|
}
|
|
2481
|
-
function
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2646
|
+
function buildRequestFromRaw(raw) {
|
|
2647
|
+
let parsedBody = raw.body;
|
|
2648
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
2649
|
+
if (contentType.includes("application/json")) {
|
|
2650
|
+
try {
|
|
2651
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2652
|
+
} catch {
|
|
2653
|
+
parsedBody = raw.body;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
return {
|
|
2657
|
+
method: raw.method,
|
|
2658
|
+
url: raw.url,
|
|
2659
|
+
path: raw.path,
|
|
2660
|
+
headers: raw.headers,
|
|
2661
|
+
query: raw.query,
|
|
2662
|
+
body: parsedBody,
|
|
2663
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2664
|
+
};
|
|
2488
2665
|
}
|
|
2489
|
-
|
|
2490
|
-
const provision = await resolveDynamicImport(
|
|
2491
|
-
config.provision
|
|
2492
|
-
);
|
|
2493
|
-
const install = await resolveDynamicImport(
|
|
2494
|
-
config.install
|
|
2495
|
-
);
|
|
2496
|
-
const tools = serializeTools(registry);
|
|
2497
|
-
const webhooks = webhookRegistry ? serializeWebhooks(webhookRegistry) : [];
|
|
2666
|
+
function buildRequestScopedConfig(env) {
|
|
2498
2667
|
return {
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2668
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2669
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
2674
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
2675
|
+
if (!hooks?.oauth_callback) {
|
|
2676
|
+
return {
|
|
2677
|
+
status: 404,
|
|
2678
|
+
body: { error: "OAuth callback handler not configured" }
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
2682
|
+
if (!envelope) {
|
|
2683
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2684
|
+
return {
|
|
2685
|
+
status: 400,
|
|
2686
|
+
body: {
|
|
2687
|
+
error: {
|
|
2688
|
+
code: -32602,
|
|
2689
|
+
message: "Missing envelope format: expected { env, request }"
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
const invocation = parsedBody.invocation;
|
|
2695
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2696
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
2697
|
+
const oauthCallbackContext = {
|
|
2698
|
+
request: oauthRequest,
|
|
2699
|
+
invocation,
|
|
2700
|
+
log: createContextLogger()
|
|
2701
|
+
};
|
|
2702
|
+
try {
|
|
2703
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
2704
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2705
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
2706
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2707
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2708
|
+
});
|
|
2709
|
+
});
|
|
2710
|
+
return {
|
|
2711
|
+
status: 200,
|
|
2712
|
+
body: {
|
|
2713
|
+
appInstallationId: result.appInstallationId,
|
|
2714
|
+
env: result.env ?? {}
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
} catch (err) {
|
|
2718
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2719
|
+
return {
|
|
2720
|
+
status: 500,
|
|
2721
|
+
body: {
|
|
2722
|
+
error: {
|
|
2723
|
+
code: -32603,
|
|
2724
|
+
message: errorMessage
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// src/server/handlers/webhook-handler.ts
|
|
2732
|
+
function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
2733
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
2734
|
+
if (isEnvelope) {
|
|
2735
|
+
const envelope = parsedBody;
|
|
2736
|
+
const requestEnv = envelope.env ?? {};
|
|
2737
|
+
const invocation = envelope.invocation;
|
|
2738
|
+
let originalParsedBody = envelope.request.body;
|
|
2739
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
2740
|
+
if (originalContentType.includes("application/json")) {
|
|
2741
|
+
try {
|
|
2742
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
2743
|
+
} catch {
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
const webhookRequest2 = {
|
|
2747
|
+
method: envelope.request.method,
|
|
2748
|
+
url: envelope.request.url,
|
|
2749
|
+
path: envelope.request.path,
|
|
2750
|
+
headers: envelope.request.headers,
|
|
2751
|
+
query: envelope.request.query,
|
|
2752
|
+
body: originalParsedBody,
|
|
2753
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
2754
|
+
};
|
|
2755
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
2756
|
+
const app = envelope.context.app;
|
|
2757
|
+
let webhookContext2;
|
|
2758
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
2759
|
+
webhookContext2 = {
|
|
2760
|
+
env: envVars,
|
|
2761
|
+
app,
|
|
2762
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
2763
|
+
workplace: envelope.context.workplace,
|
|
2764
|
+
registration: envelope.context.registration ?? {},
|
|
2765
|
+
invocation,
|
|
2766
|
+
log: createContextLogger()
|
|
2767
|
+
};
|
|
2768
|
+
} else {
|
|
2769
|
+
webhookContext2 = {
|
|
2770
|
+
env: envVars,
|
|
2771
|
+
app,
|
|
2772
|
+
invocation,
|
|
2773
|
+
log: createContextLogger()
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
2777
|
+
}
|
|
2778
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
2779
|
+
return {
|
|
2780
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
const webhookRequest = {
|
|
2784
|
+
method,
|
|
2785
|
+
url,
|
|
2786
|
+
path: path2,
|
|
2787
|
+
headers,
|
|
2788
|
+
query,
|
|
2789
|
+
body: parsedBody,
|
|
2790
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2791
|
+
};
|
|
2792
|
+
const webhookContext = {
|
|
2793
|
+
env: process.env,
|
|
2794
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
2795
|
+
log: createContextLogger()
|
|
2507
2796
|
};
|
|
2797
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
2508
2798
|
}
|
|
2509
|
-
function
|
|
2799
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
2800
|
+
const webhookDef = webhookRegistry[handle];
|
|
2801
|
+
if (!webhookDef) {
|
|
2802
|
+
return {
|
|
2803
|
+
status: 404,
|
|
2804
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
const originalEnv = { ...process.env };
|
|
2808
|
+
Object.assign(process.env, data.requestEnv);
|
|
2809
|
+
const requestConfig = {
|
|
2810
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2811
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2812
|
+
};
|
|
2813
|
+
let webhookResponse;
|
|
2814
|
+
try {
|
|
2815
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
2816
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2817
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
2818
|
+
});
|
|
2819
|
+
});
|
|
2820
|
+
} catch (err) {
|
|
2821
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2822
|
+
return {
|
|
2823
|
+
status: 500,
|
|
2824
|
+
body: { error: "Webhook handler error" }
|
|
2825
|
+
};
|
|
2826
|
+
} finally {
|
|
2827
|
+
process.env = originalEnv;
|
|
2828
|
+
}
|
|
2510
2829
|
return {
|
|
2511
|
-
|
|
2512
|
-
|
|
2830
|
+
status: webhookResponse.status ?? 200,
|
|
2831
|
+
body: webhookResponse.body,
|
|
2832
|
+
headers: webhookResponse.headers
|
|
2513
2833
|
};
|
|
2514
2834
|
}
|
|
2835
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
2836
|
+
const webhookDef = webhookRegistry[handle];
|
|
2837
|
+
if (!webhookDef) return false;
|
|
2838
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
2839
|
+
return allowedMethods.includes(method);
|
|
2840
|
+
}
|
|
2515
2841
|
|
|
2516
2842
|
// src/server/dedicated.ts
|
|
2517
|
-
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
2843
|
+
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
2518
2844
|
const port = getListeningPort(config);
|
|
2845
|
+
const registry = config.tools;
|
|
2846
|
+
const webhookRegistry = config.webhooks;
|
|
2519
2847
|
const httpServer = http.createServer(
|
|
2520
2848
|
async (req, res) => {
|
|
2521
2849
|
function sendCoreResult(result) {
|
|
@@ -2532,30 +2860,16 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2532
2860
|
return;
|
|
2533
2861
|
}
|
|
2534
2862
|
if (pathname === "/config" && req.method === "GET") {
|
|
2535
|
-
|
|
2536
|
-
if (!appConfig && config.appConfigLoader) {
|
|
2537
|
-
const loaded = await config.appConfigLoader();
|
|
2538
|
-
appConfig = loaded.default;
|
|
2539
|
-
}
|
|
2540
|
-
if (!appConfig) {
|
|
2541
|
-
appConfig = createMinimalConfig(
|
|
2542
|
-
config.metadata.name,
|
|
2543
|
-
config.metadata.version
|
|
2544
|
-
);
|
|
2545
|
-
}
|
|
2546
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
2547
|
-
sendJSON(res, 200, serializedConfig);
|
|
2863
|
+
sendJSON(res, 200, serializeConfig(config));
|
|
2548
2864
|
return;
|
|
2549
2865
|
}
|
|
2550
2866
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
2551
2867
|
const handle = pathname.slice("/webhooks/".length);
|
|
2552
|
-
|
|
2553
|
-
if (!webhookDef) {
|
|
2868
|
+
if (!webhookRegistry[handle]) {
|
|
2554
2869
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
2555
2870
|
return;
|
|
2556
2871
|
}
|
|
2557
|
-
|
|
2558
|
-
if (!allowedMethods.includes(req.method)) {
|
|
2872
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
2559
2873
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
2560
2874
|
return;
|
|
2561
2875
|
}
|
|
@@ -2577,87 +2891,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2577
2891
|
} else {
|
|
2578
2892
|
parsedBody = rawBody;
|
|
2579
2893
|
}
|
|
2580
|
-
const
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
webhookContext = {
|
|
2594
|
-
env: envVars,
|
|
2595
|
-
app,
|
|
2596
|
-
appInstallationId: context.appInstallationId,
|
|
2597
|
-
workplace: context.workplace,
|
|
2598
|
-
registration: context.registration ?? {},
|
|
2599
|
-
invocation,
|
|
2600
|
-
log: createContextLogger()
|
|
2601
|
-
};
|
|
2602
|
-
} else {
|
|
2603
|
-
webhookContext = {
|
|
2604
|
-
env: envVars,
|
|
2605
|
-
app,
|
|
2606
|
-
invocation,
|
|
2607
|
-
log: createContextLogger()
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
|
-
} else {
|
|
2611
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
2612
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
2613
|
-
if (!appId || !appVersionId) {
|
|
2614
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
2615
|
-
}
|
|
2616
|
-
webhookRequest = {
|
|
2617
|
-
method: req.method ?? "POST",
|
|
2618
|
-
url: url.toString(),
|
|
2619
|
-
path: pathname,
|
|
2620
|
-
headers: req.headers,
|
|
2621
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
2622
|
-
body: parsedBody,
|
|
2623
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2624
|
-
};
|
|
2625
|
-
webhookContext = {
|
|
2626
|
-
env: process.env,
|
|
2627
|
-
app: { id: appId, versionId: appVersionId },
|
|
2628
|
-
log: createContextLogger()
|
|
2629
|
-
};
|
|
2630
|
-
}
|
|
2631
|
-
const originalEnv = { ...process.env };
|
|
2632
|
-
Object.assign(process.env, requestEnv);
|
|
2633
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
2634
|
-
let webhookResponse;
|
|
2635
|
-
try {
|
|
2636
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
2637
|
-
return await runWithConfig(requestConfig, async () => {
|
|
2638
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
2639
|
-
});
|
|
2640
|
-
});
|
|
2641
|
-
} catch (err) {
|
|
2642
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2643
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
2894
|
+
const parseResult = parseWebhookRequest(
|
|
2895
|
+
parsedBody,
|
|
2896
|
+
req.method ?? "POST",
|
|
2897
|
+
url.toString(),
|
|
2898
|
+
pathname,
|
|
2899
|
+
req.headers,
|
|
2900
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
2901
|
+
rawBody,
|
|
2902
|
+
req.headers["x-skedyul-app-id"],
|
|
2903
|
+
req.headers["x-skedyul-app-version-id"]
|
|
2904
|
+
);
|
|
2905
|
+
if ("error" in parseResult) {
|
|
2906
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
2644
2907
|
return;
|
|
2645
|
-
} finally {
|
|
2646
|
-
process.env = originalEnv;
|
|
2647
2908
|
}
|
|
2648
|
-
const
|
|
2909
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
2649
2910
|
const responseHeaders = {
|
|
2650
|
-
...
|
|
2911
|
+
...result.headers
|
|
2651
2912
|
};
|
|
2652
2913
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
2653
2914
|
responseHeaders["Content-Type"] = "application/json";
|
|
2654
2915
|
}
|
|
2655
|
-
res.writeHead(status, responseHeaders);
|
|
2656
|
-
if (
|
|
2657
|
-
if (typeof
|
|
2658
|
-
res.end(
|
|
2916
|
+
res.writeHead(result.status, responseHeaders);
|
|
2917
|
+
if (result.body !== void 0) {
|
|
2918
|
+
if (typeof result.body === "string") {
|
|
2919
|
+
res.end(result.body);
|
|
2659
2920
|
} else {
|
|
2660
|
-
res.end(JSON.stringify(
|
|
2921
|
+
res.end(JSON.stringify(result.body));
|
|
2661
2922
|
}
|
|
2662
2923
|
} else {
|
|
2663
2924
|
res.end();
|
|
@@ -2696,10 +2957,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2696
2957
|
return;
|
|
2697
2958
|
}
|
|
2698
2959
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
2699
|
-
if (!config.hooks?.oauth_callback) {
|
|
2700
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
2701
|
-
return;
|
|
2702
|
-
}
|
|
2703
2960
|
let parsedBody;
|
|
2704
2961
|
try {
|
|
2705
2962
|
parsedBody = await parseJSONBody(req);
|
|
@@ -2710,50 +2967,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2710
2967
|
});
|
|
2711
2968
|
return;
|
|
2712
2969
|
}
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2715
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2716
|
-
sendJSON(res, 400, {
|
|
2717
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
2718
|
-
});
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
const invocation = parsedBody.invocation;
|
|
2722
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2723
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
2724
|
-
const oauthCallbackContext = {
|
|
2725
|
-
request: oauthRequest,
|
|
2726
|
-
invocation,
|
|
2727
|
-
log: createContextLogger()
|
|
2728
|
-
};
|
|
2729
|
-
try {
|
|
2730
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
2731
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2732
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
2733
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
2734
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2735
|
-
});
|
|
2736
|
-
});
|
|
2737
|
-
sendJSON(res, 200, {
|
|
2738
|
-
appInstallationId: result.appInstallationId,
|
|
2739
|
-
env: result.env ?? {}
|
|
2740
|
-
});
|
|
2741
|
-
} catch (err) {
|
|
2742
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2743
|
-
sendJSON(res, 500, {
|
|
2744
|
-
error: {
|
|
2745
|
-
code: -32603,
|
|
2746
|
-
message: errorMessage
|
|
2747
|
-
}
|
|
2748
|
-
});
|
|
2749
|
-
}
|
|
2970
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
2971
|
+
sendJSON(res, result.status, result.body);
|
|
2750
2972
|
return;
|
|
2751
2973
|
}
|
|
2752
2974
|
if (pathname === "/install" && req.method === "POST") {
|
|
2753
|
-
if (!config.hooks?.install) {
|
|
2754
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
2755
|
-
return;
|
|
2756
|
-
}
|
|
2757
2975
|
let installBody;
|
|
2758
2976
|
try {
|
|
2759
2977
|
installBody = await parseJSONBody(req);
|
|
@@ -2763,61 +2981,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2763
2981
|
});
|
|
2764
2982
|
return;
|
|
2765
2983
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
2769
|
-
});
|
|
2770
|
-
return;
|
|
2771
|
-
}
|
|
2772
|
-
const installContext = {
|
|
2773
|
-
env: installBody.env ?? {},
|
|
2774
|
-
workplace: installBody.context.workplace,
|
|
2775
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
2776
|
-
app: installBody.context.app,
|
|
2777
|
-
invocation: installBody.invocation,
|
|
2778
|
-
log: createContextLogger()
|
|
2779
|
-
};
|
|
2780
|
-
const installRequestConfig = {
|
|
2781
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2782
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2783
|
-
};
|
|
2784
|
-
try {
|
|
2785
|
-
const installHook = config.hooks.install;
|
|
2786
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2787
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
2788
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
2789
|
-
return await installHandler(installContext);
|
|
2790
|
-
});
|
|
2791
|
-
});
|
|
2792
|
-
sendJSON(res, 200, {
|
|
2793
|
-
env: result.env ?? {},
|
|
2794
|
-
redirect: result.redirect
|
|
2795
|
-
});
|
|
2796
|
-
} catch (err) {
|
|
2797
|
-
if (err instanceof InstallError) {
|
|
2798
|
-
sendJSON(res, 400, {
|
|
2799
|
-
error: {
|
|
2800
|
-
code: err.code,
|
|
2801
|
-
message: err.message,
|
|
2802
|
-
field: err.field
|
|
2803
|
-
}
|
|
2804
|
-
});
|
|
2805
|
-
} else {
|
|
2806
|
-
sendJSON(res, 500, {
|
|
2807
|
-
error: {
|
|
2808
|
-
code: -32603,
|
|
2809
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2810
|
-
}
|
|
2811
|
-
});
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2984
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
2985
|
+
sendJSON(res, result.status, result.body);
|
|
2814
2986
|
return;
|
|
2815
2987
|
}
|
|
2816
2988
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
2817
|
-
if (!config.hooks?.uninstall) {
|
|
2818
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
2819
|
-
return;
|
|
2820
|
-
}
|
|
2821
2989
|
let uninstallBody;
|
|
2822
2990
|
try {
|
|
2823
2991
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -2827,53 +2995,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2827
2995
|
});
|
|
2828
2996
|
return;
|
|
2829
2997
|
}
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
error: {
|
|
2833
|
-
code: -32602,
|
|
2834
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2835
|
-
}
|
|
2836
|
-
});
|
|
2837
|
-
return;
|
|
2838
|
-
}
|
|
2839
|
-
const uninstallContext = {
|
|
2840
|
-
env: uninstallBody.env ?? {},
|
|
2841
|
-
workplace: uninstallBody.context.workplace,
|
|
2842
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
2843
|
-
app: uninstallBody.context.app,
|
|
2844
|
-
invocation: uninstallBody.invocation,
|
|
2845
|
-
log: createContextLogger()
|
|
2846
|
-
};
|
|
2847
|
-
const uninstallRequestConfig = {
|
|
2848
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2849
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2850
|
-
};
|
|
2851
|
-
try {
|
|
2852
|
-
const uninstallHook = config.hooks.uninstall;
|
|
2853
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2854
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
2855
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
2856
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
2857
|
-
});
|
|
2858
|
-
});
|
|
2859
|
-
sendJSON(res, 200, {
|
|
2860
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
2861
|
-
});
|
|
2862
|
-
} catch (err) {
|
|
2863
|
-
sendJSON(res, 500, {
|
|
2864
|
-
error: {
|
|
2865
|
-
code: -32603,
|
|
2866
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2867
|
-
}
|
|
2868
|
-
});
|
|
2869
|
-
}
|
|
2998
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
2999
|
+
sendJSON(res, result.status, result.body);
|
|
2870
3000
|
return;
|
|
2871
3001
|
}
|
|
2872
3002
|
if (pathname === "/provision" && req.method === "POST") {
|
|
2873
|
-
if (!config.hooks?.provision) {
|
|
2874
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
2877
3003
|
let provisionBody;
|
|
2878
3004
|
try {
|
|
2879
3005
|
provisionBody = await parseJSONBody(req);
|
|
@@ -2883,46 +3009,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2883
3009
|
});
|
|
2884
3010
|
return;
|
|
2885
3011
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
2889
|
-
});
|
|
2890
|
-
return;
|
|
2891
|
-
}
|
|
2892
|
-
const mergedEnv = {};
|
|
2893
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
2894
|
-
if (value !== void 0) {
|
|
2895
|
-
mergedEnv[key] = value;
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
2899
|
-
const provisionContext = {
|
|
2900
|
-
env: mergedEnv,
|
|
2901
|
-
app: provisionBody.context.app,
|
|
2902
|
-
invocation: provisionBody.invocation,
|
|
2903
|
-
log: createContextLogger()
|
|
2904
|
-
};
|
|
2905
|
-
const provisionRequestConfig = {
|
|
2906
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
2907
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
2908
|
-
};
|
|
2909
|
-
try {
|
|
2910
|
-
const provisionHook = config.hooks.provision;
|
|
2911
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
2912
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
2913
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
2914
|
-
return await provisionHandler(provisionContext);
|
|
2915
|
-
});
|
|
2916
|
-
});
|
|
2917
|
-
sendJSON(res, 200, result);
|
|
2918
|
-
} catch (err) {
|
|
2919
|
-
sendJSON(res, 500, {
|
|
2920
|
-
error: {
|
|
2921
|
-
code: -32603,
|
|
2922
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2923
|
-
}
|
|
2924
|
-
});
|
|
2925
|
-
}
|
|
3012
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3013
|
+
sendJSON(res, result.status, result.body);
|
|
2926
3014
|
return;
|
|
2927
3015
|
}
|
|
2928
3016
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -3073,7 +3161,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3073
3161
|
const finalPort = listenPort ?? port;
|
|
3074
3162
|
return new Promise((resolve2, reject) => {
|
|
3075
3163
|
httpServer.listen(finalPort, () => {
|
|
3076
|
-
printStartupLog(config, tools,
|
|
3164
|
+
printStartupLog(config, tools, finalPort);
|
|
3077
3165
|
resolve2();
|
|
3078
3166
|
});
|
|
3079
3167
|
httpServer.once("error", reject);
|
|
@@ -3084,13 +3172,15 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3084
3172
|
}
|
|
3085
3173
|
|
|
3086
3174
|
// src/server/serverless.ts
|
|
3087
|
-
function createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
3175
|
+
function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
3088
3176
|
const headers = getDefaultHeaders(config.cors);
|
|
3177
|
+
const registry = config.tools;
|
|
3178
|
+
const webhookRegistry = config.webhooks;
|
|
3089
3179
|
let hasLoggedStartup = false;
|
|
3090
3180
|
return {
|
|
3091
3181
|
async handler(event) {
|
|
3092
3182
|
if (!hasLoggedStartup) {
|
|
3093
|
-
printStartupLog(config, tools
|
|
3183
|
+
printStartupLog(config, tools);
|
|
3094
3184
|
hasLoggedStartup = true;
|
|
3095
3185
|
}
|
|
3096
3186
|
try {
|
|
@@ -3101,12 +3191,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3101
3191
|
}
|
|
3102
3192
|
if (path2.startsWith("/webhooks/") && webhookRegistry) {
|
|
3103
3193
|
const handle = path2.slice("/webhooks/".length);
|
|
3104
|
-
|
|
3105
|
-
if (!webhookDef) {
|
|
3194
|
+
if (!webhookRegistry[handle]) {
|
|
3106
3195
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
3107
3196
|
}
|
|
3108
|
-
|
|
3109
|
-
if (!allowedMethods.includes(method)) {
|
|
3197
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
3110
3198
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
3111
3199
|
}
|
|
3112
3200
|
const rawBody = event.body ?? "";
|
|
@@ -3121,107 +3209,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3121
3209
|
} else {
|
|
3122
3210
|
parsedBody = rawBody;
|
|
3123
3211
|
}
|
|
3124
|
-
const
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
method: envelope.request.method,
|
|
3143
|
-
url: envelope.request.url,
|
|
3144
|
-
path: envelope.request.path,
|
|
3145
|
-
headers: envelope.request.headers,
|
|
3146
|
-
query: envelope.request.query,
|
|
3147
|
-
body: originalParsedBody,
|
|
3148
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
3149
|
-
};
|
|
3150
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
3151
|
-
const app = envelope.context.app;
|
|
3152
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
3153
|
-
webhookContext = {
|
|
3154
|
-
env: envVars,
|
|
3155
|
-
app,
|
|
3156
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
3157
|
-
workplace: envelope.context.workplace,
|
|
3158
|
-
registration: envelope.context.registration ?? {},
|
|
3159
|
-
invocation,
|
|
3160
|
-
log: createContextLogger()
|
|
3161
|
-
};
|
|
3162
|
-
} else {
|
|
3163
|
-
webhookContext = {
|
|
3164
|
-
env: envVars,
|
|
3165
|
-
app,
|
|
3166
|
-
invocation,
|
|
3167
|
-
log: createContextLogger()
|
|
3168
|
-
};
|
|
3169
|
-
}
|
|
3170
|
-
} else {
|
|
3171
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
3172
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
3173
|
-
if (!appId || !appVersionId) {
|
|
3174
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
3175
|
-
}
|
|
3176
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3177
|
-
const protocol = forwardedProto ?? "https";
|
|
3178
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3179
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3180
|
-
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3181
|
-
webhookRequest = {
|
|
3182
|
-
method,
|
|
3183
|
-
url: webhookUrl,
|
|
3184
|
-
path: path2,
|
|
3185
|
-
headers: event.headers,
|
|
3186
|
-
query: event.queryStringParameters ?? {},
|
|
3187
|
-
body: parsedBody,
|
|
3188
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3189
|
-
};
|
|
3190
|
-
webhookContext = {
|
|
3191
|
-
env: process.env,
|
|
3192
|
-
app: { id: appId, versionId: appVersionId },
|
|
3193
|
-
log: createContextLogger()
|
|
3194
|
-
};
|
|
3195
|
-
}
|
|
3196
|
-
const originalEnv = { ...process.env };
|
|
3197
|
-
Object.assign(process.env, requestEnv);
|
|
3198
|
-
const requestConfig = {
|
|
3199
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3200
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3201
|
-
};
|
|
3202
|
-
let webhookResponse;
|
|
3203
|
-
try {
|
|
3204
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
3205
|
-
return await runWithConfig(requestConfig, async () => {
|
|
3206
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
3207
|
-
});
|
|
3208
|
-
});
|
|
3209
|
-
} catch (err) {
|
|
3210
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3211
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
3212
|
-
} finally {
|
|
3213
|
-
process.env = originalEnv;
|
|
3212
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3213
|
+
const protocol = forwardedProto ?? "https";
|
|
3214
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3215
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3216
|
+
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3217
|
+
const parseResult = parseWebhookRequest(
|
|
3218
|
+
parsedBody,
|
|
3219
|
+
method,
|
|
3220
|
+
webhookUrl,
|
|
3221
|
+
path2,
|
|
3222
|
+
event.headers,
|
|
3223
|
+
event.queryStringParameters ?? {},
|
|
3224
|
+
rawBody,
|
|
3225
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
3226
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
3227
|
+
);
|
|
3228
|
+
if ("error" in parseResult) {
|
|
3229
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
3214
3230
|
}
|
|
3231
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
3215
3232
|
const responseHeaders = {
|
|
3216
3233
|
...headers,
|
|
3217
|
-
...
|
|
3234
|
+
...result.headers
|
|
3218
3235
|
};
|
|
3219
|
-
const status = webhookResponse.status ?? 200;
|
|
3220
|
-
const body = webhookResponse.body;
|
|
3221
3236
|
return {
|
|
3222
|
-
statusCode: status,
|
|
3237
|
+
statusCode: result.status,
|
|
3223
3238
|
headers: responseHeaders,
|
|
3224
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
3239
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
3225
3240
|
};
|
|
3226
3241
|
}
|
|
3227
3242
|
if (path2 === "/core" && method === "POST") {
|
|
@@ -3357,9 +3372,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3357
3372
|
}
|
|
3358
3373
|
}
|
|
3359
3374
|
if (path2 === "/install" && method === "POST") {
|
|
3360
|
-
if (!config.hooks?.install) {
|
|
3361
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
3362
|
-
}
|
|
3363
3375
|
let installBody;
|
|
3364
3376
|
try {
|
|
3365
3377
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3370,68 +3382,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3370
3382
|
headers
|
|
3371
3383
|
);
|
|
3372
3384
|
}
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
400,
|
|
3376
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
3377
|
-
headers
|
|
3378
|
-
);
|
|
3379
|
-
}
|
|
3380
|
-
const installContext = {
|
|
3381
|
-
env: installBody.env ?? {},
|
|
3382
|
-
workplace: installBody.context.workplace,
|
|
3383
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
3384
|
-
app: installBody.context.app,
|
|
3385
|
-
invocation: installBody.invocation,
|
|
3386
|
-
log: createContextLogger()
|
|
3387
|
-
};
|
|
3388
|
-
const installRequestConfig = {
|
|
3389
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3390
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3391
|
-
};
|
|
3392
|
-
try {
|
|
3393
|
-
const installHook = config.hooks.install;
|
|
3394
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
3395
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
3396
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
3397
|
-
return await installHandler(installContext);
|
|
3398
|
-
});
|
|
3399
|
-
});
|
|
3400
|
-
return createResponse(
|
|
3401
|
-
200,
|
|
3402
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
3403
|
-
headers
|
|
3404
|
-
);
|
|
3405
|
-
} catch (err) {
|
|
3406
|
-
if (err instanceof InstallError) {
|
|
3407
|
-
return createResponse(
|
|
3408
|
-
400,
|
|
3409
|
-
{
|
|
3410
|
-
error: {
|
|
3411
|
-
code: err.code,
|
|
3412
|
-
message: err.message,
|
|
3413
|
-
field: err.field
|
|
3414
|
-
}
|
|
3415
|
-
},
|
|
3416
|
-
headers
|
|
3417
|
-
);
|
|
3418
|
-
}
|
|
3419
|
-
return createResponse(
|
|
3420
|
-
500,
|
|
3421
|
-
{
|
|
3422
|
-
error: {
|
|
3423
|
-
code: -32603,
|
|
3424
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3425
|
-
}
|
|
3426
|
-
},
|
|
3427
|
-
headers
|
|
3428
|
-
);
|
|
3429
|
-
}
|
|
3385
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3386
|
+
return createResponse(result.status, result.body, headers);
|
|
3430
3387
|
}
|
|
3431
3388
|
if (path2 === "/uninstall" && method === "POST") {
|
|
3432
|
-
if (!config.hooks?.uninstall) {
|
|
3433
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
3434
|
-
}
|
|
3435
3389
|
let uninstallBody;
|
|
3436
3390
|
try {
|
|
3437
3391
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3442,137 +3396,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3442
3396
|
headers
|
|
3443
3397
|
);
|
|
3444
3398
|
}
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
400,
|
|
3448
|
-
{
|
|
3449
|
-
error: {
|
|
3450
|
-
code: -32602,
|
|
3451
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3452
|
-
}
|
|
3453
|
-
},
|
|
3454
|
-
headers
|
|
3455
|
-
);
|
|
3456
|
-
}
|
|
3457
|
-
const uninstallContext = {
|
|
3458
|
-
env: uninstallBody.env ?? {},
|
|
3459
|
-
workplace: uninstallBody.context.workplace,
|
|
3460
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
3461
|
-
app: uninstallBody.context.app,
|
|
3462
|
-
invocation: uninstallBody.invocation,
|
|
3463
|
-
log: createContextLogger()
|
|
3464
|
-
};
|
|
3465
|
-
const uninstallRequestConfig = {
|
|
3466
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3467
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3468
|
-
};
|
|
3469
|
-
try {
|
|
3470
|
-
const uninstallHook = config.hooks.uninstall;
|
|
3471
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3472
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
3473
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
3474
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
3475
|
-
});
|
|
3476
|
-
});
|
|
3477
|
-
return createResponse(
|
|
3478
|
-
200,
|
|
3479
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
3480
|
-
headers
|
|
3481
|
-
);
|
|
3482
|
-
} catch (err) {
|
|
3483
|
-
return createResponse(
|
|
3484
|
-
500,
|
|
3485
|
-
{
|
|
3486
|
-
error: {
|
|
3487
|
-
code: -32603,
|
|
3488
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3489
|
-
}
|
|
3490
|
-
},
|
|
3491
|
-
headers
|
|
3492
|
-
);
|
|
3493
|
-
}
|
|
3399
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3400
|
+
return createResponse(result.status, result.body, headers);
|
|
3494
3401
|
}
|
|
3495
3402
|
if (path2 === "/provision" && method === "POST") {
|
|
3496
|
-
console.log("[serverless] /provision endpoint called");
|
|
3497
|
-
if (!config.hooks?.provision) {
|
|
3498
|
-
console.log("[serverless] No provision handler configured");
|
|
3499
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
3500
|
-
}
|
|
3501
3403
|
let provisionBody;
|
|
3502
3404
|
try {
|
|
3503
3405
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
3504
|
-
console.log("[serverless] Provision body parsed:", {
|
|
3505
|
-
hasEnv: !!provisionBody.env,
|
|
3506
|
-
hasContext: !!provisionBody.context,
|
|
3507
|
-
appId: provisionBody.context?.app?.id,
|
|
3508
|
-
versionId: provisionBody.context?.app?.versionId
|
|
3509
|
-
});
|
|
3510
3406
|
} catch {
|
|
3511
|
-
console.log("[serverless] Failed to parse provision body");
|
|
3512
3407
|
return createResponse(
|
|
3513
3408
|
400,
|
|
3514
3409
|
{ error: { code: -32700, message: "Parse error" } },
|
|
3515
3410
|
headers
|
|
3516
3411
|
);
|
|
3517
3412
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
return createResponse(
|
|
3521
|
-
400,
|
|
3522
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
3523
|
-
headers
|
|
3524
|
-
);
|
|
3525
|
-
}
|
|
3526
|
-
const mergedEnv = {};
|
|
3527
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3528
|
-
if (value !== void 0) {
|
|
3529
|
-
mergedEnv[key] = value;
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3533
|
-
const provisionContext = {
|
|
3534
|
-
env: mergedEnv,
|
|
3535
|
-
app: provisionBody.context.app,
|
|
3536
|
-
invocation: provisionBody.invocation,
|
|
3537
|
-
log: createContextLogger()
|
|
3538
|
-
};
|
|
3539
|
-
const provisionRequestConfig = {
|
|
3540
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3541
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3542
|
-
};
|
|
3543
|
-
console.log("[serverless] Calling provision handler...");
|
|
3544
|
-
try {
|
|
3545
|
-
const provisionHook = config.hooks.provision;
|
|
3546
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3547
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
3548
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
3549
|
-
return await provisionHandler(provisionContext);
|
|
3550
|
-
});
|
|
3551
|
-
});
|
|
3552
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
3553
|
-
return createResponse(200, result, headers);
|
|
3554
|
-
} catch (err) {
|
|
3555
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
3556
|
-
return createResponse(
|
|
3557
|
-
500,
|
|
3558
|
-
{
|
|
3559
|
-
error: {
|
|
3560
|
-
code: -32603,
|
|
3561
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3562
|
-
}
|
|
3563
|
-
},
|
|
3564
|
-
headers
|
|
3565
|
-
);
|
|
3566
|
-
}
|
|
3413
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3414
|
+
return createResponse(result.status, result.body, headers);
|
|
3567
3415
|
}
|
|
3568
3416
|
if (path2 === "/oauth_callback" && method === "POST") {
|
|
3569
|
-
if (!config.hooks?.oauth_callback) {
|
|
3570
|
-
return createResponse(
|
|
3571
|
-
404,
|
|
3572
|
-
{ error: "OAuth callback handler not configured" },
|
|
3573
|
-
headers
|
|
3574
|
-
);
|
|
3575
|
-
}
|
|
3576
3417
|
let parsedBody;
|
|
3577
3418
|
try {
|
|
3578
3419
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3584,70 +3425,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
3584
3425
|
headers
|
|
3585
3426
|
);
|
|
3586
3427
|
}
|
|
3587
|
-
const
|
|
3588
|
-
|
|
3589
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
3590
|
-
return createResponse(
|
|
3591
|
-
400,
|
|
3592
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
3593
|
-
headers
|
|
3594
|
-
);
|
|
3595
|
-
}
|
|
3596
|
-
const invocation = parsedBody.invocation;
|
|
3597
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
3598
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
3599
|
-
const oauthCallbackContext = {
|
|
3600
|
-
request: oauthRequest,
|
|
3601
|
-
invocation,
|
|
3602
|
-
log: createContextLogger()
|
|
3603
|
-
};
|
|
3604
|
-
try {
|
|
3605
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
3606
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
3607
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
3608
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
3609
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
3610
|
-
});
|
|
3611
|
-
});
|
|
3612
|
-
return createResponse(
|
|
3613
|
-
200,
|
|
3614
|
-
{
|
|
3615
|
-
appInstallationId: result.appInstallationId,
|
|
3616
|
-
env: result.env ?? {}
|
|
3617
|
-
},
|
|
3618
|
-
headers
|
|
3619
|
-
);
|
|
3620
|
-
} catch (err) {
|
|
3621
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
3622
|
-
return createResponse(
|
|
3623
|
-
500,
|
|
3624
|
-
{
|
|
3625
|
-
error: {
|
|
3626
|
-
code: -32603,
|
|
3627
|
-
message: errorMessage
|
|
3628
|
-
}
|
|
3629
|
-
},
|
|
3630
|
-
headers
|
|
3631
|
-
);
|
|
3632
|
-
}
|
|
3428
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3429
|
+
return createResponse(result.status, result.body, headers);
|
|
3633
3430
|
}
|
|
3634
3431
|
if (path2 === "/health" && method === "GET") {
|
|
3635
3432
|
return createResponse(200, state.getHealthStatus(), headers);
|
|
3636
3433
|
}
|
|
3637
3434
|
if (path2 === "/config" && method === "GET") {
|
|
3638
|
-
|
|
3639
|
-
if (!appConfig && config.appConfigLoader) {
|
|
3640
|
-
const loaded = await config.appConfigLoader();
|
|
3641
|
-
appConfig = loaded.default;
|
|
3642
|
-
}
|
|
3643
|
-
if (!appConfig) {
|
|
3644
|
-
appConfig = createMinimalConfig(
|
|
3645
|
-
config.metadata.name,
|
|
3646
|
-
config.metadata.version
|
|
3647
|
-
);
|
|
3648
|
-
}
|
|
3649
|
-
const serializedConfig = await resolveConfig(appConfig, registry, webhookRegistry);
|
|
3650
|
-
return createResponse(200, serializedConfig, headers);
|
|
3435
|
+
return createResponse(200, serializeConfig(config), headers);
|
|
3651
3436
|
}
|
|
3652
3437
|
if (path2 === "/mcp" && method === "POST") {
|
|
3653
3438
|
let body;
|
|
@@ -3865,9 +3650,11 @@ console.log("[skedyul-node/server] All imports complete");
|
|
|
3865
3650
|
console.log("[skedyul-node/server] Installing context logger...");
|
|
3866
3651
|
installContextLogger();
|
|
3867
3652
|
console.log("[skedyul-node/server] Context logger installed");
|
|
3868
|
-
function createSkedyulServer(config
|
|
3653
|
+
function createSkedyulServer(config) {
|
|
3869
3654
|
console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
|
|
3870
3655
|
mergeRuntimeEnv();
|
|
3656
|
+
const registry = config.tools;
|
|
3657
|
+
const webhookRegistry = config.webhooks;
|
|
3871
3658
|
console.log("[createSkedyulServer] Step 2: coreApi setup");
|
|
3872
3659
|
if (config.coreApi?.service) {
|
|
3873
3660
|
coreApiService.register(config.coreApi.service);
|
|
@@ -3879,7 +3666,7 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
3879
3666
|
const tools = buildToolMetadata(registry);
|
|
3880
3667
|
console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
|
|
3881
3668
|
const toolNames = Object.values(registry).map((tool) => tool.name);
|
|
3882
|
-
const runtimeLabel = config.computeLayer;
|
|
3669
|
+
const runtimeLabel = config.computeLayer ?? "serverless";
|
|
3883
3670
|
const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
|
|
3884
3671
|
const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
|
|
3885
3672
|
console.log("[createSkedyulServer] Step 4: createRequestState()");
|
|
@@ -3892,8 +3679,8 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
3892
3679
|
console.log("[createSkedyulServer] Step 4 done");
|
|
3893
3680
|
console.log("[createSkedyulServer] Step 5: new McpServer()");
|
|
3894
3681
|
const mcpServer = new McpServer({
|
|
3895
|
-
name: config.
|
|
3896
|
-
version: config.
|
|
3682
|
+
name: config.name,
|
|
3683
|
+
version: config.version ?? "0.0.0"
|
|
3897
3684
|
});
|
|
3898
3685
|
console.log("[createSkedyulServer] Step 5 done");
|
|
3899
3686
|
const dedicatedShutdown = () => {
|
|
@@ -4021,13 +3808,11 @@ function createSkedyulServer(config, registry, webhookRegistry) {
|
|
|
4021
3808
|
tools,
|
|
4022
3809
|
callTool,
|
|
4023
3810
|
state,
|
|
4024
|
-
mcpServer
|
|
4025
|
-
registry,
|
|
4026
|
-
webhookRegistry
|
|
3811
|
+
mcpServer
|
|
4027
3812
|
);
|
|
4028
3813
|
}
|
|
4029
3814
|
console.log("[createSkedyulServer] Creating serverless instance");
|
|
4030
|
-
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer
|
|
3815
|
+
const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer);
|
|
4031
3816
|
console.log("[createSkedyulServer] Serverless instance created successfully");
|
|
4032
3817
|
return serverlessInstance;
|
|
4033
3818
|
}
|
|
@@ -4348,7 +4133,6 @@ export {
|
|
|
4348
4133
|
communicationChannel,
|
|
4349
4134
|
configure,
|
|
4350
4135
|
createContextLogger,
|
|
4351
|
-
createMinimalConfig,
|
|
4352
4136
|
createServerHookContext,
|
|
4353
4137
|
createToolCallContext,
|
|
4354
4138
|
createWebhookContext,
|
|
@@ -4375,7 +4159,6 @@ export {
|
|
|
4375
4159
|
isWorkflowDependency,
|
|
4376
4160
|
loadConfig,
|
|
4377
4161
|
report,
|
|
4378
|
-
resolveConfig,
|
|
4379
4162
|
resource,
|
|
4380
4163
|
runWithConfig,
|
|
4381
4164
|
safeParseConfig,
|