@kadoa/mcp 0.3.9-rc.2 → 0.3.9-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 +80 -135
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49167,37 +49167,13 @@ function createKadoaClient(auth) {
|
|
|
49167
49167
|
});
|
|
49168
49168
|
return client;
|
|
49169
49169
|
}
|
|
49170
|
-
var
|
|
49170
|
+
var ctxRefreshMutex;
|
|
49171
49171
|
var init_client = __esm(() => {
|
|
49172
49172
|
init_dist2();
|
|
49173
|
-
refreshRawMutex = new Map;
|
|
49174
49173
|
ctxRefreshMutex = new WeakMap;
|
|
49175
49174
|
});
|
|
49176
49175
|
|
|
49177
49176
|
// 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
|
-
}
|
|
49201
49177
|
function decodeJwtClaims(jwt2) {
|
|
49202
49178
|
try {
|
|
49203
49179
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -49219,57 +49195,36 @@ function isJwtExpired(jwt2) {
|
|
|
49219
49195
|
return true;
|
|
49220
49196
|
}
|
|
49221
49197
|
}
|
|
49222
|
-
async function
|
|
49223
|
-
|
|
49224
|
-
|
|
49225
|
-
|
|
49226
|
-
return inflight;
|
|
49198
|
+
async function refreshSupabaseJwt(ctx) {
|
|
49199
|
+
if (!ctx.supabaseRefreshToken) {
|
|
49200
|
+
console.error("[JWT_REFRESH] No refresh token available, cannot refresh");
|
|
49201
|
+
return;
|
|
49227
49202
|
}
|
|
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) {
|
|
49235
49203
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
49236
49204
|
if (!supabaseUrl) {
|
|
49237
49205
|
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");
|
|
49262
49206
|
return;
|
|
49263
49207
|
}
|
|
49264
49208
|
try {
|
|
49265
49209
|
const refreshToken = ctx.supabaseRefreshToken;
|
|
49266
49210
|
console.error(`[JWT_REFRESH] Refreshing Supabase JWT (refreshToken=${refreshToken.slice(0, 12)}..., team=${ctx.teamId ?? "unknown"})`);
|
|
49267
|
-
const
|
|
49268
|
-
|
|
49211
|
+
const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
49212
|
+
method: "POST",
|
|
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}`);
|
|
49269
49222
|
return;
|
|
49270
|
-
|
|
49271
|
-
|
|
49272
|
-
ctx.
|
|
49223
|
+
}
|
|
49224
|
+
const data = await res.json();
|
|
49225
|
+
ctx.supabaseJwt = data.access_token;
|
|
49226
|
+
ctx.supabaseRefreshToken = data.refresh_token;
|
|
49227
|
+
ctx.client.setBearerToken(data.access_token);
|
|
49273
49228
|
try {
|
|
49274
49229
|
await ctx.persist?.({
|
|
49275
49230
|
supabaseJwt: ctx.supabaseJwt,
|
|
@@ -49279,11 +49234,9 @@ async function refreshSupabaseJwt(ctx) {
|
|
|
49279
49234
|
} catch (e) {
|
|
49280
49235
|
console.error("[JWT_REFRESH] WARN: persist failed, tokens updated in-memory only:", e);
|
|
49281
49236
|
}
|
|
49282
|
-
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${
|
|
49283
|
-
return
|
|
49237
|
+
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${data.refresh_token.slice(0, 12)}...)`);
|
|
49238
|
+
return data.access_token;
|
|
49284
49239
|
} catch (error48) {
|
|
49285
|
-
if (error48 instanceof SessionExpiredError)
|
|
49286
|
-
throw error48;
|
|
49287
49240
|
console.error("[JWT_REFRESH] FAIL: threw", error48);
|
|
49288
49241
|
return;
|
|
49289
49242
|
}
|
|
@@ -49304,16 +49257,9 @@ async function getValidJwt(ctx) {
|
|
|
49304
49257
|
ctxRefreshMutex2.set(ctx, promise3);
|
|
49305
49258
|
return promise3;
|
|
49306
49259
|
}
|
|
49307
|
-
var
|
|
49260
|
+
var ctxRefreshMutex2;
|
|
49308
49261
|
var init_client2 = __esm(() => {
|
|
49309
49262
|
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;
|
|
49317
49263
|
ctxRefreshMutex2 = new WeakMap;
|
|
49318
49264
|
});
|
|
49319
49265
|
|
|
@@ -49338,6 +49284,9 @@ function coerceArray(wrapPlainString = false) {
|
|
|
49338
49284
|
function isRealTimeInterval(interval) {
|
|
49339
49285
|
return interval?.toUpperCase().replace("-", "_") === "REAL_TIME";
|
|
49340
49286
|
}
|
|
49287
|
+
function workflowDashboardUrl(workflowId) {
|
|
49288
|
+
return `${DASHBOARD_BASE_URL}/workflow/${workflowId}`;
|
|
49289
|
+
}
|
|
49341
49290
|
function jsonResult(data) {
|
|
49342
49291
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
49343
49292
|
}
|
|
@@ -49431,10 +49380,6 @@ function registerTools(server, ctx) {
|
|
|
49431
49380
|
}
|
|
49432
49381
|
return await handler(...args);
|
|
49433
49382
|
} catch (error48) {
|
|
49434
|
-
if (error48 instanceof SessionExpiredError) {
|
|
49435
|
-
console.error(`[Tool Error] ${name}: session expired, user must re-authenticate`);
|
|
49436
|
-
return errorResult("Your session has expired. Please reconnect the MCP server to re-authenticate.");
|
|
49437
|
-
}
|
|
49438
49383
|
let message = classifyError(error48);
|
|
49439
49384
|
if (KadoaHttpException.isInstance(error48) && error48.httpStatus === 403) {
|
|
49440
49385
|
try {
|
|
@@ -49636,6 +49581,7 @@ function registerTools(server, ctx) {
|
|
|
49636
49581
|
return jsonResult({
|
|
49637
49582
|
success: true,
|
|
49638
49583
|
workflowId: workflow.workflowId,
|
|
49584
|
+
dashboardUrl: workflowDashboardUrl(workflow.workflowId),
|
|
49639
49585
|
message
|
|
49640
49586
|
});
|
|
49641
49587
|
}));
|
|
@@ -49680,6 +49626,7 @@ function registerTools(server, ctx) {
|
|
|
49680
49626
|
return jsonResult({
|
|
49681
49627
|
success: true,
|
|
49682
49628
|
workflowId: workflow.workflowId,
|
|
49629
|
+
dashboardUrl: workflowDashboardUrl(workflow.workflowId),
|
|
49683
49630
|
message
|
|
49684
49631
|
});
|
|
49685
49632
|
}));
|
|
@@ -49699,6 +49646,7 @@ function registerTools(server, ctx) {
|
|
|
49699
49646
|
workflows: workflows.map((w) => ({
|
|
49700
49647
|
id: w.id,
|
|
49701
49648
|
name: w.name,
|
|
49649
|
+
dashboardUrl: workflowDashboardUrl(w.id),
|
|
49702
49650
|
status: w.displayState ?? w.state,
|
|
49703
49651
|
urls: w.urls,
|
|
49704
49652
|
totalRecords: w.totalRecords,
|
|
@@ -49721,6 +49669,7 @@ function registerTools(server, ctx) {
|
|
|
49721
49669
|
return jsonResult({
|
|
49722
49670
|
id: workflow.id,
|
|
49723
49671
|
name: workflow.name,
|
|
49672
|
+
dashboardUrl: workflowDashboardUrl(workflow.id),
|
|
49724
49673
|
description: workflow.description,
|
|
49725
49674
|
status: workflow.displayState ?? workflow.state,
|
|
49726
49675
|
urls: workflow.urls,
|
|
@@ -50148,7 +50097,7 @@ function registerTools(server, ctx) {
|
|
|
50148
50097
|
});
|
|
50149
50098
|
}));
|
|
50150
50099
|
}
|
|
50151
|
-
var SchemaFieldShape;
|
|
50100
|
+
var SchemaFieldShape, DASHBOARD_BASE_URL = "https://app.kadoa.com";
|
|
50152
50101
|
var init_tools = __esm(() => {
|
|
50153
50102
|
init_zod();
|
|
50154
50103
|
init_dist2();
|
|
@@ -54159,6 +54108,45 @@ function generatePKCE() {
|
|
|
54159
54108
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
54160
54109
|
return { verifier, challenge };
|
|
54161
54110
|
}
|
|
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
|
+
}
|
|
54162
54150
|
function jwtClaims(jwt2) {
|
|
54163
54151
|
try {
|
|
54164
54152
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -54466,22 +54454,12 @@ class KadoaOAuthProvider {
|
|
|
54466
54454
|
let { supabaseJwt, supabaseRefreshToken } = entry;
|
|
54467
54455
|
const claims = jwtClaims(entry.supabaseJwt);
|
|
54468
54456
|
const context = `email=${claims.email}, team=${entry.teamId}`;
|
|
54469
|
-
|
|
54470
|
-
|
|
54471
|
-
|
|
54472
|
-
|
|
54473
|
-
|
|
54474
|
-
|
|
54475
|
-
console.error(`[AUTH] REFRESH_OK: Supabase JWT refreshed (${context})`);
|
|
54476
|
-
} else {
|
|
54477
|
-
console.error(`[AUTH] REFRESH_WARN: using stale Supabase JWT as fallback (${context})`);
|
|
54478
|
-
}
|
|
54479
|
-
} catch (error48) {
|
|
54480
|
-
if (error48 instanceof Error && error48.name === "SessionExpiredError") {
|
|
54481
|
-
console.error(`[AUTH] REFRESH_DEAD: session permanently expired (${context}): ${error48.message}`);
|
|
54482
|
-
throw new InvalidTokenError("Supabase session expired. Please re-authenticate.");
|
|
54483
|
-
}
|
|
54484
|
-
console.error(`[AUTH] REFRESH_WARN: unexpected error, using stale JWT (${context}):`, error48);
|
|
54457
|
+
const refreshed = await refreshSupabaseToken(supabaseRefreshToken, context);
|
|
54458
|
+
if (refreshed) {
|
|
54459
|
+
supabaseJwt = refreshed.jwt;
|
|
54460
|
+
supabaseRefreshToken = refreshed.refreshToken;
|
|
54461
|
+
} else {
|
|
54462
|
+
console.error(`[AUTH] REFRESH_WARN: using stale Supabase JWT as fallback (${context})`);
|
|
54485
54463
|
}
|
|
54486
54464
|
const freshClaims = jwtClaims(supabaseJwt);
|
|
54487
54465
|
const teamId = freshClaims.activeTeamId ?? entry.teamId;
|
|
@@ -54608,7 +54586,7 @@ class KadoaOAuthProvider {
|
|
|
54608
54586
|
codeChallenge: pending.params.codeChallenge,
|
|
54609
54587
|
clientId: pending.client.client_id,
|
|
54610
54588
|
redirectUri: pending.params.redirectUri,
|
|
54611
|
-
expiresAt: Date.now() +
|
|
54589
|
+
expiresAt: Date.now() + 10 * 60 * 1000
|
|
54612
54590
|
}, 600);
|
|
54613
54591
|
const redirectUrl = new URL(pending.params.redirectUri);
|
|
54614
54592
|
redirectUrl.searchParams.set("code", mcpCode);
|
|
@@ -55037,11 +55015,12 @@ function renderLoginPage(state, error48) {
|
|
|
55037
55015
|
function escapeHtml(str) {
|
|
55038
55016
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
55039
55017
|
}
|
|
55040
|
-
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL;
|
|
55018
|
+
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL, supabaseRefreshMutex;
|
|
55041
55019
|
var init_auth2 = __esm(() => {
|
|
55042
55020
|
init_errors4();
|
|
55043
55021
|
TEAM_SELECTION_TTL = 10 * 60 * 1000;
|
|
55044
55022
|
ACCESS_TOKEN_TTL = 7 * 24 * 3600;
|
|
55023
|
+
supabaseRefreshMutex = new Map;
|
|
55045
55024
|
});
|
|
55046
55025
|
|
|
55047
55026
|
// src/redis-store.ts
|
|
@@ -55234,39 +55213,6 @@ async function startHttpServer(options) {
|
|
|
55234
55213
|
return;
|
|
55235
55214
|
}
|
|
55236
55215
|
const identity = `jwt:${auth.userId.slice(0, 8)}...:team=${auth.teamId.slice(0, 8)}...`;
|
|
55237
|
-
if (isJwtExpired(auth.jwt)) {
|
|
55238
|
-
try {
|
|
55239
|
-
const refreshed = await refreshSupabaseJwtRaw(auth.refreshToken);
|
|
55240
|
-
if (refreshed) {
|
|
55241
|
-
auth.jwt = refreshed.jwt;
|
|
55242
|
-
auth.refreshToken = refreshed.refreshToken;
|
|
55243
|
-
const entry = await store.get("access_tokens", auth.mcpToken);
|
|
55244
|
-
if (entry) {
|
|
55245
|
-
const remainingMs = entry.expiresAt - Date.now();
|
|
55246
|
-
if (remainingMs > 0) {
|
|
55247
|
-
await store.set("access_tokens", auth.mcpToken, {
|
|
55248
|
-
...entry,
|
|
55249
|
-
supabaseJwt: refreshed.jwt,
|
|
55250
|
-
supabaseRefreshToken: refreshed.refreshToken
|
|
55251
|
-
}, Math.ceil(remainingMs / 1000));
|
|
55252
|
-
console.error(`[PROACTIVE_REFRESH] OK: JWT refreshed on ${method} (${identity})`);
|
|
55253
|
-
}
|
|
55254
|
-
}
|
|
55255
|
-
}
|
|
55256
|
-
} catch (error48) {
|
|
55257
|
-
if (error48 instanceof SessionExpiredError) {
|
|
55258
|
-
console.error(`[PROACTIVE_REFRESH] Session dead on ${method} (${identity}): ${error48.message}`);
|
|
55259
|
-
await store.del("access_tokens", auth.mcpToken);
|
|
55260
|
-
res.status(401).json({
|
|
55261
|
-
jsonrpc: "2.0",
|
|
55262
|
-
error: { code: -32001, message: error48.message },
|
|
55263
|
-
id: req.body?.id ?? null
|
|
55264
|
-
});
|
|
55265
|
-
return;
|
|
55266
|
-
}
|
|
55267
|
-
console.error(`[PROACTIVE_REFRESH] WARN: refresh failed on ${method}, continuing with stale JWT`, error48);
|
|
55268
|
-
}
|
|
55269
|
-
}
|
|
55270
55216
|
try {
|
|
55271
55217
|
console.error(`[MCP] POST method=${method} auth=${identity}`);
|
|
55272
55218
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -55344,7 +55290,6 @@ var init_http2 = __esm(async () => {
|
|
|
55344
55290
|
init_bearerAuth();
|
|
55345
55291
|
init_auth2();
|
|
55346
55292
|
init_redis_store();
|
|
55347
|
-
init_client2();
|
|
55348
55293
|
await init_src();
|
|
55349
55294
|
});
|
|
55350
55295
|
|