@capxul/mcp 3.0.0 → 3.1.0

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.
Files changed (2) hide show
  1. package/dist/main.mjs +198 -48
  2. package/package.json +3 -3
package/dist/main.mjs CHANGED
@@ -21,6 +21,42 @@ import { EventEmitter } from "events";
21
21
  import http2 from "http2";
22
22
  import zlib from "zlib";
23
23
  import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
24
+ //#region ../../packages/errors/src/secret-material.ts
25
+ const SENSITIVE_MATERIAL_PATTERNS = [
26
+ /(?:^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u,
27
+ /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u,
28
+ /(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/iu,
29
+ /\bBearer\s+[^\s,;]+/iu,
30
+ /https?:\/\/[^\s"']+?\/v[0-9]\/[A-Za-z0-9_-]{16,}/iu,
31
+ /\b(?:api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|request[ _-]?body|credential|otp|one[ _-]?time[ _-]?(?:password|code)|verification[ _-]?code)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/iu
32
+ ];
33
+ const MASKS = [
34
+ [/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]"],
35
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
36
+ [/(^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
37
+ [/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"],
38
+ [/(https?:\/\/[^\s"']+?\/v[0-9]\/)[A-Za-z0-9_-]{16,}/giu, "$1[REDACTED]"],
39
+ [/\b(api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|request[ _-]?body|credential|otp|one[ _-]?time[ _-]?(?:password|code)|verification[ _-]?code)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/giu, "$1=[REDACTED]"]
40
+ ];
41
+ /**
42
+ * Reject: does the value carry any known secret material? Best effort — callers
43
+ * drop the whole value on a match; a false negative is a leak, a false positive
44
+ * merely omits an observation field.
45
+ */
46
+ function containsSensitiveMaterial(value) {
47
+ return SENSITIVE_MATERIAL_PATTERNS.some((pattern) => pattern.test(value));
48
+ }
49
+ /**
50
+ * Mask: return the value with every known secret rewritten to `[REDACTED]`,
51
+ * preserving surrounding text. Used where a value must still be shown (log
52
+ * lines, provider messages, PostHog rows) but must not carry live credentials.
53
+ */
54
+ function redactSecrets(value) {
55
+ let text = value;
56
+ for (const [pattern, replacement] of MASKS) text = text.replace(pattern, replacement);
57
+ return text;
58
+ }
59
+ //#endregion
24
60
  //#region ../../packages/errors/src/chain-cause.ts
25
61
  /**
26
62
  * Why an operation failed — the one flat vocabulary. The first four are the
@@ -43,6 +79,9 @@ const FAILURE_MODES$1 = [
43
79
  "insufficient-allowance",
44
80
  "rejected",
45
81
  "unauthorized",
82
+ "forbidden",
83
+ "bad-request",
84
+ "method-unsupported",
46
85
  "unknown"
47
86
  ];
48
87
  function isFailureMode(value) {
@@ -59,6 +98,33 @@ const CHAIN_UPSTREAMS = [
59
98
  function isChainUpstream(value) {
60
99
  return typeof value === "string" && CHAIN_UPSTREAMS.includes(value);
61
100
  }
101
+ /**
102
+ * The response headers that name a refusal or let provider support find the
103
+ * request. Nothing else crosses: a header can carry a session or a key.
104
+ */
105
+ const RESPONSE_HEADER_ALLOWLIST = [
106
+ "retry-after",
107
+ "www-authenticate",
108
+ "content-type",
109
+ "x-request-id",
110
+ "cf-ray",
111
+ "x-ratelimit-remaining",
112
+ "x-ratelimit-reset",
113
+ "server"
114
+ ];
115
+ const RESPONSE_HEADER_VALUE_MAX = 128;
116
+ /**
117
+ * The allow-listed headers of one answer, masked and bounded. `read` is
118
+ * `Headers.get` or a lookup into a decoded record. Empty when none is present.
119
+ */
120
+ function boundedResponseHeaders(read) {
121
+ const headers = {};
122
+ for (const name of RESPONSE_HEADER_ALLOWLIST) {
123
+ const value = read(name);
124
+ if (typeof value === "string" && value.length > 0) headers[name] = boundedEvidence(value, RESPONSE_HEADER_VALUE_MAX);
125
+ }
126
+ return headers;
127
+ }
62
128
  const PROVIDER_MESSAGE_MAX = 300;
63
129
  const METHOD_MAX = 64;
64
130
  const REVERT_NAME_MAX = 64;
@@ -66,19 +132,9 @@ const REVERT_ARG_KEY_MAX = 32;
66
132
  const REVERT_ARG_VALUE_MAX = 128;
67
133
  const REVERT_ARGS_MAX = 8;
68
134
  const SELECTOR_RE = /^0x(?:[0-9a-fA-F]{8})?$/u;
69
- const MASKS = [
70
- [/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]"],
71
- [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
72
- [/\b0x[a-fA-F0-9]{40,}\b/gu, "[REDACTED]"],
73
- [/(^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
74
- [/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"],
75
- [/(https?:\/\/[^\s"']+?\/v[0-9]\/)[A-Za-z0-9_-]{16,}/giu, "$1[REDACTED]"],
76
- [/\b(api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|credential|otp)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/giu, "$1=[REDACTED]"]
77
- ];
78
135
  /** Mask credential-shaped content, then cap the length. Total on any string. */
79
136
  function boundedEvidence(value, max) {
80
- let text = value;
81
- for (const [pattern, replacement] of MASKS) text = text.replace(pattern, replacement);
137
+ const text = redactSecrets(value);
82
138
  return text.length <= max ? text : text.slice(0, max);
83
139
  }
84
140
  function isRecord(value) {
@@ -120,8 +176,36 @@ function decodeChainCause(value) {
120
176
  const revert = decodeRevert(value.revert);
121
177
  if (revert !== null) cause.revert = revert;
122
178
  if (typeof value.providerMessage === "string") cause.providerMessage = boundedEvidence(value.providerMessage, PROVIDER_MESSAGE_MAX);
179
+ const attempts = decodeAttempts(value.attempts);
180
+ if (attempts !== null) cause.attempts = attempts;
181
+ if (isRecord(value.responseHeaders)) {
182
+ const raw = value.responseHeaders;
183
+ const headers = boundedResponseHeaders((name) => {
184
+ const header = raw[name];
185
+ return typeof header === "string" ? header : null;
186
+ });
187
+ if (Object.keys(headers).length > 0) cause.responseHeaders = headers;
188
+ }
123
189
  return cause;
124
190
  }
191
+ /** The attempt sequence, bounded. A malformed entry drops the sequence, never the cause. */
192
+ function decodeAttempts(value) {
193
+ if (!Array.isArray(value)) return null;
194
+ const attempts = [];
195
+ for (const entry of value.slice(0, 5)) {
196
+ if (!isRecord(entry) || !isChainUpstream(entry.upstream) || !isFailureMode(entry.mode)) return null;
197
+ const duration = entry.durationMs;
198
+ if (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0) return null;
199
+ attempts.push({
200
+ upstream: entry.upstream,
201
+ mode: entry.mode,
202
+ ...typeof entry.httpStatus === "number" && Number.isInteger(entry.httpStatus) ? { httpStatus: entry.httpStatus } : {},
203
+ ...typeof entry.rpcCode === "number" && Number.isInteger(entry.rpcCode) ? { rpcCode: entry.rpcCode } : {},
204
+ durationMs: Math.round(duration)
205
+ });
206
+ }
207
+ return attempts.length === 0 ? null : attempts;
208
+ }
125
209
  /**
126
210
  * The cause as flat PostHog properties. Same keys from every producer, so one
127
211
  * filter (`failure_mode = 'rate-limited'`) finds the gateway's, the backend's,
@@ -140,7 +224,15 @@ function chainCauseProperties(chain) {
140
224
  chain_revert_selector: chain.revert.selector,
141
225
  chain_revert_args: chain.revert.args
142
226
  },
143
- ...chain.providerMessage === void 0 ? {} : { chain_provider_message: chain.providerMessage }
227
+ ...chain.providerMessage === void 0 ? {} : { chain_provider_message: chain.providerMessage },
228
+ ...chain.attempts === void 0 ? {} : { chain_attempts: chain.attempts.map((attempt) => ({
229
+ upstream: attempt.upstream,
230
+ failure_mode: attempt.mode,
231
+ ...attempt.httpStatus === void 0 ? {} : { http_status: attempt.httpStatus },
232
+ ...attempt.rpcCode === void 0 ? {} : { rpc_code: attempt.rpcCode },
233
+ duration_ms: attempt.durationMs
234
+ })) },
235
+ ...chain.responseHeaders === void 0 ? {} : { chain_response_headers: chain.responseHeaders }
144
236
  };
145
237
  }
146
238
  //#endregion
@@ -3779,7 +3871,7 @@ function invalidValueReason(prefix, raw) {
3779
3871
  }
3780
3872
  //#endregion
3781
3873
  //#region ../../packages/sdk/package.json
3782
- var version$3 = "3.0.0";
3874
+ var version$3 = "3.1.0";
3783
3875
  //#endregion
3784
3876
  //#region ../../packages/observability/src/engineering.ts
3785
3877
  const ENGINEERING_CAPXUL_ENVS = [
@@ -3929,33 +4021,6 @@ function resolveAuthClientUrl(authBaseUrl, path) {
3929
4021
  return `${base}${path}`;
3930
4022
  }
3931
4023
  //#endregion
3932
- //#region ../../packages/wire/src/secret-material.ts
3933
- const SENSITIVE_MATERIAL_PATTERNS = [
3934
- /0x[a-fA-F0-9]{40,}/u,
3935
- /(?:^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u,
3936
- /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u,
3937
- /(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/iu
3938
- ];
3939
- const RAW_PRIVATE_KEY_MASK = /(^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu;
3940
- const KNOWN_CREDENTIAL_MASK = /(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu;
3941
- const LABELLED_SECRET_MASK = /\b(api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|request[ _-]?body|credential|otp|one[ _-]?time[ _-]?(?:password|code)|verification[ _-]?code)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/giu;
3942
- /**
3943
- * Reject: does the value carry any known secret material? Best effort — callers
3944
- * drop the whole value on a match; a false negative is a leak, a false positive
3945
- * merely omits an observation field.
3946
- */
3947
- function containsSensitiveMaterial(value) {
3948
- return SENSITIVE_MATERIAL_PATTERNS.some((pattern) => pattern.test(value));
3949
- }
3950
- /**
3951
- * Mask: return the value with every known secret rewritten to `[REDACTED]`,
3952
- * preserving surrounding text. Used where a value must still be shown (log
3953
- * lines, issue bodies) but must not carry live credentials.
3954
- */
3955
- function redactSecrets(value) {
3956
- return value.replace(/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]").replace(/\b0x[a-fA-F0-9]{40,}\b/gu, "[REDACTED]").replace(RAW_PRIVATE_KEY_MASK, "$1[REDACTED]").replace(KNOWN_CREDENTIAL_MASK, "[REDACTED]").replace(LABELLED_SECRET_MASK, (_match, label) => `${label}=[REDACTED]`);
3957
- }
3958
- //#endregion
3959
4024
  //#region ../../packages/wire/src/observation-context.ts
3960
4025
  /** Single bounded HTTP carrier used before a Convex action envelope exists. */
3961
4026
  const OBSERVATION_CONTEXT_HEADER = "x-capxul-observation-context";
@@ -10530,6 +10595,29 @@ const TelemetryEnvelopeProps = {
10530
10595
  */
10531
10596
  const FailureModeSchema = Schema.Literals(FAILURE_MODES$1);
10532
10597
  const ChainUpstreamSchema = Schema.Literals(CHAIN_UPSTREAMS);
10598
+ /**
10599
+ * The refusing upstream's response headers (#1818): exactly the names in
10600
+ * `RESPONSE_HEADER_ALLOWLIST`, each optional, so the catalog itself refuses a
10601
+ * `set-cookie` or `authorization` that a producer let through.
10602
+ */
10603
+ const ResponseHeadersSchema = Schema.Struct({
10604
+ "retry-after": OptionalString,
10605
+ "www-authenticate": OptionalString,
10606
+ "content-type": OptionalString,
10607
+ "x-request-id": OptionalString,
10608
+ "cf-ray": OptionalString,
10609
+ "x-ratelimit-remaining": OptionalString,
10610
+ "x-ratelimit-reset": OptionalString,
10611
+ server: OptionalString
10612
+ });
10613
+ /** One upstream tried, flat (#1818): the `chain_attempts` entry on every producer's row. */
10614
+ const ChainAttemptSchema = Schema.Struct({
10615
+ upstream: ChainUpstreamSchema,
10616
+ failure_mode: FailureModeSchema,
10617
+ http_status: Schema.optional(Schema.Number),
10618
+ rpc_code: Schema.optional(Schema.Number),
10619
+ duration_ms: Schema.Number
10620
+ });
10533
10621
  const ChainCauseTelemetryProps = {
10534
10622
  failure_mode: Schema.optional(FailureModeSchema),
10535
10623
  chain_upstream: Schema.optional(ChainUpstreamSchema),
@@ -10540,7 +10628,9 @@ const ChainCauseTelemetryProps = {
10540
10628
  chain_revert_name: OptionalString,
10541
10629
  chain_revert_selector: OptionalString,
10542
10630
  chain_revert_args: Schema.optional(Schema.Record(Schema.String, Schema.String)),
10543
- chain_provider_message: OptionalString
10631
+ chain_provider_message: OptionalString,
10632
+ chain_attempts: Schema.optional(Schema.Array(ChainAttemptSchema)),
10633
+ chain_response_headers: Schema.optional(ResponseHeadersSchema)
10544
10634
  };
10545
10635
  const AuthOtpRequestedProps = Schema.Struct({
10546
10636
  ...TelemetryEnvelopeProps,
@@ -10643,7 +10733,8 @@ const ChainUpstreamFallbackProps = Schema.Struct({
10643
10733
  failure_mode: FailureModeSchema,
10644
10734
  exhausted: Schema.Boolean,
10645
10735
  http_status: Schema.optional(Schema.Number),
10646
- rpc_code: Schema.optional(Schema.Number)
10736
+ rpc_code: Schema.optional(Schema.Number),
10737
+ response_headers: Schema.optional(ResponseHeadersSchema)
10647
10738
  });
10648
10739
  const BundlerErrorProps = Schema.Struct({
10649
10740
  ...TelemetryEnvelopeProps,
@@ -38268,7 +38359,10 @@ const DEFINITIVE_PROVIDER_MODES = /* @__PURE__ */ new Set([
38268
38359
  "insufficient-balance",
38269
38360
  "insufficient-allowance",
38270
38361
  "rejected",
38271
- "unauthorized"
38362
+ "unauthorized",
38363
+ "forbidden",
38364
+ "bad-request",
38365
+ "method-unsupported"
38272
38366
  ]);
38273
38367
  const isRetryable = (failure) => {
38274
38368
  if (failure.code === "CANCELLED" || failure.code === "SIGNER_REJECTED") return true;
@@ -41006,13 +41100,13 @@ function observationError(cause) {
41006
41100
  function sanitizeChainDetail(source, detail) {
41007
41101
  for (const key of [
41008
41102
  "provider",
41009
- "provider_operation",
41010
41103
  "failure_mode",
41011
41104
  "reason"
41012
41105
  ]) {
41013
41106
  const value = source[key];
41014
41107
  if (typeof value === "string" && ENUMERATED_DETAIL_RE.test(value)) detail[key] = value;
41015
41108
  }
41109
+ sanitizeProviderOperation(source, detail);
41016
41110
  sanitizeProviderErrorDetail(source, detail);
41017
41111
  const status = source.http_status;
41018
41112
  if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) detail.http_status = status;
@@ -41041,6 +41135,43 @@ function sanitizeChainDetail(source, detail) {
41041
41135
  }
41042
41136
  const providerMessage = evidenceText(source.chain_provider_message);
41043
41137
  if (providerMessage !== void 0) detail.chain_provider_message = providerMessage;
41138
+ sanitizeChainAttempts(source, detail);
41139
+ }
41140
+ /** One flat attempt entry, or undefined when a field is not what the vocabulary allows. */
41141
+ function sanitizeChainAttempt(entry) {
41142
+ if (!isPlainObject(entry)) return void 0;
41143
+ const duration = entry.duration_ms;
41144
+ if (!isChainUpstream(entry.upstream) || !isFailureMode(entry.failure_mode) || typeof duration !== "number" || !Number.isFinite(duration) || duration < 0) return;
41145
+ return {
41146
+ upstream: entry.upstream,
41147
+ failure_mode: entry.failure_mode,
41148
+ ...typeof entry.http_status === "number" && Number.isInteger(entry.http_status) ? { http_status: entry.http_status } : {},
41149
+ ...typeof entry.rpc_code === "number" && Number.isInteger(entry.rpc_code) ? { rpc_code: entry.rpc_code } : {},
41150
+ duration_ms: Math.round(duration)
41151
+ };
41152
+ }
41153
+ /**
41154
+ * The attempt sequence and the refusing upstream's headers (#1818), same
41155
+ * bounds as the backend; a malformed entry is dropped, never invented.
41156
+ */
41157
+ function sanitizeChainAttempts(source, detail) {
41158
+ if (Array.isArray(source.chain_attempts)) {
41159
+ const attempts = source.chain_attempts.slice(0, 5).map(sanitizeChainAttempt);
41160
+ if (attempts.length > 0 && attempts.every((attempt) => attempt !== void 0)) detail.chain_attempts = attempts;
41161
+ }
41162
+ if (isPlainObject(source.chain_response_headers)) {
41163
+ const raw = source.chain_response_headers;
41164
+ const headers = boundedResponseHeaders((name) => {
41165
+ const value = raw[name];
41166
+ return typeof value === "string" ? value : null;
41167
+ });
41168
+ if (Object.keys(headers).length > 0) detail.chain_response_headers = headers;
41169
+ }
41170
+ }
41171
+ /** Its own check because a provider operation carries that provider's separators. */
41172
+ function sanitizeProviderOperation(source, detail) {
41173
+ const value = source.provider_operation;
41174
+ if (typeof value === "string" && PROVIDER_OPERATION_RE.test(value)) detail.provider_operation = value;
41044
41175
  }
41045
41176
  function sanitizeProviderErrorDetail(source, detail) {
41046
41177
  if (source.provider_error_source !== "openfort") return;
@@ -41086,11 +41217,22 @@ function sanitizeFailureDetail(input) {
41086
41217
  }
41087
41218
  /** Enumerated codes only (`convex`, `no-secure-context`, `bootstrap-encode`); free text fails. */
41088
41219
  const ENUMERATED_DETAIL_RE = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u;
41220
+ /**
41221
+ * A provider operation NAMES A FUNCTION on the provider, so it carries that
41222
+ * provider's separators. Convex writes its own as `dir/file:function`
41223
+ * (`media:setProfileImage`), and the enumerated shape above rejects both `:`
41224
+ * and `/` — so the operation was dropped and the PostHog issue lost the name of
41225
+ * what actually failed (staging, 2026-09-03). This widens the separators, not
41226
+ * the class: no spaces, same bound, and it matches the backend's own
41227
+ * `normalizeOperation`, which has always accepted them.
41228
+ */
41229
+ const PROVIDER_OPERATION_RE = /^[A-Za-z][A-Za-z0-9_.:/-]{0,63}$/u;
41089
41230
  const TARGET_HOST_RE = /^[A-Za-z0-9.[\]:-]{1,253}$/u;
41090
41231
  /** A four-byte revert selector, or the bare `0x` of an empty revert. */
41091
41232
  const REVERT_SELECTOR_RE = /^0x(?:[0-9a-fA-F]{8})?$/u;
41233
+ /** Same shape as the backend's `normalizeOperation`: a Convex path (`dir/file:fn`) survives. */
41092
41234
  function normalizeOperation(value) {
41093
- return typeof value === "string" && /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,255}$/u.test(value) ? value : "unknown";
41235
+ return typeof value === "string" && /^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,255}$/u.test(value) ? value : "unknown";
41094
41236
  }
41095
41237
  function normalizeErrorKind(value) {
41096
41238
  return typeof value === "string" && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u.test(value) ? value : "Error";
@@ -43176,10 +43318,18 @@ async function recordSetupFailure(setup, orgId, error, retryable, observationSou
43176
43318
  if (!scopedRecorded.ok) return scopedRecorded;
43177
43319
  return scopedRecorded;
43178
43320
  }
43321
+ /**
43322
+ * ADR-0023 R7: the layer that saw the chain failure says whether the same
43323
+ * request can succeed later; its word wins over the mode table. The table is
43324
+ * the one `identity/model.ts` uses, so the two gates read one list
43325
+ * (`org.account.get` retried a `transient: false` 403 in 1.5 s, 2026-09-03).
43326
+ */
43179
43327
  function isRetryableProviderFailure(error) {
43180
- const mode = error.details?.failure_mode;
43181
- return mode !== "auth-origin-mismatch" && mode !== "app-env-allowlist" && mode !== "no-secure-context";
43328
+ if (error.chain !== void 0) return error.chain.retryable;
43329
+ const mode = error.mode ?? error.details?.failure_mode;
43330
+ return !DEFINITIVE_PROVIDER_MODES.has(isFailureMode(mode) ? mode : void 0);
43182
43331
  }
43332
+ /** @internal Exported for the retry-gate test; the surface calls it from the setup loop. */
43183
43333
  function isRetryableOrganizationSetupFailure(error) {
43184
43334
  if (error.code === "CANCELLED" || error.code === "SIGNER_REJECTED") return true;
43185
43335
  if (error.code === "PROVIDER_ERROR") return isRetryableProviderFailure(error);
@@ -44938,7 +45088,7 @@ function stdioIdentity(env = process.env, options = {}) {
44938
45088
  }
44939
45089
  //#endregion
44940
45090
  //#region package.json
44941
- var version = "3.0.0";
45091
+ var version = "3.1.0";
44942
45092
  //#endregion
44943
45093
  //#region src/deps.ts
44944
45094
  const NO_IDENTITY = async () => void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "description": "Capxul MCP server for auth, Account readiness, identity, Organizations, relationships, requests, destinations, and testnet funding over Streamable HTTP.",
6
6
  "keywords": [
@@ -48,8 +48,8 @@
48
48
  "typescript": "npm:@typescript/typescript6@6.0.2",
49
49
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
50
50
  "vite-plus": "0.3.0",
51
- "@capxul/config": "0.2.1",
52
- "@capxul/sdk": "3.0.0",
51
+ "@capxul/config": "0.2.3",
52
+ "@capxul/sdk": "3.1.0",
53
53
  "@capxul/typescript-config": "0.0.0"
54
54
  },
55
55
  "scripts": {