@productbrain/mcp 0.0.1-beta.9452 → 0.0.1-beta.9469

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli/index.ts
4
4
  var subcommand = process.argv[2];
5
5
  if (subcommand === "setup") {
6
- const { runSetup } = await import("../setup-6UYW6XKO.js");
6
+ const { runSetup } = await import("../setup-YETABTTN.js");
7
7
  await runSetup();
8
8
  } else {
9
9
  await import("../index.js");
package/dist/http.js CHANGED
@@ -2,19 +2,21 @@ import {
2
2
  SERVER_VERSION,
3
3
  createProductBrainServer,
4
4
  initFeatureFlags
5
- } from "./chunk-NCTCZQHK.js";
5
+ } from "./chunk-BCQ7KBLE.js";
6
6
  import {
7
7
  DEFAULT_CLOUD_URL,
8
8
  apiKeysUrl,
9
9
  bootstrapHttp,
10
+ checkKeyAgainstCandidates,
10
11
  forgetStreamLifecycle,
11
12
  getKeyState,
12
13
  getPostHogClient,
13
14
  hashKey,
14
15
  initAnalytics,
16
+ parseFallbackUrls,
15
17
  runWithAuth,
16
18
  shutdownAnalytics
17
- } from "./chunk-Q77SFDBZ.js";
19
+ } from "./chunk-FVH3B7MF.js";
18
20
 
19
21
  // src/http.ts
20
22
  import { createHash, randomUUID } from "crypto";
@@ -206,6 +208,14 @@ function recordAuthFailure(failures, ip, now) {
206
208
  }
207
209
  }
208
210
  }
211
+ function initializeKeyGate(verdict, failures, ip, now) {
212
+ if (verdict === "ok" || verdict === "rate_limited") return "admit";
213
+ if (verdict === "rejected") {
214
+ recordAuthFailure(failures, ip, now);
215
+ return "unauthorized";
216
+ }
217
+ return "unavailable";
218
+ }
209
219
 
210
220
  // src/lib/oauthRedirectAllowlist.ts
211
221
  var DEFAULT_OAUTH_REDIRECT_URIS = [
@@ -309,6 +319,10 @@ bootstrapHttp();
309
319
  initAnalytics();
310
320
  initFeatureFlags(getPostHogClient());
311
321
  var PORT = parseInt(process.env.PORT ?? process.env.MCP_PORT ?? "3002", 10);
322
+ function keyCheckCandidates() {
323
+ const primaryUrl = (process.env.CONVEX_SITE_URL ?? DEFAULT_CLOUD_URL).replace(/\/$/, "");
324
+ return [primaryUrl, ...parseFallbackUrls(process.env.CONVEX_FALLBACK_URLS).map((u) => u.replace(/\/$/, ""))];
325
+ }
312
326
  function baseUrl(req) {
313
327
  const proto = req.headers["x-forwarded-proto"] ?? req.protocol ?? "http";
314
328
  const host = req.headers.host ?? `localhost:${PORT}`;
@@ -1055,58 +1069,25 @@ app.post(
1055
1069
  return;
1056
1070
  }
1057
1071
  let workspaceName = "Your Workspace";
1058
- try {
1059
- const primaryUrl = (process.env.CONVEX_SITE_URL ?? DEFAULT_CLOUD_URL).replace(/\/$/, "");
1060
- const fallbackUrls = (process.env.CONVEX_FALLBACK_URLS ?? "").split(",").map((u) => u.trim().replace(/\/$/, "")).filter(Boolean);
1061
- const candidates = [primaryUrl, ...fallbackUrls];
1062
- let foundUrl;
1063
- let anyDefinitiveReject = false;
1064
- let rateLimitedRetryAfter;
1065
- for (const url2 of candidates) {
1066
- let checkData = null;
1067
- try {
1068
- const checkRes = await fetch(`${url2}/api/key-check`, {
1069
- method: "POST",
1070
- headers: { "Authorization": `Bearer ${api_key}`, "Content-Type": "application/json" },
1071
- signal: AbortSignal.timeout(5e3)
1072
- });
1073
- checkData = await checkRes.json();
1074
- } catch {
1075
- continue;
1076
- }
1077
- if (checkData.ok) {
1078
- if (checkData.workspaceName) workspaceName = checkData.workspaceName;
1079
- foundUrl = checkData.deploymentUrl ?? url2;
1080
- break;
1081
- }
1082
- if (checkData.error === "rate_limited") {
1083
- rateLimitedRetryAfter = checkData.retryAfterSeconds;
1084
- continue;
1085
- }
1086
- anyDefinitiveReject = true;
1087
- }
1088
- if (!foundUrl) {
1089
- if (rateLimitedRetryAfter != null && !anyDefinitiveReject) {
1090
- sendError(
1091
- "Rate limited",
1092
- `This API key is valid but temporarily rate limited. Try again in ${rateLimitedRetryAfter}s.`,
1093
- 429
1094
- );
1095
- return;
1096
- }
1097
- if (anyDefinitiveReject) {
1098
- sendError(
1099
- "Key not recognized",
1100
- "This API key wasn't found in Product Brain. Check your API Keys in Cortex and try again.",
1101
- 401
1102
- );
1103
- return;
1104
- }
1105
- process.stderr.write("[authorize] key-check unavailable \u2014 proceeding without validation\n");
1106
- } else {
1107
- getKeyState(api_key).deploymentUrl = foundUrl;
1108
- }
1109
- } catch {
1072
+ const check = await checkKeyAgainstCandidates(keyCheckCandidates(), api_key, 5e3);
1073
+ if (check.verdict === "ok") {
1074
+ if (check.workspaceName) workspaceName = check.workspaceName;
1075
+ getKeyState(api_key).deploymentUrl = check.deploymentUrl;
1076
+ } else if (check.verdict === "rate_limited") {
1077
+ sendError(
1078
+ "Rate limited",
1079
+ `This API key is valid but temporarily rate limited. Try again in ${check.retryAfterSeconds ?? 60}s.`,
1080
+ 429
1081
+ );
1082
+ return;
1083
+ } else if (check.verdict === "rejected") {
1084
+ sendError(
1085
+ "Key not recognized",
1086
+ "This API key wasn't found in Product Brain. Check your API Keys in Cortex and try again.",
1087
+ 401
1088
+ );
1089
+ return;
1090
+ } else {
1110
1091
  process.stderr.write("[authorize] key-check unavailable \u2014 proceeding without validation\n");
1111
1092
  }
1112
1093
  const code = randomUUID();
@@ -1286,6 +1267,22 @@ app.post("/mcp", mcpLimiter, async (req, res) => {
1286
1267
  entry.lastAccess = Date.now();
1287
1268
  }
1288
1269
  } else if (!sessionId && isInitializeRequest(req.body)) {
1270
+ const check = await checkKeyAgainstCandidates(keyCheckCandidates(), apiKey, 5e3);
1271
+ const gate = initializeKeyGate(check.verdict, authFailures, reqIp, Date.now());
1272
+ if (gate === "unauthorized") {
1273
+ logRequest("POST", "auth_fail");
1274
+ send401(req, res);
1275
+ return;
1276
+ }
1277
+ if (gate === "unavailable") {
1278
+ res.status(503).json({
1279
+ jsonrpc: "2.0",
1280
+ error: { code: -32e3, message: "Product Brain could not verify this API key right now. Retry shortly." },
1281
+ id: req.body?.id ?? null
1282
+ });
1283
+ return;
1284
+ }
1285
+ if (check.verdict === "ok" || check.verdict === "rate_limited") getKeyState(apiKey).deploymentUrl = check.deploymentUrl;
1289
1286
  const keyH = hashKey(apiKey);
1290
1287
  const plan = planKeyAdmission(
1291
1288
  sessions,