@productbrain/mcp 0.0.1-beta.8711 → 0.0.1-beta.8724

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.
@@ -925,6 +925,167 @@ function recordGatewayCall(params) {
925
925
  // src/client.ts
926
926
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
927
927
 
928
+ // src/errors.ts
929
+ var MCP_CALL_ERROR_RE = /^MCP call "([^"]+)" failed \(\d+\): (.+)$/s;
930
+ function asKernelCallError(err) {
931
+ if (!(err instanceof Error)) return void 0;
932
+ const e = err;
933
+ if (typeof e.code !== "string") return void 0;
934
+ const match = MCP_CALL_ERROR_RE.exec(e.message);
935
+ if (!match) return void 0;
936
+ return {
937
+ code: e.code,
938
+ fn: match[1],
939
+ cleanMessage: match[2],
940
+ // NOTE: match[2] not match[1] — group 1 is now fn scope (DEC-798)
941
+ missingRequiredFields: Array.isArray(e.missingRequiredFields) ? e.missingRequiredFields : void 0,
942
+ fieldErrors: Array.isArray(e.fieldErrors) ? e.fieldErrors : void 0
943
+ };
944
+ }
945
+ var GateError = class extends Error {
946
+ constructor(message) {
947
+ super(message);
948
+ this.name = "GateError";
949
+ }
950
+ };
951
+ var BackendError = class extends Error {
952
+ constructor(message) {
953
+ super(message);
954
+ this.name = "BackendError";
955
+ }
956
+ };
957
+ var ValidationError = class extends Error {
958
+ constructor(message) {
959
+ super(message);
960
+ this.name = "ValidationError";
961
+ }
962
+ };
963
+ var PASS_SESSION_ID = "Call `session action=start`, then pass the sessionId it returns as `sessionId` on every write: the connection does not remember your session between calls.";
964
+ var GATE_PATTERNS = [
965
+ {
966
+ pattern: /session required|no active.*session|call.*session.*start/i,
967
+ code: "SESSION_REQUIRED",
968
+ recovery: PASS_SESSION_ID,
969
+ action: { tool: "session", description: "Start session", parameters: { action: "start" } }
970
+ },
971
+ {
972
+ pattern: /session.*closed/i,
973
+ code: "SESSION_CLOSED",
974
+ recovery: "Start a new session.",
975
+ action: { tool: "session", description: "Start new session", parameters: { action: "start" } }
976
+ },
977
+ {
978
+ pattern: /orientation required|call.*orient/i,
979
+ code: "ORIENTATION_REQUIRED",
980
+ recovery: "Run orient with a task, then retry.",
981
+ action: { tool: "orient", description: "Orient with a task", parameters: { action: "task" } }
982
+ },
983
+ {
984
+ pattern: /read.?only.*scope/i,
985
+ code: "READONLY_SCOPE",
986
+ recovery: "This API key cannot write. Use a readwrite key.",
987
+ action: { tool: "workspace", description: "Check key scope", parameters: { action: "whoami" } }
988
+ }
989
+ ];
990
+ function orientationRefusal(err) {
991
+ const gate = GATE_PATTERNS.find((g) => g.code === "ORIENTATION_REQUIRED");
992
+ const mcpErr = asKernelCallError(err);
993
+ const message = mcpErr?.cleanMessage ?? (err instanceof Error ? err.message : String(err));
994
+ if (!gate || !gate.pattern.test(message)) return null;
995
+ return { code: gate.code, message, recovery: gate.recovery, availableActions: [gate.action] };
996
+ }
997
+ function classifyError(err) {
998
+ const message = err instanceof Error ? err.message : String(err);
999
+ if (err instanceof GateError) {
1000
+ return {
1001
+ code: "PERMISSION_DENIED",
1002
+ message,
1003
+ recovery: "Check permissions or use a readwrite API key.",
1004
+ availableActions: [{ tool: "workspace", description: "Check key scope", parameters: { action: "whoami" } }]
1005
+ };
1006
+ }
1007
+ if (err instanceof BackendError) {
1008
+ return { code: "BACKEND_ERROR", message, recovery: "Retry the operation or check backend health." };
1009
+ }
1010
+ if (err instanceof ValidationError) {
1011
+ return { code: "VALIDATION_ERROR", message };
1012
+ }
1013
+ const mcpErr = asKernelCallError(err);
1014
+ if (mcpErr) {
1015
+ const { code: convexCode, fn, cleanMessage, missingRequiredFields, fieldErrors } = mcpErr;
1016
+ if (convexCode === "VALIDATION_FAILED") {
1017
+ const hasDiagnostics = missingRequiredFields?.length || fieldErrors?.length;
1018
+ return {
1019
+ code: "VALIDATION_ERROR",
1020
+ fn,
1021
+ message: cleanMessage,
1022
+ ...hasDiagnostics ? {
1023
+ diagnostics: {
1024
+ ...missingRequiredFields?.length ? { missingRequiredFields } : {},
1025
+ ...fieldErrors?.length ? { fieldErrors } : {}
1026
+ }
1027
+ } : {}
1028
+ };
1029
+ }
1030
+ if (convexCode === "NOT_FOUND") {
1031
+ return {
1032
+ code: "NOT_FOUND",
1033
+ fn,
1034
+ message: cleanMessage,
1035
+ recovery: "Use entries action=search to find the correct ID.",
1036
+ availableActions: [{ tool: "entries", description: "Search entries", parameters: { action: "search" } }]
1037
+ };
1038
+ }
1039
+ if (convexCode?.startsWith("AUTHZ_")) {
1040
+ return {
1041
+ code: "PERMISSION_DENIED",
1042
+ fn,
1043
+ message: cleanMessage,
1044
+ recovery: "Check permissions or use a readwrite API key.",
1045
+ availableActions: [{ tool: "workspace", description: "Check key scope", parameters: { action: "whoami" } }]
1046
+ };
1047
+ }
1048
+ if (convexCode === "WORKSPACE_SLUG_TAKEN" || convexCode === "PERSON_ALREADY_EXISTS") {
1049
+ return {
1050
+ code: "DUPLICATE",
1051
+ fn,
1052
+ message: cleanMessage,
1053
+ recovery: "Use entries action=get to inspect the existing entry.",
1054
+ availableActions: [{ tool: "entries", description: "Get entry", parameters: { action: "get" } }]
1055
+ };
1056
+ }
1057
+ if (convexCode === "rate-limited") {
1058
+ return {
1059
+ code: "rate-limited",
1060
+ fn,
1061
+ message: cleanMessage,
1062
+ recovery: "Rate limit hit for this API key. Wait the number of seconds in the message (Retry-After), then retry the same call unchanged \u2014 it is safe to retry."
1063
+ };
1064
+ }
1065
+ return { code: convexCode, fn, message: cleanMessage };
1066
+ }
1067
+ for (const { pattern, code, recovery, action } of GATE_PATTERNS) {
1068
+ if (pattern.test(message)) {
1069
+ return { code, message, recovery, availableActions: [action] };
1070
+ }
1071
+ }
1072
+ if (err?.name === "GatewayTimeoutError") {
1073
+ const mayHaveLanded = err.mayHaveLanded === true;
1074
+ return {
1075
+ code: "GATEWAY_TIMEOUT",
1076
+ message,
1077
+ recovery: mayHaveLanded ? "The server was reachable and may have completed this write \u2014 verify before retrying, since a blind retry can duplicate it." : "The server was reachable but did not answer in time. Nothing was written \u2014 safe to retry."
1078
+ };
1079
+ }
1080
+ if (/network error|fetch failed|ECONNREFUSED|ETIMEDOUT/i.test(message)) {
1081
+ return { code: "BACKEND_UNAVAILABLE", message, recovery: "Retry in a few seconds." };
1082
+ }
1083
+ return { code: "INTERNAL_ERROR", message: message || "An unexpected error occurred." };
1084
+ }
1085
+ function retryIsFutile(code) {
1086
+ return code === "VALIDATION_ERROR";
1087
+ }
1088
+
928
1089
  // src/lib/conversation.ts
929
1090
  import { createHash as createHash2 } from "crypto";
930
1091
  var MAX_RAW_LEN = 128;
@@ -990,7 +1151,7 @@ async function probeDeploymentCandidates(candidates, apiKey) {
990
1151
  var _conversationId;
991
1152
  function getConversationId() {
992
1153
  const mcpSid = getRequestMcpSessionId();
993
- if (mcpSid) return `mcp:${mcpSid}`;
1154
+ if (mcpSid) return sessionLifecycle().conversationId ?? `mcp:${mcpSid}`;
994
1155
  if (getRequestApiKey()) return null;
995
1156
  if (_conversationId === void 0) _conversationId = resolveConversationId();
996
1157
  return _conversationId;
@@ -1076,6 +1237,8 @@ function getActiveApiKey() {
1076
1237
  var _sessionLifecycleByStream = /* @__PURE__ */ new Map();
1077
1238
  var MAX_SESSION_STREAMS = 500;
1078
1239
  function sessionLifecycle() {
1240
+ const seated = seatedSessionStore.getStore();
1241
+ if (seated) return seated;
1079
1242
  const mcpSid = getRequestMcpSessionId();
1080
1243
  if (!mcpSid) return state();
1081
1244
  const key = `${cacheScope()}:${mcpSid}`;
@@ -1102,6 +1265,13 @@ function setSessionOriented(value) {
1102
1265
  function getApiKeyScope() {
1103
1266
  return state().apiKeyScope;
1104
1267
  }
1268
+ var seatedSessionStore = new AsyncLocalStorage2();
1269
+ async function validateCallerSession(sessionId) {
1270
+ const session = await kernelCall("agent.validateSession", { sessionId, workspaceId: await getWorkspaceId(), requireActive: true });
1271
+ return { agentSessionId: session._id, sessionOriented: session.oriented, sessionClosed: false, conversationId: session.conversationId ?? null };
1272
+ }
1273
+ var runWithSeatedSession = (lc, fn) => seatedSessionStore.run(lc, fn);
1274
+ var forgetStreamLifecycle = (keyHash, mcpSessionId) => _sessionLifecycleByStream.delete(`${keyHash}:${mcpSessionId}`);
1105
1275
  async function startAgentSession(role) {
1106
1276
  const workspaceId = await getWorkspaceId();
1107
1277
  const s = state();
@@ -1419,9 +1589,7 @@ function requireWriteAccess() {
1419
1589
  const lc = sessionLifecycle();
1420
1590
  const s = state();
1421
1591
  if (!lc.agentSessionId) {
1422
- throw new Error(
1423
- "Agent session required for write operations. Call `session action=start` first."
1424
- );
1592
+ throw new Error(`Agent session required for write operations. ${PASS_SESSION_ID}`);
1425
1593
  }
1426
1594
  if (lc.sessionClosed) {
1427
1595
  throw new Error(
@@ -1518,6 +1686,10 @@ export {
1518
1686
  MCP_NPX_PACKAGE,
1519
1687
  resolveClient,
1520
1688
  writeClientConfig,
1689
+ PASS_SESSION_ID,
1690
+ orientationRefusal,
1691
+ classifyError,
1692
+ retryIsFutile,
1521
1693
  warnOnProdFallthrough,
1522
1694
  getConversationId,
1523
1695
  runWithToolContext,
@@ -1527,6 +1699,9 @@ export {
1527
1699
  isSessionOriented,
1528
1700
  setSessionOriented,
1529
1701
  getApiKeyScope,
1702
+ validateCallerSession,
1703
+ runWithSeatedSession,
1704
+ forgetStreamLifecycle,
1530
1705
  startAgentSession,
1531
1706
  closeAgentSession,
1532
1707
  orphanAgentSession,
@@ -1544,4 +1719,4 @@ export {
1544
1719
  requireVendorTriageAccess,
1545
1720
  recoverSessionState
1546
1721
  };
1547
- //# sourceMappingURL=chunk-FZWP45AU.js.map
1722
+ //# sourceMappingURL=chunk-2UJDIWEX.js.map