@dereekb/firebase-server 14.5.1 → 14.6.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/mcp/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Injectable, Inject, Optional, Logger, Post, Req, Res, Get, Delete, Controller, HttpStatus } from '@nestjs/common';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { McpServer, isLegacyRequest, isJsonContentType, createMcpHandler } from '@modelcontextprotocol/server';
4
- import { callModelOidcScopeForCallType, resolveEffectiveOidcScopeTerms, CLI_TOKEN_OIDC_SCOPE, inferKeyFromTwoWayFlatFirestoreModelKey, oidcScopeTermsSatisfied } from '@dereekb/firebase';
4
+ import { callModelOidcScopeForCallType, resolveEffectiveOidcScopeTerms, twoWayFlatFirestoreModelKey, flatFirestoreModelKey, inferKeyFromTwoWayFlatFirestoreModelKey, CLI_TOKEN_OIDC_SCOPE, oidcScopeTermsSatisfied } from '@dereekb/firebase';
5
5
  import { getOidcScopesFromRequest, OidcProviderConfigService } from '@dereekb/firebase-server/oidc';
6
6
  import { makeValuesGroupMap, AUTH_ADMIN_ROLE, IMPERSONATION_URL_QUERY_PARAM, performAsyncTasks, authRolesSetHasRoles } from '@dereekb/util';
7
7
  import { requestClientIp, ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerStorageService, FirebaseServerEnvService, FirebaseServerAnalyticsService } from '@dereekb/firebase-server';
@@ -252,7 +252,7 @@ function _object_spread$9(target) {
252
252
  }
253
253
  return target;
254
254
  }
255
- function ownKeys$3(object, enumerableOnly) {
255
+ function ownKeys$4(object, enumerableOnly) {
256
256
  var keys = Object.keys(object);
257
257
  if (Object.getOwnPropertySymbols) {
258
258
  var symbols = Object.getOwnPropertySymbols(object);
@@ -260,11 +260,11 @@ function ownKeys$3(object, enumerableOnly) {
260
260
  }
261
261
  return keys;
262
262
  }
263
- function _object_spread_props$3(target, source) {
263
+ function _object_spread_props$4(target, source) {
264
264
  source = source != null ? source : {};
265
265
  if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
266
266
  else {
267
- ownKeys$3(Object(source)).forEach(function(key) {
267
+ ownKeys$4(Object(source)).forEach(function(key) {
268
268
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
269
269
  });
270
270
  }
@@ -356,9 +356,9 @@ function _unsupported_iterable_to_array$b(o, minLen) {
356
356
  var _base_type;
357
357
  var base = (typeof inputSchema === "undefined" ? "undefined" : _type_of$4(inputSchema)) === 'object' && inputSchema != null ? inputSchema : {};
358
358
  var existingProperties = _type_of$4(base.properties) === 'object' && base.properties != null ? base.properties : {};
359
- var next = _object_spread_props$3(_object_spread$9({}, base), {
359
+ var next = _object_spread_props$4(_object_spread$9({}, base), {
360
360
  type: (_base_type = base.type) !== null && _base_type !== void 0 ? _base_type : 'object',
361
- properties: _object_spread_props$3(_object_spread$9({}, existingProperties), _define_property$b({}, resolved.parameterName, {
361
+ properties: _object_spread_props$4(_object_spread$9({}, existingProperties), _define_property$b({}, resolved.parameterName, {
362
362
  type: 'string',
363
363
  maxLength: resolved.maxLength,
364
364
  description: resolved.description
@@ -447,6 +447,34 @@ function _class_call_check$4(instance, Constructor) {
447
447
  * Mirrors {@link MCP_MANIFEST_VERSION} in `@dereekb/dbx-cli` — both packages
448
448
  * version-bump together when the manifest shape changes.
449
449
  */ var MCP_MANIFEST_VERSION = 1;
450
+ /**
451
+ * Resolves the manifest entries whose `compositeKey.from` names `source` — the models whose document
452
+ * id is derived from a `source` document's key. A wildcard `from=*` matches every model except
453
+ * `source` itself (a model's id is never derived from its own key).
454
+ *
455
+ * @param source - The manifest entry of the decoded key's leaf model.
456
+ * @param manifest - The full model manifest.
457
+ * @returns The composite-key entries derived from `source`, in manifest order.
458
+ */ function findCompositeKeyModelsDerivedFrom(source, manifest) {
459
+ return manifest.filter(function(entry) {
460
+ return isCompositeKeyModelDerivedFrom(source, entry);
461
+ });
462
+ }
463
+ /**
464
+ * Whether `candidate` is a composite-key model whose document id is derived from a `source`
465
+ * document's key — `candidate.compositeKey.from` names `source` (by interface name, identity const,
466
+ * or modelType) or is the wildcard `*`. A model is never derived from its own key.
467
+ *
468
+ * @param source - The manifest entry of the source model.
469
+ * @param candidate - The manifest entry to test.
470
+ * @returns `true` when `candidate`'s id is a flattened `source` key.
471
+ */ function isCompositeKeyModelDerivedFrom(source, candidate) {
472
+ var _candidate_compositeKey;
473
+ var from = (_candidate_compositeKey = candidate.compositeKey) === null || _candidate_compositeKey === void 0 ? void 0 : _candidate_compositeKey.from;
474
+ return from != null && candidate.modelType !== source.modelType && (from === '*' || from.some(function(name) {
475
+ return name === source.modelName || name === source.identityConst || name === source.modelType;
476
+ }));
477
+ }
450
478
  /**
451
479
  * Builds the canonical MCP manifest key for a (modelType, callType, specifier) triple.
452
480
  *
@@ -1795,31 +1823,16 @@ function _array_like_to_array$8(arr, len) {
1795
1823
  function _array_without_holes$6(arr) {
1796
1824
  if (Array.isArray(arr)) return _array_like_to_array$8(arr);
1797
1825
  }
1798
- function asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, key, arg) {
1799
- try {
1800
- var info = gen[key](arg);
1801
- var value = info.value;
1802
- } catch (error) {
1803
- reject(error);
1804
- return;
1805
- }
1806
- if (info.done) resolve(value);
1807
- else Promise.resolve(value).then(_next, _throw);
1808
- }
1809
- function _async_to_generator$7(fn) {
1810
- return function() {
1811
- var self = this, args = arguments;
1812
- return new Promise(function(resolve, reject) {
1813
- var gen = fn.apply(self, args);
1814
- function _next(value) {
1815
- asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "next", value);
1816
- }
1817
- function _throw(err) {
1818
- asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "throw", err);
1819
- }
1820
- _next(undefined);
1826
+ function _define_property$9(obj, key, value) {
1827
+ if (key in obj) {
1828
+ Object.defineProperty(obj, key, {
1829
+ value: value,
1830
+ enumerable: true,
1831
+ configurable: true,
1832
+ writable: true
1821
1833
  });
1822
- };
1834
+ } else obj[key] = value;
1835
+ return obj;
1823
1836
  }
1824
1837
  function _iterable_to_array$6(iter) {
1825
1838
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
@@ -1829,107 +1842,41 @@ function _iterable_to_array$6(iter) {
1829
1842
  function _non_iterable_spread$6() {
1830
1843
  throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1831
1844
  }
1832
- function _to_consumable_array$6(arr) {
1833
- return _array_without_holes$6(arr) || _iterable_to_array$6(arr) || _unsupported_iterable_to_array$8(arr) || _non_iterable_spread$6();
1834
- }
1835
- function _ts_generator$7(thisArg, body) {
1836
- var f, y, t, _ = {
1837
- label: 0,
1838
- sent: function() {
1839
- if (t[0] & 1) throw t[1];
1840
- return t[1];
1841
- },
1842
- trys: [],
1843
- ops: []
1844
- }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
1845
- return d(g, "next", {
1846
- value: verb(0)
1847
- }), d(g, "throw", {
1848
- value: verb(1)
1849
- }), d(g, "return", {
1850
- value: verb(2)
1851
- }), typeof Symbol === "function" && d(g, Symbol.iterator, {
1852
- value: function() {
1853
- return this;
1845
+ function _object_spread$7(target) {
1846
+ for(var i = 1; i < arguments.length; i++){
1847
+ var source = arguments[i] != null ? arguments[i] : {};
1848
+ var ownKeys = Object.keys(source);
1849
+ if (typeof Object.getOwnPropertySymbols === "function") {
1850
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
1851
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
1852
+ }));
1854
1853
  }
1855
- }), g;
1856
- function verb(n) {
1857
- return function(v) {
1858
- return step([
1859
- n,
1860
- v
1861
- ]);
1862
- };
1854
+ ownKeys.forEach(function(key) {
1855
+ _define_property$9(target, key, source[key]);
1856
+ });
1863
1857
  }
1864
- function step(op) {
1865
- if (f) throw new TypeError("Generator is already executing.");
1866
- while(g && (g = 0, op[0] && (_ = 0)), _)try {
1867
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
1868
- if (y = 0, t) op = [
1869
- op[0] & 2,
1870
- t.value
1871
- ];
1872
- switch(op[0]){
1873
- case 0:
1874
- case 1:
1875
- t = op;
1876
- break;
1877
- case 4:
1878
- _.label++;
1879
- return {
1880
- value: op[1],
1881
- done: false
1882
- };
1883
- case 5:
1884
- _.label++;
1885
- y = op[1];
1886
- op = [
1887
- 0
1888
- ];
1889
- continue;
1890
- case 7:
1891
- op = _.ops.pop();
1892
- _.trys.pop();
1893
- continue;
1894
- default:
1895
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
1896
- _ = 0;
1897
- continue;
1898
- }
1899
- if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
1900
- _.label = op[1];
1901
- break;
1902
- }
1903
- if (op[0] === 6 && _.label < t[1]) {
1904
- _.label = t[1];
1905
- t = op;
1906
- break;
1907
- }
1908
- if (t && _.label < t[2]) {
1909
- _.label = t[2];
1910
- _.ops.push(op);
1911
- break;
1912
- }
1913
- if (t[2]) _.ops.pop();
1914
- _.trys.pop();
1915
- continue;
1916
- }
1917
- op = body.call(thisArg, _);
1918
- } catch (e) {
1919
- op = [
1920
- 6,
1921
- e
1922
- ];
1923
- y = 0;
1924
- } finally{
1925
- f = t = 0;
1926
- }
1927
- if (op[0] & 5) throw op[1];
1928
- return {
1929
- value: op[0] ? op[1] : void 0,
1930
- done: true
1931
- };
1858
+ return target;
1859
+ }
1860
+ function ownKeys$3(object, enumerableOnly) {
1861
+ var keys = Object.keys(object);
1862
+ if (Object.getOwnPropertySymbols) {
1863
+ var symbols = Object.getOwnPropertySymbols(object);
1864
+ keys.push.apply(keys, symbols);
1865
+ }
1866
+ return keys;
1867
+ }
1868
+ function _object_spread_props$3(target, source) {
1869
+ source = source != null ? source : {};
1870
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
1871
+ else {
1872
+ ownKeys$3(Object(source)).forEach(function(key) {
1873
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
1874
+ });
1932
1875
  }
1876
+ return target;
1877
+ }
1878
+ function _to_consumable_array$6(arr) {
1879
+ return _array_without_holes$6(arr) || _iterable_to_array$6(arr) || _unsupported_iterable_to_array$8(arr) || _non_iterable_spread$6();
1933
1880
  }
1934
1881
  function _unsupported_iterable_to_array$8(o, minLen) {
1935
1882
  if (!o) return;
@@ -1941,56 +1888,59 @@ function _unsupported_iterable_to_array$8(o, minLen) {
1941
1888
  }
1942
1889
  // MARK: Constants
1943
1890
  /**
1944
- * Reserved tool name for the built-in `model-get` static tool.
1945
- */ var MODEL_GET_TOOL_NAME = 'model-get';
1891
+ * Reserved tool name for the built-in `model-info` static tool.
1892
+ */ var MODEL_INFO_TOOL_NAME = 'model-info';
1946
1893
  /**
1947
- * Synthetic call type used in the tool's dispatch identity. Aligns with the dbx-cli `get`
1948
- * command and the `/api/model/:modelType/get` HTTP endpoint that backs it.
1949
- */ var MODEL_GET_DISPATCH_CALL = 'get';
1894
+ * Synthetic call type used in the tool's dispatch identity. Aligns with the dbx-cli `model-info`
1895
+ * command.
1896
+ */ var MODEL_INFO_DISPATCH_CALL = 'info';
1950
1897
  /**
1951
- * Synthetic model type used in the tool's dispatch identity. The tool itself isn't bound to a
1952
- * specific model type (the `modelType` is part of its input), so we use the literal "model" as
1953
- * the identity. Apps avoiding collision should not register a real model literally named
1954
- * "model".
1955
- */ var MODEL_GET_DISPATCH_MODEL_TYPE = 'model';
1898
+ * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE};
1899
+ * apps avoiding collisions should not register a real model literally named "model".
1900
+ */ var MODEL_INFO_DISPATCH_MODEL_TYPE = 'model';
1956
1901
  /**
1957
- * Maximum number of keys per backend multi-read request. Matches
1958
- * `MAX_MODEL_ACCESS_MULTI_READ_KEYS` exported from `@dereekb/firebase-server`; mirrored as a
1959
- * local constant so the bundler doesn't have to follow the import chain just for one number.
1960
- */ var MCP_MODEL_GET_BATCH_SIZE = 50;
1902
+ * Group label applied to manifest entries that declare no `modelGroup`. Keeps the `groups` mode
1903
+ * total reconcilable with the catalog size (sum of group counts always equals `totalModels`).
1904
+ */ var MODEL_INFO_UNGROUPED_LABEL = '(ungrouped)';
1905
+ /**
1906
+ * Hint string surfaced alongside the default `groups` response so callers know how to drill in.
1907
+ */ var MODEL_INFO_GROUPS_HINT = 'Pass `model` for one/many models, `modelGroup` for a group, or `all:true` for the full catalog.';
1961
1908
  // MARK: Factory
1962
1909
  /**
1963
- * Builds the built-in `model-get` MCP tool definition.
1910
+ * Builds the built-in `model-info` MCP tool definition.
1964
1911
  *
1965
- * Mirrors dbx-cli's `get` / `get-many` behavior:
1966
- * - Accepts an array of keys; values containing `/` are treated as full keys and passed verbatim
1967
- * while bare ids are promoted to `${collectionName}/${id}` for root models.
1968
- * - Nested (subcollection) models reject bare ids since a parent path is required.
1969
- * - Auto-chunks at {@link MCP_MODEL_GET_BATCH_SIZE} keys per backend call, mirroring
1970
- * `getMultipleModelsOverHttpChunked` on the CLI side.
1912
+ * Modes (selected by input):
1913
+ * - No args: returns model groups + counts (`mode: 'groups'`) — the cheap default for browsing.
1914
+ * - `model` (string): returns the full entry for one model (`mode: 'single'`).
1915
+ * - `model` (string array): returns full detail per match (`mode: 'multiple'`); misses land in `notFound`.
1916
+ * - `modelGroup`: returns the summary list of that group's models (`mode: 'list'`).
1917
+ * - `all: true`: returns the full catalog summary (`mode: 'list'`).
1918
+ * - `fields`: forces (`true`) or suppresses (`false`) full persisted-field detail in list/multiple modes.
1971
1919
  *
1972
- * The handler returns the same `{ results, errors }` shape produced by
1973
- * `ModelApiGetService.readDocuments`, exposed via both the text content block and
1974
- * `structuredContent` so MCP clients can consume either form.
1920
+ * Each `model` query matches by `modelType`, `identityConst`, or `collectionPrefix`. When several
1921
+ * params are supplied, precedence is `model` > `modelGroup` > `all` > the default `groups` view.
1975
1922
  *
1976
- * @param deps - Read-documents and identity-resolver callbacks (typically wired to
1977
- * `ModelApiGetService`).
1923
+ * Output is delivered as both stringified JSON in `content[0].text` and `structuredContent`
1924
+ * so MCP clients can consume either form.
1925
+ *
1926
+ * @param deps - The frozen model manifest loaded at boot from the MCP manifest JSON.
1978
1927
  * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP
1979
1928
  * server factory's tool registry.
1980
- */ function createModelGetTool(deps) {
1929
+ */ function createModelInfoTool(deps) {
1981
1930
  var handler = function handler(args, ctx) {
1982
- return modelGetToolHandler(args, ctx, deps);
1931
+ return Promise.resolve(modelInfoToolHandler(args, ctx, deps));
1983
1932
  };
1984
- var name = MODEL_GET_TOOL_NAME;
1985
- var description = 'Fetch one or more Firestore model documents by key or bare id. Values containing `/` are treated as full keys; bare ids are auto-promoted to `<collectionName>/<id>` for root models. Subcollection models require full keys.';
1933
+ var name = MODEL_INFO_TOOL_NAME;
1934
+ var count = deps.manifest.length;
1935
+ 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.';
1986
1936
  return buildStaticToolDefinition({
1987
1937
  name: name,
1988
1938
  description: description,
1989
- inputSchema: MODEL_GET_INPUT_SCHEMA,
1990
- outputSchema: MODEL_GET_OUTPUT_SCHEMA,
1939
+ inputSchema: MODEL_INFO_INPUT_SCHEMA,
1940
+ outputSchema: MODEL_INFO_OUTPUT_SCHEMA,
1991
1941
  dispatch: {
1992
- call: MODEL_GET_DISPATCH_CALL,
1993
- modelType: MODEL_GET_DISPATCH_MODEL_TYPE
1942
+ call: MODEL_INFO_DISPATCH_CALL,
1943
+ modelType: MODEL_INFO_DISPATCH_MODEL_TYPE
1994
1944
  },
1995
1945
  staticHandler: handler,
1996
1946
  effectiveReadOnly: true,
@@ -2000,98 +1950,139 @@ function _unsupported_iterable_to_array$8(o, minLen) {
2000
1950
  });
2001
1951
  }
2002
1952
  // MARK: Handler
2003
- function modelGetToolHandler(args, ctx, deps) {
2004
- return _async_to_generator$7(function() {
2005
- var result, input, identity, resolvedKeys, merged, error;
2006
- return _ts_generator$7(this, function(_state) {
2007
- switch(_state.label){
2008
- case 0:
2009
- _state.trys.push([
2010
- 0,
2011
- 2,
2012
- ,
2013
- 3
2014
- ]);
2015
- input = parseModelGetInput(args);
2016
- identity = deps.resolveIdentity(input.modelType, ctx.auth);
2017
- if (identity == null) {
2018
- throw new Error("Unknown modelType: ".concat(input.modelType));
2019
- }
2020
- resolvedKeys = resolveKeys$1(input.keys, identity);
2021
- return [
2022
- 4,
2023
- readInChunks$1({
2024
- modelType: input.modelType,
2025
- keys: resolvedKeys,
2026
- auth: ctx.auth,
2027
- readDocuments: deps.readDocuments
2028
- })
2029
- ];
2030
- case 1:
2031
- merged = _state.sent();
2032
- result = {
2033
- content: [
2034
- {
2035
- type: 'text',
2036
- text: JSON.stringify(merged)
2037
- }
2038
- ],
2039
- structuredContent: merged
2040
- };
2041
- return [
2042
- 3,
2043
- 3
2044
- ];
2045
- case 2:
2046
- error = _state.sent();
2047
- result = formatMcpToolErrorResponse(error);
2048
- return [
2049
- 3,
2050
- 3
2051
- ];
2052
- case 3:
2053
- return [
2054
- 2,
2055
- result
2056
- ];
2057
- }
1953
+ function modelInfoToolHandler(args, _ctx, deps) {
1954
+ var result;
1955
+ try {
1956
+ var input = parseModelInfoInput(args);
1957
+ var resolved = resolveModelInfoOutput(input, deps.manifest);
1958
+ var output = attachReferencedEnums(resolved, deps.enums);
1959
+ result = {
1960
+ content: [
1961
+ {
1962
+ type: 'text',
1963
+ text: JSON.stringify(output)
1964
+ }
1965
+ ],
1966
+ structuredContent: output
1967
+ };
1968
+ } catch (error) {
1969
+ result = formatMcpToolErrorResponse(error);
1970
+ }
1971
+ return result;
1972
+ }
1973
+ function resolveModelInfoOutput(input, manifest) {
1974
+ var output;
1975
+ if (input.modelQueries != null) {
1976
+ output = buildModelLookupOutput({
1977
+ queries: input.modelQueries,
1978
+ isArray: input.modelIsArray,
1979
+ manifest: manifest,
1980
+ fields: input.fields
2058
1981
  });
2059
- })();
1982
+ } else if (input.modelGroup != null) {
1983
+ output = buildGroupFilterOutput({
1984
+ group: input.modelGroup,
1985
+ manifest: manifest,
1986
+ fields: input.fields
1987
+ });
1988
+ } else if (input.all) {
1989
+ output = buildAllOutput(manifest, input.fields);
1990
+ } else {
1991
+ output = buildGroupsOutput(manifest);
1992
+ }
1993
+ return output;
2060
1994
  }
2061
- function parseModelGetInput(args) {
2062
- var modelType = args['modelType'];
2063
- var keys = args['keys'];
2064
- if (typeof modelType !== 'string' || modelType.length === 0) {
2065
- throw new Error('model-get: "modelType" is required and must be a non-empty string.');
1995
+ // MARK: Enum attachment
1996
+ /**
1997
+ * Attaches the value→label tables for every enum referenced (by `enumRef`) on the returned models'
1998
+ * fields — recursively through `nestedFields` — as a top-level `enums` section.
1999
+ *
2000
+ * Gated behind full field detail: only `single` / `multiple` rows and `list` rows that opted into
2001
+ * `fields:true` carry the persisted `fields` array, so the compact default modes are untouched.
2002
+ * Returns the input unchanged when no enum map is wired or no referenced enum resolves.
2003
+ *
2004
+ * @param output - The resolved model-info output.
2005
+ * @param enums - The optional enum value tables keyed by name.
2006
+ * @returns The output, with an `enums` section attached when detailed rows reference known enums.
2007
+ */ function attachReferencedEnums(output, enums) {
2008
+ var result = output;
2009
+ if (enums != null) {
2010
+ var referenced = new Set();
2011
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2012
+ try {
2013
+ for(var _iterator = collectDetailedEntries(output)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2014
+ var entry = _step.value;
2015
+ collectFieldEnumRefs(entry.fields, referenced);
2016
+ }
2017
+ } catch (err) {
2018
+ _didIteratorError = true;
2019
+ _iteratorError = err;
2020
+ } finally{
2021
+ try {
2022
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2023
+ _iterator.return();
2024
+ }
2025
+ } finally{
2026
+ if (_didIteratorError) {
2027
+ throw _iteratorError;
2028
+ }
2029
+ }
2030
+ }
2031
+ var tables = [];
2032
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2033
+ try {
2034
+ for(var _iterator1 = referenced[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2035
+ var name = _step1.value;
2036
+ var table = enums[name];
2037
+ if (table != null) tables.push(table);
2038
+ }
2039
+ } catch (err) {
2040
+ _didIteratorError1 = true;
2041
+ _iteratorError1 = err;
2042
+ } finally{
2043
+ try {
2044
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2045
+ _iterator1.return();
2046
+ }
2047
+ } finally{
2048
+ if (_didIteratorError1) {
2049
+ throw _iteratorError1;
2050
+ }
2051
+ }
2052
+ }
2053
+ if (tables.length > 0) {
2054
+ result = _object_spread_props$3(_object_spread$7({}, output), {
2055
+ enums: tables
2056
+ });
2057
+ }
2066
2058
  }
2067
- if (!Array.isArray(keys) || keys.length === 0) {
2068
- throw new Error('model-get: "keys" is required and must be a non-empty array.');
2059
+ return result;
2060
+ }
2061
+ /**
2062
+ * Collects the full {@link McpManifestModelEntry} rows (those carrying a persisted `fields` array)
2063
+ * from a model-info output. Summary rows (which expose `fieldCount`, not `fields`) are skipped.
2064
+ *
2065
+ * @param output - The resolved model-info output.
2066
+ * @returns The detailed model entries present in the output.
2067
+ */ function collectDetailedEntries(output) {
2068
+ var rows = [];
2069
+ if (output.mode === 'single') {
2070
+ rows.push(output.model);
2071
+ } else if (output.mode === 'list' || output.mode === 'multiple') {
2072
+ var _rows;
2073
+ (_rows = rows).push.apply(_rows, _to_consumable_array$6(output.models));
2069
2074
  }
2070
- var normalizedKeys = keys.map(function(value, index) {
2071
- if (typeof value !== 'string' || value.length === 0) {
2072
- throw new Error("model-get: keys[".concat(index, "] must be a non-empty string."));
2073
- }
2074
- return value;
2075
+ return rows.filter(function(row) {
2076
+ return Array.isArray(row.fields);
2075
2077
  });
2076
- return {
2077
- modelType: modelType,
2078
- keys: normalizedKeys
2079
- };
2080
2078
  }
2081
- function resolveKeys$1(keys, identity) {
2082
- var isRoot = identity.type === 'root';
2083
- var resolved = [];
2079
+ function collectFieldEnumRefs(fields, into) {
2084
2080
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2085
2081
  try {
2086
- for(var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2087
- var value = _step.value;
2088
- if (value.includes('/')) {
2089
- resolved.push(value);
2090
- } else if (isRoot) {
2091
- resolved.push("".concat(identity.collectionName, "/").concat(value));
2092
- } else {
2093
- throw new Error('model-get: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2094
- }
2082
+ for(var _iterator = fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2083
+ var field = _step.value;
2084
+ if (field.enumRef) into.add(field.enumRef);
2085
+ if (field.nestedFields) collectFieldEnumRefs(field.nestedFields, into);
2095
2086
  }
2096
2087
  } catch (err) {
2097
2088
  _didIteratorError = true;
@@ -2107,126 +2098,593 @@ function resolveKeys$1(keys, identity) {
2107
2098
  }
2108
2099
  }
2109
2100
  }
2110
- return resolved;
2111
2101
  }
2112
- function readInChunks$1(input) {
2113
- return _async_to_generator$7(function() {
2114
- var modelType, keys, auth, readDocuments, merged, offset, _merged_results, _merged_errors, batch, partial;
2115
- return _ts_generator$7(this, function(_state) {
2116
- switch(_state.label){
2117
- case 0:
2118
- modelType = input.modelType, keys = input.keys, auth = input.auth, readDocuments = input.readDocuments;
2119
- merged = {
2120
- results: [],
2121
- errors: []
2122
- };
2123
- offset = 0;
2124
- _state.label = 1;
2125
- case 1:
2126
- if (!(offset < keys.length)) return [
2127
- 3,
2128
- 4
2129
- ];
2130
- batch = keys.slice(offset, offset + MCP_MODEL_GET_BATCH_SIZE);
2131
- return [
2132
- 4,
2133
- readDocuments(modelType, batch, auth)
2134
- ];
2135
- case 2:
2136
- partial = _state.sent();
2137
- (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$6(partial.results));
2138
- (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$6(partial.errors));
2139
- _state.label = 3;
2140
- case 3:
2141
- offset += MCP_MODEL_GET_BATCH_SIZE;
2142
- return [
2143
- 3,
2144
- 1
2145
- ];
2146
- case 4:
2147
- return [
2148
- 2,
2149
- merged
2150
- ];
2151
- }
2152
- });
2153
- })();
2102
+ // MARK: Input parsing
2103
+ function parseModelInfoInput(args) {
2104
+ var modelQueries = parseModelArg(args['model']);
2105
+ var modelGroup = parseModelGroupArg(args['modelGroup']);
2106
+ var all = parseBooleanArg(args['all'], 'all');
2107
+ var fields = parseBooleanArg(args['fields'], 'fields');
2108
+ return _object_spread$7(_object_spread_props$3(_object_spread$7(_object_spread_props$3(_object_spread$7({}, modelQueries == null ? {} : {
2109
+ modelQueries: modelQueries
2110
+ }), {
2111
+ modelIsArray: Array.isArray(args['model'])
2112
+ }), modelGroup == null ? {} : {
2113
+ modelGroup: modelGroup
2114
+ }), {
2115
+ all: all !== null && all !== void 0 ? all : false
2116
+ }), fields == null ? {} : {
2117
+ fields: fields
2118
+ });
2154
2119
  }
2155
- // MARK: Schemas
2156
- var MODEL_GET_INPUT_SCHEMA = {
2157
- type: 'object',
2158
- required: [
2159
- 'modelType',
2160
- 'keys'
2161
- ],
2162
- properties: {
2163
- modelType: {
2120
+ function parseModelArg(raw) {
2121
+ var result;
2122
+ if (raw == null) {
2123
+ result = undefined;
2124
+ } else if (typeof raw === 'string') {
2125
+ result = [
2126
+ requireNonEmptyString$1(raw, 'model')
2127
+ ];
2128
+ } else if (Array.isArray(raw)) {
2129
+ if (raw.length === 0) {
2130
+ throw new Error('model-info: "model" array must contain at least one entry.');
2131
+ }
2132
+ result = raw.map(function(value, index) {
2133
+ return requireNonEmptyString$1(value, "model[".concat(index, "]"));
2134
+ });
2135
+ } else {
2136
+ throw new TypeError('model-info: "model" must be a string or an array of strings when provided.');
2137
+ }
2138
+ return result;
2139
+ }
2140
+ function parseModelGroupArg(raw) {
2141
+ var result;
2142
+ if (raw == null) {
2143
+ result = undefined;
2144
+ } else if (typeof raw === 'string') {
2145
+ result = requireNonEmptyString$1(raw, 'modelGroup');
2146
+ } else {
2147
+ throw new TypeError('model-info: "modelGroup" must be a string when provided.');
2148
+ }
2149
+ return result;
2150
+ }
2151
+ function parseBooleanArg(raw, label) {
2152
+ var result;
2153
+ if (raw == null) {
2154
+ result = undefined;
2155
+ } else if (typeof raw === 'boolean') {
2156
+ result = raw;
2157
+ } else {
2158
+ throw new TypeError('model-info: "'.concat(label, '" must be a boolean when provided.'));
2159
+ }
2160
+ return result;
2161
+ }
2162
+ function requireNonEmptyString$1(value, label) {
2163
+ if (typeof value !== 'string') {
2164
+ throw new TypeError('model-info: "'.concat(label, '" must be a string.'));
2165
+ }
2166
+ var trimmed = value.trim();
2167
+ if (trimmed.length === 0) {
2168
+ throw new Error('model-info: "'.concat(label, '" must be a non-empty string.'));
2169
+ }
2170
+ return trimmed;
2171
+ }
2172
+ // MARK: Output builders
2173
+ function buildGroupsOutput(manifest) {
2174
+ return {
2175
+ mode: 'groups',
2176
+ groups: computeGroupCounts(manifest),
2177
+ totalModels: manifest.length,
2178
+ hint: MODEL_INFO_GROUPS_HINT
2179
+ };
2180
+ }
2181
+ function buildGroupFilterOutput(config) {
2182
+ var group = config.group, manifest = config.manifest, fields = config.fields;
2183
+ var detailed = fields !== null && fields !== void 0 ? fields : false;
2184
+ var lower = group.toLowerCase();
2185
+ var matches = manifest.filter(function(entry) {
2186
+ var _entry_modelGroup;
2187
+ return ((_entry_modelGroup = entry.modelGroup) === null || _entry_modelGroup === void 0 ? void 0 : _entry_modelGroup.toLowerCase()) === lower;
2188
+ });
2189
+ var result;
2190
+ if (matches.length === 0) {
2191
+ throw new Error(buildUnknownGroupMessage(group, manifest));
2192
+ } else {
2193
+ var canonical = matches[0].modelGroup;
2194
+ result = {
2195
+ mode: 'list',
2196
+ modelGroup: canonical,
2197
+ models: matches.map(function(entry) {
2198
+ return toModelRow(entry, detailed);
2199
+ })
2200
+ };
2201
+ }
2202
+ return result;
2203
+ }
2204
+ function buildAllOutput(manifest) {
2205
+ var fields = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
2206
+ return {
2207
+ mode: 'list',
2208
+ models: manifest.map(function(entry) {
2209
+ return toModelRow(entry, fields);
2210
+ })
2211
+ };
2212
+ }
2213
+ function buildModelLookupOutput(config) {
2214
+ var queries = config.queries, isArray = config.isArray, manifest = config.manifest, fields = config.fields;
2215
+ var detailed = fields !== null && fields !== void 0 ? fields : true;
2216
+ var matched = [];
2217
+ var notFound = [];
2218
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2219
+ try {
2220
+ for(var _iterator = queries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2221
+ var query = _step.value;
2222
+ var entry = findModelEntry(query, manifest);
2223
+ if (entry == null) {
2224
+ notFound.push({
2225
+ query: query,
2226
+ message: buildModelNotFoundMessage(query, manifest)
2227
+ });
2228
+ } else {
2229
+ matched.push(toModelRow(entry, detailed));
2230
+ }
2231
+ }
2232
+ } catch (err) {
2233
+ _didIteratorError = true;
2234
+ _iteratorError = err;
2235
+ } finally{
2236
+ try {
2237
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2238
+ _iterator.return();
2239
+ }
2240
+ } finally{
2241
+ if (_didIteratorError) {
2242
+ throw _iteratorError;
2243
+ }
2244
+ }
2245
+ }
2246
+ var result;
2247
+ if (isArray) {
2248
+ result = notFound.length > 0 ? {
2249
+ mode: 'multiple',
2250
+ models: matched,
2251
+ notFound: notFound
2252
+ } : {
2253
+ mode: 'multiple',
2254
+ models: matched
2255
+ };
2256
+ } else if (matched.length > 0) {
2257
+ result = {
2258
+ mode: 'single',
2259
+ model: matched[0]
2260
+ };
2261
+ } else {
2262
+ throw new Error(notFound[0].message);
2263
+ }
2264
+ return result;
2265
+ }
2266
+ // MARK: Row + group helpers
2267
+ function toModelRow(entry, detailed) {
2268
+ return detailed ? entry : toSummaryRow(entry);
2269
+ }
2270
+ function toSummaryRow(entry) {
2271
+ return _object_spread$7({
2272
+ modelType: entry.modelType,
2273
+ modelName: entry.modelName,
2274
+ identityConst: entry.identityConst,
2275
+ collectionPrefix: entry.collectionPrefix,
2276
+ sourcePackage: entry.sourcePackage,
2277
+ fieldCount: entry.fields.length
2278
+ }, entry.modelGroup == null ? {} : {
2279
+ modelGroup: entry.modelGroup
2280
+ }, entry.parentIdentityConst == null ? {} : {
2281
+ parentIdentityConst: entry.parentIdentityConst
2282
+ }, entry.description == null ? {} : {
2283
+ description: entry.description
2284
+ }, entry.read == null ? {} : {
2285
+ read: entry.read
2286
+ }, entry.serviceFactory == null ? {} : {
2287
+ serviceFactoryExport: entry.serviceFactory.exportName
2288
+ });
2289
+ }
2290
+ /**
2291
+ * Counts models per `modelGroup`, bucketing entries without a group under
2292
+ * {@link MODEL_INFO_UNGROUPED_LABEL}. Sorted by count descending, then group name ascending.
2293
+ *
2294
+ * @param manifest - Model manifest to summarize.
2295
+ * @returns One {@link ModelInfoGroupCount} per group, ordered most-populous-first.
2296
+ *
2297
+ * @__NO_SIDE_EFFECTS__
2298
+ */ function computeGroupCounts(manifest) {
2299
+ var grouped = makeValuesGroupMap(_to_consumable_array$6(manifest), function(entry) {
2300
+ var _entry_modelGroup;
2301
+ return (_entry_modelGroup = entry.modelGroup) !== null && _entry_modelGroup !== void 0 ? _entry_modelGroup : MODEL_INFO_UNGROUPED_LABEL;
2302
+ });
2303
+ var counts = [];
2304
+ grouped.forEach(function(entries, group) {
2305
+ counts.push({
2306
+ modelGroup: group,
2307
+ modelCount: entries.length
2308
+ });
2309
+ });
2310
+ counts.sort(function(a, b) {
2311
+ return b.modelCount - a.modelCount || a.modelGroup.localeCompare(b.modelGroup);
2312
+ });
2313
+ return counts;
2314
+ }
2315
+ function findGroupCount(query, manifest) {
2316
+ var lower = query.toLowerCase();
2317
+ var result;
2318
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2319
+ try {
2320
+ for(var _iterator = computeGroupCounts(manifest)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2321
+ var count = _step.value;
2322
+ if (count.modelGroup.toLowerCase() === lower) {
2323
+ result = count;
2324
+ break;
2325
+ }
2326
+ }
2327
+ } catch (err) {
2328
+ _didIteratorError = true;
2329
+ _iteratorError = err;
2330
+ } finally{
2331
+ try {
2332
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2333
+ _iterator.return();
2334
+ }
2335
+ } finally{
2336
+ if (_didIteratorError) {
2337
+ throw _iteratorError;
2338
+ }
2339
+ }
2340
+ }
2341
+ return result;
2342
+ }
2343
+ function buildModelNotFoundMessage(query, manifest) {
2344
+ var groupMatch = findGroupCount(query, manifest);
2345
+ var message;
2346
+ if (groupMatch == null) {
2347
+ 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.');
2348
+ } else {
2349
+ var plural = groupMatch.modelCount === 1 ? '' : 's';
2350
+ 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.');
2351
+ }
2352
+ return message;
2353
+ }
2354
+ function buildUnknownGroupMessage(group, manifest) {
2355
+ var available = computeGroupCounts(manifest).map(function(count) {
2356
+ return "".concat(count.modelGroup, " (").concat(count.modelCount, ")");
2357
+ }).join(', ');
2358
+ return 'model-info: no model group matches "'.concat(group, '". Available groups: ').concat(available || '(none)', ". Call `model-info` with no arguments to list groups.");
2359
+ }
2360
+ /**
2361
+ * Resolves a manifest entry by `modelType`, `identityConst`, or `collectionPrefix`.
2362
+ *
2363
+ * @param query - Identifier to look up.
2364
+ * @param manifest - Model manifest to search.
2365
+ * @returns The matching entry or `undefined`.
2366
+ *
2367
+ * @__NO_SIDE_EFFECTS__
2368
+ */ function findModelEntry(query, manifest) {
2369
+ var result;
2370
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2371
+ try {
2372
+ for(var _iterator = manifest[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2373
+ var entry = _step.value;
2374
+ if (entry.modelType === query || entry.identityConst === query || entry.collectionPrefix === query) {
2375
+ result = entry;
2376
+ break;
2377
+ }
2378
+ }
2379
+ } catch (err) {
2380
+ _didIteratorError = true;
2381
+ _iteratorError = err;
2382
+ } finally{
2383
+ try {
2384
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2385
+ _iterator.return();
2386
+ }
2387
+ } finally{
2388
+ if (_didIteratorError) {
2389
+ throw _iteratorError;
2390
+ }
2391
+ }
2392
+ }
2393
+ return result;
2394
+ }
2395
+ // MARK: Schemas
2396
+ var MODEL_INFO_INPUT_SCHEMA = {
2397
+ type: 'object',
2398
+ properties: {
2399
+ model: {
2400
+ 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`.',
2401
+ oneOf: [
2402
+ {
2403
+ type: 'string',
2404
+ minLength: 1
2405
+ },
2406
+ {
2407
+ type: 'array',
2408
+ minItems: 1,
2409
+ items: {
2410
+ type: 'string',
2411
+ minLength: 1
2412
+ }
2413
+ }
2414
+ ]
2415
+ },
2416
+ modelGroup: {
2164
2417
  type: 'string',
2165
2418
  minLength: 1,
2166
- description: 'Firestore model type to fetch (e.g. "guestbook", "profile").'
2419
+ description: 'Return the summary list of models in this group (`mode:"list"`). Matched case-insensitively.'
2420
+ },
2421
+ all: {
2422
+ type: 'boolean',
2423
+ description: 'Opt in to the full catalog summary (every model). Never the default — omit to get groups instead.'
2424
+ },
2425
+ fields: {
2426
+ type: 'boolean',
2427
+ description: 'Force (true) or suppress (false) full persisted-field detail. Defaults to summary rows for list mode and full entries for single/multiple modes.'
2428
+ }
2429
+ },
2430
+ additionalProperties: false
2431
+ };
2432
+ // Shared item schema for model-bearing rows. Required props cover the summary form; full entries
2433
+ // add `fields`, `sourceFile`, and `serviceFactory` (allowed as additional properties).
2434
+ var MODEL_ROW_SCHEMA = {
2435
+ type: 'object',
2436
+ required: [
2437
+ 'modelType',
2438
+ 'modelName',
2439
+ 'identityConst',
2440
+ 'collectionPrefix',
2441
+ 'sourcePackage'
2442
+ ],
2443
+ properties: {
2444
+ modelType: {
2445
+ type: 'string'
2446
+ },
2447
+ modelName: {
2448
+ type: 'string'
2449
+ },
2450
+ modelGroup: {
2451
+ type: 'string'
2452
+ },
2453
+ identityConst: {
2454
+ type: 'string'
2455
+ },
2456
+ collectionPrefix: {
2457
+ type: 'string'
2458
+ },
2459
+ parentIdentityConst: {
2460
+ type: 'string'
2461
+ },
2462
+ sourcePackage: {
2463
+ type: 'string'
2464
+ },
2465
+ sourceFile: {
2466
+ type: 'string'
2467
+ },
2468
+ fieldCount: {
2469
+ type: 'integer'
2470
+ },
2471
+ description: {
2472
+ type: 'string'
2473
+ },
2474
+ read: {
2475
+ type: 'string',
2476
+ enum: [
2477
+ 'system',
2478
+ 'owner',
2479
+ 'admin-only',
2480
+ 'permissions'
2481
+ ]
2482
+ },
2483
+ serviceFactoryExport: {
2484
+ type: 'string'
2485
+ },
2486
+ fields: {
2487
+ type: 'array',
2488
+ items: {
2489
+ type: 'object'
2490
+ }
2491
+ },
2492
+ serviceFactory: {
2493
+ type: 'object',
2494
+ required: [
2495
+ 'exportName',
2496
+ 'sourceFile'
2497
+ ],
2498
+ properties: {
2499
+ exportName: {
2500
+ type: 'string'
2501
+ },
2502
+ sourceFile: {
2503
+ type: 'string'
2504
+ }
2505
+ }
2506
+ }
2507
+ }
2508
+ };
2509
+ /**
2510
+ * JSON-schema for one enum value→label table. Shared with the `enum-info` tool so both advertise the
2511
+ * identical enum shape.
2512
+ */ var ENUM_TABLE_SCHEMA = {
2513
+ type: 'object',
2514
+ required: [
2515
+ 'name',
2516
+ 'values'
2517
+ ],
2518
+ properties: {
2519
+ name: {
2520
+ type: 'string'
2167
2521
  },
2168
- keys: {
2522
+ description: {
2523
+ type: 'string'
2524
+ },
2525
+ values: {
2169
2526
  type: 'array',
2170
- minItems: 1,
2171
- description: 'Full keys ("prefix/id") or bare ids (root models only).',
2172
2527
  items: {
2173
- type: 'string',
2174
- minLength: 1
2528
+ type: 'object',
2529
+ required: [
2530
+ 'name',
2531
+ 'value'
2532
+ ],
2533
+ properties: {
2534
+ name: {
2535
+ type: 'string'
2536
+ },
2537
+ value: {
2538
+ type: [
2539
+ 'string',
2540
+ 'number'
2541
+ ]
2542
+ },
2543
+ description: {
2544
+ type: 'string'
2545
+ }
2546
+ }
2175
2547
  }
2176
2548
  }
2177
- },
2178
- additionalProperties: false
2549
+ }
2179
2550
  };
2180
- var MODEL_GET_OUTPUT_SCHEMA = {
2551
+ // MCP's `tools/list` validator requires `outputSchema.type === 'object'` at the root
2552
+ // (see the MCP SDK's `ToolSchema`), so the variants are expressed via a top-level
2553
+ // object whose nested `oneOf` discriminates by the `mode` literal.
2554
+ var MODEL_INFO_OUTPUT_SCHEMA = {
2181
2555
  type: 'object',
2182
2556
  required: [
2183
- 'results',
2184
- 'errors'
2557
+ 'mode'
2185
2558
  ],
2559
+ 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`).',
2186
2560
  properties: {
2187
- results: {
2561
+ mode: {
2562
+ type: 'string',
2563
+ enum: [
2564
+ 'groups',
2565
+ 'list',
2566
+ 'single',
2567
+ 'multiple'
2568
+ ]
2569
+ },
2570
+ groups: {
2188
2571
  type: 'array',
2189
- description: 'Per-key successful reads.',
2572
+ description: 'Present when `mode` is `"groups"`. One entry per model group.',
2190
2573
  items: {
2191
2574
  type: 'object',
2192
2575
  required: [
2193
- 'key',
2194
- 'data'
2576
+ 'modelGroup',
2577
+ 'modelCount'
2195
2578
  ],
2196
2579
  properties: {
2197
- key: {
2580
+ modelGroup: {
2198
2581
  type: 'string'
2199
2582
  },
2200
- data: {}
2583
+ modelCount: {
2584
+ type: 'integer'
2585
+ }
2201
2586
  }
2202
2587
  }
2203
2588
  },
2204
- errors: {
2589
+ totalModels: {
2590
+ type: 'integer',
2591
+ description: 'Present when `mode` is `"groups"`. Total models across all groups.'
2592
+ },
2593
+ hint: {
2594
+ type: 'string',
2595
+ description: 'Present when `mode` is `"groups"`. Guidance on how to drill into the catalog.'
2596
+ },
2597
+ models: {
2205
2598
  type: 'array',
2206
- description: 'Per-key failures (not-found, forbidden, etc.).',
2599
+ description: 'Present when `mode` is `"list"` or `"multiple"`. Summary rows by default; full entries when `fields` requests detail.',
2600
+ items: MODEL_ROW_SCHEMA
2601
+ },
2602
+ modelGroup: {
2603
+ type: 'string',
2604
+ description: 'Present when `mode` is `"list"` from a `modelGroup` filter. The canonical group name.'
2605
+ },
2606
+ model: _object_spread_props$3(_object_spread$7({}, MODEL_ROW_SCHEMA), {
2607
+ description: 'Present when `mode` is `"single"`. The matched model entry (full detail unless `fields:false`).'
2608
+ }),
2609
+ notFound: {
2610
+ type: 'array',
2611
+ description: 'Present when `mode` is `"multiple"` and one or more queries matched nothing.',
2207
2612
  items: {
2208
2613
  type: 'object',
2209
2614
  required: [
2210
- 'key',
2615
+ 'query',
2211
2616
  'message'
2212
2617
  ],
2213
2618
  properties: {
2214
- key: {
2619
+ query: {
2215
2620
  type: 'string'
2216
2621
  },
2217
2622
  message: {
2218
2623
  type: 'string'
2219
- },
2220
- code: {
2221
- type: 'string'
2222
2624
  }
2223
2625
  }
2224
2626
  }
2627
+ },
2628
+ enums: {
2629
+ type: 'array',
2630
+ description: 'Value→label tables for the enums referenced by the returned models’ fields. Present only when full field detail is returned (single/multiple modes, or list/all with `fields:true`) and at least one referenced enum is registered.',
2631
+ items: ENUM_TABLE_SCHEMA
2225
2632
  }
2226
- }
2633
+ },
2634
+ oneOf: [
2635
+ {
2636
+ required: [
2637
+ 'groups',
2638
+ 'totalModels'
2639
+ ],
2640
+ properties: {
2641
+ mode: {
2642
+ const: 'groups'
2643
+ }
2644
+ }
2645
+ },
2646
+ {
2647
+ required: [
2648
+ 'models'
2649
+ ],
2650
+ properties: {
2651
+ mode: {
2652
+ const: 'list'
2653
+ }
2654
+ }
2655
+ },
2656
+ {
2657
+ required: [
2658
+ 'model'
2659
+ ],
2660
+ properties: {
2661
+ mode: {
2662
+ const: 'single'
2663
+ }
2664
+ }
2665
+ },
2666
+ {
2667
+ required: [
2668
+ 'models'
2669
+ ],
2670
+ properties: {
2671
+ mode: {
2672
+ const: 'multiple'
2673
+ }
2674
+ }
2675
+ }
2676
+ ]
2227
2677
  };
2228
2678
 
2229
- function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
2679
+ function _array_like_to_array$7(arr, len) {
2680
+ if (len == null || len > arr.length) len = arr.length;
2681
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2682
+ return arr2;
2683
+ }
2684
+ function _array_without_holes$5(arr) {
2685
+ if (Array.isArray(arr)) return _array_like_to_array$7(arr);
2686
+ }
2687
+ function asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, key, arg) {
2230
2688
  try {
2231
2689
  var info = gen[key](arg);
2232
2690
  var value = info.value;
@@ -2237,48 +2695,33 @@ function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
2237
2695
  if (info.done) resolve(value);
2238
2696
  else Promise.resolve(value).then(_next, _throw);
2239
2697
  }
2240
- function _async_to_generator$6(fn) {
2698
+ function _async_to_generator$7(fn) {
2241
2699
  return function() {
2242
2700
  var self = this, args = arguments;
2243
2701
  return new Promise(function(resolve, reject) {
2244
2702
  var gen = fn.apply(self, args);
2245
2703
  function _next(value) {
2246
- asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "next", value);
2704
+ asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "next", value);
2247
2705
  }
2248
2706
  function _throw(err) {
2249
- asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "throw", err);
2707
+ asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "throw", err);
2250
2708
  }
2251
2709
  _next(undefined);
2252
2710
  });
2253
2711
  };
2254
2712
  }
2255
- function _define_property$9(obj, key, value) {
2256
- if (key in obj) {
2257
- Object.defineProperty(obj, key, {
2258
- value: value,
2259
- enumerable: true,
2260
- configurable: true,
2261
- writable: true
2262
- });
2263
- } else obj[key] = value;
2264
- return obj;
2265
- }
2266
- function _object_spread$7(target) {
2267
- for(var i = 1; i < arguments.length; i++){
2268
- var source = arguments[i] != null ? arguments[i] : {};
2269
- var ownKeys = Object.keys(source);
2270
- if (typeof Object.getOwnPropertySymbols === "function") {
2271
- ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
2272
- return Object.getOwnPropertyDescriptor(source, sym).enumerable;
2273
- }));
2274
- }
2275
- ownKeys.forEach(function(key) {
2276
- _define_property$9(target, key, source[key]);
2277
- });
2713
+ function _iterable_to_array$5(iter) {
2714
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
2715
+ return Array.from(iter);
2278
2716
  }
2279
- return target;
2280
2717
  }
2281
- function _ts_generator$6(thisArg, body) {
2718
+ function _non_iterable_spread$5() {
2719
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2720
+ }
2721
+ function _to_consumable_array$5(arr) {
2722
+ return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$7(arr) || _non_iterable_spread$5();
2723
+ }
2724
+ function _ts_generator$7(thisArg, body) {
2282
2725
  var f, y, t, _ = {
2283
2726
  label: 0,
2284
2727
  sent: function() {
@@ -2377,60 +2820,68 @@ function _ts_generator$6(thisArg, body) {
2377
2820
  };
2378
2821
  }
2379
2822
  }
2823
+ function _unsupported_iterable_to_array$7(o, minLen) {
2824
+ if (!o) return;
2825
+ if (typeof o === "string") return _array_like_to_array$7(o, minLen);
2826
+ var n = Object.prototype.toString.call(o).slice(8, -1);
2827
+ if (n === "Object" && o.constructor) n = o.constructor.name;
2828
+ if (n === "Map" || n === "Set") return Array.from(n);
2829
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$7(o, minLen);
2830
+ }
2380
2831
  // MARK: Constants
2381
2832
  /**
2382
- * Reserved tool name for the built-in `model-roles` static tool.
2383
- */ var MODEL_ROLES_TOOL_NAME = 'model-roles';
2384
- /**
2385
- * Synthetic call type used in the tool's dispatch identity. Distinct from `model-get`'s `get` so
2386
- * visibility predicates can target the two independently.
2387
- */ var MODEL_ROLES_DISPATCH_CALL = 'roles';
2833
+ * Reserved tool name for the built-in `model-get` static tool.
2834
+ */ var MODEL_GET_TOOL_NAME = 'model-get';
2388
2835
  /**
2389
- * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE}
2390
- * — the tool isn't bound to one model type, so the literal "model" stands in.
2391
- */ var MODEL_ROLES_DISPATCH_MODEL_TYPE = 'model';
2836
+ * Synthetic call type used in the tool's dispatch identity. Aligns with the dbx-cli `get`
2837
+ * command and the `/api/model/:modelType/get` HTTP endpoint that backs it.
2838
+ */ var MODEL_GET_DISPATCH_CALL = 'get';
2392
2839
  /**
2393
- * Maximum number of keys accepted per `model-roles` call. Role resolution runs the model's real
2394
- * permission delegate per key (which may itself read parent documents), so this is deliberately
2395
- * lower than the `model-get` batch size.
2396
- */ var MCP_MODEL_ROLES_MAX_KEYS = 25;
2397
- // MARK: Factory
2840
+ * Synthetic model type used in the tool's dispatch identity. The tool itself isn't bound to a
2841
+ * specific model type (the `modelType` is part of its input), so we use the literal "model" as
2842
+ * the identity. Apps avoiding collision should not register a real model literally named
2843
+ * "model".
2844
+ */ var MODEL_GET_DISPATCH_MODEL_TYPE = 'model';
2398
2845
  /**
2399
- * Builds the built-in `model-roles` MCP tool definition.
2400
- *
2401
- * Answers "what is this user actually allowed to do with this document?" by running the same
2402
- * `roleMapForModel()` delegate the permission-checked read/write paths use, and returning the
2403
- * resolved roles instead of consuming them. Because the model's real delegate runs, derived and
2404
- * cascading roles appear exactly as the API grants them — there is no second implementation of the
2405
- * rules to drift.
2846
+ * Maximum number of keys per backend multi-read request. Matches
2847
+ * `MAX_MODEL_ACCESS_MULTI_READ_KEYS` exported from `@dereekb/firebase-server`; mirrored as a
2848
+ * local constant so the bundler doesn't have to follow the import chain just for one number.
2849
+ */ var MCP_MODEL_GET_BATCH_SIZE = 50;
2850
+ // MARK: Factory
2851
+ /**
2852
+ * Builds the built-in `model-get` MCP tool definition.
2406
2853
  *
2407
- * Two things it disambiguates that a plain read cannot:
2408
- * - **Missing vs. forbidden.** A key that resolves to a missing document returns
2409
- * `exists: false, roles: []` rather than an error, so "the document is not there" reads
2410
- * differently from "it is there and you have no access" (`exists: true, roles: []`).
2411
- * - **Full access.** Admin short-circuits that grant the full-access marker come back as
2412
- * `fullAccess: true` rather than as an opaque enumerated set.
2854
+ * Mirrors dbx-cli's `get` / `get-many` behavior:
2855
+ * - Accepts an array of keys; values containing `/` are treated as full keys and passed verbatim
2856
+ * while bare ids are promoted to `${collectionName}/${id}` for root models.
2857
+ * - Nested (subcollection) models reject bare ids since a parent path is required.
2858
+ * - When a manifest is supplied, a root composite-key model also accepts its declared source
2859
+ * model's key and flattens it into the document id (see {@link CreateModelGetToolDeps.manifest}).
2860
+ * - Auto-chunks at {@link MCP_MODEL_GET_BATCH_SIZE} keys per backend call, mirroring
2861
+ * `getMultipleModelsOverHttpChunked` on the CLI side.
2413
2862
  *
2414
- * By default roles are resolved for the calling user. Passing `uid` resolves them for another user
2415
- * and is gated behind {@link CreateModelRolesToolDeps.canTargetOtherUids}.
2863
+ * The handler returns the same `{ results, errors }` shape produced by
2864
+ * `ModelApiGetService.readDocuments`, exposed via both the text content block and
2865
+ * `structuredContent` so MCP clients can consume either form.
2416
2866
  *
2417
- * @param deps - Role-map reader, identity resolver, and the target-uid authorization predicate.
2867
+ * @param deps - Read-documents and identity-resolver callbacks (typically wired to
2868
+ * `ModelApiGetService`).
2418
2869
  * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP
2419
2870
  * server factory's tool registry.
2420
- */ function createModelRolesTool(deps) {
2871
+ */ function createModelGetTool(deps) {
2421
2872
  var handler = function handler(args, ctx) {
2422
- return modelRolesToolHandler(args, ctx, deps);
2873
+ return modelGetToolHandler(args, ctx, deps);
2423
2874
  };
2424
- var name = MODEL_ROLES_TOOL_NAME;
2425
- var description = 'Resolve which roles the current user (or, with "uid", another user) is granted on one or more model keys — the same permission computation the API runs, returned instead of enforced. Use to answer "why was this forbidden?" and to tell a missing document (exists:false) apart from one you cannot access (exists:true, roles:[]). Admin full-access short-circuits report fullAccess:true. Targeting another uid requires elevated access.';
2875
+ var name = MODEL_GET_TOOL_NAME;
2876
+ var description = "Fetch one or more Firestore model documents by key or bare id. Values containing `/` are treated as full keys; bare ids are auto-promoted to `<collectionName>/<id>` for root models. Subcollection models require full keys.".concat(deps.manifest == null ? '' : ' For a root composite-key model (`@dbxModelCompositeKey`), a source model key is accepted in place of the flattened id and rewritten automatically.');
2426
2877
  return buildStaticToolDefinition({
2427
2878
  name: name,
2428
2879
  description: description,
2429
- inputSchema: MODEL_ROLES_INPUT_SCHEMA,
2430
- outputSchema: MODEL_ROLES_OUTPUT_SCHEMA,
2880
+ inputSchema: MODEL_GET_INPUT_SCHEMA,
2881
+ outputSchema: MODEL_GET_OUTPUT_SCHEMA,
2431
2882
  dispatch: {
2432
- call: MODEL_ROLES_DISPATCH_CALL,
2433
- modelType: MODEL_ROLES_DISPATCH_MODEL_TYPE
2883
+ call: MODEL_GET_DISPATCH_CALL,
2884
+ modelType: MODEL_GET_DISPATCH_MODEL_TYPE
2434
2885
  },
2435
2886
  staticHandler: handler,
2436
2887
  effectiveReadOnly: true,
@@ -2440,120 +2891,56 @@ function _ts_generator$6(thisArg, body) {
2440
2891
  });
2441
2892
  }
2442
2893
  // MARK: Handler
2443
- function modelRolesToolHandler(args, ctx, deps) {
2444
- return _async_to_generator$6(function() {
2445
- var result, input, identity, targetUid, keys, resolved, error;
2446
- return _ts_generator$6(this, function(_state) {
2894
+ function modelGetToolHandler(args, ctx, deps) {
2895
+ return _async_to_generator$7(function() {
2896
+ var result, input, identity, resolvedKeys, merged, error;
2897
+ return _ts_generator$7(this, function(_state) {
2447
2898
  switch(_state.label){
2448
2899
  case 0:
2449
2900
  _state.trys.push([
2450
2901
  0,
2451
- 3,
2902
+ 2,
2452
2903
  ,
2453
- 4
2904
+ 3
2454
2905
  ]);
2455
- input = parseModelRolesInput(args);
2906
+ input = parseModelGetInput(args);
2456
2907
  identity = deps.resolveIdentity(input.modelType, ctx.auth);
2457
2908
  if (identity == null) {
2458
2909
  throw new Error("Unknown modelType: ".concat(input.modelType));
2459
2910
  }
2911
+ resolvedKeys = resolveKeys$1(input.keys, identity, deps.manifest);
2460
2912
  return [
2461
2913
  4,
2462
- resolveTargetUid({
2463
- requestedUid: input.uid,
2914
+ readInChunks$1({
2915
+ modelType: input.modelType,
2916
+ keys: resolvedKeys,
2464
2917
  auth: ctx.auth,
2465
- canTargetOtherUids: deps.canTargetOtherUids
2918
+ readDocuments: deps.readDocuments
2466
2919
  })
2467
2920
  ];
2468
2921
  case 1:
2469
- targetUid = _state.sent();
2470
- keys = resolveKeys(input.keys, identity);
2471
- return [
2472
- 4,
2473
- deps.readRoleMaps(_object_spread$7({
2474
- modelType: input.modelType,
2475
- keys: keys,
2476
- auth: ctx.auth
2477
- }, targetUid == null ? {} : {
2478
- targetUid: targetUid
2479
- }))
2480
- ];
2481
- case 2:
2482
- resolved = _state.sent();
2922
+ merged = _state.sent();
2483
2923
  result = {
2484
2924
  content: [
2485
2925
  {
2486
2926
  type: 'text',
2487
- text: JSON.stringify(resolved)
2927
+ text: JSON.stringify(merged)
2488
2928
  }
2489
2929
  ],
2490
- structuredContent: resolved
2930
+ structuredContent: merged
2491
2931
  };
2492
2932
  return [
2493
2933
  3,
2494
- 4
2934
+ 3
2495
2935
  ];
2496
- case 3:
2936
+ case 2:
2497
2937
  error = _state.sent();
2498
2938
  result = formatMcpToolErrorResponse(error);
2499
- return [
2500
- 3,
2501
- 4
2502
- ];
2503
- case 4:
2504
- return [
2505
- 2,
2506
- result
2507
- ];
2508
- }
2509
- });
2510
- })();
2511
- }
2512
- /**
2513
- * Resolves the effective target uid for a call, enforcing the elevated-access gate.
2514
- *
2515
- * Requesting your own uid is always allowed and is treated as no target at all, so a caller that
2516
- * passes their own uid never trips the predicate.
2517
- *
2518
- * @param input - The requested uid, the caller's auth, and the authorization predicate.
2519
- * @returns The uid to resolve as, or `undefined` to resolve as the caller.
2520
- * @throws {Error} When another user's uid is requested and the predicate denies (or is absent).
2521
- */ function resolveTargetUid(input) {
2522
- return _async_to_generator$6(function() {
2523
- var requestedUid, auth, canTargetOtherUids, result, allowed, _tmp;
2524
- return _ts_generator$6(this, function(_state) {
2525
- switch(_state.label){
2526
- case 0:
2527
- requestedUid = input.requestedUid, auth = input.auth, canTargetOtherUids = input.canTargetOtherUids;
2528
- if (!(requestedUid != null && requestedUid !== (auth === null || auth === void 0 ? void 0 : auth.uid))) return [
2529
- 3,
2530
- 4
2531
- ];
2532
- if (!(canTargetOtherUids == null)) return [
2533
- 3,
2534
- 1
2535
- ];
2536
- _tmp = false;
2537
2939
  return [
2538
2940
  3,
2539
2941
  3
2540
2942
  ];
2541
- case 1:
2542
- return [
2543
- 4,
2544
- canTargetOtherUids(auth)
2545
- ];
2546
- case 2:
2547
- _tmp = _state.sent();
2548
- _state.label = 3;
2549
2943
  case 3:
2550
- allowed = _tmp;
2551
- if (!allowed) {
2552
- throw new Error("model-roles: resolving roles for another user's uid requires elevated access.");
2553
- }
2554
- result = requestedUid;
2555
- _state.label = 4;
2556
- case 4:
2557
2944
  return [
2558
2945
  2,
2559
2946
  result
@@ -2562,36 +2949,27 @@ function modelRolesToolHandler(args, ctx, deps) {
2562
2949
  });
2563
2950
  })();
2564
2951
  }
2565
- function parseModelRolesInput(args) {
2952
+ function parseModelGetInput(args) {
2566
2953
  var modelType = args['modelType'];
2567
2954
  var keys = args['keys'];
2568
- var uid = args['uid'];
2569
2955
  if (typeof modelType !== 'string' || modelType.length === 0) {
2570
- throw new Error('model-roles: "modelType" is required and must be a non-empty string.');
2956
+ throw new Error('model-get: "modelType" is required and must be a non-empty string.');
2571
2957
  }
2572
2958
  if (!Array.isArray(keys) || keys.length === 0) {
2573
- throw new Error('model-roles: "keys" is required and must be a non-empty array.');
2574
- }
2575
- if (keys.length > MCP_MODEL_ROLES_MAX_KEYS) {
2576
- throw new Error("model-roles: at most ".concat(MCP_MODEL_ROLES_MAX_KEYS, " keys may be resolved per call (received ").concat(keys.length, ")."));
2577
- }
2578
- if (uid !== undefined && (typeof uid !== 'string' || uid.length === 0)) {
2579
- throw new Error('model-roles: "uid" must be a non-empty string when provided.');
2959
+ throw new Error('model-get: "keys" is required and must be a non-empty array.');
2580
2960
  }
2581
2961
  var normalizedKeys = keys.map(function(value, index) {
2582
2962
  if (typeof value !== 'string' || value.length === 0) {
2583
- throw new Error("model-roles: keys[".concat(index, "] must be a non-empty string."));
2963
+ throw new Error("model-get: keys[".concat(index, "] must be a non-empty string."));
2584
2964
  }
2585
2965
  return value;
2586
2966
  });
2587
- return _object_spread$7({
2967
+ return {
2588
2968
  modelType: modelType,
2589
2969
  keys: normalizedKeys
2590
- }, uid === undefined ? {} : {
2591
- uid: uid
2592
- });
2970
+ };
2593
2971
  }
2594
- function resolveKeys(keys, identity) {
2972
+ function resolveKeys$1(keys, identity, manifest) {
2595
2973
  var isRoot = identity.type === 'root';
2596
2974
  var resolved = [];
2597
2975
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
@@ -2599,11 +2977,12 @@ function resolveKeys(keys, identity) {
2599
2977
  for(var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2600
2978
  var value = _step.value;
2601
2979
  if (value.includes('/')) {
2602
- resolved.push(value);
2980
+ var _ref;
2981
+ resolved.push((_ref = isRoot ? flattenCompositeSourceKey(value, identity, manifest) : undefined) !== null && _ref !== void 0 ? _ref : value);
2603
2982
  } else if (isRoot) {
2604
2983
  resolved.push("".concat(identity.collectionName, "/").concat(value));
2605
2984
  } else {
2606
- throw new Error('model-roles: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2985
+ throw new Error('model-get: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2607
2986
  }
2608
2987
  }
2609
2988
  } catch (err) {
@@ -2622,8 +3001,80 @@ function resolveKeys(keys, identity) {
2622
3001
  }
2623
3002
  return resolved;
2624
3003
  }
3004
+ /**
3005
+ * Rewrites `key` into the requested composite-key model's document key when `key`'s leaf model is one
3006
+ * of that model's declared sources.
3007
+ *
3008
+ * @param key - A full `prefix/id` key supplied by the caller.
3009
+ * @param identity - The identity of the requested (root) model.
3010
+ * @param manifest - The model catalog, when wired.
3011
+ * @returns The rewritten `<collectionName>/<flattened key>`, or `undefined` — leave the key alone — when
3012
+ * the manifest is absent, the key already belongs to the requested collection, the requested model
3013
+ * declares no composite key, or the key's leaf model is not one of its sources.
3014
+ */ function flattenCompositeSourceKey(key, identity, manifest) {
3015
+ var result;
3016
+ var target = manifest == null ? undefined : manifest.find(function(entry) {
3017
+ return entry.modelType === identity.modelType;
3018
+ });
3019
+ if (manifest != null && (target === null || target === void 0 ? void 0 : target.compositeKey) != null && !key.startsWith("".concat(identity.collectionName, "/"))) {
3020
+ var segments = key.split('/').filter(function(s) {
3021
+ return s.length > 0;
3022
+ });
3023
+ var leafPrefix = segments.length >= 2 && segments.length % 2 === 0 ? segments[segments.length - 2] : undefined;
3024
+ var sourceEntry = leafPrefix == null ? undefined : findModelEntry(leafPrefix, manifest);
3025
+ var isSource = sourceEntry != null && isCompositeKeyModelDerivedFrom(sourceEntry, target);
3026
+ if (isSource) {
3027
+ var flatId = target.compositeKey.encoding === 'two-way' ? twoWayFlatFirestoreModelKey(key) : flatFirestoreModelKey(key);
3028
+ result = "".concat(identity.collectionName, "/").concat(flatId);
3029
+ }
3030
+ }
3031
+ return result;
3032
+ }
3033
+ function readInChunks$1(input) {
3034
+ return _async_to_generator$7(function() {
3035
+ var modelType, keys, auth, readDocuments, merged, offset, _merged_results, _merged_errors, batch, partial;
3036
+ return _ts_generator$7(this, function(_state) {
3037
+ switch(_state.label){
3038
+ case 0:
3039
+ modelType = input.modelType, keys = input.keys, auth = input.auth, readDocuments = input.readDocuments;
3040
+ merged = {
3041
+ results: [],
3042
+ errors: []
3043
+ };
3044
+ offset = 0;
3045
+ _state.label = 1;
3046
+ case 1:
3047
+ if (!(offset < keys.length)) return [
3048
+ 3,
3049
+ 4
3050
+ ];
3051
+ batch = keys.slice(offset, offset + MCP_MODEL_GET_BATCH_SIZE);
3052
+ return [
3053
+ 4,
3054
+ readDocuments(modelType, batch, auth)
3055
+ ];
3056
+ case 2:
3057
+ partial = _state.sent();
3058
+ (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$5(partial.results));
3059
+ (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$5(partial.errors));
3060
+ _state.label = 3;
3061
+ case 3:
3062
+ offset += MCP_MODEL_GET_BATCH_SIZE;
3063
+ return [
3064
+ 3,
3065
+ 1
3066
+ ];
3067
+ case 4:
3068
+ return [
3069
+ 2,
3070
+ merged
3071
+ ];
3072
+ }
3073
+ });
3074
+ })();
3075
+ }
2625
3076
  // MARK: Schemas
2626
- var MODEL_ROLES_INPUT_SCHEMA = {
3077
+ var MODEL_GET_INPUT_SCHEMA = {
2627
3078
  type: 'object',
2628
3079
  required: [
2629
3080
  'modelType',
@@ -2633,78 +3084,47 @@ var MODEL_ROLES_INPUT_SCHEMA = {
2633
3084
  modelType: {
2634
3085
  type: 'string',
2635
3086
  minLength: 1,
2636
- description: 'Firestore model type to resolve roles on (e.g. "guestbook", "profile").'
3087
+ description: 'Firestore model type to fetch (e.g. "guestbook", "profile").'
2637
3088
  },
2638
3089
  keys: {
2639
3090
  type: 'array',
2640
3091
  minItems: 1,
2641
- maxItems: MCP_MODEL_ROLES_MAX_KEYS,
2642
- description: 'Full keys ("prefix/id") or bare ids (root models only).',
3092
+ description: 'Full keys ("prefix/id") or bare ids (root models only). For a root composite-key model, the source model key (e.g. a District key for jobDistrict) is also accepted and flattened into the document id.',
2643
3093
  items: {
2644
3094
  type: 'string',
2645
3095
  minLength: 1
2646
3096
  }
2647
- },
2648
- uid: {
2649
- type: 'string',
2650
- minLength: 1,
2651
- description: 'Resolve roles as this user instead of the caller. Requires elevated access; passing your own uid is always allowed.'
2652
3097
  }
2653
3098
  },
2654
3099
  additionalProperties: false
2655
3100
  };
2656
- var MODEL_ROLES_OUTPUT_SCHEMA = {
3101
+ var MODEL_GET_OUTPUT_SCHEMA = {
2657
3102
  type: 'object',
2658
3103
  required: [
2659
- 'targeted',
2660
3104
  'results',
2661
3105
  'errors'
2662
3106
  ],
2663
3107
  properties: {
2664
- uid: {
2665
- type: 'string',
2666
- description: 'The uid the roles were resolved for.'
2667
- },
2668
- targeted: {
2669
- type: 'boolean',
2670
- description: 'True when the roles were resolved for a user other than the caller.'
2671
- },
2672
3108
  results: {
2673
3109
  type: 'array',
2674
- description: 'Per-key resolved permission state.',
3110
+ description: 'Per-key successful reads.',
2675
3111
  items: {
2676
3112
  type: 'object',
2677
3113
  required: [
2678
3114
  'key',
2679
- 'exists',
2680
- 'fullAccess',
2681
- 'roles'
3115
+ 'data'
2682
3116
  ],
2683
3117
  properties: {
2684
3118
  key: {
2685
3119
  type: 'string'
2686
3120
  },
2687
- exists: {
2688
- type: 'boolean',
2689
- description: 'Whether the document exists. Roles are only computed for documents that exist.'
2690
- },
2691
- fullAccess: {
2692
- type: 'boolean',
2693
- description: 'True when the full-access marker was granted; every role is implicitly held and "roles" is empty.'
2694
- },
2695
- roles: {
2696
- type: 'array',
2697
- items: {
2698
- type: 'string'
2699
- },
2700
- description: 'Granted role names, sorted.'
2701
- }
3121
+ data: {}
2702
3122
  }
2703
3123
  }
2704
3124
  },
2705
3125
  errors: {
2706
3126
  type: 'array',
2707
- description: 'Per-key failures. A missing document is NOT an error — it appears in results with exists:false.',
3127
+ description: 'Per-key failures (not-found, forbidden, etc.).',
2708
3128
  items: {
2709
3129
  type: 'object',
2710
3130
  required: [
@@ -2727,565 +3147,384 @@ var MODEL_ROLES_OUTPUT_SCHEMA = {
2727
3147
  }
2728
3148
  };
2729
3149
 
2730
- function _array_like_to_array$7(arr, len) {
2731
- if (len == null || len > arr.length) len = arr.length;
2732
- for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2733
- return arr2;
2734
- }
2735
- function _array_without_holes$5(arr) {
2736
- if (Array.isArray(arr)) return _array_like_to_array$7(arr);
2737
- }
2738
- function _define_property$8(obj, key, value) {
2739
- if (key in obj) {
2740
- Object.defineProperty(obj, key, {
2741
- value: value,
2742
- enumerable: true,
2743
- configurable: true,
2744
- writable: true
2745
- });
2746
- } else obj[key] = value;
2747
- return obj;
2748
- }
2749
- function _iterable_to_array$5(iter) {
2750
- if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
2751
- return Array.from(iter);
2752
- }
2753
- }
2754
- function _non_iterable_spread$5() {
2755
- throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2756
- }
2757
- function _object_spread$6(target) {
2758
- for(var i = 1; i < arguments.length; i++){
2759
- var source = arguments[i] != null ? arguments[i] : {};
2760
- var ownKeys = Object.keys(source);
2761
- if (typeof Object.getOwnPropertySymbols === "function") {
2762
- ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
2763
- return Object.getOwnPropertyDescriptor(source, sym).enumerable;
2764
- }));
2765
- }
2766
- ownKeys.forEach(function(key) {
2767
- _define_property$8(target, key, source[key]);
2768
- });
2769
- }
2770
- return target;
2771
- }
2772
- function ownKeys$2(object, enumerableOnly) {
2773
- var keys = Object.keys(object);
2774
- if (Object.getOwnPropertySymbols) {
2775
- var symbols = Object.getOwnPropertySymbols(object);
2776
- keys.push.apply(keys, symbols);
2777
- }
2778
- return keys;
2779
- }
2780
- function _object_spread_props$2(target, source) {
2781
- source = source != null ? source : {};
2782
- if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
2783
- else {
2784
- ownKeys$2(Object(source)).forEach(function(key) {
2785
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
2786
- });
2787
- }
2788
- return target;
2789
- }
2790
- function _to_consumable_array$5(arr) {
2791
- return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$7(arr) || _non_iterable_spread$5();
2792
- }
2793
- function _unsupported_iterable_to_array$7(o, minLen) {
2794
- if (!o) return;
2795
- if (typeof o === "string") return _array_like_to_array$7(o, minLen);
2796
- var n = Object.prototype.toString.call(o).slice(8, -1);
2797
- if (n === "Object" && o.constructor) n = o.constructor.name;
2798
- if (n === "Map" || n === "Set") return Array.from(n);
2799
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$7(o, minLen);
2800
- }
2801
- // MARK: Constants
2802
- /**
2803
- * Reserved tool name for the built-in `model-info` static tool.
2804
- */ var MODEL_INFO_TOOL_NAME = 'model-info';
2805
- /**
2806
- * Synthetic call type used in the tool's dispatch identity. Aligns with the dbx-cli `model-info`
2807
- * command.
2808
- */ var MODEL_INFO_DISPATCH_CALL = 'info';
2809
- /**
2810
- * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE};
2811
- * apps avoiding collisions should not register a real model literally named "model".
2812
- */ var MODEL_INFO_DISPATCH_MODEL_TYPE = 'model';
2813
- /**
2814
- * Group label applied to manifest entries that declare no `modelGroup`. Keeps the `groups` mode
2815
- * total reconcilable with the catalog size (sum of group counts always equals `totalModels`).
2816
- */ var MODEL_INFO_UNGROUPED_LABEL = '(ungrouped)';
2817
- /**
2818
- * Hint string surfaced alongside the default `groups` response so callers know how to drill in.
2819
- */ var MODEL_INFO_GROUPS_HINT = 'Pass `model` for one/many models, `modelGroup` for a group, or `all:true` for the full catalog.';
2820
- // MARK: Factory
2821
- /**
2822
- * Builds the built-in `model-info` MCP tool definition.
2823
- *
2824
- * Modes (selected by input):
2825
- * - No args: returns model groups + counts (`mode: 'groups'`) — the cheap default for browsing.
2826
- * - `model` (string): returns the full entry for one model (`mode: 'single'`).
2827
- * - `model` (string array): returns full detail per match (`mode: 'multiple'`); misses land in `notFound`.
2828
- * - `modelGroup`: returns the summary list of that group's models (`mode: 'list'`).
2829
- * - `all: true`: returns the full catalog summary (`mode: 'list'`).
2830
- * - `fields`: forces (`true`) or suppresses (`false`) full persisted-field detail in list/multiple modes.
2831
- *
2832
- * Each `model` query matches by `modelType`, `identityConst`, or `collectionPrefix`. When several
2833
- * params are supplied, precedence is `model` > `modelGroup` > `all` > the default `groups` view.
2834
- *
2835
- * Output is delivered as both stringified JSON in `content[0].text` and `structuredContent`
2836
- * so MCP clients can consume either form.
2837
- *
2838
- * @param deps - The frozen model manifest loaded at boot from the MCP manifest JSON.
2839
- * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP
2840
- * server factory's tool registry.
2841
- */ function createModelInfoTool(deps) {
2842
- var handler = function handler(args, ctx) {
2843
- return Promise.resolve(modelInfoToolHandler(args, ctx, deps));
2844
- };
2845
- var name = MODEL_INFO_TOOL_NAME;
2846
- var count = deps.manifest.length;
2847
- 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.';
2848
- return buildStaticToolDefinition({
2849
- name: name,
2850
- description: description,
2851
- inputSchema: MODEL_INFO_INPUT_SCHEMA,
2852
- outputSchema: MODEL_INFO_OUTPUT_SCHEMA,
2853
- dispatch: {
2854
- call: MODEL_INFO_DISPATCH_CALL,
2855
- modelType: MODEL_INFO_DISPATCH_MODEL_TYPE
2856
- },
2857
- staticHandler: handler,
2858
- effectiveReadOnly: true,
2859
- rule: {
2860
- requireAuthenticated: true
2861
- }
2862
- });
2863
- }
2864
- // MARK: Handler
2865
- function modelInfoToolHandler(args, _ctx, deps) {
2866
- var result;
3150
+ function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
2867
3151
  try {
2868
- var input = parseModelInfoInput(args);
2869
- var resolved = resolveModelInfoOutput(input, deps.manifest);
2870
- var output = attachReferencedEnums(resolved, deps.enums);
2871
- result = {
2872
- content: [
2873
- {
2874
- type: 'text',
2875
- text: JSON.stringify(output)
2876
- }
2877
- ],
2878
- structuredContent: output
2879
- };
2880
- } catch (error) {
2881
- result = formatMcpToolErrorResponse(error);
2882
- }
2883
- return result;
2884
- }
2885
- function resolveModelInfoOutput(input, manifest) {
2886
- var output;
2887
- if (input.modelQueries != null) {
2888
- output = buildModelLookupOutput({
2889
- queries: input.modelQueries,
2890
- isArray: input.modelIsArray,
2891
- manifest: manifest,
2892
- fields: input.fields
2893
- });
2894
- } else if (input.modelGroup != null) {
2895
- output = buildGroupFilterOutput({
2896
- group: input.modelGroup,
2897
- manifest: manifest,
2898
- fields: input.fields
2899
- });
2900
- } else if (input.all) {
2901
- output = buildAllOutput(manifest, input.fields);
2902
- } else {
2903
- output = buildGroupsOutput(manifest);
2904
- }
2905
- return output;
2906
- }
2907
- // MARK: Enum attachment
2908
- /**
2909
- * Attaches the value→label tables for every enum referenced (by `enumRef`) on the returned models'
2910
- * fields — recursively through `nestedFields` — as a top-level `enums` section.
2911
- *
2912
- * Gated behind full field detail: only `single` / `multiple` rows and `list` rows that opted into
2913
- * `fields:true` carry the persisted `fields` array, so the compact default modes are untouched.
2914
- * Returns the input unchanged when no enum map is wired or no referenced enum resolves.
2915
- *
2916
- * @param output - The resolved model-info output.
2917
- * @param enums - The optional enum value tables keyed by name.
2918
- * @returns The output, with an `enums` section attached when detailed rows reference known enums.
2919
- */ function attachReferencedEnums(output, enums) {
2920
- var result = output;
2921
- if (enums != null) {
2922
- var referenced = new Set();
2923
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2924
- try {
2925
- for(var _iterator = collectDetailedEntries(output)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2926
- var entry = _step.value;
2927
- collectFieldEnumRefs(entry.fields, referenced);
2928
- }
2929
- } catch (err) {
2930
- _didIteratorError = true;
2931
- _iteratorError = err;
2932
- } finally{
2933
- try {
2934
- if (!_iteratorNormalCompletion && _iterator.return != null) {
2935
- _iterator.return();
2936
- }
2937
- } finally{
2938
- if (_didIteratorError) {
2939
- throw _iteratorError;
2940
- }
2941
- }
2942
- }
2943
- var tables = [];
2944
- var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2945
- try {
2946
- for(var _iterator1 = referenced[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2947
- var name = _step1.value;
2948
- var table = enums[name];
2949
- if (table != null) tables.push(table);
2950
- }
2951
- } catch (err) {
2952
- _didIteratorError1 = true;
2953
- _iteratorError1 = err;
2954
- } finally{
2955
- try {
2956
- if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2957
- _iterator1.return();
2958
- }
2959
- } finally{
2960
- if (_didIteratorError1) {
2961
- throw _iteratorError1;
2962
- }
2963
- }
2964
- }
2965
- if (tables.length > 0) {
2966
- result = _object_spread_props$2(_object_spread$6({}, output), {
2967
- enums: tables
2968
- });
2969
- }
2970
- }
2971
- return result;
2972
- }
2973
- /**
2974
- * Collects the full {@link McpManifestModelEntry} rows (those carrying a persisted `fields` array)
2975
- * from a model-info output. Summary rows (which expose `fieldCount`, not `fields`) are skipped.
2976
- *
2977
- * @param output - The resolved model-info output.
2978
- * @returns The detailed model entries present in the output.
2979
- */ function collectDetailedEntries(output) {
2980
- var rows = [];
2981
- if (output.mode === 'single') {
2982
- rows.push(output.model);
2983
- } else if (output.mode === 'list' || output.mode === 'multiple') {
2984
- var _rows;
2985
- (_rows = rows).push.apply(_rows, _to_consumable_array$5(output.models));
3152
+ var info = gen[key](arg);
3153
+ var value = info.value;
3154
+ } catch (error) {
3155
+ reject(error);
3156
+ return;
2986
3157
  }
2987
- return rows.filter(function(row) {
2988
- return Array.isArray(row.fields);
2989
- });
3158
+ if (info.done) resolve(value);
3159
+ else Promise.resolve(value).then(_next, _throw);
2990
3160
  }
2991
- function collectFieldEnumRefs(fields, into) {
2992
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2993
- try {
2994
- for(var _iterator = fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2995
- var field = _step.value;
2996
- if (field.enumRef) into.add(field.enumRef);
2997
- if (field.nestedFields) collectFieldEnumRefs(field.nestedFields, into);
2998
- }
2999
- } catch (err) {
3000
- _didIteratorError = true;
3001
- _iteratorError = err;
3002
- } finally{
3003
- try {
3004
- if (!_iteratorNormalCompletion && _iterator.return != null) {
3005
- _iterator.return();
3161
+ function _async_to_generator$6(fn) {
3162
+ return function() {
3163
+ var self = this, args = arguments;
3164
+ return new Promise(function(resolve, reject) {
3165
+ var gen = fn.apply(self, args);
3166
+ function _next(value) {
3167
+ asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "next", value);
3006
3168
  }
3007
- } finally{
3008
- if (_didIteratorError) {
3009
- throw _iteratorError;
3169
+ function _throw(err) {
3170
+ asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "throw", err);
3010
3171
  }
3011
- }
3012
- }
3172
+ _next(undefined);
3173
+ });
3174
+ };
3013
3175
  }
3014
- // MARK: Input parsing
3015
- function parseModelInfoInput(args) {
3016
- var modelQueries = parseModelArg(args['model']);
3017
- var modelGroup = parseModelGroupArg(args['modelGroup']);
3018
- var all = parseBooleanArg(args['all'], 'all');
3019
- var fields = parseBooleanArg(args['fields'], 'fields');
3020
- return _object_spread$6(_object_spread_props$2(_object_spread$6(_object_spread_props$2(_object_spread$6({}, modelQueries == null ? {} : {
3021
- modelQueries: modelQueries
3022
- }), {
3023
- modelIsArray: Array.isArray(args['model'])
3024
- }), modelGroup == null ? {} : {
3025
- modelGroup: modelGroup
3026
- }), {
3027
- all: all !== null && all !== void 0 ? all : false
3028
- }), fields == null ? {} : {
3029
- fields: fields
3030
- });
3176
+ function _define_property$8(obj, key, value) {
3177
+ if (key in obj) {
3178
+ Object.defineProperty(obj, key, {
3179
+ value: value,
3180
+ enumerable: true,
3181
+ configurable: true,
3182
+ writable: true
3183
+ });
3184
+ } else obj[key] = value;
3185
+ return obj;
3031
3186
  }
3032
- function parseModelArg(raw) {
3033
- var result;
3034
- if (raw == null) {
3035
- result = undefined;
3036
- } else if (typeof raw === 'string') {
3037
- result = [
3038
- requireNonEmptyString$1(raw, 'model')
3039
- ];
3040
- } else if (Array.isArray(raw)) {
3041
- if (raw.length === 0) {
3042
- throw new Error('model-info: "model" array must contain at least one entry.');
3187
+ function _object_spread$6(target) {
3188
+ for(var i = 1; i < arguments.length; i++){
3189
+ var source = arguments[i] != null ? arguments[i] : {};
3190
+ var ownKeys = Object.keys(source);
3191
+ if (typeof Object.getOwnPropertySymbols === "function") {
3192
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
3193
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
3194
+ }));
3043
3195
  }
3044
- result = raw.map(function(value, index) {
3045
- return requireNonEmptyString$1(value, "model[".concat(index, "]"));
3196
+ ownKeys.forEach(function(key) {
3197
+ _define_property$8(target, key, source[key]);
3046
3198
  });
3047
- } else {
3048
- throw new TypeError('model-info: "model" must be a string or an array of strings when provided.');
3049
- }
3050
- return result;
3051
- }
3052
- function parseModelGroupArg(raw) {
3053
- var result;
3054
- if (raw == null) {
3055
- result = undefined;
3056
- } else if (typeof raw === 'string') {
3057
- result = requireNonEmptyString$1(raw, 'modelGroup');
3058
- } else {
3059
- throw new TypeError('model-info: "modelGroup" must be a string when provided.');
3060
- }
3061
- return result;
3062
- }
3063
- function parseBooleanArg(raw, label) {
3064
- var result;
3065
- if (raw == null) {
3066
- result = undefined;
3067
- } else if (typeof raw === 'boolean') {
3068
- result = raw;
3069
- } else {
3070
- throw new TypeError('model-info: "'.concat(label, '" must be a boolean when provided.'));
3071
- }
3072
- return result;
3073
- }
3074
- function requireNonEmptyString$1(value, label) {
3075
- if (typeof value !== 'string') {
3076
- throw new TypeError('model-info: "'.concat(label, '" must be a string.'));
3077
3199
  }
3078
- var trimmed = value.trim();
3079
- if (trimmed.length === 0) {
3080
- throw new Error('model-info: "'.concat(label, '" must be a non-empty string.'));
3081
- }
3082
- return trimmed;
3083
- }
3084
- // MARK: Output builders
3085
- function buildGroupsOutput(manifest) {
3086
- return {
3087
- mode: 'groups',
3088
- groups: computeGroupCounts(manifest),
3089
- totalModels: manifest.length,
3090
- hint: MODEL_INFO_GROUPS_HINT
3091
- };
3200
+ return target;
3092
3201
  }
3093
- function buildGroupFilterOutput(config) {
3094
- var group = config.group, manifest = config.manifest, fields = config.fields;
3095
- var detailed = fields !== null && fields !== void 0 ? fields : false;
3096
- var lower = group.toLowerCase();
3097
- var matches = manifest.filter(function(entry) {
3098
- var _entry_modelGroup;
3099
- return ((_entry_modelGroup = entry.modelGroup) === null || _entry_modelGroup === void 0 ? void 0 : _entry_modelGroup.toLowerCase()) === lower;
3100
- });
3101
- var result;
3102
- if (matches.length === 0) {
3103
- throw new Error(buildUnknownGroupMessage(group, manifest));
3104
- } else {
3105
- var canonical = matches[0].modelGroup;
3106
- result = {
3107
- mode: 'list',
3108
- modelGroup: canonical,
3109
- models: matches.map(function(entry) {
3110
- return toModelRow(entry, detailed);
3111
- })
3202
+ function _ts_generator$6(thisArg, body) {
3203
+ var f, y, t, _ = {
3204
+ label: 0,
3205
+ sent: function() {
3206
+ if (t[0] & 1) throw t[1];
3207
+ return t[1];
3208
+ },
3209
+ trys: [],
3210
+ ops: []
3211
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
3212
+ return d(g, "next", {
3213
+ value: verb(0)
3214
+ }), d(g, "throw", {
3215
+ value: verb(1)
3216
+ }), d(g, "return", {
3217
+ value: verb(2)
3218
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
3219
+ value: function() {
3220
+ return this;
3221
+ }
3222
+ }), g;
3223
+ function verb(n) {
3224
+ return function(v) {
3225
+ return step([
3226
+ n,
3227
+ v
3228
+ ]);
3112
3229
  };
3113
3230
  }
3114
- return result;
3115
- }
3116
- function buildAllOutput(manifest) {
3117
- var fields = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
3118
- return {
3119
- mode: 'list',
3120
- models: manifest.map(function(entry) {
3121
- return toModelRow(entry, fields);
3122
- })
3123
- };
3124
- }
3125
- function buildModelLookupOutput(config) {
3126
- var queries = config.queries, isArray = config.isArray, manifest = config.manifest, fields = config.fields;
3127
- var detailed = fields !== null && fields !== void 0 ? fields : true;
3128
- var matched = [];
3129
- var notFound = [];
3130
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3131
- try {
3132
- for(var _iterator = queries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3133
- var query = _step.value;
3134
- var entry = findModelEntry(query, manifest);
3135
- if (entry == null) {
3136
- notFound.push({
3137
- query: query,
3138
- message: buildModelNotFoundMessage(query, manifest)
3139
- });
3140
- } else {
3141
- matched.push(toModelRow(entry, detailed));
3142
- }
3143
- }
3144
- } catch (err) {
3145
- _didIteratorError = true;
3146
- _iteratorError = err;
3147
- } finally{
3148
- try {
3149
- if (!_iteratorNormalCompletion && _iterator.return != null) {
3150
- _iterator.return();
3231
+ function step(op) {
3232
+ if (f) throw new TypeError("Generator is already executing.");
3233
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
3234
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
3235
+ if (y = 0, t) op = [
3236
+ op[0] & 2,
3237
+ t.value
3238
+ ];
3239
+ switch(op[0]){
3240
+ case 0:
3241
+ case 1:
3242
+ t = op;
3243
+ break;
3244
+ case 4:
3245
+ _.label++;
3246
+ return {
3247
+ value: op[1],
3248
+ done: false
3249
+ };
3250
+ case 5:
3251
+ _.label++;
3252
+ y = op[1];
3253
+ op = [
3254
+ 0
3255
+ ];
3256
+ continue;
3257
+ case 7:
3258
+ op = _.ops.pop();
3259
+ _.trys.pop();
3260
+ continue;
3261
+ default:
3262
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
3263
+ _ = 0;
3264
+ continue;
3265
+ }
3266
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
3267
+ _.label = op[1];
3268
+ break;
3269
+ }
3270
+ if (op[0] === 6 && _.label < t[1]) {
3271
+ _.label = t[1];
3272
+ t = op;
3273
+ break;
3274
+ }
3275
+ if (t && _.label < t[2]) {
3276
+ _.label = t[2];
3277
+ _.ops.push(op);
3278
+ break;
3279
+ }
3280
+ if (t[2]) _.ops.pop();
3281
+ _.trys.pop();
3282
+ continue;
3151
3283
  }
3284
+ op = body.call(thisArg, _);
3285
+ } catch (e) {
3286
+ op = [
3287
+ 6,
3288
+ e
3289
+ ];
3290
+ y = 0;
3152
3291
  } finally{
3153
- if (_didIteratorError) {
3154
- throw _iteratorError;
3155
- }
3292
+ f = t = 0;
3156
3293
  }
3157
- }
3158
- var result;
3159
- if (isArray) {
3160
- result = notFound.length > 0 ? {
3161
- mode: 'multiple',
3162
- models: matched,
3163
- notFound: notFound
3164
- } : {
3165
- mode: 'multiple',
3166
- models: matched
3167
- };
3168
- } else if (matched.length > 0) {
3169
- result = {
3170
- mode: 'single',
3171
- model: matched[0]
3294
+ if (op[0] & 5) throw op[1];
3295
+ return {
3296
+ value: op[0] ? op[1] : void 0,
3297
+ done: true
3172
3298
  };
3173
- } else {
3174
- throw new Error(notFound[0].message);
3175
3299
  }
3176
- return result;
3177
- }
3178
- // MARK: Row + group helpers
3179
- function toModelRow(entry, detailed) {
3180
- return detailed ? entry : toSummaryRow(entry);
3181
3300
  }
3182
- function toSummaryRow(entry) {
3183
- return _object_spread$6({
3184
- modelType: entry.modelType,
3185
- modelName: entry.modelName,
3186
- identityConst: entry.identityConst,
3187
- collectionPrefix: entry.collectionPrefix,
3188
- sourcePackage: entry.sourcePackage,
3189
- fieldCount: entry.fields.length
3190
- }, entry.modelGroup == null ? {} : {
3191
- modelGroup: entry.modelGroup
3192
- }, entry.parentIdentityConst == null ? {} : {
3193
- parentIdentityConst: entry.parentIdentityConst
3194
- }, entry.description == null ? {} : {
3195
- description: entry.description
3196
- }, entry.read == null ? {} : {
3197
- read: entry.read
3198
- }, entry.serviceFactory == null ? {} : {
3199
- serviceFactoryExport: entry.serviceFactory.exportName
3301
+ // MARK: Constants
3302
+ /**
3303
+ * Reserved tool name for the built-in `model-roles` static tool.
3304
+ */ var MODEL_ROLES_TOOL_NAME = 'model-roles';
3305
+ /**
3306
+ * Synthetic call type used in the tool's dispatch identity. Distinct from `model-get`'s `get` so
3307
+ * visibility predicates can target the two independently.
3308
+ */ var MODEL_ROLES_DISPATCH_CALL = 'roles';
3309
+ /**
3310
+ * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE}
3311
+ * — the tool isn't bound to one model type, so the literal "model" stands in.
3312
+ */ var MODEL_ROLES_DISPATCH_MODEL_TYPE = 'model';
3313
+ /**
3314
+ * Maximum number of keys accepted per `model-roles` call. Role resolution runs the model's real
3315
+ * permission delegate per key (which may itself read parent documents), so this is deliberately
3316
+ * lower than the `model-get` batch size.
3317
+ */ var MCP_MODEL_ROLES_MAX_KEYS = 25;
3318
+ // MARK: Factory
3319
+ /**
3320
+ * Builds the built-in `model-roles` MCP tool definition.
3321
+ *
3322
+ * Answers "what is this user actually allowed to do with this document?" by running the same
3323
+ * `roleMapForModel()` delegate the permission-checked read/write paths use, and returning the
3324
+ * resolved roles instead of consuming them. Because the model's real delegate runs, derived and
3325
+ * cascading roles appear exactly as the API grants them — there is no second implementation of the
3326
+ * rules to drift.
3327
+ *
3328
+ * Two things it disambiguates that a plain read cannot:
3329
+ * - **Missing vs. forbidden.** A key that resolves to a missing document returns
3330
+ * `exists: false, roles: []` rather than an error, so "the document is not there" reads
3331
+ * differently from "it is there and you have no access" (`exists: true, roles: []`).
3332
+ * - **Full access.** Admin short-circuits that grant the full-access marker come back as
3333
+ * `fullAccess: true` rather than as an opaque enumerated set.
3334
+ *
3335
+ * By default roles are resolved for the calling user. Passing `uid` resolves them for another user
3336
+ * and is gated behind {@link CreateModelRolesToolDeps.canTargetOtherUids}.
3337
+ *
3338
+ * @param deps - Role-map reader, identity resolver, and the target-uid authorization predicate.
3339
+ * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP
3340
+ * server factory's tool registry.
3341
+ */ function createModelRolesTool(deps) {
3342
+ var handler = function handler(args, ctx) {
3343
+ return modelRolesToolHandler(args, ctx, deps);
3344
+ };
3345
+ var name = MODEL_ROLES_TOOL_NAME;
3346
+ var description = 'Resolve which roles the current user (or, with "uid", another user) is granted on one or more model keys — the same permission computation the API runs, returned instead of enforced. Use to answer "why was this forbidden?" and to tell a missing document (exists:false) apart from one you cannot access (exists:true, roles:[]). Admin full-access short-circuits report fullAccess:true. Targeting another uid requires elevated access.';
3347
+ return buildStaticToolDefinition({
3348
+ name: name,
3349
+ description: description,
3350
+ inputSchema: MODEL_ROLES_INPUT_SCHEMA,
3351
+ outputSchema: MODEL_ROLES_OUTPUT_SCHEMA,
3352
+ dispatch: {
3353
+ call: MODEL_ROLES_DISPATCH_CALL,
3354
+ modelType: MODEL_ROLES_DISPATCH_MODEL_TYPE
3355
+ },
3356
+ staticHandler: handler,
3357
+ effectiveReadOnly: true,
3358
+ rule: {
3359
+ requireAuthenticated: true
3360
+ }
3200
3361
  });
3201
3362
  }
3363
+ // MARK: Handler
3364
+ function modelRolesToolHandler(args, ctx, deps) {
3365
+ return _async_to_generator$6(function() {
3366
+ var result, input, identity, targetUid, keys, resolved, error;
3367
+ return _ts_generator$6(this, function(_state) {
3368
+ switch(_state.label){
3369
+ case 0:
3370
+ _state.trys.push([
3371
+ 0,
3372
+ 3,
3373
+ ,
3374
+ 4
3375
+ ]);
3376
+ input = parseModelRolesInput(args);
3377
+ identity = deps.resolveIdentity(input.modelType, ctx.auth);
3378
+ if (identity == null) {
3379
+ throw new Error("Unknown modelType: ".concat(input.modelType));
3380
+ }
3381
+ return [
3382
+ 4,
3383
+ resolveTargetUid({
3384
+ requestedUid: input.uid,
3385
+ auth: ctx.auth,
3386
+ canTargetOtherUids: deps.canTargetOtherUids
3387
+ })
3388
+ ];
3389
+ case 1:
3390
+ targetUid = _state.sent();
3391
+ keys = resolveKeys(input.keys, identity);
3392
+ return [
3393
+ 4,
3394
+ deps.readRoleMaps(_object_spread$6({
3395
+ modelType: input.modelType,
3396
+ keys: keys,
3397
+ auth: ctx.auth
3398
+ }, targetUid == null ? {} : {
3399
+ targetUid: targetUid
3400
+ }))
3401
+ ];
3402
+ case 2:
3403
+ resolved = _state.sent();
3404
+ result = {
3405
+ content: [
3406
+ {
3407
+ type: 'text',
3408
+ text: JSON.stringify(resolved)
3409
+ }
3410
+ ],
3411
+ structuredContent: resolved
3412
+ };
3413
+ return [
3414
+ 3,
3415
+ 4
3416
+ ];
3417
+ case 3:
3418
+ error = _state.sent();
3419
+ result = formatMcpToolErrorResponse(error);
3420
+ return [
3421
+ 3,
3422
+ 4
3423
+ ];
3424
+ case 4:
3425
+ return [
3426
+ 2,
3427
+ result
3428
+ ];
3429
+ }
3430
+ });
3431
+ })();
3432
+ }
3202
3433
  /**
3203
- * Counts models per `modelGroup`, bucketing entries without a group under
3204
- * {@link MODEL_INFO_UNGROUPED_LABEL}. Sorted by count descending, then group name ascending.
3434
+ * Resolves the effective target uid for a call, enforcing the elevated-access gate.
3205
3435
  *
3206
- * @param manifest - Model manifest to summarize.
3207
- * @returns One {@link ModelInfoGroupCount} per group, ordered most-populous-first.
3436
+ * Requesting your own uid is always allowed and is treated as no target at all, so a caller that
3437
+ * passes their own uid never trips the predicate.
3208
3438
  *
3209
- * @__NO_SIDE_EFFECTS__
3210
- */ function computeGroupCounts(manifest) {
3211
- var grouped = makeValuesGroupMap(_to_consumable_array$5(manifest), function(entry) {
3212
- var _entry_modelGroup;
3213
- return (_entry_modelGroup = entry.modelGroup) !== null && _entry_modelGroup !== void 0 ? _entry_modelGroup : MODEL_INFO_UNGROUPED_LABEL;
3214
- });
3215
- var counts = [];
3216
- grouped.forEach(function(entries, group) {
3217
- counts.push({
3218
- modelGroup: group,
3219
- modelCount: entries.length
3439
+ * @param input - The requested uid, the caller's auth, and the authorization predicate.
3440
+ * @returns The uid to resolve as, or `undefined` to resolve as the caller.
3441
+ * @throws {Error} When another user's uid is requested and the predicate denies (or is absent).
3442
+ */ function resolveTargetUid(input) {
3443
+ return _async_to_generator$6(function() {
3444
+ var requestedUid, auth, canTargetOtherUids, result, allowed, _tmp;
3445
+ return _ts_generator$6(this, function(_state) {
3446
+ switch(_state.label){
3447
+ case 0:
3448
+ requestedUid = input.requestedUid, auth = input.auth, canTargetOtherUids = input.canTargetOtherUids;
3449
+ if (!(requestedUid != null && requestedUid !== (auth === null || auth === void 0 ? void 0 : auth.uid))) return [
3450
+ 3,
3451
+ 4
3452
+ ];
3453
+ if (!(canTargetOtherUids == null)) return [
3454
+ 3,
3455
+ 1
3456
+ ];
3457
+ _tmp = false;
3458
+ return [
3459
+ 3,
3460
+ 3
3461
+ ];
3462
+ case 1:
3463
+ return [
3464
+ 4,
3465
+ canTargetOtherUids(auth)
3466
+ ];
3467
+ case 2:
3468
+ _tmp = _state.sent();
3469
+ _state.label = 3;
3470
+ case 3:
3471
+ allowed = _tmp;
3472
+ if (!allowed) {
3473
+ throw new Error("model-roles: resolving roles for another user's uid requires elevated access.");
3474
+ }
3475
+ result = requestedUid;
3476
+ _state.label = 4;
3477
+ case 4:
3478
+ return [
3479
+ 2,
3480
+ result
3481
+ ];
3482
+ }
3220
3483
  });
3221
- });
3222
- counts.sort(function(a, b) {
3223
- return b.modelCount - a.modelCount || a.modelGroup.localeCompare(b.modelGroup);
3224
- });
3225
- return counts;
3484
+ })();
3226
3485
  }
3227
- function findGroupCount(query, manifest) {
3228
- var lower = query.toLowerCase();
3229
- var result;
3230
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3231
- try {
3232
- for(var _iterator = computeGroupCounts(manifest)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3233
- var count = _step.value;
3234
- if (count.modelGroup.toLowerCase() === lower) {
3235
- result = count;
3236
- break;
3237
- }
3238
- }
3239
- } catch (err) {
3240
- _didIteratorError = true;
3241
- _iteratorError = err;
3242
- } finally{
3243
- try {
3244
- if (!_iteratorNormalCompletion && _iterator.return != null) {
3245
- _iterator.return();
3246
- }
3247
- } finally{
3248
- if (_didIteratorError) {
3249
- throw _iteratorError;
3250
- }
3251
- }
3486
+ function parseModelRolesInput(args) {
3487
+ var modelType = args['modelType'];
3488
+ var keys = args['keys'];
3489
+ var uid = args['uid'];
3490
+ if (typeof modelType !== 'string' || modelType.length === 0) {
3491
+ throw new Error('model-roles: "modelType" is required and must be a non-empty string.');
3252
3492
  }
3253
- return result;
3254
- }
3255
- function buildModelNotFoundMessage(query, manifest) {
3256
- var groupMatch = findGroupCount(query, manifest);
3257
- var message;
3258
- if (groupMatch == null) {
3259
- 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.');
3260
- } else {
3261
- var plural = groupMatch.modelCount === 1 ? '' : 's';
3262
- 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.');
3493
+ if (!Array.isArray(keys) || keys.length === 0) {
3494
+ throw new Error('model-roles: "keys" is required and must be a non-empty array.');
3263
3495
  }
3264
- return message;
3265
- }
3266
- function buildUnknownGroupMessage(group, manifest) {
3267
- var available = computeGroupCounts(manifest).map(function(count) {
3268
- return "".concat(count.modelGroup, " (").concat(count.modelCount, ")");
3269
- }).join(', ');
3270
- return 'model-info: no model group matches "'.concat(group, '". Available groups: ').concat(available || '(none)', ". Call `model-info` with no arguments to list groups.");
3496
+ if (keys.length > MCP_MODEL_ROLES_MAX_KEYS) {
3497
+ throw new Error("model-roles: at most ".concat(MCP_MODEL_ROLES_MAX_KEYS, " keys may be resolved per call (received ").concat(keys.length, ")."));
3498
+ }
3499
+ if (uid !== undefined && (typeof uid !== 'string' || uid.length === 0)) {
3500
+ throw new Error('model-roles: "uid" must be a non-empty string when provided.');
3501
+ }
3502
+ var normalizedKeys = keys.map(function(value, index) {
3503
+ if (typeof value !== 'string' || value.length === 0) {
3504
+ throw new Error("model-roles: keys[".concat(index, "] must be a non-empty string."));
3505
+ }
3506
+ return value;
3507
+ });
3508
+ return _object_spread$6({
3509
+ modelType: modelType,
3510
+ keys: normalizedKeys
3511
+ }, uid === undefined ? {} : {
3512
+ uid: uid
3513
+ });
3271
3514
  }
3272
- /**
3273
- * Resolves a manifest entry by `modelType`, `identityConst`, or `collectionPrefix`.
3274
- *
3275
- * @param query - Identifier to look up.
3276
- * @param manifest - Model manifest to search.
3277
- * @returns The matching entry or `undefined`.
3278
- *
3279
- * @__NO_SIDE_EFFECTS__
3280
- */ function findModelEntry(query, manifest) {
3281
- var result;
3515
+ function resolveKeys(keys, identity) {
3516
+ var isRoot = identity.type === 'root';
3517
+ var resolved = [];
3282
3518
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3283
3519
  try {
3284
- for(var _iterator = manifest[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3285
- var entry = _step.value;
3286
- if (entry.modelType === query || entry.identityConst === query || entry.collectionPrefix === query) {
3287
- result = entry;
3288
- break;
3520
+ for(var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3521
+ var value = _step.value;
3522
+ if (value.includes('/')) {
3523
+ resolved.push(value);
3524
+ } else if (isRoot) {
3525
+ resolved.push("".concat(identity.collectionName, "/").concat(value));
3526
+ } else {
3527
+ throw new Error('model-roles: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
3289
3528
  }
3290
3529
  }
3291
3530
  } catch (err) {
@@ -3302,290 +3541,111 @@ function buildUnknownGroupMessage(group, manifest) {
3302
3541
  }
3303
3542
  }
3304
3543
  }
3305
- return result;
3544
+ return resolved;
3306
3545
  }
3307
3546
  // MARK: Schemas
3308
- var MODEL_INFO_INPUT_SCHEMA = {
3309
- type: 'object',
3310
- properties: {
3311
- model: {
3312
- 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`.',
3313
- oneOf: [
3314
- {
3315
- type: 'string',
3316
- minLength: 1
3317
- },
3318
- {
3319
- type: 'array',
3320
- minItems: 1,
3321
- items: {
3322
- type: 'string',
3323
- minLength: 1
3324
- }
3325
- }
3326
- ]
3327
- },
3328
- modelGroup: {
3329
- type: 'string',
3330
- minLength: 1,
3331
- description: 'Return the summary list of models in this group (`mode:"list"`). Matched case-insensitively.'
3332
- },
3333
- all: {
3334
- type: 'boolean',
3335
- description: 'Opt in to the full catalog summary (every model). Never the default — omit to get groups instead.'
3336
- },
3337
- fields: {
3338
- type: 'boolean',
3339
- description: 'Force (true) or suppress (false) full persisted-field detail. Defaults to summary rows for list mode and full entries for single/multiple modes.'
3340
- }
3341
- },
3342
- additionalProperties: false
3343
- };
3344
- // Shared item schema for model-bearing rows. Required props cover the summary form; full entries
3345
- // add `fields`, `sourceFile`, and `serviceFactory` (allowed as additional properties).
3346
- var MODEL_ROW_SCHEMA = {
3347
- type: 'object',
3348
- required: [
3349
- 'modelType',
3350
- 'modelName',
3351
- 'identityConst',
3352
- 'collectionPrefix',
3353
- 'sourcePackage'
3354
- ],
3355
- properties: {
3356
- modelType: {
3357
- type: 'string'
3358
- },
3359
- modelName: {
3360
- type: 'string'
3361
- },
3362
- modelGroup: {
3363
- type: 'string'
3364
- },
3365
- identityConst: {
3366
- type: 'string'
3367
- },
3368
- collectionPrefix: {
3369
- type: 'string'
3370
- },
3371
- parentIdentityConst: {
3372
- type: 'string'
3373
- },
3374
- sourcePackage: {
3375
- type: 'string'
3376
- },
3377
- sourceFile: {
3378
- type: 'string'
3379
- },
3380
- fieldCount: {
3381
- type: 'integer'
3382
- },
3383
- description: {
3384
- type: 'string'
3385
- },
3386
- read: {
3387
- type: 'string',
3388
- enum: [
3389
- 'system',
3390
- 'owner',
3391
- 'admin-only',
3392
- 'permissions'
3393
- ]
3394
- },
3395
- serviceFactoryExport: {
3396
- type: 'string'
3397
- },
3398
- fields: {
3399
- type: 'array',
3400
- items: {
3401
- type: 'object'
3402
- }
3403
- },
3404
- serviceFactory: {
3405
- type: 'object',
3406
- required: [
3407
- 'exportName',
3408
- 'sourceFile'
3409
- ],
3410
- properties: {
3411
- exportName: {
3412
- type: 'string'
3413
- },
3414
- sourceFile: {
3415
- type: 'string'
3416
- }
3417
- }
3418
- }
3419
- }
3420
- };
3421
- /**
3422
- * JSON-schema for one enum value→label table. Shared with the `enum-info` tool so both advertise the
3423
- * identical enum shape.
3424
- */ var ENUM_TABLE_SCHEMA = {
3425
- type: 'object',
3426
- required: [
3427
- 'name',
3428
- 'values'
3429
- ],
3430
- properties: {
3431
- name: {
3432
- type: 'string'
3433
- },
3434
- description: {
3435
- type: 'string'
3436
- },
3437
- values: {
3438
- type: 'array',
3439
- items: {
3440
- type: 'object',
3441
- required: [
3442
- 'name',
3443
- 'value'
3444
- ],
3445
- properties: {
3446
- name: {
3447
- type: 'string'
3448
- },
3449
- value: {
3450
- type: [
3451
- 'string',
3452
- 'number'
3453
- ]
3454
- },
3455
- description: {
3456
- type: 'string'
3457
- }
3458
- }
3547
+ var MODEL_ROLES_INPUT_SCHEMA = {
3548
+ type: 'object',
3549
+ required: [
3550
+ 'modelType',
3551
+ 'keys'
3552
+ ],
3553
+ properties: {
3554
+ modelType: {
3555
+ type: 'string',
3556
+ minLength: 1,
3557
+ description: 'Firestore model type to resolve roles on (e.g. "guestbook", "profile").'
3558
+ },
3559
+ keys: {
3560
+ type: 'array',
3561
+ minItems: 1,
3562
+ maxItems: MCP_MODEL_ROLES_MAX_KEYS,
3563
+ description: 'Full keys ("prefix/id") or bare ids (root models only).',
3564
+ items: {
3565
+ type: 'string',
3566
+ minLength: 1
3459
3567
  }
3568
+ },
3569
+ uid: {
3570
+ type: 'string',
3571
+ minLength: 1,
3572
+ description: 'Resolve roles as this user instead of the caller. Requires elevated access; passing your own uid is always allowed.'
3460
3573
  }
3461
- }
3574
+ },
3575
+ additionalProperties: false
3462
3576
  };
3463
- // MCP's `tools/list` validator requires `outputSchema.type === 'object'` at the root
3464
- // (see the MCP SDK's `ToolSchema`), so the variants are expressed via a top-level
3465
- // object whose nested `oneOf` discriminates by the `mode` literal.
3466
- var MODEL_INFO_OUTPUT_SCHEMA = {
3577
+ var MODEL_ROLES_OUTPUT_SCHEMA = {
3467
3578
  type: 'object',
3468
3579
  required: [
3469
- 'mode'
3580
+ 'targeted',
3581
+ 'results',
3582
+ 'errors'
3470
3583
  ],
3471
- 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`).',
3472
3584
  properties: {
3473
- mode: {
3585
+ uid: {
3474
3586
  type: 'string',
3475
- enum: [
3476
- 'groups',
3477
- 'list',
3478
- 'single',
3479
- 'multiple'
3480
- ]
3587
+ description: 'The uid the roles were resolved for.'
3481
3588
  },
3482
- groups: {
3589
+ targeted: {
3590
+ type: 'boolean',
3591
+ description: 'True when the roles were resolved for a user other than the caller.'
3592
+ },
3593
+ results: {
3483
3594
  type: 'array',
3484
- description: 'Present when `mode` is `"groups"`. One entry per model group.',
3595
+ description: 'Per-key resolved permission state.',
3485
3596
  items: {
3486
3597
  type: 'object',
3487
3598
  required: [
3488
- 'modelGroup',
3489
- 'modelCount'
3599
+ 'key',
3600
+ 'exists',
3601
+ 'fullAccess',
3602
+ 'roles'
3490
3603
  ],
3491
3604
  properties: {
3492
- modelGroup: {
3605
+ key: {
3493
3606
  type: 'string'
3494
3607
  },
3495
- modelCount: {
3496
- type: 'integer'
3608
+ exists: {
3609
+ type: 'boolean',
3610
+ description: 'Whether the document exists. Roles are only computed for documents that exist.'
3611
+ },
3612
+ fullAccess: {
3613
+ type: 'boolean',
3614
+ description: 'True when the full-access marker was granted; every role is implicitly held and "roles" is empty.'
3615
+ },
3616
+ roles: {
3617
+ type: 'array',
3618
+ items: {
3619
+ type: 'string'
3620
+ },
3621
+ description: 'Granted role names, sorted.'
3497
3622
  }
3498
3623
  }
3499
3624
  }
3500
3625
  },
3501
- totalModels: {
3502
- type: 'integer',
3503
- description: 'Present when `mode` is `"groups"`. Total models across all groups.'
3504
- },
3505
- hint: {
3506
- type: 'string',
3507
- description: 'Present when `mode` is `"groups"`. Guidance on how to drill into the catalog.'
3508
- },
3509
- models: {
3510
- type: 'array',
3511
- description: 'Present when `mode` is `"list"` or `"multiple"`. Summary rows by default; full entries when `fields` requests detail.',
3512
- items: MODEL_ROW_SCHEMA
3513
- },
3514
- modelGroup: {
3515
- type: 'string',
3516
- description: 'Present when `mode` is `"list"` from a `modelGroup` filter. The canonical group name.'
3517
- },
3518
- model: _object_spread_props$2(_object_spread$6({}, MODEL_ROW_SCHEMA), {
3519
- description: 'Present when `mode` is `"single"`. The matched model entry (full detail unless `fields:false`).'
3520
- }),
3521
- notFound: {
3626
+ errors: {
3522
3627
  type: 'array',
3523
- description: 'Present when `mode` is `"multiple"` and one or more queries matched nothing.',
3628
+ description: 'Per-key failures. A missing document is NOT an error — it appears in results with exists:false.',
3524
3629
  items: {
3525
3630
  type: 'object',
3526
3631
  required: [
3527
- 'query',
3632
+ 'key',
3528
3633
  'message'
3529
3634
  ],
3530
3635
  properties: {
3531
- query: {
3636
+ key: {
3532
3637
  type: 'string'
3533
3638
  },
3534
3639
  message: {
3535
3640
  type: 'string'
3641
+ },
3642
+ code: {
3643
+ type: 'string'
3536
3644
  }
3537
3645
  }
3538
3646
  }
3539
- },
3540
- enums: {
3541
- type: 'array',
3542
- description: 'Value→label tables for the enums referenced by the returned models’ fields. Present only when full field detail is returned (single/multiple modes, or list/all with `fields:true`) and at least one referenced enum is registered.',
3543
- items: ENUM_TABLE_SCHEMA
3544
- }
3545
- },
3546
- oneOf: [
3547
- {
3548
- required: [
3549
- 'groups',
3550
- 'totalModels'
3551
- ],
3552
- properties: {
3553
- mode: {
3554
- const: 'groups'
3555
- }
3556
- }
3557
- },
3558
- {
3559
- required: [
3560
- 'models'
3561
- ],
3562
- properties: {
3563
- mode: {
3564
- const: 'list'
3565
- }
3566
- }
3567
- },
3568
- {
3569
- required: [
3570
- 'model'
3571
- ],
3572
- properties: {
3573
- mode: {
3574
- const: 'single'
3575
- }
3576
- }
3577
- },
3578
- {
3579
- required: [
3580
- 'models'
3581
- ],
3582
- properties: {
3583
- mode: {
3584
- const: 'multiple'
3585
- }
3586
- }
3587
3647
  }
3588
- ]
3648
+ }
3589
3649
  };
3590
3650
 
3591
3651
  function _define_property$7(obj, key, value) {
@@ -3614,6 +3674,24 @@ function _object_spread$5(target) {
3614
3674
  }
3615
3675
  return target;
3616
3676
  }
3677
+ function ownKeys$2(object, enumerableOnly) {
3678
+ var keys = Object.keys(object);
3679
+ if (Object.getOwnPropertySymbols) {
3680
+ var symbols = Object.getOwnPropertySymbols(object);
3681
+ keys.push.apply(keys, symbols);
3682
+ }
3683
+ return keys;
3684
+ }
3685
+ function _object_spread_props$2(target, source) {
3686
+ source = source != null ? source : {};
3687
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
3688
+ else {
3689
+ ownKeys$2(Object(source)).forEach(function(key) {
3690
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
3691
+ });
3692
+ }
3693
+ return target;
3694
+ }
3617
3695
  // MARK: Constants
3618
3696
  /**
3619
3697
  * Reserved tool name for the built-in `model-decode` static tool.
@@ -3647,7 +3725,7 @@ function _object_spread$5(target) {
3647
3725
  return Promise.resolve(modelDecodeToolHandler(args, ctx, deps));
3648
3726
  };
3649
3727
  var name = MODEL_DECODE_TOOL_NAME;
3650
- var description = 'Decode a Firestore model key ("prefix/id", or subcollection path "parentPrefix/parentId/childPrefix/childId") into { leaf, ancestors, unresolvedPrefixes } using the registered manifest ('.concat(deps.manifest.length, " model").concat(deps.manifest.length === 1 ? '' : 's', ").");
3728
+ var description = 'Decode a Firestore model key ("prefix/id", or subcollection path "parentPrefix/parentId/childPrefix/childId") into { leaf, ancestors, unresolvedPrefixes, derivedKeys, compositeSource? } using the registered manifest ('.concat(deps.manifest.length, " model").concat(deps.manifest.length === 1 ? '' : 's', "). `derivedKeys` lists composite-key models whose document id is a flattened form of this key, each with its ready-to-use key.");
3651
3729
  return buildStaticToolDefinition({
3652
3730
  name: name,
3653
3731
  description: description,
@@ -3695,18 +3773,77 @@ function parseModelDecodeInput(args) {
3695
3773
  }
3696
3774
  /**
3697
3775
  * Splits `rawKey` on `/`, resolves each `[prefix, id]` pair against the manifest, and
3698
- * returns the leaf segment + ancestor chain.
3776
+ * returns the leaf segment + ancestor chain, plus the composite-key relationships declared
3777
+ * on the manifest: the keys of models derived from this key (`derivedKeys`) and, for a
3778
+ * two-way composite-key leaf, the recovered source key (`compositeSource`).
3699
3779
  *
3700
3780
  * Mirrors the dbx-cli `decodeFirestoreModelKey` helper structurally; both implementations
3701
- * must stay in lockstep on segment count and resolution order.
3781
+ * must stay in lockstep on segment count, resolution order, and composite-key output.
3702
3782
  *
3703
3783
  * @param rawKey - The Firestore key string.
3704
3784
  * @param manifest - The model manifest.
3705
- * @returns The decoded key with leaf, ancestors, and any unresolved prefixes.
3785
+ * @returns The decoded key with leaf, ancestors, unresolved prefixes, and composite-key relationships.
3706
3786
  * @throws {Error} When `rawKey` is empty or does not parse into an even number of `prefix/id` segments.
3707
3787
  *
3708
3788
  * @__NO_SIDE_EFFECTS__
3709
3789
  */ function decodeFirestoreModelKey(rawKey, manifest) {
3790
+ var _leafEntry_compositeKey;
3791
+ var segments = decodeFirestoreModelKeySegments(rawKey, manifest);
3792
+ var leafEntry = segments.leaf.modelType == null ? undefined : findModelEntry(segments.leaf.prefix, manifest);
3793
+ var derivedKeys = leafEntry == null ? [] : findCompositeKeyModelsDerivedFrom(leafEntry, manifest).map(function(entry) {
3794
+ return toDerivedCompositeKey(segments.key, entry);
3795
+ });
3796
+ var compositeSource = (leafEntry === null || leafEntry === void 0 ? void 0 : (_leafEntry_compositeKey = leafEntry.compositeKey) === null || _leafEntry_compositeKey === void 0 ? void 0 : _leafEntry_compositeKey.encoding) === 'two-way' ? decodeTwoWayCompositeSource(segments.leaf.id, manifest) : undefined;
3797
+ return _object_spread$5(_object_spread_props$2(_object_spread$5({}, segments), {
3798
+ derivedKeys: derivedKeys
3799
+ }), compositeSource == null ? {} : {
3800
+ compositeSource: compositeSource
3801
+ });
3802
+ }
3803
+ function toDerivedCompositeKey(sourceKey, entry) {
3804
+ var _ref;
3805
+ var _entry_compositeKey;
3806
+ var encoding = (_ref = (_entry_compositeKey = entry.compositeKey) === null || _entry_compositeKey === void 0 ? void 0 : _entry_compositeKey.encoding) !== null && _ref !== void 0 ? _ref : 'one-way';
3807
+ var flatId = encoding === 'two-way' ? twoWayFlatFirestoreModelKey(sourceKey) : flatFirestoreModelKey(sourceKey);
3808
+ return {
3809
+ key: "".concat(entry.collectionPrefix, "/").concat(flatId),
3810
+ modelType: entry.modelType,
3811
+ modelName: entry.modelName,
3812
+ collectionPrefix: entry.collectionPrefix,
3813
+ encoding: encoding
3814
+ };
3815
+ }
3816
+ /**
3817
+ * Recovers and decodes the source key behind a two-way flattened id.
3818
+ *
3819
+ * @param flatId - The leaf document id of a two-way composite-key model.
3820
+ * @param manifest - The model manifest.
3821
+ * @returns The decoded source key, or `undefined` when the id does not contain a separator or does
3822
+ * not recover to an even number of segments — it was not produced by `twoWayFlatFirestoreModelKey`.
3823
+ */ function decodeTwoWayCompositeSource(flatId, manifest) {
3824
+ var result;
3825
+ if (flatId.includes('_')) {
3826
+ var sourceKey = inferKeyFromTwoWayFlatFirestoreModelKey(flatId);
3827
+ var segmentCount = sourceKey.split('/').filter(function(s) {
3828
+ return s.length > 0;
3829
+ }).length;
3830
+ if (segmentCount >= 2 && segmentCount % 2 === 0) {
3831
+ result = decodeFirestoreModelKeySegments(sourceKey, manifest);
3832
+ }
3833
+ }
3834
+ return result;
3835
+ }
3836
+ /**
3837
+ * Splits `rawKey` on `/` and resolves each `[prefix, id]` pair against the manifest — the segment
3838
+ * walk shared by {@link decodeFirestoreModelKey} and the two-way composite-source decode.
3839
+ *
3840
+ * @param rawKey - The Firestore key string.
3841
+ * @param manifest - The model manifest.
3842
+ * @returns The decoded key with leaf, ancestors, and any unresolved prefixes.
3843
+ * @throws {Error} When `rawKey` is empty or does not parse into an even number of `prefix/id` segments.
3844
+ *
3845
+ * @__NO_SIDE_EFFECTS__
3846
+ */ function decodeFirestoreModelKeySegments(rawKey, manifest) {
3710
3847
  var trimmed = rawKey.trim();
3711
3848
  if (trimmed.length === 0) {
3712
3849
  throw new Error('model-decode: key is empty. Provide a Firestore key like `sf/abc123`.');
@@ -3807,34 +3944,86 @@ var SEGMENT_SCHEMA = {
3807
3944
  }
3808
3945
  }
3809
3946
  };
3810
- var MODEL_DECODE_OUTPUT_SCHEMA = {
3947
+ var DECODED_SEGMENTS_PROPERTIES = {
3948
+ key: {
3949
+ type: 'string',
3950
+ description: 'The trimmed input key.'
3951
+ },
3952
+ leaf: SEGMENT_SCHEMA,
3953
+ ancestors: {
3954
+ type: 'array',
3955
+ description: 'Parent segments, root-first.',
3956
+ items: SEGMENT_SCHEMA
3957
+ },
3958
+ unresolvedPrefixes: {
3959
+ type: 'array',
3960
+ description: 'Prefixes that did not match any model in the manifest.',
3961
+ items: {
3962
+ type: 'string'
3963
+ }
3964
+ }
3965
+ };
3966
+ var DERIVED_KEY_SCHEMA = {
3811
3967
  type: 'object',
3812
3968
  required: [
3813
3969
  'key',
3814
- 'leaf',
3815
- 'ancestors',
3816
- 'unresolvedPrefixes'
3970
+ 'modelType',
3971
+ 'modelName',
3972
+ 'collectionPrefix',
3973
+ 'encoding'
3817
3974
  ],
3818
3975
  properties: {
3819
3976
  key: {
3820
3977
  type: 'string',
3821
- description: 'The trimmed input key.'
3978
+ description: 'Ready-to-use key of the derived document (`<collectionPrefix>/<flattened source key>`).'
3822
3979
  },
3823
- leaf: SEGMENT_SCHEMA,
3824
- ancestors: {
3825
- type: 'array',
3826
- description: 'Parent segments, root-first.',
3827
- items: SEGMENT_SCHEMA
3980
+ modelType: {
3981
+ type: 'string'
3828
3982
  },
3829
- unresolvedPrefixes: {
3830
- type: 'array',
3831
- description: 'Prefixes that did not match any model in the manifest.',
3832
- items: {
3833
- type: 'string'
3834
- }
3983
+ modelName: {
3984
+ type: 'string'
3985
+ },
3986
+ collectionPrefix: {
3987
+ type: 'string'
3988
+ },
3989
+ encoding: {
3990
+ type: 'string',
3991
+ enum: [
3992
+ 'one-way',
3993
+ 'two-way'
3994
+ ],
3995
+ description: 'Flat-key encoding: `one-way` strips slashes, `two-way` replaces them with underscores.'
3835
3996
  }
3836
3997
  }
3837
3998
  };
3999
+ var MODEL_DECODE_OUTPUT_SCHEMA = {
4000
+ type: 'object',
4001
+ required: [
4002
+ 'key',
4003
+ 'leaf',
4004
+ 'ancestors',
4005
+ 'unresolvedPrefixes',
4006
+ 'derivedKeys'
4007
+ ],
4008
+ properties: _object_spread_props$2(_object_spread$5({}, DECODED_SEGMENTS_PROPERTIES), {
4009
+ derivedKeys: {
4010
+ type: 'array',
4011
+ description: 'Composite-key models whose document id is derived from this key (from `@dbxModelCompositeKey`), each with its ready-to-use key. Empty when none apply.',
4012
+ items: DERIVED_KEY_SCHEMA
4013
+ },
4014
+ compositeSource: {
4015
+ type: 'object',
4016
+ description: 'When the leaf is a two-way composite-key model, the source key recovered from its id, decoded. Absent otherwise.',
4017
+ required: [
4018
+ 'key',
4019
+ 'leaf',
4020
+ 'ancestors',
4021
+ 'unresolvedPrefixes'
4022
+ ],
4023
+ properties: DECODED_SEGMENTS_PROPERTIES
4024
+ }
4025
+ })
4026
+ };
3838
4027
 
3839
4028
  // MARK: Constants
3840
4029
  /**
@@ -4722,21 +4911,35 @@ function cliTokenToolHandler(args, ctx, deps) {
4722
4911
  });
4723
4912
  })();
4724
4913
  }
4725
- function buildOutput(minted) {
4914
+ /**
4915
+ * Renders the tool's output, including the command the target machine runs verbatim.
4916
+ *
4917
+ * `--env` is appended whenever the mint supplied an `envName`. Without it the rendered command is
4918
+ * only correct on a machine that already has the right env ACTIVE — `auth handoff` takes the issuer
4919
+ * to redeem against from the active env, so a bare machine fails `AUTH_HANDOFF_NO_ISSUER` and a
4920
+ * machine pointed at another env sends the claim to the wrong issuer. Naming the env also
4921
+ * instantiates it from its built-in default template, which a name alone does not do.
4922
+ *
4923
+ * @param minted - The mint result from the app-supplied minter.
4924
+ * @returns The tool output payload.
4925
+ */ function buildOutput(minted) {
4726
4926
  var _minted_cliName;
4727
4927
  var cliName = (_minted_cliName = minted.cliName) !== null && _minted_cliName !== void 0 ? _minted_cliName : '<cli>';
4928
+ var envArgument = minted.envName == null ? '' : " --env ".concat(minted.envName);
4728
4929
  return _object_spread$3({
4729
4930
  claimCode: minted.claimCode,
4730
4931
  claimExpiresAt: minted.claimExpiresAt,
4731
4932
  expiresAt: minted.expiresAt,
4732
4933
  scope: minted.scope,
4733
- handoffCommand: "".concat(cliName, " auth handoff ").concat(minted.claimCode)
4934
+ handoffCommand: "".concat(cliName, " auth handoff ").concat(minted.claimCode).concat(envArgument)
4734
4935
  }, minted.cliName == null ? {} : {
4735
4936
  cliName: minted.cliName
4736
4937
  }, minted.downloadUrl == null ? {} : {
4737
4938
  downloadUrl: minted.downloadUrl
4738
4939
  }, minted.downloadSha256 == null ? {} : {
4739
4940
  downloadSha256: minted.downloadSha256
4941
+ }, minted.envName == null ? {} : {
4942
+ envName: minted.envName
4740
4943
  });
4741
4944
  }
4742
4945
  // Reads the optional `scopes` input as a string array, ignoring anything that is not a non-empty string.
@@ -4827,6 +5030,9 @@ var CLI_TOKEN_OUTPUT_SCHEMA = {
4827
5030
  downloadSha256: {
4828
5031
  type: 'string'
4829
5032
  },
5033
+ envName: {
5034
+ type: 'string'
5035
+ },
4830
5036
  handoffCommand: {
4831
5037
  type: 'string'
4832
5038
  }
@@ -7197,15 +7403,19 @@ var McpServerFactoryService_1;
7197
7403
  this._resolveManifest();
7198
7404
  var staticTools = [];
7199
7405
  var getService = this.modelApiGetService;
7406
+ var modelManifest = this._cachedManifestModels;
7407
+ var enumManifest = this._cachedManifestEnums;
7200
7408
  if (getService != null) {
7201
- staticTools.push(createModelGetTool({
7409
+ staticTools.push(createModelGetTool(_object_spread$1({
7202
7410
  readDocuments: function readDocuments(modelType, keys, auth) {
7203
7411
  return getService.readDocuments(modelType, keys, auth);
7204
7412
  },
7205
7413
  resolveIdentity: function resolveIdentity(modelType, auth) {
7206
7414
  return getService.getModelIdentity(modelType, auth);
7207
7415
  }
7208
- }), createModelRolesTool({
7416
+ }, modelManifest == null ? {} : {
7417
+ manifest: modelManifest
7418
+ })), createModelRolesTool({
7209
7419
  readRoleMaps: function readRoleMaps(params) {
7210
7420
  return getService.readRoleMaps(params);
7211
7421
  },
@@ -7229,8 +7439,6 @@ var McpServerFactoryService_1;
7229
7439
  }));
7230
7440
  }
7231
7441
  }
7232
- var modelManifest = this._cachedManifestModels;
7233
- var enumManifest = this._cachedManifestEnums;
7234
7442
  if (modelManifest != null && modelManifest.length > 0) {
7235
7443
  staticTools.push(createModelInfoTool(_object_spread$1({
7236
7444
  manifest: modelManifest
@@ -9020,4 +9228,4 @@ function _unsupported_iterable_to_array(o, minLen) {
9020
9228
  };
9021
9229
  }
9022
9230
 
9023
- export { BATCH_EXECUTE_DISPATCH_CALL, BATCH_EXECUTE_DISPATCH_MODEL_TYPE, BATCH_EXECUTE_TOOL_NAME, CLI_TOKEN_DISPATCH_CALL, CLI_TOKEN_DISPATCH_MODEL_TYPE, CLI_TOKEN_TOOL_NAME, DEFAULT_BATCH_EXECUTE_MAX_PARALLEL, DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_REASON_PARAMETER_DESCRIPTION, DEFAULT_MCP_REASON_PARAMETER_MAX_LENGTH, DEFAULT_MCP_REASON_PARAMETER_NAME, DEFAULT_MCP_REASON_PARAMETER_REQUIRED, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, ENUM_INFO_DISPATCH_CALL, ENUM_INFO_DISPATCH_MODEL_TYPE, ENUM_INFO_TOOL_NAME, ENUM_TABLE_SCHEMA, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MAX_BATCH_EXECUTE_FILE_BYTES, MAX_BATCH_EXECUTE_OPERATIONS, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_CALL_TYPE_ABBREVIATIONS, MCP_CLI_TOKEN_MINTER, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MCP_MODEL_ROLES_MAX_KEYS, MCP_MODEL_ROLES_TARGET_UID_PREDICATE, MCP_TOOL_NAME_MAX_LENGTH, MCP_TOOL_NAME_WARN_LENGTH, MCP_WRITE_TOOL_DESCRIPTION_PREFIX, 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, MODEL_ROLES_DISPATCH_CALL, MODEL_ROLES_DISPATCH_MODEL_TYPE, MODEL_ROLES_TOOL_NAME, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, ROUTE_MANIFEST_VERSION, URL_MODELS_DISPATCH_CALL, URL_MODELS_DISPATCH_MODEL_TYPE, URL_MODELS_TOOL_NAME, abbreviateMcpCallType, appMcpAnalyticsModuleMetadata, applyMcpReasonParameterToSchema, applyWriteMarker, batchOperationCoordKey, buildDefaultMcpToolDescription, buildDisambiguatedMcpToolName, buildMcpToolName, buildStaticToolDefinition, buildStaticWireEntry, classifyVisibility, createBatchExecuteTool, createCliTokenTool, createEnumInfoTool, createModelDecodeTool, createModelGetTool, createModelInfoTool, createModelRolesTool, createUrlModelsTool, decodeFirestoreModelKey, extractMcpReasonFromArgs, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, matchRouteManifestUrl, mcpManifestKey, mcpModuleMetadata, mcpSchemaDeclaresProperty, noopMcpAnalyticsService, parseUrlModelsPathname, readUrlModelsImpersonationUid, resolveEffectiveReadOnly, resolveEnumInfoOutput, resolveMcpReasonParameterConfig, resolveMcpToolAnnotations, resolveRequiredScope, validateMcpToolName };
9231
+ export { BATCH_EXECUTE_DISPATCH_CALL, BATCH_EXECUTE_DISPATCH_MODEL_TYPE, BATCH_EXECUTE_TOOL_NAME, CLI_TOKEN_DISPATCH_CALL, CLI_TOKEN_DISPATCH_MODEL_TYPE, CLI_TOKEN_TOOL_NAME, DEFAULT_BATCH_EXECUTE_MAX_PARALLEL, DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_REASON_PARAMETER_DESCRIPTION, DEFAULT_MCP_REASON_PARAMETER_MAX_LENGTH, DEFAULT_MCP_REASON_PARAMETER_NAME, DEFAULT_MCP_REASON_PARAMETER_REQUIRED, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, ENUM_INFO_DISPATCH_CALL, ENUM_INFO_DISPATCH_MODEL_TYPE, ENUM_INFO_TOOL_NAME, ENUM_TABLE_SCHEMA, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MAX_BATCH_EXECUTE_FILE_BYTES, MAX_BATCH_EXECUTE_OPERATIONS, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_CALL_TYPE_ABBREVIATIONS, MCP_CLI_TOKEN_MINTER, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MCP_MODEL_ROLES_MAX_KEYS, MCP_MODEL_ROLES_TARGET_UID_PREDICATE, MCP_TOOL_NAME_MAX_LENGTH, MCP_TOOL_NAME_WARN_LENGTH, MCP_WRITE_TOOL_DESCRIPTION_PREFIX, 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, MODEL_ROLES_DISPATCH_CALL, MODEL_ROLES_DISPATCH_MODEL_TYPE, MODEL_ROLES_TOOL_NAME, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, ROUTE_MANIFEST_VERSION, URL_MODELS_DISPATCH_CALL, URL_MODELS_DISPATCH_MODEL_TYPE, URL_MODELS_TOOL_NAME, abbreviateMcpCallType, appMcpAnalyticsModuleMetadata, applyMcpReasonParameterToSchema, applyWriteMarker, batchOperationCoordKey, buildDefaultMcpToolDescription, buildDisambiguatedMcpToolName, buildMcpToolName, buildStaticToolDefinition, buildStaticWireEntry, classifyVisibility, createBatchExecuteTool, createCliTokenTool, createEnumInfoTool, createModelDecodeTool, createModelGetTool, createModelInfoTool, createModelRolesTool, createUrlModelsTool, decodeFirestoreModelKey, decodeFirestoreModelKeySegments, extractMcpReasonFromArgs, findCompositeKeyModelsDerivedFrom, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, isCompositeKeyModelDerivedFrom, matchRouteManifestUrl, mcpManifestKey, mcpModuleMetadata, mcpSchemaDeclaresProperty, noopMcpAnalyticsService, parseUrlModelsPathname, readUrlModelsImpersonationUid, resolveEffectiveReadOnly, resolveEnumInfoOutput, resolveMcpReasonParameterConfig, resolveMcpToolAnnotations, resolveRequiredScope, validateMcpToolName };