@kadoa/mcp 0.3.8 → 0.3.9-rc.1
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 +134 -72
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48958,13 +48958,37 @@ function createKadoaClient(auth) {
|
|
|
48958
48958
|
});
|
|
48959
48959
|
return client;
|
|
48960
48960
|
}
|
|
48961
|
-
var ctxRefreshMutex;
|
|
48961
|
+
var refreshRawMutex, ctxRefreshMutex;
|
|
48962
48962
|
var init_client = __esm(() => {
|
|
48963
48963
|
init_dist2();
|
|
48964
|
+
refreshRawMutex = new Map;
|
|
48964
48965
|
ctxRefreshMutex = new WeakMap;
|
|
48965
48966
|
});
|
|
48966
48967
|
|
|
48967
48968
|
// src/client.ts
|
|
48969
|
+
var exports_client = {};
|
|
48970
|
+
__export(exports_client, {
|
|
48971
|
+
refreshSupabaseJwtRaw: () => refreshSupabaseJwtRaw,
|
|
48972
|
+
refreshSupabaseJwt: () => refreshSupabaseJwt,
|
|
48973
|
+
isJwtExpired: () => isJwtExpired,
|
|
48974
|
+
getValidJwt: () => getValidJwt,
|
|
48975
|
+
decodeJwtClaims: () => decodeJwtClaims,
|
|
48976
|
+
createKadoaClient: () => createKadoaClient2,
|
|
48977
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
48978
|
+
KadoaSdkException: () => KadoaSdkException,
|
|
48979
|
+
KadoaClient: () => KadoaClient
|
|
48980
|
+
});
|
|
48981
|
+
function createKadoaClient2(auth) {
|
|
48982
|
+
const client = new KadoaClient({ bearerToken: auth.jwt });
|
|
48983
|
+
client.axiosInstance.interceptors.request.use((config2) => {
|
|
48984
|
+
config2.headers["x-kadoa-source"] = "mcp";
|
|
48985
|
+
if (auth.teamId) {
|
|
48986
|
+
config2.headers["x-team-id"] = auth.teamId;
|
|
48987
|
+
}
|
|
48988
|
+
return config2;
|
|
48989
|
+
});
|
|
48990
|
+
return client;
|
|
48991
|
+
}
|
|
48968
48992
|
function decodeJwtClaims(jwt2) {
|
|
48969
48993
|
try {
|
|
48970
48994
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -48986,36 +49010,57 @@ function isJwtExpired(jwt2) {
|
|
|
48986
49010
|
return true;
|
|
48987
49011
|
}
|
|
48988
49012
|
}
|
|
48989
|
-
async function
|
|
48990
|
-
|
|
48991
|
-
|
|
48992
|
-
|
|
49013
|
+
async function refreshSupabaseJwtRaw(supabaseRefreshToken) {
|
|
49014
|
+
const inflight = refreshRawMutex2.get(supabaseRefreshToken);
|
|
49015
|
+
if (inflight) {
|
|
49016
|
+
console.error(`[JWT_REFRESH] DEDUP: reusing in-flight raw refresh`);
|
|
49017
|
+
return inflight;
|
|
48993
49018
|
}
|
|
49019
|
+
const promise3 = _doRefreshRaw(supabaseRefreshToken).finally(() => {
|
|
49020
|
+
refreshRawMutex2.delete(supabaseRefreshToken);
|
|
49021
|
+
});
|
|
49022
|
+
refreshRawMutex2.set(supabaseRefreshToken, promise3);
|
|
49023
|
+
return promise3;
|
|
49024
|
+
}
|
|
49025
|
+
async function _doRefreshRaw(supabaseRefreshToken) {
|
|
48994
49026
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
48995
49027
|
if (!supabaseUrl) {
|
|
48996
49028
|
console.error("[JWT_REFRESH] SUPABASE_URL not set, cannot refresh");
|
|
49029
|
+
return null;
|
|
49030
|
+
}
|
|
49031
|
+
const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
49032
|
+
method: "POST",
|
|
49033
|
+
headers: {
|
|
49034
|
+
"Content-Type": "application/json",
|
|
49035
|
+
apikey: process.env.SUPABASE_ANON_KEY
|
|
49036
|
+
},
|
|
49037
|
+
body: JSON.stringify({ refresh_token: supabaseRefreshToken })
|
|
49038
|
+
});
|
|
49039
|
+
if (res.ok) {
|
|
49040
|
+
const data = await res.json();
|
|
49041
|
+
return { jwt: data.access_token, refreshToken: data.refresh_token };
|
|
49042
|
+
}
|
|
49043
|
+
const body = await res.text().catch(() => "");
|
|
49044
|
+
console.error(`[JWT_REFRESH] FAIL: Supabase returned ${res.status} (refreshToken=${supabaseRefreshToken.slice(0, 12)}...): ${body}`);
|
|
49045
|
+
if (body.includes("session_expired") || body.includes("refresh_token_not_found") || body.includes("refresh_token_already_used")) {
|
|
49046
|
+
throw new SessionExpiredError("Your Kadoa session has expired due to inactivity. Please reconnect to re-authenticate.");
|
|
49047
|
+
}
|
|
49048
|
+
return null;
|
|
49049
|
+
}
|
|
49050
|
+
async function refreshSupabaseJwt(ctx) {
|
|
49051
|
+
if (!ctx.supabaseRefreshToken) {
|
|
49052
|
+
console.error("[JWT_REFRESH] No refresh token available, cannot refresh");
|
|
48997
49053
|
return;
|
|
48998
49054
|
}
|
|
48999
49055
|
try {
|
|
49000
49056
|
const refreshToken = ctx.supabaseRefreshToken;
|
|
49001
49057
|
console.error(`[JWT_REFRESH] Refreshing Supabase JWT (refreshToken=${refreshToken.slice(0, 12)}..., team=${ctx.teamId ?? "unknown"})`);
|
|
49002
|
-
const
|
|
49003
|
-
|
|
49004
|
-
headers: {
|
|
49005
|
-
"Content-Type": "application/json",
|
|
49006
|
-
apikey: process.env.SUPABASE_ANON_KEY
|
|
49007
|
-
},
|
|
49008
|
-
body: JSON.stringify({ refresh_token: refreshToken })
|
|
49009
|
-
});
|
|
49010
|
-
if (!res.ok) {
|
|
49011
|
-
const body = await res.text().catch(() => "");
|
|
49012
|
-
console.error(`[JWT_REFRESH] FAIL: Supabase returned ${res.status} (refreshToken=${refreshToken.slice(0, 12)}...): ${body}`);
|
|
49058
|
+
const result = await refreshSupabaseJwtRaw(refreshToken);
|
|
49059
|
+
if (!result)
|
|
49013
49060
|
return;
|
|
49014
|
-
|
|
49015
|
-
|
|
49016
|
-
ctx.
|
|
49017
|
-
ctx.supabaseRefreshToken = data.refresh_token;
|
|
49018
|
-
ctx.client.setBearerToken(data.access_token);
|
|
49061
|
+
ctx.supabaseJwt = result.jwt;
|
|
49062
|
+
ctx.supabaseRefreshToken = result.refreshToken;
|
|
49063
|
+
ctx.client.setBearerToken(result.jwt);
|
|
49019
49064
|
try {
|
|
49020
49065
|
await ctx.persist?.({
|
|
49021
49066
|
supabaseJwt: ctx.supabaseJwt,
|
|
@@ -49025,9 +49070,11 @@ async function refreshSupabaseJwt(ctx) {
|
|
|
49025
49070
|
} catch (e) {
|
|
49026
49071
|
console.error("[JWT_REFRESH] WARN: persist failed, tokens updated in-memory only:", e);
|
|
49027
49072
|
}
|
|
49028
|
-
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${
|
|
49029
|
-
return
|
|
49073
|
+
console.error(`[JWT_REFRESH] OK: token refreshed (team=${ctx.teamId ?? "unknown"}, newRefreshToken=${result.refreshToken.slice(0, 12)}...)`);
|
|
49074
|
+
return result.jwt;
|
|
49030
49075
|
} catch (error48) {
|
|
49076
|
+
if (error48 instanceof SessionExpiredError)
|
|
49077
|
+
throw error48;
|
|
49031
49078
|
console.error("[JWT_REFRESH] FAIL: threw", error48);
|
|
49032
49079
|
return;
|
|
49033
49080
|
}
|
|
@@ -49048,9 +49095,16 @@ async function getValidJwt(ctx) {
|
|
|
49048
49095
|
ctxRefreshMutex2.set(ctx, promise3);
|
|
49049
49096
|
return promise3;
|
|
49050
49097
|
}
|
|
49051
|
-
var ctxRefreshMutex2;
|
|
49098
|
+
var SessionExpiredError, refreshRawMutex2, ctxRefreshMutex2;
|
|
49052
49099
|
var init_client2 = __esm(() => {
|
|
49053
49100
|
init_dist2();
|
|
49101
|
+
SessionExpiredError = class SessionExpiredError extends Error {
|
|
49102
|
+
constructor(message) {
|
|
49103
|
+
super(message ?? "Supabase session expired. Please re-authenticate.");
|
|
49104
|
+
this.name = "SessionExpiredError";
|
|
49105
|
+
}
|
|
49106
|
+
};
|
|
49107
|
+
refreshRawMutex2 = new Map;
|
|
49054
49108
|
ctxRefreshMutex2 = new WeakMap;
|
|
49055
49109
|
});
|
|
49056
49110
|
|
|
@@ -49168,6 +49222,10 @@ function registerTools(server, ctx) {
|
|
|
49168
49222
|
}
|
|
49169
49223
|
return await handler(...args);
|
|
49170
49224
|
} catch (error48) {
|
|
49225
|
+
if (error48 instanceof SessionExpiredError) {
|
|
49226
|
+
console.error(`[Tool Error] ${name}: session expired, user must re-authenticate`);
|
|
49227
|
+
return errorResult("Your session has expired. Please reconnect the MCP server to re-authenticate.");
|
|
49228
|
+
}
|
|
49171
49229
|
let message = classifyError(error48);
|
|
49172
49230
|
if (KadoaHttpException.isInstance(error48) && error48.httpStatus === 403) {
|
|
49173
49231
|
try {
|
|
@@ -53892,45 +53950,6 @@ function generatePKCE() {
|
|
|
53892
53950
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
53893
53951
|
return { verifier, challenge };
|
|
53894
53952
|
}
|
|
53895
|
-
async function refreshSupabaseToken(supabaseRefreshToken, context) {
|
|
53896
|
-
const inflight = supabaseRefreshMutex.get(supabaseRefreshToken);
|
|
53897
|
-
if (inflight) {
|
|
53898
|
-
console.error(`[AUTH] REFRESH_DEDUP: reusing in-flight refresh (${context})`);
|
|
53899
|
-
return inflight;
|
|
53900
|
-
}
|
|
53901
|
-
const promise3 = (async () => {
|
|
53902
|
-
const supabaseUrl = process.env.SUPABASE_URL;
|
|
53903
|
-
if (!supabaseUrl || !supabaseRefreshToken)
|
|
53904
|
-
return null;
|
|
53905
|
-
try {
|
|
53906
|
-
const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
53907
|
-
method: "POST",
|
|
53908
|
-
headers: {
|
|
53909
|
-
"Content-Type": "application/json",
|
|
53910
|
-
apikey: process.env.SUPABASE_ANON_KEY
|
|
53911
|
-
},
|
|
53912
|
-
body: JSON.stringify({ refresh_token: supabaseRefreshToken })
|
|
53913
|
-
});
|
|
53914
|
-
if (res.ok) {
|
|
53915
|
-
const data = await res.json();
|
|
53916
|
-
const newClaims = jwtClaims(data.access_token);
|
|
53917
|
-
console.log(`[AUTH] REFRESH_OK: Supabase JWT refreshed (${context}, newEmail=${newClaims.email})`);
|
|
53918
|
-
return { jwt: data.access_token, refreshToken: data.refresh_token };
|
|
53919
|
-
}
|
|
53920
|
-
const body = await res.text().catch(() => "");
|
|
53921
|
-
console.error(`[AUTH] REFRESH_FAIL: Supabase returned ${res.status} (${context}): ${body.slice(0, 200)}`);
|
|
53922
|
-
return null;
|
|
53923
|
-
} catch (err) {
|
|
53924
|
-
console.error(`[AUTH] REFRESH_FAIL: Supabase refresh threw (${context}):`, err);
|
|
53925
|
-
return null;
|
|
53926
|
-
}
|
|
53927
|
-
})();
|
|
53928
|
-
supabaseRefreshMutex.set(supabaseRefreshToken, promise3);
|
|
53929
|
-
promise3.finally(() => {
|
|
53930
|
-
supabaseRefreshMutex.delete(supabaseRefreshToken);
|
|
53931
|
-
});
|
|
53932
|
-
return promise3;
|
|
53933
|
-
}
|
|
53934
53953
|
function jwtClaims(jwt2) {
|
|
53935
53954
|
try {
|
|
53936
53955
|
const payload = JSON.parse(Buffer.from(jwt2.split(".")[1], "base64url").toString());
|
|
@@ -54238,12 +54257,22 @@ class KadoaOAuthProvider {
|
|
|
54238
54257
|
let { supabaseJwt, supabaseRefreshToken } = entry;
|
|
54239
54258
|
const claims = jwtClaims(entry.supabaseJwt);
|
|
54240
54259
|
const context = `email=${claims.email}, team=${entry.teamId}`;
|
|
54241
|
-
|
|
54242
|
-
|
|
54243
|
-
|
|
54244
|
-
|
|
54245
|
-
|
|
54246
|
-
|
|
54260
|
+
try {
|
|
54261
|
+
const { refreshSupabaseJwtRaw: refreshSupabaseJwtRaw2, SessionExpiredError: SessionExpiredError2 } = await Promise.resolve().then(() => (init_client2(), exports_client));
|
|
54262
|
+
const refreshed = await refreshSupabaseJwtRaw2(supabaseRefreshToken);
|
|
54263
|
+
if (refreshed) {
|
|
54264
|
+
supabaseJwt = refreshed.jwt;
|
|
54265
|
+
supabaseRefreshToken = refreshed.refreshToken;
|
|
54266
|
+
console.error(`[AUTH] REFRESH_OK: Supabase JWT refreshed (${context})`);
|
|
54267
|
+
} else {
|
|
54268
|
+
console.error(`[AUTH] REFRESH_WARN: using stale Supabase JWT as fallback (${context})`);
|
|
54269
|
+
}
|
|
54270
|
+
} catch (error48) {
|
|
54271
|
+
if (error48 instanceof Error && error48.name === "SessionExpiredError") {
|
|
54272
|
+
console.error(`[AUTH] REFRESH_DEAD: session permanently expired (${context}): ${error48.message}`);
|
|
54273
|
+
throw new InvalidTokenError("Supabase session expired. Please re-authenticate.");
|
|
54274
|
+
}
|
|
54275
|
+
console.error(`[AUTH] REFRESH_WARN: unexpected error, using stale JWT (${context}):`, error48);
|
|
54247
54276
|
}
|
|
54248
54277
|
const freshClaims = jwtClaims(supabaseJwt);
|
|
54249
54278
|
const teamId = freshClaims.activeTeamId ?? entry.teamId;
|
|
@@ -54370,7 +54399,7 @@ class KadoaOAuthProvider {
|
|
|
54370
54399
|
codeChallenge: pending.params.codeChallenge,
|
|
54371
54400
|
clientId: pending.client.client_id,
|
|
54372
54401
|
redirectUri: pending.params.redirectUri,
|
|
54373
|
-
expiresAt: Date.now() +
|
|
54402
|
+
expiresAt: Date.now() + 600000
|
|
54374
54403
|
}, 600);
|
|
54375
54404
|
const redirectUrl = new URL(pending.params.redirectUri);
|
|
54376
54405
|
redirectUrl.searchParams.set("code", mcpCode);
|
|
@@ -54799,12 +54828,11 @@ function renderLoginPage(state, error48) {
|
|
|
54799
54828
|
function escapeHtml(str) {
|
|
54800
54829
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
54801
54830
|
}
|
|
54802
|
-
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL
|
|
54831
|
+
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL;
|
|
54803
54832
|
var init_auth2 = __esm(() => {
|
|
54804
54833
|
init_errors4();
|
|
54805
54834
|
TEAM_SELECTION_TTL = 10 * 60 * 1000;
|
|
54806
54835
|
ACCESS_TOKEN_TTL = 7 * 24 * 3600;
|
|
54807
|
-
supabaseRefreshMutex = new Map;
|
|
54808
54836
|
});
|
|
54809
54837
|
|
|
54810
54838
|
// src/redis-store.ts
|
|
@@ -54997,6 +55025,39 @@ async function startHttpServer(options) {
|
|
|
54997
55025
|
return;
|
|
54998
55026
|
}
|
|
54999
55027
|
const identity = `jwt:${auth.userId.slice(0, 8)}...:team=${auth.teamId.slice(0, 8)}...`;
|
|
55028
|
+
if (isJwtExpired(auth.jwt)) {
|
|
55029
|
+
try {
|
|
55030
|
+
const refreshed = await refreshSupabaseJwtRaw(auth.refreshToken);
|
|
55031
|
+
if (refreshed) {
|
|
55032
|
+
auth.jwt = refreshed.jwt;
|
|
55033
|
+
auth.refreshToken = refreshed.refreshToken;
|
|
55034
|
+
const entry = await store.get("access_tokens", auth.mcpToken);
|
|
55035
|
+
if (entry) {
|
|
55036
|
+
const remainingMs = entry.expiresAt - Date.now();
|
|
55037
|
+
if (remainingMs > 0) {
|
|
55038
|
+
await store.set("access_tokens", auth.mcpToken, {
|
|
55039
|
+
...entry,
|
|
55040
|
+
supabaseJwt: refreshed.jwt,
|
|
55041
|
+
supabaseRefreshToken: refreshed.refreshToken
|
|
55042
|
+
}, Math.ceil(remainingMs / 1000));
|
|
55043
|
+
console.error(`[PROACTIVE_REFRESH] OK: JWT refreshed on ${method} (${identity})`);
|
|
55044
|
+
}
|
|
55045
|
+
}
|
|
55046
|
+
}
|
|
55047
|
+
} catch (error48) {
|
|
55048
|
+
if (error48 instanceof SessionExpiredError) {
|
|
55049
|
+
console.error(`[PROACTIVE_REFRESH] Session dead on ${method} (${identity}): ${error48.message}`);
|
|
55050
|
+
await store.del("access_tokens", auth.mcpToken);
|
|
55051
|
+
res.status(401).json({
|
|
55052
|
+
jsonrpc: "2.0",
|
|
55053
|
+
error: { code: -32001, message: error48.message },
|
|
55054
|
+
id: req.body?.id ?? null
|
|
55055
|
+
});
|
|
55056
|
+
return;
|
|
55057
|
+
}
|
|
55058
|
+
console.error(`[PROACTIVE_REFRESH] WARN: refresh failed on ${method}, continuing with stale JWT`, error48);
|
|
55059
|
+
}
|
|
55060
|
+
}
|
|
55000
55061
|
try {
|
|
55001
55062
|
console.error(`[MCP] POST method=${method} auth=${identity}`);
|
|
55002
55063
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -55074,6 +55135,7 @@ var init_http2 = __esm(async () => {
|
|
|
55074
55135
|
init_bearerAuth();
|
|
55075
55136
|
init_auth2();
|
|
55076
55137
|
init_redis_store();
|
|
55138
|
+
init_client2();
|
|
55077
55139
|
await init_src();
|
|
55078
55140
|
});
|
|
55079
55141
|
|