@dereekb/firebase-server 13.13.0 → 13.15.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
@@ -3,8 +3,8 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { getOidcScopesFromRequest } from '@dereekb/firebase-server/oidc';
6
- import { makeValuesGroupMap, authRolesSetHasRoles } from '@dereekb/util';
7
- import { ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerEnvService, FirebaseServerAnalyticsService } from '@dereekb/firebase-server';
6
+ import { makeValuesGroupMap, performAsyncTasks, authRolesSetHasRoles } from '@dereekb/util';
7
+ import { ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerStorageService, FirebaseServerEnvService, FirebaseServerAnalyticsService } from '@dereekb/firebase-server';
8
8
  import { inspect } from 'node:util';
9
9
  import { arktypeToJsonSchemaForExport } from '@dereekb/model';
10
10
  import { callModelOidcScopeForCallType } from '@dereekb/firebase';
@@ -115,13 +115,13 @@ function _define_property$7(obj, key, value) {
115
115
  * NestJS injection token for the optional {@link McpAuthRoleReader} provider.
116
116
  */ var MCP_AUTH_ROLE_READER = 'MCP_AUTH_ROLE_READER';
117
117
 
118
- function _type_of$2(obj) {
118
+ function _type_of$3(obj) {
119
119
  "@swc/helpers - typeof";
120
120
  return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
121
121
  }
122
122
  function __decorate(decorators, target, key, desc) {
123
123
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
124
- if ((typeof Reflect === "undefined" ? "undefined" : _type_of$2(Reflect)) === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
124
+ if ((typeof Reflect === "undefined" ? "undefined" : _type_of$3(Reflect)) === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
125
125
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
126
126
  return c > 3 && r && Object.defineProperty(target, key, r), r;
127
127
  }
@@ -190,7 +190,36 @@ function _class_call_check$4(instance, Constructor) {
190
190
  return isDefault ? "".concat(modelType, ".").concat(call, "._") : "".concat(modelType, ".").concat(call, ".").concat(specifier);
191
191
  }
192
192
 
193
- function _instanceof(left, right) {
193
+ function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
194
+ try {
195
+ var info = gen[key](arg);
196
+ var value = info.value;
197
+ } catch (error) {
198
+ reject(error);
199
+ return;
200
+ }
201
+ if (info.done) {
202
+ resolve(value);
203
+ } else {
204
+ Promise.resolve(value).then(_next, _throw);
205
+ }
206
+ }
207
+ function _async_to_generator$5(fn) {
208
+ return function() {
209
+ var self = this, args = arguments;
210
+ return new Promise(function(resolve, reject) {
211
+ var gen = fn.apply(self, args);
212
+ function _next(value) {
213
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "next", value);
214
+ }
215
+ function _throw(err) {
216
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "throw", err);
217
+ }
218
+ _next(undefined);
219
+ });
220
+ };
221
+ }
222
+ function _instanceof$1(left, right) {
194
223
  "@swc/helpers - instanceof";
195
224
  if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
196
225
  return !!right[Symbol.hasInstance](left);
@@ -198,6 +227,105 @@ function _instanceof(left, right) {
198
227
  return left instanceof right;
199
228
  }
200
229
  }
230
+ function _ts_generator$5(thisArg, body) {
231
+ var f, y, t, _ = {
232
+ label: 0,
233
+ sent: function() {
234
+ if (t[0] & 1) throw t[1];
235
+ return t[1];
236
+ },
237
+ trys: [],
238
+ ops: []
239
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
240
+ return d(g, "next", {
241
+ value: verb(0)
242
+ }), d(g, "throw", {
243
+ value: verb(1)
244
+ }), d(g, "return", {
245
+ value: verb(2)
246
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
247
+ value: function() {
248
+ return this;
249
+ }
250
+ }), g;
251
+ function verb(n) {
252
+ return function(v) {
253
+ return step([
254
+ n,
255
+ v
256
+ ]);
257
+ };
258
+ }
259
+ function step(op) {
260
+ if (f) throw new TypeError("Generator is already executing.");
261
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
262
+ 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;
263
+ if (y = 0, t) op = [
264
+ op[0] & 2,
265
+ t.value
266
+ ];
267
+ switch(op[0]){
268
+ case 0:
269
+ case 1:
270
+ t = op;
271
+ break;
272
+ case 4:
273
+ _.label++;
274
+ return {
275
+ value: op[1],
276
+ done: false
277
+ };
278
+ case 5:
279
+ _.label++;
280
+ y = op[1];
281
+ op = [
282
+ 0
283
+ ];
284
+ continue;
285
+ case 7:
286
+ op = _.ops.pop();
287
+ _.trys.pop();
288
+ continue;
289
+ default:
290
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
291
+ _ = 0;
292
+ continue;
293
+ }
294
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
295
+ _.label = op[1];
296
+ break;
297
+ }
298
+ if (op[0] === 6 && _.label < t[1]) {
299
+ _.label = t[1];
300
+ t = op;
301
+ break;
302
+ }
303
+ if (t && _.label < t[2]) {
304
+ _.label = t[2];
305
+ _.ops.push(op);
306
+ break;
307
+ }
308
+ if (t[2]) _.ops.pop();
309
+ _.trys.pop();
310
+ continue;
311
+ }
312
+ op = body.call(thisArg, _);
313
+ } catch (e) {
314
+ op = [
315
+ 6,
316
+ e
317
+ ];
318
+ y = 0;
319
+ } finally{
320
+ f = t = 0;
321
+ }
322
+ if (op[0] & 5) throw op[1];
323
+ return {
324
+ value: op[0] ? op[1] : void 0,
325
+ done: true
326
+ };
327
+ }
328
+ }
201
329
  /**
202
330
  * Default structured value emitted by MCP when a handler returns `undefined`.
203
331
  *
@@ -210,46 +338,84 @@ function _instanceof(left, right) {
210
338
  /**
211
339
  * Resolves a dispatch result + handler API details into the MCP `CallToolResult` shape.
212
340
  *
341
+ * When `mcp.mapSuccessfulResult` is set, the raw result is first mapped (async-capable) to the value
342
+ * exposed via MCP; the tiers and the default path then operate on the mapped value. The tier
343
+ * callbacks receive `(value, context)` where `context` carries both the raw + mapped values + params.
344
+ *
213
345
  * Three-tier resolution as documented on {@link OnCallModelFunctionApiDetails.mcp}:
214
346
  *
215
347
  * - **Tier 3** — when `mcp.formatResponse` is set, its return value is used verbatim.
216
348
  * - **Tier 2** — when `mcp.summarizeResponse` is set, the summary string is wrapped into a
217
- * single text content block with the raw `result` exposed as `structuredContent`.
218
- * - **Tier 1** — default: JSON-stringify `result` as a single text content block, also
219
- * exposing the raw value as `structuredContent`.
349
+ * single text content block with the (mapped) value exposed as `structuredContent`.
350
+ * - **Tier 1** — default: JSON-stringify the (mapped) value as a single text content block, also
351
+ * exposing it as `structuredContent`.
220
352
  *
221
- * @param result - The handler's return value.
353
+ * @param result - The handler's raw return value.
222
354
  * @param params - The {@link OnCallTypedModelParams} that were dispatched.
223
- * @param details - The handler-level API details (carries Tier 2/3 formatters).
355
+ * @param details - The handler-level API details (carries the mapper + Tier 2/3 formatters).
224
356
  * @returns The MCP tool response content.
225
357
  */ function formatMcpToolResponse(result, params, details) {
226
- var mcp = details === null || details === void 0 ? void 0 : details.mcp;
227
- var response;
228
- if (mcp === null || mcp === void 0 ? void 0 : mcp.formatResponse) {
229
- response = mcp.formatResponse(result, params);
230
- } else if (mcp === null || mcp === void 0 ? void 0 : mcp.summarizeResponse) {
231
- var summary = mcp.summarizeResponse(result, params);
232
- response = {
233
- content: [
234
- {
235
- type: 'text',
236
- text: summary
237
- }
238
- ],
239
- structuredContent: result
240
- };
241
- } else {
242
- response = {
243
- content: [
244
- {
245
- type: 'text',
246
- text: stringifyResult(result)
247
- }
248
- ],
249
- structuredContent: result === undefined ? DEFAULT_VOID_MCP_SUCCESS_VALUE : result
250
- };
251
- }
252
- return response;
358
+ return _async_to_generator$5(function() {
359
+ var mcp, value, _tmp, context, response, summary;
360
+ return _ts_generator$5(this, function(_state) {
361
+ switch(_state.label){
362
+ case 0:
363
+ mcp = details === null || details === void 0 ? void 0 : details.mcp;
364
+ if (!(mcp === null || mcp === void 0 ? void 0 : mcp.mapSuccessfulResult)) return [
365
+ 3,
366
+ 2
367
+ ];
368
+ return [
369
+ 4,
370
+ mcp.mapSuccessfulResult(result, params)
371
+ ];
372
+ case 1:
373
+ _tmp = _state.sent();
374
+ return [
375
+ 3,
376
+ 3
377
+ ];
378
+ case 2:
379
+ _tmp = result;
380
+ _state.label = 3;
381
+ case 3:
382
+ value = _tmp;
383
+ context = {
384
+ raw: result,
385
+ value: value,
386
+ params: params
387
+ };
388
+ if (mcp === null || mcp === void 0 ? void 0 : mcp.formatResponse) {
389
+ response = mcp.formatResponse(value, context);
390
+ } else if (mcp === null || mcp === void 0 ? void 0 : mcp.summarizeResponse) {
391
+ summary = mcp.summarizeResponse(value, context);
392
+ response = {
393
+ content: [
394
+ {
395
+ type: 'text',
396
+ text: summary
397
+ }
398
+ ],
399
+ structuredContent: value
400
+ };
401
+ } else {
402
+ response = {
403
+ content: [
404
+ {
405
+ type: 'text',
406
+ text: stringifyResult(value)
407
+ }
408
+ ],
409
+ structuredContent: value === undefined ? DEFAULT_VOID_MCP_SUCCESS_VALUE : value
410
+ };
411
+ }
412
+ return [
413
+ 2,
414
+ response
415
+ ];
416
+ }
417
+ });
418
+ })();
253
419
  }
254
420
  /**
255
421
  * Converts an error thrown from the dispatch chain into the MCP error response shape.
@@ -257,7 +423,7 @@ function _instanceof(left, right) {
257
423
  * @param error - The thrown error.
258
424
  * @returns An MCP tool response with `isError: true` and the error message as a text block.
259
425
  */ function formatMcpToolErrorResponse(error) {
260
- var message = _instanceof(error, Error) ? error.message : String(error);
426
+ var message = _instanceof$1(error, Error) ? error.message : String(error);
261
427
  return {
262
428
  isError: true,
263
429
  content: [
@@ -355,15 +521,15 @@ function stringifyResult(result) {
355
521
  return callModelOidcScopeForCallType(callType);
356
522
  }
357
523
 
358
- function _array_like_to_array$6(arr, len) {
524
+ function _array_like_to_array$7(arr, len) {
359
525
  if (len == null || len > arr.length) len = arr.length;
360
526
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
361
527
  return arr2;
362
528
  }
363
- function _array_with_holes$2(arr) {
529
+ function _array_with_holes$3(arr) {
364
530
  if (Array.isArray(arr)) return arr;
365
531
  }
366
- function _iterable_to_array_limit$2(arr, i) {
532
+ function _iterable_to_array_limit$3(arr, i) {
367
533
  var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
368
534
  if (_i == null) return;
369
535
  var _arr = [];
@@ -387,23 +553,23 @@ function _iterable_to_array_limit$2(arr, i) {
387
553
  }
388
554
  return _arr;
389
555
  }
390
- function _non_iterable_rest$2() {
556
+ function _non_iterable_rest$3() {
391
557
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
392
558
  }
393
- function _sliced_to_array$2(arr, i) {
394
- return _array_with_holes$2(arr) || _iterable_to_array_limit$2(arr, i) || _unsupported_iterable_to_array$6(arr, i) || _non_iterable_rest$2();
559
+ function _sliced_to_array$3(arr, i) {
560
+ return _array_with_holes$3(arr) || _iterable_to_array_limit$3(arr, i) || _unsupported_iterable_to_array$7(arr, i) || _non_iterable_rest$3();
395
561
  }
396
- function _type_of$1(obj) {
562
+ function _type_of$2(obj) {
397
563
  "@swc/helpers - typeof";
398
564
  return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
399
565
  }
400
- function _unsupported_iterable_to_array$6(o, minLen) {
566
+ function _unsupported_iterable_to_array$7(o, minLen) {
401
567
  if (!o) return;
402
- if (typeof o === "string") return _array_like_to_array$6(o, minLen);
568
+ if (typeof o === "string") return _array_like_to_array$7(o, minLen);
403
569
  var n = Object.prototype.toString.call(o).slice(8, -1);
404
570
  if (n === "Object" && o.constructor) n = o.constructor.name;
405
571
  if (n === "Map" || n === "Set") return Array.from(n);
406
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$6(o, minLen);
572
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$7(o, minLen);
407
573
  }
408
574
  /**
409
575
  * Default `toJsonSchema()` options.
@@ -456,18 +622,124 @@ function _unsupported_iterable_to_array$6(o, minLen) {
456
622
  * The default specifier key used when a handler is not behind a specifier router.
457
623
  */ var DEFAULT_SPECIFIER_KEY = '_';
458
624
  /**
459
- * Builds the MCP tool name for a (modelType, callType, specifier) triple.
625
+ * Soft limit for an MCP tool name. Names longer than this still register, but the generator
626
+ * surfaces a `name_length_warning` so the drift toward the hard cap is visible at boot / build.
627
+ */ var MCP_TOOL_NAME_WARN_LENGTH = 55;
628
+ /**
629
+ * Hard limit for an MCP tool name. Remote MCP clients reject a `tools/list` payload that contains
630
+ * any tool whose `name` exceeds this (`FrontendRemoteMcpToolDefinition.name: String should have at
631
+ * most 64 characters`), which fails the whole connection. Names over this are not registered.
632
+ */ var MCP_TOOL_NAME_MAX_LENGTH = 64;
633
+ /**
634
+ * Classifies a tool name's length against the soft/hard MCP name-length limits.
635
+ *
636
+ * Shared by the runtime generator and the build-time manifest renderer so both apply the
637
+ * same thresholds and never drift.
638
+ *
639
+ * @param name - The fully-resolved tool name (including any per-handler override).
640
+ * @returns The length classification — `error` over {@link MCP_TOOL_NAME_MAX_LENGTH}, `warn` over
641
+ * {@link MCP_TOOL_NAME_WARN_LENGTH}, otherwise `ok`.
642
+ *
643
+ * @example
644
+ * ```ts
645
+ * validateMcpToolName('worker-create'); // { name: 'worker-create', length: 13, level: 'ok' }
646
+ * ```
647
+ */ function validateMcpToolName(name) {
648
+ var length = name.length;
649
+ var level = 'ok';
650
+ if (length > MCP_TOOL_NAME_MAX_LENGTH) {
651
+ level = 'error';
652
+ } else if (length > MCP_TOOL_NAME_WARN_LENGTH) {
653
+ level = 'warn';
654
+ }
655
+ return {
656
+ name: name,
657
+ length: length,
658
+ level: level
659
+ };
660
+ }
661
+ /**
662
+ * Builds the MCP tool name for a (modelSegment, callType, specifier) triple.
460
663
  *
461
- * Apps can override the auto-generated name by setting
462
- * {@link OnCallModelFunctionApiDetails.mcp.name} on the handler.
664
+ * The call-type segment is only emitted for the default (`_`) specifier, where it carries the
665
+ * meaning (`worker-create`, `worker-update`). Named specifiers drop it — the specifier already
666
+ * disambiguates (`worker-syncCheckHqEmployee`), which keeps names short and within the MCP
667
+ * 64-character cap. Apps can override the whole name per handler via
668
+ * {@link OnCallModelFunctionApiDetails.mcp.name}.
463
669
  *
464
- * @param modelType - The Firestore model type (e.g., `storageFile`).
670
+ * This is the preferred (short) form. When two visible tools collide on it — two call types share a
671
+ * specifier once the call-type segment is dropped — the generator re-derives both names with
672
+ * {@link buildDisambiguatedMcpToolName} instead.
673
+ *
674
+ * @param modelSegment - The model segment of the name. Defaults to the model type, but may be a
675
+ * shorter per-model override (e.g. the collection prefix) resolved by the caller.
465
676
  * @param callType - The call type (e.g., `invoke`).
466
677
  * @param specifier - The specifier key, or `_` / undefined for the default entry.
467
678
  * @returns The hyphen-joined tool name advertised on `tools/list`.
468
- */ function buildMcpToolName(modelType, callType, specifier) {
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * buildMcpToolName('worker', 'create'); // 'worker-create'
683
+ * buildMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-syncCheckHqEmployee'
684
+ * ```
685
+ */ function buildMcpToolName(modelSegment, callType, specifier) {
686
+ var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
687
+ return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(specifier);
688
+ }
689
+ /**
690
+ * Single-character abbreviations for the standard CRUDQ + invoke call types, used to disambiguate
691
+ * colliding tool names without re-introducing the full call-type segment everywhere.
692
+ *
693
+ * @example
694
+ * ```ts
695
+ * MCP_CALL_TYPE_ABBREVIATIONS.update; // 'u'
696
+ * ```
697
+ */ var MCP_CALL_TYPE_ABBREVIATIONS = {
698
+ create: 'c',
699
+ read: 'r',
700
+ update: 'u',
701
+ delete: 'd',
702
+ query: 'q',
703
+ invoke: 'i'
704
+ };
705
+ /**
706
+ * Abbreviates a call type for use in a disambiguated tool name. Known CRUDQ + invoke types collapse
707
+ * to a single character; a custom call type is returned unchanged so the name stays unambiguous.
708
+ *
709
+ * @param callType - The call type / verb.
710
+ * @returns The single-character abbreviation, or the original string for a custom call type.
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * abbreviateMcpCallType('update'); // 'u'
715
+ * abbreviateMcpCallType('recompute'); // 'recompute'
716
+ * ```
717
+ */ function abbreviateMcpCallType(callType) {
718
+ var _MCP_CALL_TYPE_ABBREVIATIONS_callType;
719
+ 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;
720
+ }
721
+ /**
722
+ * Builds the disambiguated MCP tool name for a (modelSegment, callType, specifier) triple — the form
723
+ * used only when the preferred {@link buildMcpToolName} output collides with another visible tool.
724
+ *
725
+ * Named specifiers re-insert the call type, abbreviated, between the segment and specifier
726
+ * (`worker-u-syncCheckHqEmployee`); since the two colliding tools differ only by call type, their
727
+ * abbreviations differ and the names no longer clash. Default (`_`) specifiers already carry the
728
+ * full call type, so they are returned in their {@link buildMcpToolName} form unchanged.
729
+ *
730
+ * @param modelSegment - The model segment of the name (model type, or a per-model override).
731
+ * @param callType - The call type / verb.
732
+ * @param specifier - The specifier key, or `_` / undefined for the default entry.
733
+ * @returns The hyphen-joined disambiguated tool name.
734
+ *
735
+ * @example
736
+ * ```ts
737
+ * buildDisambiguatedMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-u-syncCheckHqEmployee'
738
+ * buildDisambiguatedMcpToolName('worker', 'create'); // 'worker-create'
739
+ * ```
740
+ */ function buildDisambiguatedMcpToolName(modelSegment, callType, specifier) {
469
741
  var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
470
- return isDefault ? "".concat(modelType, "-").concat(callType) : "".concat(modelType, "-").concat(callType, "-").concat(specifier);
742
+ return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(abbreviateMcpCallType(callType), "-").concat(specifier);
471
743
  }
472
744
  /**
473
745
  * Builds the default description used when no build-time MCP manifest
@@ -481,7 +753,6 @@ function _unsupported_iterable_to_array$6(o, minLen) {
481
753
  var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
482
754
  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.');
483
755
  }
484
- // MARK: Generation
485
756
  /**
486
757
  * Generates MCP tool definitions from a model-first API details tree.
487
758
  *
@@ -489,38 +760,136 @@ function _unsupported_iterable_to_array$6(o, minLen) {
489
760
  * `inputType.toJsonSchema(options)` for the schema, and applies any handler-level
490
761
  * MCP `name` override. Descriptions and input/output schemas are pulled from the
491
762
  * build-time manifest when supplied. Tools without an `inputType` are skipped and
492
- * reported so callers can log the gap at startup.
763
+ * reported so callers can log the gap at startup. Tools whose resolved name exceeds
764
+ * {@link MCP_TOOL_NAME_MAX_LENGTH} are skipped so the advertised `tools/list` stays valid.
765
+ *
766
+ * Generation runs in two passes so a name clash is known before any name is finalized: the first
767
+ * pass plans every candidate and counts how many visible auto-named tools share each preferred name;
768
+ * the second builds each tool, re-deriving the colliding ones with the abbreviated call type
769
+ * ({@link buildDisambiguatedMcpToolName}) so both survive instead of one shadowing the other. A
770
+ * residual collision that disambiguation cannot resolve (e.g. an `mcp.name` override matching an auto
771
+ * name) drops the later tool so the dispatch map stays unambiguous.
493
772
  *
494
773
  * @param apiDetails - The model-first API details tree returned by `getModelApiDetails(callModelFn)`.
495
774
  * @param options - Optional schema generation options forwarded to `toJsonSchema()`. Defaults to {@link DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS}.
496
- * @param manifest - Optional build-time manifest map; supplies overrides for descriptions and input/output schemas keyed by {@link mcpManifestKey}.
775
+ * @param context - Optional build-time context (manifest overrides + per-model name segments).
497
776
  * @returns The list of generated tool definitions plus any skip reports.
498
777
  */ function generateMcpToolDefinitions(apiDetails) {
499
- var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, manifest = arguments.length > 2 ? arguments[2] : void 0;
778
+ var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, context = arguments.length > 2 ? arguments[2] : void 0;
500
779
  var tools = [];
501
780
  var neverVisibleTools = [];
502
781
  var skipped = [];
782
+ var warnings = [];
783
+ var seenNames = new Set();
784
+ var manifest = context === null || context === void 0 ? void 0 : context.manifest;
785
+ var naming = context === null || context === void 0 ? void 0 : context.naming;
786
+ var candidates = planMcpToolCandidates(apiDetails, naming);
787
+ var clashCounts = countVisibleAutoNameClashes(candidates);
788
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
789
+ try {
790
+ for(var _iterator = candidates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
791
+ var candidate = _step.value;
792
+ buildToolFromCandidate({
793
+ candidate: candidate,
794
+ clashCounts: clashCounts,
795
+ options: options,
796
+ manifest: manifest,
797
+ seenNames: seenNames,
798
+ outTools: tools,
799
+ outNeverVisibleTools: neverVisibleTools,
800
+ outSkipped: skipped,
801
+ outWarnings: warnings
802
+ });
803
+ }
804
+ } catch (err) {
805
+ _didIteratorError = true;
806
+ _iteratorError = err;
807
+ } finally{
808
+ try {
809
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
810
+ _iterator.return();
811
+ }
812
+ } finally{
813
+ if (_didIteratorError) {
814
+ throw _iteratorError;
815
+ }
816
+ }
817
+ }
818
+ return {
819
+ tools: tools,
820
+ neverVisibleTools: neverVisibleTools,
821
+ skipped: skipped,
822
+ warnings: warnings
823
+ };
824
+ }
825
+ /**
826
+ * First pass: walk the API tree and plan one {@link McpToolCandidate} per handler. No schema
827
+ * generation happens here — only the cheap naming + visibility classification needed to detect
828
+ * clashes before any name is finalized.
829
+ *
830
+ * @param apiDetails - The model-first API details tree.
831
+ * @param naming - Optional per-model name-segment overrides.
832
+ * @returns One candidate per non-null handler, in tree order.
833
+ */ function planMcpToolCandidates(apiDetails, naming) {
834
+ var candidates = [];
503
835
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
504
836
  try {
505
837
  for(var _iterator = Object.entries(apiDetails.models)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
506
- var _step_value = _sliced_to_array$2(_step.value, 2), modelType = _step_value[0], modelEntry = _step_value[1];
838
+ var _step_value = _sliced_to_array$3(_step.value, 2), modelType = _step_value[0], modelEntry = _step_value[1];
507
839
  var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
508
840
  try {
509
841
  for(var _iterator1 = Object.entries(modelEntry.calls)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
510
- var _step_value1 = _sliced_to_array$2(_step1.value, 2), callType = _step_value1[0], callDetails = _step_value1[1];
842
+ var _step_value1 = _sliced_to_array$3(_step1.value, 2), callType = _step_value1[0], callDetails = _step_value1[1];
511
843
  if (callDetails == null) {
512
844
  continue;
513
845
  }
514
- generateToolsForModelCall({
515
- modelType: modelType,
516
- callType: callType,
517
- callDetails: callDetails,
518
- options: options,
519
- manifest: manifest,
520
- outTools: tools,
521
- outNeverVisibleTools: neverVisibleTools,
522
- outSkipped: skipped
523
- });
846
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
847
+ try {
848
+ for(var _iterator2 = Object.entries(callDetails.specifiers)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
849
+ var _step_value2 = _sliced_to_array$3(_step2.value, 2), specifierKey = _step_value2[0], handlerDetails = _step_value2[1];
850
+ var _ref;
851
+ var _naming_modelSegments, _handlerDetails_mcp, _handlerDetails_mcp1;
852
+ if (handlerDetails == null) {
853
+ continue;
854
+ }
855
+ var specifier = callDetails.isSpecifier ? specifierKey : undefined;
856
+ var dispatch = {
857
+ call: callType,
858
+ modelType: modelType,
859
+ specifier: specifier
860
+ };
861
+ 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;
862
+ var overrideName = (_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.name;
863
+ var baseName = overrideName !== null && overrideName !== void 0 ? overrideName : buildMcpToolName(modelSegment, callType, specifier);
864
+ var classified = classifyVisibility((_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.visibility);
865
+ var isVisible = classified.visibilityKind !== 'never';
866
+ candidates.push({
867
+ modelType: modelType,
868
+ callType: callType,
869
+ handlerDetails: handlerDetails,
870
+ specifier: specifier,
871
+ dispatch: dispatch,
872
+ modelSegment: modelSegment,
873
+ overrideName: overrideName,
874
+ baseName: baseName,
875
+ classified: classified,
876
+ isVisible: isVisible
877
+ });
878
+ }
879
+ } catch (err) {
880
+ _didIteratorError2 = true;
881
+ _iteratorError2 = err;
882
+ } finally{
883
+ try {
884
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
885
+ _iterator2.return();
886
+ }
887
+ } finally{
888
+ if (_didIteratorError2) {
889
+ throw _iteratorError2;
890
+ }
891
+ }
892
+ }
524
893
  }
525
894
  } catch (err) {
526
895
  _didIteratorError1 = true;
@@ -551,19 +920,24 @@ function _unsupported_iterable_to_array$6(o, minLen) {
551
920
  }
552
921
  }
553
922
  }
554
- return {
555
- tools: tools,
556
- neverVisibleTools: neverVisibleTools,
557
- skipped: skipped
558
- };
923
+ return candidates;
559
924
  }
560
- function generateToolsForModelCall(context) {
925
+ /**
926
+ * Counts how many visible, auto-named (no `mcp.name` override) candidates share each preferred name.
927
+ * Only these participate in clash detection — hidden tools never reach the wire, and overrides are
928
+ * explicit. A base name with a count over 1 is a clash that the build pass disambiguates.
929
+ *
930
+ * @param candidates - The planned candidates from {@link planMcpToolCandidates}.
931
+ * @returns A map of preferred name to the number of visible auto-named candidates producing it.
932
+ */ function countVisibleAutoNameClashes(candidates) {
933
+ var counts = new Map();
561
934
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
562
935
  try {
563
- for(var _iterator = Object.entries(context.callDetails.specifiers)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
564
- var _step_value = _sliced_to_array$2(_step.value, 2), specifierKey = _step_value[0], handlerDetails = _step_value[1];
565
- if (handlerDetails != null) {
566
- generateToolForSpecifier(context, specifierKey, handlerDetails);
936
+ for(var _iterator = candidates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
937
+ var candidate = _step.value;
938
+ if (candidate.isVisible && candidate.overrideName == null) {
939
+ var _counts_get;
940
+ counts.set(candidate.baseName, ((_counts_get = counts.get(candidate.baseName)) !== null && _counts_get !== void 0 ? _counts_get : 0) + 1);
567
941
  }
568
942
  }
569
943
  } catch (err) {
@@ -580,20 +954,36 @@ function generateToolsForModelCall(context) {
580
954
  }
581
955
  }
582
956
  }
957
+ return counts;
583
958
  }
584
- function generateToolForSpecifier(context, specifierKey, handlerDetails) {
585
- var _ref, _ref1, _resolveRequiredScope;
586
- var _handlerDetails_mcp, _handlerDetails_mcp1, _handlerDetails_mcp2, _handlerDetails_mcp3;
587
- 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;
588
- var specifier = callDetails.isSpecifier ? specifierKey : undefined;
589
- var dispatch = {
590
- call: callType,
591
- modelType: modelType,
592
- specifier: specifier
593
- };
594
- 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);
959
+ /**
960
+ * Second pass: turn one planned {@link McpToolCandidate} into a tool definition, resolving its final
961
+ * name (disambiguating a clash with the abbreviated call type), generating its schema, and routing it
962
+ * to the visible or never-visible bucket.
963
+ *
964
+ * @param context - The candidate plus the shared generation accumulators.
965
+ */ function buildToolFromCandidate(context) {
966
+ var _clashCounts_get, _ref, _resolveRequiredScope;
967
+ var _handlerDetails_mcp, _handlerDetails_mcp1;
968
+ 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;
969
+ 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;
970
+ // A visible auto-named tool whose preferred name is produced by more than one visible tool is
971
+ // re-derived with the abbreviated call type so both survive on the wire. Overrides and hidden tools
972
+ // keep their preferred name (overrides are explicit; hidden tools never reach the wire).
973
+ var needsDisambiguation = isVisible && overrideName == null && ((_clashCounts_get = clashCounts.get(baseName)) !== null && _clashCounts_get !== void 0 ? _clashCounts_get : 0) > 1;
974
+ var name = needsDisambiguation ? buildDisambiguatedMcpToolName(modelSegment, callType, specifier) : baseName;
975
+ var nameValidation = validateMcpToolName(name);
976
+ // A name over the hard cap would make remote clients reject the whole tools/list payload — never advertise it.
977
+ if (nameValidation.level === 'error') {
978
+ outSkipped.push({
979
+ toolName: name,
980
+ reason: 'name_too_long',
981
+ dispatch: dispatch
982
+ });
983
+ return;
984
+ }
595
985
  var manifestEntry = manifest === null || manifest === void 0 ? void 0 : manifest.get(mcpManifestKey(modelType, callType, specifier));
596
- var description = (_ref1 = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.description) !== null && _ref1 !== void 0 ? _ref1 : buildDefaultMcpToolDescription(modelType, callType, specifier);
986
+ var description = (_ref = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.description) !== null && _ref !== void 0 ? _ref : buildDefaultMcpToolDescription(modelType, callType, specifier);
597
987
  var inputSchema = resolveInputSchema({
598
988
  handlerDetails: handlerDetails,
599
989
  manifestEntry: manifestEntry,
@@ -605,9 +995,28 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
605
995
  if (inputSchema == null) {
606
996
  return;
607
997
  }
608
- var classified = classifyVisibility((_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.visibility);
998
+ // Cross-check the handler's mapSuccessfulResult against the manifest's mapped-result signal. Only
999
+ // meaningful when a manifest is supplied (it carries the `.api.ts` annotation outcome).
1000
+ if (manifest != null) {
1001
+ var _handlerDetails_mcp2;
1002
+ var hasMapper = ((_handlerDetails_mcp2 = handlerDetails.mcp) === null || _handlerDetails_mcp2 === void 0 ? void 0 : _handlerDetails_mcp2.mapSuccessfulResult) != null;
1003
+ var manifestMapped = (manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.mcpResultTypeName) != null;
1004
+ if (hasMapper && !manifestMapped) {
1005
+ outWarnings.push({
1006
+ toolName: name,
1007
+ reason: 'mapper_without_mapped_manifest',
1008
+ dispatch: dispatch
1009
+ });
1010
+ } else if (!hasMapper && manifestMapped) {
1011
+ outWarnings.push({
1012
+ toolName: name,
1013
+ reason: 'mapped_manifest_without_mapper',
1014
+ dispatch: dispatch
1015
+ });
1016
+ }
1017
+ }
609
1018
  var requiredScope = (_resolveRequiredScope = resolveRequiredScope(callType)) !== null && _resolveRequiredScope !== void 0 ? _resolveRequiredScope : undefined;
610
- var effectiveReadOnly = resolveEffectiveReadOnly((_handlerDetails_mcp2 = handlerDetails.mcp) === null || _handlerDetails_mcp2 === void 0 ? void 0 : _handlerDetails_mcp2.readOnly, callType);
1019
+ var effectiveReadOnly = resolveEffectiveReadOnly((_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.readOnly, callType);
611
1020
  var filterMetadata;
612
1021
  if (classified.visibilityKind === 'declarative') {
613
1022
  filterMetadata = {
@@ -630,6 +1039,21 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
630
1039
  effectiveReadOnly: effectiveReadOnly
631
1040
  };
632
1041
  }
1042
+ // Disambiguation has already separated the common dropped-call-type clash; this is the backstop for
1043
+ // a residual collision it cannot resolve (an `mcp.name` override matching an auto name, or two model
1044
+ // segments coinciding on the same call type) which would otherwise let one tool silently shadow the
1045
+ // other in the per-request name→definition map. Hidden tools never reach the map, so skip the check.
1046
+ if (isVisible) {
1047
+ if (seenNames.has(name)) {
1048
+ outSkipped.push({
1049
+ toolName: name,
1050
+ reason: 'duplicate_name',
1051
+ dispatch: dispatch
1052
+ });
1053
+ return;
1054
+ }
1055
+ seenNames.add(name);
1056
+ }
633
1057
  var outputSchema = manifestEntry === null || manifestEntry === void 0 ? void 0 : manifestEntry.outputSchema;
634
1058
  var staticWireEntry = buildStaticWireEntry({
635
1059
  name: name,
@@ -646,12 +1070,12 @@ function generateToolForSpecifier(context, specifierKey, handlerDetails) {
646
1070
  dispatch: dispatch,
647
1071
  filterMetadata: filterMetadata,
648
1072
  staticWireEntry: staticWireEntry,
649
- toolDetailsBuilder: (_handlerDetails_mcp3 = handlerDetails.mcp) === null || _handlerDetails_mcp3 === void 0 ? void 0 : _handlerDetails_mcp3.toolDetails
1073
+ toolDetailsBuilder: (_handlerDetails_mcp1 = handlerDetails.mcp) === null || _handlerDetails_mcp1 === void 0 ? void 0 : _handlerDetails_mcp1.toolDetails
650
1074
  };
651
- if (filterMetadata.visibilityKind === 'never') {
652
- outNeverVisibleTools.push(definition);
653
- } else {
1075
+ if (isVisible) {
654
1076
  outTools.push(definition);
1077
+ } else {
1078
+ outNeverVisibleTools.push(definition);
655
1079
  }
656
1080
  }
657
1081
  function resolveInputSchema(context) {
@@ -670,7 +1094,7 @@ function resolveInputSchema(context) {
670
1094
  var result;
671
1095
  try {
672
1096
  var exported = arktypeToJsonSchemaForExport(handlerDetails.inputType);
673
- if (exported && (typeof exported === "undefined" ? "undefined" : _type_of$1(exported)) === 'object') {
1097
+ if (exported && (typeof exported === "undefined" ? "undefined" : _type_of$2(exported)) === 'object') {
674
1098
  result = exported;
675
1099
  } else {
676
1100
  // arktypeToJsonSchemaForExport returned a non-object (shouldn't happen for object schemas).
@@ -689,15 +1113,15 @@ function resolveInputSchema(context) {
689
1113
  return result;
690
1114
  }
691
1115
 
692
- function _array_like_to_array$5(arr, len) {
1116
+ function _array_like_to_array$6(arr, len) {
693
1117
  if (len == null || len > arr.length) len = arr.length;
694
1118
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
695
1119
  return arr2;
696
1120
  }
697
1121
  function _array_without_holes$5(arr) {
698
- if (Array.isArray(arr)) return _array_like_to_array$5(arr);
1122
+ if (Array.isArray(arr)) return _array_like_to_array$6(arr);
699
1123
  }
700
- function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
1124
+ function asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, key, arg) {
701
1125
  try {
702
1126
  var info = gen[key](arg);
703
1127
  var value = info.value;
@@ -711,16 +1135,16 @@ function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
711
1135
  Promise.resolve(value).then(_next, _throw);
712
1136
  }
713
1137
  }
714
- function _async_to_generator$3(fn) {
1138
+ function _async_to_generator$4(fn) {
715
1139
  return function() {
716
1140
  var self = this, args = arguments;
717
1141
  return new Promise(function(resolve, reject) {
718
1142
  var gen = fn.apply(self, args);
719
1143
  function _next(value) {
720
- asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, "next", value);
1144
+ asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, "next", value);
721
1145
  }
722
1146
  function _throw(err) {
723
- asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, "throw", err);
1147
+ asyncGeneratorStep$4(gen, resolve, reject, _next, _throw, "throw", err);
724
1148
  }
725
1149
  _next(undefined);
726
1150
  });
@@ -733,17 +1157,17 @@ function _non_iterable_spread$5() {
733
1157
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
734
1158
  }
735
1159
  function _to_consumable_array$5(arr) {
736
- return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$5(arr) || _non_iterable_spread$5();
1160
+ return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$6(arr) || _non_iterable_spread$5();
737
1161
  }
738
- function _unsupported_iterable_to_array$5(o, minLen) {
1162
+ function _unsupported_iterable_to_array$6(o, minLen) {
739
1163
  if (!o) return;
740
- if (typeof o === "string") return _array_like_to_array$5(o, minLen);
1164
+ if (typeof o === "string") return _array_like_to_array$6(o, minLen);
741
1165
  var n = Object.prototype.toString.call(o).slice(8, -1);
742
1166
  if (n === "Object" && o.constructor) n = o.constructor.name;
743
1167
  if (n === "Map" || n === "Set") return Array.from(n);
744
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
1168
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$6(o, minLen);
745
1169
  }
746
- function _ts_generator$3(thisArg, body) {
1170
+ function _ts_generator$4(thisArg, body) {
747
1171
  var f, y, t, _ = {
748
1172
  label: 0,
749
1173
  sent: function() {
@@ -913,9 +1337,9 @@ function _ts_generator$3(thisArg, body) {
913
1337
  }
914
1338
  // MARK: Handler
915
1339
  function modelGetToolHandler(args, ctx, deps) {
916
- return _async_to_generator$3(function() {
1340
+ return _async_to_generator$4(function() {
917
1341
  var result, input, identity, resolvedKeys, merged, error;
918
- return _ts_generator$3(this, function(_state) {
1342
+ return _ts_generator$4(this, function(_state) {
919
1343
  switch(_state.label){
920
1344
  case 0:
921
1345
  _state.trys.push([
@@ -1022,9 +1446,9 @@ function resolveKeys(keys, identity) {
1022
1446
  return resolved;
1023
1447
  }
1024
1448
  function readInChunks(input) {
1025
- return _async_to_generator$3(function() {
1449
+ return _async_to_generator$4(function() {
1026
1450
  var modelType, keys, auth, readDocuments, merged, offset, _merged_results, _merged_errors, batch, partial;
1027
- return _ts_generator$3(this, function(_state) {
1451
+ return _ts_generator$4(this, function(_state) {
1028
1452
  switch(_state.label){
1029
1453
  case 0:
1030
1454
  modelType = input.modelType, keys = input.keys, auth = input.auth, readDocuments = input.readDocuments;
@@ -1138,13 +1562,13 @@ var MODEL_GET_OUTPUT_SCHEMA = {
1138
1562
  }
1139
1563
  };
1140
1564
 
1141
- function _array_like_to_array$4(arr, len) {
1565
+ function _array_like_to_array$5(arr, len) {
1142
1566
  if (len == null || len > arr.length) len = arr.length;
1143
1567
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
1144
1568
  return arr2;
1145
1569
  }
1146
1570
  function _array_without_holes$4(arr) {
1147
- if (Array.isArray(arr)) return _array_like_to_array$4(arr);
1571
+ if (Array.isArray(arr)) return _array_like_to_array$5(arr);
1148
1572
  }
1149
1573
  function _define_property$6(obj, key, value) {
1150
1574
  if (key in obj) {
@@ -1200,15 +1624,15 @@ function _object_spread_props(target, source) {
1200
1624
  return target;
1201
1625
  }
1202
1626
  function _to_consumable_array$4(arr) {
1203
- return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
1627
+ return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$5(arr) || _non_iterable_spread$4();
1204
1628
  }
1205
- function _unsupported_iterable_to_array$4(o, minLen) {
1629
+ function _unsupported_iterable_to_array$5(o, minLen) {
1206
1630
  if (!o) return;
1207
- if (typeof o === "string") return _array_like_to_array$4(o, minLen);
1631
+ if (typeof o === "string") return _array_like_to_array$5(o, minLen);
1208
1632
  var n = Object.prototype.toString.call(o).slice(8, -1);
1209
1633
  if (n === "Object" && o.constructor) n = o.constructor.name;
1210
1634
  if (n === "Map" || n === "Set") return Array.from(n);
1211
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
1635
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
1212
1636
  }
1213
1637
  // MARK: Constants
1214
1638
  /**
@@ -2113,16 +2537,16 @@ var MODEL_DECODE_OUTPUT_SCHEMA = {
2113
2537
  }
2114
2538
  };
2115
2539
 
2116
- function _array_like_to_array$3(arr, len) {
2540
+ function _array_like_to_array$4(arr, len) {
2117
2541
  if (len == null || len > arr.length) len = arr.length;
2118
2542
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2119
2543
  return arr2;
2120
2544
  }
2121
- function _array_with_holes$1(arr) {
2545
+ function _array_with_holes$2(arr) {
2122
2546
  if (Array.isArray(arr)) return arr;
2123
2547
  }
2124
2548
  function _array_without_holes$3(arr) {
2125
- if (Array.isArray(arr)) return _array_like_to_array$3(arr);
2549
+ if (Array.isArray(arr)) return _array_like_to_array$4(arr);
2126
2550
  }
2127
2551
  function _define_property$4(obj, key, value) {
2128
2552
  if (key in obj) {
@@ -2140,7 +2564,7 @@ function _define_property$4(obj, key, value) {
2140
2564
  function _iterable_to_array$3(iter) {
2141
2565
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
2142
2566
  }
2143
- function _iterable_to_array_limit$1(arr, i) {
2567
+ function _iterable_to_array_limit$2(arr, i) {
2144
2568
  var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
2145
2569
  if (_i == null) return;
2146
2570
  var _arr = [];
@@ -2164,7 +2588,7 @@ function _iterable_to_array_limit$1(arr, i) {
2164
2588
  }
2165
2589
  return _arr;
2166
2590
  }
2167
- function _non_iterable_rest$1() {
2591
+ function _non_iterable_rest$2() {
2168
2592
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2169
2593
  }
2170
2594
  function _non_iterable_spread$3() {
@@ -2185,19 +2609,19 @@ function _object_spread(target) {
2185
2609
  }
2186
2610
  return target;
2187
2611
  }
2188
- function _sliced_to_array$1(arr, i) {
2189
- return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$3(arr, i) || _non_iterable_rest$1();
2612
+ function _sliced_to_array$2(arr, i) {
2613
+ return _array_with_holes$2(arr) || _iterable_to_array_limit$2(arr, i) || _unsupported_iterable_to_array$4(arr, i) || _non_iterable_rest$2();
2190
2614
  }
2191
2615
  function _to_consumable_array$3(arr) {
2192
- return _array_without_holes$3(arr) || _iterable_to_array$3(arr) || _unsupported_iterable_to_array$3(arr) || _non_iterable_spread$3();
2616
+ return _array_without_holes$3(arr) || _iterable_to_array$3(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$3();
2193
2617
  }
2194
- function _unsupported_iterable_to_array$3(o, minLen) {
2618
+ function _unsupported_iterable_to_array$4(o, minLen) {
2195
2619
  if (!o) return;
2196
- if (typeof o === "string") return _array_like_to_array$3(o, minLen);
2620
+ if (typeof o === "string") return _array_like_to_array$4(o, minLen);
2197
2621
  var n = Object.prototype.toString.call(o).slice(8, -1);
2198
2622
  if (n === "Object" && o.constructor) n = o.constructor.name;
2199
2623
  if (n === "Map" || n === "Set") return Array.from(n);
2200
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
2624
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
2201
2625
  }
2202
2626
  // MARK: Constants
2203
2627
  /**
@@ -2271,10 +2695,10 @@ function whoamiToolHandler(ctx, deps) {
2271
2695
  }
2272
2696
  var token = (_auth_token = auth.token) !== null && _auth_token !== void 0 ? _auth_token : {};
2273
2697
  var presentKeys = Object.entries(token).filter(function(param) {
2274
- var _param = _sliced_to_array$1(param, 2), value = _param[1];
2698
+ var _param = _sliced_to_array$2(param, 2), value = _param[1];
2275
2699
  return value !== undefined;
2276
2700
  }).map(function(param) {
2277
- var _param = _sliced_to_array$1(param, 1), key = _param[0];
2701
+ var _param = _sliced_to_array$2(param, 1), key = _param[0];
2278
2702
  return key;
2279
2703
  });
2280
2704
  var roleSet = deps.roleReader == null ? new Set() : deps.roleReader(token);
@@ -2450,99 +2874,956 @@ function appendUnknownClaimLines(lines, unknownClaimKeys) {
2450
2874
  if (!_iteratorNormalCompletion && _iterator.return != null) {
2451
2875
  _iterator.return();
2452
2876
  }
2453
- } finally{
2454
- if (_didIteratorError) {
2455
- throw _iteratorError;
2877
+ } finally{
2878
+ if (_didIteratorError) {
2879
+ throw _iteratorError;
2880
+ }
2881
+ }
2882
+ }
2883
+ }
2884
+ // MARK: Schemas
2885
+ var WHOAMI_INPUT_SCHEMA = {
2886
+ type: 'object',
2887
+ properties: {},
2888
+ additionalProperties: false
2889
+ };
2890
+ var WHOAMI_OUTPUT_SCHEMA = {
2891
+ type: 'object',
2892
+ required: [
2893
+ 'authenticated',
2894
+ 'claims',
2895
+ 'roles',
2896
+ 'claimDetails',
2897
+ 'unknownClaimKeys'
2898
+ ],
2899
+ description: 'Caller identity, raw token claims, decoded role set, and per-claim descriptions for every key present on the live token.',
2900
+ properties: {
2901
+ authenticated: {
2902
+ type: 'boolean'
2903
+ },
2904
+ uid: {
2905
+ type: 'string'
2906
+ },
2907
+ email: {
2908
+ type: 'string'
2909
+ },
2910
+ emailVerified: {
2911
+ type: 'boolean'
2912
+ },
2913
+ app: {
2914
+ type: 'string'
2915
+ },
2916
+ claims: {
2917
+ type: 'object',
2918
+ additionalProperties: true
2919
+ },
2920
+ roles: {
2921
+ type: 'array',
2922
+ items: {
2923
+ type: 'string'
2924
+ }
2925
+ },
2926
+ claimDetails: {
2927
+ type: 'array',
2928
+ items: {
2929
+ type: 'object',
2930
+ required: [
2931
+ 'key',
2932
+ 'description',
2933
+ 'grantedRoles',
2934
+ 'inverse',
2935
+ 'tags'
2936
+ ],
2937
+ properties: {
2938
+ key: {
2939
+ type: 'string'
2940
+ },
2941
+ interfaceName: {
2942
+ type: 'string'
2943
+ },
2944
+ description: {
2945
+ type: 'string'
2946
+ },
2947
+ grantedRoles: {
2948
+ type: 'array',
2949
+ items: {
2950
+ type: 'string'
2951
+ }
2952
+ },
2953
+ inverse: {
2954
+ type: 'boolean'
2955
+ },
2956
+ tags: {
2957
+ type: 'array',
2958
+ items: {
2959
+ type: 'string'
2960
+ }
2961
+ }
2962
+ }
2963
+ }
2964
+ },
2965
+ unknownClaimKeys: {
2966
+ type: 'array',
2967
+ items: {
2968
+ type: 'string'
2969
+ }
2970
+ }
2971
+ }
2972
+ };
2973
+
2974
+ function _array_like_to_array$3(arr, len) {
2975
+ if (len == null || len > arr.length) len = arr.length;
2976
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2977
+ return arr2;
2978
+ }
2979
+ function _array_with_holes$1(arr) {
2980
+ if (Array.isArray(arr)) return arr;
2981
+ }
2982
+ function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
2983
+ try {
2984
+ var info = gen[key](arg);
2985
+ var value = info.value;
2986
+ } catch (error) {
2987
+ reject(error);
2988
+ return;
2989
+ }
2990
+ if (info.done) {
2991
+ resolve(value);
2992
+ } else {
2993
+ Promise.resolve(value).then(_next, _throw);
2994
+ }
2995
+ }
2996
+ function _async_to_generator$3(fn) {
2997
+ return function() {
2998
+ var self = this, args = arguments;
2999
+ return new Promise(function(resolve, reject) {
3000
+ var gen = fn.apply(self, args);
3001
+ function _next(value) {
3002
+ asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, "next", value);
3003
+ }
3004
+ function _throw(err) {
3005
+ asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, "throw", err);
3006
+ }
3007
+ _next(undefined);
3008
+ });
3009
+ };
3010
+ }
3011
+ function _instanceof(left, right) {
3012
+ "@swc/helpers - instanceof";
3013
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
3014
+ return !!right[Symbol.hasInstance](left);
3015
+ } else {
3016
+ return left instanceof right;
3017
+ }
3018
+ }
3019
+ function _iterable_to_array_limit$1(arr, i) {
3020
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
3021
+ if (_i == null) return;
3022
+ var _arr = [];
3023
+ var _n = true;
3024
+ var _d = false;
3025
+ var _s, _e;
3026
+ try {
3027
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
3028
+ _arr.push(_s.value);
3029
+ if (i && _arr.length === i) break;
3030
+ }
3031
+ } catch (err) {
3032
+ _d = true;
3033
+ _e = err;
3034
+ } finally{
3035
+ try {
3036
+ if (!_n && _i["return"] != null) _i["return"]();
3037
+ } finally{
3038
+ if (_d) throw _e;
3039
+ }
3040
+ }
3041
+ return _arr;
3042
+ }
3043
+ function _non_iterable_rest$1() {
3044
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
3045
+ }
3046
+ function _sliced_to_array$1(arr, i) {
3047
+ return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$3(arr, i) || _non_iterable_rest$1();
3048
+ }
3049
+ function _type_of$1(obj) {
3050
+ "@swc/helpers - typeof";
3051
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
3052
+ }
3053
+ function _unsupported_iterable_to_array$3(o, minLen) {
3054
+ if (!o) return;
3055
+ if (typeof o === "string") return _array_like_to_array$3(o, minLen);
3056
+ var n = Object.prototype.toString.call(o).slice(8, -1);
3057
+ if (n === "Object" && o.constructor) n = o.constructor.name;
3058
+ if (n === "Map" || n === "Set") return Array.from(n);
3059
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
3060
+ }
3061
+ function _ts_generator$3(thisArg, body) {
3062
+ var f, y, t, _ = {
3063
+ label: 0,
3064
+ sent: function() {
3065
+ if (t[0] & 1) throw t[1];
3066
+ return t[1];
3067
+ },
3068
+ trys: [],
3069
+ ops: []
3070
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
3071
+ return d(g, "next", {
3072
+ value: verb(0)
3073
+ }), d(g, "throw", {
3074
+ value: verb(1)
3075
+ }), d(g, "return", {
3076
+ value: verb(2)
3077
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
3078
+ value: function() {
3079
+ return this;
3080
+ }
3081
+ }), g;
3082
+ function verb(n) {
3083
+ return function(v) {
3084
+ return step([
3085
+ n,
3086
+ v
3087
+ ]);
3088
+ };
3089
+ }
3090
+ function step(op) {
3091
+ if (f) throw new TypeError("Generator is already executing.");
3092
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
3093
+ 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;
3094
+ if (y = 0, t) op = [
3095
+ op[0] & 2,
3096
+ t.value
3097
+ ];
3098
+ switch(op[0]){
3099
+ case 0:
3100
+ case 1:
3101
+ t = op;
3102
+ break;
3103
+ case 4:
3104
+ _.label++;
3105
+ return {
3106
+ value: op[1],
3107
+ done: false
3108
+ };
3109
+ case 5:
3110
+ _.label++;
3111
+ y = op[1];
3112
+ op = [
3113
+ 0
3114
+ ];
3115
+ continue;
3116
+ case 7:
3117
+ op = _.ops.pop();
3118
+ _.trys.pop();
3119
+ continue;
3120
+ default:
3121
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
3122
+ _ = 0;
3123
+ continue;
3124
+ }
3125
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
3126
+ _.label = op[1];
3127
+ break;
3128
+ }
3129
+ if (op[0] === 6 && _.label < t[1]) {
3130
+ _.label = t[1];
3131
+ t = op;
3132
+ break;
3133
+ }
3134
+ if (t && _.label < t[2]) {
3135
+ _.label = t[2];
3136
+ _.ops.push(op);
3137
+ break;
3138
+ }
3139
+ if (t[2]) _.ops.pop();
3140
+ _.trys.pop();
3141
+ continue;
3142
+ }
3143
+ op = body.call(thisArg, _);
3144
+ } catch (e) {
3145
+ op = [
3146
+ 6,
3147
+ e
3148
+ ];
3149
+ y = 0;
3150
+ } finally{
3151
+ f = t = 0;
3152
+ }
3153
+ if (op[0] & 5) throw op[1];
3154
+ return {
3155
+ value: op[0] ? op[1] : void 0,
3156
+ done: true
3157
+ };
3158
+ }
3159
+ }
3160
+ // MARK: Constants
3161
+ /**
3162
+ * Reserved tool name for the built-in `batch-execute` static tool.
3163
+ */ var BATCH_EXECUTE_TOOL_NAME = 'batch-execute';
3164
+ /**
3165
+ * Synthetic call type used in the tool's dispatch identity (analytics only — the tool re-dispatches
3166
+ * to other callModel tools rather than resolving a `batch` call type of its own).
3167
+ */ var BATCH_EXECUTE_DISPATCH_CALL = 'batch';
3168
+ /**
3169
+ * Synthetic model type used in the tool's dispatch identity. The tool isn't bound to a model type
3170
+ * (each operation carries its own), so we use the literal "batch" as the identity. Apps avoiding
3171
+ * collision should not register a real model literally named "batch".
3172
+ */ var BATCH_EXECUTE_DISPATCH_MODEL_TYPE = 'batch';
3173
+ /**
3174
+ * Default per-operation concurrency when the caller does not specify `maxParallel`.
3175
+ */ var DEFAULT_BATCH_EXECUTE_MAX_PARALLEL = 5;
3176
+ /**
3177
+ * Hard upper bound on operations accepted in a single batch file, guarding against an
3178
+ * accidentally enormous upload exhausting the function's memory/time budget.
3179
+ */ var MAX_BATCH_EXECUTE_OPERATIONS = 1000;
3180
+ /**
3181
+ * Maximum bytes read from the operations file. NDJSON of typical CRUD payloads stays well under
3182
+ * this; an oversized file is rejected rather than streamed.
3183
+ */ var MAX_BATCH_EXECUTE_FILE_BYTES = 5 * 1024 * 1024;
3184
+ // MARK: Coordinate key
3185
+ /**
3186
+ * Builds the lookup key identifying a callModel dispatch coordinate. The factory indexes the
3187
+ * caller's visible callModel tools by this key; the batch handler looks each operation up by the
3188
+ * same key, so authorization reuses the exact tool set advertised on `tools/list`.
3189
+ *
3190
+ * @param dispatch - The `(call, modelType, specifier?)` coordinate to key.
3191
+ * @returns A stable string key. Uses a NUL separator so segment values can't collide.
3192
+ */ function batchOperationCoordKey(dispatch) {
3193
+ var _dispatch_specifier;
3194
+ return "".concat(dispatch.call, "\0").concat(dispatch.modelType, "\0").concat((_dispatch_specifier = dispatch.specifier) !== null && _dispatch_specifier !== void 0 ? _dispatch_specifier : '');
3195
+ }
3196
+ // MARK: Factory
3197
+ /**
3198
+ * Builds the built-in `batch-execute` MCP tool definition.
3199
+ *
3200
+ * The tool ingests an uploaded JSON / NDJSON file of `(call, modelType, specifier?, data)`
3201
+ * operations and runs them server-side, returning a single success/failure summary. It exists so an
3202
+ * agent can perform a bulk mutation (e.g. "update 200 workers") by *generating* the operations file
3203
+ * with a script and uploading it — rather than emitting each payload as tool-call output and paying
3204
+ * one model turn per record.
3205
+ *
3206
+ * Safety model:
3207
+ * - Every operation is validated and authorized against the caller's visible tool set *before any
3208
+ * dispatch*. A malformed or forbidden operation aborts the whole batch with a pre-flight error and
3209
+ * nothing runs — `dispatch` would otherwise bypass the `tools/list` visibility filter.
3210
+ * - Runtime failures are best-effort by default (collected into the summary); `stopOnError` halts at
3211
+ * the first failure (forcing sequential execution).
3212
+ *
3213
+ * @param deps - Storage reader, dispatch fn, and per-operation authorizer (typically wired in the
3214
+ * server factory to the request's visible tool set).
3215
+ * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP server
3216
+ * factory's tool registry.
3217
+ */ function createBatchExecuteTool(deps) {
3218
+ var handler = function handler(args, ctx) {
3219
+ return batchExecuteToolHandler(args, ctx, deps);
3220
+ };
3221
+ var name = BATCH_EXECUTE_TOOL_NAME;
3222
+ var description = 'Execute a batch of MCP operations server-side from an uploaded JSON/NDJSON file, returning a success/failure summary. Each line/element is an operation `{ call, modelType, specifier?, data }` matching any other write tool you could call individually. Generate the file with a script and upload it instead of invoking a per-record tool many times. Every operation is authorized against your visible tools before any run; a forbidden or malformed operation aborts the whole batch with nothing executed.';
3223
+ return {
3224
+ name: name,
3225
+ description: description,
3226
+ inputSchema: BATCH_EXECUTE_INPUT_SCHEMA,
3227
+ outputSchema: BATCH_EXECUTE_OUTPUT_SCHEMA,
3228
+ dispatch: {
3229
+ call: BATCH_EXECUTE_DISPATCH_CALL,
3230
+ modelType: BATCH_EXECUTE_DISPATCH_MODEL_TYPE
3231
+ },
3232
+ staticHandler: handler,
3233
+ filterMetadata: {
3234
+ visibilityKind: 'declarative',
3235
+ rule: {
3236
+ requireAuthenticated: true
3237
+ },
3238
+ effectiveReadOnly: false
3239
+ },
3240
+ staticWireEntry: buildStaticWireEntry({
3241
+ name: name,
3242
+ description: description,
3243
+ inputSchema: BATCH_EXECUTE_INPUT_SCHEMA,
3244
+ outputSchema: BATCH_EXECUTE_OUTPUT_SCHEMA
3245
+ })
3246
+ };
3247
+ }
3248
+ // MARK: Handler
3249
+ function batchExecuteToolHandler(args, ctx, deps) {
3250
+ return _async_to_generator$3(function() {
3251
+ var result, _deps_maxOperations, input, maxOperations, fileText, operations, preflightErrors, execution, failureCount, successCount, skippedCount, uploadDeleted, summary, error;
3252
+ return _ts_generator$3(this, function(_state) {
3253
+ switch(_state.label){
3254
+ case 0:
3255
+ _state.trys.push([
3256
+ 0,
3257
+ 4,
3258
+ ,
3259
+ 5
3260
+ ]);
3261
+ input = parseBatchExecuteInput(args);
3262
+ maxOperations = (_deps_maxOperations = deps.maxOperations) !== null && _deps_maxOperations !== void 0 ? _deps_maxOperations : MAX_BATCH_EXECUTE_OPERATIONS;
3263
+ return [
3264
+ 4,
3265
+ readOperationsFile({
3266
+ storageService: deps.storageService,
3267
+ uploadPath: input.uploadPath,
3268
+ bucketId: input.bucketId
3269
+ })
3270
+ ];
3271
+ case 1:
3272
+ fileText = _state.sent();
3273
+ operations = parseOperations(fileText, input.format, maxOperations);
3274
+ // Pre-flight: validate + authorize every operation before any dispatch. dispatch() bypasses the
3275
+ // tools/list visibility filter, so an unauthorized operation must never reach it.
3276
+ preflightErrors = collectPreflightErrors(operations, deps.authorizeOperation);
3277
+ if (preflightErrors.length > 0) {
3278
+ throw new Error("batch-execute: ".concat(preflightErrors.length, " of ").concat(operations.length, " operation(s) failed pre-flight; nothing was executed. ").concat(describePreflightErrors(preflightErrors)));
3279
+ }
3280
+ return [
3281
+ 4,
3282
+ executeOperations({
3283
+ operations: operations,
3284
+ input: input,
3285
+ ctx: ctx,
3286
+ dispatch: deps.dispatch
3287
+ })
3288
+ ];
3289
+ case 2:
3290
+ execution = _state.sent();
3291
+ failureCount = execution.errors.length;
3292
+ successCount = execution.succeededCount;
3293
+ skippedCount = operations.length - successCount - failureCount;
3294
+ return [
3295
+ 4,
3296
+ maybeDeleteUpload({
3297
+ storageService: deps.storageService,
3298
+ input: input,
3299
+ failureCount: failureCount
3300
+ })
3301
+ ];
3302
+ case 3:
3303
+ uploadDeleted = _state.sent();
3304
+ summary = {
3305
+ total: operations.length,
3306
+ successCount: successCount,
3307
+ failureCount: failureCount,
3308
+ skippedCount: skippedCount,
3309
+ errors: execution.errors,
3310
+ uploadDeleted: uploadDeleted
3311
+ };
3312
+ result = {
3313
+ content: [
3314
+ {
3315
+ type: 'text',
3316
+ text: JSON.stringify(summary)
3317
+ }
3318
+ ],
3319
+ structuredContent: summary
3320
+ };
3321
+ return [
3322
+ 3,
3323
+ 5
3324
+ ];
3325
+ case 4:
3326
+ error = _state.sent();
3327
+ result = formatMcpToolErrorResponse(error);
3328
+ return [
3329
+ 3,
3330
+ 5
3331
+ ];
3332
+ case 5:
3333
+ return [
3334
+ 2,
3335
+ result
3336
+ ];
3337
+ }
3338
+ });
3339
+ })();
3340
+ }
3341
+ // MARK: Input parsing
3342
+ function parseBatchExecuteInput(args) {
3343
+ var _args_format, _args_maxParallel;
3344
+ var uploadPath = args.uploadPath;
3345
+ if (typeof uploadPath !== 'string' || uploadPath.length === 0) {
3346
+ throw new Error('batch-execute: "uploadPath" is required and must be a non-empty string.');
3347
+ }
3348
+ var bucketId = args.bucketId;
3349
+ if (bucketId != null && (typeof bucketId !== 'string' || bucketId.length === 0)) {
3350
+ throw new Error('batch-execute: "bucketId" must be a non-empty string when provided.');
3351
+ }
3352
+ var format = (_args_format = args.format) !== null && _args_format !== void 0 ? _args_format : 'ndjson';
3353
+ if (format !== 'json' && format !== 'ndjson') {
3354
+ throw new Error('batch-execute: "format" must be "json" or "ndjson".');
3355
+ }
3356
+ var maxParallel = (_args_maxParallel = args.maxParallel) !== null && _args_maxParallel !== void 0 ? _args_maxParallel : DEFAULT_BATCH_EXECUTE_MAX_PARALLEL;
3357
+ if (typeof maxParallel !== 'number' || !Number.isInteger(maxParallel) || maxParallel < 1) {
3358
+ throw new Error('batch-execute: "maxParallel" must be a positive integer.');
3359
+ }
3360
+ return {
3361
+ uploadPath: uploadPath,
3362
+ bucketId: bucketId !== null && bucketId !== void 0 ? bucketId : undefined,
3363
+ format: format,
3364
+ maxParallel: maxParallel,
3365
+ stopOnError: args.stopOnError === true,
3366
+ deleteUploadOnSuccess: args.deleteUploadOnSuccess !== false
3367
+ };
3368
+ }
3369
+ function readOperationsFile(input) {
3370
+ return _async_to_generator$3(function() {
3371
+ var file, exists, bytes;
3372
+ return _ts_generator$3(this, function(_state) {
3373
+ switch(_state.label){
3374
+ case 0:
3375
+ file = input.storageService.file(toStoragePathInput(input.uploadPath, input.bucketId));
3376
+ return [
3377
+ 4,
3378
+ file.exists()
3379
+ ];
3380
+ case 1:
3381
+ exists = _state.sent();
3382
+ if (!exists) {
3383
+ throw new Error('batch-execute: operations file not found at "'.concat(input.uploadPath, '".'));
3384
+ }
3385
+ return [
3386
+ 4,
3387
+ file.getBytes(MAX_BATCH_EXECUTE_FILE_BYTES)
3388
+ ];
3389
+ case 2:
3390
+ bytes = _state.sent();
3391
+ return [
3392
+ 2,
3393
+ Buffer.from(bytes).toString('utf8')
3394
+ ];
3395
+ }
3396
+ });
3397
+ })();
3398
+ }
3399
+ // MARK: Operation parsing
3400
+ function parseOperations(fileText, format, maxOperations) {
3401
+ var rawOperations = format === 'ndjson' ? parseNdjsonOperations(fileText) : parseJsonArrayOperations(fileText);
3402
+ if (rawOperations.length === 0) {
3403
+ throw new Error('batch-execute: the operations file contained no operations.');
3404
+ }
3405
+ if (rawOperations.length > maxOperations) {
3406
+ throw new Error("batch-execute: ".concat(rawOperations.length, " operations exceed the limit of ").concat(maxOperations, "."));
3407
+ }
3408
+ return rawOperations.map(function(value, index) {
3409
+ return validateOperation(value, index);
3410
+ });
3411
+ }
3412
+ function parseNdjsonOperations(fileText) {
3413
+ var operations = [];
3414
+ fileText.split('\n').forEach(function(line, lineIndex) {
3415
+ var trimmed = line.trim();
3416
+ if (trimmed.length > 0) {
3417
+ try {
3418
+ operations.push(JSON.parse(trimmed));
3419
+ } catch (error) {
3420
+ throw new Error("batch-execute: invalid JSON on line ".concat(lineIndex + 1, ": ").concat(error.message), {
3421
+ cause: error
3422
+ });
3423
+ }
3424
+ }
3425
+ });
3426
+ return operations;
3427
+ }
3428
+ function parseJsonArrayOperations(fileText) {
3429
+ var parsed;
3430
+ try {
3431
+ parsed = JSON.parse(fileText);
3432
+ } catch (error) {
3433
+ throw new Error("batch-execute: file is not valid JSON: ".concat(error.message), {
3434
+ cause: error
3435
+ });
3436
+ }
3437
+ if (!Array.isArray(parsed)) {
3438
+ throw new Error('batch-execute: with format "json" the file must contain a top-level array of operations.');
3439
+ }
3440
+ return parsed;
3441
+ }
3442
+ function validateOperation(value, index) {
3443
+ if (value == null || (typeof value === "undefined" ? "undefined" : _type_of$1(value)) !== 'object') {
3444
+ throw new Error("batch-execute: operation[".concat(index, "] must be an object."));
3445
+ }
3446
+ var candidate = value;
3447
+ var modelType = candidate.modelType;
3448
+ var call = candidate.call;
3449
+ var specifier = candidate.specifier;
3450
+ if (typeof modelType !== 'string' || modelType.length === 0) {
3451
+ throw new Error("batch-execute: operation[".concat(index, '] is missing a non-empty "modelType".'));
3452
+ }
3453
+ if (typeof call !== 'string' || call.length === 0) {
3454
+ throw new Error("batch-execute: operation[".concat(index, '] is missing a non-empty "call".'));
3455
+ }
3456
+ if (specifier != null && typeof specifier !== 'string') {
3457
+ throw new Error("batch-execute: operation[".concat(index, '] "specifier" must be a string when provided.'));
3458
+ }
3459
+ return {
3460
+ call: call,
3461
+ modelType: modelType,
3462
+ specifier: specifier !== null && specifier !== void 0 ? specifier : undefined,
3463
+ data: candidate.data
3464
+ };
3465
+ }
3466
+ function collectPreflightErrors(operations, authorizeOperation) {
3467
+ var errors = [];
3468
+ operations.forEach(function(operation, index) {
3469
+ var authorization = authorizeOperation(operation);
3470
+ if (!authorization.allowed) {
3471
+ var _operation_call;
3472
+ errors.push({
3473
+ index: index,
3474
+ modelType: operation.modelType,
3475
+ call: (_operation_call = operation.call) !== null && _operation_call !== void 0 ? _operation_call : undefined,
3476
+ specifier: operation.specifier,
3477
+ reason: authorization.reason
3478
+ });
3479
+ }
3480
+ });
3481
+ return errors;
3482
+ }
3483
+ function describePreflightErrors(errors) {
3484
+ var sample = errors.slice(0, 5).map(function(error) {
3485
+ var _error_call;
3486
+ var specifierSuffix = error.specifier ? "/".concat(error.specifier) : '';
3487
+ return "[".concat(error.index, "] ").concat((_error_call = error.call) !== null && _error_call !== void 0 ? _error_call : '?', "/").concat(error.modelType).concat(specifierSuffix, ": ").concat(error.reason);
3488
+ }).join('; ');
3489
+ var suffix = errors.length > 5 ? " (+".concat(errors.length - 5, " more)") : '';
3490
+ return "".concat(sample).concat(suffix);
3491
+ }
3492
+ function executeOperations(input) {
3493
+ return _async_to_generator$3(function() {
3494
+ return _ts_generator$3(this, function(_state) {
3495
+ return [
3496
+ 2,
3497
+ input.input.stopOnError ? executeSequentialStopOnError(input) : executeBestEffort(input)
3498
+ ];
3499
+ });
3500
+ })();
3501
+ }
3502
+ function executeBestEffort(input) {
3503
+ return _async_to_generator$3(function() {
3504
+ var operations, ctx, dispatch, indices, taskResult, errors;
3505
+ return _ts_generator$3(this, function(_state) {
3506
+ switch(_state.label){
3507
+ case 0:
3508
+ operations = input.operations, ctx = input.ctx, dispatch = input.dispatch;
3509
+ indices = operations.map(function(_, index) {
3510
+ return index;
3511
+ });
3512
+ return [
3513
+ 4,
3514
+ performAsyncTasks(indices, function(index) {
3515
+ return _async_to_generator$3(function() {
3516
+ var entry, error;
3517
+ return _ts_generator$3(this, function(_state) {
3518
+ switch(_state.label){
3519
+ case 0:
3520
+ _state.trys.push([
3521
+ 0,
3522
+ 2,
3523
+ ,
3524
+ 3
3525
+ ]);
3526
+ return [
3527
+ 4,
3528
+ dispatch(operations[index], ctx.auth, ctx.rawRequest)
3529
+ ];
3530
+ case 1:
3531
+ _state.sent();
3532
+ return [
3533
+ 3,
3534
+ 3
3535
+ ];
3536
+ case 2:
3537
+ error = _state.sent();
3538
+ entry = toErrorEntry(operations[index], index, error);
3539
+ return [
3540
+ 3,
3541
+ 3
3542
+ ];
3543
+ case 3:
3544
+ return [
3545
+ 2,
3546
+ entry
3547
+ ];
3548
+ }
3549
+ });
3550
+ })();
3551
+ }, {
3552
+ throwError: false,
3553
+ maxParallelTasks: input.input.maxParallel
3554
+ })
3555
+ ];
3556
+ case 1:
3557
+ taskResult = _state.sent();
3558
+ errors = taskResult.results.map(function(param) {
3559
+ var _param = _sliced_to_array$1(param, 2), entry = _param[1];
3560
+ return entry;
3561
+ }).filter(function(entry) {
3562
+ return entry != null;
3563
+ }).sort(function(a, b) {
3564
+ return a.index - b.index;
3565
+ });
3566
+ return [
3567
+ 2,
3568
+ {
3569
+ succeededCount: operations.length - errors.length,
3570
+ errors: errors
3571
+ }
3572
+ ];
3573
+ }
3574
+ });
3575
+ })();
3576
+ }
3577
+ function executeSequentialStopOnError(input) {
3578
+ return _async_to_generator$3(function() {
3579
+ var operations, ctx, dispatch, errors, succeededCount, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _step_value, index, operation, error, err;
3580
+ return _ts_generator$3(this, function(_state) {
3581
+ switch(_state.label){
3582
+ case 0:
3583
+ operations = input.operations, ctx = input.ctx, dispatch = input.dispatch;
3584
+ errors = [];
3585
+ succeededCount = 0;
3586
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3587
+ _state.label = 1;
3588
+ case 1:
3589
+ _state.trys.push([
3590
+ 1,
3591
+ 8,
3592
+ 9,
3593
+ 10
3594
+ ]);
3595
+ _iterator = operations.entries()[Symbol.iterator]();
3596
+ _state.label = 2;
3597
+ case 2:
3598
+ if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [
3599
+ 3,
3600
+ 7
3601
+ ];
3602
+ _step_value = _sliced_to_array$1(_step.value, 2), index = _step_value[0], operation = _step_value[1];
3603
+ _state.label = 3;
3604
+ case 3:
3605
+ _state.trys.push([
3606
+ 3,
3607
+ 5,
3608
+ ,
3609
+ 6
3610
+ ]);
3611
+ return [
3612
+ 4,
3613
+ dispatch(operation, ctx.auth, ctx.rawRequest)
3614
+ ];
3615
+ case 4:
3616
+ _state.sent();
3617
+ succeededCount += 1;
3618
+ return [
3619
+ 3,
3620
+ 6
3621
+ ];
3622
+ case 5:
3623
+ error = _state.sent();
3624
+ errors.push(toErrorEntry(operation, index, error));
3625
+ return [
3626
+ 3,
3627
+ 7
3628
+ ];
3629
+ case 6:
3630
+ _iteratorNormalCompletion = true;
3631
+ return [
3632
+ 3,
3633
+ 2
3634
+ ];
3635
+ case 7:
3636
+ return [
3637
+ 3,
3638
+ 10
3639
+ ];
3640
+ case 8:
3641
+ err = _state.sent();
3642
+ _didIteratorError = true;
3643
+ _iteratorError = err;
3644
+ return [
3645
+ 3,
3646
+ 10
3647
+ ];
3648
+ case 9:
3649
+ try {
3650
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
3651
+ _iterator.return();
3652
+ }
3653
+ } finally{
3654
+ if (_didIteratorError) {
3655
+ throw _iteratorError;
3656
+ }
3657
+ }
3658
+ return [
3659
+ 7
3660
+ ];
3661
+ case 10:
3662
+ return [
3663
+ 2,
3664
+ {
3665
+ succeededCount: succeededCount,
3666
+ errors: errors
3667
+ }
3668
+ ];
3669
+ }
3670
+ });
3671
+ })();
3672
+ }
3673
+ function toErrorEntry(operation, index, error) {
3674
+ var _operation_call;
3675
+ return {
3676
+ index: index,
3677
+ modelType: operation.modelType,
3678
+ call: (_operation_call = operation.call) !== null && _operation_call !== void 0 ? _operation_call : undefined,
3679
+ specifier: operation.specifier,
3680
+ message: _instanceof(error, Error) ? error.message : String(error)
3681
+ };
3682
+ }
3683
+ function maybeDeleteUpload(input) {
3684
+ return _async_to_generator$3(function() {
3685
+ var deleted, file;
3686
+ return _ts_generator$3(this, function(_state) {
3687
+ switch(_state.label){
3688
+ case 0:
3689
+ deleted = false;
3690
+ if (!(input.input.deleteUploadOnSuccess && input.failureCount === 0)) return [
3691
+ 3,
3692
+ 2
3693
+ ];
3694
+ file = input.storageService.file(toStoragePathInput(input.input.uploadPath, input.input.bucketId));
3695
+ return [
3696
+ 4,
3697
+ file.delete()
3698
+ ];
3699
+ case 1:
3700
+ _state.sent();
3701
+ deleted = true;
3702
+ _state.label = 2;
3703
+ case 2:
3704
+ return [
3705
+ 2,
3706
+ deleted
3707
+ ];
2456
3708
  }
2457
- }
2458
- }
3709
+ });
3710
+ })();
3711
+ }
3712
+ /**
3713
+ * Builds a {@link StoragePathInput} from a path + optional bucket. Omits `bucketId` entirely when
3714
+ * absent (rather than passing `undefined`) so the accessor falls back to the app default bucket.
3715
+ *
3716
+ * @param pathString - The slash path of the file within the bucket.
3717
+ * @param bucketId - The bucket id, or `undefined` for the default bucket.
3718
+ * @returns The resolved storage path input.
3719
+ */ function toStoragePathInput(pathString, bucketId) {
3720
+ return bucketId != null ? {
3721
+ bucketId: bucketId,
3722
+ pathString: pathString
3723
+ } : {
3724
+ pathString: pathString
3725
+ };
2459
3726
  }
2460
3727
  // MARK: Schemas
2461
- var WHOAMI_INPUT_SCHEMA = {
2462
- type: 'object',
2463
- properties: {},
2464
- additionalProperties: false
2465
- };
2466
- var WHOAMI_OUTPUT_SCHEMA = {
3728
+ var BATCH_EXECUTE_INPUT_SCHEMA = {
2467
3729
  type: 'object',
2468
3730
  required: [
2469
- 'authenticated',
2470
- 'claims',
2471
- 'roles',
2472
- 'claimDetails',
2473
- 'unknownClaimKeys'
3731
+ 'uploadPath'
2474
3732
  ],
2475
- description: 'Caller identity, raw token claims, decoded role set, and per-claim descriptions for every key present on the live token.',
2476
3733
  properties: {
2477
- authenticated: {
2478
- type: 'boolean'
3734
+ uploadPath: {
3735
+ type: 'string',
3736
+ minLength: 1,
3737
+ description: 'Storage path of the uploaded operations file (JSON array or NDJSON).'
2479
3738
  },
2480
- uid: {
2481
- type: 'string'
3739
+ bucketId: {
3740
+ type: 'string',
3741
+ minLength: 1,
3742
+ description: 'Storage bucket id. Defaults to the app default bucket when omitted.'
2482
3743
  },
2483
- email: {
2484
- type: 'string'
3744
+ format: {
3745
+ type: 'string',
3746
+ enum: [
3747
+ 'json',
3748
+ 'ndjson'
3749
+ ],
3750
+ description: 'File format. "ndjson" (default) = one operation per line; "json" = a top-level array.'
2485
3751
  },
2486
- emailVerified: {
2487
- type: 'boolean'
3752
+ maxParallel: {
3753
+ type: 'integer',
3754
+ minimum: 1,
3755
+ description: "Operations dispatched concurrently. Defaults to ".concat(DEFAULT_BATCH_EXECUTE_MAX_PARALLEL, ". Ignored (forced to sequential) when stopOnError is true.")
2488
3756
  },
2489
- app: {
2490
- type: 'string'
3757
+ stopOnError: {
3758
+ type: 'boolean',
3759
+ description: 'Halt at the first runtime failure (runs sequentially). Defaults to false (best-effort; all operations attempted).'
2491
3760
  },
2492
- claims: {
2493
- type: 'object',
2494
- additionalProperties: true
3761
+ deleteUploadOnSuccess: {
3762
+ type: 'boolean',
3763
+ description: 'Delete the uploaded file when every operation succeeds. Defaults to true. The file is always kept when any operation fails.'
3764
+ }
3765
+ },
3766
+ additionalProperties: false
3767
+ };
3768
+ var BATCH_EXECUTE_OUTPUT_SCHEMA = {
3769
+ type: 'object',
3770
+ required: [
3771
+ 'total',
3772
+ 'successCount',
3773
+ 'failureCount',
3774
+ 'skippedCount',
3775
+ 'errors',
3776
+ 'uploadDeleted'
3777
+ ],
3778
+ properties: {
3779
+ total: {
3780
+ type: 'integer',
3781
+ description: 'Total operations in the file.'
2495
3782
  },
2496
- roles: {
2497
- type: 'array',
2498
- items: {
2499
- type: 'string'
2500
- }
3783
+ successCount: {
3784
+ type: 'integer',
3785
+ description: 'Operations that succeeded.'
2501
3786
  },
2502
- claimDetails: {
3787
+ failureCount: {
3788
+ type: 'integer',
3789
+ description: 'Operations that failed at runtime.'
3790
+ },
3791
+ skippedCount: {
3792
+ type: 'integer',
3793
+ description: 'Operations not attempted (only non-zero when stopOnError halted the run early).'
3794
+ },
3795
+ errors: {
2503
3796
  type: 'array',
3797
+ description: 'Per-operation runtime failures, keyed by original file index.',
2504
3798
  items: {
2505
3799
  type: 'object',
2506
3800
  required: [
2507
- 'key',
2508
- 'description',
2509
- 'grantedRoles',
2510
- 'inverse',
2511
- 'tags'
3801
+ 'index',
3802
+ 'modelType',
3803
+ 'message'
2512
3804
  ],
2513
3805
  properties: {
2514
- key: {
2515
- type: 'string'
3806
+ index: {
3807
+ type: 'integer'
2516
3808
  },
2517
- interfaceName: {
3809
+ modelType: {
2518
3810
  type: 'string'
2519
3811
  },
2520
- description: {
3812
+ call: {
2521
3813
  type: 'string'
2522
3814
  },
2523
- grantedRoles: {
2524
- type: 'array',
2525
- items: {
2526
- type: 'string'
2527
- }
2528
- },
2529
- inverse: {
2530
- type: 'boolean'
3815
+ specifier: {
3816
+ type: 'string'
2531
3817
  },
2532
- tags: {
2533
- type: 'array',
2534
- items: {
2535
- type: 'string'
2536
- }
3818
+ message: {
3819
+ type: 'string'
2537
3820
  }
2538
3821
  }
2539
3822
  }
2540
3823
  },
2541
- unknownClaimKeys: {
2542
- type: 'array',
2543
- items: {
2544
- type: 'string'
2545
- }
3824
+ uploadDeleted: {
3825
+ type: 'boolean',
3826
+ description: 'Whether the uploaded operations file was deleted after the run.'
2546
3827
  }
2547
3828
  }
2548
3829
  };
@@ -2773,12 +4054,13 @@ var McpServerFactoryService_1;
2773
4054
  * recommends a fresh `McpServer` + transport pair per stateless JSON-RPC request,
2774
4055
  * which sidesteps session bookkeeping for the common Claude-connector case.
2775
4056
  */ var McpServerFactoryService = McpServerFactoryService_1 = /*#__PURE__*/ function() {
2776
- function McpServerFactoryService(mcpConfig, dispatchService, modelApiGetService, roleReader, analyticsService) {
4057
+ function McpServerFactoryService(mcpConfig, dispatchService, modelApiGetService, roleReader, analyticsService, storageService) {
2777
4058
  _class_call_check$3(this, McpServerFactoryService);
2778
4059
  _define_property$3(this, "mcpConfig", void 0);
2779
4060
  _define_property$3(this, "dispatchService", void 0);
2780
4061
  _define_property$3(this, "modelApiGetService", void 0);
2781
4062
  _define_property$3(this, "roleReader", void 0);
4063
+ _define_property$3(this, "storageService", void 0);
2782
4064
  _define_property$3(this, "_logger", new Logger(McpServerFactoryService_1.name));
2783
4065
  _define_property$3(this, "_cachedTools", void 0);
2784
4066
  _define_property$3(this, "_cachedStaticTools", void 0);
@@ -2793,6 +4075,7 @@ var McpServerFactoryService_1;
2793
4075
  this.dispatchService = dispatchService;
2794
4076
  this.modelApiGetService = modelApiGetService;
2795
4077
  this.roleReader = roleReader;
4078
+ this.storageService = storageService;
2796
4079
  this._analyticsService = analyticsService !== null && analyticsService !== void 0 ? analyticsService : noopMcpAnalyticsService();
2797
4080
  }
2798
4081
  _create_class$3(McpServerFactoryService, [
@@ -2833,6 +4116,16 @@ var McpServerFactoryService_1;
2833
4116
  tool
2834
4117
  ];
2835
4118
  }));
4119
+ // The batch tool is request-scoped: it authorizes each batched operation against this caller's
4120
+ // resolved visible tool set, so it can't be cached like the other static tools.
4121
+ var batchTool = this._resolveBatchTool(ctx, definitionsByName);
4122
+ var listedTools = visibleTools;
4123
+ if (batchTool != null) {
4124
+ definitionsByName.set(batchTool.name, batchTool);
4125
+ listedTools = _to_consumable_array$2(visibleTools).concat([
4126
+ batchTool
4127
+ ]);
4128
+ }
2836
4129
  server.server.setRequestHandler(ListToolsRequestSchema, function() {
2837
4130
  return _async_to_generator$2(function() {
2838
4131
  var _this;
@@ -2841,7 +4134,7 @@ var McpServerFactoryService_1;
2841
4134
  return [
2842
4135
  2,
2843
4136
  {
2844
- tools: visibleTools.map(function(tool) {
4137
+ tools: listedTools.map(function(tool) {
2845
4138
  return _this._buildToolListEntry(tool, ctx, scopes);
2846
4139
  })
2847
4140
  }
@@ -2880,21 +4173,84 @@ var McpServerFactoryService_1;
2880
4173
  result = {
2881
4174
  tools: [],
2882
4175
  neverVisibleTools: [],
2883
- skipped: []
4176
+ skipped: [],
4177
+ warnings: []
2884
4178
  };
2885
4179
  } else {
2886
- result = generateMcpToolDefinitions(apiDetails, undefined, manifest);
4180
+ result = generateMcpToolDefinitions(apiDetails, undefined, {
4181
+ manifest: manifest,
4182
+ naming: this._resolveToolNamingOptions()
4183
+ });
2887
4184
  }
2888
4185
  this._cachedTools = result;
2889
4186
  }
2890
- if (!this._loggedSkips && result.skipped.length > 0) {
4187
+ if (!this._loggedSkips && (result.skipped.length > 0 || result.warnings.length > 0)) {
2891
4188
  this._loggedSkips = true;
2892
4189
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2893
4190
  try {
2894
4191
  for(var _iterator = result.skipped[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2895
4192
  var skip = _step.value;
2896
- var errorSuffix = skip.error ? ": ".concat(skip.error.message) : '';
2897
- this._logger.warn("Skipped MCP tool ".concat(skip.toolName, " (").concat(skip.reason, ")").concat(errorSuffix));
4193
+ this._logSkip(skip);
4194
+ }
4195
+ } catch (err) {
4196
+ _didIteratorError = true;
4197
+ _iteratorError = err;
4198
+ } finally{
4199
+ try {
4200
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
4201
+ _iterator.return();
4202
+ }
4203
+ } finally{
4204
+ if (_didIteratorError) {
4205
+ throw _iteratorError;
4206
+ }
4207
+ }
4208
+ }
4209
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
4210
+ try {
4211
+ for(var _iterator1 = result.warnings[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
4212
+ var warning = _step1.value;
4213
+ this._logger.warn(this._describeToolGenerationWarning(warning));
4214
+ }
4215
+ } catch (err) {
4216
+ _didIteratorError1 = true;
4217
+ _iteratorError1 = err;
4218
+ } finally{
4219
+ try {
4220
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
4221
+ _iterator1.return();
4222
+ }
4223
+ } finally{
4224
+ if (_didIteratorError1) {
4225
+ throw _iteratorError1;
4226
+ }
4227
+ }
4228
+ }
4229
+ }
4230
+ return result;
4231
+ }
4232
+ },
4233
+ {
4234
+ /**
4235
+ * Builds the per-model tool-name segment overrides from the loaded manifest's `models` catalog.
4236
+ *
4237
+ * Sourcing the segments from the manifest (rather than runtime config) keeps the runtime in
4238
+ * agreement with the build-time manifest validation, which reads the same `mcpToolNameSegment`.
4239
+ *
4240
+ * @returns Naming options carrying the segment map, or `undefined` when no model declares one.
4241
+ */ key: "_resolveToolNamingOptions",
4242
+ value: function _resolveToolNamingOptions() {
4243
+ var models = this._cachedManifestModels;
4244
+ var result;
4245
+ if (models != null && models.length > 0) {
4246
+ var modelSegments = new Map();
4247
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
4248
+ try {
4249
+ for(var _iterator = models[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
4250
+ var model = _step.value;
4251
+ if (model.mcpToolNameSegment != null && model.mcpToolNameSegment.length > 0) {
4252
+ modelSegments.set(model.modelType, model.mcpToolNameSegment);
4253
+ }
2898
4254
  }
2899
4255
  } catch (err) {
2900
4256
  _didIteratorError = true;
@@ -2910,6 +4266,47 @@ var McpServerFactoryService_1;
2910
4266
  }
2911
4267
  }
2912
4268
  }
4269
+ if (modelSegments.size > 0) {
4270
+ result = {
4271
+ modelSegments: modelSegments
4272
+ };
4273
+ }
4274
+ }
4275
+ return result;
4276
+ }
4277
+ },
4278
+ {
4279
+ /**
4280
+ * Logs one skipped tool at the appropriate level: name-cap and collision skips are errors (they
4281
+ * would otherwise break or shadow tools on the wire), the rest are warnings.
4282
+ *
4283
+ * @param skip - The skipped-tool report to log.
4284
+ */ key: "_logSkip",
4285
+ value: function _logSkip(skip) {
4286
+ var errorSuffix = skip.error ? ": ".concat(skip.error.message) : '';
4287
+ if (skip.reason === 'name_too_long') {
4288
+ 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."));
4289
+ } else if (skip.reason === 'duplicate_name') {
4290
+ 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."));
4291
+ } else {
4292
+ this._logger.warn("Skipped MCP tool ".concat(skip.toolName, " (").concat(skip.reason, ")").concat(errorSuffix));
4293
+ }
4294
+ }
4295
+ },
4296
+ {
4297
+ /**
4298
+ * Renders a human-readable boot-time warning for an MCP-result mapping inconsistency between a
4299
+ * handler's `mapSuccessfulResult` and the build-time manifest.
4300
+ *
4301
+ * @param warning - The tool-generation warning to describe.
4302
+ * @returns The log line to emit at startup.
4303
+ */ key: "_describeToolGenerationWarning",
4304
+ value: function _describeToolGenerationWarning(warning) {
4305
+ var result;
4306
+ if (warning.reason === 'mapper_without_mapped_manifest') {
4307
+ 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.");
4308
+ } else {
4309
+ 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.");
2913
4310
  }
2914
4311
  return result;
2915
4312
  }
@@ -2998,6 +4395,92 @@ var McpServerFactoryService_1;
2998
4395
  },
2999
4396
  {
3000
4397
  /**
4398
+ * Builds the per-request `batch-execute` tool, or `undefined` when it should not be offered.
4399
+ *
4400
+ * Unlike the cached static tools this is rebuilt per request, because it closes over the caller's
4401
+ * resolved visible tool set: each batched operation is authorized against exactly the callModel
4402
+ * tools this caller may invoke directly. Offered only when a storage service is wired (it reads
4403
+ * the uploaded operations file), the caller is authenticated, and the server is not in read-only
4404
+ * mode (the tool performs writes).
4405
+ *
4406
+ * @param ctx - The per-request context (auth, raw request) forwarded to dispatched operations.
4407
+ * @param definitionsByName - The caller's visible tool definitions, keyed by name.
4408
+ * @returns The batch tool definition, or `undefined` when unavailable for this request.
4409
+ */ key: "_resolveBatchTool",
4410
+ value: function _resolveBatchTool(ctx, definitionsByName) {
4411
+ var _this = this;
4412
+ var result;
4413
+ var storageService = this.storageService;
4414
+ if (storageService != null && ctx.auth != null && this.mcpConfig.readOnly !== true) {
4415
+ var authorizedCoords = new Set();
4416
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
4417
+ try {
4418
+ for(var _iterator = definitionsByName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
4419
+ var definition = _step.value;
4420
+ // Only callModel tools are re-dispatchable; static tools (model-get, whoami) have synthetic
4421
+ // dispatch identities the callModel chain can't resolve.
4422
+ if (definition.staticHandler == null) {
4423
+ authorizedCoords.add(batchOperationCoordKey(definition.dispatch));
4424
+ }
4425
+ }
4426
+ } catch (err) {
4427
+ _didIteratorError = true;
4428
+ _iteratorError = err;
4429
+ } finally{
4430
+ try {
4431
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
4432
+ _iterator.return();
4433
+ }
4434
+ } finally{
4435
+ if (_didIteratorError) {
4436
+ throw _iteratorError;
4437
+ }
4438
+ }
4439
+ }
4440
+ result = createBatchExecuteTool({
4441
+ storageService: storageService,
4442
+ dispatch: function dispatch(operation, auth, rawRequest) {
4443
+ return _this.dispatchService.dispatch(operation, auth, rawRequest);
4444
+ },
4445
+ authorizeOperation: function authorizeOperation(operation) {
4446
+ return _this._authorizeBatchOperation(operation, authorizedCoords);
4447
+ }
4448
+ });
4449
+ }
4450
+ return result;
4451
+ }
4452
+ },
4453
+ {
4454
+ /**
4455
+ * Authorizes a single batched operation against the caller's visible callModel tool coordinates.
4456
+ *
4457
+ * Re-applies the same gate the `tools/list` filter already enforced: an operation is allowed only
4458
+ * when its `(call, modelType, specifier)` coordinate matches a tool this caller can see. This is
4459
+ * the critical guard — `dispatchService.dispatch` bypasses the MCP visibility filter, so without
4460
+ * this check a batch could reach scope-, role-, or read-only-gated handlers.
4461
+ *
4462
+ * @param operation - The operation to authorize.
4463
+ * @param authorizedCoords - The set of dispatchable coordinate keys visible to this caller.
4464
+ * @returns Whether the operation may be dispatched, with a reason when it may not.
4465
+ */ key: "_authorizeBatchOperation",
4466
+ value: function _authorizeBatchOperation(operation, authorizedCoords) {
4467
+ var _operation_call, _operation_specifier, _operation_call1;
4468
+ var coordKey = batchOperationCoordKey({
4469
+ call: (_operation_call = operation.call) !== null && _operation_call !== void 0 ? _operation_call : '',
4470
+ modelType: operation.modelType,
4471
+ specifier: (_operation_specifier = operation.specifier) !== null && _operation_specifier !== void 0 ? _operation_specifier : undefined
4472
+ });
4473
+ var specifierSuffix = operation.specifier ? ' specifier="'.concat(operation.specifier, '"') : '';
4474
+ return authorizedCoords.has(coordKey) ? {
4475
+ allowed: true
4476
+ } : {
4477
+ allowed: false,
4478
+ reason: 'no visible tool matches call="'.concat((_operation_call1 = operation.call) !== null && _operation_call1 !== void 0 ? _operation_call1 : '', '" modelType="').concat(operation.modelType, '"').concat(specifierSuffix, " (unknown, or hidden by scope/role/read-only).")
4479
+ };
4480
+ }
4481
+ },
4482
+ {
4483
+ /**
3001
4484
  * Reads the pre-rendered MCP manifest JSON once, validates its version, and caches the
3002
4485
  * resulting `key → entry` map plus the optional `models` catalog for the process lifetime.
3003
4486
  *
@@ -3422,7 +4905,7 @@ var McpServerFactoryService_1;
3422
4905
  key: "_handleCallModelToolCall",
3423
4906
  value: function _handleCallModelToolCall(definition, args, ctx) {
3424
4907
  return _async_to_generator$2(function() {
3425
- var params, outcome, result, error;
4908
+ var params, outcome, result, _tmp, error;
3426
4909
  return _ts_generator$2(this, function(_state) {
3427
4910
  switch(_state.label){
3428
4911
  case 0:
@@ -3436,9 +4919,9 @@ var McpServerFactoryService_1;
3436
4919
  case 1:
3437
4920
  _state.trys.push([
3438
4921
  1,
3439
- 3,
4922
+ 4,
3440
4923
  ,
3441
- 4
4924
+ 5
3442
4925
  ]);
3443
4926
  return [
3444
4927
  4,
@@ -3446,14 +4929,18 @@ var McpServerFactoryService_1;
3446
4929
  ];
3447
4930
  case 2:
3448
4931
  result = _state.sent();
3449
- outcome = {
3450
- response: formatMcpToolResponse(result, params, definition.details)
3451
- };
4932
+ _tmp = {};
3452
4933
  return [
3453
- 3,
3454
- 4
4934
+ 4,
4935
+ formatMcpToolResponse(result, params, definition.details)
3455
4936
  ];
3456
4937
  case 3:
4938
+ outcome = (_tmp.response = _state.sent(), _tmp);
4939
+ return [
4940
+ 3,
4941
+ 5
4942
+ ];
4943
+ case 4:
3457
4944
  error = _state.sent();
3458
4945
  outcome = {
3459
4946
  response: formatMcpToolErrorResponse(error),
@@ -3461,9 +4948,9 @@ var McpServerFactoryService_1;
3461
4948
  };
3462
4949
  return [
3463
4950
  3,
3464
- 4
4951
+ 5
3465
4952
  ];
3466
- case 4:
4953
+ case 5:
3467
4954
  return [
3468
4955
  2,
3469
4956
  outcome
@@ -3485,7 +4972,9 @@ McpServerFactoryService = McpServerFactoryService_1 = __decorate([
3485
4972
  __param(3, Optional()),
3486
4973
  __param(3, Inject(MCP_AUTH_ROLE_READER)),
3487
4974
  __param(4, Optional()),
3488
- __param(4, Inject(MCP_ANALYTICS_SERVICE))
4975
+ __param(4, Inject(MCP_ANALYTICS_SERVICE)),
4976
+ __param(5, Optional()),
4977
+ __param(5, Inject(FirebaseServerStorageService))
3489
4978
  ], McpServerFactoryService);
3490
4979
 
3491
4980
  function asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, key, arg) {
@@ -4277,4 +5766,4 @@ function _unsupported_iterable_to_array(o, minLen) {
4277
5766
  };
4278
5767
  }
4279
5768
 
4280
- export { DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MODEL_DECODE_DISPATCH_CALL, MODEL_DECODE_DISPATCH_MODEL_TYPE, MODEL_DECODE_TOOL_NAME, MODEL_GET_DISPATCH_CALL, MODEL_GET_DISPATCH_MODEL_TYPE, MODEL_GET_TOOL_NAME, MODEL_INFO_DISPATCH_CALL, MODEL_INFO_DISPATCH_MODEL_TYPE, MODEL_INFO_GROUPS_HINT, MODEL_INFO_TOOL_NAME, MODEL_INFO_UNGROUPED_LABEL, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, appMcpAnalyticsModuleMetadata, buildDefaultMcpToolDescription, buildMcpToolName, buildStaticWireEntry, classifyVisibility, createModelDecodeTool, createModelGetTool, createModelInfoTool, decodeFirestoreModelKey, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, mcpManifestKey, mcpModuleMetadata, noopMcpAnalyticsService, resolveEffectiveReadOnly, resolveRequiredScope };
5769
+ export { BATCH_EXECUTE_DISPATCH_CALL, BATCH_EXECUTE_DISPATCH_MODEL_TYPE, BATCH_EXECUTE_TOOL_NAME, DEFAULT_BATCH_EXECUTE_MAX_PARALLEL, DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS, DEFAULT_MCP_ANALYTICS_EVENT_NAME, DEFAULT_MCP_PATH, DEFAULT_MCP_SERVER_INSTRUCTIONS, DEFAULT_MCP_SERVER_NAME, DEFAULT_SPECIFIER_KEY, DEFAULT_VOID_MCP_SUCCESS_VALUE, FIREBASE_SERVER_MCP_ANALYTICS_CONFIG, FIREBASE_SERVER_MCP_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerMcpAnalyticsService, MAX_BATCH_EXECUTE_FILE_BYTES, MAX_BATCH_EXECUTE_OPERATIONS, MCP_ANALYTICS_SERVICE, MCP_AUTH_ROLE_READER, MCP_CALL_TYPE_ABBREVIATIONS, MCP_MANIFEST_VERSION, MCP_MODEL_GET_BATCH_SIZE, MCP_TOOL_NAME_MAX_LENGTH, MCP_TOOL_NAME_WARN_LENGTH, MODEL_DECODE_DISPATCH_CALL, MODEL_DECODE_DISPATCH_MODEL_TYPE, MODEL_DECODE_TOOL_NAME, MODEL_GET_DISPATCH_CALL, MODEL_GET_DISPATCH_MODEL_TYPE, MODEL_GET_TOOL_NAME, MODEL_INFO_DISPATCH_CALL, MODEL_INFO_DISPATCH_MODEL_TYPE, MODEL_INFO_GROUPS_HINT, MODEL_INFO_TOOL_NAME, MODEL_INFO_UNGROUPED_LABEL, McpAnalyticsService, McpController, McpModuleConfig, McpServerFactoryService, McpWellKnownController, abbreviateMcpCallType, appMcpAnalyticsModuleMetadata, batchOperationCoordKey, buildDefaultMcpToolDescription, buildDisambiguatedMcpToolName, buildMcpToolName, buildStaticWireEntry, classifyVisibility, createBatchExecuteTool, createModelDecodeTool, createModelGetTool, createModelInfoTool, decodeFirestoreModelKey, findModelEntry, formatMcpToolErrorResponse, formatMcpToolResponse, generateMcpToolDefinitions, handleStreamableHttpMcpRequest, mcpManifestKey, mcpModuleMetadata, noopMcpAnalyticsService, resolveEffectiveReadOnly, resolveRequiredScope, validateMcpToolName };