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

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/http.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  SERVER_VERSION,
3
3
  createProductBrainServer,
4
4
  initFeatureFlags
5
- } from "./chunk-BCYOMOD2.js";
5
+ } from "./chunk-NCTCZQHK.js";
6
6
  import {
7
7
  DEFAULT_CLOUD_URL,
8
8
  apiKeysUrl,
@@ -14,10 +14,10 @@ import {
14
14
  initAnalytics,
15
15
  runWithAuth,
16
16
  shutdownAnalytics
17
- } from "./chunk-DKJ4X2D7.js";
17
+ } from "./chunk-Q77SFDBZ.js";
18
18
 
19
19
  // src/http.ts
20
- import { createHash, randomUUID as randomUUID2 } from "crypto";
20
+ import { createHash, randomUUID } from "crypto";
21
21
  import express from "express";
22
22
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
23
23
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
@@ -56,63 +56,6 @@ function appLogoMarkup(opts = {}) {
56
56
  return `<span class="${cls}"><span class="pb-logo__mark"><span class="pb-logo__core"></span></span>${wordmark}</span>`;
57
57
  }
58
58
 
59
- // src/lib/refresh-token.ts
60
- import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "crypto";
61
- var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 6e4;
62
- var PREFIX = "pb_rt_";
63
- var secret = (() => {
64
- const fromEnv = process.env.MCP_REFRESH_SECRET;
65
- if (fromEnv && fromEnv.length > 0) return Buffer.from(fromEnv, "utf8");
66
- if (process.env.NODE_ENV === "production") {
67
- console.warn(
68
- "[HTTP] WARNING MCP_REFRESH_SECRET not set \u2014 refresh tokens will not survive restart"
69
- );
70
- }
71
- return randomBytes(32);
72
- })();
73
- function sign(payloadB64) {
74
- return createHmac("sha256", secret).update(payloadB64).digest();
75
- }
76
- function signRefreshToken(apiKey) {
77
- const payload = {
78
- k: apiKey,
79
- i: Date.now(),
80
- j: randomUUID()
81
- };
82
- const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
83
- const sigB64 = sign(payloadB64).toString("base64url");
84
- return `${PREFIX}${payloadB64}.${sigB64}`;
85
- }
86
- function verifyRefreshToken(token) {
87
- if (typeof token !== "string" || !token.startsWith(PREFIX)) return null;
88
- const body = token.slice(PREFIX.length);
89
- const dot = body.indexOf(".");
90
- if (dot <= 0 || dot === body.length - 1) return null;
91
- const payloadB64 = body.slice(0, dot);
92
- const sigB64 = body.slice(dot + 1);
93
- let providedSig;
94
- try {
95
- providedSig = Buffer.from(sigB64, "base64url");
96
- } catch {
97
- return null;
98
- }
99
- const expectedSig = sign(payloadB64);
100
- if (providedSig.length !== expectedSig.length) return null;
101
- if (!timingSafeEqual(providedSig, expectedSig)) return null;
102
- let payload;
103
- try {
104
- const json = Buffer.from(payloadB64, "base64url").toString("utf8");
105
- payload = JSON.parse(json);
106
- } catch {
107
- return null;
108
- }
109
- if (!payload || typeof payload.k !== "string" || typeof payload.i !== "number" || typeof payload.j !== "string") {
110
- return null;
111
- }
112
- if (Date.now() - payload.i > REFRESH_TOKEN_TTL_MS) return null;
113
- return { apiKey: payload.k };
114
- }
115
-
116
59
  // src/sessionCap.ts
117
60
  function planKeyAdmission(sessions2, keyHash, cap, now, opts) {
118
61
  if (cap < 1) {
@@ -225,7 +168,7 @@ var AUTH_FAILURE_MAX = 10;
225
168
  var AUTH_FAILURE_WINDOW_MS = 5 * 6e4;
226
169
  var AUTH_BLOCK_DURATION_MS = 15 * 6e4;
227
170
  var MAX_AUTH_FAILURE_ENTRIES = 1e4;
228
- function resolveBearerAuth(authorizationHeader, accessTokens2, now, accessTokenTtlMs) {
171
+ function resolveBearerAuth(authorizationHeader) {
229
172
  if (typeof authorizationHeader !== "string" || authorizationHeader.trim() === "") {
230
173
  return { status: "absent" };
231
174
  }
@@ -239,15 +182,6 @@ function resolveBearerAuth(authorizationHeader, accessTokens2, now, accessTokenT
239
182
  if (token.startsWith("pb_sk_")) {
240
183
  return { status: "ok", apiKey: token };
241
184
  }
242
- if (token.startsWith("pb_at_")) {
243
- const entry = accessTokens2.get(token);
244
- if (!entry) return { status: "rejected" };
245
- if (now - entry.createdAt > accessTokenTtlMs) {
246
- accessTokens2.delete(token);
247
- return { status: "rejected" };
248
- }
249
- return { status: "ok", apiKey: entry.apiKey };
250
- }
251
185
  return { status: "rejected" };
252
186
  }
253
187
  function checkAuthBlock(failures, ip, now) {
@@ -361,6 +295,14 @@ function pendingCodeRefusal(pending, redirectUri, now) {
361
295
  if (now > pending.expiresAt) return "expired";
362
296
  return null;
363
297
  }
298
+ function tokenResponse(apiKey) {
299
+ return {
300
+ access_token: apiKey,
301
+ token_type: "Bearer",
302
+ // 1 year: validity is enforced by Convex on every call, not by this clock.
303
+ expires_in: 365 * 24 * 3600
304
+ };
305
+ }
364
306
 
365
307
  // src/http.ts
366
308
  bootstrapHttp();
@@ -411,7 +353,8 @@ app.get("/.well-known/oauth-authorization-server", (req, res) => {
411
353
  token_endpoint: `${base}/oauth/token`,
412
354
  registration_endpoint: `${base}/register`,
413
355
  response_types_supported: ["code"],
414
- grant_types_supported: ["authorization_code", "refresh_token"],
356
+ grant_types_supported: ["authorization_code"],
357
+ // TEN-4548: no refresh grant (lib/oauthAuthCode.ts tokenResponse)
415
358
  code_challenge_methods_supported: ["S256"],
416
359
  token_endpoint_auth_methods_supported: ["none"],
417
360
  scopes_supported: ["mcp:tools", "mcp:resources"]
@@ -444,7 +387,7 @@ app.post(
444
387
  res.status(400).json({ error: "invalid_redirect_uri", error_description: refusal });
445
388
  return;
446
389
  }
447
- const clientId = `pb_client_${randomUUID2()}`;
390
+ const clientId = `pb_client_${randomUUID()}`;
448
391
  const client = {
449
392
  client_id: clientId,
450
393
  redirect_uris,
@@ -463,9 +406,6 @@ app.post(
463
406
  }
464
407
  );
465
408
  var pendingCodes = /* @__PURE__ */ new Map();
466
- var ACCESS_TOKEN_TTL = 3600;
467
- var ACCESS_TOKEN_TTL_MS = ACCESS_TOKEN_TTL * 1e3;
468
- var accessTokens = /* @__PURE__ */ new Map();
469
409
  setInterval(() => {
470
410
  const now = Date.now();
471
411
  for (const [code, auth] of pendingCodes) {
@@ -474,9 +414,6 @@ setInterval(() => {
474
414
  for (const [id, client] of registeredClients) {
475
415
  if (now - client.registeredAt > 24 * 60 * 6e4) registeredClients.delete(id);
476
416
  }
477
- for (const [token, entry] of accessTokens) {
478
- if (now - entry.createdAt > ACCESS_TOKEN_TTL_MS) accessTokens.delete(token);
479
- }
480
417
  for (const [ip, rec] of authFailures) {
481
418
  if (rec.blockedUntil < now && rec.firstFailure + AUTH_FAILURE_WINDOW_MS < now) {
482
419
  authFailures.delete(ip);
@@ -1172,7 +1109,7 @@ app.post(
1172
1109
  } catch {
1173
1110
  process.stderr.write("[authorize] key-check unavailable \u2014 proceeding without validation\n");
1174
1111
  }
1175
- const code = randomUUID2();
1112
+ const code = randomUUID();
1176
1113
  pendingCodes.set(code, {
1177
1114
  apiKey: api_key,
1178
1115
  codeChallenge: code_challenge,
@@ -1191,35 +1128,13 @@ app.post(
1191
1128
  }
1192
1129
  }
1193
1130
  );
1194
- function issueTokens(apiKey) {
1195
- return {
1196
- access_token: apiKey,
1197
- token_type: "Bearer",
1198
- // 1-year TTL: actual validity enforced by Convex, not by expiry clock.
1199
- // Long TTL prevents unnecessary refresh cycles after restarts.
1200
- expires_in: 365 * 24 * 3600,
1201
- refresh_token: signRefreshToken(apiKey)
1202
- };
1203
- }
1204
1131
  app.post(
1205
1132
  "/oauth/token",
1206
1133
  authLimiter,
1207
1134
  express.urlencoded({ extended: false }),
1208
1135
  express.json(),
1209
1136
  (req, res) => {
1210
- const { grant_type, code, code_verifier, redirect_uri, refresh_token } = req.body;
1211
- if (grant_type === "refresh_token") {
1212
- const verified = verifyRefreshToken(refresh_token);
1213
- if (!verified) {
1214
- res.status(400).json({
1215
- error: "invalid_grant",
1216
- error_description: "Invalid or expired refresh token"
1217
- });
1218
- return;
1219
- }
1220
- res.json(issueTokens(verified.apiKey));
1221
- return;
1222
- }
1137
+ const { grant_type, code, code_verifier, redirect_uri } = req.body;
1223
1138
  if (grant_type !== "authorization_code") {
1224
1139
  res.status(400).json({ error: "unsupported_grant_type" });
1225
1140
  return;
@@ -1245,7 +1160,7 @@ app.post(
1245
1160
  return;
1246
1161
  }
1247
1162
  pendingCodes.delete(code);
1248
- res.json(issueTokens(pending.apiKey));
1163
+ res.json(tokenResponse(pending.apiKey));
1249
1164
  }
1250
1165
  );
1251
1166
  var mcpLimiter = rateLimit({
@@ -1339,7 +1254,7 @@ app.post("/mcp", mcpLimiter, async (req, res) => {
1339
1254
  send429(res, buildAuthLockoutError({ retryAfterSeconds, limit: AUTH_FAILURE_MAX, current: rec?.count ?? AUTH_FAILURE_MAX, id: req.body?.id ?? null }));
1340
1255
  return;
1341
1256
  }
1342
- const auth = resolveBearerAuth(req.headers?.authorization, accessTokens, nowTs, ACCESS_TOKEN_TTL_MS);
1257
+ const auth = resolveBearerAuth(req.headers?.authorization);
1343
1258
  if (auth.status !== "ok") {
1344
1259
  logRequest("POST", "auth_fail");
1345
1260
  if (auth.status === "rejected") recordAuthFailure(authFailures, reqIp, nowTs);
@@ -1396,7 +1311,7 @@ app.post("/mcp", mcpLimiter, async (req, res) => {
1396
1311
  }));
1397
1312
  return;
1398
1313
  }
1399
- const sid = randomUUID2();
1314
+ const sid = randomUUID();
1400
1315
  let initialized = false;
1401
1316
  const transport = new StreamableHTTPServerTransport({
1402
1317
  sessionIdGenerator: () => sid,
@@ -1479,7 +1394,7 @@ app.get("/mcp", mcpLimiter, async (req, res) => {
1479
1394
  send429(res, buildAuthLockoutError({ retryAfterSeconds, limit: AUTH_FAILURE_MAX, current: rec?.count ?? AUTH_FAILURE_MAX, id: req.body?.id ?? null }));
1480
1395
  return;
1481
1396
  }
1482
- const auth = resolveBearerAuth(req.headers?.authorization, accessTokens, nowTs, ACCESS_TOKEN_TTL_MS);
1397
+ const auth = resolveBearerAuth(req.headers?.authorization);
1483
1398
  if (auth.status !== "ok") {
1484
1399
  logRequest("GET", "auth_fail");
1485
1400
  if (auth.status === "rejected") recordAuthFailure(authFailures, reqIp, nowTs);
@@ -1528,7 +1443,7 @@ app.delete("/mcp", mcpLimiter, async (req, res) => {
1528
1443
  send429(res, buildAuthLockoutError({ retryAfterSeconds, limit: AUTH_FAILURE_MAX, current: rec?.count ?? AUTH_FAILURE_MAX, id: req.body?.id ?? null }));
1529
1444
  return;
1530
1445
  }
1531
- const auth = resolveBearerAuth(req.headers?.authorization, accessTokens, nowTs, ACCESS_TOKEN_TTL_MS);
1446
+ const auth = resolveBearerAuth(req.headers?.authorization);
1532
1447
  if (auth.status !== "ok") {
1533
1448
  logRequest("DELETE", "auth_fail");
1534
1449
  if (auth.status === "rejected") recordAuthFailure(authFailures, reqIp, nowTs);