@chrischall/tripadvisor-mcp 0.4.0 → 0.5.1

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;
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;
3187
3193
  }
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(str2, 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 resolve(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,
@@ -34730,12 +35013,82 @@ function messageOf(err) {
34730
35013
  return String(err);
34731
35014
  }
34732
35015
 
34733
- // node_modules/@chrischall/mcp-utils/dist/response/index.js
34734
- function textResult(data) {
34735
- return {
34736
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
34737
- };
35016
+ // node_modules/@chrischall/mcp-utils/dist/response/view.js
35017
+ var VIEWS = ["compact", "full", "raw"];
35018
+ var DEFAULT_VIEW = "compact";
35019
+ var BLURB = {
35020
+ compact: '"compact" (default) drops fields the response already carries elsewhere',
35021
+ full: '"full" returns every field this server understands',
35022
+ raw: '"raw" returns the upstream payload unprojected'
35023
+ };
35024
+ function viewParam(honoured, opts = {}) {
35025
+ if (honoured.length < 2) {
35026
+ throw new Error("viewParam needs at least two rungs: a parameter offering one value decides nothing");
35027
+ }
35028
+ if (!honoured.includes("compact")) {
35029
+ throw new Error('viewParam must offer "compact": a tool with no cheap rung has nothing to default to');
35030
+ }
35031
+ const ordered = VIEWS.filter((v) => honoured.includes(v));
35032
+ const sentence = `Response shape: ${ordered.map((v) => BLURB[v]).join("; ")}.`;
35033
+ return external_exports.enum(Object.fromEntries(ordered.map((v) => [v, v]))).optional().describe(opts.note ? `${sentence} ${opts.note}` : sentence);
35034
+ }
35035
+ function resolveView(value, honoured) {
35036
+ return value !== void 0 && honoured.includes(value) ? value : DEFAULT_VIEW;
35037
+ }
35038
+ function minifiedResult(data) {
35039
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
35040
+ }
35041
+
35042
+ // node_modules/@chrischall/mcp-utils/dist/response/media.js
35043
+ var MEDIA_NOUN = "(?:avatar|picture|photo|thumbnail|thumb|image|icon|banner|profile_pic(?:ture)?|logo)";
35044
+ var MEDIA_QUALIFIER = "(?:primary|secondary|main|default|cover|hero|profile|master|rendered|small|medium|large|full|original|tall)";
35045
+ var MEDIA_KEY = new RegExp(`^(?:(?:${MEDIA_QUALIFIER}|${MEDIA_NOUN})[_-]?)?${MEDIA_NOUN}s?(?:[_-]?(?:link|uri|url|src)s?)?$`, "i");
35046
+ var MEDIA_URL = /^https?:\/\/[^\s]+?\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)([?#]|$)/i;
35047
+ function stripMediaUrls(value, opts = {}) {
35048
+ const keep = normalizeRules(opts.keep ?? []);
35049
+ const drop = normalizeRules(opts.drop ?? []);
35050
+ return walk(value, keep, drop);
35051
+ }
35052
+ function normalizeRules(rules) {
35053
+ return rules.map((rule) => typeof rule === "string" ? rule.toLowerCase() : new RegExp(rule.source, rule.flags));
35054
+ }
35055
+ function matchesRule(key, rules) {
35056
+ const lower = key.toLowerCase();
35057
+ for (const rule of rules) {
35058
+ if (typeof rule === "string") {
35059
+ if (rule === lower)
35060
+ return true;
35061
+ continue;
35062
+ }
35063
+ rule.lastIndex = 0;
35064
+ if (rule.test(key))
35065
+ return true;
35066
+ }
35067
+ return false;
35068
+ }
35069
+ function walk(value, keep, drop) {
35070
+ if (Array.isArray(value))
35071
+ return value.map((v) => walk(v, keep, drop));
35072
+ if (value === null || typeof value !== "object")
35073
+ return value;
35074
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
35075
+ return value;
35076
+ const out = {};
35077
+ for (const [key, v] of Object.entries(value)) {
35078
+ if (matchesRule(key, keep)) {
35079
+ out[key] = v;
35080
+ continue;
35081
+ }
35082
+ if (MEDIA_KEY.test(key) || matchesRule(key, drop))
35083
+ continue;
35084
+ if (typeof v === "string" && MEDIA_URL.test(v))
35085
+ continue;
35086
+ out[key] = walk(v, keep, drop);
35087
+ }
35088
+ return out;
34738
35089
  }
35090
+
35091
+ // node_modules/@chrischall/mcp-utils/dist/response/index.js
34739
35092
  function errorResult(message) {
34740
35093
  return {
34741
35094
  content: [{ type: "text", text: redactSecrets(message) }],
@@ -35006,7 +35359,7 @@ var pageSchema = {
35006
35359
  };
35007
35360
 
35008
35361
  // src/version.ts
35009
- var VERSION = "0.4.0";
35362
+ var VERSION = "0.5.1";
35010
35363
 
35011
35364
  // src/client.ts
35012
35365
  import { dirname, join } from "node:path";
@@ -35167,15 +35520,25 @@ function compactLocationList(raw) {
35167
35520
  return { data: rows.map((r) => compactLocation(r)) };
35168
35521
  }
35169
35522
 
35523
+ // src/view.ts
35524
+ var TA_VIEWS = ["compact", "full"];
35525
+ var NOTE = `compact returns the slim projection where one exists and strips image URLs elsewhere; "full" returns TripAdvisor's whole records.`;
35526
+ var viewArg = () => viewParam(TA_VIEWS, { note: NOTE });
35527
+ function viewResponse(view, data, projector) {
35528
+ const rung = resolveView(view, TA_VIEWS);
35529
+ if (rung !== "compact") return minifiedResult(data);
35530
+ if (projector === "list") return minifiedResult(compactList(data));
35531
+ if (projector === "locationList") return minifiedResult(compactLocationList(data));
35532
+ return minifiedResult(stripMediaUrls(data));
35533
+ }
35534
+
35170
35535
  // src/tools/search.ts
35171
- var compactParam = {
35172
- compact: external_exports.boolean().optional().describe("Return a slim summary per result (id, name, category, city, rating, review_count, url) instead of full records")
35173
- };
35536
+ var viewParamShared = { view: viewArg() };
35174
35537
  function registerSearchTools(server) {
35175
35538
  server.registerTool(
35176
35539
  "ta_search_locations",
35177
35540
  {
35178
- description: "Search TripAdvisor locations (restaurants, attractions, hotels) by name. Returns matches with a location id for the detail tools, plus pagination. Pass compact:true for slim summaries.",
35541
+ description: 'Search TripAdvisor locations (restaurants, attractions, hotels) by name. Returns matches with a location id for the detail tools, plus pagination. Returns slim summaries by default; pass view:"full" for the whole records.',
35179
35542
  annotations: { readOnlyHint: true, openWorldHint: true },
35180
35543
  inputSchema: {
35181
35544
  query: external_exports.string().min(1).max(500).describe("Text to search location names for"),
@@ -35185,21 +35548,21 @@ function registerSearchTools(server) {
35185
35548
  postal_code: external_exports.string().optional().describe("Postal/ZIP code (takes precedence over geo_name)"),
35186
35549
  locale: LocaleList,
35187
35550
  ...pageParams,
35188
- ...compactParam
35551
+ ...viewParamShared
35189
35552
  }
35190
35553
  },
35191
- async ({ query, category, country_code, geo_name, postal_code, locale, page, size, compact }) => {
35554
+ async ({ query, category, country_code, geo_name, postal_code, locale, page, size, view }) => {
35192
35555
  const data = await client.get(
35193
35556
  `/locations/search${qs({ query, category, country_code, geo_name, postal_code, locale, page, size })}`,
35194
35557
  { cache: "dynamic" }
35195
35558
  );
35196
- return textResult(compact ? compactList(data) : data);
35559
+ return viewResponse(view, data, "list");
35197
35560
  }
35198
35561
  );
35199
35562
  server.registerTool(
35200
35563
  "ta_search_nearby",
35201
35564
  {
35202
- description: "Find TripAdvisor locations near a point within a radius, or inside a bounding box. Center by lat+lon+radius, by a reference location_id+radius, or by a sw/ne bounding box. Returns matches with distance and a location id. Pass compact:true for slim summaries.",
35565
+ description: 'Find TripAdvisor locations near a point within a radius, or inside a bounding box. Center by lat+lon+radius, by a reference location_id+radius, or by a sw/ne bounding box. Returns matches with distance and a location id. Returns slim summaries by default; pass view:"full" for the whole records.',
35203
35566
  annotations: { readOnlyHint: true, openWorldHint: true },
35204
35567
  inputSchema: {
35205
35568
  // Center — supply exactly one of: lat+lon, location_id, or the sw/ne box.
@@ -35218,11 +35581,11 @@ function registerSearchTools(server) {
35218
35581
  sort: external_exports.enum(["distance", "rating"]).optional().describe("Sort order (default distance)"),
35219
35582
  locale: LocaleList,
35220
35583
  ...pageParams,
35221
- ...compactParam
35584
+ ...viewParamShared
35222
35585
  }
35223
35586
  },
35224
35587
  async (args) => {
35225
- const { lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon, compact, ...rest } = args;
35588
+ const { lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon, view, ...rest } = args;
35226
35589
  const boxParts = [sw_lat, sw_lon, ne_lat, ne_lon];
35227
35590
  const boxGiven = boxParts.filter((v) => v !== void 0).length;
35228
35591
  if (boxGiven > 0 && boxGiven < 4) {
@@ -35253,7 +35616,7 @@ function registerSearchTools(server) {
35253
35616
  `/locations/nearby${qs({ lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon, ...rest })}`,
35254
35617
  { cache: "dynamic" }
35255
35618
  );
35256
- return textResult(compact ? compactList(data) : data);
35619
+ return viewResponse(view, data, "list");
35257
35620
  }
35258
35621
  );
35259
35622
  }
@@ -35263,17 +35626,17 @@ function registerLocationTools(server) {
35263
35626
  server.registerTool(
35264
35627
  "ta_get_locations",
35265
35628
  {
35266
- description: "Get details for MULTIPLE locations in one call (batch). Pass an array of location ids \u2014 cheaper than repeated ta_get_location_details. Unknown or unlicensed ids are silently omitted. Pass compact:true for slim summaries.",
35629
+ description: 'Get details for MULTIPLE locations in one call (batch). Pass an array of location ids \u2014 cheaper than repeated ta_get_location_details. Unknown or unlicensed ids are silently omitted. Returns slim summaries by default; pass view:"full" for the whole records.',
35267
35630
  annotations: { readOnlyHint: true, openWorldHint: true },
35268
35631
  inputSchema: {
35269
35632
  ids: external_exports.array(LocationId).min(1).max(50).describe("Location IDs to fetch (1\u201350)"),
35270
35633
  locale: LocaleList,
35271
- compact: external_exports.boolean().optional().describe("Return a slim summary per location instead of full records")
35634
+ view: viewArg()
35272
35635
  }
35273
35636
  },
35274
- async ({ ids, locale, compact }) => {
35637
+ async ({ ids, locale, view }) => {
35275
35638
  const data = await client.get(`/locations${qs({ id: ids, locale })}`, { cache: "static" });
35276
- return textResult(compact ? compactLocationList(data) : data);
35639
+ return viewResponse(view, data, "locationList");
35277
35640
  }
35278
35641
  );
35279
35642
  server.registerTool(
@@ -35288,7 +35651,7 @@ function registerLocationTools(server) {
35288
35651
  },
35289
35652
  async ({ locationId, locale }) => {
35290
35653
  const data = await client.get(`/locations/${locationId}${qs({ locale })}`, { cache: "static" });
35291
- return textResult(data);
35654
+ return minifiedResult(data);
35292
35655
  }
35293
35656
  );
35294
35657
  server.registerTool(
@@ -35306,25 +35669,26 @@ function registerLocationTools(server) {
35306
35669
  const data = await client.get(`/locations/${locationId}/photos${qs({ locale, page, size })}`, {
35307
35670
  cache: "static"
35308
35671
  });
35309
- return textResult(data);
35672
+ return minifiedResult(data);
35310
35673
  }
35311
35674
  );
35312
35675
  server.registerTool(
35313
35676
  "ta_get_location_reviews",
35314
35677
  {
35315
- description: "Get traveler reviews for a TripAdvisor location, with pagination.",
35678
+ description: `Get traveler reviews for a TripAdvisor location, with pagination. Reviewer avatars and other image URLs are dropped by default; pass view:"full" for TripAdvisor's whole records.`,
35316
35679
  annotations: { readOnlyHint: true, openWorldHint: true },
35317
35680
  inputSchema: {
35318
35681
  locationId: LocationId,
35319
35682
  locale: LocaleList,
35320
- ...pageParams
35683
+ ...pageParams,
35684
+ view: viewArg()
35321
35685
  }
35322
35686
  },
35323
- async ({ locationId, locale, page, size }) => {
35687
+ async ({ locationId, locale, page, size, view }) => {
35324
35688
  const data = await client.get(`/locations/${locationId}/reviews${qs({ locale, page, size })}`, {
35325
35689
  cache: "static"
35326
35690
  });
35327
- return textResult(data);
35691
+ return viewResponse(view, data);
35328
35692
  }
35329
35693
  );
35330
35694
  }
@@ -39354,6 +39718,13 @@ function createFetchproxyTransport(opts) {
39354
39718
  return transport;
39355
39719
  }
39356
39720
  function bridgeErrorInfo(err) {
39721
+ if (err instanceof FetchproxySessionNotReadyError) {
39722
+ return {
39723
+ type: "session_not_ready",
39724
+ message: truncateErrorMessage(messageOf(err).replace(err.hint, "").trim()),
39725
+ hint: err.hint
39726
+ };
39727
+ }
39357
39728
  const kind = classifyBridgeError(err);
39358
39729
  const message = truncateErrorMessage(messageOf(err));
39359
39730
  switch (kind) {
@@ -39386,15 +39757,32 @@ function bridgeErrorInfo(err) {
39386
39757
  }
39387
39758
  function healthcheckHint(args) {
39388
39759
  const { hostLabel, prefix, probePath, port } = args;
39760
+ const portNote = port === null ? "" : ` (port ${port})`;
39389
39761
  if (args.ok) {
39762
+ if (args.direct) {
39763
+ return `Direct fetch round-tripped ${probePath} successfully \u2014 the browser bridge wasn't used for this probe. If real tools still fail, the problem is on the ${hostLabel} side (a bot wall answering some calls but not this one, a field that moved, \u2026), not the transport.`;
39764
+ }
39390
39765
  return `Bridge round-tripped ${probePath} successfully. If real tools still fail, the problem is downstream of fetchproxy (${hostLabel} redirecting on login, a bot-wall / behavioral challenge, etc.) \u2014 not the bridge.`;
39391
39766
  }
39767
+ if (args.errorKind === "session_not_ready") {
39768
+ const s = args.session;
39769
+ if (s?.pairCode) {
39770
+ return `The Transporter extension is waiting for you to approve pair code ${s.pairCode} for ${prefix}-mcp. Open the extension popup, approve it, then retry.`;
39771
+ }
39772
+ if (s?.state === "extension_disconnected") {
39773
+ return `No Transporter extension is attached to this bridge${portNote}. Open Chrome with the extension installed and a ${hostLabel} tab, then retry.`;
39774
+ }
39775
+ return `The Transporter extension is attached but never confirmed a session for ${prefix}-mcp \u2014 its hello got no answer within the session-ready timeout. Reload the extension (chrome://extensions) or reopen the ${hostLabel} tab, then retry. On a hosted bridge this is also what a relay that dialled the child before it bound its port${portNote} looks like.`;
39776
+ }
39392
39777
  if (args.errorKind === "bridge_down") {
39393
39778
  const base = `The fetchproxy browser extension's service worker is not responding. Chrome evicts extension service workers after ~30s idle by default \u2014 this looks like that case. Wake it by clicking the fetchproxy extension icon (or opening any ${hostLabel} tab and reloading), then retry. If it keeps happening, reload the extension from chrome://extensions.`;
39394
39779
  return args.bridgeHint ? `${args.bridgeHint} ${base}` : base;
39395
39780
  }
39781
+ if (args.direct) {
39782
+ return `The probe ran over the direct fetch \u2014 the browser bridge wasn't used for this probe \u2014 and failed; see error.message. If ${hostLabel} is answering with a bot wall, pin the bridge (the consumer's transport env var) or leave the default fallback to switch on the next challenge.`;
39783
+ }
39396
39784
  if (args.role === null) {
39397
- return `The bridge never bound a role. listen() may have failed silently on startup. Check stderr from ${prefix}-mcp for an error during start, and confirm port ${port} isn't blocked.`;
39785
+ return `The bridge never bound a role. listen() may have failed silently on startup. Check stderr from ${prefix}-mcp for an error during start, and confirm ${port === null ? "the bridge port" : `port ${port}`} isn't blocked.`;
39398
39786
  }
39399
39787
  if (args.errorKind === "timeout") {
39400
39788
  return `Bridge is alive (role=${args.role}), but the request didn't get a response in time. Either (a) the fetchproxy browser extension isn't connected to this MCP yet \u2014 open the extension popup and check for a green dot next to "${prefix}-mcp", or (b) the signed-in ${hostLabel} tab is sleeping / closed. Open ${hostLabel} in your browser, then retry.`;
@@ -39404,12 +39792,32 @@ function healthcheckHint(args) {
39404
39792
  }
39405
39793
  return `Unexpected error \u2014 see the error.message field for details.`;
39406
39794
  }
39795
+ function projectBridgeStatus(health) {
39796
+ const session = health.session;
39797
+ return {
39798
+ role: health.role,
39799
+ port: health.port,
39800
+ server_version: health.serverVersion,
39801
+ fetch_timeout_ms: health.fetchTimeoutMs,
39802
+ last_success_at: health.lastSuccessAt,
39803
+ last_failure_at: health.lastFailureAt,
39804
+ last_failure_reason: health.lastFailureReason,
39805
+ consecutive_failures: health.consecutiveFailures,
39806
+ last_extension_message_at: health.lastExtensionMessageAt,
39807
+ ...session ? {
39808
+ session_state: session.state,
39809
+ pending_pair_code: session.pairCode,
39810
+ extension_connected: session.extensionConnected
39811
+ } : {}
39812
+ };
39813
+ }
39407
39814
  function registerBridgeHealthcheckTool(args) {
39408
- const { server, prefix, probePath, hostLabel, transport, probeFn, classifyThrown, hints } = args;
39815
+ const { server, prefix, probePath, hostLabel, probeFn, classifyThrown, hints, path } = args;
39409
39816
  const probeUrl = `https://${hostLabel}${probePath}`;
39817
+ const resolveTransport = () => typeof args.transport === "function" ? args.transport() : args.transport;
39410
39818
  server.registerTool(`${prefix}_healthcheck`, {
39411
39819
  title: "Verify the fetchproxy bridge end-to-end",
39412
- description: `Round-trips a small public ${hostLabel} URL (${probePath}) through the fetchproxy bridge and returns diagnostics: the bridge's role (host/peer/null), port, version, the elapsed round-trip time, and a plain-English hint distinguishing 'bridge never came up' from 'extension not connected' from 'real ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only, no auth required.`,
39820
+ description: `Round-trips a small public ${hostLabel} URL (${probePath}) through the fetchproxy bridge and returns diagnostics: the bridge's role (host/peer/null), port, version, the extension link (linked / pair pending / not attached / never answered), the elapsed round-trip time, and a plain-English hint distinguishing 'bridge never came up' from 'extension not connected' from 'real ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only, no auth required.`,
39413
39821
  annotations: {
39414
39822
  title: "Verify the fetchproxy bridge end-to-end",
39415
39823
  readOnlyHint: true,
@@ -39420,29 +39828,60 @@ function registerBridgeHealthcheckTool(args) {
39420
39828
  }, async () => {
39421
39829
  let probeBody = "";
39422
39830
  let thrown;
39423
- const probeResult = await transport.runProbe(async (path) => {
39831
+ const wrappedProbe = async (p) => {
39424
39832
  try {
39425
- probeBody = await probeFn(path);
39833
+ probeBody = await probeFn(p);
39426
39834
  return probeBody;
39427
39835
  } catch (e) {
39428
39836
  thrown = e;
39429
39837
  throw e;
39430
39838
  }
39431
- }, probePath);
39432
- const ok = probeResult.ok;
39433
- const probe = ok ? {
39434
- url: probeUrl,
39435
- elapsed_ms: probeResult.elapsed_ms,
39436
- status: 200,
39437
- body_length: probeBody.length
39438
- } : { url: probeUrl, elapsed_ms: probeResult.elapsed_ms };
39839
+ };
39840
+ let ok;
39841
+ let elapsedMs;
39842
+ let bridge;
39843
+ let rawError;
39844
+ let pathNow;
39845
+ if (path) {
39846
+ const start = Date.now();
39847
+ try {
39848
+ await wrappedProbe(probePath);
39849
+ ok = true;
39850
+ } catch (e) {
39851
+ ok = false;
39852
+ rawError = { kind: classifyBridgeError(e), message: truncateErrorMessage(messageOf(e)) };
39853
+ }
39854
+ elapsedMs = Date.now() - start;
39855
+ pathNow = path();
39856
+ const transport = resolveTransport();
39857
+ bridge = transport ? projectBridgeStatus(transport.status()) : void 0;
39858
+ } else {
39859
+ const transport = resolveTransport();
39860
+ if (!transport) {
39861
+ throw new Error("registerBridgeHealthcheckTool: transport() returned nothing and no `path` was supplied \u2014 a bridge-only healthcheck needs its bridge.");
39862
+ }
39863
+ const probeResult = await transport.runProbe(wrappedProbe, probePath);
39864
+ ok = probeResult.ok;
39865
+ elapsedMs = probeResult.elapsed_ms;
39866
+ bridge = {
39867
+ ...probeResult.bridge,
39868
+ last_extension_message_at: transport.status().lastExtensionMessageAt
39869
+ };
39870
+ rawError = probeResult.error;
39871
+ }
39872
+ const probe = ok ? { url: probeUrl, elapsed_ms: elapsedMs, status: 200, body_length: probeBody.length } : { url: probeUrl, elapsed_ms: elapsedMs };
39439
39873
  let error51;
39440
39874
  let bridgeHint;
39441
39875
  let customHint;
39442
39876
  let customDetail;
39443
- if (probeResult.error) {
39444
- let kind = probeResult.error.kind === "other" ? "unknown" : probeResult.error.kind;
39445
- bridgeHint = thrown instanceof FetchproxyBridgeDownError ? thrown.hint : void 0;
39877
+ if (rawError) {
39878
+ let kind = rawError.kind === "other" ? "unknown" : rawError.kind;
39879
+ if (thrown instanceof FetchproxySessionNotReadyError) {
39880
+ kind = "session_not_ready";
39881
+ bridgeHint = thrown.hint;
39882
+ } else if (thrown instanceof FetchproxyBridgeDownError) {
39883
+ bridgeHint = thrown.hint;
39884
+ }
39446
39885
  if (thrown !== void 0 && classifyThrown) {
39447
39886
  const custom2 = classifyThrown(thrown);
39448
39887
  if (custom2) {
@@ -39453,30 +39892,33 @@ function registerBridgeHealthcheckTool(args) {
39453
39892
  }
39454
39893
  error51 = {
39455
39894
  kind,
39456
- message: probeResult.error.message,
39895
+ message: rawError.message,
39457
39896
  ...bridgeHint !== void 0 ? { bridge_hint: bridgeHint } : {},
39458
39897
  ...customDetail !== void 0 ? { detail: customDetail } : {}
39459
39898
  };
39460
39899
  }
39461
- const lastExtensionMessageAt = transport.status().lastExtensionMessageAt;
39462
- const arm = ok ? "ok" : error51?.kind === "bridge_down" ? "bridge_down" : probeResult.bridge.role === null ? "no_role" : error51?.kind === "timeout" ? "timeout" : error51?.kind === "protocol" || error51?.kind === "http" ? "protocol" : "unknown";
39900
+ const direct = pathNow ? pathNow.transport === "direct" : bridge === void 0;
39901
+ const arm = ok ? "ok" : error51?.kind === "session_not_ready" ? "session_not_ready" : error51?.kind === "bridge_down" ? "bridge_down" : direct ? "direct" : bridge === void 0 || bridge.role === null ? "no_role" : error51?.kind === "timeout" ? "timeout" : error51?.kind === "protocol" || error51?.kind === "http" ? "protocol" : "unknown";
39463
39902
  const defaultHint = healthcheckHint({
39464
39903
  ok,
39465
- role: probeResult.bridge.role,
39466
- // FIX: the real configured port from bridgeHealth(), not a literal 37149.
39467
- port: probeResult.bridge.port,
39904
+ role: bridge?.role ?? null,
39905
+ // The real configured port from bridgeHealth(), never a literal 37149.
39906
+ port: bridge?.port ?? null,
39468
39907
  hostLabel,
39469
39908
  prefix,
39470
39909
  probePath,
39471
39910
  errorKind: error51?.kind,
39472
- bridgeHint: error51?.kind === "bridge_down" ? bridgeHint : void 0
39911
+ bridgeHint: error51?.kind === "bridge_down" ? bridgeHint : void 0,
39912
+ session: {
39913
+ ...bridge?.session_state !== void 0 ? { state: bridge.session_state } : {},
39914
+ pairCode: bridge?.pending_pair_code ?? (thrown instanceof FetchproxySessionNotReadyError ? thrown.pairCode : null)
39915
+ },
39916
+ direct
39473
39917
  });
39474
39918
  const result = {
39475
39919
  ok,
39476
- bridge: {
39477
- ...probeResult.bridge,
39478
- last_extension_message_at: lastExtensionMessageAt
39479
- },
39920
+ ...bridge ? { bridge } : {},
39921
+ ...pathNow ? { transport: pathNow } : {},
39480
39922
  probe,
39481
39923
  ...error51 ? { error: error51 } : {},
39482
39924
  // Precedence: classifyThrown's hint > per-arm override > default ladder.
@@ -39707,7 +40149,7 @@ function registerWebTools(server) {
39707
40149
  hint: "The page may be a bot-challenge shell or the id may be wrong \u2014 run ta_web_healthcheck and confirm a signed-in www.tripadvisor.com tab is open, then retry."
39708
40150
  });
39709
40151
  }
39710
- return textResult({ location_id: locationId, ...detail });
40152
+ return minifiedResult({ location_id: locationId, ...detail });
39711
40153
  }
39712
40154
  );
39713
40155
  }