@dereekb/firebase-server 13.12.2 → 13.12.3

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/mcp/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { getOidcScopesFromRequest } from '@dereekb/firebase-server/oidc';
6
- import { authRolesSetHasRoles } from '@dereekb/util';
6
+ import { makeValuesGroupMap, authRolesSetHasRoles } from '@dereekb/util';
7
7
  import { ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerAnalyticsService } from '@dereekb/firebase-server';
8
8
  import { inspect } from 'node:util';
9
9
  import { arktypeToJsonSchemaForExport } from '@dereekb/model';
@@ -355,7 +355,7 @@ function stringifyResult(result) {
355
355
  return callModelOidcScopeForCallType(callType);
356
356
  }
357
357
 
358
- function _array_like_to_array$5(arr, len) {
358
+ function _array_like_to_array$6(arr, len) {
359
359
  if (len == null || len > arr.length) len = arr.length;
360
360
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
361
361
  return arr2;
@@ -391,19 +391,19 @@ function _non_iterable_rest$2() {
391
391
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
392
392
  }
393
393
  function _sliced_to_array$2(arr, i) {
394
- return _array_with_holes$2(arr) || _iterable_to_array_limit$2(arr, i) || _unsupported_iterable_to_array$5(arr, i) || _non_iterable_rest$2();
394
+ return _array_with_holes$2(arr) || _iterable_to_array_limit$2(arr, i) || _unsupported_iterable_to_array$6(arr, i) || _non_iterable_rest$2();
395
395
  }
396
396
  function _type_of$1(obj) {
397
397
  "@swc/helpers - typeof";
398
398
  return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
399
399
  }
400
- function _unsupported_iterable_to_array$5(o, minLen) {
400
+ function _unsupported_iterable_to_array$6(o, minLen) {
401
401
  if (!o) return;
402
- if (typeof o === "string") return _array_like_to_array$5(o, minLen);
402
+ if (typeof o === "string") return _array_like_to_array$6(o, minLen);
403
403
  var n = Object.prototype.toString.call(o).slice(8, -1);
404
404
  if (n === "Object" && o.constructor) n = o.constructor.name;
405
405
  if (n === "Map" || n === "Set") return Array.from(n);
406
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
406
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$6(o, minLen);
407
407
  }
408
408
  /**
409
409
  * Default `toJsonSchema()` options.
@@ -689,13 +689,13 @@ function resolveInputSchema(context) {
689
689
  return result;
690
690
  }
691
691
 
692
- function _array_like_to_array$4(arr, len) {
692
+ function _array_like_to_array$5(arr, len) {
693
693
  if (len == null || len > arr.length) len = arr.length;
694
694
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
695
695
  return arr2;
696
696
  }
697
- function _array_without_holes$4(arr) {
698
- if (Array.isArray(arr)) return _array_like_to_array$4(arr);
697
+ function _array_without_holes$5(arr) {
698
+ if (Array.isArray(arr)) return _array_like_to_array$5(arr);
699
699
  }
700
700
  function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
701
701
  try {
@@ -726,22 +726,22 @@ function _async_to_generator$3(fn) {
726
726
  });
727
727
  };
728
728
  }
729
- function _iterable_to_array$4(iter) {
729
+ function _iterable_to_array$5(iter) {
730
730
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
731
731
  }
732
- function _non_iterable_spread$4() {
732
+ function _non_iterable_spread$5() {
733
733
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
734
734
  }
735
- function _to_consumable_array$4(arr) {
736
- return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
735
+ function _to_consumable_array$5(arr) {
736
+ return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$5(arr) || _non_iterable_spread$5();
737
737
  }
738
- function _unsupported_iterable_to_array$4(o, minLen) {
738
+ function _unsupported_iterable_to_array$5(o, minLen) {
739
739
  if (!o) return;
740
- if (typeof o === "string") return _array_like_to_array$4(o, minLen);
740
+ if (typeof o === "string") return _array_like_to_array$5(o, minLen);
741
741
  var n = Object.prototype.toString.call(o).slice(8, -1);
742
742
  if (n === "Object" && o.constructor) n = o.constructor.name;
743
743
  if (n === "Map" || n === "Set") return Array.from(n);
744
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
744
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
745
745
  }
746
746
  function _ts_generator$3(thisArg, body) {
747
747
  var f, y, t, _ = {
@@ -1046,8 +1046,8 @@ function readInChunks(input) {
1046
1046
  ];
1047
1047
  case 2:
1048
1048
  partial = _state.sent();
1049
- (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$4(partial.results));
1050
- (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$4(partial.errors));
1049
+ (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$5(partial.results));
1050
+ (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$5(partial.errors));
1051
1051
  _state.label = 3;
1052
1052
  case 3:
1053
1053
  offset += MCP_MODEL_GET_BATCH_SIZE;
@@ -1138,6 +1138,14 @@ var MODEL_GET_OUTPUT_SCHEMA = {
1138
1138
  }
1139
1139
  };
1140
1140
 
1141
+ function _array_like_to_array$4(arr, len) {
1142
+ if (len == null || len > arr.length) len = arr.length;
1143
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
1144
+ return arr2;
1145
+ }
1146
+ function _array_without_holes$4(arr) {
1147
+ if (Array.isArray(arr)) return _array_like_to_array$4(arr);
1148
+ }
1141
1149
  function _define_property$6(obj, key, value) {
1142
1150
  if (key in obj) {
1143
1151
  Object.defineProperty(obj, key, {
@@ -1151,6 +1159,12 @@ function _define_property$6(obj, key, value) {
1151
1159
  }
1152
1160
  return obj;
1153
1161
  }
1162
+ function _iterable_to_array$4(iter) {
1163
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
1164
+ }
1165
+ function _non_iterable_spread$4() {
1166
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1167
+ }
1154
1168
  function _object_spread$2(target) {
1155
1169
  for(var i = 1; i < arguments.length; i++){
1156
1170
  var source = arguments[i] != null ? arguments[i] : {};
@@ -1166,6 +1180,36 @@ function _object_spread$2(target) {
1166
1180
  }
1167
1181
  return target;
1168
1182
  }
1183
+ function ownKeys(object, enumerableOnly) {
1184
+ var keys = Object.keys(object);
1185
+ if (Object.getOwnPropertySymbols) {
1186
+ var symbols = Object.getOwnPropertySymbols(object);
1187
+ keys.push.apply(keys, symbols);
1188
+ }
1189
+ return keys;
1190
+ }
1191
+ function _object_spread_props(target, source) {
1192
+ source = source != null ? source : {};
1193
+ if (Object.getOwnPropertyDescriptors) {
1194
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
1195
+ } else {
1196
+ ownKeys(Object(source)).forEach(function(key) {
1197
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
1198
+ });
1199
+ }
1200
+ return target;
1201
+ }
1202
+ function _to_consumable_array$4(arr) {
1203
+ return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
1204
+ }
1205
+ function _unsupported_iterable_to_array$4(o, minLen) {
1206
+ if (!o) return;
1207
+ if (typeof o === "string") return _array_like_to_array$4(o, minLen);
1208
+ var n = Object.prototype.toString.call(o).slice(8, -1);
1209
+ if (n === "Object" && o.constructor) n = o.constructor.name;
1210
+ if (n === "Map" || n === "Set") return Array.from(n);
1211
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
1212
+ }
1169
1213
  // MARK: Constants
1170
1214
  /**
1171
1215
  * Reserved tool name for the built-in `model-info` static tool.
@@ -1178,13 +1222,27 @@ function _object_spread$2(target) {
1178
1222
  * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE};
1179
1223
  * apps avoiding collisions should not register a real model literally named "model".
1180
1224
  */ var MODEL_INFO_DISPATCH_MODEL_TYPE = 'model';
1225
+ /**
1226
+ * Group label applied to manifest entries that declare no `modelGroup`. Keeps the `groups` mode
1227
+ * total reconcilable with the catalog size (sum of group counts always equals `totalModels`).
1228
+ */ var MODEL_INFO_UNGROUPED_LABEL = '(ungrouped)';
1229
+ /**
1230
+ * Hint string surfaced alongside the default `groups` response so callers know how to drill in.
1231
+ */ var MODEL_INFO_GROUPS_HINT = 'Pass `model` for one/many models, `modelGroup` for a group, or `all:true` for the full catalog.';
1181
1232
  // MARK: Factory
1182
1233
  /**
1183
1234
  * Builds the built-in `model-info` MCP tool definition.
1184
1235
  *
1185
- * Mirrors dbx-cli's `model-info [model]` command:
1186
- * - Without a `model` arg: returns a summary list (modelType, modelName, prefix, group, identity, field count).
1187
- * - With a `model` arg: matches by `modelType`, `identityConst`, or `collectionPrefix` and returns the full entry.
1236
+ * Modes (selected by input):
1237
+ * - No args: returns model groups + counts (`mode: 'groups'`) — the cheap default for browsing.
1238
+ * - `model` (string): returns the full entry for one model (`mode: 'single'`).
1239
+ * - `model` (string array): returns full detail per match (`mode: 'multiple'`); misses land in `notFound`.
1240
+ * - `modelGroup`: returns the summary list of that group's models (`mode: 'list'`).
1241
+ * - `all: true`: returns the full catalog summary (`mode: 'list'`).
1242
+ * - `fields`: forces (`true`) or suppresses (`false`) full persisted-field detail in list/multiple modes.
1243
+ *
1244
+ * Each `model` query matches by `modelType`, `identityConst`, or `collectionPrefix`. When several
1245
+ * params are supplied, precedence is `model` > `modelGroup` > `all` > the default `groups` view.
1188
1246
  *
1189
1247
  * Output is delivered as both stringified JSON in `content[0].text` and `structuredContent`
1190
1248
  * so MCP clients can consume either form.
@@ -1197,7 +1255,8 @@ function _object_spread$2(target) {
1197
1255
  return Promise.resolve(modelInfoToolHandler(args, ctx, deps));
1198
1256
  };
1199
1257
  var name = MODEL_INFO_TOOL_NAME;
1200
- var description = "Browse the Firestore model catalog (".concat(deps.manifest.length, " model").concat(deps.manifest.length === 1 ? '' : 's', "). Without `model`, returns a summary list of every model. With `model` (matched by modelType, identityConst, or collectionPrefix), returns the full entry with persisted fields.");
1258
+ var count = deps.manifest.length;
1259
+ var description = "Browse the Firestore model catalog (".concat(count, " model").concat(count === 1 ? '' : 's', "). No args returns model groups + counts (start here). ") + '`model` (a modelType/identityConst/collectionPrefix string, or an array of them) returns full detail per match — misses are reported in `notFound`. ' + '`modelGroup` returns the summary list for that group. `all:true` returns the full catalog summary. ' + '`fields` (boolean) forces or suppresses persisted-field detail in list/multiple modes.';
1201
1260
  return {
1202
1261
  name: name,
1203
1262
  description: description,
@@ -1228,7 +1287,7 @@ function modelInfoToolHandler(args, _ctx, deps) {
1228
1287
  var result;
1229
1288
  try {
1230
1289
  var input = parseModelInfoInput(args);
1231
- var output = input.model == null ? buildListOutput(deps.manifest) : buildSingleOutput(input.model, deps.manifest);
1290
+ var output = resolveModelInfoOutput(input, deps.manifest);
1232
1291
  result = {
1233
1292
  content: [
1234
1293
  {
@@ -1243,60 +1302,285 @@ function modelInfoToolHandler(args, _ctx, deps) {
1243
1302
  }
1244
1303
  return result;
1245
1304
  }
1305
+ function resolveModelInfoOutput(input, manifest) {
1306
+ var output;
1307
+ if (input.modelQueries != null) {
1308
+ output = buildModelLookupOutput({
1309
+ queries: input.modelQueries,
1310
+ isArray: input.modelIsArray,
1311
+ manifest: manifest,
1312
+ fields: input.fields
1313
+ });
1314
+ } else if (input.modelGroup != null) {
1315
+ output = buildGroupFilterOutput({
1316
+ group: input.modelGroup,
1317
+ manifest: manifest,
1318
+ fields: input.fields
1319
+ });
1320
+ } else if (input.all) {
1321
+ output = buildAllOutput(manifest, input.fields);
1322
+ } else {
1323
+ output = buildGroupsOutput(manifest);
1324
+ }
1325
+ return output;
1326
+ }
1327
+ // MARK: Input parsing
1246
1328
  function parseModelInfoInput(args) {
1247
- var raw = args['model'];
1248
- var model;
1329
+ var modelQueries = parseModelArg(args['model']);
1330
+ var modelGroup = parseModelGroupArg(args['modelGroup']);
1331
+ var all = parseBooleanArg(args['all'], 'all');
1332
+ var fields = parseBooleanArg(args['fields'], 'fields');
1333
+ return _object_spread$2(_object_spread_props(_object_spread$2(_object_spread_props(_object_spread$2({}, modelQueries == null ? {} : {
1334
+ modelQueries: modelQueries
1335
+ }), {
1336
+ modelIsArray: Array.isArray(args['model'])
1337
+ }), modelGroup == null ? {} : {
1338
+ modelGroup: modelGroup
1339
+ }), {
1340
+ all: all !== null && all !== void 0 ? all : false
1341
+ }), fields == null ? {} : {
1342
+ fields: fields
1343
+ });
1344
+ }
1345
+ function parseModelArg(raw) {
1346
+ var result;
1249
1347
  if (raw == null) {
1250
- model = undefined;
1348
+ result = undefined;
1251
1349
  } else if (typeof raw === 'string') {
1252
- var trimmed = raw.trim();
1253
- if (trimmed.length === 0) {
1254
- throw new Error('model-info: "model" must be a non-empty string when provided.');
1350
+ result = [
1351
+ requireNonEmptyString(raw, 'model')
1352
+ ];
1353
+ } else if (Array.isArray(raw)) {
1354
+ if (raw.length === 0) {
1355
+ throw new Error('model-info: "model" array must contain at least one entry.');
1255
1356
  }
1256
- model = trimmed;
1357
+ result = raw.map(function(value, index) {
1358
+ return requireNonEmptyString(value, "model[".concat(index, "]"));
1359
+ });
1360
+ } else {
1361
+ throw new TypeError('model-info: "model" must be a string or an array of strings when provided.');
1362
+ }
1363
+ return result;
1364
+ }
1365
+ function parseModelGroupArg(raw) {
1366
+ var result;
1367
+ if (raw == null) {
1368
+ result = undefined;
1369
+ } else if (typeof raw === 'string') {
1370
+ result = requireNonEmptyString(raw, 'modelGroup');
1257
1371
  } else {
1258
- throw new TypeError('model-info: "model" must be a string when provided.');
1372
+ throw new TypeError('model-info: "modelGroup" must be a string when provided.');
1373
+ }
1374
+ return result;
1375
+ }
1376
+ function parseBooleanArg(raw, label) {
1377
+ var result;
1378
+ if (raw == null) {
1379
+ result = undefined;
1380
+ } else if (typeof raw === 'boolean') {
1381
+ result = raw;
1382
+ } else {
1383
+ throw new TypeError('model-info: "'.concat(label, '" must be a boolean when provided.'));
1384
+ }
1385
+ return result;
1386
+ }
1387
+ function requireNonEmptyString(value, label) {
1388
+ if (typeof value !== 'string') {
1389
+ throw new TypeError('model-info: "'.concat(label, '" must be a string.'));
1259
1390
  }
1391
+ var trimmed = value.trim();
1392
+ if (trimmed.length === 0) {
1393
+ throw new Error('model-info: "'.concat(label, '" must be a non-empty string.'));
1394
+ }
1395
+ return trimmed;
1396
+ }
1397
+ // MARK: Output builders
1398
+ function buildGroupsOutput(manifest) {
1260
1399
  return {
1261
- model: model
1400
+ mode: 'groups',
1401
+ groups: computeGroupCounts(manifest),
1402
+ totalModels: manifest.length,
1403
+ hint: MODEL_INFO_GROUPS_HINT
1262
1404
  };
1263
1405
  }
1264
- function buildListOutput(manifest) {
1265
- var models = manifest.map(function(entry) {
1266
- var row = _object_spread$2({
1267
- modelType: entry.modelType,
1268
- modelName: entry.modelName,
1269
- identityConst: entry.identityConst,
1270
- collectionPrefix: entry.collectionPrefix,
1271
- sourcePackage: entry.sourcePackage,
1272
- fieldCount: entry.fields.length
1273
- }, entry.modelGroup == null ? {} : {
1274
- modelGroup: entry.modelGroup
1275
- }, entry.parentIdentityConst == null ? {} : {
1276
- parentIdentityConst: entry.parentIdentityConst
1277
- }, entry.description == null ? {} : {
1278
- description: entry.description
1279
- }, entry.read == null ? {} : {
1280
- read: entry.read
1281
- }, entry.serviceFactory == null ? {} : {
1282
- serviceFactoryExport: entry.serviceFactory.exportName
1283
- });
1284
- return row;
1406
+ function buildGroupFilterOutput(config) {
1407
+ var group = config.group, manifest = config.manifest, fields = config.fields;
1408
+ var detailed = fields !== null && fields !== void 0 ? fields : false;
1409
+ var lower = group.toLowerCase();
1410
+ var matches = manifest.filter(function(entry) {
1411
+ var _entry_modelGroup;
1412
+ return ((_entry_modelGroup = entry.modelGroup) === null || _entry_modelGroup === void 0 ? void 0 : _entry_modelGroup.toLowerCase()) === lower;
1285
1413
  });
1414
+ var result;
1415
+ if (matches.length === 0) {
1416
+ throw new Error(buildUnknownGroupMessage(group, manifest));
1417
+ } else {
1418
+ var canonical = matches[0].modelGroup;
1419
+ result = {
1420
+ mode: 'list',
1421
+ modelGroup: canonical,
1422
+ models: matches.map(function(entry) {
1423
+ return toModelRow(entry, detailed);
1424
+ })
1425
+ };
1426
+ }
1427
+ return result;
1428
+ }
1429
+ function buildAllOutput(manifest, fields) {
1430
+ var detailed = fields !== null && fields !== void 0 ? fields : false;
1286
1431
  return {
1287
1432
  mode: 'list',
1288
- models: models
1433
+ models: manifest.map(function(entry) {
1434
+ return toModelRow(entry, detailed);
1435
+ })
1289
1436
  };
1290
1437
  }
1291
- function buildSingleOutput(query, manifest) {
1292
- var entry = findModelEntry(query, manifest);
1293
- if (entry == null) {
1294
- throw new Error('model-info: no model matches "'.concat(query, '". Call `model-info` without arguments to list available models.'));
1438
+ function buildModelLookupOutput(config) {
1439
+ var queries = config.queries, isArray = config.isArray, manifest = config.manifest, fields = config.fields;
1440
+ var detailed = fields !== null && fields !== void 0 ? fields : true;
1441
+ var matched = [];
1442
+ var notFound = [];
1443
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1444
+ try {
1445
+ for(var _iterator = queries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1446
+ var query = _step.value;
1447
+ var entry = findModelEntry(query, manifest);
1448
+ if (entry == null) {
1449
+ notFound.push({
1450
+ query: query,
1451
+ message: buildModelNotFoundMessage(query, manifest)
1452
+ });
1453
+ } else {
1454
+ matched.push(toModelRow(entry, detailed));
1455
+ }
1456
+ }
1457
+ } catch (err) {
1458
+ _didIteratorError = true;
1459
+ _iteratorError = err;
1460
+ } finally{
1461
+ try {
1462
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1463
+ _iterator.return();
1464
+ }
1465
+ } finally{
1466
+ if (_didIteratorError) {
1467
+ throw _iteratorError;
1468
+ }
1469
+ }
1295
1470
  }
1296
- return {
1297
- mode: 'single',
1298
- model: entry
1299
- };
1471
+ var result;
1472
+ if (isArray) {
1473
+ result = notFound.length > 0 ? {
1474
+ mode: 'multiple',
1475
+ models: matched,
1476
+ notFound: notFound
1477
+ } : {
1478
+ mode: 'multiple',
1479
+ models: matched
1480
+ };
1481
+ } else if (matched.length > 0) {
1482
+ result = {
1483
+ mode: 'single',
1484
+ model: matched[0]
1485
+ };
1486
+ } else {
1487
+ throw new Error(notFound[0].message);
1488
+ }
1489
+ return result;
1490
+ }
1491
+ // MARK: Row + group helpers
1492
+ function toModelRow(entry, detailed) {
1493
+ return detailed ? entry : toSummaryRow(entry);
1494
+ }
1495
+ function toSummaryRow(entry) {
1496
+ return _object_spread$2({
1497
+ modelType: entry.modelType,
1498
+ modelName: entry.modelName,
1499
+ identityConst: entry.identityConst,
1500
+ collectionPrefix: entry.collectionPrefix,
1501
+ sourcePackage: entry.sourcePackage,
1502
+ fieldCount: entry.fields.length
1503
+ }, entry.modelGroup == null ? {} : {
1504
+ modelGroup: entry.modelGroup
1505
+ }, entry.parentIdentityConst == null ? {} : {
1506
+ parentIdentityConst: entry.parentIdentityConst
1507
+ }, entry.description == null ? {} : {
1508
+ description: entry.description
1509
+ }, entry.read == null ? {} : {
1510
+ read: entry.read
1511
+ }, entry.serviceFactory == null ? {} : {
1512
+ serviceFactoryExport: entry.serviceFactory.exportName
1513
+ });
1514
+ }
1515
+ /**
1516
+ * Counts models per `modelGroup`, bucketing entries without a group under
1517
+ * {@link MODEL_INFO_UNGROUPED_LABEL}. Sorted by count descending, then group name ascending.
1518
+ *
1519
+ * @param manifest - Model manifest to summarize.
1520
+ * @returns One {@link ModelInfoGroupCount} per group, ordered most-populous-first.
1521
+ *
1522
+ * @__NO_SIDE_EFFECTS__
1523
+ */ function computeGroupCounts(manifest) {
1524
+ var grouped = makeValuesGroupMap(_to_consumable_array$4(manifest), function(entry) {
1525
+ var _entry_modelGroup;
1526
+ return (_entry_modelGroup = entry.modelGroup) !== null && _entry_modelGroup !== void 0 ? _entry_modelGroup : MODEL_INFO_UNGROUPED_LABEL;
1527
+ });
1528
+ var counts = [];
1529
+ grouped.forEach(function(entries, group) {
1530
+ counts.push({
1531
+ modelGroup: group,
1532
+ modelCount: entries.length
1533
+ });
1534
+ });
1535
+ counts.sort(function(a, b) {
1536
+ return b.modelCount - a.modelCount || a.modelGroup.localeCompare(b.modelGroup);
1537
+ });
1538
+ return counts;
1539
+ }
1540
+ function findGroupCount(query, manifest) {
1541
+ var lower = query.toLowerCase();
1542
+ var result;
1543
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1544
+ try {
1545
+ for(var _iterator = computeGroupCounts(manifest)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1546
+ var count = _step.value;
1547
+ if (count.modelGroup.toLowerCase() === lower) {
1548
+ result = count;
1549
+ break;
1550
+ }
1551
+ }
1552
+ } catch (err) {
1553
+ _didIteratorError = true;
1554
+ _iteratorError = err;
1555
+ } finally{
1556
+ try {
1557
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1558
+ _iterator.return();
1559
+ }
1560
+ } finally{
1561
+ if (_didIteratorError) {
1562
+ throw _iteratorError;
1563
+ }
1564
+ }
1565
+ }
1566
+ return result;
1567
+ }
1568
+ function buildModelNotFoundMessage(query, manifest) {
1569
+ var groupMatch = findGroupCount(query, manifest);
1570
+ var message;
1571
+ if (groupMatch != null) {
1572
+ var plural = groupMatch.modelCount === 1 ? '' : 's';
1573
+ message = 'model-info: no exact match for "'.concat(query, '" — did you mean group:"').concat(groupMatch.modelGroup, '" (').concat(groupMatch.modelCount, " model").concat(plural, ')? Use modelGroup:"').concat(groupMatch.modelGroup, '", or all:true for everything.');
1574
+ } else {
1575
+ message = 'model-info: no exact match for "'.concat(query, '". Call `model-info` with no arguments to list groups, or pass all:true for the full catalog.');
1576
+ }
1577
+ return message;
1578
+ }
1579
+ function buildUnknownGroupMessage(group, manifest) {
1580
+ var available = computeGroupCounts(manifest).map(function(count) {
1581
+ return "".concat(count.modelGroup, " (").concat(count.modelCount, ")");
1582
+ }).join(', ');
1583
+ return 'model-info: no model group matches "'.concat(group, '". Available groups: ').concat(available || '(none)', ". Call `model-info` with no arguments to list groups.");
1300
1584
  }
1301
1585
  /**
1302
1586
  * Resolves a manifest entry by `modelType`, `identityConst`, or `collectionPrefix`.
@@ -1338,160 +1622,205 @@ var MODEL_INFO_INPUT_SCHEMA = {
1338
1622
  type: 'object',
1339
1623
  properties: {
1340
1624
  model: {
1625
+ description: 'One model (string) or many (string array), each matched by modelType, identityConst, or collectionPrefix. A string → `mode:"single"`; an array → `mode:"multiple"`. Misses are reported in `notFound`.',
1626
+ oneOf: [
1627
+ {
1628
+ type: 'string',
1629
+ minLength: 1
1630
+ },
1631
+ {
1632
+ type: 'array',
1633
+ minItems: 1,
1634
+ items: {
1635
+ type: 'string',
1636
+ minLength: 1
1637
+ }
1638
+ }
1639
+ ]
1640
+ },
1641
+ modelGroup: {
1341
1642
  type: 'string',
1342
1643
  minLength: 1,
1343
- description: 'Model to inspect (modelType, identityConst, or collectionPrefix). Omit to list every model.'
1644
+ description: 'Return the summary list of models in this group (`mode:"list"`). Matched case-insensitively.'
1645
+ },
1646
+ all: {
1647
+ type: 'boolean',
1648
+ description: 'Opt in to the full catalog summary (every model). Never the default — omit to get groups instead.'
1649
+ },
1650
+ fields: {
1651
+ type: 'boolean',
1652
+ description: 'Force (true) or suppress (false) full persisted-field detail. Defaults to summary rows for list mode and full entries for single/multiple modes.'
1344
1653
  }
1345
1654
  },
1346
1655
  additionalProperties: false
1347
1656
  };
1657
+ // Shared item schema for model-bearing rows. Required props cover the summary form; full entries
1658
+ // add `fields`, `sourceFile`, and `serviceFactory` (allowed as additional properties).
1659
+ var MODEL_ROW_SCHEMA = {
1660
+ type: 'object',
1661
+ required: [
1662
+ 'modelType',
1663
+ 'modelName',
1664
+ 'identityConst',
1665
+ 'collectionPrefix',
1666
+ 'sourcePackage'
1667
+ ],
1668
+ properties: {
1669
+ modelType: {
1670
+ type: 'string'
1671
+ },
1672
+ modelName: {
1673
+ type: 'string'
1674
+ },
1675
+ modelGroup: {
1676
+ type: 'string'
1677
+ },
1678
+ identityConst: {
1679
+ type: 'string'
1680
+ },
1681
+ collectionPrefix: {
1682
+ type: 'string'
1683
+ },
1684
+ parentIdentityConst: {
1685
+ type: 'string'
1686
+ },
1687
+ sourcePackage: {
1688
+ type: 'string'
1689
+ },
1690
+ sourceFile: {
1691
+ type: 'string'
1692
+ },
1693
+ fieldCount: {
1694
+ type: 'integer'
1695
+ },
1696
+ description: {
1697
+ type: 'string'
1698
+ },
1699
+ read: {
1700
+ type: 'string',
1701
+ enum: [
1702
+ 'system',
1703
+ 'owner',
1704
+ 'admin-only',
1705
+ 'permissions'
1706
+ ]
1707
+ },
1708
+ serviceFactoryExport: {
1709
+ type: 'string'
1710
+ },
1711
+ fields: {
1712
+ type: 'array',
1713
+ items: {
1714
+ type: 'object'
1715
+ }
1716
+ },
1717
+ serviceFactory: {
1718
+ type: 'object',
1719
+ required: [
1720
+ 'exportName',
1721
+ 'sourceFile'
1722
+ ],
1723
+ properties: {
1724
+ exportName: {
1725
+ type: 'string'
1726
+ },
1727
+ sourceFile: {
1728
+ type: 'string'
1729
+ }
1730
+ }
1731
+ }
1732
+ }
1733
+ };
1348
1734
  // MCP's `tools/list` validator requires `outputSchema.type === 'object'` at the root
1349
- // (see @modelcontextprotocol/sdk Zod schema), so the list/single union is expressed via a
1350
- // top-level object whose nested `oneOf` discriminates by the `mode` literal.
1735
+ // (see @modelcontextprotocol/sdk Zod schema), so the variants are expressed via a top-level
1736
+ // object whose nested `oneOf` discriminates by the `mode` literal.
1351
1737
  var MODEL_INFO_OUTPUT_SCHEMA = {
1352
1738
  type: 'object',
1353
1739
  required: [
1354
1740
  'mode'
1355
1741
  ],
1356
- description: 'Either lists every model in the manifest (when `model` is omitted) or returns a full entry for the requested model (when `model` is provided). The `mode` field discriminates the variant.',
1742
+ description: 'Discriminated by `mode`: `groups` (default model groups + counts), `list` (summary/full list for `modelGroup` or `all`), `single` (one `model`), or `multiple` (a `model` array, with misses in `notFound`).',
1357
1743
  properties: {
1358
1744
  mode: {
1359
1745
  type: 'string',
1360
1746
  enum: [
1747
+ 'groups',
1361
1748
  'list',
1362
- 'single'
1749
+ 'single',
1750
+ 'multiple'
1363
1751
  ]
1364
1752
  },
1365
- models: {
1753
+ groups: {
1366
1754
  type: 'array',
1367
- description: 'Present when `mode` is `"list"`. One summary entry per registered model.',
1755
+ description: 'Present when `mode` is `"groups"`. One entry per model group.',
1368
1756
  items: {
1369
1757
  type: 'object',
1370
1758
  required: [
1371
- 'modelType',
1372
- 'modelName',
1373
- 'identityConst',
1374
- 'collectionPrefix',
1375
- 'sourcePackage',
1376
- 'fieldCount'
1759
+ 'modelGroup',
1760
+ 'modelCount'
1377
1761
  ],
1378
1762
  properties: {
1379
- modelType: {
1380
- type: 'string'
1381
- },
1382
- modelName: {
1383
- type: 'string'
1384
- },
1385
1763
  modelGroup: {
1386
1764
  type: 'string'
1387
1765
  },
1388
- identityConst: {
1389
- type: 'string'
1390
- },
1391
- collectionPrefix: {
1392
- type: 'string'
1393
- },
1394
- parentIdentityConst: {
1395
- type: 'string'
1396
- },
1397
- sourcePackage: {
1398
- type: 'string'
1399
- },
1400
- fieldCount: {
1766
+ modelCount: {
1401
1767
  type: 'integer'
1402
- },
1403
- description: {
1768
+ }
1769
+ }
1770
+ }
1771
+ },
1772
+ totalModels: {
1773
+ type: 'integer',
1774
+ description: 'Present when `mode` is `"groups"`. Total models across all groups.'
1775
+ },
1776
+ hint: {
1777
+ type: 'string',
1778
+ description: 'Present when `mode` is `"groups"`. Guidance on how to drill into the catalog.'
1779
+ },
1780
+ models: {
1781
+ type: 'array',
1782
+ description: 'Present when `mode` is `"list"` or `"multiple"`. Summary rows by default; full entries when `fields` requests detail.',
1783
+ items: MODEL_ROW_SCHEMA
1784
+ },
1785
+ modelGroup: {
1786
+ type: 'string',
1787
+ description: 'Present when `mode` is `"list"` from a `modelGroup` filter. The canonical group name.'
1788
+ },
1789
+ model: _object_spread_props(_object_spread$2({}, MODEL_ROW_SCHEMA), {
1790
+ description: 'Present when `mode` is `"single"`. The matched model entry (full detail unless `fields:false`).'
1791
+ }),
1792
+ notFound: {
1793
+ type: 'array',
1794
+ description: 'Present when `mode` is `"multiple"` and one or more queries matched nothing.',
1795
+ items: {
1796
+ type: 'object',
1797
+ required: [
1798
+ 'query',
1799
+ 'message'
1800
+ ],
1801
+ properties: {
1802
+ query: {
1404
1803
  type: 'string'
1405
1804
  },
1406
- read: {
1407
- type: 'string',
1408
- enum: [
1409
- 'system',
1410
- 'owner',
1411
- 'admin-only',
1412
- 'permissions'
1413
- ]
1414
- },
1415
- serviceFactoryExport: {
1805
+ message: {
1416
1806
  type: 'string'
1417
1807
  }
1418
1808
  }
1419
1809
  }
1420
- },
1421
- model: {
1422
- type: 'object',
1423
- description: 'Present when `mode` is `"single"`. The full manifest entry for the requested model.',
1810
+ }
1811
+ },
1812
+ oneOf: [
1813
+ {
1424
1814
  required: [
1425
- 'modelType',
1426
- 'modelName',
1427
- 'identityConst',
1428
- 'collectionPrefix',
1429
- 'sourcePackage',
1430
- 'sourceFile',
1431
- 'fields'
1815
+ 'groups',
1816
+ 'totalModels'
1432
1817
  ],
1433
1818
  properties: {
1434
- modelType: {
1435
- type: 'string'
1436
- },
1437
- modelName: {
1438
- type: 'string'
1439
- },
1440
- modelGroup: {
1441
- type: 'string'
1442
- },
1443
- identityConst: {
1444
- type: 'string'
1445
- },
1446
- collectionPrefix: {
1447
- type: 'string'
1448
- },
1449
- parentIdentityConst: {
1450
- type: 'string'
1451
- },
1452
- description: {
1453
- type: 'string'
1454
- },
1455
- sourcePackage: {
1456
- type: 'string'
1457
- },
1458
- sourceFile: {
1459
- type: 'string'
1460
- },
1461
- fields: {
1462
- type: 'array',
1463
- items: {
1464
- type: 'object'
1465
- }
1466
- },
1467
- read: {
1468
- type: 'string',
1469
- enum: [
1470
- 'system',
1471
- 'owner',
1472
- 'admin-only',
1473
- 'permissions'
1474
- ]
1475
- },
1476
- serviceFactory: {
1477
- type: 'object',
1478
- required: [
1479
- 'exportName',
1480
- 'sourceFile'
1481
- ],
1482
- properties: {
1483
- exportName: {
1484
- type: 'string'
1485
- },
1486
- sourceFile: {
1487
- type: 'string'
1488
- }
1489
- }
1819
+ mode: {
1820
+ const: 'groups'
1490
1821
  }
1491
1822
  }
1492
- }
1493
- },
1494
- oneOf: [
1823
+ },
1495
1824
  {
1496
1825
  required: [
1497
1826
  'models'
@@ -1511,6 +1840,16 @@ var MODEL_INFO_OUTPUT_SCHEMA = {
1511
1840
  const: 'single'
1512
1841
  }
1513
1842
  }
1843
+ },
1844
+ {
1845
+ required: [
1846
+ 'models'
1847
+ ],
1848
+ properties: {
1849
+ mode: {
1850
+ const: 'multiple'
1851
+ }
1852
+ }
1514
1853
  }
1515
1854
  ]
1516
1855
  };
@@ -3936,4 +4275,4 @@ function _unsupported_iterable_to_array(o, minLen) {
3936
4275
  };
3937
4276
  }
3938
4277
 
3939
- export { DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MODEL_DECODE_DISPATCH_CALL, MODEL_DECODE_DISPATCH_MODEL_TYPE, MODEL_DECODE_TOOL_NAME, MODEL_GET_DISPATCH_CALL, MODEL_GET_DISPATCH_MODEL_TYPE, MODEL_GET_TOOL_NAME, MODEL_INFO_DISPATCH_CALL, MODEL_INFO_DISPATCH_MODEL_TYPE, MODEL_INFO_TOOL_NAME, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, appMcpAnalyticsModuleMetadata, buildDefaultMcpToolDescription, buildMcpToolName, buildStaticWireEntry, classifyVisibility, createModelDecodeTool, createModelGetTool, createModelInfoTool, decodeFirestoreModelKey, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, mcpManifestKey, mcpModuleMetadata, noopMcpAnalyticsService, resolveEffectiveReadOnly, resolveRequiredScope };
4278
+ export { DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MODEL_DECODE_DISPATCH_CALL, MODEL_DECODE_DISPATCH_MODEL_TYPE, MODEL_DECODE_TOOL_NAME, MODEL_GET_DISPATCH_CALL, MODEL_GET_DISPATCH_MODEL_TYPE, MODEL_GET_TOOL_NAME, MODEL_INFO_DISPATCH_CALL, MODEL_INFO_DISPATCH_MODEL_TYPE, MODEL_INFO_GROUPS_HINT, MODEL_INFO_TOOL_NAME, MODEL_INFO_UNGROUPED_LABEL, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, appMcpAnalyticsModuleMetadata, buildDefaultMcpToolDescription, buildMcpToolName, buildStaticWireEntry, classifyVisibility, createModelDecodeTool, createModelGetTool, createModelInfoTool, decodeFirestoreModelKey, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, mcpManifestKey, mcpModuleMetadata, noopMcpAnalyticsService, resolveEffectiveReadOnly, resolveRequiredScope };