@kadoa/mcp 0.3.6-rc.4 → 0.3.6-rc.5
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 +166 -62
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -54028,8 +54028,27 @@ async function setActiveTeamAndRefresh(jwt2, refreshToken, teamId) {
|
|
|
54028
54028
|
}
|
|
54029
54029
|
|
|
54030
54030
|
class KadoaOAuthProvider {
|
|
54031
|
+
store;
|
|
54032
|
+
constructor(store) {
|
|
54033
|
+
this.store = store;
|
|
54034
|
+
}
|
|
54031
54035
|
get clientsStore() {
|
|
54032
|
-
|
|
54036
|
+
const store = this.store;
|
|
54037
|
+
return {
|
|
54038
|
+
async getClient(clientId) {
|
|
54039
|
+
return store.get("clients", clientId);
|
|
54040
|
+
},
|
|
54041
|
+
async registerClient(client) {
|
|
54042
|
+
const clientId = randomToken(16);
|
|
54043
|
+
const full = {
|
|
54044
|
+
...client,
|
|
54045
|
+
client_id: clientId,
|
|
54046
|
+
client_id_issued_at: Math.floor(Date.now() / 1000)
|
|
54047
|
+
};
|
|
54048
|
+
await store.set("clients", clientId, full, 2592000);
|
|
54049
|
+
return full;
|
|
54050
|
+
}
|
|
54051
|
+
};
|
|
54033
54052
|
}
|
|
54034
54053
|
async authorize(client, params, res) {
|
|
54035
54054
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
@@ -54039,16 +54058,16 @@ class KadoaOAuthProvider {
|
|
|
54039
54058
|
}
|
|
54040
54059
|
const state = randomToken();
|
|
54041
54060
|
const { verifier, challenge } = generatePKCE();
|
|
54042
|
-
|
|
54061
|
+
await this.store.set("pending_auths", state, {
|
|
54043
54062
|
client,
|
|
54044
54063
|
params,
|
|
54045
54064
|
supabaseCodeVerifier: verifier
|
|
54046
|
-
});
|
|
54065
|
+
}, 600);
|
|
54047
54066
|
res.type("html").send(renderLoginPage(state));
|
|
54048
54067
|
}
|
|
54049
54068
|
async handleGoogleLogin(req, res) {
|
|
54050
54069
|
const { state } = req.body;
|
|
54051
|
-
const pending =
|
|
54070
|
+
const pending = await this.store.get("pending_auths", state);
|
|
54052
54071
|
if (!pending) {
|
|
54053
54072
|
res.status(400).send("Unknown or expired state parameter");
|
|
54054
54073
|
return;
|
|
@@ -54073,7 +54092,7 @@ class KadoaOAuthProvider {
|
|
|
54073
54092
|
res.status(400).send("Missing required fields");
|
|
54074
54093
|
return;
|
|
54075
54094
|
}
|
|
54076
|
-
const pending =
|
|
54095
|
+
const pending = await this.store.get("pending_auths", state);
|
|
54077
54096
|
if (!pending) {
|
|
54078
54097
|
res.status(400).type("html").send(renderLoginPage(state, "Session expired — please try again"));
|
|
54079
54098
|
return;
|
|
@@ -54099,7 +54118,7 @@ class KadoaOAuthProvider {
|
|
|
54099
54118
|
return;
|
|
54100
54119
|
}
|
|
54101
54120
|
const data = await tokenRes.json();
|
|
54102
|
-
|
|
54121
|
+
await this.store.del("pending_auths", state);
|
|
54103
54122
|
await this.completeAuthWithTokens(pending, res, data.access_token, data.refresh_token);
|
|
54104
54123
|
} catch (error48) {
|
|
54105
54124
|
console.error("Email/password login error:", error48);
|
|
@@ -54112,7 +54131,7 @@ class KadoaOAuthProvider {
|
|
|
54112
54131
|
res.status(400).send("Missing required fields");
|
|
54113
54132
|
return;
|
|
54114
54133
|
}
|
|
54115
|
-
const pending =
|
|
54134
|
+
const pending = await this.store.get("pending_auths", state);
|
|
54116
54135
|
if (!pending) {
|
|
54117
54136
|
res.status(400).type("html").send(renderLoginPage(state, "Session expired — please try again"));
|
|
54118
54137
|
return;
|
|
@@ -54160,7 +54179,7 @@ class KadoaOAuthProvider {
|
|
|
54160
54179
|
const teams = await fetchUserTeams(supabaseJwt);
|
|
54161
54180
|
if (teams.length === 1) {
|
|
54162
54181
|
const refreshed = await setActiveTeamAndRefresh(supabaseJwt, supabaseRefreshToken, teams[0].id);
|
|
54163
|
-
this.completeAuthFlow(pending, res, {
|
|
54182
|
+
await this.completeAuthFlow(pending, res, {
|
|
54164
54183
|
jwt: refreshed.jwt,
|
|
54165
54184
|
refreshToken: refreshed.refreshToken,
|
|
54166
54185
|
teamId: teams[0].id
|
|
@@ -54168,27 +54187,27 @@ class KadoaOAuthProvider {
|
|
|
54168
54187
|
return;
|
|
54169
54188
|
}
|
|
54170
54189
|
const selectionToken = randomToken();
|
|
54171
|
-
|
|
54190
|
+
await this.store.set("pending_team_selections", selectionToken, {
|
|
54172
54191
|
supabaseJwt,
|
|
54173
54192
|
supabaseRefreshToken,
|
|
54174
54193
|
teams,
|
|
54175
54194
|
pending,
|
|
54176
54195
|
expiresAt: Date.now() + TEAM_SELECTION_TTL
|
|
54177
|
-
});
|
|
54196
|
+
}, 600);
|
|
54178
54197
|
res.type("html").send(renderTeamSelectionPage(teams, selectionToken));
|
|
54179
54198
|
}
|
|
54180
54199
|
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
54181
|
-
const entry =
|
|
54200
|
+
const entry = await this.store.get("auth_codes", authorizationCode);
|
|
54182
54201
|
if (!entry)
|
|
54183
54202
|
throw new Error("Unknown authorization code");
|
|
54184
54203
|
return entry.codeChallenge;
|
|
54185
54204
|
}
|
|
54186
54205
|
async exchangeAuthorizationCode(_client, authorizationCode, _codeVerifier, redirectUri) {
|
|
54187
|
-
const entry =
|
|
54206
|
+
const entry = await this.store.get("auth_codes", authorizationCode);
|
|
54188
54207
|
if (!entry)
|
|
54189
54208
|
throw new Error("Unknown authorization code");
|
|
54190
54209
|
if (entry.expiresAt < Date.now()) {
|
|
54191
|
-
|
|
54210
|
+
await this.store.del("auth_codes", authorizationCode);
|
|
54192
54211
|
throw new Error("Authorization code expired");
|
|
54193
54212
|
}
|
|
54194
54213
|
if (redirectUri && redirectUri !== entry.redirectUri) {
|
|
@@ -54197,22 +54216,23 @@ class KadoaOAuthProvider {
|
|
|
54197
54216
|
const accessToken = randomToken();
|
|
54198
54217
|
const refreshToken = randomToken();
|
|
54199
54218
|
const expiresAt = Date.now() + ACCESS_TOKEN_TTL * 1000;
|
|
54200
|
-
|
|
54219
|
+
await this.store.set("access_tokens", accessToken, {
|
|
54201
54220
|
supabaseJwt: entry.supabaseJwt,
|
|
54202
54221
|
supabaseRefreshToken: entry.supabaseRefreshToken,
|
|
54203
54222
|
teamId: entry.teamId,
|
|
54204
54223
|
clientId: entry.clientId,
|
|
54205
54224
|
expiresAt
|
|
54206
|
-
});
|
|
54207
|
-
|
|
54225
|
+
}, ACCESS_TOKEN_TTL);
|
|
54226
|
+
await this.store.set("refresh_tokens", refreshToken, {
|
|
54208
54227
|
supabaseJwt: entry.supabaseJwt,
|
|
54209
54228
|
supabaseRefreshToken: entry.supabaseRefreshToken,
|
|
54210
54229
|
teamId: entry.teamId,
|
|
54211
54230
|
clientId: entry.clientId
|
|
54212
|
-
});
|
|
54213
|
-
|
|
54231
|
+
}, 2592000);
|
|
54232
|
+
await this.store.del("auth_codes", authorizationCode);
|
|
54214
54233
|
const claims = jwtClaims(entry.supabaseJwt);
|
|
54215
|
-
|
|
54234
|
+
const sessionCount = await this.store.size("access_tokens");
|
|
54235
|
+
console.log(`[AUTH] LOGIN: tokens issued (email=${claims.email}, team=${entry.teamId}, token=${accessToken.slice(0, 12)}..., ttl=${ACCESS_TOKEN_TTL}s, active_sessions=${sessionCount})`);
|
|
54216
54236
|
return {
|
|
54217
54237
|
access_token: accessToken,
|
|
54218
54238
|
token_type: "bearer",
|
|
@@ -54221,12 +54241,13 @@ class KadoaOAuthProvider {
|
|
|
54221
54241
|
};
|
|
54222
54242
|
}
|
|
54223
54243
|
async exchangeRefreshToken(_client, refreshToken) {
|
|
54224
|
-
const entry =
|
|
54244
|
+
const entry = await this.store.get("refresh_tokens", refreshToken);
|
|
54225
54245
|
if (!entry) {
|
|
54226
|
-
|
|
54246
|
+
const sessionCount = await this.store.size("refresh_tokens");
|
|
54247
|
+
console.error(`[AUTH] REFRESH_FAIL: unknown refresh token (token=${refreshToken.slice(0, 12)}..., active_sessions=${sessionCount})`);
|
|
54227
54248
|
throw new InvalidTokenError("Unknown or expired refresh token");
|
|
54228
54249
|
}
|
|
54229
|
-
|
|
54250
|
+
await this.store.del("refresh_tokens", refreshToken);
|
|
54230
54251
|
let { supabaseJwt, supabaseRefreshToken } = entry;
|
|
54231
54252
|
try {
|
|
54232
54253
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
@@ -54258,19 +54279,19 @@ class KadoaOAuthProvider {
|
|
|
54258
54279
|
const newAccessToken = randomToken();
|
|
54259
54280
|
const newRefreshToken = randomToken();
|
|
54260
54281
|
const expiresAt = Date.now() + ACCESS_TOKEN_TTL * 1000;
|
|
54261
|
-
|
|
54282
|
+
await this.store.set("access_tokens", newAccessToken, {
|
|
54262
54283
|
supabaseJwt,
|
|
54263
54284
|
supabaseRefreshToken,
|
|
54264
54285
|
teamId: entry.teamId,
|
|
54265
54286
|
clientId: entry.clientId,
|
|
54266
54287
|
expiresAt
|
|
54267
|
-
});
|
|
54268
|
-
|
|
54288
|
+
}, ACCESS_TOKEN_TTL);
|
|
54289
|
+
await this.store.set("refresh_tokens", newRefreshToken, {
|
|
54269
54290
|
supabaseJwt,
|
|
54270
54291
|
supabaseRefreshToken,
|
|
54271
54292
|
teamId: entry.teamId,
|
|
54272
54293
|
clientId: entry.clientId
|
|
54273
|
-
});
|
|
54294
|
+
}, 2592000);
|
|
54274
54295
|
return {
|
|
54275
54296
|
access_token: newAccessToken,
|
|
54276
54297
|
token_type: "bearer",
|
|
@@ -54288,16 +54309,17 @@ class KadoaOAuthProvider {
|
|
|
54288
54309
|
extra: { apiKey: token }
|
|
54289
54310
|
};
|
|
54290
54311
|
}
|
|
54291
|
-
const entry =
|
|
54312
|
+
const entry = await this.store.get("access_tokens", token);
|
|
54292
54313
|
if (!entry) {
|
|
54293
|
-
|
|
54314
|
+
const sessionCount = await this.store.size("access_tokens");
|
|
54315
|
+
console.error(`[AUTH] VERIFY_FAIL: unknown token (token=${token.slice(0, 12)}..., active_sessions=${sessionCount})`);
|
|
54294
54316
|
throw new InvalidTokenError("Unknown or expired access token");
|
|
54295
54317
|
}
|
|
54296
54318
|
if (entry.expiresAt < Date.now()) {
|
|
54297
54319
|
const expiredAgo = Math.round((Date.now() - entry.expiresAt) / 1000);
|
|
54298
54320
|
const claims = jwtClaims(entry.supabaseJwt);
|
|
54299
54321
|
console.error(`[AUTH] VERIFY_FAIL: token expired ${expiredAgo}s ago (email=${claims.email}, team=${entry.teamId}, token=${token.slice(0, 12)}...)`);
|
|
54300
|
-
|
|
54322
|
+
await this.store.del("access_tokens", token);
|
|
54301
54323
|
throw new InvalidTokenError("Access token expired");
|
|
54302
54324
|
}
|
|
54303
54325
|
return {
|
|
@@ -54318,12 +54340,12 @@ class KadoaOAuthProvider {
|
|
|
54318
54340
|
res.status(400).send("Missing code or mcp_state parameter");
|
|
54319
54341
|
return;
|
|
54320
54342
|
}
|
|
54321
|
-
const pending =
|
|
54343
|
+
const pending = await this.store.get("pending_auths", state);
|
|
54322
54344
|
if (!pending) {
|
|
54323
54345
|
res.status(400).send("Unknown or expired state parameter");
|
|
54324
54346
|
return;
|
|
54325
54347
|
}
|
|
54326
|
-
|
|
54348
|
+
await this.store.del("pending_auths", state);
|
|
54327
54349
|
try {
|
|
54328
54350
|
const supabaseTokens = await exchangeSupabaseCode(code, pending.supabaseCodeVerifier);
|
|
54329
54351
|
await this.completeAuthWithTokens(pending, res, supabaseTokens.accessToken, supabaseTokens.refreshToken);
|
|
@@ -54344,13 +54366,13 @@ class KadoaOAuthProvider {
|
|
|
54344
54366
|
res.status(400).send("Missing token or teamId");
|
|
54345
54367
|
return;
|
|
54346
54368
|
}
|
|
54347
|
-
const entry =
|
|
54369
|
+
const entry = await this.store.get("pending_team_selections", token);
|
|
54348
54370
|
if (!entry) {
|
|
54349
54371
|
res.status(400).send("Unknown or expired team selection token");
|
|
54350
54372
|
return;
|
|
54351
54373
|
}
|
|
54352
54374
|
if (entry.expiresAt < Date.now()) {
|
|
54353
|
-
|
|
54375
|
+
await this.store.del("pending_team_selections", token);
|
|
54354
54376
|
res.status(400).send("Team selection expired — please log in again");
|
|
54355
54377
|
return;
|
|
54356
54378
|
}
|
|
@@ -54358,10 +54380,10 @@ class KadoaOAuthProvider {
|
|
|
54358
54380
|
res.status(403).send("Invalid team selection");
|
|
54359
54381
|
return;
|
|
54360
54382
|
}
|
|
54361
|
-
|
|
54383
|
+
await this.store.del("pending_team_selections", token);
|
|
54362
54384
|
try {
|
|
54363
54385
|
const refreshed = await setActiveTeamAndRefresh(entry.supabaseJwt, entry.supabaseRefreshToken, teamId);
|
|
54364
|
-
this.completeAuthFlow(entry.pending, res, {
|
|
54386
|
+
await this.completeAuthFlow(entry.pending, res, {
|
|
54365
54387
|
jwt: refreshed.jwt,
|
|
54366
54388
|
refreshToken: refreshed.refreshToken,
|
|
54367
54389
|
teamId
|
|
@@ -54377,9 +54399,9 @@ class KadoaOAuthProvider {
|
|
|
54377
54399
|
res.redirect(redirectUrl.toString());
|
|
54378
54400
|
}
|
|
54379
54401
|
}
|
|
54380
|
-
completeAuthFlow(pending, res, credentials) {
|
|
54402
|
+
async completeAuthFlow(pending, res, credentials) {
|
|
54381
54403
|
const mcpCode = randomToken();
|
|
54382
|
-
|
|
54404
|
+
await this.store.set("auth_codes", mcpCode, {
|
|
54383
54405
|
supabaseJwt: credentials.jwt,
|
|
54384
54406
|
supabaseRefreshToken: credentials.refreshToken,
|
|
54385
54407
|
teamId: credentials.teamId,
|
|
@@ -54387,7 +54409,7 @@ class KadoaOAuthProvider {
|
|
|
54387
54409
|
clientId: pending.client.client_id,
|
|
54388
54410
|
redirectUri: pending.params.redirectUri,
|
|
54389
54411
|
expiresAt: Date.now() + 10 * 60 * 1000
|
|
54390
|
-
});
|
|
54412
|
+
}, 600);
|
|
54391
54413
|
const redirectUrl = new URL(pending.params.redirectUri);
|
|
54392
54414
|
redirectUrl.searchParams.set("code", mcpCode);
|
|
54393
54415
|
if (pending.params.state) {
|
|
@@ -54815,34 +54837,109 @@ function renderLoginPage(state, error48) {
|
|
|
54815
54837
|
function escapeHtml(str) {
|
|
54816
54838
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
54817
54839
|
}
|
|
54818
|
-
var
|
|
54840
|
+
var TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL;
|
|
54819
54841
|
var init_auth2 = __esm(() => {
|
|
54820
54842
|
init_errors4();
|
|
54821
|
-
clients = new Map;
|
|
54822
|
-
pendingAuths = new Map;
|
|
54823
|
-
pendingTeamSelections = new Map;
|
|
54824
|
-
authCodes = new Map;
|
|
54825
|
-
accessTokens = new Map;
|
|
54826
|
-
refreshTokens = new Map;
|
|
54827
54843
|
TEAM_SELECTION_TTL = 10 * 60 * 1000;
|
|
54828
54844
|
ACCESS_TOKEN_TTL = 7 * 24 * 3600;
|
|
54829
|
-
kadoaClientsStore = {
|
|
54830
|
-
getClient(clientId) {
|
|
54831
|
-
return clients.get(clientId);
|
|
54832
|
-
},
|
|
54833
|
-
registerClient(client) {
|
|
54834
|
-
const clientId = randomToken(16);
|
|
54835
|
-
const full = {
|
|
54836
|
-
...client,
|
|
54837
|
-
client_id: clientId,
|
|
54838
|
-
client_id_issued_at: Math.floor(Date.now() / 1000)
|
|
54839
|
-
};
|
|
54840
|
-
clients.set(clientId, full);
|
|
54841
|
-
return full;
|
|
54842
|
-
}
|
|
54843
|
-
};
|
|
54844
54845
|
});
|
|
54845
54846
|
|
|
54847
|
+
// src/redis-store.ts
|
|
54848
|
+
import Redis from "ioredis";
|
|
54849
|
+
|
|
54850
|
+
class RedisTokenStore {
|
|
54851
|
+
redis = null;
|
|
54852
|
+
fallback = new Map;
|
|
54853
|
+
constructor(redisHost) {
|
|
54854
|
+
const host = redisHost ?? process.env.REDIS_HOST;
|
|
54855
|
+
if (host) {
|
|
54856
|
+
const port = Number(process.env.REDIS_PORT) || 6379;
|
|
54857
|
+
console.error(`[Redis] Connecting to ${host}:${port}...`);
|
|
54858
|
+
this.redis = new Redis({
|
|
54859
|
+
host,
|
|
54860
|
+
port,
|
|
54861
|
+
maxRetriesPerRequest: 3,
|
|
54862
|
+
lazyConnect: true
|
|
54863
|
+
});
|
|
54864
|
+
this.redis.on("ready", () => {
|
|
54865
|
+
console.error(`[Redis] Connected to ${host}:${port}`);
|
|
54866
|
+
});
|
|
54867
|
+
this.redis.on("error", (err) => {
|
|
54868
|
+
console.error("[Redis] Error:", err.message);
|
|
54869
|
+
});
|
|
54870
|
+
this.redis.connect().catch((err) => {
|
|
54871
|
+
console.error("[Redis] Connection failed, falling back to in-memory:", err.message);
|
|
54872
|
+
this.redis?.disconnect();
|
|
54873
|
+
this.redis = null;
|
|
54874
|
+
});
|
|
54875
|
+
} else {
|
|
54876
|
+
console.error("[Redis] No REDIS_HOST set, using in-memory token store");
|
|
54877
|
+
}
|
|
54878
|
+
}
|
|
54879
|
+
get useRedis() {
|
|
54880
|
+
return this.redis?.status === "ready";
|
|
54881
|
+
}
|
|
54882
|
+
isConnected() {
|
|
54883
|
+
return this.useRedis;
|
|
54884
|
+
}
|
|
54885
|
+
async get(namespace, key) {
|
|
54886
|
+
const fullKey = `${KEY_PREFIX}:${namespace}:${key}`;
|
|
54887
|
+
const raw = this.useRedis ? await this.redis.get(fullKey) : this.fallback.get(fullKey);
|
|
54888
|
+
if (!raw)
|
|
54889
|
+
return;
|
|
54890
|
+
return JSON.parse(raw);
|
|
54891
|
+
}
|
|
54892
|
+
async set(namespace, key, value, ttlSeconds) {
|
|
54893
|
+
const fullKey = `${KEY_PREFIX}:${namespace}:${key}`;
|
|
54894
|
+
const json2 = JSON.stringify(value);
|
|
54895
|
+
if (this.useRedis) {
|
|
54896
|
+
if (ttlSeconds) {
|
|
54897
|
+
await this.redis.set(fullKey, json2, "EX", ttlSeconds);
|
|
54898
|
+
} else {
|
|
54899
|
+
await this.redis.set(fullKey, json2);
|
|
54900
|
+
}
|
|
54901
|
+
} else {
|
|
54902
|
+
this.fallback.set(fullKey, json2);
|
|
54903
|
+
}
|
|
54904
|
+
}
|
|
54905
|
+
async size(namespace) {
|
|
54906
|
+
const pattern = `${KEY_PREFIX}:${namespace}:*`;
|
|
54907
|
+
if (this.useRedis) {
|
|
54908
|
+
let count2 = 0;
|
|
54909
|
+
let cursor = "0";
|
|
54910
|
+
do {
|
|
54911
|
+
const [next, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
54912
|
+
cursor = next;
|
|
54913
|
+
count2 += keys.length;
|
|
54914
|
+
} while (cursor !== "0");
|
|
54915
|
+
return count2;
|
|
54916
|
+
}
|
|
54917
|
+
let count = 0;
|
|
54918
|
+
for (const key of this.fallback.keys()) {
|
|
54919
|
+
if (key.startsWith(`${KEY_PREFIX}:${namespace}:`))
|
|
54920
|
+
count++;
|
|
54921
|
+
}
|
|
54922
|
+
return count;
|
|
54923
|
+
}
|
|
54924
|
+
async del(namespace, key) {
|
|
54925
|
+
const fullKey = `${KEY_PREFIX}:${namespace}:${key}`;
|
|
54926
|
+
if (this.useRedis) {
|
|
54927
|
+
await this.redis.del(fullKey);
|
|
54928
|
+
} else {
|
|
54929
|
+
this.fallback.delete(fullKey);
|
|
54930
|
+
}
|
|
54931
|
+
}
|
|
54932
|
+
async disconnect() {
|
|
54933
|
+
if (this.redis) {
|
|
54934
|
+
console.error("[Redis] Disconnecting...");
|
|
54935
|
+
await this.redis.quit();
|
|
54936
|
+
this.redis = null;
|
|
54937
|
+
}
|
|
54938
|
+
}
|
|
54939
|
+
}
|
|
54940
|
+
var KEY_PREFIX = "kadoa-mcp";
|
|
54941
|
+
var init_redis_store = () => {};
|
|
54942
|
+
|
|
54846
54943
|
// src/http.ts
|
|
54847
54944
|
var exports_http = {};
|
|
54848
54945
|
__export(exports_http, {
|
|
@@ -54883,7 +54980,8 @@ async function startHttpServer() {
|
|
|
54883
54980
|
const app = createMcpExpressApp({ host: "0.0.0.0" });
|
|
54884
54981
|
app.set("trust proxy", 1);
|
|
54885
54982
|
const sessions = {};
|
|
54886
|
-
const
|
|
54983
|
+
const store = new RedisTokenStore;
|
|
54984
|
+
const provider = new KadoaOAuthProvider(store);
|
|
54887
54985
|
const serverUrl = process.env.MCP_SERVER_URL || `http://localhost:${port}`;
|
|
54888
54986
|
app.use(mcpAuthRouter({
|
|
54889
54987
|
provider,
|
|
@@ -54907,7 +55005,11 @@ async function startHttpServer() {
|
|
|
54907
55005
|
provider.handleTeamSelection(req, res);
|
|
54908
55006
|
});
|
|
54909
55007
|
app.get("/health", (_req, res) => {
|
|
54910
|
-
res.json({
|
|
55008
|
+
res.json({
|
|
55009
|
+
status: "ok",
|
|
55010
|
+
sessions: Object.keys(sessions).length,
|
|
55011
|
+
redis: store.isConnected() ? "connected" : "fallback"
|
|
55012
|
+
});
|
|
54911
55013
|
});
|
|
54912
55014
|
const bearerAuth = requireBearerAuth({ verifier: provider });
|
|
54913
55015
|
app.post("/mcp", bearerAuth, async (req, res) => {
|
|
@@ -55015,6 +55117,7 @@ async function startHttpServer() {
|
|
|
55015
55117
|
await sessions[sid].transport.close();
|
|
55016
55118
|
delete sessions[sid];
|
|
55017
55119
|
}
|
|
55120
|
+
await store.disconnect();
|
|
55018
55121
|
httpServer.close();
|
|
55019
55122
|
process.exit(0);
|
|
55020
55123
|
};
|
|
@@ -55029,6 +55132,7 @@ var init_http2 = __esm(async () => {
|
|
|
55029
55132
|
init_bearerAuth();
|
|
55030
55133
|
init_types2();
|
|
55031
55134
|
init_auth2();
|
|
55135
|
+
init_redis_store();
|
|
55032
55136
|
await init_src();
|
|
55033
55137
|
});
|
|
55034
55138
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kadoa/mcp",
|
|
3
|
-
"version": "0.3.6-rc.
|
|
3
|
+
"version": "0.3.6-rc.5",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"lint:fix": "bunx biome check --write",
|
|
20
20
|
"dev": "bun src/index.ts",
|
|
21
21
|
"dev:http": "MCP_HTTP=1 bun src/index.ts",
|
|
22
|
-
"build": "bun build src/index.ts --outdir=dist --target=node --external express && node -e \"const f='dist/index.js';require('fs').writeFileSync(f,require('fs').readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))\"",
|
|
22
|
+
"build": "bun build src/index.ts --outdir=dist --target=node --external express --external ioredis && node -e \"const f='dist/index.js';require('fs').writeFileSync(f,require('fs').readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))\"",
|
|
23
23
|
"check-types": "tsc --noEmit",
|
|
24
24
|
"test": "BUN_TEST=1 bun test",
|
|
25
25
|
"test:unit": "BUN_TEST=1 bun test tests/unit --timeout=120000",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@kadoa/node-sdk": "^0.23.0",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
33
33
|
"express": "^5.2.1",
|
|
34
|
+
"ioredis": "^5.6.1",
|
|
34
35
|
"zod": "^4.3.6"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|