@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.
- package/dist/main.mjs +198 -48
- 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
|
-
|
|
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.
|
|
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_
|
|
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
|
-
|
|
43181
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
52
|
-
"@capxul/sdk": "3.
|
|
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": {
|