@dereekb/firebase-server 13.13.0 → 13.14.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.cjs.js CHANGED
@@ -192,6 +192,35 @@ function _class_call_check$4(instance, Constructor) {
192
192
  return isDefault ? "".concat(modelType, ".").concat(call, "._") : "".concat(modelType, ".").concat(call, ".").concat(specifier);
193
193
  }
194
194
 
195
+ function asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, key, arg) {
196
+ try {
197
+ var info = gen[key](arg);
198
+ var value = info.value;
199
+ } catch (error) {
200
+ reject(error);
201
+ return;
202
+ }
203
+ if (info.done) {
204
+ resolve(value);
205
+ } else {
206
+ Promise.resolve(value).then(_next, _throw);
207
+ }
208
+ }
209
+ function _async_to_generator$4(fn) {
210
+ return function() {
211
+ var self = this, args = arguments;
212
+ return new Promise(function(resolve, reject) {
213
+ var gen = fn.apply(self, args);
214
+ function _next(value) {
215
+ asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, "next", value);
216
+ }
217
+ function _throw(err) {
218
+ asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, "throw", err);
219
+ }
220
+ _next(undefined);
221
+ });
222
+ };
223
+ }
195
224
  function _instanceof(left, right) {
196
225
  "@swc/helpers - instanceof";
197
226
  if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
@@ -200,6 +229,105 @@ function _instanceof(left, right) {
200
229
  return left instanceof right;
201
230
  }
202
231
  }
232
+ function _ts_generator$4(thisArg, body) {
233
+ var f, y, t, _ = {
234
+ label: 0,
235
+ sent: function() {
236
+ if (t[0] & 1) throw t[1];
237
+ return t[1];
238
+ },
239
+ trys: [],
240
+ ops: []
241
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
242
+ return d(g, "next", {
243
+ value: verb(0)
244
+ }), d(g, "throw", {
245
+ value: verb(1)
246
+ }), d(g, "return", {
247
+ value: verb(2)
248
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
249
+ value: function() {
250
+ return this;
251
+ }
252
+ }), g;
253
+ function verb(n) {
254
+ return function(v) {
255
+ return step([
256
+ n,
257
+ v
258
+ ]);
259
+ };
260
+ }
261
+ function step(op) {
262
+ if (f) throw new TypeError("Generator is already executing.");
263
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
264
+ 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;
265
+ if (y = 0, t) op = [
266
+ op[0] & 2,
267
+ t.value
268
+ ];
269
+ switch(op[0]){
270
+ case 0:
271
+ case 1:
272
+ t = op;
273
+ break;
274
+ case 4:
275
+ _.label++;
276
+ return {
277
+ value: op[1],
278
+ done: false
279
+ };
280
+ case 5:
281
+ _.label++;
282
+ y = op[1];
283
+ op = [
284
+ 0
285
+ ];
286
+ continue;
287
+ case 7:
288
+ op = _.ops.pop();
289
+ _.trys.pop();
290
+ continue;
291
+ default:
292
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
293
+ _ = 0;
294
+ continue;
295
+ }
296
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
297
+ _.label = op[1];
298
+ break;
299
+ }
300
+ if (op[0] === 6 && _.label < t[1]) {
301
+ _.label = t[1];
302
+ t = op;
303
+ break;
304
+ }
305
+ if (t && _.label < t[2]) {
306
+ _.label = t[2];
307
+ _.ops.push(op);
308
+ break;
309
+ }
310
+ if (t[2]) _.ops.pop();
311
+ _.trys.pop();
312
+ continue;
313
+ }
314
+ op = body.call(thisArg, _);
315
+ } catch (e) {
316
+ op = [
317
+ 6,
318
+ e
319
+ ];
320
+ y = 0;
321
+ } finally{
322
+ f = t = 0;
323
+ }
324
+ if (op[0] & 5) throw op[1];
325
+ return {
326
+ value: op[0] ? op[1] : void 0,
327
+ done: true
328
+ };
329
+ }
330
+ }
203
331
  /**
204
332
  * Default structured value emitted by MCP when a handler returns `undefined`.
205
333
  *
@@ -212,46 +340,84 @@ function _instanceof(left, right) {
212
340
  /**
213
341
  * Resolves a dispatch result + handler API details into the MCP `CallToolResult` shape.
214
342
  *
343
+ * When `mcp.mapSuccessfulResult` is set, the raw result is first mapped (async-capable) to the value
344
+ * exposed via MCP; the tiers and the default path then operate on the mapped value. The tier
345
+ * callbacks receive `(value, context)` where `context` carries both the raw + mapped values + params.
346
+ *
215
347
  * Three-tier resolution as documented on {@link OnCallModelFunctionApiDetails.mcp}:
216
348
  *
217
349
  * - **Tier 3** — when `mcp.formatResponse` is set, its return value is used verbatim.
218
350
  * - **Tier 2** — when `mcp.summarizeResponse` is set, the summary string is wrapped into a
219
- * single text content block with the raw `result` exposed as `structuredContent`.
220
- * - **Tier 1** — default: JSON-stringify `result` as a single text content block, also
221
- * exposing the raw value as `structuredContent`.
351
+ * single text content block with the (mapped) value exposed as `structuredContent`.
352
+ * - **Tier 1** — default: JSON-stringify the (mapped) value as a single text content block, also
353
+ * exposing it as `structuredContent`.
222
354
  *
223
- * @param result - The handler's return value.
355
+ * @param result - The handler's raw return value.
224
356
  * @param params - The {@link OnCallTypedModelParams} that were dispatched.
225
- * @param details - The handler-level API details (carries Tier 2/3 formatters).
357
+ * @param details - The handler-level API details (carries the mapper + Tier 2/3 formatters).
226
358
  * @returns The MCP tool response content.
227
359
  */ function formatMcpToolResponse(result, params, details) {
228
- var mcp = details === null || details === void 0 ? void 0 : details.mcp;
229
- var response;
230
- if (mcp === null || mcp === void 0 ? void 0 : mcp.formatResponse) {
231
- response = mcp.formatResponse(result, params);
232
- } else if (mcp === null || mcp === void 0 ? void 0 : mcp.summarizeResponse) {
233
- var summary = mcp.summarizeResponse(result, params);
234
- response = {
235
- content: [
236
- {
237
- type: 'text',
238
- text: summary
239
- }
240
- ],
241
- structuredContent: result
242
- };
243
- } else {
244
- response = {
245
- content: [
246
- {
247
- type: 'text',
248
- text: stringifyResult(result)
249
- }
250
- ],
251
- structuredContent: result === undefined ? DEFAULT_VOID_MCP_SUCCESS_VALUE : result
252
- };
253
- }
254
- return response;
360
+ return _async_to_generator$4(function() {
361
+ var mcp, value, _tmp, context, response, summary;
362
+ return _ts_generator$4(this, function(_state) {
363
+ switch(_state.label){
364
+ case 0:
365
+ mcp = details === null || details === void 0 ? void 0 : details.mcp;
366
+ if (!(mcp === null || mcp === void 0 ? void 0 : mcp.mapSuccessfulResult)) return [
367
+ 3,
368
+ 2
369
+ ];
370
+ return [
371
+ 4,
372
+ mcp.mapSuccessfulResult(result, params)
373
+ ];
374
+ case 1:
375
+ _tmp = _state.sent();
376
+ return [
377
+ 3,
378
+ 3
379
+ ];
380
+ case 2:
381
+ _tmp = result;
382
+ _state.label = 3;
383
+ case 3:
384
+ value = _tmp;
385
+ context = {
386
+ raw: result,
387
+ value: value,
388
+ params: params
389
+ };
390
+ if (mcp === null || mcp === void 0 ? void 0 : mcp.formatResponse) {
391
+ response = mcp.formatResponse(value, context);
392
+ } else if (mcp === null || mcp === void 0 ? void 0 : mcp.summarizeResponse) {
393
+ summary = mcp.summarizeResponse(value, context);
394
+ response = {
395
+ content: [
396
+ {
397
+ type: 'text',
398
+ text: summary
399
+ }
400
+ ],
401
+ structuredContent: value
402
+ };
403
+ } else {
404
+ response = {
405
+ content: [
406
+ {
407
+ type: 'text',
408
+ text: stringifyResult(value)
409
+ }
410
+ ],
411
+ structuredContent: value === undefined ? DEFAULT_VOID_MCP_SUCCESS_VALUE : value
412
+ };
413
+ }
414
+ return [
415
+ 2,
416
+ response
417
+ ];
418
+ }
419
+ });
420
+ })();
255
421
  }
256
422
  /**
257
423
  * Converts an error thrown from the dispatch chain into the MCP error response shape.
@@ -458,18 +624,124 @@ function _unsupported_iterable_to_array$6(o, minLen) {
458
624
  * The default specifier key used when a handler is not behind a specifier router.
459
625
  */ var DEFAULT_SPECIFIER_KEY = '_';
460
626
  /**
461
- * Builds the MCP tool name for a (modelType, callType, specifier) triple.
627
+ * Soft limit for an MCP tool name. Names longer than this still register, but the generator
628
+ * surfaces a `name_length_warning` so the drift toward the hard cap is visible at boot / build.
629
+ */ var MCP_TOOL_NAME_WARN_LENGTH = 55;
630
+ /**
631
+ * Hard limit for an MCP tool name. Remote MCP clients reject a `tools/list` payload that contains
632
+ * any tool whose `name` exceeds this (`FrontendRemoteMcpToolDefinition.name: String should have at
633
+ * most 64 characters`), which fails the whole connection. Names over this are not registered.
634
+ */ var MCP_TOOL_NAME_MAX_LENGTH = 64;
635
+ /**
636
+ * Classifies a tool name's length against the soft/hard MCP name-length limits.
637
+ *
638
+ * Shared by the runtime generator and the build-time manifest renderer so both apply the
639
+ * same thresholds and never drift.
640
+ *
641
+ * @param name - The fully-resolved tool name (including any per-handler override).
642
+ * @returns The length classification — `error` over {@link MCP_TOOL_NAME_MAX_LENGTH}, `warn` over
643
+ * {@link MCP_TOOL_NAME_WARN_LENGTH}, otherwise `ok`.
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * validateMcpToolName('worker-create'); // { name: 'worker-create', length: 13, level: 'ok' }
648
+ * ```
649
+ */ function validateMcpToolName(name) {
650
+ var length = name.length;
651
+ var level = 'ok';
652
+ if (length > MCP_TOOL_NAME_MAX_LENGTH) {
653
+ level = 'error';
654
+ } else if (length > MCP_TOOL_NAME_WARN_LENGTH) {
655
+ level = 'warn';
656
+ }
657
+ return {
658
+ name: name,
659
+ length: length,
660
+ level: level
661
+ };
662
+ }
663
+ /**
664
+ * Builds the MCP tool name for a (modelSegment, callType, specifier) triple.
665
+ *
666
+ * The call-type segment is only emitted for the default (`_`) specifier, where it carries the
667
+ * meaning (`worker-create`, `worker-update`). Named specifiers drop it — the specifier already
668
+ * disambiguates (`worker-syncCheckHqEmployee`), which keeps names short and within the MCP
669
+ * 64-character cap. Apps can override the whole name per handler via
670
+ * {@link OnCallModelFunctionApiDetails.mcp.name}.
462
671
  *
463
- * Apps can override the auto-generated name by setting
464
- * {@link OnCallModelFunctionApiDetails.mcp.name} on the handler.
672
+ * This is the preferred (short) form. When two visible tools collide on it — two call types share a
673
+ * specifier once the call-type segment is dropped — the generator re-derives both names with
674
+ * {@link buildDisambiguatedMcpToolName} instead.
465
675
  *
466
- * @param modelType - The Firestore model type (e.g., `storageFile`).
676
+ * @param modelSegment - The model segment of the name. Defaults to the model type, but may be a
677
+ * shorter per-model override (e.g. the collection prefix) resolved by the caller.
467
678
  * @param callType - The call type (e.g., `invoke`).
468
679
  * @param specifier - The specifier key, or `_` / undefined for the default entry.
469
680
  * @returns The hyphen-joined tool name advertised on `tools/list`.
470
- */ function buildMcpToolName(modelType, callType, specifier) {
681
+ *
682
+ * @example
683
+ * ```ts
684
+ * buildMcpToolName('worker', 'create'); // 'worker-create'
685
+ * buildMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-syncCheckHqEmployee'
686
+ * ```
687
+ */ function buildMcpToolName(modelSegment, callType, specifier) {
688
+ var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
689
+ return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(specifier);
690
+ }
691
+ /**
692
+ * Single-character abbreviations for the standard CRUDQ + invoke call types, used to disambiguate
693
+ * colliding tool names without re-introducing the full call-type segment everywhere.
694
+ *
695
+ * @example
696
+ * ```ts
697
+ * MCP_CALL_TYPE_ABBREVIATIONS.update; // 'u'
698
+ * ```
699
+ */ var MCP_CALL_TYPE_ABBREVIATIONS = {
700
+ create: 'c',
701
+ read: 'r',
702
+ update: 'u',
703
+ delete: 'd',
704
+ query: 'q',
705
+ invoke: 'i'
706
+ };
707
+ /**
708
+ * Abbreviates a call type for use in a disambiguated tool name. Known CRUDQ + invoke types collapse
709
+ * to a single character; a custom call type is returned unchanged so the name stays unambiguous.
710
+ *
711
+ * @param callType - The call type / verb.
712
+ * @returns The single-character abbreviation, or the original string for a custom call type.
713
+ *
714
+ * @example
715
+ * ```ts
716
+ * abbreviateMcpCallType('update'); // 'u'
717
+ * abbreviateMcpCallType('recompute'); // 'recompute'
718
+ * ```
719
+ */ function abbreviateMcpCallType(callType) {
720
+ var _MCP_CALL_TYPE_ABBREVIATIONS_callType;
721
+ return (_MCP_CALL_TYPE_ABBREVIATIONS_callType = MCP_CALL_TYPE_ABBREVIATIONS[callType]) !== null && _MCP_CALL_TYPE_ABBREVIATIONS_callType !== void 0 ? _MCP_CALL_TYPE_ABBREVIATIONS_callType : callType;
722
+ }
723
+ /**
724
+ * Builds the disambiguated MCP tool name for a (modelSegment, callType, specifier) triple — the form
725
+ * used only when the preferred {@link buildMcpToolName} output collides with another visible tool.
726
+ *
727
+ * Named specifiers re-insert the call type, abbreviated, between the segment and specifier
728
+ * (`worker-u-syncCheckHqEmployee`); since the two colliding tools differ only by call type, their
729
+ * abbreviations differ and the names no longer clash. Default (`_`) specifiers already carry the
730
+ * full call type, so they are returned in their {@link buildMcpToolName} form unchanged.
731
+ *
732
+ * @param modelSegment - The model segment of the name (model type, or a per-model override).
733
+ * @param callType - The call type / verb.
734
+ * @param specifier - The specifier key, or `_` / undefined for the default entry.
735
+ * @returns The hyphen-joined disambiguated tool name.
736
+ *
737
+ * @example
738
+ * ```ts
739
+ * buildDisambiguatedMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-u-syncCheckHqEmployee'
740
+ * buildDisambiguatedMcpToolName('worker', 'create'); // 'worker-create'
741
+ * ```
742
+ */ function buildDisambiguatedMcpToolName(modelSegment, callType, specifier) {
471
743
  var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
472
- return isDefault ? "".concat(modelType, "-").concat(callType) : "".concat(modelType, "-").concat(callType, "-").concat(specifier);
744
+ return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(abbreviateMcpCallType(callType), "-").concat(specifier);
473
745
  }
474
746
  /**
475
747
  * Builds the default description used when no build-time MCP manifest
@@ -483,7 +755,6 @@ function _unsupported_iterable_to_array$6(o, minLen) {
483
755
  var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
484
756
  return isDefault ? 'Performs the "'.concat(callType, '" call on the "').concat(modelType, '" model.') : 'Performs the "'.concat(callType, '" call on the "').concat(modelType, '" model with the "').concat(specifier, '" specifier.');
485
757
  }
486
- // MARK: Generation
487
758
  /**
488
759
  * Generates MCP tool definitions from a model-first API details tree.
489
760
  *
@@ -491,17 +762,78 @@ function _unsupported_iterable_to_array$6(o, minLen) {
491
762
  * `inputType.toJsonSchema(options)` for the schema, and applies any handler-level
492
763
  * MCP `name` override. Descriptions and input/output schemas are pulled from the
493
764
  * build-time manifest when supplied. Tools without an `inputType` are skipped and
494
- * reported so callers can log the gap at startup.
765
+ * reported so callers can log the gap at startup. Tools whose resolved name exceeds
766
+ * {@link MCP_TOOL_NAME_MAX_LENGTH} are skipped so the advertised `tools/list` stays valid.
767
+ *
768
+ * Generation runs in two passes so a name clash is known before any name is finalized: the first
769
+ * pass plans every candidate and counts how many visible auto-named tools share each preferred name;
770
+ * the second builds each tool, re-deriving the colliding ones with the abbreviated call type
771
+ * ({@link buildDisambiguatedMcpToolName}) so both survive instead of one shadowing the other. A
772
+ * residual collision that disambiguation cannot resolve (e.g. an `mcp.name` override matching an auto
773
+ * name) drops the later tool so the dispatch map stays unambiguous.
495
774
  *
496
775
  * @param apiDetails - The model-first API details tree returned by `getModelApiDetails(callModelFn)`.
497
776
  * @param options - Optional schema generation options forwarded to `toJsonSchema()`. Defaults to {@link DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS}.
498
- * @param manifest - Optional build-time manifest map; supplies overrides for descriptions and input/output schemas keyed by {@link mcpManifestKey}.
777
+ * @param context - Optional build-time context (manifest overrides + per-model name segments).
499
778
  * @returns The list of generated tool definitions plus any skip reports.
500
779
  */ function generateMcpToolDefinitions(apiDetails) {
501
- var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, manifest = arguments.length > 2 ? arguments[2] : void 0;
780
+ var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, context = arguments.length > 2 ? arguments[2] : void 0;
502
781
  var tools = [];
503
782
  var neverVisibleTools = [];
504
783
  var skipped = [];
784
+ var warnings = [];
785
+ var seenNames = new Set();
786
+ var manifest = context === null || context === void 0 ? void 0 : context.manifest;
787
+ var naming = context === null || context === void 0 ? void 0 : context.naming;
788
+ var candidates = planMcpToolCandidates(apiDetails, naming);
789
+ var clashCounts = countVisibleAutoNameClashes(candidates);
790
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
791
+ try {
792
+ for(var _iterator = candidates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
793
+ var candidate = _step.value;
794
+ buildToolFromCandidate({
795
+ candidate: candidate,
796
+ clashCounts: clashCounts,
797
+ options: options,
798
+ manifest: manifest,
799
+ seenNames: seenNames,
800
+ outTools: tools,
801
+ outNeverVisibleTools: neverVisibleTools,
802
+ outSkipped: skipped,
803
+ outWarnings: warnings
804
+ });
805
+ }
806
+ } catch (err) {
807
+ _didIteratorError = true;
808
+ _iteratorError = err;
809
+ } finally{
810
+ try {
811
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
812
+ _iterator.return();
813
+ }
814
+ } finally{
815
+ if (_didIteratorError) {
816
+ throw _iteratorError;
817
+ }
818
+ }
819
+ }
820
+ return {
821
+ tools: tools,
822
+ neverVisibleTools: neverVisibleTools,
823
+ skipped: skipped,
824
+ warnings: warnings
825
+ };
826
+ }
827
+ /**
828
+ * First pass: walk the API tree and plan one {@link McpToolCandidate} per handler. No schema
829
+ * generation happens here — only the cheap naming + visibility classification needed to detect
830
+ * clashes before any name is finalized.
831
+ *
832
+ * @param apiDetails - The model-first API details tree.
833
+ * @param naming - Optional per-model name-segment overrides.
834
+ * @returns One candidate per non-null handler, in tree order.
835
+ */ function planMcpToolCandidates(apiDetails, naming) {
836
+ var candidates = [];
505
837
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
506
838
  try {
507
839
  for(var _iterator = Object.entries(apiDetails.models)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -513,16 +845,53 @@ function _unsupported_iterable_to_array$6(o, minLen) {
513
845
  if (callDetails == null) {
514
846
  continue;
515
847
  }
516
- generateToolsForModelCall({
517
- modelType: modelType,
518
- callType: callType,
519
- callDetails: callDetails,
520
- options: options,
521
- manifest: manifest,
522
- outTools: tools,
523
- outNeverVisibleTools: neverVisibleTools,
524
- outSkipped: skipped
525
- });
848
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
849
+ try {
850
+ for(var _iterator2 = Object.entries(callDetails.specifiers)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
851
+ var _step_value2 = _sliced_to_array$2(_step2.value, 2), specifierKey = _step_value2[0], handlerDetails = _step_value2[1];
852
+ var _ref;
853
+ var _naming_modelSegments, _handlerDetails_mcp, _handlerDetails_mcp1;
854
+ if (handlerDetails == null) {
855
+ continue;
856
+ }
857
+ var specifier = callDetails.isSpecifier ? specifierKey : undefined;
858
+ var dispatch = {
859
+ call: callType,
860
+ modelType: modelType,
861
+ specifier: specifier
862
+ };
863
+ var modelSegment = (_ref = naming === null || naming === void 0 ? void 0 : (_naming_modelSegments = naming.modelSegments) === null || _naming_modelSegments === void 0 ? void 0 : _naming_modelSegments.get(modelType)) !== null && _ref !== void 0 ? _ref : modelType;
864
+ var overrideName = (_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.name;
865
+ var baseName = overrideName !== null && overrideName !== void 0 ? overrideName : buildMcpToolName(modelSegment, callType, specifier);
866
+ var classified = classifyVisibility((_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.visibility);
867
+ var isVisible = classified.visibilityKind !== 'never';
868
+ candidates.push({
869
+ modelType: modelType,
870
+ callType: callType,
871
+ handlerDetails: handlerDetails,
872
+ specifier: specifier,
873
+ dispatch: dispatch,
874
+ modelSegment: modelSegment,
875
+ overrideName: overrideName,
876
+ baseName: baseName,
877
+ classified: classified,
878
+ isVisible: isVisible
879
+ });
880
+ }
881
+ } catch (err) {
882
+ _didIteratorError2 = true;
883
+ _iteratorError2 = err;
884
+ } finally{
885
+ try {
886
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
887
+ _iterator2.return();
888
+ }
889
+ } finally{
890
+ if (_didIteratorError2) {
891
+ throw _iteratorError2;
892
+ }
893
+ }
894
+ }
526
895
  }
527
896
  } catch (err) {
528
897
  _didIteratorError1 = true;
@@ -553,19 +922,24 @@ function _unsupported_iterable_to_array$6(o, minLen) {
553
922
  }
554
923
  }
555
924
  }
556
- return {
557
- tools: tools,
558
- neverVisibleTools: neverVisibleTools,
559
- skipped: skipped
560
- };
925
+ return candidates;
561
926
  }
562
- function generateToolsForModelCall(context) {
927
+ /**
928
+ * Counts how many visible, auto-named (no `mcp.name` override) candidates share each preferred name.
929
+ * Only these participate in clash detection — hidden tools never reach the wire, and overrides are
930
+ * explicit. A base name with a count over 1 is a clash that the build pass disambiguates.
931
+ *
932
+ * @param candidates - The planned candidates from {@link planMcpToolCandidates}.
933
+ * @returns A map of preferred name to the number of visible auto-named candidates producing it.
934
+ */ function countVisibleAutoNameClashes(candidates) {
935
+ var counts = new Map();
563
936
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
564
937
  try {
565
- for(var _iterator = Object.entries(context.callDetails.specifiers)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
566
- var _step_value = _sliced_to_array$2(_step.value, 2), specifierKey = _step_value[0], handlerDetails = _step_value[1];
567
- if (handlerDetails != null) {
568
- generateToolForSpecifier(context, specifierKey, handlerDetails);
938
+ for(var _iterator = candidates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
939
+ var candidate = _step.value;
940
+ if (candidate.isVisible && candidate.overrideName == null) {
941
+ var _counts_get;
942
+ counts.set(candidate.baseName, ((_counts_get = counts.get(candidate.baseName)) !== null && _counts_get !== void 0 ? _counts_get : 0) + 1);
569
943
  }
570
944
  }
571
945
  } catch (err) {
@@ -582,20 +956,36 @@ function generateToolsForModelCall(context) {
582
956
  }
583
957
  }
584
958
  }
959
+ return counts;
585
960
  }
586
- function generateToolForSpecifier(context, specifierKey, handlerDetails) {
587
- var _ref, _ref1, _resolveRequiredScope;
588
- var _handlerDetails_mcp, _handlerDetails_mcp1, _handlerDetails_mcp2, _handlerDetails_mcp3;
589
- var modelType = context.modelType, callType = context.callType, callDetails = context.callDetails, options = context.options, manifest = context.manifest, outTools = context.outTools, outNeverVisibleTools = context.outNeverVisibleTools, outSkipped = context.outSkipped;
590
- var specifier = callDetails.isSpecifier ? specifierKey : undefined;
591
- var dispatch = {
592
- call: callType,
593
- modelType: modelType,
594
- specifier: specifier
595
- };
596
- var name = (_ref = (_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.name) !== null && _ref !== void 0 ? _ref : buildMcpToolName(modelType, callType, specifier);
961
+ /**
962
+ * Second pass: turn one planned {@link McpToolCandidate} into a tool definition, resolving its final
963
+ * name (disambiguating a clash with the abbreviated call type), generating its schema, and routing it
964
+ * to the visible or never-visible bucket.
965
+ *
966
+ * @param context - The candidate plus the shared generation accumulators.
967
+ */ function buildToolFromCandidate(context) {
968
+ var _clashCounts_get, _ref, _resolveRequiredScope;
969
+ var _handlerDetails_mcp, _handlerDetails_mcp1;
970
+ var candidate = context.candidate, clashCounts = context.clashCounts, options = context.options, manifest = context.manifest, seenNames = context.seenNames, outTools = context.outTools, outNeverVisibleTools = context.outNeverVisibleTools, outSkipped = context.outSkipped, outWarnings = context.outWarnings;
971
+ var modelType = candidate.modelType, callType = candidate.callType, handlerDetails = candidate.handlerDetails, specifier = candidate.specifier, dispatch = candidate.dispatch, modelSegment = candidate.modelSegment, overrideName = candidate.overrideName, baseName = candidate.baseName, classified = candidate.classified, isVisible = candidate.isVisible;
972
+ // A visible auto-named tool whose preferred name is produced by more than one visible tool is
973
+ // re-derived with the abbreviated call type so both survive on the wire. Overrides and hidden tools
974
+ // keep their preferred name (overrides are explicit; hidden tools never reach the wire).
975
+ var needsDisambiguation = isVisible && overrideName == null && ((_clashCounts_get = clashCounts.get(baseName)) !== null && _clashCounts_get !== void 0 ? _clashCounts_get : 0) > 1;
976
+ var name = needsDisambiguation ? buildDisambiguatedMcpToolName(modelSegment, callType, specifier) : baseName;
977
+ var nameValidation = validateMcpToolName(name);
978
+ // A name over the hard cap would make remote clients reject the whole tools/list payload — never advertise it.
979
+ if (nameValidation.level === 'error') {
980
+ outSkipped.push({
981
+ toolName: name,
982
+ reason: 'name_too_long',
983
+ dispatch: dispatch
984
+ });
985
+ return;
986
+ }
597
987
  var manifestEntry = manifest === null || manifest === void 0 ? void 0 : manifest.get(mcpManifestKey(modelType, callType, specifier));
598
- var description = (_ref1 = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.description) !== null && _ref1 !== void 0 ? _ref1 : buildDefaultMcpToolDescription(modelType, callType, specifier);
988
+ var description = (_ref = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.description) !== null && _ref !== void 0 ? _ref : buildDefaultMcpToolDescription(modelType, callType, specifier);
599
989
  var inputSchema = resolveInputSchema({
600
990
  handlerDetails: handlerDetails,
601
991
  manifestEntry: manifestEntry,
@@ -607,9 +997,28 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
607
997
  if (inputSchema == null) {
608
998
  return;
609
999
  }
610
- var classified = classifyVisibility((_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.visibility);
1000
+ // Cross-check the handler's mapSuccessfulResult against the manifest's mapped-result signal. Only
1001
+ // meaningful when a manifest is supplied (it carries the `.api.ts` annotation outcome).
1002
+ if (manifest != null) {
1003
+ var _handlerDetails_mcp2;
1004
+ var hasMapper = ((_handlerDetails_mcp2 = handlerDetails.mcp) === null || _handlerDetails_mcp2 === void 0 ? void 0 : _handlerDetails_mcp2.mapSuccessfulResult) != null;
1005
+ var manifestMapped = (manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.mcpResultTypeName) != null;
1006
+ if (hasMapper && !manifestMapped) {
1007
+ outWarnings.push({
1008
+ toolName: name,
1009
+ reason: 'mapper_without_mapped_manifest',
1010
+ dispatch: dispatch
1011
+ });
1012
+ } else if (!hasMapper && manifestMapped) {
1013
+ outWarnings.push({
1014
+ toolName: name,
1015
+ reason: 'mapped_manifest_without_mapper',
1016
+ dispatch: dispatch
1017
+ });
1018
+ }
1019
+ }
611
1020
  var requiredScope = (_resolveRequiredScope = resolveRequiredScope(callType)) !== null && _resolveRequiredScope !== void 0 ? _resolveRequiredScope : undefined;
612
- var effectiveReadOnly = resolveEffectiveReadOnly((_handlerDetails_mcp2 = handlerDetails.mcp) === null || _handlerDetails_mcp2 === void 0 ? void 0 : _handlerDetails_mcp2.readOnly, callType);
1021
+ var effectiveReadOnly = resolveEffectiveReadOnly((_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.readOnly, callType);
613
1022
  var filterMetadata;
614
1023
  if (classified.visibilityKind === 'declarative') {
615
1024
  filterMetadata = {
@@ -632,6 +1041,21 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
632
1041
  effectiveReadOnly: effectiveReadOnly
633
1042
  };
634
1043
  }
1044
+ // Disambiguation has already separated the common dropped-call-type clash; this is the backstop for
1045
+ // a residual collision it cannot resolve (an `mcp.name` override matching an auto name, or two model
1046
+ // segments coinciding on the same call type) which would otherwise let one tool silently shadow the
1047
+ // other in the per-request name→definition map. Hidden tools never reach the map, so skip the check.
1048
+ if (isVisible) {
1049
+ if (seenNames.has(name)) {
1050
+ outSkipped.push({
1051
+ toolName: name,
1052
+ reason: 'duplicate_name',
1053
+ dispatch: dispatch
1054
+ });
1055
+ return;
1056
+ }
1057
+ seenNames.add(name);
1058
+ }
635
1059
  var outputSchema = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.outputSchema;
636
1060
  var staticWireEntry = buildStaticWireEntry({
637
1061
  name: name,
@@ -648,12 +1072,12 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
648
1072
  dispatch: dispatch,
649
1073
  filterMetadata: filterMetadata,
650
1074
  staticWireEntry: staticWireEntry,
651
- toolDetailsBuilder: (_handlerDetails_mcp3 = handlerDetails.mcp) === null || _handlerDetails_mcp3 === void 0 ? void 0 : _handlerDetails_mcp3.toolDetails
1075
+ toolDetailsBuilder: (_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.toolDetails
652
1076
  };
653
- if (filterMetadata.visibilityKind === 'never') {
654
- outNeverVisibleTools.push(definition);
655
- } else {
1077
+ if (isVisible) {
656
1078
  outTools.push(definition);
1079
+ } else {
1080
+ outNeverVisibleTools.push(definition);
657
1081
  }
658
1082
  }
659
1083
  function resolveInputSchema(context) {
@@ -2882,21 +3306,24 @@ var McpServerFactoryService_1;
2882
3306
  result = {
2883
3307
  tools: [],
2884
3308
  neverVisibleTools: [],
2885
- skipped: []
3309
+ skipped: [],
3310
+ warnings: []
2886
3311
  };
2887
3312
  } else {
2888
- result = generateMcpToolDefinitions(apiDetails, undefined, manifest);
3313
+ result = generateMcpToolDefinitions(apiDetails, undefined, {
3314
+ manifest: manifest,
3315
+ naming: this._resolveToolNamingOptions()
3316
+ });
2889
3317
  }
2890
3318
  this._cachedTools = result;
2891
3319
  }
2892
- if (!this._loggedSkips && result.skipped.length > 0) {
3320
+ if (!this._loggedSkips && (result.skipped.length > 0 || result.warnings.length > 0)) {
2893
3321
  this._loggedSkips = true;
2894
3322
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2895
3323
  try {
2896
3324
  for(var _iterator = result.skipped[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2897
3325
  var skip = _step.value;
2898
- var errorSuffix = skip.error ? ": ".concat(skip.error.message) : '';
2899
- this._logger.warn("Skipped MCP tool ".concat(skip.toolName, " (").concat(skip.reason, ")").concat(errorSuffix));
3326
+ this._logSkip(skip);
2900
3327
  }
2901
3328
  } catch (err) {
2902
3329
  _didIteratorError = true;
@@ -2912,6 +3339,107 @@ var McpServerFactoryService_1;
2912
3339
  }
2913
3340
  }
2914
3341
  }
3342
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
3343
+ try {
3344
+ for(var _iterator1 = result.warnings[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
3345
+ var warning = _step1.value;
3346
+ this._logger.warn(this._describeToolGenerationWarning(warning));
3347
+ }
3348
+ } catch (err) {
3349
+ _didIteratorError1 = true;
3350
+ _iteratorError1 = err;
3351
+ } finally{
3352
+ try {
3353
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
3354
+ _iterator1.return();
3355
+ }
3356
+ } finally{
3357
+ if (_didIteratorError1) {
3358
+ throw _iteratorError1;
3359
+ }
3360
+ }
3361
+ }
3362
+ }
3363
+ return result;
3364
+ }
3365
+ },
3366
+ {
3367
+ /**
3368
+ * Builds the per-model tool-name segment overrides from the loaded manifest's `models` catalog.
3369
+ *
3370
+ * Sourcing the segments from the manifest (rather than runtime config) keeps the runtime in
3371
+ * agreement with the build-time manifest validation, which reads the same `mcpToolNameSegment`.
3372
+ *
3373
+ * @returns Naming options carrying the segment map, or `undefined` when no model declares one.
3374
+ */ key: "_resolveToolNamingOptions",
3375
+ value: function _resolveToolNamingOptions() {
3376
+ var models = this._cachedManifestModels;
3377
+ var result;
3378
+ if (models != null && models.length > 0) {
3379
+ var modelSegments = new Map();
3380
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3381
+ try {
3382
+ for(var _iterator = models[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3383
+ var model = _step.value;
3384
+ if (model.mcpToolNameSegment != null && model.mcpToolNameSegment.length > 0) {
3385
+ modelSegments.set(model.modelType, model.mcpToolNameSegment);
3386
+ }
3387
+ }
3388
+ } catch (err) {
3389
+ _didIteratorError = true;
3390
+ _iteratorError = err;
3391
+ } finally{
3392
+ try {
3393
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
3394
+ _iterator.return();
3395
+ }
3396
+ } finally{
3397
+ if (_didIteratorError) {
3398
+ throw _iteratorError;
3399
+ }
3400
+ }
3401
+ }
3402
+ if (modelSegments.size > 0) {
3403
+ result = {
3404
+ modelSegments: modelSegments
3405
+ };
3406
+ }
3407
+ }
3408
+ return result;
3409
+ }
3410
+ },
3411
+ {
3412
+ /**
3413
+ * Logs one skipped tool at the appropriate level: name-cap and collision skips are errors (they
3414
+ * would otherwise break or shadow tools on the wire), the rest are warnings.
3415
+ *
3416
+ * @param skip - The skipped-tool report to log.
3417
+ */ key: "_logSkip",
3418
+ value: function _logSkip(skip) {
3419
+ var errorSuffix = skip.error ? ": ".concat(skip.error.message) : '';
3420
+ if (skip.reason === 'name_too_long') {
3421
+ this._logger.error("Dropped MCP tool ".concat(skip.toolName, " — its name is ").concat(skip.toolName.length, " chars, over the ").concat(MCP_TOOL_NAME_MAX_LENGTH, "-char limit. Shorten the model/specifier, hide it (mcp.visibility: false), or set an mcp.name override."));
3422
+ } else if (skip.reason === 'duplicate_name') {
3423
+ this._logger.error("Dropped MCP tool ".concat(skip.toolName, " — another visible tool already resolved to this name. Give one an mcp.name override to disambiguate."));
3424
+ } else {
3425
+ this._logger.warn("Skipped MCP tool ".concat(skip.toolName, " (").concat(skip.reason, ")").concat(errorSuffix));
3426
+ }
3427
+ }
3428
+ },
3429
+ {
3430
+ /**
3431
+ * Renders a human-readable boot-time warning for an MCP-result mapping inconsistency between a
3432
+ * handler's `mapSuccessfulResult` and the build-time manifest.
3433
+ *
3434
+ * @param warning - The tool-generation warning to describe.
3435
+ * @returns The log line to emit at startup.
3436
+ */ key: "_describeToolGenerationWarning",
3437
+ value: function _describeToolGenerationWarning(warning) {
3438
+ var result;
3439
+ if (warning.reason === 'mapper_without_mapped_manifest') {
3440
+ result = "MCP tool ".concat(warning.toolName, " declares mcp.mapSuccessfulResult but its manifest entry has no mapped result type — annotate the matching '.api.ts' leaf with '@dbxModelApiMcpResult <TypeName>' and regenerate the manifest so the advertised output schema matches the mapped result.");
3441
+ } else {
3442
+ result = "MCP tool ".concat(warning.toolName, " has a '@dbxModelApiMcpResult' manifest annotation but its handler no longer declares mcp.mapSuccessfulResult — remove the stale annotation and regenerate the manifest, or restore the mapper.");
2915
3443
  }
2916
3444
  return result;
2917
3445
  }
@@ -3424,7 +3952,7 @@ var McpServerFactoryService_1;
3424
3952
  key: "_handleCallModelToolCall",
3425
3953
  value: function _handleCallModelToolCall(definition, args, ctx) {
3426
3954
  return _async_to_generator$2(function() {
3427
- var params, outcome, result, error;
3955
+ var params, outcome, result, _tmp, error;
3428
3956
  return _ts_generator$2(this, function(_state) {
3429
3957
  switch(_state.label){
3430
3958
  case 0:
@@ -3438,9 +3966,9 @@ var McpServerFactoryService_1;
3438
3966
  case 1:
3439
3967
  _state.trys.push([
3440
3968
  1,
3441
- 3,
3969
+ 4,
3442
3970
  ,
3443
- 4
3971
+ 5
3444
3972
  ]);
3445
3973
  return [
3446
3974
  4,
@@ -3448,14 +3976,18 @@ var McpServerFactoryService_1;
3448
3976
  ];
3449
3977
  case 2:
3450
3978
  result = _state.sent();
3451
- outcome = {
3452
- response: formatMcpToolResponse(result, params, definition.details)
3453
- };
3979
+ _tmp = {};
3454
3980
  return [
3455
- 3,
3456
- 4
3981
+ 4,
3982
+ formatMcpToolResponse(result, params, definition.details)
3457
3983
  ];
3458
3984
  case 3:
3985
+ outcome = (_tmp.response = _state.sent(), _tmp);
3986
+ return [
3987
+ 3,
3988
+ 5
3989
+ ];
3990
+ case 4:
3459
3991
  error = _state.sent();
3460
3992
  outcome = {
3461
3993
  response: formatMcpToolErrorResponse(error),
@@ -3463,9 +3995,9 @@ var McpServerFactoryService_1;
3463
3995
  };
3464
3996
  return [
3465
3997
  3,
3466
- 4
3998
+ 5
3467
3999
  ];
3468
- case 4:
4000
+ case 5:
3469
4001
  return [
3470
4002
  2,
3471
4003
  outcome
@@ -4290,8 +4822,11 @@ exports.FIREBASE_SERVER_MCP_ANALYTICS_CONFIG = FIREBASE_SERVER_MCP_ANALYTICS_CON
4290
4822
  exports.FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE = FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE;
4291
4823
  exports.MCP_ANALYTICS_SERVICE = MCP_ANALYTICS_SERVICE;
4292
4824
  exports.MCP_AUTH_ROLE_READER = MCP_AUTH_ROLE_READER;
4825
+ exports.MCP_CALL_TYPE_ABBREVIATIONS = MCP_CALL_TYPE_ABBREVIATIONS;
4293
4826
  exports.MCP_MANIFEST_VERSION = MCP_MANIFEST_VERSION;
4294
4827
  exports.MCP_MODEL_GET_BATCH_SIZE = MCP_MODEL_GET_BATCH_SIZE;
4828
+ exports.MCP_TOOL_NAME_MAX_LENGTH = MCP_TOOL_NAME_MAX_LENGTH;
4829
+ exports.MCP_TOOL_NAME_WARN_LENGTH = MCP_TOOL_NAME_WARN_LENGTH;
4295
4830
  exports.MODEL_DECODE_DISPATCH_CALL = MODEL_DECODE_DISPATCH_CALL;
4296
4831
  exports.MODEL_DECODE_DISPATCH_MODEL_TYPE = MODEL_DECODE_DISPATCH_MODEL_TYPE;
4297
4832
  exports.MODEL_DECODE_TOOL_NAME = MODEL_DECODE_TOOL_NAME;
@@ -4305,8 +4840,10 @@ exports.MODEL_INFO_TOOL_NAME = MODEL_INFO_TOOL_NAME;
4305
4840
  exports.MODEL_INFO_UNGROUPED_LABEL = MODEL_INFO_UNGROUPED_LABEL;
4306
4841
  exports.McpAnalyticsService = McpAnalyticsService;
4307
4842
  exports.McpModuleConfig = McpModuleConfig;
4843
+ exports.abbreviateMcpCallType = abbreviateMcpCallType;
4308
4844
  exports.appMcpAnalyticsModuleMetadata = appMcpAnalyticsModuleMetadata;
4309
4845
  exports.buildDefaultMcpToolDescription = buildDefaultMcpToolDescription;
4846
+ exports.buildDisambiguatedMcpToolName = buildDisambiguatedMcpToolName;
4310
4847
  exports.buildMcpToolName = buildMcpToolName;
4311
4848
  exports.buildStaticWireEntry = buildStaticWireEntry;
4312
4849
  exports.classifyVisibility = classifyVisibility;
@@ -4324,3 +4861,4 @@ exports.mcpModuleMetadata = mcpModuleMetadata;
4324
4861
  exports.noopMcpAnalyticsService = noopMcpAnalyticsService;
4325
4862
  exports.resolveEffectiveReadOnly = resolveEffectiveReadOnly;
4326
4863
  exports.resolveRequiredScope = resolveRequiredScope;
4864
+ exports.validateMcpToolName = validateMcpToolName;