@kadoa/mcp 0.3.10-rc.1 → 0.3.10-rc.3
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/index.js +205 -89
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -49167,13 +49167,37 @@ function createKadoaClient(auth) {
|
|
|
49167
49167
|
});
|
|
49168
49168
|
return client;
|
|
49169
49169
|
}
|
|
49170
|
-
var ctxRefreshMutex;
|
|
49170
|
+
var refreshRawMutex, ctxRefreshMutex;
|
|
49171
49171
|
var init_client = __esm(() => {
|
|
49172
49172
|
init_dist2();
|
|
49173
|
+
refreshRawMutex = new Map;
|
|
49173
49174
|
ctxRefreshMutex = new WeakMap;
|
|
49174
49175
|
});
|
|
49175
49176
|
|
|
49176
49177
|
// src/client.ts
|
|
49178
|
+
var exports_client = {};
|
|
49179
|
+
__export(exports_client, {
|
|
49180
|
+
refreshSupabaseJwtRaw: () => refreshSupabaseJwtRaw,
|
|
49181
|
+
refreshSupabaseJwt: () => refreshSupabaseJwt,
|
|
49182
|
+
isJwtExpired: () => isJwtExpired,
|
|
49183
|
+
getValidJwt: () => getValidJwt,
|
|
49184
|
+
decodeJwtClaims: () => decodeJwtClaims,
|
|
49185
|
+
createKadoaClient: () => createKadoaClient2,
|
|
49186
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
49187
|
+
KadoaSdkException: () => KadoaSdkException,
|
|
49188
|
+
KadoaClient: () => KadoaClient
|
|
49189
|
+
});
|
|
49190
|
+
function createKadoaClient2(auth) {
|
|
49191
|
+
const client = new KadoaClient({ bearerToken: auth.jwt });
|
|
49192
|
+
client.axiosInstance.interceptors.request.use((config2) => {
|
|
49193
|
+
config2.headers["x-kadoa-source"] = "mcp";
|
|
49194
|
+
if (auth.teamId) {
|
|
49195
|
+
config2.headers["x-team-id"] = auth.teamId;
|
|
49196
|
+
}
|
|
49197
|
+
return config2;
|
|
49198
|
+
});
|
|
49199
|
+
return client;
|
|
49200
|
+
}
|
|
49177
49201
|
function decodeJwtClaims(jwt2) {
|
|
49178
49202
|
try {
|
|
49179
49203
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -49195,36 +49219,57 @@ function isJwtExpired(jwt2) {
|
|
|
49195
49219
|
return true;
|
|
49196
49220
|
}
|
|
49197
49221
|
}
|
|
49198
|
-
async function
|
|
49199
|
-
|
|
49200
|
-
|
|
49201
|
-
|
|
49222
|
+
async function refreshSupabaseJwtRaw(supabaseRefreshToken) {
|
|
49223
|
+
const inflight = refreshRawMutex2.get(supabaseRefreshToken);
|
|
49224
|
+
if (inflight) {
|
|
49225
|
+
console.error(`[JWT_REFRESH] DEDUP: reusing in-flight raw refresh`);
|
|
49226
|
+
return inflight;
|
|
49202
49227
|
}
|
|
49228
|
+
const promise3 = _doRefreshRaw(supabaseRefreshToken).finally(() => {
|
|
49229
|
+
refreshRawMutex2.delete(supabaseRefreshToken);
|
|
49230
|
+
});
|
|
49231
|
+
refreshRawMutex2.set(supabaseRefreshToken, promise3);
|
|
49232
|
+
return promise3;
|
|
49233
|
+
}
|
|
49234
|
+
async function _doRefreshRaw(supabaseRefreshToken) {
|
|
49203
49235
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
49204
49236
|
if (!supabaseUrl) {
|
|
49205
49237
|
console.error("[JWT_REFRESH] SUPABASE_URL not set, cannot refresh");
|
|
49238
|
+
return null;
|
|
49239
|
+
}
|
|
49240
|
+
const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
49241
|
+
method: "POST",
|
|
49242
|
+
headers: {
|
|
49243
|
+
"Content-Type": "application/json",
|
|
49244
|
+
apikey: process.env.SUPABASE_ANON_KEY
|
|
49245
|
+
},
|
|
49246
|
+
body: JSON.stringify({ refresh_token: supabaseRefreshToken })
|
|
49247
|
+
});
|
|
49248
|
+
if (res.ok) {
|
|
49249
|
+
const data = await res.json();
|
|
49250
|
+
return { jwt: data.access_token, refreshToken: data.refresh_token };
|
|
49251
|
+
}
|
|
49252
|
+
const body = await res.text().catch(() => "");
|
|
49253
|
+
console.error(`[JWT_REFRESH] FAIL: Supabase returned ${res.status} (refreshToken=${supabaseRefreshToken.slice(0, 12)}...): ${body}`);
|
|
49254
|
+
if (body.includes("session_expired") || body.includes("refresh_token_not_found") || body.includes("refresh_token_already_used")) {
|
|
49255
|
+
throw new SessionExpiredError("Your Kadoa session has expired due to inactivity. Please reconnect to re-authenticate.");
|
|
49256
|
+
}
|
|
49257
|
+
return null;
|
|
49258
|
+
}
|
|
49259
|
+
async function refreshSupabaseJwt(ctx) {
|
|
49260
|
+
if (!ctx.supabaseRefreshToken) {
|
|
49261
|
+
console.error("[JWT_REFRESH] No refresh token available, cannot refresh");
|
|
49206
49262
|
return;
|
|
49207
49263
|
}
|
|
49208
49264
|
try {
|
|
49209
49265
|
const refreshToken = ctx.supabaseRefreshToken;
|
|
49210
49266
|
console.error(`[JWT_REFRESH] Refreshing Supabase JWT (refreshToken=${refreshToken.slice(0, 12)}..., team=${ctx.teamId ?? "unknown"})`);
|
|
49211
|
-
const
|
|
49212
|
-
|
|
49213
|
-
headers: {
|
|
49214
|
-
"Content-Type": "application/json",
|
|
49215
|
-
apikey: process.env.SUPABASE_ANON_KEY
|
|
49216
|
-
},
|
|
49217
|
-
body: JSON.stringify({ refresh_token: refreshToken })
|
|
49218
|
-
});
|
|
49219
|
-
if (!res.ok) {
|
|
49220
|
-
const body = await res.text().catch(() => "");
|
|
49221
|
-
console.error(`[JWT_REFRESH] FAIL: Supabase returned ${res.status} (refreshToken=${refreshToken.slice(0, 12)}...): ${body}`);
|
|
49267
|
+
const result = await refreshSupabaseJwtRaw(refreshToken);
|
|
49268
|
+
if (!result)
|
|
49222
49269
|
return;
|
|
49223
|
-
|
|
49224
|
-
|
|
49225
|
-
ctx.
|
|
49226
|
-
ctx.supabaseRefreshToken = data.refresh_token;
|
|
49227
|
-
ctx.client.setBearerToken(data.access_token);
|
|
49270
|
+
ctx.supabaseJwt = result.jwt;
|
|
49271
|
+
ctx.supabaseRefreshToken = result.refreshToken;
|
|
49272
|
+
ctx.client.setBearerToken(result.jwt);
|
|
49228
49273
|
try {
|
|
49229
49274
|
await ctx.persist?.({
|
|
49230
49275
|
supabaseJwt: ctx.supabaseJwt,
|
|
@@ -49234,9 +49279,11 @@ async function refreshSupabaseJwt(ctx) {
|
|
|
49234
49279
|
} catch (e) {
|
|
49235
49280
|
console.error("[JWT_REFRESH] WARN: persist failed, tokens updated in-memory only:", e);
|
|
49236
49281
|
}
|
|
49237
|
-
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${
|
|
49238
|
-
return
|
|
49282
|
+
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${result.refreshToken.slice(0, 12)}...)`);
|
|
49283
|
+
return result.jwt;
|
|
49239
49284
|
} catch (error48) {
|
|
49285
|
+
if (error48 instanceof SessionExpiredError)
|
|
49286
|
+
throw error48;
|
|
49240
49287
|
console.error("[JWT_REFRESH] FAIL: threw", error48);
|
|
49241
49288
|
return;
|
|
49242
49289
|
}
|
|
@@ -49257,9 +49304,16 @@ async function getValidJwt(ctx) {
|
|
|
49257
49304
|
ctxRefreshMutex2.set(ctx, promise3);
|
|
49258
49305
|
return promise3;
|
|
49259
49306
|
}
|
|
49260
|
-
var ctxRefreshMutex2;
|
|
49307
|
+
var SessionExpiredError, refreshRawMutex2, ctxRefreshMutex2;
|
|
49261
49308
|
var init_client2 = __esm(() => {
|
|
49262
49309
|
init_dist2();
|
|
49310
|
+
SessionExpiredError = class SessionExpiredError extends Error {
|
|
49311
|
+
constructor(message) {
|
|
49312
|
+
super(message ?? "Supabase session expired. Please re-authenticate.");
|
|
49313
|
+
this.name = "SessionExpiredError";
|
|
49314
|
+
}
|
|
49315
|
+
};
|
|
49316
|
+
refreshRawMutex2 = new Map;
|
|
49263
49317
|
ctxRefreshMutex2 = new WeakMap;
|
|
49264
49318
|
});
|
|
49265
49319
|
|
|
@@ -49281,6 +49335,32 @@ function coerceArray(wrapPlainString = false) {
|
|
|
49281
49335
|
return val;
|
|
49282
49336
|
};
|
|
49283
49337
|
}
|
|
49338
|
+
function coerceNumber() {
|
|
49339
|
+
return (val) => typeof val === "string" ? Number(val) : val;
|
|
49340
|
+
}
|
|
49341
|
+
function coerceBoolean() {
|
|
49342
|
+
return (val) => {
|
|
49343
|
+
if (typeof val === "string") {
|
|
49344
|
+
if (val === "true")
|
|
49345
|
+
return true;
|
|
49346
|
+
if (val === "false")
|
|
49347
|
+
return false;
|
|
49348
|
+
}
|
|
49349
|
+
return val;
|
|
49350
|
+
};
|
|
49351
|
+
}
|
|
49352
|
+
function coerceJson() {
|
|
49353
|
+
return (val) => {
|
|
49354
|
+
if (typeof val === "string") {
|
|
49355
|
+
try {
|
|
49356
|
+
const parsed = JSON.parse(val);
|
|
49357
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
|
|
49358
|
+
return parsed;
|
|
49359
|
+
} catch {}
|
|
49360
|
+
}
|
|
49361
|
+
return val;
|
|
49362
|
+
};
|
|
49363
|
+
}
|
|
49284
49364
|
function isRealTimeInterval(interval) {
|
|
49285
49365
|
return interval?.toUpperCase().replace("-", "_") === "REAL_TIME";
|
|
49286
49366
|
}
|
|
@@ -49380,6 +49460,10 @@ function registerTools(server, ctx) {
|
|
|
49380
49460
|
}
|
|
49381
49461
|
return await handler(...args);
|
|
49382
49462
|
} catch (error48) {
|
|
49463
|
+
if (error48 instanceof SessionExpiredError) {
|
|
49464
|
+
console.error(`[Tool Error] ${name}: session expired, user must re-authenticate`);
|
|
49465
|
+
return errorResult("Your session has expired. Please reconnect the MCP server to re-authenticate.");
|
|
49466
|
+
}
|
|
49383
49467
|
let message = classifyError(error48);
|
|
49384
49468
|
if (KadoaHttpException.isInstance(error48) && error48.httpStatus === 403) {
|
|
49385
49469
|
try {
|
|
@@ -49435,7 +49519,7 @@ function registerTools(server, ctx) {
|
|
|
49435
49519
|
headers: exports_external.object({}).passthrough().optional().describe("Custom headers as key-value pairs (required for 'header' type)")
|
|
49436
49520
|
}).optional().describe("Authentication configuration for the webhook endpoint");
|
|
49437
49521
|
const notificationsInputShape = {
|
|
49438
|
-
notifications: exports_external.object({
|
|
49522
|
+
notifications: exports_external.preprocess(coerceJson(), exports_external.object({
|
|
49439
49523
|
email: exports_external.object({
|
|
49440
49524
|
recipients: exports_external.array(exports_external.string().email()).optional().describe("Email addresses to notify. Omit to use account default email.")
|
|
49441
49525
|
}).optional().describe("Send email notifications. Pass {} for account default email, or {recipients: [...]} for custom addresses."),
|
|
@@ -49450,7 +49534,7 @@ function registerTools(server, ctx) {
|
|
|
49450
49534
|
slackChannelName: exports_external.string().optional().describe("Slack channel name (e.g. #alerts)")
|
|
49451
49535
|
}).optional().describe("Send notifications to a Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
|
|
49452
49536
|
websocket: exports_external.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
|
|
49453
|
-
}).optional().describe("Notification channels to alert when data changes.")
|
|
49537
|
+
}).optional().describe("Notification channels to alert when data changes."))
|
|
49454
49538
|
};
|
|
49455
49539
|
function resolveUrls(args) {
|
|
49456
49540
|
const urls = args.urls ?? (args.url ? [args.url] : []);
|
|
@@ -49529,7 +49613,10 @@ function registerTools(server, ctx) {
|
|
|
49529
49613
|
inputSchema: strictSchema({
|
|
49530
49614
|
...extractionInputShape,
|
|
49531
49615
|
...urlInputShape,
|
|
49532
|
-
|
|
49616
|
+
description: exports_external.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
49617
|
+
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
49618
|
+
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
|
|
49619
|
+
updateInterval: exports_external.enum([
|
|
49533
49620
|
"ONLY_ONCE",
|
|
49534
49621
|
"EVERY_10_MINUTES",
|
|
49535
49622
|
"HALF_HOURLY",
|
|
@@ -49545,8 +49632,10 @@ function registerTools(server, ctx) {
|
|
|
49545
49632
|
"BIWEEKLY",
|
|
49546
49633
|
"TRIWEEKLY",
|
|
49547
49634
|
"FOUR_WEEKS",
|
|
49548
|
-
"MONTHLY"
|
|
49549
|
-
|
|
49635
|
+
"MONTHLY",
|
|
49636
|
+
"CUSTOM"
|
|
49637
|
+
]).optional().describe("How often the workflow runs. Defaults to ONLY_ONCE. Use CUSTOM with the 'schedules' field for cron-based scheduling."),
|
|
49638
|
+
schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM updateInterval (e.g. '0 8 * * 2' for Tuesdays at 8am UTC). Requires updateInterval='CUSTOM'."),
|
|
49550
49639
|
...notificationsInputShape
|
|
49551
49640
|
}),
|
|
49552
49641
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
@@ -49555,13 +49644,18 @@ function registerTools(server, ctx) {
|
|
|
49555
49644
|
if (!urls) {
|
|
49556
49645
|
return errorResult("At least one URL is required. Provide 'urls' (array) or 'url' (string).");
|
|
49557
49646
|
}
|
|
49647
|
+
if (args.updateInterval === "CUSTOM" && (!args.schedules || args.schedules.length === 0)) {
|
|
49648
|
+
return errorResult("updateInterval='CUSTOM' requires at least one cron expression in the 'schedules' field (e.g. '0 8 * * 2' for Tuesdays at 8am UTC).");
|
|
49649
|
+
}
|
|
49558
49650
|
let builder = ctx.client.extract({
|
|
49559
49651
|
urls,
|
|
49560
49652
|
name: args.name || "Untitled Workflow",
|
|
49561
49653
|
navigationMode: "agentic-navigation",
|
|
49562
49654
|
userPrompt: args.prompt,
|
|
49563
49655
|
extraction: buildExtraction(args),
|
|
49564
|
-
interval: args.
|
|
49656
|
+
interval: args.updateInterval,
|
|
49657
|
+
description: args.description,
|
|
49658
|
+
schedules: args.schedules
|
|
49565
49659
|
});
|
|
49566
49660
|
const n = args.notifications;
|
|
49567
49661
|
const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
|
|
@@ -49572,6 +49666,15 @@ function registerTools(server, ctx) {
|
|
|
49572
49666
|
});
|
|
49573
49667
|
}
|
|
49574
49668
|
const workflow = await builder.create();
|
|
49669
|
+
const needsUpdate = args.limit !== undefined || args.tags && args.tags.length > 0;
|
|
49670
|
+
if (needsUpdate) {
|
|
49671
|
+
const updates = {};
|
|
49672
|
+
if (args.limit !== undefined)
|
|
49673
|
+
updates.limit = args.limit;
|
|
49674
|
+
if (args.tags && args.tags.length > 0)
|
|
49675
|
+
updates.tags = args.tags;
|
|
49676
|
+
await ctx.client.workflow.update(workflow.workflowId, updates);
|
|
49677
|
+
}
|
|
49575
49678
|
const enabledChannels = await describeNotifications(n);
|
|
49576
49679
|
let message = "Workflow created successfully. The AI agent will start extracting data automatically.";
|
|
49577
49680
|
if (enabledChannels.length > 0) {
|
|
@@ -49592,6 +49695,8 @@ function registerTools(server, ctx) {
|
|
|
49592
49695
|
inputSchema: strictSchema({
|
|
49593
49696
|
...extractionInputShape,
|
|
49594
49697
|
...urlInputShape,
|
|
49698
|
+
description: exports_external.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
|
|
49699
|
+
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing monitors"),
|
|
49595
49700
|
...notificationsInputShape
|
|
49596
49701
|
}),
|
|
49597
49702
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
@@ -49610,13 +49715,17 @@ function registerTools(server, ctx) {
|
|
|
49610
49715
|
name: args.name || "Untitled Monitor",
|
|
49611
49716
|
userPrompt: args.prompt,
|
|
49612
49717
|
extraction: buildExtraction(args),
|
|
49613
|
-
interval: "REAL_TIME"
|
|
49718
|
+
interval: "REAL_TIME",
|
|
49719
|
+
description: args.description
|
|
49614
49720
|
});
|
|
49615
49721
|
builder = builder.withNotifications({
|
|
49616
49722
|
events: ["workflow_data_change"],
|
|
49617
49723
|
channels: buildNotificationChannels(n)
|
|
49618
49724
|
});
|
|
49619
49725
|
const workflow = await builder.create();
|
|
49726
|
+
if (args.tags && args.tags.length > 0) {
|
|
49727
|
+
await ctx.client.workflow.update(workflow.workflowId, { tags: args.tags });
|
|
49728
|
+
}
|
|
49620
49729
|
const enabledChannels = await describeNotifications(n);
|
|
49621
49730
|
let message = "Real-time monitor created successfully. It will continuously watch for changes — no manual runs needed.";
|
|
49622
49731
|
if (enabledChannels.length > 0) {
|
|
@@ -49633,7 +49742,7 @@ function registerTools(server, ctx) {
|
|
|
49633
49742
|
server.registerTool("list_workflows", {
|
|
49634
49743
|
description: "List all workflows with their current status",
|
|
49635
49744
|
inputSchema: {
|
|
49636
|
-
limit: exports_external.number().optional().describe("Maximum number of workflows to return"),
|
|
49745
|
+
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of workflows to return"),
|
|
49637
49746
|
state: exports_external.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)")
|
|
49638
49747
|
},
|
|
49639
49748
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
@@ -49694,7 +49803,7 @@ function registerTools(server, ctx) {
|
|
|
49694
49803
|
description: "Run a workflow to extract fresh data. The run is asynchronous and may take several minutes. Do NOT poll or sleep-wait for completion. Return the workflow ID to the user and let them check status with get_workflow or fetch results later with fetch_data.",
|
|
49695
49804
|
inputSchema: {
|
|
49696
49805
|
workflowId: exports_external.string().describe("The workflow ID to run"),
|
|
49697
|
-
limit: exports_external.number().optional().describe("Maximum number of records to extract (default: 1000)")
|
|
49806
|
+
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract (default: 1000)")
|
|
49698
49807
|
},
|
|
49699
49808
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
49700
49809
|
}, withErrorHandling("run_workflow", async (args) => {
|
|
@@ -49715,8 +49824,8 @@ function registerTools(server, ctx) {
|
|
|
49715
49824
|
description: "Get extracted data from a workflow. Data is only available after the workflow run has completed (displayState is no longer RUNNING). Do NOT poll or sleep-wait for completion.",
|
|
49716
49825
|
inputSchema: {
|
|
49717
49826
|
workflowId: exports_external.string().describe("The workflow ID"),
|
|
49718
|
-
limit: exports_external.number().optional().describe("Maximum number of records to return"),
|
|
49719
|
-
page: exports_external.number().optional().describe("Page number for pagination")
|
|
49827
|
+
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to return"),
|
|
49828
|
+
page: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Page number for pagination")
|
|
49720
49829
|
},
|
|
49721
49830
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
49722
49831
|
}, withErrorHandling("fetch_data", async (args) => {
|
|
@@ -49735,7 +49844,7 @@ function registerTools(server, ctx) {
|
|
|
49735
49844
|
description: "Delete a workflow permanently. This is irreversible. " + "You MUST first call this tool without confirmed=true to preview what will be deleted, " + "then show the user the workflow name and ask for confirmation, " + "then call again with confirmed=true only after the user explicitly agrees.",
|
|
49736
49845
|
inputSchema: {
|
|
49737
49846
|
workflowId: exports_external.string().describe("The workflow ID to delete"),
|
|
49738
|
-
confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
|
|
49847
|
+
confirmed: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
|
|
49739
49848
|
},
|
|
49740
49849
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
|
|
49741
49850
|
}, withErrorHandling("delete_workflow", async (args) => {
|
|
@@ -49785,7 +49894,7 @@ function registerTools(server, ctx) {
|
|
|
49785
49894
|
urls: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
|
|
49786
49895
|
entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
49787
49896
|
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape))).optional().describe("New extraction schema fields"),
|
|
49788
|
-
description: exports_external.string().optional().describe("Workflow description"),
|
|
49897
|
+
description: exports_external.string().max(500).optional().describe("Workflow description (max 500 characters)"),
|
|
49789
49898
|
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
49790
49899
|
userPrompt: exports_external.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
|
|
49791
49900
|
updateInterval: exports_external.enum([
|
|
@@ -49808,7 +49917,7 @@ function registerTools(server, ctx) {
|
|
|
49808
49917
|
"CUSTOM"
|
|
49809
49918
|
]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here — real-time workflows must be created via create_workflow."),
|
|
49810
49919
|
schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM update interval"),
|
|
49811
|
-
limit: exports_external.number().optional().describe("Maximum number of records to extract per run")
|
|
49920
|
+
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract per run")
|
|
49812
49921
|
}),
|
|
49813
49922
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
49814
49923
|
}, withErrorHandling("update_workflow", async (args) => {
|
|
@@ -49824,6 +49933,9 @@ function registerTools(server, ctx) {
|
|
|
49824
49933
|
return errorResult("Cannot change a real-time workflow's interval to a scheduled interval. " + "Real-time workflows are architecturally different and cannot be converted to regular workflows. " + "Delete this workflow and create a new one with the desired interval using create_workflow.");
|
|
49825
49934
|
}
|
|
49826
49935
|
}
|
|
49936
|
+
if (updates.updateInterval === "CUSTOM" && (!updates.schedules || updates.schedules.length === 0)) {
|
|
49937
|
+
return errorResult("updateInterval='CUSTOM' requires at least one cron expression in the 'schedules' field (e.g. '0 8 * * 2' for Tuesdays at 8am UTC).");
|
|
49938
|
+
}
|
|
49827
49939
|
const result = await ctx.client.workflow.update(workflowId, updates);
|
|
49828
49940
|
return jsonResult({
|
|
49829
49941
|
success: true,
|
|
@@ -49894,7 +50006,7 @@ function registerTools(server, ctx) {
|
|
|
49894
50006
|
description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
|
|
49895
50007
|
inputSchema: {
|
|
49896
50008
|
channelId: exports_external.string().describe("The channel ID to delete"),
|
|
49897
|
-
confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
50009
|
+
confirmed: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
49898
50010
|
},
|
|
49899
50011
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
|
|
49900
50012
|
}, withErrorHandling("delete_notification_channel", async (args) => {
|
|
@@ -50032,7 +50144,7 @@ function registerTools(server, ctx) {
|
|
|
50032
50144
|
description: "Delete a notification setting by ID. This removes the event-to-channel mapping but does not delete the channels themselves. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
|
|
50033
50145
|
inputSchema: {
|
|
50034
50146
|
settingsId: exports_external.string().describe("The notification settings ID to delete"),
|
|
50035
|
-
confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
50147
|
+
confirmed: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
50036
50148
|
},
|
|
50037
50149
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
|
|
50038
50150
|
}, withErrorHandling("delete_notification_setting", async (args) => {
|
|
@@ -50097,7 +50209,7 @@ function registerTools(server, ctx) {
|
|
|
50097
50209
|
});
|
|
50098
50210
|
}));
|
|
50099
50211
|
}
|
|
50100
|
-
var SchemaFieldShape, DASHBOARD_BASE_URL = "https://
|
|
50212
|
+
var SchemaFieldShape, DASHBOARD_BASE_URL = "https://www.kadoa.com";
|
|
50101
50213
|
var init_tools = __esm(() => {
|
|
50102
50214
|
init_zod();
|
|
50103
50215
|
init_dist2();
|
|
@@ -54108,45 +54220,6 @@ function generatePKCE() {
|
|
|
54108
54220
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
54109
54221
|
return { verifier, challenge };
|
|
54110
54222
|
}
|
|
54111
|
-
async function refreshSupabaseToken(supabaseRefreshToken, context) {
|
|
54112
|
-
const inflight = supabaseRefreshMutex.get(supabaseRefreshToken);
|
|
54113
|
-
if (inflight) {
|
|
54114
|
-
console.error(`[AUTH] REFRESH_DEDUP: reusing in-flight refresh (${context})`);
|
|
54115
|
-
return inflight;
|
|
54116
|
-
}
|
|
54117
|
-
const promise3 = (async () => {
|
|
54118
|
-
const supabaseUrl = process.env.SUPABASE_URL;
|
|
54119
|
-
if (!supabaseUrl || !supabaseRefreshToken)
|
|
54120
|
-
return null;
|
|
54121
|
-
try {
|
|
54122
|
-
const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
54123
|
-
method: "POST",
|
|
54124
|
-
headers: {
|
|
54125
|
-
"Content-Type": "application/json",
|
|
54126
|
-
apikey: process.env.SUPABASE_ANON_KEY
|
|
54127
|
-
},
|
|
54128
|
-
body: JSON.stringify({ refresh_token: supabaseRefreshToken })
|
|
54129
|
-
});
|
|
54130
|
-
if (res.ok) {
|
|
54131
|
-
const data = await res.json();
|
|
54132
|
-
const newClaims = jwtClaims(data.access_token);
|
|
54133
|
-
console.log(`[AUTH] REFRESH_OK: Supabase JWT refreshed (${context}, newEmail=${newClaims.email})`);
|
|
54134
|
-
return { jwt: data.access_token, refreshToken: data.refresh_token };
|
|
54135
|
-
}
|
|
54136
|
-
const body = await res.text().catch(() => "");
|
|
54137
|
-
console.error(`[AUTH] REFRESH_FAIL: Supabase returned ${res.status} (${context}): ${body.slice(0, 200)}`);
|
|
54138
|
-
return null;
|
|
54139
|
-
} catch (err) {
|
|
54140
|
-
console.error(`[AUTH] REFRESH_FAIL: Supabase refresh threw (${context}):`, err);
|
|
54141
|
-
return null;
|
|
54142
|
-
}
|
|
54143
|
-
})();
|
|
54144
|
-
supabaseRefreshMutex.set(supabaseRefreshToken, promise3);
|
|
54145
|
-
promise3.finally(() => {
|
|
54146
|
-
supabaseRefreshMutex.delete(supabaseRefreshToken);
|
|
54147
|
-
});
|
|
54148
|
-
return promise3;
|
|
54149
|
-
}
|
|
54150
54223
|
function jwtClaims(jwt2) {
|
|
54151
54224
|
try {
|
|
54152
54225
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -54454,12 +54527,22 @@ class KadoaOAuthProvider {
|
|
|
54454
54527
|
let { supabaseJwt, supabaseRefreshToken } = entry;
|
|
54455
54528
|
const claims = jwtClaims(entry.supabaseJwt);
|
|
54456
54529
|
const context = `email=${claims.email}, team=${entry.teamId}`;
|
|
54457
|
-
|
|
54458
|
-
|
|
54459
|
-
|
|
54460
|
-
|
|
54461
|
-
|
|
54462
|
-
|
|
54530
|
+
try {
|
|
54531
|
+
const { refreshSupabaseJwtRaw: refreshSupabaseJwtRaw2, SessionExpiredError: SessionExpiredError2 } = await Promise.resolve().then(() => (init_client2(), exports_client));
|
|
54532
|
+
const refreshed = await refreshSupabaseJwtRaw2(supabaseRefreshToken);
|
|
54533
|
+
if (refreshed) {
|
|
54534
|
+
supabaseJwt = refreshed.jwt;
|
|
54535
|
+
supabaseRefreshToken = refreshed.refreshToken;
|
|
54536
|
+
console.error(`[AUTH] REFRESH_OK: Supabase JWT refreshed (${context})`);
|
|
54537
|
+
} else {
|
|
54538
|
+
console.error(`[AUTH] REFRESH_WARN: using stale Supabase JWT as fallback (${context})`);
|
|
54539
|
+
}
|
|
54540
|
+
} catch (error48) {
|
|
54541
|
+
if (error48 instanceof Error && error48.name === "SessionExpiredError") {
|
|
54542
|
+
console.error(`[AUTH] REFRESH_DEAD: session permanently expired (${context}): ${error48.message}`);
|
|
54543
|
+
throw new InvalidTokenError("Supabase session expired. Please re-authenticate.");
|
|
54544
|
+
}
|
|
54545
|
+
console.error(`[AUTH] REFRESH_WARN: unexpected error, using stale JWT (${context}):`, error48);
|
|
54463
54546
|
}
|
|
54464
54547
|
const freshClaims = jwtClaims(supabaseJwt);
|
|
54465
54548
|
const teamId = freshClaims.activeTeamId ?? entry.teamId;
|
|
@@ -54586,7 +54669,7 @@ class KadoaOAuthProvider {
|
|
|
54586
54669
|
codeChallenge: pending.params.codeChallenge,
|
|
54587
54670
|
clientId: pending.client.client_id,
|
|
54588
54671
|
redirectUri: pending.params.redirectUri,
|
|
54589
|
-
expiresAt: Date.now() +
|
|
54672
|
+
expiresAt: Date.now() + 600000
|
|
54590
54673
|
}, 600);
|
|
54591
54674
|
const redirectUrl = new URL(pending.params.redirectUri);
|
|
54592
54675
|
redirectUrl.searchParams.set("code", mcpCode);
|
|
@@ -55015,12 +55098,11 @@ function renderLoginPage(state, error48) {
|
|
|
55015
55098
|
function escapeHtml(str) {
|
|
55016
55099
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
55017
55100
|
}
|
|
55018
|
-
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL
|
|
55101
|
+
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL;
|
|
55019
55102
|
var init_auth2 = __esm(() => {
|
|
55020
55103
|
init_errors4();
|
|
55021
55104
|
TEAM_SELECTION_TTL = 10 * 60 * 1000;
|
|
55022
55105
|
ACCESS_TOKEN_TTL = 7 * 24 * 3600;
|
|
55023
|
-
supabaseRefreshMutex = new Map;
|
|
55024
55106
|
});
|
|
55025
55107
|
|
|
55026
55108
|
// src/redis-store.ts
|
|
@@ -55213,6 +55295,39 @@ async function startHttpServer(options) {
|
|
|
55213
55295
|
return;
|
|
55214
55296
|
}
|
|
55215
55297
|
const identity = `jwt:${auth.userId.slice(0, 8)}...:team=${auth.teamId.slice(0, 8)}...`;
|
|
55298
|
+
if (isJwtExpired(auth.jwt)) {
|
|
55299
|
+
try {
|
|
55300
|
+
const refreshed = await refreshSupabaseJwtRaw(auth.refreshToken);
|
|
55301
|
+
if (refreshed) {
|
|
55302
|
+
auth.jwt = refreshed.jwt;
|
|
55303
|
+
auth.refreshToken = refreshed.refreshToken;
|
|
55304
|
+
const entry = await store.get("access_tokens", auth.mcpToken);
|
|
55305
|
+
if (entry) {
|
|
55306
|
+
const remainingMs = entry.expiresAt - Date.now();
|
|
55307
|
+
if (remainingMs > 0) {
|
|
55308
|
+
await store.set("access_tokens", auth.mcpToken, {
|
|
55309
|
+
...entry,
|
|
55310
|
+
supabaseJwt: refreshed.jwt,
|
|
55311
|
+
supabaseRefreshToken: refreshed.refreshToken
|
|
55312
|
+
}, Math.ceil(remainingMs / 1000));
|
|
55313
|
+
console.error(`[PROACTIVE_REFRESH] OK: JWT refreshed on ${method} (${identity})`);
|
|
55314
|
+
}
|
|
55315
|
+
}
|
|
55316
|
+
}
|
|
55317
|
+
} catch (error48) {
|
|
55318
|
+
if (error48 instanceof SessionExpiredError) {
|
|
55319
|
+
console.error(`[PROACTIVE_REFRESH] Session dead on ${method} (${identity}): ${error48.message}`);
|
|
55320
|
+
await store.del("access_tokens", auth.mcpToken);
|
|
55321
|
+
res.status(401).json({
|
|
55322
|
+
jsonrpc: "2.0",
|
|
55323
|
+
error: { code: -32001, message: error48.message },
|
|
55324
|
+
id: req.body?.id ?? null
|
|
55325
|
+
});
|
|
55326
|
+
return;
|
|
55327
|
+
}
|
|
55328
|
+
console.error(`[PROACTIVE_REFRESH] WARN: refresh failed on ${method}, continuing with stale JWT`, error48);
|
|
55329
|
+
}
|
|
55330
|
+
}
|
|
55216
55331
|
try {
|
|
55217
55332
|
console.error(`[MCP] POST method=${method} auth=${identity}`);
|
|
55218
55333
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -55290,6 +55405,7 @@ var init_http2 = __esm(async () => {
|
|
|
55290
55405
|
init_bearerAuth();
|
|
55291
55406
|
init_auth2();
|
|
55292
55407
|
init_redis_store();
|
|
55408
|
+
init_client2();
|
|
55293
55409
|
await init_src();
|
|
55294
55410
|
});
|
|
55295
55411
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kadoa/mcp",
|
|
3
|
-
"version": "0.3.10-rc.
|
|
3
|
+
"version": "0.3.10-rc.3",
|
|
4
4
|
"description": "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "bun run check-types && bun run test:unit && bun run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@kadoa/node-sdk": "^0.
|
|
27
|
+
"@kadoa/node-sdk": "^0.23.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
29
29
|
"express": "^5.2.1",
|
|
30
30
|
"ioredis": "^5.6.1",
|