infinitecampus-mcp 2.6.0 → 2.8.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;
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;
3183
3193
  }
3184
- if (++tokenCount > 7) {
3185
- output.error = true;
3186
- break;
3187
- }
3188
- if (i > 0 && input[i - 1] === ":") {
3189
- endipv6Encountered = true;
3190
- }
3191
- address.push(":");
3192
- continue;
3193
- } else if (cursor === "%") {
3194
- if (!consume(buffer, address, output)) {
3195
- break;
3196
- }
3197
- consume = consumeIsZone;
3198
3194
  } else {
3199
- buffer.push(cursor);
3195
+ runStart = -1;
3196
+ runLength = 0;
3197
+ }
3198
+ }
3199
+ if (bestLength < 2) return hextets.join(":");
3200
+ const head = hextets.slice(0, bestStart).join(":");
3201
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3202
+ return head + "::" + tail;
3203
+ }
3204
+ function normalizeIPv6Address(input) {
3205
+ const compression = input.indexOf("::");
3206
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
3207
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3208
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3209
+ if (compression !== -1) {
3210
+ if (left.length === 1 && left[0] === "") left.length = 0;
3211
+ if (right.length === 1 && right[0] === "") right.length = 0;
3212
+ }
3213
+ const parts = left.concat(right);
3214
+ let hextetCount = 0;
3215
+ for (let i = 0; i < parts.length; i++) {
3216
+ const part = parts[i];
3217
+ if (part === "") return void 0;
3218
+ if (part.indexOf(".") !== -1) {
3219
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
3220
+ hextetCount += 2;
3200
3221
  continue;
3201
3222
  }
3223
+ if (!isHextet(part)) return void 0;
3224
+ parts[i] = parseInt(part, 16).toString(16);
3225
+ hextetCount++;
3202
3226
  }
3203
- if (buffer.length) {
3204
- if (consume === consumeIsZone) {
3205
- output.zone = buffer.join("");
3206
- } else if (endIpv6) {
3207
- address.push(buffer.join(""));
3208
- } else {
3209
- address.push(stringArrayToHexStripped(buffer));
3210
- }
3227
+ if (compression === -1) {
3228
+ if (hextetCount !== 8) return void 0;
3229
+ return compressIPv6ZeroRun(parts);
3211
3230
  }
3212
- output.address = address.join("");
3213
- return output;
3231
+ if (hextetCount >= 8) return void 0;
3232
+ const expanded = parts.slice(0, left.length);
3233
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
3234
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
3235
+ return compressIPv6ZeroRun(expanded);
3214
3236
  }
3215
3237
  function normalizeIPv6(host) {
3216
- if (findToken(host, ":") < 2) {
3217
- return { host, isIPV6: false };
3218
- }
3219
- const ipv63 = getIPV6(host);
3220
- if (!ipv63.error) {
3221
- let newHost = ipv63.address;
3222
- let escapedHost = ipv63.address;
3223
- if (ipv63.zone) {
3224
- newHost += "%" + ipv63.zone;
3225
- escapedHost += "%25" + ipv63.zone;
3226
- }
3227
- return { host: newHost, isIPV6: true, escapedHost };
3228
- } else {
3229
- return { host, isIPV6: false };
3230
- }
3238
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3239
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3240
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
3241
+ let input = bracketed ? host.slice(1, -1) : host;
3242
+ if (bracketed && isIPvFuture(input)) {
3243
+ input = input.toLowerCase();
3244
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3245
+ }
3246
+ if (findToken(input, ":") < 2) {
3247
+ return { host, isIPV6: false, error: bracketed };
3248
+ }
3249
+ let zoneIdentifier = "";
3250
+ const zoneSeparator = input.indexOf("%");
3251
+ if (zoneSeparator !== -1) {
3252
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3253
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3254
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
3255
+ input = input.slice(0, zoneSeparator);
3256
+ }
3257
+ const address = normalizeIPv6Address(input);
3258
+ if (address === void 0) return { host, isIPV6: false, error: true };
3259
+ return {
3260
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3261
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3262
+ isIPV6: true
3263
+ };
3231
3264
  }
3232
3265
  function findToken(str, token) {
3233
3266
  let ind = 0;
@@ -3346,7 +3379,8 @@ var require_utils = __commonJS({
3346
3379
  function normalizePathEncoding(input) {
3347
3380
  let output = "";
3348
3381
  for (let i = 0; i < input.length; i++) {
3349
- if (input[i] === "%" && i + 2 < input.length) {
3382
+ const ch = input[i];
3383
+ if (ch === "%" && i + 2 < input.length) {
3350
3384
  const hex3 = input.slice(i + 1, i + 3);
3351
3385
  if (isHexPair(hex3)) {
3352
3386
  const normalizedHex = hex3.toUpperCase();
@@ -3360,10 +3394,152 @@ var require_utils = __commonJS({
3360
3394
  continue;
3361
3395
  }
3362
3396
  }
3363
- if (isPathCharacter(input[i])) {
3364
- output += input[i];
3397
+ if (isPathCharacter(ch)) {
3398
+ output += ch;
3365
3399
  } else {
3366
- output += escape(input[i]);
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;
3526
+ } else {
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 resolve2(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,
@@ -34682,6 +34965,7 @@ var StdioServerTransport = class {
34682
34965
  };
34683
34966
 
34684
34967
  // node_modules/@chrischall/mcp-utils/dist/errors/index.js
34968
+ var DEFAULT_ERROR_MESSAGE_MAX = 500;
34685
34969
  var McpToolError = class extends Error {
34686
34970
  /** Actionable remediation text, when one applies. */
34687
34971
  hint;
@@ -34720,13 +35004,95 @@ var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*('
34720
35004
  function redactSecrets(text) {
34721
35005
  return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
34722
35006
  }
35007
+ function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
35008
+ const str = text === null || text === void 0 ? "" : String(text);
35009
+ const redacted = redactSecrets(str);
35010
+ if (redacted.length <= max)
35011
+ return redacted;
35012
+ return `${redacted.slice(0, max)}\u2026 [truncated]`;
35013
+ }
35014
+ function messageOf(err) {
35015
+ if (err instanceof Error)
35016
+ return err.message;
35017
+ return String(err);
35018
+ }
35019
+
35020
+ // node_modules/@chrischall/mcp-utils/dist/response/view.js
35021
+ var VIEWS = ["compact", "full", "raw"];
35022
+ var DEFAULT_VIEW = "compact";
35023
+ var BLURB = {
35024
+ compact: '"compact" (default) drops fields the response already carries elsewhere',
35025
+ full: '"full" returns every field this server understands',
35026
+ raw: '"raw" returns the upstream payload unprojected'
35027
+ };
35028
+ function viewParam(honoured, opts = {}) {
35029
+ if (honoured.length < 2) {
35030
+ throw new Error("viewParam needs at least two rungs: a parameter offering one value decides nothing");
35031
+ }
35032
+ if (!honoured.includes("compact")) {
35033
+ throw new Error('viewParam must offer "compact": a tool with no cheap rung has nothing to default to');
35034
+ }
35035
+ const ordered = VIEWS.filter((v) => honoured.includes(v));
35036
+ const sentence = `Response shape: ${ordered.map((v) => BLURB[v]).join("; ")}.`;
35037
+ return external_exports.enum(Object.fromEntries(ordered.map((v) => [v, v]))).optional().describe(opts.note ? `${sentence} ${opts.note}` : sentence);
35038
+ }
35039
+ function resolveView(value, honoured) {
35040
+ return value !== void 0 && honoured.includes(value) ? value : DEFAULT_VIEW;
35041
+ }
35042
+ function minifiedResult(data) {
35043
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
35044
+ }
35045
+
35046
+ // node_modules/@chrischall/mcp-utils/dist/response/media.js
35047
+ var MEDIA_NOUN = "(?:avatar|picture|photo|thumbnail|thumb|image|icon|banner|profile_pic(?:ture)?|logo)";
35048
+ var MEDIA_QUALIFIER = "(?:primary|secondary|main|default|cover|hero|profile|master|rendered|small|medium|large|full|original|tall)";
35049
+ var MEDIA_KEY = new RegExp(`^(?:(?:${MEDIA_QUALIFIER}|${MEDIA_NOUN})[_-]?)?${MEDIA_NOUN}s?(?:[_-]?(?:link|uri|url|src)s?)?$`, "i");
35050
+ var MEDIA_URL = /^https?:\/\/[^\s]+?\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)([?#]|$)/i;
35051
+ function stripMediaUrls(value, opts = {}) {
35052
+ const keep = normalizeRules(opts.keep ?? []);
35053
+ const drop = normalizeRules(opts.drop ?? []);
35054
+ return walk(value, keep, drop);
35055
+ }
35056
+ function normalizeRules(rules) {
35057
+ return rules.map((rule) => typeof rule === "string" ? rule.toLowerCase() : new RegExp(rule.source, rule.flags));
35058
+ }
35059
+ function matchesRule(key, rules) {
35060
+ const lower = key.toLowerCase();
35061
+ for (const rule of rules) {
35062
+ if (typeof rule === "string") {
35063
+ if (rule === lower)
35064
+ return true;
35065
+ continue;
35066
+ }
35067
+ rule.lastIndex = 0;
35068
+ if (rule.test(key))
35069
+ return true;
35070
+ }
35071
+ return false;
35072
+ }
35073
+ function walk(value, keep, drop) {
35074
+ if (Array.isArray(value))
35075
+ return value.map((v) => walk(v, keep, drop));
35076
+ if (value === null || typeof value !== "object")
35077
+ return value;
35078
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
35079
+ return value;
35080
+ const out = {};
35081
+ for (const [key, v] of Object.entries(value)) {
35082
+ if (matchesRule(key, keep)) {
35083
+ out[key] = v;
35084
+ continue;
35085
+ }
35086
+ if (MEDIA_KEY.test(key) || matchesRule(key, drop))
35087
+ continue;
35088
+ if (typeof v === "string" && MEDIA_URL.test(v))
35089
+ continue;
35090
+ out[key] = walk(v, keep, drop);
35091
+ }
35092
+ return out;
35093
+ }
34723
35094
 
34724
35095
  // node_modules/@chrischall/mcp-utils/dist/response/index.js
34725
- function textResult(data) {
34726
- return {
34727
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
34728
- };
34729
- }
34730
35096
  function errorResult(message) {
34731
35097
  return {
34732
35098
  content: [{ type: "text", text: redactSecrets(message) }],
@@ -39015,6 +39381,144 @@ var BootstrapDisabledError = class extends Error {
39015
39381
  }
39016
39382
  };
39017
39383
 
39384
+ // node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
39385
+ function statusOf(err) {
39386
+ if (typeof err !== "object" || err === null)
39387
+ return void 0;
39388
+ const s = err.status ?? err.statusCode;
39389
+ return typeof s === "number" ? s : void 0;
39390
+ }
39391
+ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
39392
+ "ok",
39393
+ "no_credential",
39394
+ "credential_rejected",
39395
+ "timeout",
39396
+ "http",
39397
+ "transport",
39398
+ "unknown"
39399
+ ]);
39400
+ function isArm(kind) {
39401
+ return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
39402
+ }
39403
+ function credentialHint(arm, prefix, hostLabel, source2) {
39404
+ switch (arm) {
39405
+ case "ok":
39406
+ return `Credential from '${source2}' works: ${hostLabel} accepted an authenticated request. If a real tool still fails, the problem is that tool, not auth.`;
39407
+ case "no_credential":
39408
+ return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
39409
+ case "credential_rejected":
39410
+ return `${hostLabel} rejected the credential from '${source2}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
39411
+ case "timeout":
39412
+ return `The credential from '${source2}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
39413
+ case "http":
39414
+ return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
39415
+ case "transport":
39416
+ return `Could not reach ${hostLabel} at all. Check network egress; the credential itself was never judged.`;
39417
+ default:
39418
+ return `Unexpected failure \u2014 see error.message.`;
39419
+ }
39420
+ }
39421
+ function registerCredentialHealthcheckTool(args) {
39422
+ const { server, prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
39423
+ const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
39424
+ server.registerTool(`${prefix}_healthcheck`, {
39425
+ title: "Verify credentials and upstream reachability",
39426
+ description: `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only; never returns the credential itself.`,
39427
+ annotations: {
39428
+ title: "Verify credentials and upstream reachability",
39429
+ readOnlyHint: true,
39430
+ idempotentHint: true,
39431
+ openWorldHint: true
39432
+ },
39433
+ inputSchema: {}
39434
+ }, async () => {
39435
+ let probeStarted = 0;
39436
+ let state;
39437
+ try {
39438
+ state = await resolveCredential();
39439
+ } catch (e) {
39440
+ const classified = classifyThrown?.(e);
39441
+ const result2 = {
39442
+ ok: false,
39443
+ // Still false, and still no source: a classification explains WHY
39444
+ // nothing resolved, it does not invent a credential that did.
39445
+ credential: { source: null, resolved: false },
39446
+ // No `url`: nothing was probed, and naming one implies it was tried.
39447
+ probe: { elapsed_ms: 0 },
39448
+ error: {
39449
+ kind: classified?.kind ?? "no_credential",
39450
+ message: truncateErrorMessage(messageOf(e)),
39451
+ ...classified?.detail !== void 0 ? { detail: classified.detail } : {}
39452
+ },
39453
+ // The hint must follow the KIND beside it. Falling back to
39454
+ // `no_credential`'s copy under a classified kind would state a cause
39455
+ // the kind contradicts — the same disagreement this path exists to
39456
+ // remove. So: an inline hint wins; else the classified arm's own
39457
+ // copy (consumer override first); else, for a kind this module has
39458
+ // no copy for, the neutral `unknown` text rather than one that
39459
+ // asserts a cause; else the unclassified `no_credential` default.
39460
+ hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
39461
+ };
39462
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
39463
+ }
39464
+ const credential = {
39465
+ source: state.source,
39466
+ resolved: state.source !== null,
39467
+ ...state.detail !== void 0 ? { detail: state.detail } : {}
39468
+ };
39469
+ if (!credential.resolved) {
39470
+ const result2 = {
39471
+ ok: false,
39472
+ credential,
39473
+ probe: { elapsed_ms: 0 },
39474
+ error: { kind: "no_credential", message: "no credential source resolved" },
39475
+ hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
39476
+ };
39477
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
39478
+ }
39479
+ let arm = "ok";
39480
+ let error51;
39481
+ let status;
39482
+ let customHint;
39483
+ probeStarted = Date.now();
39484
+ try {
39485
+ await probeFn();
39486
+ } catch (e) {
39487
+ status = statusOf(e);
39488
+ const aborted2 = e instanceof Error && e.name === "AbortError";
39489
+ arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
39490
+ let kind = arm;
39491
+ let detail;
39492
+ const custom2 = classifyThrown?.(e);
39493
+ if (custom2) {
39494
+ kind = custom2.kind;
39495
+ customHint = custom2.hint;
39496
+ detail = custom2.detail;
39497
+ }
39498
+ error51 = {
39499
+ kind,
39500
+ // Redacted AND bounded before it reaches the result: an upstream
39501
+ // failure routinely quotes what it was sent, and a healthcheck is
39502
+ // the tool people paste into a chat when something is broken.
39503
+ message: truncateErrorMessage(messageOf(e)),
39504
+ ...detail !== void 0 ? { detail } : {}
39505
+ };
39506
+ }
39507
+ const result = {
39508
+ ok: error51 === void 0,
39509
+ credential,
39510
+ probe: {
39511
+ ...probeUrl ? { url: probeUrl } : {},
39512
+ elapsed_ms: Date.now() - probeStarted,
39513
+ ...status !== void 0 ? { status } : {}
39514
+ },
39515
+ ...error51 ? { error: error51 } : {},
39516
+ hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
39517
+ };
39518
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
39519
+ });
39520
+ }
39521
+
39018
39522
  // src/config.ts
39019
39523
  function readVar(env, key) {
39020
39524
  return readEnvVar(key, { env });
@@ -39056,7 +39560,7 @@ function loadAccount(env = process.env) {
39056
39560
  // package.json
39057
39561
  var package_default = {
39058
39562
  name: "infinitecampus-mcp",
39059
- version: "2.6.0",
39563
+ version: "2.8.0",
39060
39564
  mcpName: "io.github.chrischall/infinitecampus-mcp",
39061
39565
  description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
39062
39566
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -39101,7 +39605,7 @@ var package_default = {
39101
39605
  typecheck: "tsc -p tsconfig.json --noEmit"
39102
39606
  },
39103
39607
  dependencies: {
39104
- "@chrischall/mcp-utils": "^0.18.0",
39608
+ "@chrischall/mcp-utils": "^0.23.2",
39105
39609
  "@fetchproxy/bootstrap": "^2.2.0",
39106
39610
  "@fetchproxy/server": "^2.2.0",
39107
39611
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -40113,15 +40617,71 @@ var FileExistsError = class extends Error {
40113
40617
  path;
40114
40618
  };
40115
40619
 
40620
+ // src/view.ts
40621
+ var IC_VIEWS = ["compact", "full"];
40622
+ var NOTE = `compact strips image/avatar URLs from the response; "full" returns Infinite Campus's payload untouched. No field projection: this server has no verified record of which Infinite Campus fields matter, and inventing one would risk dropping a field a caller needs.`;
40623
+ var viewArg = () => viewParam(IC_VIEWS, { note: NOTE });
40624
+ function viewResponse(view, data) {
40625
+ const rung = resolveView(view, IC_VIEWS);
40626
+ return minifiedResult(rung === "compact" ? stripMediaUrls(data) : data);
40627
+ }
40628
+
40116
40629
  // src/tools/districts.ts
40117
40630
  function registerDistrictTools(server, client) {
40118
40631
  server.registerTool("ic_list_districts", {
40119
40632
  description: "List Infinite Campus districts configured for this MCP server. Returns names + base URLs (no credentials).",
40633
+ inputSchema: {
40634
+ view: viewArg()
40635
+ },
40120
40636
  annotations: { readOnlyHint: true }
40121
- }, async () => {
40637
+ }, async ({ view }) => {
40122
40638
  await client.ensureDiscovery();
40123
40639
  const data = client.listDistricts();
40124
- return textResult(data);
40640
+ return viewResponse(view, data);
40641
+ });
40642
+ }
40643
+
40644
+ // src/tools/healthcheck.ts
40645
+ function registerHealthcheckTools(server, state) {
40646
+ registerCredentialHealthcheckTool({
40647
+ server,
40648
+ prefix: "ic",
40649
+ hostLabel: "your Infinite Campus portal",
40650
+ resolveCredential: async () => {
40651
+ if (!state.account || !state.client) {
40652
+ throw state.configError ?? new Error("Infinite Campus is not configured.");
40653
+ }
40654
+ const detail = {
40655
+ base_url: state.account.baseUrl,
40656
+ configured_district: state.account.district,
40657
+ auth_source: state.source ?? "unknown"
40658
+ };
40659
+ try {
40660
+ await state.client.ensureDiscovery();
40661
+ const districts = state.client.listDistricts();
40662
+ detail.districts = districts.map((d) => ({ name: d.name, linked: d.linked }));
40663
+ const linked = districts.filter((d) => d.linked).map((d) => d.name);
40664
+ detail.configured_district_is_linked = districts.some(
40665
+ (d) => d.name === state.account.district && d.linked
40666
+ );
40667
+ if (linked.length > 0) detail.linked_districts = linked;
40668
+ } catch {
40669
+ detail.districts = "unavailable (discovery needs a live session)";
40670
+ }
40671
+ return { source: state.source ?? "env", detail };
40672
+ },
40673
+ // Probe the district the account is actually LINKED to when that differs
40674
+ // from the configured one — probing a district you are not enrolled in
40675
+ // fails for a reason that has nothing to do with your credentials.
40676
+ probeFn: () => {
40677
+ const districts = state.client.listDistricts();
40678
+ const linked = districts.find((d) => d.linked);
40679
+ const district = linked?.name ?? state.account.district;
40680
+ return state.client.request(district, "/campus/api/portal/students");
40681
+ },
40682
+ hints: {
40683
+ credential_rejected: "Infinite Campus rejected the credentials. Check IC_USERNAME/IC_PASSWORD, or sign into your IC portal in the browser so the fetchproxy fallback can lift a session."
40684
+ }
40125
40685
  });
40126
40686
  }
40127
40687
 
@@ -40133,11 +40693,11 @@ function registerStudentTools(server, client) {
40133
40693
  server.registerTool("ic_list_students", {
40134
40694
  description: "List students enrolled under the parent account for a given district.",
40135
40695
  annotations: { readOnlyHint: true },
40136
- inputSchema: argsSchema.shape
40696
+ inputSchema: { ...argsSchema.shape, view: viewArg() }
40137
40697
  }, async (rawArgs) => {
40138
40698
  const args = argsSchema.parse(rawArgs);
40139
40699
  const data = await client.request(args.district, "/campus/api/portal/students");
40140
- return textResult(data);
40700
+ return viewResponse(rawArgs.view, data);
40141
40701
  });
40142
40702
  }
40143
40703
 
@@ -40152,12 +40712,12 @@ function registerScheduleTools(server, client) {
40152
40712
  server.registerTool("ic_get_schedule", {
40153
40713
  description: "Get a student's class schedule for a given date (default: today).",
40154
40714
  annotations: { readOnlyHint: true },
40155
- inputSchema: argsSchema2.shape
40715
+ inputSchema: { ...argsSchema2.shape, view: viewArg() }
40156
40716
  }, async (rawArgs) => {
40157
40717
  const args = argsSchema2.parse(rawArgs);
40158
40718
  const params = new URLSearchParams({ personID: args.studentId });
40159
40719
  const data = await client.request(args.district, `/campus/resources/portal/roster?${params}`);
40160
- return textResult(data);
40720
+ return viewResponse(rawArgs.view, data);
40161
40721
  });
40162
40722
  }
40163
40723
 
@@ -40174,7 +40734,7 @@ function registerAssignmentTools(server, client) {
40174
40734
  server.registerTool("ic_list_assignments", {
40175
40735
  description: "List a student's assignments. The IC endpoint returns the full term history (~hundreds of items); date and missing filters are applied client-side. For a single course, pass courseId (the sectionID from ic_get_schedule).",
40176
40736
  annotations: { readOnlyHint: true },
40177
- inputSchema: argsSchema3.shape
40737
+ inputSchema: { ...argsSchema3.shape, view: viewArg() }
40178
40738
  }, async (rawArgs) => {
40179
40739
  const args = argsSchema3.parse(rawArgs);
40180
40740
  const params = new URLSearchParams({ personID: args.studentId });
@@ -40195,7 +40755,7 @@ function registerAssignmentTools(server, client) {
40195
40755
  if (args.missingOnly) {
40196
40756
  data = data.filter((a) => a.missing);
40197
40757
  }
40198
- return textResult(data);
40758
+ return viewResponse(rawArgs.view, data);
40199
40759
  });
40200
40760
  }
40201
40761
 
@@ -40209,13 +40769,13 @@ function registerGradeTools(server, client) {
40209
40769
  server.registerTool("ic_list_grades", {
40210
40770
  description: "List a student's term grades and in-progress course grades.",
40211
40771
  annotations: { readOnlyHint: true },
40212
- inputSchema: argsSchema4.shape
40772
+ inputSchema: { ...argsSchema4.shape, view: viewArg() }
40213
40773
  }, async (rawArgs) => {
40214
40774
  const args = argsSchema4.parse(rawArgs);
40215
40775
  const params = new URLSearchParams({ personID: args.studentId });
40216
40776
  if (args.termId) params.set("termID", args.termId);
40217
40777
  const data = await client.request(args.district, `/campus/resources/portal/grades?${params}`);
40218
- return textResult(data);
40778
+ return viewResponse(rawArgs.view, data);
40219
40779
  });
40220
40780
  }
40221
40781
 
@@ -40224,14 +40784,14 @@ function is404(e) {
40224
40784
  return e instanceof Error && e.message.startsWith("IC 404 ");
40225
40785
  }
40226
40786
  function featureDisabled(feature, district, data = []) {
40227
- return textResult({ warning: "FeatureDisabled", feature, district, data });
40787
+ return minifiedResult({ warning: "FeatureDisabled", feature, district, data });
40228
40788
  }
40229
40789
  async function findStudent(client, district, studentId) {
40230
40790
  const students = await client.request(district, "/campus/api/portal/students");
40231
40791
  return students.find((s) => String(s.personID) === studentId) ?? null;
40232
40792
  }
40233
40793
  function studentNotFound(studentId) {
40234
- return textResult({ error: "StudentNotFound", studentId });
40794
+ return minifiedResult({ error: "StudentNotFound", studentId });
40235
40795
  }
40236
40796
  async function checkFeatureDisabled(client, district, studentId, student, flag, toolName, emptyData = []) {
40237
40797
  const structureID = student.enrollments?.[0]?.structureID;
@@ -40284,7 +40844,7 @@ function registerAttendanceTools(server, client) {
40284
40844
  server.registerTool("ic_list_attendance", {
40285
40845
  description: "List a student's absences and tardies (per-course summary grouped by term). Auto-resolves enrollmentID from the student record.",
40286
40846
  annotations: { readOnlyHint: true },
40287
- inputSchema: argsSchema5.shape
40847
+ inputSchema: { ...argsSchema5.shape, view: viewArg() }
40288
40848
  }, async (rawArgs) => {
40289
40849
  const args = argsSchema5.parse(rawArgs);
40290
40850
  const student = await findStudent(client, args.district, args.studentId);
@@ -40314,7 +40874,7 @@ function registerAttendanceTools(server, client) {
40314
40874
  results.push({ ...entry, terms: trimmedTerms });
40315
40875
  }
40316
40876
  }
40317
- return textResult(results);
40877
+ return viewResponse(rawArgs.view, results);
40318
40878
  } catch (e) {
40319
40879
  if (is404(e)) return featureDisabled("attendance", args.district);
40320
40880
  throw e;
@@ -40333,7 +40893,7 @@ function registerBehaviorTools(server, client) {
40333
40893
  server.registerTool("ic_list_behavior", {
40334
40894
  description: "List a student's behavior events / referrals. Returns FeatureDisabled if the district has the behavior module turned off (detected via displayOptions or a 404 backstop).",
40335
40895
  annotations: { readOnlyHint: true },
40336
- inputSchema: argsSchema6.shape
40896
+ inputSchema: { ...argsSchema6.shape, view: viewArg() }
40337
40897
  }, async (rawArgs) => {
40338
40898
  const args = argsSchema6.parse(rawArgs);
40339
40899
  const student = await findStudent(client, args.district, args.studentId);
@@ -40345,7 +40905,7 @@ function registerBehaviorTools(server, client) {
40345
40905
  if (args.until) params.set("endDate", args.until);
40346
40906
  try {
40347
40907
  const data = await client.request(args.district, `/campus/resources/portal/behavior?${params}`);
40348
- return textResult(data);
40908
+ return viewResponse(rawArgs.view, data);
40349
40909
  } catch (e) {
40350
40910
  if (is404(e)) return featureDisabled("behavior", args.district);
40351
40911
  throw e;
@@ -40365,7 +40925,7 @@ function registerFoodServiceTools(server, client) {
40365
40925
  server.registerTool("ic_list_food_service", {
40366
40926
  description: "List a student's lunch balance and recent food-service transactions. Returns FeatureDisabled if the district has the module turned off (detected via displayOptions or a 404 backstop).",
40367
40927
  annotations: { readOnlyHint: true },
40368
- inputSchema: argsSchema7.shape
40928
+ inputSchema: { ...argsSchema7.shape, view: viewArg() }
40369
40929
  }, async (rawArgs) => {
40370
40930
  const args = argsSchema7.parse(rawArgs);
40371
40931
  const student = await findStudent(client, args.district, args.studentId);
@@ -40385,7 +40945,7 @@ function registerFoodServiceTools(server, client) {
40385
40945
  if (args.until) params.set("endDate", args.until);
40386
40946
  try {
40387
40947
  const data = await client.request(args.district, `/campus/resources/portal/foodService?${params}`);
40388
- return textResult(data);
40948
+ return viewResponse(rawArgs.view, data);
40389
40949
  } catch (e) {
40390
40950
  if (is404(e)) return featureDisabled("foodService", args.district, EMPTY_FOOD);
40391
40951
  throw e;
@@ -40510,7 +41070,7 @@ function registerMessageTools(server, client) {
40510
41070
  server.registerTool("ic_list_messages", {
40511
41071
  description: "List all parent-visible messages from three IC sources combined: (1) prism notifications (assignment alerts, grade postings, attendance alerts), (2) Messenger 2.0 inbox (teacher messages, district announcements with newMessage/actionRequired flags), and (3) portal userNotice announcements. Each section has its own count and items; if any source errors, that section contains an error field and the others still return normally. The `limit` arg caps the prism notifications only (the high-volume source). Note: listing inbox messages does not mark them as read in normal portal behavior, but some district configurations may update read-tracking; use ic_get_message for the full HTML body.",
40512
41072
  annotations: { readOnlyHint: true },
40513
- inputSchema: listArgs.shape
41073
+ inputSchema: { ...listArgs.shape, view: viewArg() }
40514
41074
  }, async (rawArgs) => {
40515
41075
  const args = listArgs.parse(rawArgs);
40516
41076
  const limit = args.limit ?? 20;
@@ -40543,18 +41103,18 @@ function registerMessageTools(server, client) {
40543
41103
  return { count: 0, items: [], error: e instanceof Error ? e.message : String(e) };
40544
41104
  });
40545
41105
  const [notifications, inbox, announcements] = await Promise.all([prismPromise, inboxPromise, noticePromise]);
40546
- return textResult({ notifications, inbox, announcements });
41106
+ return viewResponse(rawArgs.view, { notifications, inbox, announcements });
40547
41107
  });
40548
41108
  server.registerTool("ic_get_message", {
40549
41109
  description: "Fetch the HTML body of an inbox message and return it parsed into { subject, date, body, url }. Takes a `messageUrl` which is the `url` field from an item returned by ic_list_messages' inbox section (e.g. 'portal/messageView.xsl?x=messenger.MessengerEngine-getMessageRecipientView&messageID=...'). Relative and /campus/-prefixed URLs are both accepted. Note: fetching the HTML body may mark the message as read on some district configurations; probe against an empty inbox could not confirm the side effect.",
40550
41110
  annotations: { readOnlyHint: true },
40551
- inputSchema: getArgs.shape
41111
+ inputSchema: { ...getArgs.shape, view: viewArg() }
40552
41112
  }, async (rawArgs) => {
40553
41113
  const args = getArgs.parse(rawArgs);
40554
41114
  const path = normalizeMessageUrl(args.messageUrl);
40555
41115
  const html = await client.request(args.district, path, { responseType: "text" });
40556
41116
  const parsed = parseMessageHtml(html ?? "", path);
40557
- return textResult(parsed);
41117
+ return viewResponse(rawArgs.view, parsed);
40558
41118
  });
40559
41119
  }
40560
41120
 
@@ -40573,7 +41133,7 @@ function registerDocumentTools(server, client) {
40573
41133
  server.registerTool("ic_list_documents", {
40574
41134
  description: "List a student's available documents (report cards, transcripts, schedules). Returns metadata only \u2014 use ic_download_document to fetch the file. Returns FeatureDisabled if the district has the module turned off.",
40575
41135
  annotations: { readOnlyHint: true },
40576
- inputSchema: listArgs2.shape
41136
+ inputSchema: { ...listArgs2.shape, view: viewArg() }
40577
41137
  }, async (rawArgs) => {
40578
41138
  const args = listArgs2.parse(rawArgs);
40579
41139
  const student = await findStudent(client, args.district, args.studentId);
@@ -40594,7 +41154,7 @@ function registerDocumentTools(server, client) {
40594
41154
  if (d.endYear !== void 0) out.endYear = d.endYear;
40595
41155
  return out;
40596
41156
  });
40597
- return textResult(trimmed);
41157
+ return viewResponse(rawArgs.view, trimmed);
40598
41158
  } catch (e) {
40599
41159
  if (is404(e)) return featureDisabled("documents", args.district);
40600
41160
  throw e;
@@ -40610,10 +41170,10 @@ function registerDocumentTools(server, client) {
40610
41170
  const meta3 = await client.download(args.district, args.documentId, args.destinationPath, {
40611
41171
  overwrite: args.overwrite ?? false
40612
41172
  });
40613
- return textResult(meta3);
41173
+ return minifiedResult(meta3);
40614
41174
  } catch (e) {
40615
41175
  if (e instanceof Error && e.message.startsWith("IC download 404")) {
40616
- return textResult({ warning: "FeatureDisabled", feature: "documents", district: args.district });
41176
+ return minifiedResult({ warning: "FeatureDisabled", feature: "documents", district: args.district });
40617
41177
  }
40618
41178
  throw e;
40619
41179
  }
@@ -40631,7 +41191,7 @@ function registerCalendarTools(server, client) {
40631
41191
  server.registerTool("ic_list_school_days", {
40632
41192
  description: "List a student's school days (instructional calendar) grouped by term. Returns one entry per enrollment, with term boundaries (Q1-Q4 start/end dates) and the school days inside each term \u2014 including comments like 'Teacher Workday' or 'Spring Break'. Use since/until to narrow the range.",
40633
41193
  annotations: { readOnlyHint: true },
40634
- inputSchema: argsSchema8.shape
41194
+ inputSchema: { ...argsSchema8.shape, view: viewArg() }
40635
41195
  }, async (rawArgs) => {
40636
41196
  const args = argsSchema8.parse(rawArgs);
40637
41197
  const student = await findStudent(client, args.district, args.studentId);
@@ -40669,7 +41229,7 @@ function registerCalendarTools(server, client) {
40669
41229
  terms: trimmedTerms
40670
41230
  });
40671
41231
  }
40672
- return textResult(result);
41232
+ return viewResponse(rawArgs.view, result);
40673
41233
  });
40674
41234
  }
40675
41235
 
@@ -40727,7 +41287,7 @@ function registerAttendanceEventsTools(server, client) {
40727
41287
  server.registerTool("ic_list_attendance_events", {
40728
41288
  description: "List individual attendance events (absences, tardies, early releases) for a student. Each event has a code, description, excuse reason, and optional human-readable comments. Auto-resolves enrollmentID from the student record. Use since/until to filter by date and excusedOnly to show only excused events.",
40729
41289
  annotations: { readOnlyHint: true },
40730
- inputSchema: argsSchema9.shape
41290
+ inputSchema: { ...argsSchema9.shape, view: viewArg() }
40731
41291
  }, async (rawArgs) => {
40732
41292
  const args = argsSchema9.parse(rawArgs);
40733
41293
  const student = await findStudent(client, args.district, args.studentId);
@@ -40760,7 +41320,7 @@ function registerAttendanceEventsTools(server, client) {
40760
41320
  results.push(trimmed);
40761
41321
  }
40762
41322
  }
40763
- return textResult(results);
41323
+ return viewResponse(rawArgs.view, results);
40764
41324
  } catch (e) {
40765
41325
  if (is404(e)) return featureDisabled("attendance_events", args.district);
40766
41326
  throw e;
@@ -40799,7 +41359,7 @@ function registerRecentGradesTools(server, client) {
40799
41359
  server.registerTool("ic_list_recent_grades", {
40800
41360
  description: "List recently-graded assignments for a student. Server-side filtered by scoreModifiedDate. Pass since=YYYY-MM-DD to set the cutoff; defaults to 14 days ago.",
40801
41361
  annotations: { readOnlyHint: true },
40802
- inputSchema: argsSchema10.shape
41362
+ inputSchema: { ...argsSchema10.shape, view: viewArg() }
40803
41363
  }, async (rawArgs) => {
40804
41364
  const args = argsSchema10.parse(rawArgs);
40805
41365
  const sinceDate = args.since ?? defaultSinceDate(/* @__PURE__ */ new Date());
@@ -40816,7 +41376,7 @@ function registerRecentGradesTools(server, client) {
40816
41376
  }
40817
41377
  return out;
40818
41378
  });
40819
- return textResult(trimmed);
41379
+ return viewResponse(rawArgs.view, trimmed);
40820
41380
  });
40821
41381
  }
40822
41382
 
@@ -40850,7 +41410,7 @@ function registerTeacherTools(server, client) {
40850
41410
  server.registerTool("ic_list_teachers", {
40851
41411
  description: "List a student's teachers (per enrolled section) and assigned counselor(s). Combines two endpoints (section/contacts and studentCounselor/byUser). Response field shapes may vary slightly by district \u2014 core fields (firstName, lastName, email) are consistent; additional fields are passed through.",
40852
41412
  annotations: { readOnlyHint: true },
40853
- inputSchema: argsSchema11.shape
41413
+ inputSchema: { ...argsSchema11.shape, view: viewArg() }
40854
41414
  }, async (rawArgs) => {
40855
41415
  const args = argsSchema11.parse(rawArgs);
40856
41416
  const personID = encodeURIComponent(args.studentId);
@@ -40871,7 +41431,7 @@ function registerTeacherTools(server, client) {
40871
41431
  const [teachersRaw, counselorsRaw] = await Promise.all([teachersPromise, counselorsPromise]);
40872
41432
  const teachers = toArray(teachersRaw).map((t) => trimRecord(t));
40873
41433
  const counselors = toArray(counselorsRaw).map((c) => trimRecord(c));
40874
- return textResult({ counselors, teachers });
41434
+ return viewResponse(rawArgs.view, { counselors, teachers });
40875
41435
  });
40876
41436
  }
40877
41437
 
@@ -40884,7 +41444,7 @@ function registerAssessmentTools(server, client) {
40884
41444
  server.registerTool("ic_list_assessments", {
40885
41445
  description: "List a student's standardized test scores (state, national, district tests). Auto-resolves calendarID from each of the student's enrollments and returns one entry per enrollment. The shape of individual test records varies by district and test type \u2014 fields are passed through unchanged.",
40886
41446
  annotations: { readOnlyHint: true },
40887
- inputSchema: argsSchema12.shape
41447
+ inputSchema: { ...argsSchema12.shape, view: viewArg() }
40888
41448
  }, async (rawArgs) => {
40889
41449
  const args = argsSchema12.parse(rawArgs);
40890
41450
  const student = await findStudent(client, args.district, args.studentId);
@@ -40922,7 +41482,7 @@ function registerAssessmentTools(server, client) {
40922
41482
  if (feature404 && result.length === 0) {
40923
41483
  return featureDisabled("assessments", args.district);
40924
41484
  }
40925
- return textResult(result);
41485
+ return viewResponse(rawArgs.view, result);
40926
41486
  });
40927
41487
  }
40928
41488
 
@@ -40935,7 +41495,7 @@ function registerFeeTools(server, client) {
40935
41495
  server.registerTool("ic_list_fees", {
40936
41496
  description: "List a student's fee assignments (charges owed) and running balance/surplus. Combines two endpoints: fee assignments and totalSurplus. Returns FeatureDisabled only if both endpoints 404; if only one works, returns that side with warning: 'PartialSuccess' and an issues[] explaining which endpoint failed.",
40937
41497
  annotations: { readOnlyHint: true },
40938
- inputSchema: argsSchema13.shape
41498
+ inputSchema: { ...argsSchema13.shape, view: viewArg() }
40939
41499
  }, async (rawArgs) => {
40940
41500
  const args = argsSchema13.parse(rawArgs);
40941
41501
  const personIDEnc = encodeURIComponent(args.studentId);
@@ -40966,7 +41526,7 @@ function registerFeeTools(server, client) {
40966
41526
  feeAssignments: assignments.ok ? assignments.value : [],
40967
41527
  ...issues.length > 0 ? { issues } : {}
40968
41528
  };
40969
- return textResult(response);
41529
+ return viewResponse(rawArgs.view, response);
40970
41530
  });
40971
41531
  }
40972
41532
 
@@ -40979,7 +41539,7 @@ function registerFeaturesTools(server, client) {
40979
41539
  server.registerTool("ic_get_features", {
40980
41540
  description: "List the district's displayOptions feature-flag allow-list for each of a student's enrollments. Each enrollment's `features` object is a map of ~90 flag names (attendance, behavior, assessment, documents, grades, schedule, etc.) to booleans. A `false` value means the district has that feature disabled for this enrollment; `true` or missing means it's available. Used internally by other tools to short-circuit disabled features, but exposed here so the LLM can answer capability questions directly.",
40981
41541
  annotations: { readOnlyHint: true },
40982
- inputSchema: argsSchema14.shape
41542
+ inputSchema: { ...argsSchema14.shape, view: viewArg() }
40983
41543
  }, async (rawArgs) => {
40984
41544
  const args = argsSchema14.parse(rawArgs);
40985
41545
  const student = await findStudent(client, args.district, args.studentId);
@@ -40994,7 +41554,7 @@ function registerFeaturesTools(server, client) {
40994
41554
  features
40995
41555
  });
40996
41556
  }
40997
- return textResult(result);
41557
+ return viewResponse(rawArgs.view, result);
40998
41558
  });
40999
41559
  }
41000
41560
 
@@ -41018,12 +41578,15 @@ try {
41018
41578
  }
41019
41579
  var COMMON = {
41020
41580
  name: "infinitecampus",
41021
- version: "2.6.0"
41581
+ version: "2.8.0"
41022
41582
  // x-release-please-version
41023
41583
  };
41584
+ var healthState = { account, source, configError, client: null };
41024
41585
  if (account) {
41025
41586
  const client = new ICClient(account, { refreshSession });
41587
+ healthState.client = client;
41026
41588
  const tools = [
41589
+ (server) => registerHealthcheckTools(server, healthState),
41027
41590
  registerDistrictTools,
41028
41591
  registerStudentTools,
41029
41592
  registerScheduleTools,
@@ -41049,6 +41612,10 @@ ${AI_NOTICE}` });
41049
41612
  } else {
41050
41613
  const notConfigured = `[infinitecampus-mcp] Not configured: ${configError?.message ?? "unknown error"}
41051
41614
  [infinitecampus-mcp] Server is running with no tools registered. Set the required env vars and reinstall.`;
41052
- await runMcp({ ...COMMON, tools: [], banner: `${notConfigured}
41053
- ${AI_NOTICE}` });
41615
+ await runMcp({
41616
+ ...COMMON,
41617
+ tools: [(server) => registerHealthcheckTools(server, healthState)],
41618
+ banner: `${notConfigured}
41619
+ ${AI_NOTICE}`
41620
+ });
41054
41621
  }