@capxul/mcp 4.1.4 → 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 +430 -70
  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
@@ -11568,7 +11804,7 @@ const moneyExecutionContract = {
11568
11804
  };
11569
11805
  //#endregion
11570
11806
  //#region ../../packages/sdk/package.json
11571
- var version$1 = "4.1.4";
11807
+ var version$1 = "4.2.0";
11572
11808
  //#endregion
11573
11809
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
11574
11810
  /** Fixed fallback for failures that have no safe message. */
@@ -11747,8 +11983,6 @@ function evidenceText(value) {
11747
11983
  if (typeof value !== "string" || value.length === 0) return void 0;
11748
11984
  return redactSecrets(value).slice(0, MAX_EVIDENCE_TEXT);
11749
11985
  }
11750
- /** A field whose NAME says credential is masked whatever its value looks like. */
11751
- const CREDENTIAL_FIELD_NAME = /(?:otp|pass(?:word|wd|phrase)?|token|secret|credential|api[_-]?key|private[_-]?key|authorization|cookie|session)/iu;
11752
11986
  /** Copy a details object into a JSON-safe shape with every string masked. */
11753
11987
  function evidenceValue(value, depth = 0) {
11754
11988
  if (typeof value === "string") return evidenceText(value);
@@ -11759,7 +11993,7 @@ function evidenceValue(value, depth = 0) {
11759
11993
  if (Array.isArray(value)) return value.slice(0, 50).map((item) => evidenceValue(item, depth + 1));
11760
11994
  const copy = {};
11761
11995
  for (const [key, nested] of Object.entries(value)) {
11762
- if (CREDENTIAL_FIELD_NAME.test(key)) {
11996
+ if (isCredentialField(key)) {
11763
11997
  copy[key] = "[REDACTED]";
11764
11998
  continue;
11765
11999
  }
@@ -13526,21 +13760,8 @@ const ENGINEERING_CAPXUL_ENVS = [
13526
13760
  "production",
13527
13761
  "local"
13528
13762
  ];
13529
- const SAFE_TRACED_HEADER_NAMES = [
13530
- "content-length",
13531
- "content-type",
13532
- "traceparent",
13533
- "tracestate",
13534
- "x-request-id"
13535
- ];
13536
- const ENGINEERING_REDACTED_HEADER_NAMES = Object.freeze([
13537
- "authorization",
13538
- "cookie",
13539
- "set-cookie",
13540
- "x-api-key",
13541
- /auth|email|key|otp|secret|session|token|wallet/i
13542
- ]);
13543
- 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") });
13544
13765
  const postHogOtlpEndpoints = (host) => {
13545
13766
  const base = host.replace(/\/+$/, "");
13546
13767
  return {
@@ -13572,23 +13793,79 @@ const validateEngineeringTelemetryConfig = (config) => {
13572
13793
  headers: Object.freeze({ authorization: headerEntries[0][1] })
13573
13794
  };
13574
13795
  };
13575
- var RedactedEngineeringSpanFailure = class extends Error {
13576
- constructor() {
13577
- super("Engineering operation failed");
13578
- this.name = "RedactedEngineeringSpanFailure";
13579
- 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;
13580
13838
  }
13581
13839
  };
13582
- const REDACTED_SPAN_FAILURE = Exit.fail(new RedactedEngineeringSpanFailure());
13583
13840
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
13584
13841
  const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
13585
13842
  span(options) {
13586
13843
  const span = delegate.span(options);
13587
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
+ } });
13588
13858
  Object.defineProperty(wrapped, "end", {
13589
13859
  configurable: false,
13590
13860
  enumerable: false,
13591
- 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
+ },
13592
13869
  writable: false
13593
13870
  });
13594
13871
  return wrapped;
@@ -13619,8 +13896,29 @@ const makeEngineeringTelemetryLayer = (config) => {
13619
13896
  resource,
13620
13897
  mergeWithExisting: true
13621
13898
  });
13622
- const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, ENGINEERING_REDACTED_HEADER_NAMES));
13623
- 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));
13624
13922
  };
13625
13923
  //#endregion
13626
13924
  //#region ../../packages/sdk/src/ports/auth-client.ts
@@ -17949,6 +18247,12 @@ function isTransportError(err) {
17949
18247
  }
17950
18248
  //#endregion
17951
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
+ ];
17952
18256
  var IdentityError = class extends Data.TaggedError("IdentityError") {};
17953
18257
  function identityErrorFromCapxul(operation, error, cause = error) {
17954
18258
  return new IdentityError({
@@ -19965,7 +20269,7 @@ AxiosError$1.ERR_FORM_DATA_DEPTH_EXCEEDED = "ERR_FORM_DATA_DEPTH_EXCEEDED";
19965
20269
  //#endregion
19966
20270
  //#region ../../node_modules/.pnpm/delayed-stream@1.0.0/node_modules/delayed-stream/lib/delayed_stream.js
19967
20271
  var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19968
- var Stream$2 = __require("stream").Stream;
20272
+ var Stream$3 = __require("stream").Stream;
19969
20273
  var util$4 = __require("util");
19970
20274
  module.exports = DelayedStream;
19971
20275
  function DelayedStream() {
@@ -19977,7 +20281,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
19977
20281
  this._released = false;
19978
20282
  this._bufferedEvents = [];
19979
20283
  }
19980
- util$4.inherits(DelayedStream, Stream$2);
20284
+ util$4.inherits(DelayedStream, Stream$3);
19981
20285
  DelayedStream.create = function(source, options) {
19982
20286
  var delayedStream = new this();
19983
20287
  options = options || {};
@@ -20017,7 +20321,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
20017
20321
  this._bufferedEvents = [];
20018
20322
  };
20019
20323
  DelayedStream.prototype.pipe = function() {
20020
- var r = Stream$2.prototype.pipe.apply(this, arguments);
20324
+ var r = Stream$3.prototype.pipe.apply(this, arguments);
20021
20325
  this.resume();
20022
20326
  return r;
20023
20327
  };
@@ -20044,7 +20348,7 @@ var require_delayed_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =>
20044
20348
  //#region ../../node_modules/.pnpm/combined-stream@1.0.8/node_modules/combined-stream/lib/combined_stream.js
20045
20349
  var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20046
20350
  var util$3 = __require("util");
20047
- var Stream$1 = __require("stream").Stream;
20351
+ var Stream$2 = __require("stream").Stream;
20048
20352
  var DelayedStream = require_delayed_stream();
20049
20353
  module.exports = CombinedStream;
20050
20354
  function CombinedStream() {
@@ -20059,7 +20363,7 @@ var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =
20059
20363
  this._insideLoop = false;
20060
20364
  this._pendingNext = false;
20061
20365
  }
20062
- util$3.inherits(CombinedStream, Stream$1);
20366
+ util$3.inherits(CombinedStream, Stream$2);
20063
20367
  CombinedStream.create = function(options) {
20064
20368
  var combinedStream = new this();
20065
20369
  options = options || {};
@@ -20086,7 +20390,7 @@ var require_combined_stream = /* @__PURE__ */ __commonJSMin(((exports, module) =
20086
20390
  return this;
20087
20391
  };
20088
20392
  CombinedStream.prototype.pipe = function(dest, options) {
20089
- Stream$1.prototype.pipe.call(this, dest, options);
20393
+ Stream$2.prototype.pipe.call(this, dest, options);
20090
20394
  this.resume();
20091
20395
  return dest;
20092
20396
  };
@@ -28334,7 +28638,7 @@ var FormData_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
28334
28638
  var https$2 = __require("https");
28335
28639
  var parseUrl$1 = __require("url").parse;
28336
28640
  var fs = __require("fs");
28337
- var Stream = __require("stream").Stream;
28641
+ var Stream$1 = __require("stream").Stream;
28338
28642
  var crypto$2 = __require("crypto");
28339
28643
  var mime = require_mime_types();
28340
28644
  var asynckit = require_asynckit();
@@ -28384,7 +28688,7 @@ var FormData_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
28384
28688
  else if (typeof value === "string") valueLength = Buffer.byteLength(value);
28385
28689
  this._valueLength += valueLength;
28386
28690
  this._overheadLength += Buffer.byteLength(header) + FormData.LINE_BREAK.length;
28387
- 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;
28388
28692
  if (!options.knownLength) this._valuesToMeasure.push(value);
28389
28693
  };
28390
28694
  FormData.prototype._lengthRetriever = function(value, callback) {
@@ -43067,8 +43371,52 @@ const loadIdentityProgram = Effect.gen(function* () {
43067
43371
  if (authUserId === void 0) return null;
43068
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));
43069
43373
  });
43070
- //#endregion
43071
- //#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
+ }
43072
43420
  function makeIdentityMethods(deps) {
43073
43421
  const convexCall = deps.convexCall;
43074
43422
  return {
@@ -43107,6 +43455,11 @@ function makeIdentityMethods(deps) {
43107
43455
  error: Errors.invalidInput("country", "unsupported country")
43108
43456
  };
43109
43457
  }
43458
+ const carried = checkProfileCarriage(profile);
43459
+ if (!carried.ok) return {
43460
+ ok: false,
43461
+ error: Errors.invalidInput(carried.field, carried.message)
43462
+ };
43110
43463
  const session = deps.actor?.authSession();
43111
43464
  if (session === void 0 || session === null) return {
43112
43465
  ok: false,
@@ -43117,7 +43470,8 @@ function makeIdentityMethods(deps) {
43117
43470
  email: session.email,
43118
43471
  displayName,
43119
43472
  country,
43120
- handle
43473
+ handle,
43474
+ ...carried.carriage
43121
43475
  }), options, CAPXUL_OPERATIONS.identity.completeProfile);
43122
43476
  },
43123
43477
  async handleAvailable(handle, options) {
@@ -45211,6 +45565,11 @@ function assembleCapxulClient(input) {
45211
45565
  ok: false,
45212
45566
  reason: "INVALID_INPUT"
45213
45567
  };
45568
+ const carried = checkProfileCarriage(profile);
45569
+ if (!carried.ok) return {
45570
+ ok: false,
45571
+ reason: "INVALID_INPUT"
45572
+ };
45214
45573
  const session = actor.authSession();
45215
45574
  if (session === null) return {
45216
45575
  ok: false,
@@ -45221,7 +45580,8 @@ function assembleCapxulClient(input) {
45221
45580
  email: session.email,
45222
45581
  displayName,
45223
45582
  country,
45224
- handle
45583
+ handle,
45584
+ ...carried.carriage
45225
45585
  }), controls, CAPXUL_OPERATIONS.identity.completeProfile, effectRunner.runPromise);
45226
45586
  return result.ok ? { ok: true } : {
45227
45587
  ok: false,
@@ -46715,7 +47075,7 @@ function stdioIdentity(env = process.env, options = {}) {
46715
47075
  }
46716
47076
  //#endregion
46717
47077
  //#region package.json
46718
- var version = "4.1.4";
47078
+ var version = "4.2.0";
46719
47079
  //#endregion
46720
47080
  //#region src/deps.ts
46721
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.4",
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.4",
54
+ "@capxul/sdk": "4.2.0",
55
55
  "@capxul/typescript-config": "0.0.0"
56
56
  },
57
57
  "scripts": {