@dereekb/firebase-server 13.36.0 → 13.38.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.
Files changed (45) hide show
  1. package/calcom/package.json +10 -10
  2. package/discord/package.json +10 -10
  3. package/index.cjs.js +1732 -510
  4. package/index.esm.js +1717 -513
  5. package/mailgun/package.json +9 -9
  6. package/mcp/index.cjs.js +913 -167
  7. package/mcp/index.esm.js +906 -166
  8. package/mcp/package.json +13 -12
  9. package/mcp/src/lib/controller/mcp.controller.d.ts +28 -3
  10. package/mcp/src/lib/controller/mcp.wellknown.controller.d.ts +21 -1
  11. package/mcp/src/lib/mcp.config.d.ts +31 -8
  12. package/mcp/src/lib/mcp.module.d.ts +2 -1
  13. package/mcp/src/lib/service/index.d.ts +1 -0
  14. package/mcp/src/lib/service/mcp.server.factory.d.ts +8 -6
  15. package/mcp/src/lib/service/mcp.tool-generator.d.ts +1 -1
  16. package/mcp/src/lib/service/mcp.visibility.d.ts +1 -1
  17. package/mcp/src/lib/service/tools/mcp.tool.model-roles.d.ts +95 -0
  18. package/mcp/src/lib/transport/streamable-http.transport.d.ts +16 -11
  19. package/model/index.cjs.js +1 -1
  20. package/model/index.esm.js +1 -1
  21. package/model/package.json +9 -9
  22. package/oidc/index.cjs.js +257 -67
  23. package/oidc/index.esm.js +256 -69
  24. package/oidc/package.json +10 -10
  25. package/oidc/src/lib/profile.d.ts +21 -0
  26. package/oidc/src/lib/service/index.d.ts +1 -0
  27. package/oidc/src/lib/service/oidc.config.service.d.ts +14 -0
  28. package/oidc/src/lib/service/oidc.interaction-policy.d.ts +35 -0
  29. package/oidc/src/lib/service/oidc.service.d.ts +29 -0
  30. package/package.json +14 -13
  31. package/src/lib/env/env.service.d.ts +27 -3
  32. package/src/lib/nest/controller/api.scope.d.ts +63 -0
  33. package/src/lib/nest/controller/index.d.ts +2 -0
  34. package/src/lib/nest/controller/model/model.api.get.service.d.ts +146 -1
  35. package/src/lib/nest/controller/model/model.api.scope.d.ts +3 -2
  36. package/src/lib/nest/controller/session/index.d.ts +4 -0
  37. package/src/lib/nest/controller/session/session.api.config.d.ts +104 -0
  38. package/src/lib/nest/controller/session/session.api.controller.d.ts +26 -0
  39. package/src/lib/nest/controller/session/session.api.module.d.ts +49 -0
  40. package/src/lib/nest/controller/session/session.api.service.d.ts +71 -0
  41. package/test/index.cjs.js +8 -8
  42. package/test/index.esm.js +8 -8
  43. package/test/package.json +11 -11
  44. package/twilio/package.json +8 -8
  45. package/zoho/package.json +10 -10
package/mcp/index.cjs.js CHANGED
@@ -2,22 +2,21 @@
2
2
 
3
3
  var common = require('@nestjs/common');
4
4
  var node_fs = require('node:fs');
5
- var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
6
- var types_js = require('@modelcontextprotocol/sdk/types.js');
5
+ var server = require('@modelcontextprotocol/server');
7
6
  var firebase = require('@dereekb/firebase');
8
7
  var oidc = require('@dereekb/firebase-server/oidc');
9
8
  var util = require('@dereekb/util');
10
9
  var firebaseServer = require('@dereekb/firebase-server');
11
10
  var node_util = require('node:util');
12
11
  var model = require('@dereekb/model');
13
- var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
12
+ var node = require('@modelcontextprotocol/node');
14
13
 
15
14
  function _class_call_check$5(instance, Constructor) {
16
15
  if (!(instance instanceof Constructor)) {
17
16
  throw new TypeError("Cannot call a class as a function");
18
17
  }
19
18
  }
20
- function _define_property$9(obj, key, value) {
19
+ function _define_property$a(obj, key, value) {
21
20
  if (key in obj) {
22
21
  Object.defineProperty(obj, key, {
23
22
  value: value,
@@ -88,7 +87,7 @@ function _define_property$9(obj, key, value) {
88
87
  * discovery document. Claude custom-connector reads this to discover the OIDC issuer.
89
88
  *
90
89
  * @example 'https://api.example.com/oidc'
91
- */ _define_property$9(this, "oidcIssuer", void 0);
90
+ */ _define_property$a(this, "oidcIssuer", void 0);
92
91
  /**
93
92
  * The canonical resource URL of the MCP endpoint.
94
93
  *
@@ -96,17 +95,18 @@ function _define_property$9(obj, key, value) {
96
95
  * and used as the audience the access token's `aud` claim should match.
97
96
  *
98
97
  * @example 'https://api.example.com/mcp'
99
- */ _define_property$9(this, "mcpUrl", void 0);
98
+ */ _define_property$a(this, "mcpUrl", void 0);
100
99
  /**
101
100
  * Optional filter applied to the OIDC provider's scope list when building the
102
101
  * protected-resource discovery document's `scopes_supported` (RFC 9728 §2).
103
102
  *
104
103
  * The base list is pulled automatically from the OIDC provider config via the
105
- * injected {@link OidcProviderConfigService.scopesSupported} (i.e.
106
- * `Object.keys(providerConfig.claims)`), so the MCP resource advertises exactly
107
- * the scopes the issuer supports without the app restating them. Provide this
108
- * only to narrow that set — it receives every scope the provider issues and
109
- * returns the subset to advertise. When unset, all of them are advertised.
104
+ * injected {@link OidcProviderConfigService.clientRequestableScopesSupported}, so
105
+ * the MCP resource advertises the scopes the issuer grants an arbitrary client
106
+ * without the app restating them. Scopes only an admin-assigned provider profile
107
+ * unlocks are already excluded — requesting one is fatal at the consent unlock
108
+ * gate. Provide this only to narrow the set further; it returns the subset to
109
+ * advertise. When unset, the whole base list is advertised.
110
110
  *
111
111
  * Advertising these matters because dynamic-registration MCP clients (the Claude
112
112
  * Code CLI) read `scopes_supported` to decide which scopes to request on the
@@ -114,19 +114,19 @@ function _define_property$9(obj, key, value) {
114
114
  * the OIDC provider nothing to grant and ending the interaction in `access_denied`.
115
115
  *
116
116
  * @example (allScopes) => allScopes.filter((scope) => scope === 'openid' || scope.startsWith('model.'))
117
- */ _define_property$9(this, "scopesSupported", void 0);
117
+ */ _define_property$a(this, "scopesSupported", void 0);
118
118
  /**
119
119
  * Optional name advertised on the MCP `initialize` handshake. Defaults to
120
120
  * {@link DEFAULT_MCP_SERVER_NAME}.
121
- */ _define_property$9(this, "serverName", void 0);
121
+ */ _define_property$a(this, "serverName", void 0);
122
122
  /**
123
123
  * Optional version advertised on the MCP `initialize` handshake.
124
- */ _define_property$9(this, "serverVersion", void 0);
124
+ */ _define_property$a(this, "serverVersion", void 0);
125
125
  /**
126
126
  * Optional override for the `instructions` string passed to `new McpServer(...)` and
127
127
  * advertised on the JSON-RPC `initialize` handshake. Defaults to
128
128
  * {@link DEFAULT_MCP_SERVER_INSTRUCTIONS}.
129
- */ _define_property$9(this, "serverInstructions", void 0);
129
+ */ _define_property$a(this, "serverInstructions", void 0);
130
130
  /**
131
131
  * Absolute path to a pre-rendered MCP manifest JSON file produced by
132
132
  * `dbx-cli-generate-mcp-manifest`. When set, the runtime reads it once at
@@ -136,7 +136,7 @@ function _define_property$9(obj, key, value) {
136
136
  * Optional. When unset or the file is missing, the runtime falls back to
137
137
  * today's behavior (auto-generated descriptions, ArkType-derived input
138
138
  * schemas, no output schemas) and emits a single boot warning.
139
- */ _define_property$9(this, "mcpManifestPath", void 0);
139
+ */ _define_property$a(this, "mcpManifestPath", void 0);
140
140
  /**
141
141
  * Absolute path to a pre-rendered route manifest JSON file produced by
142
142
  * `dbx-cli-generate-route-manifest`. When set, the runtime reads it once at
@@ -146,7 +146,7 @@ function _define_property$9(obj, key, value) {
146
146
  * Optional. When unset or the file is missing, the runtime skips registering
147
147
  * `url-models` and emits a single boot warning when the path was set but
148
148
  * unreadable.
149
- */ _define_property$9(this, "mcpRouteManifestPath", void 0);
149
+ */ _define_property$a(this, "mcpRouteManifestPath", void 0);
150
150
  /**
151
151
  * When `true`, the MCP server only advertises tools whose effective read-only classification is `true`.
152
152
  *
@@ -156,7 +156,7 @@ function _define_property$9(obj, key, value) {
156
156
  *
157
157
  * The advertised `serverName` on the JSON-RPC `initialize` handshake is suffixed with
158
158
  * ` (read-only)` so the client surface reflects the mode.
159
- */ _define_property$9(this, "readOnly", void 0);
159
+ */ _define_property$a(this, "readOnly", void 0);
160
160
  /**
161
161
  * Controls the auto-injected `reason` parameter added to every advertised tool's input schema.
162
162
  *
@@ -166,7 +166,7 @@ function _define_property$9(obj, key, value) {
166
166
  * When enabled, every advertised tool's `inputSchema` carries a required `reason` string the model
167
167
  * fills with a short justification for the call. The value is forwarded to analytics and stripped
168
168
  * from the dispatched handler body. See {@link McpReasonParameterConfig}.
169
- */ _define_property$9(this, "reasonParameter", void 0);
169
+ */ _define_property$a(this, "reasonParameter", void 0);
170
170
  /**
171
171
  * Default OIDC scope group term required on EVERY `callModel` op, mirrored into MCP tool-list
172
172
  * visibility, unless a finer term overrides it (a per-function `requiredScope`, then a
@@ -176,17 +176,20 @@ function _define_property$9(obj, key, value) {
176
176
  *
177
177
  * Set this to the SAME value passed to `ModelApiDispatchConfig.defaultRequiredScope` to
178
178
  * keep tool visibility and enforcement in lockstep.
179
- */ _define_property$9(this, "defaultRequiredScope", void 0);
179
+ */ _define_property$a(this, "defaultRequiredScope", void 0);
180
180
  /**
181
181
  * Per-model OIDC scope group-term overrides, keyed by {@link FirestoreModelType}, mirrored into MCP
182
182
  * tool-list visibility. Set this to the SAME value passed to
183
183
  * `ModelApiDispatchConfig.modelRequiredScopes` so per-model / verb-keyed restrictions that
184
184
  * confine a subset client apply identically to the advertised tool list.
185
- */ _define_property$9(this, "modelRequiredScopes", void 0);
185
+ */ _define_property$a(this, "modelRequiredScopes", void 0);
186
186
  };
187
187
  /**
188
188
  * NestJS injection token for the optional {@link McpAuthRoleReader} provider.
189
189
  */ var MCP_AUTH_ROLE_READER = 'MCP_AUTH_ROLE_READER';
190
+ /**
191
+ * NestJS injection token for the optional {@link McpModelRolesTargetUidPredicate} provider.
192
+ */ var MCP_MODEL_ROLES_TARGET_UID_PREDICATE = 'MCP_MODEL_ROLES_TARGET_UID_PREDICATE';
190
193
 
191
194
  function _type_of$5(obj) {
192
195
  "@swc/helpers - typeof";
@@ -216,7 +219,7 @@ function _array_like_to_array$b(arr, len) {
216
219
  function _array_without_holes$8(arr) {
217
220
  if (Array.isArray(arr)) return _array_like_to_array$b(arr);
218
221
  }
219
- function _define_property$8(obj, key, value) {
222
+ function _define_property$9(obj, key, value) {
220
223
  if (key in obj) {
221
224
  Object.defineProperty(obj, key, {
222
225
  value: value,
@@ -235,7 +238,7 @@ function _iterable_to_array$8(iter) {
235
238
  function _non_iterable_spread$8() {
236
239
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
237
240
  }
238
- function _object_spread$6(target) {
241
+ function _object_spread$7(target) {
239
242
  for(var i = 1; i < arguments.length; i++){
240
243
  var source = arguments[i] != null ? arguments[i] : {};
241
244
  var ownKeys = Object.keys(source);
@@ -245,7 +248,7 @@ function _object_spread$6(target) {
245
248
  }));
246
249
  }
247
250
  ownKeys.forEach(function(key) {
248
- _define_property$8(target, key, source[key]);
251
+ _define_property$9(target, key, source[key]);
249
252
  });
250
253
  }
251
254
  return target;
@@ -355,9 +358,9 @@ function _unsupported_iterable_to_array$b(o, minLen) {
355
358
  var _base_type;
356
359
  var base = (typeof inputSchema === "undefined" ? "undefined" : _type_of$4(inputSchema)) === 'object' && inputSchema != null ? inputSchema : {};
357
360
  var existingProperties = _type_of$4(base.properties) === 'object' && base.properties != null ? base.properties : {};
358
- var next = _object_spread_props$3(_object_spread$6({}, base), {
361
+ var next = _object_spread_props$3(_object_spread$7({}, base), {
359
362
  type: (_base_type = base.type) !== null && _base_type !== void 0 ? _base_type : 'object',
360
- properties: _object_spread_props$3(_object_spread$6({}, existingProperties), _define_property$8({}, resolved.parameterName, {
363
+ properties: _object_spread_props$3(_object_spread$7({}, existingProperties), _define_property$9({}, resolved.parameterName, {
361
364
  type: 'string',
362
365
  maxLength: resolved.maxLength,
363
366
  description: resolved.description
@@ -391,7 +394,7 @@ function _unsupported_iterable_to_array$b(o, minLen) {
391
394
  var result;
392
395
  if (resolved.enabled && !toolDeclaresOwnReason && Object.prototype.hasOwnProperty.call(args, resolved.parameterName)) {
393
396
  var rawValue = args[resolved.parameterName];
394
- var nextArgs = _object_spread$6({}, args);
397
+ var nextArgs = _object_spread$7({}, args);
395
398
  delete nextArgs[resolved.parameterName];
396
399
  var stringValue = rawValue == null ? '' : String(rawValue);
397
400
  var clamped = stringValue.length > resolved.maxLength ? stringValue.slice(0, resolved.maxLength) : stringValue;
@@ -746,7 +749,7 @@ function scoreCandidates(states, pathname) {
746
749
  });
747
750
  }
748
751
 
749
- function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
752
+ function asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, key, arg) {
750
753
  try {
751
754
  var info = gen[key](arg);
752
755
  var value = info.value;
@@ -760,16 +763,16 @@ function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
760
763
  Promise.resolve(value).then(_next, _throw);
761
764
  }
762
765
  }
763
- function _async_to_generator$6(fn) {
766
+ function _async_to_generator$7(fn) {
764
767
  return function() {
765
768
  var self = this, args = arguments;
766
769
  return new Promise(function(resolve, reject) {
767
770
  var gen = fn.apply(self, args);
768
771
  function _next(value) {
769
- asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "next", value);
772
+ asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "next", value);
770
773
  }
771
774
  function _throw(err) {
772
- asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "throw", err);
775
+ asyncGeneratorStep$7(gen, resolve, reject, _next, _throw, "throw", err);
773
776
  }
774
777
  _next(undefined);
775
778
  });
@@ -783,7 +786,7 @@ function _instanceof$1(left, right) {
783
786
  return left instanceof right;
784
787
  }
785
788
  }
786
- function _ts_generator$6(thisArg, body) {
789
+ function _ts_generator$7(thisArg, body) {
787
790
  var f, y, t, _ = {
788
791
  label: 0,
789
792
  sent: function() {
@@ -911,9 +914,9 @@ function _ts_generator$6(thisArg, body) {
911
914
  * @param details - The handler-level API details (carries the mapper + Tier 2/3 formatters).
912
915
  * @returns The MCP tool response content.
913
916
  */ function formatMcpToolResponse(result, params, details) {
914
- return _async_to_generator$6(function() {
917
+ return _async_to_generator$7(function() {
915
918
  var mcp, value, _tmp, context, response, summary;
916
- return _ts_generator$6(this, function(_state) {
919
+ return _ts_generator$7(this, function(_state) {
917
920
  switch(_state.label){
918
921
  case 0:
919
922
  mcp = details === null || details === void 0 ? void 0 : details.mcp;
@@ -1773,7 +1776,7 @@ function _array_like_to_array$8(arr, len) {
1773
1776
  function _array_without_holes$7(arr) {
1774
1777
  if (Array.isArray(arr)) return _array_like_to_array$8(arr);
1775
1778
  }
1776
- function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
1779
+ function asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, key, arg) {
1777
1780
  try {
1778
1781
  var info = gen[key](arg);
1779
1782
  var value = info.value;
@@ -1787,16 +1790,16 @@ function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
1787
1790
  Promise.resolve(value).then(_next, _throw);
1788
1791
  }
1789
1792
  }
1790
- function _async_to_generator$5(fn) {
1793
+ function _async_to_generator$6(fn) {
1791
1794
  return function() {
1792
1795
  var self = this, args = arguments;
1793
1796
  return new Promise(function(resolve, reject) {
1794
1797
  var gen = fn.apply(self, args);
1795
1798
  function _next(value) {
1796
- asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "next", value);
1799
+ asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "next", value);
1797
1800
  }
1798
1801
  function _throw(err) {
1799
- asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "throw", err);
1802
+ asyncGeneratorStep$6(gen, resolve, reject, _next, _throw, "throw", err);
1800
1803
  }
1801
1804
  _next(undefined);
1802
1805
  });
@@ -1819,7 +1822,7 @@ function _unsupported_iterable_to_array$8(o, minLen) {
1819
1822
  if (n === "Map" || n === "Set") return Array.from(n);
1820
1823
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$8(o, minLen);
1821
1824
  }
1822
- function _ts_generator$5(thisArg, body) {
1825
+ function _ts_generator$6(thisArg, body) {
1823
1826
  var f, y, t, _ = {
1824
1827
  label: 0,
1825
1828
  sent: function() {
@@ -1980,55 +1983,564 @@ function _ts_generator$5(thisArg, body) {
1980
1983
  }
1981
1984
  // MARK: Handler
1982
1985
  function modelGetToolHandler(args, ctx, deps) {
1986
+ return _async_to_generator$6(function() {
1987
+ var result, input, identity, resolvedKeys, merged, error;
1988
+ return _ts_generator$6(this, function(_state) {
1989
+ switch(_state.label){
1990
+ case 0:
1991
+ _state.trys.push([
1992
+ 0,
1993
+ 2,
1994
+ ,
1995
+ 3
1996
+ ]);
1997
+ input = parseModelGetInput(args);
1998
+ identity = deps.resolveIdentity(input.modelType, ctx.auth);
1999
+ if (identity == null) {
2000
+ throw new Error("Unknown modelType: ".concat(input.modelType));
2001
+ }
2002
+ resolvedKeys = resolveKeys$1(input.keys, identity);
2003
+ return [
2004
+ 4,
2005
+ readInChunks$1({
2006
+ modelType: input.modelType,
2007
+ keys: resolvedKeys,
2008
+ auth: ctx.auth,
2009
+ readDocuments: deps.readDocuments
2010
+ })
2011
+ ];
2012
+ case 1:
2013
+ merged = _state.sent();
2014
+ result = {
2015
+ content: [
2016
+ {
2017
+ type: 'text',
2018
+ text: JSON.stringify(merged)
2019
+ }
2020
+ ],
2021
+ structuredContent: merged
2022
+ };
2023
+ return [
2024
+ 3,
2025
+ 3
2026
+ ];
2027
+ case 2:
2028
+ error = _state.sent();
2029
+ result = formatMcpToolErrorResponse(error);
2030
+ return [
2031
+ 3,
2032
+ 3
2033
+ ];
2034
+ case 3:
2035
+ return [
2036
+ 2,
2037
+ result
2038
+ ];
2039
+ }
2040
+ });
2041
+ })();
2042
+ }
2043
+ function parseModelGetInput(args) {
2044
+ var modelType = args['modelType'];
2045
+ var keys = args['keys'];
2046
+ if (typeof modelType !== 'string' || modelType.length === 0) {
2047
+ throw new Error('model-get: "modelType" is required and must be a non-empty string.');
2048
+ }
2049
+ if (!Array.isArray(keys) || keys.length === 0) {
2050
+ throw new Error('model-get: "keys" is required and must be a non-empty array.');
2051
+ }
2052
+ var normalizedKeys = keys.map(function(value, index) {
2053
+ if (typeof value !== 'string' || value.length === 0) {
2054
+ throw new Error("model-get: keys[".concat(index, "] must be a non-empty string."));
2055
+ }
2056
+ return value;
2057
+ });
2058
+ return {
2059
+ modelType: modelType,
2060
+ keys: normalizedKeys
2061
+ };
2062
+ }
2063
+ function resolveKeys$1(keys, identity) {
2064
+ var isRoot = identity.type === 'root';
2065
+ var resolved = [];
2066
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2067
+ try {
2068
+ for(var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2069
+ var value = _step.value;
2070
+ if (value.includes('/')) {
2071
+ resolved.push(value);
2072
+ } else if (isRoot) {
2073
+ resolved.push("".concat(identity.collectionName, "/").concat(value));
2074
+ } else {
2075
+ throw new Error('model-get: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2076
+ }
2077
+ }
2078
+ } catch (err) {
2079
+ _didIteratorError = true;
2080
+ _iteratorError = err;
2081
+ } finally{
2082
+ try {
2083
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2084
+ _iterator.return();
2085
+ }
2086
+ } finally{
2087
+ if (_didIteratorError) {
2088
+ throw _iteratorError;
2089
+ }
2090
+ }
2091
+ }
2092
+ return resolved;
2093
+ }
2094
+ function readInChunks$1(input) {
2095
+ return _async_to_generator$6(function() {
2096
+ var modelType, keys, auth, readDocuments, merged, offset, _merged_results, _merged_errors, batch, partial;
2097
+ return _ts_generator$6(this, function(_state) {
2098
+ switch(_state.label){
2099
+ case 0:
2100
+ modelType = input.modelType, keys = input.keys, auth = input.auth, readDocuments = input.readDocuments;
2101
+ merged = {
2102
+ results: [],
2103
+ errors: []
2104
+ };
2105
+ offset = 0;
2106
+ _state.label = 1;
2107
+ case 1:
2108
+ if (!(offset < keys.length)) return [
2109
+ 3,
2110
+ 4
2111
+ ];
2112
+ batch = keys.slice(offset, offset + MCP_MODEL_GET_BATCH_SIZE);
2113
+ return [
2114
+ 4,
2115
+ readDocuments(modelType, batch, auth)
2116
+ ];
2117
+ case 2:
2118
+ partial = _state.sent();
2119
+ (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$7(partial.results));
2120
+ (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$7(partial.errors));
2121
+ _state.label = 3;
2122
+ case 3:
2123
+ offset += MCP_MODEL_GET_BATCH_SIZE;
2124
+ return [
2125
+ 3,
2126
+ 1
2127
+ ];
2128
+ case 4:
2129
+ return [
2130
+ 2,
2131
+ merged
2132
+ ];
2133
+ }
2134
+ });
2135
+ })();
2136
+ }
2137
+ // MARK: Schemas
2138
+ var MODEL_GET_INPUT_SCHEMA = {
2139
+ type: 'object',
2140
+ required: [
2141
+ 'modelType',
2142
+ 'keys'
2143
+ ],
2144
+ properties: {
2145
+ modelType: {
2146
+ type: 'string',
2147
+ minLength: 1,
2148
+ description: 'Firestore model type to fetch (e.g. "guestbook", "profile").'
2149
+ },
2150
+ keys: {
2151
+ type: 'array',
2152
+ minItems: 1,
2153
+ description: 'Full keys ("prefix/id") or bare ids (root models only).',
2154
+ items: {
2155
+ type: 'string',
2156
+ minLength: 1
2157
+ }
2158
+ }
2159
+ },
2160
+ additionalProperties: false
2161
+ };
2162
+ var MODEL_GET_OUTPUT_SCHEMA = {
2163
+ type: 'object',
2164
+ required: [
2165
+ 'results',
2166
+ 'errors'
2167
+ ],
2168
+ properties: {
2169
+ results: {
2170
+ type: 'array',
2171
+ description: 'Per-key successful reads.',
2172
+ items: {
2173
+ type: 'object',
2174
+ required: [
2175
+ 'key',
2176
+ 'data'
2177
+ ],
2178
+ properties: {
2179
+ key: {
2180
+ type: 'string'
2181
+ },
2182
+ data: {}
2183
+ }
2184
+ }
2185
+ },
2186
+ errors: {
2187
+ type: 'array',
2188
+ description: 'Per-key failures (not-found, forbidden, etc.).',
2189
+ items: {
2190
+ type: 'object',
2191
+ required: [
2192
+ 'key',
2193
+ 'message'
2194
+ ],
2195
+ properties: {
2196
+ key: {
2197
+ type: 'string'
2198
+ },
2199
+ message: {
2200
+ type: 'string'
2201
+ },
2202
+ code: {
2203
+ type: 'string'
2204
+ }
2205
+ }
2206
+ }
2207
+ }
2208
+ }
2209
+ };
2210
+
2211
+ function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
2212
+ try {
2213
+ var info = gen[key](arg);
2214
+ var value = info.value;
2215
+ } catch (error) {
2216
+ reject(error);
2217
+ return;
2218
+ }
2219
+ if (info.done) {
2220
+ resolve(value);
2221
+ } else {
2222
+ Promise.resolve(value).then(_next, _throw);
2223
+ }
2224
+ }
2225
+ function _async_to_generator$5(fn) {
2226
+ return function() {
2227
+ var self = this, args = arguments;
2228
+ return new Promise(function(resolve, reject) {
2229
+ var gen = fn.apply(self, args);
2230
+ function _next(value) {
2231
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "next", value);
2232
+ }
2233
+ function _throw(err) {
2234
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "throw", err);
2235
+ }
2236
+ _next(undefined);
2237
+ });
2238
+ };
2239
+ }
2240
+ function _define_property$8(obj, key, value) {
2241
+ if (key in obj) {
2242
+ Object.defineProperty(obj, key, {
2243
+ value: value,
2244
+ enumerable: true,
2245
+ configurable: true,
2246
+ writable: true
2247
+ });
2248
+ } else {
2249
+ obj[key] = value;
2250
+ }
2251
+ return obj;
2252
+ }
2253
+ function _object_spread$6(target) {
2254
+ for(var i = 1; i < arguments.length; i++){
2255
+ var source = arguments[i] != null ? arguments[i] : {};
2256
+ var ownKeys = Object.keys(source);
2257
+ if (typeof Object.getOwnPropertySymbols === "function") {
2258
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
2259
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
2260
+ }));
2261
+ }
2262
+ ownKeys.forEach(function(key) {
2263
+ _define_property$8(target, key, source[key]);
2264
+ });
2265
+ }
2266
+ return target;
2267
+ }
2268
+ function _ts_generator$5(thisArg, body) {
2269
+ var f, y, t, _ = {
2270
+ label: 0,
2271
+ sent: function() {
2272
+ if (t[0] & 1) throw t[1];
2273
+ return t[1];
2274
+ },
2275
+ trys: [],
2276
+ ops: []
2277
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
2278
+ return d(g, "next", {
2279
+ value: verb(0)
2280
+ }), d(g, "throw", {
2281
+ value: verb(1)
2282
+ }), d(g, "return", {
2283
+ value: verb(2)
2284
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
2285
+ value: function() {
2286
+ return this;
2287
+ }
2288
+ }), g;
2289
+ function verb(n) {
2290
+ return function(v) {
2291
+ return step([
2292
+ n,
2293
+ v
2294
+ ]);
2295
+ };
2296
+ }
2297
+ function step(op) {
2298
+ if (f) throw new TypeError("Generator is already executing.");
2299
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
2300
+ 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;
2301
+ if (y = 0, t) op = [
2302
+ op[0] & 2,
2303
+ t.value
2304
+ ];
2305
+ switch(op[0]){
2306
+ case 0:
2307
+ case 1:
2308
+ t = op;
2309
+ break;
2310
+ case 4:
2311
+ _.label++;
2312
+ return {
2313
+ value: op[1],
2314
+ done: false
2315
+ };
2316
+ case 5:
2317
+ _.label++;
2318
+ y = op[1];
2319
+ op = [
2320
+ 0
2321
+ ];
2322
+ continue;
2323
+ case 7:
2324
+ op = _.ops.pop();
2325
+ _.trys.pop();
2326
+ continue;
2327
+ default:
2328
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
2329
+ _ = 0;
2330
+ continue;
2331
+ }
2332
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
2333
+ _.label = op[1];
2334
+ break;
2335
+ }
2336
+ if (op[0] === 6 && _.label < t[1]) {
2337
+ _.label = t[1];
2338
+ t = op;
2339
+ break;
2340
+ }
2341
+ if (t && _.label < t[2]) {
2342
+ _.label = t[2];
2343
+ _.ops.push(op);
2344
+ break;
2345
+ }
2346
+ if (t[2]) _.ops.pop();
2347
+ _.trys.pop();
2348
+ continue;
2349
+ }
2350
+ op = body.call(thisArg, _);
2351
+ } catch (e) {
2352
+ op = [
2353
+ 6,
2354
+ e
2355
+ ];
2356
+ y = 0;
2357
+ } finally{
2358
+ f = t = 0;
2359
+ }
2360
+ if (op[0] & 5) throw op[1];
2361
+ return {
2362
+ value: op[0] ? op[1] : void 0,
2363
+ done: true
2364
+ };
2365
+ }
2366
+ }
2367
+ // MARK: Constants
2368
+ /**
2369
+ * Reserved tool name for the built-in `model-roles` static tool.
2370
+ */ var MODEL_ROLES_TOOL_NAME = 'model-roles';
2371
+ /**
2372
+ * Synthetic call type used in the tool's dispatch identity. Distinct from `model-get`'s `get` so
2373
+ * visibility predicates can target the two independently.
2374
+ */ var MODEL_ROLES_DISPATCH_CALL = 'roles';
2375
+ /**
2376
+ * Synthetic model type used in the tool's dispatch identity. Mirrors {@link MODEL_GET_DISPATCH_MODEL_TYPE}
2377
+ * — the tool isn't bound to one model type, so the literal "model" stands in.
2378
+ */ var MODEL_ROLES_DISPATCH_MODEL_TYPE = 'model';
2379
+ /**
2380
+ * Maximum number of keys accepted per `model-roles` call. Role resolution runs the model's real
2381
+ * permission delegate per key (which may itself read parent documents), so this is deliberately
2382
+ * lower than the `model-get` batch size.
2383
+ */ var MCP_MODEL_ROLES_MAX_KEYS = 25;
2384
+ // MARK: Factory
2385
+ /**
2386
+ * Builds the built-in `model-roles` MCP tool definition.
2387
+ *
2388
+ * Answers "what is this user actually allowed to do with this document?" by running the same
2389
+ * `roleMapForModel()` delegate the permission-checked read/write paths use, and returning the
2390
+ * resolved roles instead of consuming them. Because the model's real delegate runs, derived and
2391
+ * cascading roles appear exactly as the API grants them — there is no second implementation of the
2392
+ * rules to drift.
2393
+ *
2394
+ * Two things it disambiguates that a plain read cannot:
2395
+ * - **Missing vs. forbidden.** A key that resolves to a missing document returns
2396
+ * `exists: false, roles: []` rather than an error, so "the document is not there" reads
2397
+ * differently from "it is there and you have no access" (`exists: true, roles: []`).
2398
+ * - **Full access.** Admin short-circuits that grant the full-access marker come back as
2399
+ * `fullAccess: true` rather than as an opaque enumerated set.
2400
+ *
2401
+ * By default roles are resolved for the calling user. Passing `uid` resolves them for another user
2402
+ * and is gated behind {@link CreateModelRolesToolDeps.canTargetOtherUids}.
2403
+ *
2404
+ * @param deps - Role-map reader, identity resolver, and the target-uid authorization predicate.
2405
+ * @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP
2406
+ * server factory's tool registry.
2407
+ */ function createModelRolesTool(deps) {
2408
+ var handler = function handler(args, ctx) {
2409
+ return modelRolesToolHandler(args, ctx, deps);
2410
+ };
2411
+ var name = MODEL_ROLES_TOOL_NAME;
2412
+ var description = 'Resolve which roles the current user (or, with "uid", another user) is granted on one or more model keys — the same permission computation the API runs, returned instead of enforced. Use to answer "why was this forbidden?" and to tell a missing document (exists:false) apart from one you cannot access (exists:true, roles:[]). Admin full-access short-circuits report fullAccess:true. Targeting another uid requires elevated access.';
2413
+ return buildStaticToolDefinition({
2414
+ name: name,
2415
+ description: description,
2416
+ inputSchema: MODEL_ROLES_INPUT_SCHEMA,
2417
+ outputSchema: MODEL_ROLES_OUTPUT_SCHEMA,
2418
+ dispatch: {
2419
+ call: MODEL_ROLES_DISPATCH_CALL,
2420
+ modelType: MODEL_ROLES_DISPATCH_MODEL_TYPE
2421
+ },
2422
+ staticHandler: handler,
2423
+ effectiveReadOnly: true,
2424
+ rule: {
2425
+ requireAuthenticated: true
2426
+ }
2427
+ });
2428
+ }
2429
+ // MARK: Handler
2430
+ function modelRolesToolHandler(args, ctx, deps) {
1983
2431
  return _async_to_generator$5(function() {
1984
- var result, input, identity, resolvedKeys, merged, error;
2432
+ var result, input, identity, targetUid, keys, resolved, error;
1985
2433
  return _ts_generator$5(this, function(_state) {
1986
2434
  switch(_state.label){
1987
2435
  case 0:
1988
2436
  _state.trys.push([
1989
2437
  0,
1990
- 2,
2438
+ 3,
1991
2439
  ,
1992
- 3
2440
+ 4
1993
2441
  ]);
1994
- input = parseModelGetInput(args);
2442
+ input = parseModelRolesInput(args);
1995
2443
  identity = deps.resolveIdentity(input.modelType, ctx.auth);
1996
2444
  if (identity == null) {
1997
2445
  throw new Error("Unknown modelType: ".concat(input.modelType));
1998
2446
  }
1999
- resolvedKeys = resolveKeys(input.keys, identity);
2000
2447
  return [
2001
2448
  4,
2002
- readInChunks$1({
2003
- modelType: input.modelType,
2004
- keys: resolvedKeys,
2449
+ resolveTargetUid({
2450
+ requestedUid: input.uid,
2005
2451
  auth: ctx.auth,
2006
- readDocuments: deps.readDocuments
2452
+ canTargetOtherUids: deps.canTargetOtherUids
2007
2453
  })
2008
2454
  ];
2009
2455
  case 1:
2010
- merged = _state.sent();
2456
+ targetUid = _state.sent();
2457
+ keys = resolveKeys(input.keys, identity);
2458
+ return [
2459
+ 4,
2460
+ deps.readRoleMaps(_object_spread$6({
2461
+ modelType: input.modelType,
2462
+ keys: keys,
2463
+ auth: ctx.auth
2464
+ }, targetUid == null ? {} : {
2465
+ targetUid: targetUid
2466
+ }))
2467
+ ];
2468
+ case 2:
2469
+ resolved = _state.sent();
2011
2470
  result = {
2012
2471
  content: [
2013
2472
  {
2014
2473
  type: 'text',
2015
- text: JSON.stringify(merged)
2474
+ text: JSON.stringify(resolved)
2016
2475
  }
2017
2476
  ],
2018
- structuredContent: merged
2477
+ structuredContent: resolved
2019
2478
  };
2020
2479
  return [
2021
2480
  3,
2022
- 3
2481
+ 4
2023
2482
  ];
2024
- case 2:
2483
+ case 3:
2025
2484
  error = _state.sent();
2026
2485
  result = formatMcpToolErrorResponse(error);
2486
+ return [
2487
+ 3,
2488
+ 4
2489
+ ];
2490
+ case 4:
2491
+ return [
2492
+ 2,
2493
+ result
2494
+ ];
2495
+ }
2496
+ });
2497
+ })();
2498
+ }
2499
+ /**
2500
+ * Resolves the effective target uid for a call, enforcing the elevated-access gate.
2501
+ *
2502
+ * Requesting your own uid is always allowed and is treated as no target at all, so a caller that
2503
+ * passes their own uid never trips the predicate.
2504
+ *
2505
+ * @param input - The requested uid, the caller's auth, and the authorization predicate.
2506
+ * @returns The uid to resolve as, or `undefined` to resolve as the caller.
2507
+ * @throws {Error} When another user's uid is requested and the predicate denies (or is absent).
2508
+ */ function resolveTargetUid(input) {
2509
+ return _async_to_generator$5(function() {
2510
+ var requestedUid, auth, canTargetOtherUids, result, allowed, _tmp;
2511
+ return _ts_generator$5(this, function(_state) {
2512
+ switch(_state.label){
2513
+ case 0:
2514
+ requestedUid = input.requestedUid, auth = input.auth, canTargetOtherUids = input.canTargetOtherUids;
2515
+ if (!(requestedUid != null && requestedUid !== (auth === null || auth === void 0 ? void 0 : auth.uid))) return [
2516
+ 3,
2517
+ 4
2518
+ ];
2519
+ if (!(canTargetOtherUids == null)) return [
2520
+ 3,
2521
+ 1
2522
+ ];
2523
+ _tmp = false;
2027
2524
  return [
2028
2525
  3,
2029
2526
  3
2030
2527
  ];
2528
+ case 1:
2529
+ return [
2530
+ 4,
2531
+ canTargetOtherUids(auth)
2532
+ ];
2533
+ case 2:
2534
+ _tmp = _state.sent();
2535
+ _state.label = 3;
2031
2536
  case 3:
2537
+ allowed = _tmp;
2538
+ if (!allowed) {
2539
+ throw new Error("model-roles: resolving roles for another user's uid requires elevated access.");
2540
+ }
2541
+ result = requestedUid;
2542
+ _state.label = 4;
2543
+ case 4:
2032
2544
  return [
2033
2545
  2,
2034
2546
  result
@@ -2037,25 +2549,34 @@ function modelGetToolHandler(args, ctx, deps) {
2037
2549
  });
2038
2550
  })();
2039
2551
  }
2040
- function parseModelGetInput(args) {
2041
- var modelType = args.modelType;
2042
- var keys = args.keys;
2552
+ function parseModelRolesInput(args) {
2553
+ var modelType = args['modelType'];
2554
+ var keys = args['keys'];
2555
+ var uid = args['uid'];
2043
2556
  if (typeof modelType !== 'string' || modelType.length === 0) {
2044
- throw new Error('model-get: "modelType" is required and must be a non-empty string.');
2557
+ throw new Error('model-roles: "modelType" is required and must be a non-empty string.');
2045
2558
  }
2046
2559
  if (!Array.isArray(keys) || keys.length === 0) {
2047
- throw new Error('model-get: "keys" is required and must be a non-empty array.');
2560
+ throw new Error('model-roles: "keys" is required and must be a non-empty array.');
2561
+ }
2562
+ if (keys.length > MCP_MODEL_ROLES_MAX_KEYS) {
2563
+ throw new Error("model-roles: at most ".concat(MCP_MODEL_ROLES_MAX_KEYS, " keys may be resolved per call (received ").concat(keys.length, ")."));
2564
+ }
2565
+ if (uid !== undefined && (typeof uid !== 'string' || uid.length === 0)) {
2566
+ throw new Error('model-roles: "uid" must be a non-empty string when provided.');
2048
2567
  }
2049
2568
  var normalizedKeys = keys.map(function(value, index) {
2050
2569
  if (typeof value !== 'string' || value.length === 0) {
2051
- throw new Error("model-get: keys[".concat(index, "] must be a non-empty string."));
2570
+ throw new Error("model-roles: keys[".concat(index, "] must be a non-empty string."));
2052
2571
  }
2053
2572
  return value;
2054
2573
  });
2055
- return {
2574
+ return _object_spread$6({
2056
2575
  modelType: modelType,
2057
2576
  keys: normalizedKeys
2058
- };
2577
+ }, uid === undefined ? {} : {
2578
+ uid: uid
2579
+ });
2059
2580
  }
2060
2581
  function resolveKeys(keys, identity) {
2061
2582
  var isRoot = identity.type === 'root';
@@ -2069,7 +2590,7 @@ function resolveKeys(keys, identity) {
2069
2590
  } else if (isRoot) {
2070
2591
  resolved.push("".concat(identity.collectionName, "/").concat(value));
2071
2592
  } else {
2072
- throw new Error('model-get: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2593
+ throw new Error('model-roles: modelType "'.concat(identity.modelType, '" is a subcollection; bare ids are not allowed. Provide full keys (e.g. "parentPrefix/parentId/').concat(identity.collectionName, "/").concat(value, '").'));
2073
2594
  }
2074
2595
  }
2075
2596
  } catch (err) {
@@ -2088,51 +2609,8 @@ function resolveKeys(keys, identity) {
2088
2609
  }
2089
2610
  return resolved;
2090
2611
  }
2091
- function readInChunks$1(input) {
2092
- return _async_to_generator$5(function() {
2093
- var modelType, keys, auth, readDocuments, merged, offset, _merged_results, _merged_errors, batch, partial;
2094
- return _ts_generator$5(this, function(_state) {
2095
- switch(_state.label){
2096
- case 0:
2097
- modelType = input.modelType, keys = input.keys, auth = input.auth, readDocuments = input.readDocuments;
2098
- merged = {
2099
- results: [],
2100
- errors: []
2101
- };
2102
- offset = 0;
2103
- _state.label = 1;
2104
- case 1:
2105
- if (!(offset < keys.length)) return [
2106
- 3,
2107
- 4
2108
- ];
2109
- batch = keys.slice(offset, offset + MCP_MODEL_GET_BATCH_SIZE);
2110
- return [
2111
- 4,
2112
- readDocuments(modelType, batch, auth)
2113
- ];
2114
- case 2:
2115
- partial = _state.sent();
2116
- (_merged_results = merged.results).push.apply(_merged_results, _to_consumable_array$7(partial.results));
2117
- (_merged_errors = merged.errors).push.apply(_merged_errors, _to_consumable_array$7(partial.errors));
2118
- _state.label = 3;
2119
- case 3:
2120
- offset += MCP_MODEL_GET_BATCH_SIZE;
2121
- return [
2122
- 3,
2123
- 1
2124
- ];
2125
- case 4:
2126
- return [
2127
- 2,
2128
- merged
2129
- ];
2130
- }
2131
- });
2132
- })();
2133
- }
2134
2612
  // MARK: Schemas
2135
- var MODEL_GET_INPUT_SCHEMA = {
2613
+ var MODEL_ROLES_INPUT_SCHEMA = {
2136
2614
  type: 'object',
2137
2615
  required: [
2138
2616
  'modelType',
@@ -2142,47 +2620,78 @@ var MODEL_GET_INPUT_SCHEMA = {
2142
2620
  modelType: {
2143
2621
  type: 'string',
2144
2622
  minLength: 1,
2145
- description: 'Firestore model type to fetch (e.g. "guestbook", "profile").'
2623
+ description: 'Firestore model type to resolve roles on (e.g. "guestbook", "profile").'
2146
2624
  },
2147
2625
  keys: {
2148
2626
  type: 'array',
2149
2627
  minItems: 1,
2628
+ maxItems: MCP_MODEL_ROLES_MAX_KEYS,
2150
2629
  description: 'Full keys ("prefix/id") or bare ids (root models only).',
2151
2630
  items: {
2152
2631
  type: 'string',
2153
2632
  minLength: 1
2154
2633
  }
2634
+ },
2635
+ uid: {
2636
+ type: 'string',
2637
+ minLength: 1,
2638
+ description: 'Resolve roles as this user instead of the caller. Requires elevated access; passing your own uid is always allowed.'
2155
2639
  }
2156
2640
  },
2157
2641
  additionalProperties: false
2158
2642
  };
2159
- var MODEL_GET_OUTPUT_SCHEMA = {
2643
+ var MODEL_ROLES_OUTPUT_SCHEMA = {
2160
2644
  type: 'object',
2161
2645
  required: [
2646
+ 'targeted',
2162
2647
  'results',
2163
2648
  'errors'
2164
2649
  ],
2165
2650
  properties: {
2651
+ uid: {
2652
+ type: 'string',
2653
+ description: 'The uid the roles were resolved for.'
2654
+ },
2655
+ targeted: {
2656
+ type: 'boolean',
2657
+ description: 'True when the roles were resolved for a user other than the caller.'
2658
+ },
2166
2659
  results: {
2167
2660
  type: 'array',
2168
- description: 'Per-key successful reads.',
2661
+ description: 'Per-key resolved permission state.',
2169
2662
  items: {
2170
2663
  type: 'object',
2171
2664
  required: [
2172
2665
  'key',
2173
- 'data'
2666
+ 'exists',
2667
+ 'fullAccess',
2668
+ 'roles'
2174
2669
  ],
2175
2670
  properties: {
2176
2671
  key: {
2177
2672
  type: 'string'
2178
2673
  },
2179
- data: {}
2674
+ exists: {
2675
+ type: 'boolean',
2676
+ description: 'Whether the document exists. Roles are only computed for documents that exist.'
2677
+ },
2678
+ fullAccess: {
2679
+ type: 'boolean',
2680
+ description: 'True when the full-access marker was granted; every role is implicitly held and "roles" is empty.'
2681
+ },
2682
+ roles: {
2683
+ type: 'array',
2684
+ items: {
2685
+ type: 'string'
2686
+ },
2687
+ description: 'Granted role names, sorted.'
2688
+ }
2180
2689
  }
2181
2690
  }
2182
2691
  },
2183
2692
  errors: {
2184
2693
  type: 'array',
2185
- description: 'Per-key failures (not-found, forbidden, etc.).',
2694
+ description: 'Per-key failures. A missing document is NOT an error — it appears in results with exists:false.',
2186
2695
  items: {
2187
2696
  type: 'object',
2188
2697
  required: [
@@ -2940,7 +3449,7 @@ var MODEL_ROW_SCHEMA = {
2940
3449
  }
2941
3450
  };
2942
3451
  // MCP's `tools/list` validator requires `outputSchema.type === 'object'` at the root
2943
- // (see @modelcontextprotocol/sdk Zod schema), so the variants are expressed via a top-level
3452
+ // (see the MCP SDK's `ToolSchema`), so the variants are expressed via a top-level
2944
3453
  // object whose nested `oneOf` discriminates by the `mode` literal.
2945
3454
  var MODEL_INFO_OUTPUT_SCHEMA = {
2946
3455
  type: 'object',
@@ -3742,7 +4251,7 @@ function whoamiToolHandler(args, ctx, deps) {
3742
4251
  }
3743
4252
  // Coerces the `detail` input into a WhoamiDetailLevel, defaulting unknown or absent values to `summary`.
3744
4253
  function parseDetailLevel(args) {
3745
- var value = args.detail;
4254
+ var value = args['detail'];
3746
4255
  return value === 'claims' || value === 'full' ? value : 'summary';
3747
4256
  }
3748
4257
  function buildResult(output, text) {
@@ -4337,30 +4846,30 @@ function urlModelsToolHandler(args, ctx, deps) {
4337
4846
  })();
4338
4847
  }
4339
4848
  function parseUrlModelsInput(args) {
4340
- var url = args.url;
4849
+ var url = args['url'];
4341
4850
  if (typeof url !== 'string' || url.length === 0) {
4342
4851
  throw new Error('url-models: "url" is required and must be a non-empty string.');
4343
4852
  }
4344
- var keysOnly = args.keysOnly === true;
4345
- var load = args.load === true;
4853
+ var keysOnly = args['keysOnly'] === true;
4854
+ var load = args['load'] === true;
4346
4855
  if (keysOnly && load) {
4347
4856
  throw new Error('url-models: "keysOnly" and "load" cannot be combined — "keysOnly" returns keys without loading documents.');
4348
4857
  }
4349
4858
  var models;
4350
- if (args.models !== undefined) {
4351
- if (!Array.isArray(args.models) || args.models.some(function(m) {
4859
+ if (args['models'] !== undefined) {
4860
+ if (!Array.isArray(args['models']) || args['models'].some(function(m) {
4352
4861
  return typeof m !== 'string';
4353
4862
  })) {
4354
4863
  throw new Error('url-models: "models" must be an array of model-type strings.');
4355
4864
  }
4356
- models = args.models;
4865
+ models = args['models'];
4357
4866
  }
4358
4867
  var currentUserUid;
4359
- if (args.currentUserUid !== undefined) {
4360
- if (typeof args.currentUserUid !== 'string' || args.currentUserUid.length === 0) {
4868
+ if (args['currentUserUid'] !== undefined) {
4869
+ if (typeof args['currentUserUid'] !== 'string' || args['currentUserUid'].length === 0) {
4361
4870
  throw new Error('url-models: "currentUserUid" must be a non-empty string.');
4362
4871
  }
4363
- currentUserUid = args.currentUserUid;
4872
+ currentUserUid = args['currentUserUid'];
4364
4873
  }
4365
4874
  return _object_spread$2(_object_spread_props$1(_object_spread$2({
4366
4875
  url: url
@@ -5308,19 +5817,19 @@ function batchExecuteToolHandler(args, ctx, deps) {
5308
5817
  // MARK: Input parsing
5309
5818
  function parseBatchExecuteInput(args) {
5310
5819
  var _args_format, _args_maxParallel;
5311
- var uploadPath = args.uploadPath;
5820
+ var uploadPath = args['uploadPath'];
5312
5821
  if (typeof uploadPath !== 'string' || uploadPath.length === 0) {
5313
5822
  throw new Error('batch-execute: "uploadPath" is required and must be a non-empty string.');
5314
5823
  }
5315
- var bucketId = args.bucketId;
5824
+ var bucketId = args['bucketId'];
5316
5825
  if (bucketId != null && (typeof bucketId !== 'string' || bucketId.length === 0)) {
5317
5826
  throw new Error('batch-execute: "bucketId" must be a non-empty string when provided.');
5318
5827
  }
5319
- var format = (_args_format = args.format) !== null && _args_format !== void 0 ? _args_format : 'ndjson';
5828
+ var format = (_args_format = args['format']) !== null && _args_format !== void 0 ? _args_format : 'ndjson';
5320
5829
  if (format !== 'json' && format !== 'ndjson') {
5321
5830
  throw new Error('batch-execute: "format" must be "json" or "ndjson".');
5322
5831
  }
5323
- var maxParallel = (_args_maxParallel = args.maxParallel) !== null && _args_maxParallel !== void 0 ? _args_maxParallel : DEFAULT_BATCH_EXECUTE_MAX_PARALLEL;
5832
+ var maxParallel = (_args_maxParallel = args['maxParallel']) !== null && _args_maxParallel !== void 0 ? _args_maxParallel : DEFAULT_BATCH_EXECUTE_MAX_PARALLEL;
5324
5833
  if (typeof maxParallel !== 'number' || !Number.isInteger(maxParallel) || maxParallel < 1) {
5325
5834
  throw new Error('batch-execute: "maxParallel" must be a positive integer.');
5326
5835
  }
@@ -5329,8 +5838,8 @@ function parseBatchExecuteInput(args) {
5329
5838
  bucketId: bucketId !== null && bucketId !== void 0 ? bucketId : undefined,
5330
5839
  format: format,
5331
5840
  maxParallel: maxParallel,
5332
- stopOnError: args.stopOnError === true,
5333
- deleteUploadOnSuccess: args.deleteUploadOnSuccess !== false
5841
+ stopOnError: args['stopOnError'] === true,
5842
+ deleteUploadOnSuccess: args['deleteUploadOnSuccess'] !== false
5334
5843
  };
5335
5844
  }
5336
5845
  function readOperationsFile(input) {
@@ -5411,9 +5920,9 @@ function validateOperation(value, index) {
5411
5920
  throw new Error("batch-execute: operation[".concat(index, "] must be an object."));
5412
5921
  }
5413
5922
  var candidate = value;
5414
- var modelType = candidate.modelType;
5415
- var call = candidate.call;
5416
- var specifier = candidate.specifier;
5923
+ var modelType = candidate['modelType'];
5924
+ var call = candidate['call'];
5925
+ var specifier = candidate['specifier'];
5417
5926
  if (typeof modelType !== 'string' || modelType.length === 0) {
5418
5927
  throw new Error("batch-execute: operation[".concat(index, '] is missing a non-empty "modelType".'));
5419
5928
  }
@@ -5427,7 +5936,7 @@ function validateOperation(value, index) {
5427
5936
  call: call,
5428
5937
  modelType: modelType,
5429
5938
  specifier: specifier !== null && specifier !== void 0 ? specifier : undefined,
5430
- data: candidate.data
5939
+ data: candidate['data']
5431
5940
  };
5432
5941
  }
5433
5942
  function collectPreflightErrors(operations, authorizeOperation) {
@@ -6055,17 +6564,19 @@ var McpServerFactoryService_1;
6055
6564
  * Injectable factory that builds {@link McpServer} instances pre-wired to the
6056
6565
  * call model dispatch chain.
6057
6566
  *
6058
- * The factory is invoked per Streamable HTTP request — `@modelcontextprotocol/sdk`
6059
- * recommends a fresh `McpServer` + transport pair per stateless JSON-RPC request,
6060
- * which sidesteps session bookkeeping for the common Claude-connector case.
6567
+ * The factory is invoked per Streamable HTTP request. That is the shape the MCP
6568
+ * SDK's 2026-07-28 entry (`createMcpHandler`) requires — it builds a fresh server
6569
+ * per request and has no session concept at all — and it is equally what the SDK
6570
+ * recommends for stateless 2025-era serving, so one factory backs both eras.
6061
6571
  */ exports.McpServerFactoryService = McpServerFactoryService_1 = /*#__PURE__*/ function() {
6062
- function McpServerFactoryService(mcpConfig, dispatchService, modelApiGetService, roleReader, analyticsService, storageService) {
6572
+ function McpServerFactoryService(mcpConfig, dispatchService, modelApiGetService, roleReader, analyticsService, storageService, modelRolesTargetUidPredicate) {
6063
6573
  _class_call_check$3(this, McpServerFactoryService);
6064
6574
  _define_property$3(this, "mcpConfig", void 0);
6065
6575
  _define_property$3(this, "dispatchService", void 0);
6066
6576
  _define_property$3(this, "modelApiGetService", void 0);
6067
6577
  _define_property$3(this, "roleReader", void 0);
6068
6578
  _define_property$3(this, "storageService", void 0);
6579
+ _define_property$3(this, "modelRolesTargetUidPredicate", void 0);
6069
6580
  _define_property$3(this, "_logger", new common.Logger(McpServerFactoryService_1.name));
6070
6581
  _define_property$3(this, "_cachedTools", void 0);
6071
6582
  _define_property$3(this, "_cachedStaticTools", void 0);
@@ -6085,6 +6596,7 @@ var McpServerFactoryService_1;
6085
6596
  this.modelApiGetService = modelApiGetService;
6086
6597
  this.roleReader = roleReader;
6087
6598
  this.storageService = storageService;
6599
+ this.modelRolesTargetUidPredicate = modelRolesTargetUidPredicate;
6088
6600
  this._analyticsService = analyticsService !== null && analyticsService !== void 0 ? analyticsService : noopMcpAnalyticsService();
6089
6601
  }
6090
6602
  _create_class$3(McpServerFactoryService, [
@@ -6100,13 +6612,13 @@ var McpServerFactoryService_1;
6100
6612
  var _this_mcpConfig_serverName, _this_mcpConfig_serverVersion, _this_mcpConfig_serverInstructions;
6101
6613
  var baseServerName = (_this_mcpConfig_serverName = this.mcpConfig.serverName) !== null && _this_mcpConfig_serverName !== void 0 ? _this_mcpConfig_serverName : DEFAULT_MCP_SERVER_NAME;
6102
6614
  var serverName = this.mcpConfig.readOnly === true ? "".concat(baseServerName, " (read-only)") : baseServerName;
6103
- var server = new mcp_js.McpServer({
6615
+ var server$1 = new server.McpServer({
6104
6616
  name: serverName,
6105
6617
  version: (_this_mcpConfig_serverVersion = this.mcpConfig.serverVersion) !== null && _this_mcpConfig_serverVersion !== void 0 ? _this_mcpConfig_serverVersion : '0.0.0'
6106
6618
  }, {
6107
6619
  instructions: (_this_mcpConfig_serverInstructions = this.mcpConfig.serverInstructions) !== null && _this_mcpConfig_serverInstructions !== void 0 ? _this_mcpConfig_serverInstructions : DEFAULT_MCP_SERVER_INSTRUCTIONS
6108
6620
  });
6109
- server.server.registerCapabilities({
6621
+ server$1.server.registerCapabilities({
6110
6622
  tools: {}
6111
6623
  });
6112
6624
  var toolGeneration = this._resolveToolDefinitions();
@@ -6135,7 +6647,7 @@ var McpServerFactoryService_1;
6135
6647
  batchTool
6136
6648
  ]);
6137
6649
  }
6138
- server.server.setRequestHandler(types_js.ListToolsRequestSchema, function() {
6650
+ server$1.server.setRequestHandler('tools/list', function() {
6139
6651
  return _async_to_generator$2(function() {
6140
6652
  var _this;
6141
6653
  return _ts_generator$2(this, function(_state) {
@@ -6143,6 +6655,10 @@ var McpServerFactoryService_1;
6143
6655
  return [
6144
6656
  2,
6145
6657
  {
6658
+ // McpToolListEntry types its schemas as opaque `object`: they are read from a manifest file
6659
+ // or produced by arktype's JSON Schema export, so the `type: 'object'` root the SDK's wire
6660
+ // type mandates holds at runtime but cannot be proven statically. Every schema reaching here
6661
+ // either came from arktypeToJsonSchemaForExport or defaulted to `{ type: 'object' }`.
6146
6662
  tools: listedTools.map(function(tool) {
6147
6663
  return _this._buildToolListEntry(tool, ctx, scopes);
6148
6664
  })
@@ -6151,7 +6667,7 @@ var McpServerFactoryService_1;
6151
6667
  });
6152
6668
  }).call(_this);
6153
6669
  });
6154
- server.server.setRequestHandler(types_js.CallToolRequestSchema, function(request) {
6670
+ server$1.server.setRequestHandler('tools/call', function(request) {
6155
6671
  return _async_to_generator$2(function() {
6156
6672
  return _ts_generator$2(this, function(_state) {
6157
6673
  return [
@@ -6161,7 +6677,7 @@ var McpServerFactoryService_1;
6161
6677
  });
6162
6678
  }).call(_this);
6163
6679
  });
6164
- return server;
6680
+ return server$1;
6165
6681
  }
6166
6682
  },
6167
6683
  {
@@ -6351,6 +6867,14 @@ var McpServerFactoryService_1;
6351
6867
  resolveIdentity: function resolveIdentity(modelType, auth) {
6352
6868
  return getService.getModelIdentity(modelType, auth);
6353
6869
  }
6870
+ }), createModelRolesTool({
6871
+ readRoleMaps: function readRoleMaps(params) {
6872
+ return getService.readRoleMaps(params);
6873
+ },
6874
+ resolveIdentity: function resolveIdentity(modelType, auth) {
6875
+ return getService.getModelIdentity(modelType, auth);
6876
+ },
6877
+ canTargetOtherUids: this.modelRolesTargetUidPredicate
6354
6878
  }));
6355
6879
  // `url-models` reuses the model-get read/identity path, so it is only offered when the get
6356
6880
  // service is wired AND a route manifest with at least one state was loaded.
@@ -7104,7 +7628,9 @@ exports.McpServerFactoryService = McpServerFactoryService_1 = __decorate([
7104
7628
  __param(4, common.Optional()),
7105
7629
  __param(4, common.Inject(MCP_ANALYTICS_SERVICE)),
7106
7630
  __param(5, common.Optional()),
7107
- __param(5, common.Inject(firebaseServer.FirebaseServerStorageService))
7631
+ __param(5, common.Inject(firebaseServer.FirebaseServerStorageService)),
7632
+ __param(6, common.Optional()),
7633
+ __param(6, common.Inject(MCP_MODEL_ROLES_TARGET_UID_PREDICATE))
7108
7634
  ], exports.McpServerFactoryService);
7109
7635
 
7110
7636
  function asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, key, arg) {
@@ -7236,29 +7762,168 @@ function _ts_generator$1(thisArg, body) {
7236
7762
  }
7237
7763
  }
7238
7764
  /**
7239
- * Handles a single Streamable HTTP JSON-RPC request against the supplied MCP server.
7765
+ * Handles a single Streamable HTTP JSON-RPC request, building a fresh MCP server for it.
7766
+ *
7767
+ * Both protocol eras are served, each by its own leg:
7240
7768
  *
7241
- * Each call creates a fresh `StreamableHTTPServerTransport` in stateless mode — the
7242
- * transport binds the server, processes one request, and is discarded. Stateless
7243
- * mode is adequate for Claude custom-connector usage and avoids the bookkeeping
7244
- * cost of session-tracked transports for the common case.
7769
+ * - **2026-07-28 (modern)** — `createMcpHandler`, which is per-request and stateless by
7770
+ * construction: there is no `Mcp-Session-Id` and no transport bookkeeping.
7771
+ * - **2025-era (legacy)** — a `NodeStreamableHTTPServerTransport` in stateless mode, which is
7772
+ * byte-for-byte what this endpoint served before the SDK v2 upgrade.
7773
+ *
7774
+ * The legacy leg is wired by hand (the SDK's documented `isLegacyRequest` composition) rather
7775
+ * than left to `createMcpHandler`'s built-in `legacy: 'stateless'` fallback, because that
7776
+ * fallback builds its transport with `sessionIdGenerator` alone and therefore answers in
7777
+ * `text/event-stream`. See {@link handleLegacyMcpRequest} for why that framing is not viable
7778
+ * here.
7245
7779
  *
7246
7780
  * The caller is expected to have already validated the bearer token (via the
7247
7781
  * OIDC bearer middleware) before this function runs.
7248
7782
  *
7249
7783
  * @param req - The Express request carrying the JSON-RPC body.
7250
7784
  * @param res - The Express response.
7785
+ * @param buildServer - Factory producing the MCP server that backs this request.
7786
+ */ function handleStreamableHttpMcpRequest(req, res, buildServer) {
7787
+ return _async_to_generator$1(function() {
7788
+ var probe;
7789
+ return _ts_generator$1(this, function(_state) {
7790
+ switch(_state.label){
7791
+ case 0:
7792
+ // Hand-wired compositions must reject non-JSON POST bodies themselves — neither the modern
7793
+ // entry's routing nor the legacy transport does it on our behalf once we classify up front.
7794
+ if (!server.isJsonContentType(req.headers['content-type'])) {
7795
+ res.status(415).json({
7796
+ statusCode: 415,
7797
+ message: 'Unsupported Media Type'
7798
+ });
7799
+ return [
7800
+ 2
7801
+ ];
7802
+ }
7803
+ return [
7804
+ 4,
7805
+ node.toWebRequest(req, req.body)
7806
+ ];
7807
+ case 1:
7808
+ probe = _state.sent();
7809
+ return [
7810
+ 4,
7811
+ server.isLegacyRequest(probe)
7812
+ ];
7813
+ case 2:
7814
+ if (!_state.sent()) return [
7815
+ 3,
7816
+ 4
7817
+ ];
7818
+ return [
7819
+ 4,
7820
+ handleLegacyMcpRequest(req, res, buildServer())
7821
+ ];
7822
+ case 3:
7823
+ _state.sent();
7824
+ return [
7825
+ 3,
7826
+ 6
7827
+ ];
7828
+ case 4:
7829
+ return [
7830
+ 4,
7831
+ handleModernMcpRequest(req, res, buildServer)
7832
+ ];
7833
+ case 5:
7834
+ _state.sent();
7835
+ _state.label = 6;
7836
+ case 6:
7837
+ return [
7838
+ 2
7839
+ ];
7840
+ }
7841
+ });
7842
+ })();
7843
+ }
7844
+ /**
7845
+ * Serves one 2026-07-28 request through `createMcpHandler`.
7846
+ *
7847
+ * `legacy: 'reject'` keeps this leg modern-only — 2025-era traffic never reaches it, having
7848
+ * already been routed to {@link handleLegacyMcpRequest}.
7849
+ *
7850
+ * `responseMode: 'json'` is the modern-leg equivalent of the legacy transport's
7851
+ * `enableJsonResponse`, keeping the endpoint on a single `application/json` body. Safe here
7852
+ * because no tool handler emits mid-call notifications, which are the only thing `'json'` drops.
7853
+ *
7854
+ * @param req - The Express request carrying the JSON-RPC body.
7855
+ * @param res - The Express response.
7856
+ * @param buildServer - Factory producing the MCP server that backs this request.
7857
+ */ function handleModernMcpRequest(req, res, buildServer) {
7858
+ return _async_to_generator$1(function() {
7859
+ var handler;
7860
+ return _ts_generator$1(this, function(_state) {
7861
+ switch(_state.label){
7862
+ case 0:
7863
+ handler = server.createMcpHandler(buildServer, {
7864
+ legacy: 'reject',
7865
+ responseMode: 'json'
7866
+ });
7867
+ _state.label = 1;
7868
+ case 1:
7869
+ _state.trys.push([
7870
+ 1,
7871
+ ,
7872
+ 3,
7873
+ 5
7874
+ ]);
7875
+ return [
7876
+ 4,
7877
+ node.toNodeHandler(handler)(req, res, req.body)
7878
+ ];
7879
+ case 2:
7880
+ _state.sent();
7881
+ return [
7882
+ 3,
7883
+ 5
7884
+ ];
7885
+ case 3:
7886
+ return [
7887
+ 4,
7888
+ handler.close()
7889
+ ];
7890
+ case 4:
7891
+ _state.sent();
7892
+ return [
7893
+ 7
7894
+ ];
7895
+ case 5:
7896
+ return [
7897
+ 2
7898
+ ];
7899
+ }
7900
+ });
7901
+ })();
7902
+ }
7903
+ /**
7904
+ * Serves one 2025-era request through a stateless `NodeStreamableHTTPServerTransport`.
7905
+ *
7906
+ * `sessionIdGenerator: undefined` selects stateless mode: the transport binds the server,
7907
+ * processes one request, and is discarded.
7908
+ *
7909
+ * `enableJsonResponse: true` replies with a single `application/json` body instead of framing it
7910
+ * as `text/event-stream`. Stateless mode answers exactly one JSON-RPC message per request and the
7911
+ * controller only implements POST (there is no standalone SSE stream to keep open), so SSE framing
7912
+ * adds nothing while making the endpoint fragile behind proxies that buffer or rewrite streamed
7913
+ * responses — the failure that originally pushed the demo app off its dev proxy.
7914
+ *
7915
+ * @param req - The Express request carrying the JSON-RPC body.
7916
+ * @param res - The Express response.
7251
7917
  * @param server - The MCP server instance to back the transport.
7252
- * @param server.connect - The MCP server's connect method that binds the transport before request handling.
7253
- */ function handleStreamableHttpMcpRequest(req, res, server) {
7918
+ */ function handleLegacyMcpRequest(req, res, server) {
7254
7919
  return _async_to_generator$1(function() {
7255
7920
  var transport;
7256
7921
  return _ts_generator$1(this, function(_state) {
7257
7922
  switch(_state.label){
7258
7923
  case 0:
7259
- // sessionIdGenerator: undefined → stateless mode.
7260
- transport = new streamableHttp_js.StreamableHTTPServerTransport({
7261
- sessionIdGenerator: undefined
7924
+ transport = new node.NodeStreamableHTTPServerTransport({
7925
+ sessionIdGenerator: undefined,
7926
+ enableJsonResponse: true
7262
7927
  });
7263
7928
  res.on('close', function() {
7264
7929
  void transport.close();
@@ -7450,9 +8115,11 @@ var McpController_1;
7450
8115
  * which must include `'/mcp'` in its `protectedPaths`. By the time the request reaches
7451
8116
  * this controller, `req.auth` is populated with the authenticated user's data.
7452
8117
  *
7453
- * Each request gets a fresh transport + MCP server pair (stateless mode), which is
7454
- * adequate for Claude custom-connector style usage. A session-tracked variant can
7455
- * be layered on later if streaming tool output becomes a requirement.
8118
+ * Each request gets a fresh MCP server (stateless mode), which is adequate for Claude
8119
+ * custom-connector style usage. Both protocol eras are served — 2026-07-28 through the
8120
+ * SDK's per-request `createMcpHandler` entry, and 2025-era through a stateless Streamable
8121
+ * HTTP transport. A session-tracked variant can be layered on later if streaming tool
8122
+ * output becomes a requirement.
7456
8123
  */ exports.McpController = McpController_1 = /*#__PURE__*/ function() {
7457
8124
  function McpController(factory) {
7458
8125
  _class_call_check$2(this, McpController);
@@ -7465,15 +8132,12 @@ var McpController_1;
7465
8132
  key: "handleMcpRequest",
7466
8133
  value: function handleMcpRequest(req, res) {
7467
8134
  return _async_to_generator(function() {
7468
- var auth, server, error;
8135
+ var _this, auth, error;
7469
8136
  return _ts_generator(this, function(_state) {
7470
8137
  switch(_state.label){
7471
8138
  case 0:
8139
+ _this = this;
7472
8140
  auth = req.auth;
7473
- server = this.factory.createServer({
7474
- auth: auth,
7475
- rawRequest: req
7476
- });
7477
8141
  _state.label = 1;
7478
8142
  case 1:
7479
8143
  _state.trys.push([
@@ -7484,7 +8148,12 @@ var McpController_1;
7484
8148
  ]);
7485
8149
  return [
7486
8150
  4,
7487
- handleStreamableHttpMcpRequest(req, res, server)
8151
+ handleStreamableHttpMcpRequest(req, res, function() {
8152
+ return _this.factory.createServer({
8153
+ auth: auth,
8154
+ rawRequest: req
8155
+ });
8156
+ })
7488
8157
  ];
7489
8158
  case 2:
7490
8159
  _state.sent();
@@ -7513,6 +8182,45 @@ var McpController_1;
7513
8182
  });
7514
8183
  }).call(this);
7515
8184
  }
8185
+ },
8186
+ {
8187
+ /**
8188
+ * Rejects the Streamable HTTP transport's optional `GET` method, which opens a standalone SSE
8189
+ * stream. Not applicable in stateless mode. Separate from {@link handleUnsupportedDelete} because
8190
+ * NestJS binds one HTTP method per handler — stacking route decorators would silently drop one.
8191
+ *
8192
+ * @param res - The Express response to write the rejection to.
8193
+ */ key: "handleUnsupportedGet",
8194
+ value: function handleUnsupportedGet(res) {
8195
+ this._rejectUnsupportedMethod(res);
8196
+ }
8197
+ },
8198
+ {
8199
+ /**
8200
+ * Rejects the Streamable HTTP transport's optional `DELETE` method, which tears down a session.
8201
+ * Not applicable in stateless mode — no session is ever issued.
8202
+ *
8203
+ * @param res - The Express response to write the rejection to.
8204
+ */ key: "handleUnsupportedDelete",
8205
+ value: function handleUnsupportedDelete(res) {
8206
+ this._rejectUnsupportedMethod(res);
8207
+ }
8208
+ },
8209
+ {
8210
+ /**
8211
+ * Answers with a spec-conformant `405 Method Not Allowed` + `Allow` header. Without these
8212
+ * handlers NestJS answers `404`, which some clients treat as a hard failure rather than
8213
+ * "the server doesn't offer this".
8214
+ *
8215
+ * @param res - The Express response to write the rejection to.
8216
+ */ key: "_rejectUnsupportedMethod",
8217
+ value: function _rejectUnsupportedMethod(res) {
8218
+ res.setHeader('Allow', 'POST');
8219
+ res.status(common.HttpStatus.METHOD_NOT_ALLOWED).json({
8220
+ statusCode: common.HttpStatus.METHOD_NOT_ALLOWED,
8221
+ message: 'Method Not Allowed'
8222
+ });
8223
+ }
7516
8224
  }
7517
8225
  ]);
7518
8226
  return McpController;
@@ -7522,6 +8230,14 @@ __decorate([
7522
8230
  __param(0, common.Req()),
7523
8231
  __param(1, common.Res())
7524
8232
  ], exports.McpController.prototype, "handleMcpRequest", null);
8233
+ __decorate([
8234
+ common.Get(),
8235
+ __param(0, common.Res())
8236
+ ], exports.McpController.prototype, "handleUnsupportedGet", null);
8237
+ __decorate([
8238
+ common.Delete(),
8239
+ __param(0, common.Res())
8240
+ ], exports.McpController.prototype, "handleUnsupportedDelete", null);
7525
8241
  exports.McpController = McpController_1 = __decorate([
7526
8242
  common.Controller('mcp'),
7527
8243
  __param(0, common.Inject(exports.McpServerFactoryService))
@@ -7607,6 +8323,13 @@ function _unsupported_iterable_to_array$2(o, minLen) {
7607
8323
  * URIs must live at the host root. Apps need to exclude `.well-known/{*path}`
7608
8324
  * from any global API route prefix (see `FIREBASE_SERVER_OIDC_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE`
7609
8325
  * in `firebase-server/oidc` for the corresponding OIDC excludes).
8326
+ *
8327
+ * Because the document must be reachable at the origin root, the MCP endpoint has to be
8328
+ * exposed through a host that serves the app at `/` — Firebase Hosting, or the hosting
8329
+ * emulator in development. Pointing a client straight at the Functions emulator origin
8330
+ * (`http://localhost:<port>/<project>/<region>/<function>/mcp`) breaks discovery: that
8331
+ * runtime only routes `/<project>/<region>/<function>/…`, so nothing answers at the root
8332
+ * and a client that has not yet seen a 401 challenge cannot find the issuer.
7610
8333
  */ exports.McpWellKnownController = /*#__PURE__*/ function() {
7611
8334
  function McpWellKnownController(mcpConfig, oidcProviderConfigService) {
7612
8335
  _class_call_check$1(this, McpWellKnownController);
@@ -7617,12 +8340,26 @@ function _unsupported_iterable_to_array$2(o, minLen) {
7617
8340
  }
7618
8341
  _create_class$1(McpWellKnownController, [
7619
8342
  {
7620
- key: "getProtectedResourceMetadata",
8343
+ /**
8344
+ * Serves both RFC 9728 discovery URLs — the primary form (§3.1), which inserts the resource's
8345
+ * path after the well-known prefix (`/.well-known/oauth-protected-resource/mcp`), and the bare
8346
+ * form (`/.well-known/oauth-protected-resource`) that clients probe as a fallback.
8347
+ *
8348
+ * The document is identical either way; the resource identity comes from
8349
+ * {@link McpModuleConfig.mcpUrl}, not from the request path. Serving the primary form means
8350
+ * discovery succeeds on a client's first probe rather than depending on it implementing the
8351
+ * fallback.
8352
+ *
8353
+ * @returns The protected-resource metadata document.
8354
+ */ key: "getProtectedResourceMetadata",
7621
8355
  value: function getProtectedResourceMetadata() {
7622
8356
  var _this_mcpConfig = this.mcpConfig, mcpUrl = _this_mcpConfig.mcpUrl, oidcIssuer = _this_mcpConfig.oidcIssuer, scopesFilter = _this_mcpConfig.scopesSupported;
7623
- // Base scope list is the OIDC provider's own `scopes_supported`, so the resource
7624
- // advertises exactly what the issuer grants; the optional filter narrows it.
7625
- var providerScopes = this.oidcProviderConfigService.scopesSupported;
8357
+ // Base scope list is what the issuer grants an arbitrary client, NOT its full
8358
+ // `scopes_supported`: a dynamic-registration client copies this list verbatim onto
8359
+ // `/authorize`, and a provider-profile-gated scope in it would be hard-rejected at the
8360
+ // consent unlock gate (`access_denied`) for every client lacking that profile assignment.
8361
+ // The optional filter narrows the list further.
8362
+ var providerScopes = this.oidcProviderConfigService.clientRequestableScopesSupported;
7626
8363
  var scopes = scopesFilter ? scopesFilter(providerScopes) : providerScopes;
7627
8364
  return _object_spread({
7628
8365
  resource: mcpUrl,
@@ -7638,7 +8375,10 @@ function _unsupported_iterable_to_array$2(o, minLen) {
7638
8375
  return McpWellKnownController;
7639
8376
  }();
7640
8377
  __decorate([
7641
- common.Get('oauth-protected-resource')
8378
+ common.Get([
8379
+ 'oauth-protected-resource',
8380
+ 'oauth-protected-resource/{*path}'
8381
+ ])
7642
8382
  ], exports.McpWellKnownController.prototype, "getProtectedResourceMetadata", null);
7643
8383
  exports.McpWellKnownController = __decorate([
7644
8384
  common.Controller('.well-known'),
@@ -7976,6 +8716,8 @@ exports.MCP_AUTH_ROLE_READER = MCP_AUTH_ROLE_READER;
7976
8716
  exports.MCP_CALL_TYPE_ABBREVIATIONS = MCP_CALL_TYPE_ABBREVIATIONS;
7977
8717
  exports.MCP_MANIFEST_VERSION = MCP_MANIFEST_VERSION;
7978
8718
  exports.MCP_MODEL_GET_BATCH_SIZE = MCP_MODEL_GET_BATCH_SIZE;
8719
+ exports.MCP_MODEL_ROLES_MAX_KEYS = MCP_MODEL_ROLES_MAX_KEYS;
8720
+ exports.MCP_MODEL_ROLES_TARGET_UID_PREDICATE = MCP_MODEL_ROLES_TARGET_UID_PREDICATE;
7979
8721
  exports.MCP_TOOL_NAME_MAX_LENGTH = MCP_TOOL_NAME_MAX_LENGTH;
7980
8722
  exports.MCP_TOOL_NAME_WARN_LENGTH = MCP_TOOL_NAME_WARN_LENGTH;
7981
8723
  exports.MCP_WRITE_TOOL_DESCRIPTION_PREFIX = MCP_WRITE_TOOL_DESCRIPTION_PREFIX;
@@ -7990,6 +8732,9 @@ exports.MODEL_INFO_DISPATCH_MODEL_TYPE = MODEL_INFO_DISPATCH_MODEL_TYPE;
7990
8732
  exports.MODEL_INFO_GROUPS_HINT = MODEL_INFO_GROUPS_HINT;
7991
8733
  exports.MODEL_INFO_TOOL_NAME = MODEL_INFO_TOOL_NAME;
7992
8734
  exports.MODEL_INFO_UNGROUPED_LABEL = MODEL_INFO_UNGROUPED_LABEL;
8735
+ exports.MODEL_ROLES_DISPATCH_CALL = MODEL_ROLES_DISPATCH_CALL;
8736
+ exports.MODEL_ROLES_DISPATCH_MODEL_TYPE = MODEL_ROLES_DISPATCH_MODEL_TYPE;
8737
+ exports.MODEL_ROLES_TOOL_NAME = MODEL_ROLES_TOOL_NAME;
7993
8738
  exports.McpAnalyticsService = McpAnalyticsService;
7994
8739
  exports.McpModuleConfig = McpModuleConfig;
7995
8740
  exports.ROUTE_MANIFEST_VERSION = ROUTE_MANIFEST_VERSION;
@@ -8012,6 +8757,7 @@ exports.createEnumInfoTool = createEnumInfoTool;
8012
8757
  exports.createModelDecodeTool = createModelDecodeTool;
8013
8758
  exports.createModelGetTool = createModelGetTool;
8014
8759
  exports.createModelInfoTool = createModelInfoTool;
8760
+ exports.createModelRolesTool = createModelRolesTool;
8015
8761
  exports.createUrlModelsTool = createUrlModelsTool;
8016
8762
  exports.decodeFirestoreModelKey = decodeFirestoreModelKey;
8017
8763
  exports.extractMcpReasonFromArgs = extractMcpReasonFromArgs;