ofw-mcp 2.13.0 → 2.15.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/bundle.js CHANGED
@@ -3116,9 +3116,28 @@ var require_utils = __commonJS({
3116
3116
  "use strict";
3117
3117
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3118
3118
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3119
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3119
3120
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3120
3121
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3121
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3122
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3123
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3124
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3125
+ var BYTE_HEX = new Array(256);
3126
+ {
3127
+ const HEX_DIGITS = "0123456789ABCDEF";
3128
+ for (let i = 0; i < 256; i++) {
3129
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3130
+ }
3131
+ }
3132
+ function percentEncodeNonAscii(cp) {
3133
+ if (cp < 2048) {
3134
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3135
+ }
3136
+ if (cp < 65536) {
3137
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3138
+ }
3139
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3140
+ }
3122
3141
  function stringArrayToHexStripped(input) {
3123
3142
  let acc = "";
3124
3143
  let code = 0;
@@ -3143,91 +3162,105 @@ var require_utils = __commonJS({
3143
3162
  }
3144
3163
  return acc;
3145
3164
  }
3165
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3166
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3167
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3146
3168
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3147
- function consumeIsZone(buffer) {
3148
- buffer.length = 0;
3149
- return true;
3150
- }
3151
- function consumeHextets(buffer, address, output) {
3152
- if (buffer.length) {
3153
- const hex3 = stringArrayToHexStripped(buffer);
3154
- if (hex3 !== "") {
3155
- address.push(hex3);
3156
- } else {
3157
- output.error = true;
3158
- return false;
3169
+ function isZoneIdentifier(zone) {
3170
+ if (zone.length === 0) return false;
3171
+ for (let i = 0; i < zone.length; i++) {
3172
+ if (isZoneCharacter(zone[i])) continue;
3173
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3174
+ i += 2;
3175
+ continue;
3159
3176
  }
3160
- buffer.length = 0;
3177
+ return false;
3161
3178
  }
3162
3179
  return true;
3163
3180
  }
3164
- function getIPV6(input) {
3165
- let tokenCount = 0;
3166
- const output = { error: false, address: "", zone: "" };
3167
- const address = [];
3168
- const buffer = [];
3169
- let endipv6Encountered = false;
3170
- let endIpv6 = false;
3171
- let consume = consumeHextets;
3172
- for (let i = 0; i < input.length; i++) {
3173
- const cursor = input[i];
3174
- if (cursor === "[" || cursor === "]") {
3175
- continue;
3176
- }
3177
- if (cursor === ":") {
3178
- if (endipv6Encountered === true) {
3179
- endIpv6 = true;
3181
+ function compressIPv6ZeroRun(hextets) {
3182
+ let bestStart = -1;
3183
+ let bestLength = 0;
3184
+ let runStart = -1;
3185
+ let runLength = 0;
3186
+ for (let i = 0; i < hextets.length; i++) {
3187
+ if (hextets[i] === "0") {
3188
+ if (runStart === -1) runStart = i;
3189
+ runLength++;
3190
+ if (runLength > bestLength) {
3191
+ bestLength = runLength;
3192
+ bestStart = runStart;
3180
3193
  }
3181
- if (!consume(buffer, address, output)) {
3182
- break;
3183
- }
3184
- if (++tokenCount > 7) {
3185
- output.error = true;
3186
- break;
3187
- }
3188
- if (i > 0 && input[i - 1] === ":") {
3189
- endipv6Encountered = true;
3190
- }
3191
- address.push(":");
3192
- continue;
3193
- } else if (cursor === "%") {
3194
- if (!consume(buffer, address, output)) {
3195
- break;
3196
- }
3197
- consume = consumeIsZone;
3198
3194
  } else {
3199
- buffer.push(cursor);
3195
+ runStart = -1;
3196
+ runLength = 0;
3197
+ }
3198
+ }
3199
+ if (bestLength < 2) return hextets.join(":");
3200
+ const head = hextets.slice(0, bestStart).join(":");
3201
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3202
+ return head + "::" + tail;
3203
+ }
3204
+ function normalizeIPv6Address(input) {
3205
+ const compression = input.indexOf("::");
3206
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
3207
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3208
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3209
+ if (compression !== -1) {
3210
+ if (left.length === 1 && left[0] === "") left.length = 0;
3211
+ if (right.length === 1 && right[0] === "") right.length = 0;
3212
+ }
3213
+ const parts = left.concat(right);
3214
+ let hextetCount = 0;
3215
+ for (let i = 0; i < parts.length; i++) {
3216
+ const part = parts[i];
3217
+ if (part === "") return void 0;
3218
+ if (part.indexOf(".") !== -1) {
3219
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
3220
+ hextetCount += 2;
3200
3221
  continue;
3201
3222
  }
3223
+ if (!isHextet(part)) return void 0;
3224
+ parts[i] = parseInt(part, 16).toString(16);
3225
+ hextetCount++;
3202
3226
  }
3203
- if (buffer.length) {
3204
- if (consume === consumeIsZone) {
3205
- output.zone = buffer.join("");
3206
- } else if (endIpv6) {
3207
- address.push(buffer.join(""));
3208
- } else {
3209
- address.push(stringArrayToHexStripped(buffer));
3210
- }
3227
+ if (compression === -1) {
3228
+ if (hextetCount !== 8) return void 0;
3229
+ return compressIPv6ZeroRun(parts);
3211
3230
  }
3212
- output.address = address.join("");
3213
- return output;
3231
+ if (hextetCount >= 8) return void 0;
3232
+ const expanded = parts.slice(0, left.length);
3233
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
3234
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
3235
+ return compressIPv6ZeroRun(expanded);
3214
3236
  }
3215
3237
  function normalizeIPv6(host) {
3216
- if (findToken(host, ":") < 2) {
3217
- return { host, isIPV6: false };
3218
- }
3219
- const ipv63 = getIPV6(host);
3220
- if (!ipv63.error) {
3221
- let newHost = ipv63.address;
3222
- let escapedHost = ipv63.address;
3223
- if (ipv63.zone) {
3224
- newHost += "%" + ipv63.zone;
3225
- escapedHost += "%25" + ipv63.zone;
3226
- }
3227
- return { host: newHost, isIPV6: true, escapedHost };
3228
- } else {
3229
- return { host, isIPV6: false };
3230
- }
3238
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3239
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3240
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
3241
+ let input = bracketed ? host.slice(1, -1) : host;
3242
+ if (bracketed && isIPvFuture(input)) {
3243
+ input = input.toLowerCase();
3244
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3245
+ }
3246
+ if (findToken(input, ":") < 2) {
3247
+ return { host, isIPV6: false, error: bracketed };
3248
+ }
3249
+ let zoneIdentifier = "";
3250
+ const zoneSeparator = input.indexOf("%");
3251
+ if (zoneSeparator !== -1) {
3252
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3253
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3254
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
3255
+ input = input.slice(0, zoneSeparator);
3256
+ }
3257
+ const address = normalizeIPv6Address(input);
3258
+ if (address === void 0) return { host, isIPV6: false, error: true };
3259
+ return {
3260
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3261
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3262
+ isIPV6: true
3263
+ };
3231
3264
  }
3232
3265
  function findToken(str, token) {
3233
3266
  let ind = 0;
@@ -3346,7 +3379,8 @@ var require_utils = __commonJS({
3346
3379
  function normalizePathEncoding(input) {
3347
3380
  let output = "";
3348
3381
  for (let i = 0; i < input.length; i++) {
3349
- if (input[i] === "%" && i + 2 < input.length) {
3382
+ const ch = input[i];
3383
+ if (ch === "%" && i + 2 < input.length) {
3350
3384
  const hex3 = input.slice(i + 1, i + 3);
3351
3385
  if (isHexPair(hex3)) {
3352
3386
  const normalizedHex = hex3.toUpperCase();
@@ -3360,10 +3394,152 @@ var require_utils = __commonJS({
3360
3394
  continue;
3361
3395
  }
3362
3396
  }
3363
- if (isPathCharacter(input[i])) {
3364
- output += input[i];
3397
+ if (isPathCharacter(ch)) {
3398
+ output += ch;
3399
+ } else {
3400
+ const code = input.charCodeAt(i);
3401
+ if (code < 128) {
3402
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3403
+ } else if (code < 55296 || code > 57343) {
3404
+ output += percentEncodeNonAscii(code);
3405
+ } else if (code <= 56319 && i + 1 < input.length) {
3406
+ const low = input.charCodeAt(i + 1);
3407
+ if (low >= 56320 && low <= 57343) {
3408
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3409
+ i++;
3410
+ } else {
3411
+ output += percentEncodeNonAscii(65533);
3412
+ }
3413
+ } else {
3414
+ output += percentEncodeNonAscii(65533);
3415
+ }
3416
+ }
3417
+ }
3418
+ return output;
3419
+ }
3420
+ function serializePathEncoding(input, pathNoScheme = false) {
3421
+ let output = "";
3422
+ let firstSegment = pathNoScheme && input[0] !== "/";
3423
+ for (let i = 0; i < input.length; i++) {
3424
+ const ch = input[i];
3425
+ if (ch === "%" && i + 2 < input.length) {
3426
+ const hex3 = input.slice(i + 1, i + 3);
3427
+ if (isHexPair(hex3)) {
3428
+ output += "%" + hex3.toUpperCase();
3429
+ i += 2;
3430
+ continue;
3431
+ }
3432
+ }
3433
+ if (ch === "/") {
3434
+ firstSegment = false;
3435
+ }
3436
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3437
+ output += ch;
3438
+ } else {
3439
+ const code = input.charCodeAt(i);
3440
+ if (code < 128) {
3441
+ output += BYTE_HEX[code];
3442
+ } else if (code < 55296 || code > 57343) {
3443
+ output += percentEncodeNonAscii(code);
3444
+ } else if (code <= 56319 && i + 1 < input.length) {
3445
+ const low = input.charCodeAt(i + 1);
3446
+ if (low >= 56320 && low <= 57343) {
3447
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3448
+ i++;
3449
+ } else {
3450
+ output += percentEncodeNonAscii(65533);
3451
+ }
3452
+ } else {
3453
+ output += percentEncodeNonAscii(65533);
3454
+ }
3455
+ }
3456
+ }
3457
+ return output;
3458
+ }
3459
+ function encodeComponent(input, isAllowed) {
3460
+ let output = "";
3461
+ for (let i = 0; i < input.length; i++) {
3462
+ const ch = input[i];
3463
+ if (ch === "%" && i + 2 < input.length) {
3464
+ const hex3 = input.slice(i + 1, i + 3);
3465
+ if (isHexPair(hex3)) {
3466
+ output += "%" + hex3.toUpperCase();
3467
+ i += 2;
3468
+ continue;
3469
+ }
3470
+ }
3471
+ if (isAllowed(ch)) {
3472
+ output += ch;
3473
+ } else {
3474
+ const code = input.charCodeAt(i);
3475
+ if (code < 128) {
3476
+ output += BYTE_HEX[code];
3477
+ } else if (code < 55296 || code > 57343) {
3478
+ output += percentEncodeNonAscii(code);
3479
+ } else if (code <= 56319 && i + 1 < input.length) {
3480
+ const low = input.charCodeAt(i + 1);
3481
+ if (low >= 56320 && low <= 57343) {
3482
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3483
+ i++;
3484
+ } else {
3485
+ output += percentEncodeNonAscii(65533);
3486
+ }
3487
+ } else {
3488
+ output += percentEncodeNonAscii(65533);
3489
+ }
3490
+ }
3491
+ }
3492
+ return output;
3493
+ }
3494
+ function encodeUserinfo(input) {
3495
+ return encodeComponent(input, isUserinfoCharacter);
3496
+ }
3497
+ function encodeQuery(input) {
3498
+ return encodeComponent(input, isQueryFragmentCharacter);
3499
+ }
3500
+ function encodeFragment(input) {
3501
+ return encodeComponent(input, isQueryFragmentCharacter);
3502
+ }
3503
+ function isEscapeSafe(cp) {
3504
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
3505
+ }
3506
+ function normalizeQueryFragmentEncoding(input) {
3507
+ let output = "";
3508
+ for (let i = 0; i < input.length; i++) {
3509
+ const ch = input[i];
3510
+ if (ch === "%" && i + 2 < input.length) {
3511
+ const hex3 = input.slice(i + 1, i + 3);
3512
+ if (isHexPair(hex3)) {
3513
+ const normalizedHex = hex3.toUpperCase();
3514
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3515
+ if (isUnreserved(decoded)) {
3516
+ output += decoded;
3517
+ } else {
3518
+ output += "%" + normalizedHex;
3519
+ }
3520
+ i += 2;
3521
+ continue;
3522
+ }
3523
+ }
3524
+ if (isQueryFragmentCharacter(ch)) {
3525
+ output += ch;
3365
3526
  } else {
3366
- output += escape(input[i]);
3527
+ const code = input.charCodeAt(i);
3528
+ if (code < 128) {
3529
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3530
+ } else if (code < 55296 || code > 57343) {
3531
+ output += percentEncodeNonAscii(code);
3532
+ } else if (code <= 56319 && i + 1 < input.length) {
3533
+ const low = input.charCodeAt(i + 1);
3534
+ if (low >= 56320 && low <= 57343) {
3535
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3536
+ i++;
3537
+ } else {
3538
+ output += percentEncodeNonAscii(65533);
3539
+ }
3540
+ } else {
3541
+ output += percentEncodeNonAscii(65533);
3542
+ }
3367
3543
  }
3368
3544
  }
3369
3545
  return output;
@@ -3386,14 +3562,18 @@ var require_utils = __commonJS({
3386
3562
  function recomposeAuthority(component) {
3387
3563
  const uriTokens = [];
3388
3564
  if (component.userinfo !== void 0) {
3389
- uriTokens.push(component.userinfo);
3565
+ uriTokens.push(encodeUserinfo(component.userinfo));
3390
3566
  uriTokens.push("@");
3391
3567
  }
3392
3568
  if (component.host !== void 0) {
3393
- let host = unescape(component.host);
3569
+ let host = component.host;
3394
3570
  if (!isIPv4(host)) {
3395
- const ipV6res = normalizeIPv6(host);
3396
- if (ipV6res.isIPV6 === true) {
3571
+ let ipV6res = normalizeIPv6(host);
3572
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3573
+ host = normalizePercentEncoding(host, true);
3574
+ ipV6res = normalizeIPv6(host);
3575
+ }
3576
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3397
3577
  host = `[${ipV6res.escapedHost}]`;
3398
3578
  } else {
3399
3579
  host = reescapeHostDelimiters(host, false);
@@ -3402,8 +3582,12 @@ var require_utils = __commonJS({
3402
3582
  uriTokens.push(host);
3403
3583
  }
3404
3584
  if (typeof component.port === "number" || typeof component.port === "string") {
3585
+ const port = String(component.port);
3586
+ if (!isPort(port)) {
3587
+ throw new TypeError("URI port is malformed.");
3588
+ }
3405
3589
  uriTokens.push(":");
3406
- uriTokens.push(String(component.port));
3590
+ uriTokens.push(port);
3407
3591
  }
3408
3592
  return uriTokens.length ? uriTokens.join("") : void 0;
3409
3593
  }
@@ -3413,6 +3597,11 @@ var require_utils = __commonJS({
3413
3597
  reescapeHostDelimiters,
3414
3598
  normalizePercentEncoding,
3415
3599
  normalizePathEncoding,
3600
+ serializePathEncoding,
3601
+ normalizeQueryFragmentEncoding,
3602
+ encodeUserinfo,
3603
+ encodeQuery,
3604
+ encodeFragment,
3416
3605
  escapePreservingEscapes,
3417
3606
  removeDotSegments,
3418
3607
  isIPv4,
@@ -3428,7 +3617,7 @@ var require_schemes = __commonJS({
3428
3617
  "node_modules/fast-uri/lib/schemes.js"(exports, module) {
3429
3618
  "use strict";
3430
3619
  var { isUUID } = require_utils();
3431
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3620
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3432
3621
  var supportedSchemeNames = (
3433
3622
  /** @type {const} */
3434
3623
  [
@@ -3489,9 +3678,10 @@ var require_schemes = __commonJS({
3489
3678
  wsComponent.secure = void 0;
3490
3679
  }
3491
3680
  if (wsComponent.resourceName) {
3492
- const [path, query] = wsComponent.resourceName.split("?");
3681
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3682
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3493
3683
  wsComponent.path = path && path !== "/" ? path : void 0;
3494
- wsComponent.query = query;
3684
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3495
3685
  wsComponent.resourceName = void 0;
3496
3686
  }
3497
3687
  wsComponent.fragment = void 0;
@@ -3503,7 +3693,7 @@ var require_schemes = __commonJS({
3503
3693
  return urnComponent;
3504
3694
  }
3505
3695
  const matches = urnComponent.path.match(URN_REG);
3506
- if (matches) {
3696
+ if (matches && matches[0] === urnComponent.path) {
3507
3697
  const scheme = options.scheme || urnComponent.scheme || "urn";
3508
3698
  urnComponent.nid = matches[1].toLowerCase();
3509
3699
  urnComponent.nss = matches[2];
@@ -3637,8 +3827,17 @@ var require_schemes = __commonJS({
3637
3827
  var require_fast_uri = __commonJS({
3638
3828
  "node_modules/fast-uri/index.js"(exports, module) {
3639
3829
  "use strict";
3640
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3830
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3641
3831
  var { SCHEMES, getSchemeHandler } = require_schemes();
3832
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3833
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3834
+ function decodeValidScheme(scheme) {
3835
+ const decodedScheme = unescape(String(scheme));
3836
+ if (!VALID_SCHEME.test(decodedScheme)) {
3837
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3838
+ }
3839
+ return decodedScheme;
3840
+ }
3642
3841
  function normalize(uri, options) {
3643
3842
  if (typeof uri === "string") {
3644
3843
  uri = /** @type {T} */
@@ -3651,12 +3850,34 @@ var require_fast_uri = __commonJS({
3651
3850
  }
3652
3851
  function resolve3(baseURI, relativeURI, options) {
3653
3852
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3654
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3655
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3656
- if (baseMalformed || relativeMalformed) {
3853
+ const {
3854
+ parsed: baseParsed,
3855
+ malformedAuthorityOrPort: baseMalformed,
3856
+ malformedPercentEncoding: baseMalformedPercentEncoding,
3857
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
3858
+ malformedHost: baseMalformedHost,
3859
+ malformedScheme: baseMalformedScheme
3860
+ } = parseWithStatus(baseURI, schemelessOptions);
3861
+ const {
3862
+ parsed: relativeParsed,
3863
+ malformedAuthorityOrPort: relativeMalformed,
3864
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
3865
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
3866
+ malformedHost: relativeMalformedHost,
3867
+ malformedScheme: relativeMalformedScheme
3868
+ } = parseWithStatus(relativeURI, schemelessOptions);
3869
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3657
3870
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3658
3871
  }
3659
3872
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3873
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3874
+ const resolvedHost = resolved.host;
3875
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3876
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3877
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3878
+ if (resolved.error && !encodedASCIIHost) {
3879
+ throw new Error(resolved.error);
3880
+ }
3660
3881
  schemelessOptions.skipEscape = true;
3661
3882
  return serialize(resolved, schemelessOptions);
3662
3883
  }
@@ -3716,7 +3937,7 @@ var require_fast_uri = __commonJS({
3716
3937
  function equal(uriA, uriB, options) {
3717
3938
  const normalizedA = normalizeComparableURI(uriA, options);
3718
3939
  const normalizedB = normalizeComparableURI(uriB, options);
3719
- return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3940
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
3720
3941
  }
3721
3942
  function serialize(cmpts, opts) {
3722
3943
  const component = {
@@ -3737,19 +3958,22 @@ var require_fast_uri = __commonJS({
3737
3958
  };
3738
3959
  const options = Object.assign({}, opts);
3739
3960
  const uriTokens = [];
3961
+ if (component.scheme) {
3962
+ component.scheme = decodeValidScheme(component.scheme);
3963
+ }
3740
3964
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3741
3965
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3966
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
3967
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3742
3968
  if (component.path !== void 0) {
3743
3969
  if (!options.skipEscape) {
3744
- component.path = escapePreservingEscapes(component.path);
3745
- if (component.scheme !== void 0) {
3746
- component.path = component.path.split("%3A").join(":");
3747
- }
3970
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3748
3971
  } else {
3749
3972
  component.path = normalizePercentEncoding(component.path);
3750
3973
  }
3751
3974
  }
3752
3975
  if (options.reference !== "suffix" && component.scheme) {
3976
+ component.scheme = decodeValidScheme(component.scheme);
3753
3977
  uriTokens.push(component.scheme, ":");
3754
3978
  }
3755
3979
  const authority = recomposeAuthority(component);
@@ -3767,16 +3991,19 @@ var require_fast_uri = __commonJS({
3767
3991
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3768
3992
  s = removeDotSegments(s);
3769
3993
  }
3994
+ if (pathNoScheme) {
3995
+ s = serializePathEncoding(s, true);
3996
+ }
3770
3997
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
3771
3998
  s = "/%2F" + s.slice(2);
3772
3999
  }
3773
4000
  uriTokens.push(s);
3774
4001
  }
3775
4002
  if (component.query !== void 0) {
3776
- uriTokens.push("?", component.query);
4003
+ uriTokens.push("?", encodeQuery(component.query));
3777
4004
  }
3778
4005
  if (component.fragment !== void 0) {
3779
- uriTokens.push("#", component.fragment);
4006
+ uriTokens.push("#", encodeFragment(component.fragment));
3780
4007
  }
3781
4008
  return uriTokens.join("");
3782
4009
  }
@@ -3792,6 +4019,35 @@ var require_fast_uri = __commonJS({
3792
4019
  }
3793
4020
  return void 0;
3794
4021
  }
4022
+ function hasMalformedPercentEncoding(component) {
4023
+ if (component === void 0) return false;
4024
+ let percent = component.indexOf("%");
4025
+ while (percent !== -1) {
4026
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
4027
+ return true;
4028
+ }
4029
+ percent = component.indexOf("%", percent + 3);
4030
+ }
4031
+ return false;
4032
+ }
4033
+ function isIPLiteral(host) {
4034
+ return host[0] === "[" && host[host.length - 1] === "]";
4035
+ }
4036
+ function hasMalformedComponentPercentEncoding(matches) {
4037
+ const host = matches[4];
4038
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4039
+ }
4040
+ function canonicalizeHost(parsed, options, schemeHandler, isIP2) {
4041
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
4042
+ try {
4043
+ parsed.host = new URL("http://" + parsed.host).hostname;
4044
+ } catch (e) {
4045
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4046
+ return true;
4047
+ }
4048
+ }
4049
+ return false;
4050
+ }
3795
4051
  function parseWithStatus(uri, opts) {
3796
4052
  const options = Object.assign({}, opts);
3797
4053
  const parsed = {
@@ -3804,6 +4060,11 @@ var require_fast_uri = __commonJS({
3804
4060
  fragment: void 0
3805
4061
  };
3806
4062
  let malformedAuthorityOrPort = false;
4063
+ let malformedPercentEncoding = false;
4064
+ let malformedSchemeSpecific = false;
4065
+ let malformedHost = false;
4066
+ let malformedIPLiteral = false;
4067
+ let malformedScheme = false;
3807
4068
  let isIP2 = false;
3808
4069
  if (options.reference === "suffix") {
3809
4070
  if (options.scheme) {
@@ -3840,6 +4101,19 @@ var require_fast_uri = __commonJS({
3840
4101
  parsed.path = matches[6] || "";
3841
4102
  parsed.query = matches[7];
3842
4103
  parsed.fragment = matches[8];
4104
+ if (parsed.scheme !== void 0) {
4105
+ const decodedScheme = unescape(parsed.scheme);
4106
+ if (VALID_SCHEME.test(decodedScheme)) {
4107
+ parsed.scheme = decodedScheme.toLowerCase();
4108
+ } else {
4109
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4110
+ malformedScheme = true;
4111
+ }
4112
+ }
4113
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4114
+ if (malformedPercentEncoding) {
4115
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4116
+ }
3843
4117
  if (isNaN(parsed.port)) {
3844
4118
  parsed.port = matches[5];
3845
4119
  }
@@ -3851,9 +4125,16 @@ var require_fast_uri = __commonJS({
3851
4125
  if (parsed.host) {
3852
4126
  const ipv4result = isIPv4(parsed.host);
3853
4127
  if (ipv4result === false) {
4128
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
4129
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
3854
4130
  const ipv6result = normalizeIPv6(parsed.host);
3855
- parsed.host = ipv6result.host.toLowerCase();
3856
- isIP2 = ipv6result.isIPV6;
4131
+ isIP2 = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4132
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4133
+ parsed.host = isIP2 ? ipv6result.host : ipv6result.host.toLowerCase();
4134
+ if (malformedIPLiteral) {
4135
+ parsed.error = parsed.error || "URI host is malformed.";
4136
+ malformedAuthorityOrPort = true;
4137
+ }
3857
4138
  } else {
3858
4139
  isIP2 = true;
3859
4140
  }
@@ -3871,42 +4152,36 @@ var require_fast_uri = __commonJS({
3871
4152
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3872
4153
  }
3873
4154
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3874
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3875
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
3876
- try {
3877
- parsed.host = new URL("http://" + parsed.host).hostname;
3878
- } catch (e) {
3879
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3880
- }
3881
- }
4155
+ if (!malformedIPLiteral) {
4156
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP2);
3882
4157
  }
3883
4158
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3884
4159
  if (uri.indexOf("%") !== -1) {
3885
- if (parsed.scheme !== void 0) {
3886
- parsed.scheme = unescape(parsed.scheme);
3887
- }
3888
- if (parsed.host !== void 0) {
3889
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP2);
4160
+ if (parsed.host !== void 0 && !malformedIPLiteral) {
4161
+ const host = isIP2 ? parsed.host : normalizePercentEncoding(parsed.host, true);
4162
+ parsed.host = reescapeHostDelimiters(host, isIP2);
3890
4163
  }
3891
4164
  }
3892
4165
  if (parsed.path) {
3893
4166
  parsed.path = normalizePathEncoding(parsed.path);
3894
4167
  }
4168
+ if (parsed.query) {
4169
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
4170
+ }
3895
4171
  if (parsed.fragment) {
3896
- try {
3897
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3898
- } catch {
3899
- parsed.error = parsed.error || "URI malformed";
3900
- }
4172
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3901
4173
  }
3902
4174
  }
3903
4175
  if (schemeHandler && schemeHandler.parse) {
3904
4176
  schemeHandler.parse(parsed, options);
4177
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
4178
+ malformedSchemeSpecific = true;
4179
+ }
3905
4180
  }
3906
4181
  } else {
3907
4182
  parsed.error = parsed.error || "URI can not be parsed.";
3908
4183
  }
3909
- return { parsed, malformedAuthorityOrPort };
4184
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3910
4185
  }
3911
4186
  function parse3(uri, opts) {
3912
4187
  return parseWithStatus(uri, opts).parsed;
@@ -3915,20 +4190,28 @@ var require_fast_uri = __commonJS({
3915
4190
  return normalizeStringWithStatus(uri, opts).normalized;
3916
4191
  }
3917
4192
  function normalizeStringWithStatus(uri, opts) {
3918
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4193
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3919
4194
  return {
3920
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3921
- malformedAuthorityOrPort
4195
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4196
+ malformedAuthorityOrPort,
4197
+ malformedPercentEncoding,
4198
+ malformedSchemeSpecific,
4199
+ malformedHost,
4200
+ malformedScheme
3922
4201
  };
3923
4202
  }
3924
4203
  function normalizeComparableURI(uri, opts) {
3925
- if (typeof uri === "string") {
3926
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3927
- return malformedAuthorityOrPort ? void 0 : normalized;
4204
+ if (typeof uri !== "string" && typeof uri !== "object") {
4205
+ return void 0;
3928
4206
  }
3929
- if (typeof uri === "object") {
3930
- return serialize(uri, opts);
4207
+ let value;
4208
+ try {
4209
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4210
+ } catch {
4211
+ return void 0;
3931
4212
  }
4213
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4214
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
3932
4215
  }
3933
4216
  var fastUri = {
3934
4217
  SCHEMES,
@@ -34678,6 +34961,7 @@ var StdioServerTransport = class {
34678
34961
  };
34679
34962
 
34680
34963
  // node_modules/@chrischall/mcp-utils/dist/errors/index.js
34964
+ var DEFAULT_ERROR_MESSAGE_MAX = 500;
34681
34965
  var McpToolError = class extends Error {
34682
34966
  /** Actionable remediation text, when one applies. */
34683
34967
  hint;
@@ -34719,13 +35003,62 @@ var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*('
34719
35003
  function redactSecrets(text) {
34720
35004
  return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
34721
35005
  }
35006
+ function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
35007
+ const str = text === null || text === void 0 ? "" : String(text);
35008
+ const redacted = redactSecrets(str);
35009
+ if (redacted.length <= max)
35010
+ return redacted;
35011
+ return `${redacted.slice(0, max)}\u2026 [truncated]`;
35012
+ }
35013
+ function messageOf(err) {
35014
+ if (err instanceof Error)
35015
+ return err.message;
35016
+ return String(err);
35017
+ }
34722
35018
 
34723
- // node_modules/@chrischall/mcp-utils/dist/response/index.js
34724
- function textResult(data) {
34725
- return {
34726
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
34727
- };
35019
+ // node_modules/@chrischall/mcp-utils/dist/response/view.js
35020
+ var VIEWS = ["compact", "full", "raw"];
35021
+ var DEFAULT_VIEW = "compact";
35022
+ var BLURB = {
35023
+ compact: '"compact" (default) drops fields the response already carries elsewhere',
35024
+ full: '"full" returns every field this server understands',
35025
+ raw: '"raw" returns the upstream payload unprojected'
35026
+ };
35027
+ function viewParam(honoured, opts = {}) {
35028
+ if (honoured.length < 2) {
35029
+ throw new Error("viewParam needs at least two rungs: a parameter offering one value decides nothing");
35030
+ }
35031
+ if (!honoured.includes("compact")) {
35032
+ throw new Error('viewParam must offer "compact": a tool with no cheap rung has nothing to default to');
35033
+ }
35034
+ const ordered = VIEWS.filter((v) => honoured.includes(v));
35035
+ const sentence = `Response shape: ${ordered.map((v) => BLURB[v]).join("; ")}.`;
35036
+ return external_exports.enum(Object.fromEntries(ordered.map((v) => [v, v]))).optional().describe(opts.note ? `${sentence} ${opts.note}` : sentence);
35037
+ }
35038
+ function resolveView(value, honoured) {
35039
+ return value !== void 0 && honoured.includes(value) ? value : DEFAULT_VIEW;
34728
35040
  }
35041
+ function minifiedResult(data) {
35042
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
35043
+ }
35044
+ function projectOrRaw(value, project, opts) {
35045
+ try {
35046
+ const projected = project(value);
35047
+ if (projected === void 0) {
35048
+ warn(opts, "the projection produced nothing");
35049
+ return value;
35050
+ }
35051
+ return projected;
35052
+ } catch (error51) {
35053
+ warn(opts, error51 instanceof Error ? error51.message : String(error51));
35054
+ return value;
35055
+ }
35056
+ }
35057
+ function warn(opts, why) {
35058
+ console.error(`[${opts.label}] WARNING: could not project ${opts.context} (${why}); returning the unprojected payload. The upstream shape has probably changed \u2014 the projection needs updating.`);
35059
+ }
35060
+
35061
+ // node_modules/@chrischall/mcp-utils/dist/response/index.js
34729
35062
  function rawTextResult(text) {
34730
35063
  return { content: [{ type: "text", text }] };
34731
35064
  }
@@ -34735,6 +35068,14 @@ function errorResult(message) {
34735
35068
  isError: true
34736
35069
  };
34737
35070
  }
35071
+ function pruneUndefined(obj) {
35072
+ const out = {};
35073
+ for (const key of Object.keys(obj)) {
35074
+ if (obj[key] !== void 0)
35075
+ out[key] = obj[key];
35076
+ }
35077
+ return out;
35078
+ }
34738
35079
 
34739
35080
  // node_modules/@chrischall/mcp-utils/dist/server/index.js
34740
35081
  function hintResultOrRethrow(err) {
@@ -39377,6 +39718,144 @@ var BootstrapDisabledError = class extends Error {
39377
39718
  }
39378
39719
  };
39379
39720
 
39721
+ // node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
39722
+ function statusOf(err) {
39723
+ if (typeof err !== "object" || err === null)
39724
+ return void 0;
39725
+ const s = err.status ?? err.statusCode;
39726
+ return typeof s === "number" ? s : void 0;
39727
+ }
39728
+ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
39729
+ "ok",
39730
+ "no_credential",
39731
+ "credential_rejected",
39732
+ "timeout",
39733
+ "http",
39734
+ "transport",
39735
+ "unknown"
39736
+ ]);
39737
+ function isArm(kind) {
39738
+ return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
39739
+ }
39740
+ function credentialHint(arm, prefix, hostLabel, source) {
39741
+ switch (arm) {
39742
+ case "ok":
39743
+ return `Credential from '${source}' works: ${hostLabel} accepted an authenticated request. If a real tool still fails, the problem is that tool, not auth.`;
39744
+ case "no_credential":
39745
+ return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
39746
+ case "credential_rejected":
39747
+ return `${hostLabel} rejected the credential from '${source}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
39748
+ case "timeout":
39749
+ return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
39750
+ case "http":
39751
+ return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
39752
+ case "transport":
39753
+ return `Could not reach ${hostLabel} at all. Check network egress; the credential itself was never judged.`;
39754
+ default:
39755
+ return `Unexpected failure \u2014 see error.message.`;
39756
+ }
39757
+ }
39758
+ function registerCredentialHealthcheckTool(args) {
39759
+ const { server, prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
39760
+ const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
39761
+ server.registerTool(`${prefix}_healthcheck`, {
39762
+ title: "Verify credentials and upstream reachability",
39763
+ description: `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only; never returns the credential itself.`,
39764
+ annotations: {
39765
+ title: "Verify credentials and upstream reachability",
39766
+ readOnlyHint: true,
39767
+ idempotentHint: true,
39768
+ openWorldHint: true
39769
+ },
39770
+ inputSchema: {}
39771
+ }, async () => {
39772
+ let probeStarted = 0;
39773
+ let state;
39774
+ try {
39775
+ state = await resolveCredential();
39776
+ } catch (e) {
39777
+ const classified = classifyThrown?.(e);
39778
+ const result2 = {
39779
+ ok: false,
39780
+ // Still false, and still no source: a classification explains WHY
39781
+ // nothing resolved, it does not invent a credential that did.
39782
+ credential: { source: null, resolved: false },
39783
+ // No `url`: nothing was probed, and naming one implies it was tried.
39784
+ probe: { elapsed_ms: 0 },
39785
+ error: {
39786
+ kind: classified?.kind ?? "no_credential",
39787
+ message: truncateErrorMessage(messageOf(e)),
39788
+ ...classified?.detail !== void 0 ? { detail: classified.detail } : {}
39789
+ },
39790
+ // The hint must follow the KIND beside it. Falling back to
39791
+ // `no_credential`'s copy under a classified kind would state a cause
39792
+ // the kind contradicts — the same disagreement this path exists to
39793
+ // remove. So: an inline hint wins; else the classified arm's own
39794
+ // copy (consumer override first); else, for a kind this module has
39795
+ // no copy for, the neutral `unknown` text rather than one that
39796
+ // asserts a cause; else the unclassified `no_credential` default.
39797
+ hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
39798
+ };
39799
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
39800
+ }
39801
+ const credential = {
39802
+ source: state.source,
39803
+ resolved: state.source !== null,
39804
+ ...state.detail !== void 0 ? { detail: state.detail } : {}
39805
+ };
39806
+ if (!credential.resolved) {
39807
+ const result2 = {
39808
+ ok: false,
39809
+ credential,
39810
+ probe: { elapsed_ms: 0 },
39811
+ error: { kind: "no_credential", message: "no credential source resolved" },
39812
+ hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
39813
+ };
39814
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
39815
+ }
39816
+ let arm = "ok";
39817
+ let error51;
39818
+ let status;
39819
+ let customHint;
39820
+ probeStarted = Date.now();
39821
+ try {
39822
+ await probeFn();
39823
+ } catch (e) {
39824
+ status = statusOf(e);
39825
+ const aborted2 = e instanceof Error && e.name === "AbortError";
39826
+ arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
39827
+ let kind = arm;
39828
+ let detail;
39829
+ const custom2 = classifyThrown?.(e);
39830
+ if (custom2) {
39831
+ kind = custom2.kind;
39832
+ customHint = custom2.hint;
39833
+ detail = custom2.detail;
39834
+ }
39835
+ error51 = {
39836
+ kind,
39837
+ // Redacted AND bounded before it reaches the result: an upstream
39838
+ // failure routinely quotes what it was sent, and a healthcheck is
39839
+ // the tool people paste into a chat when something is broken.
39840
+ message: truncateErrorMessage(messageOf(e)),
39841
+ ...detail !== void 0 ? { detail } : {}
39842
+ };
39843
+ }
39844
+ const result = {
39845
+ ok: error51 === void 0,
39846
+ credential,
39847
+ probe: {
39848
+ ...probeUrl ? { url: probeUrl } : {},
39849
+ elapsed_ms: Date.now() - probeStarted,
39850
+ ...status !== void 0 ? { status } : {}
39851
+ },
39852
+ ...error51 ? { error: error51 } : {},
39853
+ hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
39854
+ };
39855
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
39856
+ });
39857
+ }
39858
+
39380
39859
  // src/protocol.ts
39381
39860
  var BASE_URL = "https://ofw.ourfamilywizard.com";
39382
39861
  var OFW_PROTOCOL_HEADERS = {
@@ -39431,7 +39910,7 @@ async function loginWithPassword(username, password) {
39431
39910
  // package.json
39432
39911
  var package_default = {
39433
39912
  name: "ofw-mcp",
39434
- version: "2.13.0",
39913
+ version: "2.15.0",
39435
39914
  license: "MIT",
39436
39915
  mcpName: "io.github.chrischall/ofw-mcp",
39437
39916
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -39465,7 +39944,7 @@ var package_default = {
39465
39944
  typecheck: "tsc -p tsconfig.json --noEmit"
39466
39945
  },
39467
39946
  dependencies: {
39468
- "@chrischall/mcp-utils": "^0.18.0",
39947
+ "@chrischall/mcp-utils": "^0.21.0",
39469
39948
  "@fetchproxy/bootstrap": "^2.2.0",
39470
39949
  "@modelcontextprotocol/sdk": "^1.29.0",
39471
39950
  dotenv: "^17.4.2",
@@ -39484,6 +39963,14 @@ var package_default = {
39484
39963
  function fetchproxyDisabled() {
39485
39964
  return parseBoolEnv("OFW_DISABLE_FETCHPROXY");
39486
39965
  }
39966
+ var NO_AUTH_CONFIGURED = "OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install the fetchproxy extension and sign into ourfamilywizard.com (unset OFW_DISABLE_FETCHPROXY if it is set).";
39967
+ var BRIDGE_DOWN_PREFIX = "OFW auth: fetchproxy bridge is down";
39968
+ function isBridgeDown(e) {
39969
+ return e instanceof Error && e.message.startsWith(BRIDGE_DOWN_PREFIX);
39970
+ }
39971
+ function isNoAuthConfigured(e) {
39972
+ return e instanceof Error && e.message === NO_AUTH_CONFIGURED;
39973
+ }
39487
39974
  async function resolveAuth() {
39488
39975
  const pattern = {};
39489
39976
  const username = readEnvVar("OFW_USERNAME");
@@ -39531,7 +40018,7 @@ async function resolveAuth() {
39531
40018
  if (classifyBridgeError(e) === "bridge_down") {
39532
40019
  const downErr = e;
39533
40020
  throw new Error(
39534
- `OFW auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
40021
+ `${BRIDGE_DOWN_PREFIX} (extension service worker unreachable after retry). ${downErr.hint}`
39535
40022
  );
39536
40023
  }
39537
40024
  const msg = e instanceof Error ? e.message : String(e);
@@ -39542,9 +40029,7 @@ async function resolveAuth() {
39542
40029
  };
39543
40030
  }
39544
40031
  if (!pattern.sessionScrape && !pattern.fetchproxy) {
39545
- throw new Error(
39546
- "OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install the fetchproxy extension and sign into ourfamilywizard.com (unset OFW_DISABLE_FETCHPROXY if it is set)."
39547
- );
40032
+ throw new Error(NO_AUTH_CONFIGURED);
39548
40033
  }
39549
40034
  const { credential, source, expiresAt } = await resolveAuthPattern(pattern);
39550
40035
  return {
@@ -39961,7 +40446,7 @@ function normalizeTimestampsInValue(value, tz = displayTimeZone()) {
39961
40446
 
39962
40447
  // src/tools/_shared.ts
39963
40448
  function jsonResponse(data) {
39964
- return textResult(normalizeTimestampsInValue(data));
40449
+ return minifiedResult(normalizeTimestampsInValue(data));
39965
40450
  }
39966
40451
  var textResponse = rawTextResult;
39967
40452
  function jsonErrorResponse(data) {
@@ -40068,6 +40553,51 @@ function registerUserTools(server, client2) {
40068
40553
  });
40069
40554
  }
40070
40555
 
40556
+ // src/tools/healthcheck.ts
40557
+ function registerHealthcheckTools(server, client2, resolve3 = resolveAuth) {
40558
+ registerCredentialHealthcheckTool({
40559
+ server,
40560
+ prefix: "ofw",
40561
+ hostLabel: "ourfamilywizard.com",
40562
+ // The same read `ofw_get_profile` makes: authenticated, cheap, and it
40563
+ // changes nothing. A healthcheck that marked a message read would be
40564
+ // co-parent-visible and irreversible.
40565
+ probePath: "/pub/v2/profiles",
40566
+ resolveCredential: async () => {
40567
+ try {
40568
+ const auth = await resolve3();
40569
+ return {
40570
+ source: auth.source,
40571
+ // Never the token. Expiry is the fact that explains a connector
40572
+ // that worked an hour ago and does not now.
40573
+ detail: auth.expiresAt ? { expires_at: auth.expiresAt.toISOString() } : void 0
40574
+ };
40575
+ } catch (e) {
40576
+ if (isNoAuthConfigured(e)) return { source: null };
40577
+ throw e;
40578
+ }
40579
+ },
40580
+ probeFn: () => client2.request("GET", "/pub/v2/profiles"),
40581
+ // A downed bridge is not a missing credential, and since mcp-utils 0.19.3
40582
+ // the helper consults this for a `resolveCredential` failure too — so it
40583
+ // gets its own arm instead of the `no_credential` copy. That copy could
40584
+ // previously only hedge across both cases and point at `error.message`;
40585
+ // now each answer names one cause and one fix.
40586
+ classifyThrown: (err) => isBridgeDown(err) ? {
40587
+ kind: "transport",
40588
+ // The upstream `.hint` rides along in `error.message` — it carries
40589
+ // the actionable "click the toolbar icon" copy this cannot know.
40590
+ hint: "The fetchproxy bridge is down, so the browser path could not be tried. This is not a credential problem: OFW_USERNAME/OFW_PASSWORD, if set, were not reached either. See error.message for the extension-specific fix."
40591
+ } : void 0,
40592
+ hints: {
40593
+ // Now means exactly what it says: nothing is set up. A configured path
40594
+ // that was tried and failed no longer lands here.
40595
+ no_credential: "No OFW credential is configured. Either set OFW_USERNAME + OFW_PASSWORD, or install the fetchproxy extension and sign in to ourfamilywizard.com in a tab (unsetting OFW_DISABLE_FETCHPROXY if you set it).",
40596
+ credential_rejected: "OurFamilyWizard rejected the credential. If it came from `env`, the password changed or the account is locked; if from `fetchproxy`, the browser session expired \u2014 sign in again in the tab. Retrying will not fix either."
40597
+ }
40598
+ });
40599
+ }
40600
+
40071
40601
  // src/sync.ts
40072
40602
  var FileMetaSchema = external_exports.looseObject({
40073
40603
  fileId: external_exports.number(),
@@ -42069,6 +42599,101 @@ function withPaginationFirst(input) {
42069
42599
  return { ...head, ...body, ...head };
42070
42600
  }
42071
42601
 
42602
+ // src/tools/project.ts
42603
+ var MESSAGE_VIEWS = ["compact", "full"];
42604
+ var LABEL = "ofw-mcp";
42605
+ function asRecord2(value) {
42606
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
42607
+ }
42608
+ function senderOf(row) {
42609
+ if (typeof row.fromUser === "string" && row.fromUser !== "") return row.fromUser;
42610
+ const author = asRecord2(asRecord2(row.listData)?.author);
42611
+ const name = author?.name;
42612
+ return typeof name === "string" && name !== "" ? name : null;
42613
+ }
42614
+ function fileCountOf(row) {
42615
+ const files = asRecord2(row.listData)?.files;
42616
+ if (typeof files === "number") return files;
42617
+ if (Array.isArray(files)) return files.length;
42618
+ return void 0;
42619
+ }
42620
+ function repliedOf(row) {
42621
+ const replied = asRecord2(row.listData)?.replied;
42622
+ return typeof replied === "boolean" ? replied : void 0;
42623
+ }
42624
+ function compactMessage(row) {
42625
+ const { listData: _drop, fromUser: _dropFrom, ...rest } = row;
42626
+ return pruneUndefined({
42627
+ id: rest.id,
42628
+ folder: rest.folder,
42629
+ subject: rest.subject,
42630
+ from: senderOf(row),
42631
+ sentAt: rest.sentAt,
42632
+ recipients: rest.recipients,
42633
+ read: rest.read,
42634
+ replied: repliedOf(row),
42635
+ files: fileCountOf(row),
42636
+ replyToId: rest.replyToId,
42637
+ chainRootId: rest.chainRootId,
42638
+ body: rest.body,
42639
+ fetchedBodyAt: rest.fetchedBodyAt,
42640
+ // Anything a TOOL added on top of the cache row — `attachments`,
42641
+ // `revision`, `cacheStatus`, `serverConfirmed`, `freshness` — is kept
42642
+ // wholesale. Those are this server's own answers, never OFW's echo, and a
42643
+ // projection that dropped one would be removing the thing the caller
42644
+ // asked for rather than the thing they got twice.
42645
+ ...omitKnown(rest, MESSAGE_ROW_KEYS)
42646
+ });
42647
+ }
42648
+ var MESSAGE_ROW_KEYS = /* @__PURE__ */ new Set([
42649
+ "id",
42650
+ "folder",
42651
+ "subject",
42652
+ "sentAt",
42653
+ "recipients",
42654
+ "read",
42655
+ "replyToId",
42656
+ "chainRootId",
42657
+ "body",
42658
+ "fetchedBodyAt"
42659
+ ]);
42660
+ var DRAFT_ROW_KEYS = /* @__PURE__ */ new Set(["id", "subject", "recipients", "replyToId", "modifiedAt", "body"]);
42661
+ function omitKnown(rest, own) {
42662
+ const out = {};
42663
+ for (const [key, value] of Object.entries(rest)) if (!own.has(key)) out[key] = value;
42664
+ return out;
42665
+ }
42666
+ function compactDraft(row) {
42667
+ const { listData: _drop, ...rest } = row;
42668
+ return pruneUndefined({
42669
+ id: rest.id,
42670
+ subject: rest.subject,
42671
+ recipients: rest.recipients,
42672
+ replyToId: rest.replyToId,
42673
+ modifiedAt: rest.modifiedAt,
42674
+ body: rest.body,
42675
+ ...omitKnown(rest, DRAFT_ROW_KEYS)
42676
+ });
42677
+ }
42678
+ function viewMessages(view, rows) {
42679
+ if (view !== "compact") return rows;
42680
+ return projectOrRaw(rows, (rs) => rs.map((r) => compactMessage(r)), {
42681
+ label: LABEL,
42682
+ context: "the cached message rows"
42683
+ });
42684
+ }
42685
+ function viewDrafts(view, rows) {
42686
+ if (view !== "compact") return rows;
42687
+ return projectOrRaw(rows, (rs) => rs.map((r) => compactDraft(r)), {
42688
+ label: LABEL,
42689
+ context: "the cached draft rows"
42690
+ });
42691
+ }
42692
+ function viewOne(view, row) {
42693
+ if (view !== "compact") return row;
42694
+ return projectOrRaw(row, (r) => compactMessage(r), { label: LABEL, context: "a cached message row" });
42695
+ }
42696
+
42072
42697
  // src/tools/messages.ts
42073
42698
  var DateSchema = external_exports.looseObject({ dateTime: external_exports.string() });
42074
42699
  var SentDetailSchema = external_exports.looseObject({
@@ -42215,12 +42840,16 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42215
42840
  until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive)").optional(),
42216
42841
  q: external_exports.string().describe("Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.").optional(),
42217
42842
  sort: external_exports.enum(["newest", "oldest"]).describe('Result order: "newest" (default, newest first) or "oldest" (oldest first). This decides which end a truncated page keeps \u2014 with "newest" page 1 of a wide date range holds its most RECENT slice, with "oldest" its earliest. Use "oldest" to start at the old end of a range instead of paging to it.').optional(),
42218
- autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional()
42843
+ autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional(),
42844
+ view: viewParam(MESSAGE_VIEWS, {
42845
+ note: "compact omits OurFamilyWizard's raw `listData` echo, which duplicates this record's own id, subject, sentAt, recipients and read flag; the sender is promoted to `from`, and `files`/`replied` are kept. Pass \"full\" for the echo."
42846
+ })
42219
42847
  }
42220
42848
  }, async (args) => {
42221
42849
  const page = args.page ?? 1;
42222
42850
  const size = args.size ?? 50;
42223
42851
  const sort = args.sort ?? "newest";
42852
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42224
42853
  const folderArg = args.folderId ?? "both";
42225
42854
  let folder;
42226
42855
  if (folderArg === "inbox") folder = "inbox";
@@ -42292,7 +42921,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42292
42921
  payload.autoRefreshed = true;
42293
42922
  }
42294
42923
  payload.freshness = freshness;
42295
- payload.messages = messages;
42924
+ payload.messages = viewMessages(view, messages);
42296
42925
  return jsonResponse(payload);
42297
42926
  });
42298
42927
  server.registerTool("ofw_get_message", {
@@ -42300,10 +42929,14 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42300
42929
  annotations: { readOnlyHint: false },
42301
42930
  inputSchema: {
42302
42931
  messageId: external_exports.string().describe("Message ID (also accepts draft IDs \u2014 drafts are routed via the drafts cache)"),
42303
- allowMarkRead: external_exports.boolean().describe("Default true (the long-standing behaviour). Set false to refuse a fetch that would mark an unread INBOX message as READ on OurFamilyWizard \u2014 an irreversible, co-parent-visible change to the record. Reads that cannot stamp anything (a cached body, a sent message, an already-read message) still succeed. The server-wide OFW_ALLOW_MARK_READ=false is a ceiling this argument cannot raise.").optional()
42932
+ allowMarkRead: external_exports.boolean().describe("Default true (the long-standing behaviour). Set false to refuse a fetch that would mark an unread INBOX message as READ on OurFamilyWizard \u2014 an irreversible, co-parent-visible change to the record. Reads that cannot stamp anything (a cached body, a sent message, an already-read message) still succeed. The server-wide OFW_ALLOW_MARK_READ=false is a ceiling this argument cannot raise.").optional(),
42933
+ view: viewParam(MESSAGE_VIEWS, {
42934
+ note: "compact omits OurFamilyWizard's raw `listData` echo, which duplicates this record's own id, subject, sentAt, recipients and read flag; the sender is promoted to `from`, and `files`/`replied` are kept. Pass \"full\" for the echo."
42935
+ })
42304
42936
  }
42305
42937
  }, async (args) => {
42306
42938
  const id = Number(args.messageId);
42939
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42307
42940
  const cache = cacheProvider();
42308
42941
  const draftRow = await cache.getDraft(id);
42309
42942
  if (draftRow !== null) {
@@ -42317,7 +42950,13 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42317
42950
  id: draftRow.id,
42318
42951
  folder: "drafts",
42319
42952
  subject: draftRow.subject,
42320
- fromUser: "",
42953
+ // One tool, one sender key per rung. `compact` names the sender `from`
42954
+ // everywhere else, so emitting `fromUser` here would mean the same
42955
+ // tool's compact rung used two different key names depending on
42956
+ // whether the id happened to be a draft. `null` rather than '' because
42957
+ // a draft has no sender yet — it is unsent, and an empty string reads
42958
+ // as a sender whose name we failed to find.
42959
+ ...view === "compact" ? { from: null } : { fromUser: "" },
42321
42960
  sentAt: draftRow.modifiedAt,
42322
42961
  recipients: draftRow.recipients,
42323
42962
  body: draftRow.body,
@@ -42327,7 +42966,10 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42327
42966
  fetchedBodyAt: draftRow.modifiedAt,
42328
42967
  replyToId: draftRow.replyToId,
42329
42968
  chainRootId: null,
42330
- listData: draftRow.listData,
42969
+ // OFW's raw echo, on `full` only. Everything above it is derived from
42970
+ // the DRAFTS table, which is the source of truth for a draft id — the
42971
+ // echo duplicates it and adds a stale copy of nothing else.
42972
+ ...view === "compact" ? {} : { listData: draftRow.listData },
42331
42973
  attachments: [],
42332
42974
  // Concurrency token — pass as expectedRevision to ofw_save_draft /
42333
42975
  // ofw_delete_draft / ofw_send_message to assert you are acting on
@@ -42377,7 +43019,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42377
43019
  }
42378
43020
  }
42379
43021
  const freshness2 = await buildFreshness(cache, { source: "cache", folders: [row2.folder] });
42380
- return jsonResponse({ ...withReadState(row2), attachments: attachments2, freshness: freshness2 });
43022
+ return jsonResponse({ ...viewOne(view, withReadState(row2)), attachments: attachments2, freshness: freshness2 });
42381
43023
  }
42382
43024
  const markReadCheck = markReadVerdict(cached2, args.allowMarkRead);
42383
43025
  if (markReadCheck !== null) return markReadCheck;
@@ -42412,7 +43054,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42412
43054
  }
42413
43055
  const attachments = await cache.listAttachmentsForMessage(detail.id);
42414
43056
  const freshness = await buildFreshness(cache, { source: "live", folders: [folder] });
42415
- return jsonResponse({ ...withReadState(row), attachments, freshness });
43057
+ return jsonResponse({ ...viewOne(view, withReadState(row)), attachments, freshness });
42416
43058
  });
42417
43059
  if (allowSend) server.registerTool("ofw_send_message", {
42418
43060
  description: "Send a message via OurFamilyWizard \u2014 the ONE irreversible operation here, so it carries the strongest guard. TO SEND AN EXISTING DRAFT (the safe default): pass draftId (or messageId \u2014 same thing). The tool re-reads the draft from OFW and sends the SERVER'S version, so what goes out is what is on OurFamilyWizard, not what this session remembers \u2014 subject/body act only as explicit overrides. It is guarded exactly like ofw_save_draft: pass expectedRevision to assert which version you are sending; if the draft changed on OFW since you read it \u2014 or no longer exists (it may already have been SENT) \u2014 the send is REFUSED with the current server content echoed back, and nothing goes out. RECIPIENTS: OurFamilyWizard does not persist recipients on drafts, so recipientIds is usually still required at send time (ids from ofw_get_profile). After the send is CONFIRMED (OFW returned the new message id and the re-fetched sent record matches what was posted), the source draft is deleted automatically; pass deleteDraftOnSuccess:false to keep it. On ANY failure or ambiguity the draft is never deleted \u2014 the response carries draftRetained:true with the reason. TO COMPOSE FROM SCRATCH: supply subject/body/recipientIds with no draftId. If replyToId is provided (or inherited from the draft), the cache may rewrite it to the latest reply in the same thread (a note is included when this happens). ATTACHMENTS: when sending by draftId, the server draft's own attachments carry over automatically; myFileIDs (from ofw_upload_attachment) overrides or attaches files on a fresh compose. The response leads with sentMessageId and the stable draftKey, and reports threaded (whether OFW actually linked the reply) and draftDeleted.",
@@ -42684,11 +43326,15 @@ ${JSON.stringify(
42684
43326
  page: external_exports.number().int().min(1).describe("Page number (default 1)").optional(),
42685
43327
  size: external_exports.number().int().min(1).describe("Drafts per page (default 50)").optional(),
42686
43328
  verify: external_exports.boolean().describe("Default true: when the drafts cache is not verified-fresh, run a drafts sync first (cheap \u2014 one list page plus one detail per draft) so the response is server-confirmed in one call. Set false to serve straight from the local cache with no OFW requests.").optional(),
42687
- autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional()
43329
+ autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional(),
43330
+ view: viewParam(MESSAGE_VIEWS, {
43331
+ note: "compact omits OurFamilyWizard's raw `listData` echo, which duplicates this draft's own id, subject, modifiedAt and recipients. `revision`, `draftKey` and `cacheStatus` are kept on both rungs."
43332
+ })
42688
43333
  }
42689
43334
  }, async (args) => {
42690
43335
  const page = args.page ?? 1;
42691
43336
  const size = args.size ?? 50;
43337
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42692
43338
  const cache = cacheProvider();
42693
43339
  let autoVerified = false;
42694
43340
  let verifyNote = null;
@@ -42768,7 +43414,7 @@ ${JSON.stringify(
42768
43414
  payload.verifyNote = verifyNote;
42769
43415
  }
42770
43416
  payload.freshness = freshness;
42771
- payload.drafts = drafts;
43417
+ payload.drafts = viewDrafts(view, drafts);
42772
43418
  return jsonResponse(payload);
42773
43419
  });
42774
43420
  if (allowDrafts) server.registerTool("ofw_save_draft", {
@@ -42959,6 +43605,11 @@ ${text}` : text);
42959
43605
  page: external_exports.number().int().min(1).describe("Page (default 1)").optional(),
42960
43606
  size: external_exports.number().int().min(1).describe("Per page (default 50)").optional(),
42961
43607
  autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional()
43608
+ // No `view` here, deliberately. This tool never emitted a cache row: it
43609
+ // builds a VERDICT list of `{id, subject, sentAt, unreadBy}`, which is
43610
+ // already narrower than anything the compact projection would produce.
43611
+ // A parameter offering a rung that changes nothing is the no-op schema
43612
+ // `viewParam` exists to refuse.
42962
43613
  }
42963
43614
  }, async (args) => {
42964
43615
  const page = args.page ?? 1;
@@ -44244,10 +44895,11 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
44244
44895
  var nodeAttachmentIO = new NodeAttachmentIO();
44245
44896
  await runMcp({
44246
44897
  name: "ofw",
44247
- version: "2.13.0",
44898
+ version: "2.15.0",
44248
44899
  // x-release-please-version
44249
44900
  deps: client,
44250
44901
  tools: [
44902
+ registerHealthcheckTools,
44251
44903
  registerUserTools,
44252
44904
  (server, deps) => registerMessageTools(server, deps, nodeCacheProvider, nodeAttachmentIO),
44253
44905
  registerCalendarTools,