ofw-mcp 2.14.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;
3180
- }
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;
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;
3196
3193
  }
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,
@@ -34733,12 +35016,49 @@ function messageOf(err) {
34733
35016
  return String(err);
34734
35017
  }
34735
35018
 
34736
- // node_modules/@chrischall/mcp-utils/dist/response/index.js
34737
- function textResult(data) {
34738
- return {
34739
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
34740
- };
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;
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
+ }
34741
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
34742
35062
  function rawTextResult(text) {
34743
35063
  return { content: [{ type: "text", text }] };
34744
35064
  }
@@ -34748,6 +35068,14 @@ function errorResult(message) {
34748
35068
  isError: true
34749
35069
  };
34750
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
+ }
34751
35079
 
34752
35080
  // node_modules/@chrischall/mcp-utils/dist/server/index.js
34753
35081
  function hintResultOrRethrow(err) {
@@ -39582,7 +39910,7 @@ async function loginWithPassword(username, password) {
39582
39910
  // package.json
39583
39911
  var package_default = {
39584
39912
  name: "ofw-mcp",
39585
- version: "2.14.0",
39913
+ version: "2.15.0",
39586
39914
  license: "MIT",
39587
39915
  mcpName: "io.github.chrischall/ofw-mcp",
39588
39916
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -39616,7 +39944,7 @@ var package_default = {
39616
39944
  typecheck: "tsc -p tsconfig.json --noEmit"
39617
39945
  },
39618
39946
  dependencies: {
39619
- "@chrischall/mcp-utils": "^0.19.3",
39947
+ "@chrischall/mcp-utils": "^0.21.0",
39620
39948
  "@fetchproxy/bootstrap": "^2.2.0",
39621
39949
  "@modelcontextprotocol/sdk": "^1.29.0",
39622
39950
  dotenv: "^17.4.2",
@@ -40118,7 +40446,7 @@ function normalizeTimestampsInValue(value, tz = displayTimeZone()) {
40118
40446
 
40119
40447
  // src/tools/_shared.ts
40120
40448
  function jsonResponse(data) {
40121
- return textResult(normalizeTimestampsInValue(data));
40449
+ return minifiedResult(normalizeTimestampsInValue(data));
40122
40450
  }
40123
40451
  var textResponse = rawTextResult;
40124
40452
  function jsonErrorResponse(data) {
@@ -42271,6 +42599,101 @@ function withPaginationFirst(input) {
42271
42599
  return { ...head, ...body, ...head };
42272
42600
  }
42273
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
+
42274
42697
  // src/tools/messages.ts
42275
42698
  var DateSchema = external_exports.looseObject({ dateTime: external_exports.string() });
42276
42699
  var SentDetailSchema = external_exports.looseObject({
@@ -42417,12 +42840,16 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42417
42840
  until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive)").optional(),
42418
42841
  q: external_exports.string().describe("Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.").optional(),
42419
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(),
42420
- 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
+ })
42421
42847
  }
42422
42848
  }, async (args) => {
42423
42849
  const page = args.page ?? 1;
42424
42850
  const size = args.size ?? 50;
42425
42851
  const sort = args.sort ?? "newest";
42852
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42426
42853
  const folderArg = args.folderId ?? "both";
42427
42854
  let folder;
42428
42855
  if (folderArg === "inbox") folder = "inbox";
@@ -42494,7 +42921,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42494
42921
  payload.autoRefreshed = true;
42495
42922
  }
42496
42923
  payload.freshness = freshness;
42497
- payload.messages = messages;
42924
+ payload.messages = viewMessages(view, messages);
42498
42925
  return jsonResponse(payload);
42499
42926
  });
42500
42927
  server.registerTool("ofw_get_message", {
@@ -42502,10 +42929,14 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42502
42929
  annotations: { readOnlyHint: false },
42503
42930
  inputSchema: {
42504
42931
  messageId: external_exports.string().describe("Message ID (also accepts draft IDs \u2014 drafts are routed via the drafts cache)"),
42505
- 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
+ })
42506
42936
  }
42507
42937
  }, async (args) => {
42508
42938
  const id = Number(args.messageId);
42939
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42509
42940
  const cache = cacheProvider();
42510
42941
  const draftRow = await cache.getDraft(id);
42511
42942
  if (draftRow !== null) {
@@ -42519,7 +42950,13 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42519
42950
  id: draftRow.id,
42520
42951
  folder: "drafts",
42521
42952
  subject: draftRow.subject,
42522
- 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: "" },
42523
42960
  sentAt: draftRow.modifiedAt,
42524
42961
  recipients: draftRow.recipients,
42525
42962
  body: draftRow.body,
@@ -42529,7 +42966,10 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42529
42966
  fetchedBodyAt: draftRow.modifiedAt,
42530
42967
  replyToId: draftRow.replyToId,
42531
42968
  chainRootId: null,
42532
- 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 },
42533
42973
  attachments: [],
42534
42974
  // Concurrency token — pass as expectedRevision to ofw_save_draft /
42535
42975
  // ofw_delete_draft / ofw_send_message to assert you are acting on
@@ -42579,7 +43019,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42579
43019
  }
42580
43020
  }
42581
43021
  const freshness2 = await buildFreshness(cache, { source: "cache", folders: [row2.folder] });
42582
- return jsonResponse({ ...withReadState(row2), attachments: attachments2, freshness: freshness2 });
43022
+ return jsonResponse({ ...viewOne(view, withReadState(row2)), attachments: attachments2, freshness: freshness2 });
42583
43023
  }
42584
43024
  const markReadCheck = markReadVerdict(cached2, args.allowMarkRead);
42585
43025
  if (markReadCheck !== null) return markReadCheck;
@@ -42614,7 +43054,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
42614
43054
  }
42615
43055
  const attachments = await cache.listAttachmentsForMessage(detail.id);
42616
43056
  const freshness = await buildFreshness(cache, { source: "live", folders: [folder] });
42617
- return jsonResponse({ ...withReadState(row), attachments, freshness });
43057
+ return jsonResponse({ ...viewOne(view, withReadState(row)), attachments, freshness });
42618
43058
  });
42619
43059
  if (allowSend) server.registerTool("ofw_send_message", {
42620
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.",
@@ -42886,11 +43326,15 @@ ${JSON.stringify(
42886
43326
  page: external_exports.number().int().min(1).describe("Page number (default 1)").optional(),
42887
43327
  size: external_exports.number().int().min(1).describe("Drafts per page (default 50)").optional(),
42888
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(),
42889
- 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
+ })
42890
43333
  }
42891
43334
  }, async (args) => {
42892
43335
  const page = args.page ?? 1;
42893
43336
  const size = args.size ?? 50;
43337
+ const view = resolveView(args.view, MESSAGE_VIEWS);
42894
43338
  const cache = cacheProvider();
42895
43339
  let autoVerified = false;
42896
43340
  let verifyNote = null;
@@ -42970,7 +43414,7 @@ ${JSON.stringify(
42970
43414
  payload.verifyNote = verifyNote;
42971
43415
  }
42972
43416
  payload.freshness = freshness;
42973
- payload.drafts = drafts;
43417
+ payload.drafts = viewDrafts(view, drafts);
42974
43418
  return jsonResponse(payload);
42975
43419
  });
42976
43420
  if (allowDrafts) server.registerTool("ofw_save_draft", {
@@ -43161,6 +43605,11 @@ ${text}` : text);
43161
43605
  page: external_exports.number().int().min(1).describe("Page (default 1)").optional(),
43162
43606
  size: external_exports.number().int().min(1).describe("Per page (default 50)").optional(),
43163
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.
43164
43613
  }
43165
43614
  }, async (args) => {
43166
43615
  const page = args.page ?? 1;
@@ -44446,7 +44895,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
44446
44895
  var nodeAttachmentIO = new NodeAttachmentIO();
44447
44896
  await runMcp({
44448
44897
  name: "ofw",
44449
- version: "2.14.0",
44898
+ version: "2.15.0",
44450
44899
  // x-release-please-version
44451
44900
  deps: client,
44452
44901
  tools: [