@capxul/mcp 4.1.3 → 4.2.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 (3) hide show
  1. package/README.md +6 -3
  2. package/dist/main.mjs +442 -72
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -104,9 +104,12 @@ vp run --filter @capxul/mcp build
104
104
 
105
105
  The tests are hermetic: a real HTTP server, a fake token exchange, and a fake
106
106
  SDK client. They do not prove a live OAuth flow against a deployed backend, a
107
- chain path, or settlement. The published CLI still lacks an isolated
108
- packed-install smoke; [#922](https://github.com/Xelmar-tech/infrastructure/issues/922)
109
- owns that gap.
107
+ chain path, or settlement.
108
+ [#922](https://github.com/Xelmar-tech/infrastructure/issues/922) records the
109
+ historical packed-install gap. The current
110
+ `scripts/__tests__/pack-install-smoke.test.mjs` installs MCP and Sandbox
111
+ tarballs in a clean project and invokes their bins with `--help`. Run it
112
+ against the current artifacts before claiming installed-bin proof.
110
113
 
111
114
  A live staging run in #1812 proves browser login, `account:write` consent,
112
115
  Person onboarding, a ready Account, OAuth scope step-up, and the Account tools. A
package/dist/main.mjs CHANGED
@@ -8,7 +8,7 @@ import { createHash } from "node:crypto";
8
8
  import { createServer } from "node:http";
9
9
  import { localhostHostValidation, localhostOriginValidation, toNodeHandler } from "@modelcontextprotocol/node";
10
10
  import { z } from "zod";
11
- import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, Layer, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Tracer } from "effect";
11
+ import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
12
12
  import { FetchHttpClient, Headers, HttpClient } from "effect/unstable/http";
13
13
  import { OtlpExporter, OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
14
14
  import { resolve } from "path";
@@ -24,39 +24,275 @@ import zlib from "zlib";
24
24
  import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
25
25
  import { instrument } from "@posthog/mcp";
26
26
  //#region ../../packages/errors/src/secret-material.ts
27
- const SENSITIVE_MATERIAL_PATTERNS = [
28
- /(?:^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u,
29
- /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u,
30
- /(?:(?: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,
31
- /\bBearer\s+[^\s,;]+/iu,
32
- /https?:\/\/[^\s"']+?\/v[0-9]\/[A-Za-z0-9_-]{16,}/iu,
33
- /\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
34
- ];
35
- const MASKS = [
36
- [/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]"],
27
+ const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
28
+ "password",
29
+ "passwd",
30
+ "pwd",
31
+ "pass",
32
+ "passphrase",
33
+ "currentpassword",
34
+ "newpassword",
35
+ "confirmpassword",
36
+ "passwordconfirmation",
37
+ "passwordhash",
38
+ "hashedpassword",
39
+ "pin",
40
+ "privatekey",
41
+ "walletprivatekey",
42
+ "secretkey",
43
+ "signingkey",
44
+ "signingsecret",
45
+ "seed",
46
+ "seedphrase",
47
+ "mnemonic",
48
+ "recoveryphrase",
49
+ "recovery",
50
+ "recoverycode",
51
+ "recoverycodes",
52
+ "keyshare",
53
+ "signingmaterial",
54
+ "recoverymaterial",
55
+ "privatekeybytes",
56
+ "signingprivatekey",
57
+ "encryptionkey",
58
+ "decryptionkey",
59
+ "authtoken",
60
+ "apitoken",
61
+ "bearertoken",
62
+ "oauthtoken",
63
+ "csrftoken",
64
+ "clientassertion",
65
+ "token",
66
+ "accesstoken",
67
+ "refreshtoken",
68
+ "idtoken",
69
+ "sessiontoken",
70
+ "session",
71
+ "sessionkey",
72
+ "sessionsecret",
73
+ "authsessionid",
74
+ "sessioncredential",
75
+ "sessioncredentials",
76
+ "sessioncookie",
77
+ "auth",
78
+ "authentication",
79
+ "authorization",
80
+ "proxyauthorization",
81
+ "cookie",
82
+ "cookies",
83
+ "setcookie",
84
+ "apikey",
85
+ "xapikey",
86
+ "xauthtoken",
87
+ "clientsecret",
88
+ "secret",
89
+ "secrets",
90
+ "credential",
91
+ "credentials",
92
+ "signature",
93
+ "otp",
94
+ "otpcode",
95
+ "totpsecret",
96
+ "onetimecode",
97
+ "onetimepassword",
98
+ "verificationcode",
99
+ "authcode",
100
+ "authorizationcode",
101
+ "oauthcode",
102
+ "codeverifier"
103
+ ]);
104
+ /** Match credential fields by meaning, without hiding token addresses or replay IDs. */
105
+ function isCredentialField(name, context) {
106
+ const normalized = name.toLowerCase().replace(/[^a-z0-9]/gu, "");
107
+ return CREDENTIAL_FIELDS.has(normalized) || /(?:password(?:hash)?|passwd|passphrase|privatekey|signingkey|apikey|clientsecret|accesstoken|refreshtoken|sessiontoken)$/u.test(normalized) || context === "authentication" && (normalized === "code" || normalized === "sessionid") || context === "header" && /(?:token|secret|apikey|privatekey|signingkey|password|authorization|cookie|session)$/u.test(normalized);
108
+ }
109
+ function decodeUrlComponent(text) {
110
+ try {
111
+ return decodeURIComponent(text);
112
+ } catch {
113
+ return text;
114
+ }
115
+ }
116
+ /** Preserve URL diagnostics while removing credentials from their declared components. */
117
+ function redactUrlSecrets(value, source) {
118
+ try {
119
+ const absolute = /^[a-z][a-z0-9+.-]*:/iu.test(value);
120
+ const protocolRelative = value.startsWith("//");
121
+ const url = new URL(value, "https://redaction.invalid");
122
+ if (url.protocol !== "https:" && url.protocol !== "http:") return maskText(value);
123
+ let changed = url.username.length > 0 || url.password.length > 0;
124
+ const maskParams = (params) => new URLSearchParams(Array.from(params, ([name, entry]) => {
125
+ const safeName = maskText(name);
126
+ const safeEntry = isCredentialField(name) || /^(?:code|draft|__posthog)$/iu.test(name) ? "[REDACTED]" : maskText(entry);
127
+ changed ||= safeName !== name || safeEntry !== entry;
128
+ return [safeName, safeEntry];
129
+ })).toString();
130
+ const provider = source !== void 0 || /(?:^|\.)(?:alchemy\.com|alchemyapi\.io|infura\.io)$/iu.test(url.hostname);
131
+ const segments = (absolute || protocolRelative ? url.pathname : value.split(/[?#]/u)[0] ?? "").split("/");
132
+ const path = segments.map((segment, index) => {
133
+ const decoded = decodeUrlComponent(segment);
134
+ const masked = provider && /^v[23]$/u.test(decodeUrlComponent(segments[index - 1] ?? "")) && decoded.length > 0 ? "[REDACTED]" : maskText(decoded);
135
+ changed ||= masked !== decoded;
136
+ return masked === decoded ? segment : encodeURIComponent(masked);
137
+ }).join("/");
138
+ const query = maskParams(url.searchParams);
139
+ const fragment = url.hash.slice(1);
140
+ const decodedFragment = decodeUrlComponent(fragment);
141
+ const maskedFragment = maskText(decodedFragment);
142
+ changed ||= maskedFragment !== decodedFragment;
143
+ const fragmentQuery = fragment.indexOf("?");
144
+ const fragmentPath = fragment.slice(0, fragmentQuery);
145
+ const maskedFragmentPath = maskText(decodeUrlComponent(fragmentPath));
146
+ changed ||= maskedFragmentPath !== decodeUrlComponent(fragmentPath);
147
+ const hash = fragmentQuery >= 0 ? `${maskedFragmentPath === decodeUrlComponent(fragmentPath) ? fragmentPath : encodeURIComponent(maskedFragmentPath)}?${maskParams(new URLSearchParams(fragment.slice(fragmentQuery + 1)))}` : fragment.includes("=") ? maskParams(new URLSearchParams(fragment)) : maskedFragment === decodedFragment ? fragment : encodeURIComponent(maskedFragment);
148
+ const origin = absolute ? url.origin : protocolRelative ? `//${url.host}` : "";
149
+ if (!changed) return value;
150
+ return `${origin}${path}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
151
+ } catch {
152
+ return maskText(value.replace(/(https?:\/\/)[^/\s@]+@/giu, "$1[REDACTED]@").replace(/([?&#])([^=&#]+)=([^&#]*)/gu, (whole, separator, name) => isCredentialField(decodeUrlComponent(name)) || /^(?:code|draft|__posthog)$/iu.test(decodeUrlComponent(name)) ? `${separator}${name}=[REDACTED]` : whole));
153
+ }
154
+ }
155
+ const JSON_FIELD = /("(?:[^"\\]|\\.)*")(\s*:\s*)("(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/gu;
156
+ const PROSE_FIELD = /(?=(\b([A-Za-z][A-Za-z0-9_-]*(?:[ \t]+[A-Za-z][A-Za-z0-9_-]*){0,3})\s*[:=]\s*((?:Bearer|Basic)[ \t]+[^\s,;"']+|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&"']+)))/gu;
157
+ const EMBEDDED_URL = /(https?:\/\/[^\s"<>]*)/giu;
158
+ function isSecretLabel(name) {
159
+ return isCredentialField(name) || /^request[ _-]?body$/iu.test(name);
160
+ }
161
+ const JSON_ENCODING_MAX = 8;
162
+ const JSON_NESTING_MAX = 64;
163
+ const JSON_KEY = /("(?:[^"\\]|\\.)*")\s*:/gu;
164
+ const JSON_CONTAINER_START = /(?:\{\s*["}]|\[\s*[[\]{}"0-9tfn-])/uy;
165
+ function hasSecretJsonKey(value) {
166
+ for (const match of value.matchAll(JSON_KEY)) try {
167
+ if (match[1] !== void 0 && isSecretLabel(JSON.parse(match[1]))) return true;
168
+ } catch {}
169
+ return false;
170
+ }
171
+ /** Project a complete fragment once. Never retry all of its nested substrings. */
172
+ function projectJsonFragment(value, encodingDepth, overDepth) {
173
+ if (overDepth || encodingDepth >= JSON_ENCODING_MAX) return "[REDACTED]";
174
+ let parsed;
175
+ try {
176
+ parsed = JSON.parse(value);
177
+ } catch {
178
+ return hasSecretJsonKey(value) ? "[REDACTED]" : value;
179
+ }
180
+ try {
181
+ let changed = false;
182
+ const masked = JSON.stringify(parsed, (name, nested) => {
183
+ if (isSecretLabel(name)) {
184
+ changed ||= nested !== "[REDACTED]";
185
+ return "[REDACTED]";
186
+ }
187
+ if (typeof nested === "string") {
188
+ const projected = maskJsonFragments(nested, encodingDepth + 1);
189
+ changed ||= projected !== nested;
190
+ return projected;
191
+ }
192
+ return nested;
193
+ });
194
+ return changed ? masked : value;
195
+ } catch {
196
+ return "[REDACTED]";
197
+ }
198
+ }
199
+ /** Scan balanced object/array fragments once, respecting JSON quotes and escapes. */
200
+ function maskJsonFragments(value, encodingDepth = 0) {
201
+ if (value.trimStart().startsWith("\"")) try {
202
+ const decoded = JSON.parse(value);
203
+ if (typeof decoded === "string") {
204
+ if (encodingDepth >= JSON_ENCODING_MAX) return "\"[REDACTED]\"";
205
+ const masked = maskJsonFragments(decoded, encodingDepth + 1);
206
+ return masked === decoded ? value : JSON.stringify(masked);
207
+ }
208
+ } catch {}
209
+ let output = "";
210
+ let cursor = 0;
211
+ let start = -1;
212
+ let depth = 0;
213
+ let overDepth = false;
214
+ let quoted = false;
215
+ let escaped = false;
216
+ for (let index = 0; index < value.length; index++) {
217
+ const character = value[index];
218
+ if (start < 0) {
219
+ JSON_CONTAINER_START.lastIndex = index;
220
+ if (!JSON_CONTAINER_START.test(value)) continue;
221
+ start = index;
222
+ depth = 1;
223
+ overDepth = false;
224
+ continue;
225
+ }
226
+ if (quoted) {
227
+ if (escaped) escaped = false;
228
+ else if (character === "\\") escaped = true;
229
+ else if (character === "\"") quoted = false;
230
+ continue;
231
+ }
232
+ if (character === "\"") quoted = true;
233
+ else if (character === "{" || character === "[") {
234
+ depth++;
235
+ overDepth ||= depth > JSON_NESTING_MAX;
236
+ } else if (character === "}" || character === "]") {
237
+ depth--;
238
+ if (depth !== 0) continue;
239
+ output += value.slice(cursor, start) + projectJsonFragment(value.slice(start, index + 1), encodingDepth, overDepth);
240
+ cursor = index + 1;
241
+ start = -1;
242
+ }
243
+ }
244
+ if (start >= 0) {
245
+ const fragment = value.slice(start);
246
+ output += value.slice(cursor, start) + (overDepth || hasSecretJsonKey(fragment) ? "[REDACTED]" : fragment);
247
+ cursor = value.length;
248
+ }
249
+ return output + value.slice(cursor);
250
+ }
251
+ function maskLabelledText(value) {
252
+ const jsonMasked = maskJsonFragments(value).replace(JSON_FIELD, (whole, quotedName, separator) => {
253
+ try {
254
+ return isSecretLabel(JSON.parse(quotedName)) ? `${quotedName}${separator}"[REDACTED]"` : whole;
255
+ } catch {
256
+ return whole;
257
+ }
258
+ });
259
+ let output = "";
260
+ let cursor = 0;
261
+ for (const match of jsonMasked.matchAll(PROSE_FIELD)) {
262
+ const [, whole, label, entry] = match;
263
+ if (whole === void 0 || label === void 0 || entry === void 0 || match.index < cursor || /^Bearer[ \t]+/iu.test(entry)) continue;
264
+ const words = label.split(/[ \t]+/u);
265
+ for (let index = 0; index < words.length; index++) {
266
+ const name = words.slice(index).join(" ");
267
+ if (isSecretLabel(name)) {
268
+ const prefix = words.slice(0, index).join(" ");
269
+ output += jsonMasked.slice(cursor, match.index) + `${prefix ? `${prefix} ` : ""}${name}=[REDACTED]`;
270
+ cursor = match.index + whole.length;
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ return output + jsonMasked.slice(cursor);
276
+ }
277
+ const TOKEN_MASKS = [
278
+ [/\bBearer\s+(?!(?:realm|error|error_description|scope|authorization_uri|resource|claims)\s*=)[^\s,;"']+/giu, "Bearer [REDACTED]"],
37
279
  [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
38
280
  [/(^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
39
- [/(?:(?: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]"],
40
- [/(https?:\/\/[^\s"']+?\/v[0-9]\/)[A-Za-z0-9_-]{16,}/giu, "$1[REDACTED]"],
41
- [/\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]"]
281
+ [/(?:(?: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]"]
42
282
  ];
43
- /**
44
- * Reject: does the value carry any known secret material? Best effort — callers
45
- * drop the whole value on a match; a false negative is a leak, a false positive
46
- * merely omits an observation field.
47
- */
283
+ /** URL components and malformed URLs use this non-recursive masking layer. */
284
+ function maskText(value) {
285
+ let text = maskLabelledText(value);
286
+ for (const [pattern, replacement] of TOKEN_MASKS) text = text.replace(pattern, replacement);
287
+ return text;
288
+ }
289
+ /** Reject only when the same semantic sanitizer removes credential material. */
48
290
  function containsSensitiveMaterial(value) {
49
- return SENSITIVE_MATERIAL_PATTERNS.some((pattern) => pattern.test(value));
291
+ return redactSecrets(value) !== value;
50
292
  }
51
- /**
52
- * Mask: return the value with every known secret rewritten to `[REDACTED]`,
53
- * preserving surrounding text. Used where a value must still be shown (log
54
- * lines, provider messages, PostHog rows) but must not carry live credentials.
55
- */
56
- function redactSecrets(value) {
57
- let text = value;
58
- for (const [pattern, replacement] of MASKS) text = text.replace(pattern, replacement);
59
- return text;
293
+ /** Mask credential fields and embedded URLs while preserving surrounding evidence. */
294
+ function redactSecrets(value, source) {
295
+ return maskLabelledText(value).split(EMBEDDED_URL).map((part, index) => index % 2 === 1 ? redactUrlSecrets(part, source).replaceAll("%5BREDACTED%5D", "[REDACTED]") : maskText(part)).join("");
60
296
  }
61
297
  //#endregion
62
298
  //#region ../../packages/errors/src/chain-cause.ts
@@ -5177,6 +5413,7 @@ const ActivityFilterProps = {
5177
5413
  filter_status: Schema.optional(Schema.Array(Schema.Literals([
5178
5414
  "pending",
5179
5415
  "settling",
5416
+ "unresolved",
5180
5417
  "pending_claim",
5181
5418
  "scheduled",
5182
5419
  "streaming",
@@ -10353,6 +10590,7 @@ function verifiedOperationFields(attributes) {
10353
10590
  /** Observe a public Effect that already uses the canonical CapxulError channel. */
10354
10591
  async function runCapxulEffect(effect, controls, operation = CAPXUL_OPERATIONS.runtime.port, runPromise = Effect.runPromise) {
10355
10592
  const invocation = readInvocationObservation(controls);
10593
+ let owningSpan;
10356
10594
  const correlationId = safeEngineeringIdentifier(invocation?.context?.correlationId ?? controls?.correlation_id);
10357
10595
  const context = invocation?.active === false ? void 0 : sanitizeObservationContext(invocation?.context);
10358
10596
  const fields = {
@@ -10379,6 +10617,10 @@ async function runCapxulEffect(effect, controls, operation = CAPXUL_OPERATIONS.r
10379
10617
  const failure = error instanceof CapxulError ? error : void 0;
10380
10618
  const outcome = Exit.isSuccess(exit) ? "succeeded" : failure?.code === "CANCELLED" ? "cancelled" : failure !== void 0 && EXPECTED_OPERATION_OUTCOMES.has(failure.code) ? "refused" : "failed";
10381
10619
  const span = yield* Effect.currentSpan.pipe(Effect.orDie);
10620
+ if (Exit.isFailure(exit) && invocation?.active === true) owningSpan = Object.freeze({
10621
+ traceId: span.traceId,
10622
+ spanId: span.spanId
10623
+ });
10382
10624
  const completion = {
10383
10625
  ...fields,
10384
10626
  ...verifiedOperationFields(span.attributes),
@@ -10392,7 +10634,10 @@ async function runCapxulEffect(effect, controls, operation = CAPXUL_OPERATIONS.r
10392
10634
  yield* (outcome === "failed" ? Effect.logError("capxul_sdk_operation") : Effect.logInfo("capxul_sdk_operation")).pipe(Effect.annotateLogs(completion), Effect.catchCause(() => Effect.void));
10393
10635
  return yield* Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause);
10394
10636
  }).pipe(Effect.withSpan(operation)), controls));
10395
- return !result.ok && invocation !== void 0 ? markFailureInvocationSnapshot(result, invocation) : result;
10637
+ return !result.ok && invocation !== void 0 ? markFailureInvocationSnapshot(result, {
10638
+ ...invocation,
10639
+ ...owningSpan === void 0 ? {} : { owningSpan }
10640
+ }) : result;
10396
10641
  }
10397
10642
  /** Map machine-internal failure state onto the public SDK error vocabulary. */
10398
10643
  function publicIdentityFailure(failure) {
@@ -11559,7 +11804,7 @@ const moneyExecutionContract = {
11559
11804
  };
11560
11805
  //#endregion
11561
11806
  //#region ../../packages/sdk/package.json
11562
- var version$1 = "4.1.3";
11807
+ var version$1 = "4.2.0";
11563
11808
  //#endregion
11564
11809
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
11565
11810
  /** Fixed fallback for failures that have no safe message. */
@@ -11698,7 +11943,8 @@ function report(adapter, kind, operation, cause, invocation, origin, authoritati
11698
11943
  }, {
11699
11944
  active: invocation.active,
11700
11945
  ...invocation.actorId === void 0 ? {} : { actorId: invocation.actorId },
11701
- ...context === void 0 ? {} : { context }
11946
+ ...context === void 0 ? {} : { context },
11947
+ ...invocation.owningSpan === void 0 ? {} : { owningSpan: invocation.owningSpan }
11702
11948
  });
11703
11949
  try {
11704
11950
  ignoreDeliveryFailure(kind === "operation" ? adapter.captureOperationFailure(failure) : adapter.captureException(failure));
@@ -11737,8 +11983,6 @@ function evidenceText(value) {
11737
11983
  if (typeof value !== "string" || value.length === 0) return void 0;
11738
11984
  return redactSecrets(value).slice(0, MAX_EVIDENCE_TEXT);
11739
11985
  }
11740
- /** A field whose NAME says credential is masked whatever its value looks like. */
11741
- const CREDENTIAL_FIELD_NAME = /(?:otp|pass(?:word|wd|phrase)?|token|secret|credential|api[_-]?key|private[_-]?key|authorization|cookie|session)/iu;
11742
11986
  /** Copy a details object into a JSON-safe shape with every string masked. */
11743
11987
  function evidenceValue(value, depth = 0) {
11744
11988
  if (typeof value === "string") return evidenceText(value);
@@ -11749,7 +11993,7 @@ function evidenceValue(value, depth = 0) {
11749
11993
  if (Array.isArray(value)) return value.slice(0, 50).map((item) => evidenceValue(item, depth + 1));
11750
11994
  const copy = {};
11751
11995
  for (const [key, nested] of Object.entries(value)) {
11752
- if (CREDENTIAL_FIELD_NAME.test(key)) {
11996
+ if (isCredentialField(key)) {
11753
11997
  copy[key] = "[REDACTED]";
11754
11998
  continue;
11755
11999
  }
@@ -13516,21 +13760,8 @@ const ENGINEERING_CAPXUL_ENVS = [
13516
13760
  "production",
13517
13761
  "local"
13518
13762
  ];
13519
- const SAFE_TRACED_HEADER_NAMES = [
13520
- "content-length",
13521
- "content-type",
13522
- "traceparent",
13523
- "tracestate",
13524
- "x-request-id"
13525
- ];
13526
- const ENGINEERING_REDACTED_HEADER_NAMES = Object.freeze([
13527
- "authorization",
13528
- "cookie",
13529
- "set-cookie",
13530
- "x-api-key",
13531
- /auth|email|key|otp|secret|session|token|wallet/i
13532
- ]);
13533
- const traceHeaderFilter = (name) => SAFE_TRACED_HEADER_NAMES.includes(name.toLowerCase());
13763
+ const traceHeaderFilter = (_name) => true;
13764
+ const credentialHeaderPattern = Object.assign(/./u, { test: (name) => isCredentialField(name, "header") });
13534
13765
  const postHogOtlpEndpoints = (host) => {
13535
13766
  const base = host.replace(/\/+$/, "");
13536
13767
  return {
@@ -13562,23 +13793,79 @@ const validateEngineeringTelemetryConfig = (config) => {
13562
13793
  headers: Object.freeze({ authorization: headerEntries[0][1] })
13563
13794
  };
13564
13795
  };
13565
- var RedactedEngineeringSpanFailure = class extends Error {
13566
- constructor() {
13567
- super("Engineering operation failed");
13568
- this.name = "RedactedEngineeringSpanFailure";
13569
- delete this.stack;
13796
+ const failureText = (failure, key, limit, source) => {
13797
+ if (failure === null || typeof failure !== "object") return void 0;
13798
+ try {
13799
+ let owner = failure;
13800
+ let descriptor;
13801
+ for (let depth = 0; owner !== null && depth < 4; depth += 1) {
13802
+ descriptor = Object.getOwnPropertyDescriptor(owner, key);
13803
+ if (descriptor !== void 0 || key !== "name") break;
13804
+ owner = Object.getPrototypeOf(owner);
13805
+ }
13806
+ const value = descriptor?.value;
13807
+ return typeof value === "string" || key === "code" && typeof value === "number" ? redactSecrets(String(value), source).slice(0, limit) : void 0;
13808
+ } catch {
13809
+ return;
13810
+ }
13811
+ };
13812
+ const exportFailure = (failure, source) => {
13813
+ const message = failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
13814
+ const code = failureText(failure, "code", 128);
13815
+ const error = new Error((code === void 0 ? message : `${message} [code=${code}]`).slice(0, 2048));
13816
+ error.name = failureText(failure, "_tag", 128) ?? failureText(failure, "name", 128) ?? "Error";
13817
+ error.stack = failureText(failure, "stack", 8192, source) ?? `${error.name}: ${error.message}`;
13818
+ return error;
13819
+ };
13820
+ const exportFailureExit = (cause, source) => Exit.failCause(Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isInterruptReason(reason)).slice(0, 8).map((reason) => Cause.isFailReason(reason) ? Cause.makeFailReason(exportFailure(reason.error, source)) : Cause.makeDieReason(exportFailure(reason.defect, source)))));
13821
+ const BROWSER_KEEPALIVE_BYTES = 65536;
13822
+ let unfinishedBrowserExportBytes = 0;
13823
+ const hasBrowserDocument = () => typeof window !== "undefined" && typeof document !== "undefined";
13824
+ const browserExportClient = (client) => HttpClient.transform(client, (response, request) => Effect.acquireUseRelease(Effect.sync(() => {
13825
+ const bytes = "contentLength" in request.body ? request.body.contentLength : void 0;
13826
+ if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
13827
+ unfinishedBrowserExportBytes += bytes;
13828
+ return bytes;
13829
+ }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catch((error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error))))), (bytes) => Effect.sync(() => {
13830
+ if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
13831
+ })));
13832
+ const spanUpstream = (span) => {
13833
+ let current = span;
13834
+ while (current?._tag === "Span") {
13835
+ const upstream = current.attributes.get("chain.upstream");
13836
+ if (upstream === "alchemy" || upstream === "infura") return upstream;
13837
+ current = current.parent._tag === "Some" ? current.parent.value : void 0;
13570
13838
  }
13571
13839
  };
13572
- const REDACTED_SPAN_FAILURE = Exit.fail(new RedactedEngineeringSpanFailure());
13573
13840
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
13574
13841
  const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
13575
13842
  span(options) {
13576
13843
  const span = delegate.span(options);
13577
13844
  const wrapped = Object.create(span);
13845
+ Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
13846
+ const source = spanUpstream(span);
13847
+ const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
13848
+ if (header !== void 0 && isCredentialField(header, "header")) span.attribute(name, "[REDACTED]");
13849
+ else if (typeof value === "string" && name === "url.query") span.attribute(name, redactUrlSecrets(`?${value}`).slice(1, 2049));
13850
+ else if (typeof value === "string" && name === "url.path") {
13851
+ const fullUrl = span.attributes.get("url.full");
13852
+ const path = typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
13853
+ span.attribute(name, redactUrlSecrets(path, source).slice(0, 2048));
13854
+ } else if (typeof value === "string" && (name === "url.full" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
13855
+ else if (header !== void 0 && typeof value === "string") span.attribute(name, redactSecrets(value, source).slice(0, 2048));
13856
+ else span.attribute(name, value);
13857
+ } });
13578
13858
  Object.defineProperty(wrapped, "end", {
13579
13859
  configurable: false,
13580
13860
  enumerable: false,
13581
- value: (endTime, exit) => span.end(endTime, Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? REDACTED_SPAN_FAILURE : exit),
13861
+ value: (endTime, exit) => {
13862
+ if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
13863
+ const first = exit.cause.reasons.find((reason) => !Cause.isInterruptReason(reason));
13864
+ const code = failureText(first === void 0 ? void 0 : Cause.isFailReason(first) ? first.error : first.defect, "code", 128);
13865
+ if (code !== void 0) span.attribute("error.code", code);
13866
+ span.end(endTime, exportFailureExit(exit.cause, spanUpstream(span)));
13867
+ } else span.end(endTime, exit);
13868
+ },
13582
13869
  writable: false
13583
13870
  });
13584
13871
  return wrapped;
@@ -13609,8 +13896,29 @@ const makeEngineeringTelemetryLayer = (config) => {
13609
13896
  resource,
13610
13897
  mergeWithExisting: true
13611
13898
  });
13612
- const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, ENGINEERING_REDACTED_HEADER_NAMES));
13613
- return Layer.mergeAll(tracing, logging, headerPolicy).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
13899
+ const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
13900
+ const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
13901
+ if (validated.producer !== "browser" || !hasBrowserDocument()) return;
13902
+ const browserWindow = window;
13903
+ const browserDocument = document;
13904
+ const flusher = yield* OtlpExporter.Flusher;
13905
+ const run = yield* FiberSet.makeRuntime();
13906
+ const flush = () => {
13907
+ run(flusher.flush);
13908
+ };
13909
+ const visibilityChanged = () => {
13910
+ if (browserDocument.visibilityState === "hidden") flush();
13911
+ };
13912
+ yield* Effect.acquireRelease(Effect.sync(() => {
13913
+ browserDocument.addEventListener("visibilitychange", visibilityChanged);
13914
+ browserWindow.addEventListener("pagehide", flush);
13915
+ }), () => Effect.sync(() => {
13916
+ browserDocument.removeEventListener("visibilitychange", visibilityChanged);
13917
+ browserWindow.removeEventListener("pagehide", flush);
13918
+ }));
13919
+ }));
13920
+ const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client)).pipe(Layer.provide(FetchHttpClient.layer));
13921
+ return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, headerPolicy)), Layer.provide(OtlpSerialization.layerJson), Layer.provide(transport));
13614
13922
  };
13615
13923
  //#endregion
13616
13924
  //#region ../../packages/sdk/src/ports/auth-client.ts
@@ -17939,6 +18247,12 @@ function isTransportError(err) {
17939
18247
  }
17940
18248
  //#endregion
17941
18249
  //#region ../../packages/sdk/src/ports/identity.ts
18250
+ /** The chain families a payout address may name (#1955). */
18251
+ const PAYOUT_CHAINS = [
18252
+ "evm",
18253
+ "solana",
18254
+ "starknet"
18255
+ ];
17942
18256
  var IdentityError = class extends Data.TaggedError("IdentityError") {};
17943
18257
  function identityErrorFromCapxul(operation, error, cause = error) {
17944
18258
  return new IdentityError({
@@ -19955,7 +20269,7 @@ AxiosError$1.ERR_FORM_DATA_DEPTH_EXCEEDED = "ERR_FORM_DATA_DEPTH_EXCEEDED";
19955
20269
  //#endregion
19956
20270
  //#region ../../node_modules/.pnpm/delayed-stream@1.0.0/node_modules/delayed-stream/lib/delayed_stream.js
19957
20271
  var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19958
- var Stream$2 = __require("stream").Stream;
20272
+ var Stream$3 = __require("stream").Stream;
19959
20273
  var util$4 = __require("util");
19960
20274
  module.exports = DelayedStream;
19961
20275
  function DelayedStream() {
@@ -19967,7 +20281,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
19967
20281
  this._released = false;
19968
20282
  this._bufferedEvents = [];
19969
20283
  }
19970
- util$4.inherits(DelayedStream, Stream$2);
20284
+ util$4.inherits(DelayedStream, Stream$3);
19971
20285
  DelayedStream.create = function(source, options) {
19972
20286
  var delayedStream = new this();
19973
20287
  options = options || {};
@@ -20007,7 +20321,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
20007
20321
  this._bufferedEvents = [];
20008
20322
  };
20009
20323
  DelayedStream.prototype.pipe = function() {
20010
- var r = Stream$2.prototype.pipe.apply(this, arguments);
20324
+ var r = Stream$3.prototype.pipe.apply(this, arguments);
20011
20325
  this.resume();
20012
20326
  return r;
20013
20327
  };
@@ -20034,7 +20348,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
20034
20348
  //#region ../../node_modules/.pnpm/combined-stream@1.0.8/node_modules/combined-stream/lib/combined_stream.js
20035
20349
  var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20036
20350
  var util$3 = __require("util");
20037
- var Stream$1 = __require("stream").Stream;
20351
+ var Stream$2 = __require("stream").Stream;
20038
20352
  var DelayedStream = require_delayed_stream();
20039
20353
  module.exports = CombinedStream;
20040
20354
  function CombinedStream() {
@@ -20049,7 +20363,7 @@ var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =
20049
20363
  this._insideLoop = false;
20050
20364
  this._pendingNext = false;
20051
20365
  }
20052
- util$3.inherits(CombinedStream, Stream$1);
20366
+ util$3.inherits(CombinedStream, Stream$2);
20053
20367
  CombinedStream.create = function(options) {
20054
20368
  var combinedStream = new this();
20055
20369
  options = options || {};
@@ -20076,7 +20390,7 @@ var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =
20076
20390
  return this;
20077
20391
  };
20078
20392
  CombinedStream.prototype.pipe = function(dest, options) {
20079
- Stream$1.prototype.pipe.call(this, dest, options);
20393
+ Stream$2.prototype.pipe.call(this, dest, options);
20080
20394
  this.resume();
20081
20395
  return dest;
20082
20396
  };
@@ -28324,7 +28638,7 @@ var FormData_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
28324
28638
  var https$2 = __require("https");
28325
28639
  var parseUrl$1 = __require("url").parse;
28326
28640
  var fs = __require("fs");
28327
- var Stream = __require("stream").Stream;
28641
+ var Stream$1 = __require("stream").Stream;
28328
28642
  var crypto$2 = __require("crypto");
28329
28643
  var mime = require_mime_types();
28330
28644
  var asynckit = require_asynckit();
@@ -28374,7 +28688,7 @@ var FormData_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
28374
28688
  else if (typeof value === "string") valueLength = Buffer.byteLength(value);
28375
28689
  this._valueLength += valueLength;
28376
28690
  this._overheadLength += Buffer.byteLength(header) + FormData.LINE_BREAK.length;
28377
- if (!value || !value.path && !(value.readable && hasOwn(value, "httpVersion")) && !(value instanceof Stream)) return;
28691
+ if (!value || !value.path && !(value.readable && hasOwn(value, "httpVersion")) && !(value instanceof Stream$1)) return;
28378
28692
  if (!options.knownLength) this._valuesToMeasure.push(value);
28379
28693
  };
28380
28694
  FormData.prototype._lengthRetriever = function(value, callback) {
@@ -43057,8 +43371,52 @@ const loadIdentityProgram = Effect.gen(function* () {
43057
43371
  if (authUserId === void 0) return null;
43058
43372
  return yield* deps.identityPort.loadByAuthUserId(authUserId).pipe(Effect.catchDefect((cause) => Effect.fail(identityErrorFromCapxul("loadByAuthUserId", cause instanceof CapxulError ? cause : Errors.unknown(cause), cause))), Effect.mapError((failure) => failure.publicError));
43059
43373
  });
43060
- //#endregion
43061
- //#region ../../packages/sdk/src/surface/identity.ts
43374
+ /**
43375
+ * THE ONE boundary grammar for those optional fields. Both `completeProfile`
43376
+ * entry points — the method bundle here and the identity runtime in
43377
+ * `create-capxul-client.ts` — run it, so a malformed value is refused the same
43378
+ * way at each and never reaches the port.
43379
+ */
43380
+ function checkProfileCarriage(profile) {
43381
+ const carriage = {};
43382
+ if (profile.imageStorageId !== void 0) {
43383
+ if (typeof profile.imageStorageId !== "string" || profile.imageStorageId.trim().length === 0) return {
43384
+ ok: false,
43385
+ field: "imageStorageId",
43386
+ message: "must be a non-empty storage id"
43387
+ };
43388
+ carriage.imageStorageId = profile.imageStorageId.trim();
43389
+ }
43390
+ if (profile.payoutAddresses !== void 0) {
43391
+ if (!Array.isArray(profile.payoutAddresses)) return {
43392
+ ok: false,
43393
+ field: "payoutAddresses",
43394
+ message: "must be an array"
43395
+ };
43396
+ if (profile.payoutAddresses.length > 10) return {
43397
+ ok: false,
43398
+ field: "payoutAddresses",
43399
+ message: `must carry at most 10 addresses`
43400
+ };
43401
+ const entries = [];
43402
+ for (const entry of profile.payoutAddresses) {
43403
+ if (typeof entry !== "object" || entry === null || !PAYOUT_CHAINS.some((chain) => chain === entry.chain) || typeof entry.address !== "string" || entry.address.trim().length === 0) return {
43404
+ ok: false,
43405
+ field: "payoutAddresses",
43406
+ message: `each entry needs a chain of ${PAYOUT_CHAINS.join(", ")} and an address`
43407
+ };
43408
+ entries.push({
43409
+ chain: entry.chain,
43410
+ address: entry.address.trim()
43411
+ });
43412
+ }
43413
+ carriage.payoutAddresses = entries;
43414
+ }
43415
+ return {
43416
+ ok: true,
43417
+ carriage
43418
+ };
43419
+ }
43062
43420
  function makeIdentityMethods(deps) {
43063
43421
  const convexCall = deps.convexCall;
43064
43422
  return {
@@ -43097,6 +43455,11 @@ function makeIdentityMethods(deps) {
43097
43455
  error: Errors.invalidInput("country", "unsupported country")
43098
43456
  };
43099
43457
  }
43458
+ const carried = checkProfileCarriage(profile);
43459
+ if (!carried.ok) return {
43460
+ ok: false,
43461
+ error: Errors.invalidInput(carried.field, carried.message)
43462
+ };
43100
43463
  const session = deps.actor?.authSession();
43101
43464
  if (session === void 0 || session === null) return {
43102
43465
  ok: false,
@@ -43107,7 +43470,8 @@ function makeIdentityMethods(deps) {
43107
43470
  email: session.email,
43108
43471
  displayName,
43109
43472
  country,
43110
- handle
43473
+ handle,
43474
+ ...carried.carriage
43111
43475
  }), options, CAPXUL_OPERATIONS.identity.completeProfile);
43112
43476
  },
43113
43477
  async handleAvailable(handle, options) {
@@ -45201,6 +45565,11 @@ function assembleCapxulClient(input) {
45201
45565
  ok: false,
45202
45566
  reason: "INVALID_INPUT"
45203
45567
  };
45568
+ const carried = checkProfileCarriage(profile);
45569
+ if (!carried.ok) return {
45570
+ ok: false,
45571
+ reason: "INVALID_INPUT"
45572
+ };
45204
45573
  const session = actor.authSession();
45205
45574
  if (session === null) return {
45206
45575
  ok: false,
@@ -45211,7 +45580,8 @@ function assembleCapxulClient(input) {
45211
45580
  email: session.email,
45212
45581
  displayName,
45213
45582
  country,
45214
- handle
45583
+ handle,
45584
+ ...carried.carriage
45215
45585
  }), controls, CAPXUL_OPERATIONS.identity.completeProfile, effectRunner.runPromise);
45216
45586
  return result.ok ? { ok: true } : {
45217
45587
  ok: false,
@@ -46705,7 +47075,7 @@ function stdioIdentity(env = process.env, options = {}) {
46705
47075
  }
46706
47076
  //#endregion
46707
47077
  //#region package.json
46708
- var version = "4.1.3";
47078
+ var version = "4.2.0";
46709
47079
  //#endregion
46710
47080
  //#region src/deps.ts
46711
47081
  const NO_IDENTITY = async () => void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/mcp",
3
- "version": "4.1.3",
3
+ "version": "4.2.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": [
@@ -51,7 +51,7 @@
51
51
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
52
52
  "vite-plus": "0.3.0",
53
53
  "@capxul/config": "0.3.0",
54
- "@capxul/sdk": "4.1.3",
54
+ "@capxul/sdk": "4.2.0",
55
55
  "@capxul/typescript-config": "0.0.0"
56
56
  },
57
57
  "scripts": {