firebase-tools 14.20.0 → 14.22.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 (93) hide show
  1. package/lib/appUtils.js +2 -1
  2. package/lib/command.js +5 -9
  3. package/lib/commands/dataconnect-sdk-generate.js +66 -11
  4. package/lib/commands/deploy.js +6 -4
  5. package/lib/commands/firestore-databases-clone.js +99 -0
  6. package/lib/commands/functions-secrets-set.js +19 -1
  7. package/lib/commands/hosting-sites-create.js +4 -3
  8. package/lib/commands/index.js +1 -0
  9. package/lib/commands/init.js +12 -8
  10. package/lib/commands/internaltesting-functions-discover.js +1 -3
  11. package/lib/dataconnect/provisionCloudSql.js +3 -2
  12. package/lib/deploy/extensions/prepare.js +3 -1
  13. package/lib/deploy/functions/checkIam.js +1 -1
  14. package/lib/deploy/functions/functionsDeployHelper.js +8 -7
  15. package/lib/deploy/functions/params.js +15 -5
  16. package/lib/deploy/functions/prepare.js +9 -6
  17. package/lib/detectProjectRoot.js +1 -1
  18. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  19. package/lib/emulator/hubExport.js +5 -0
  20. package/lib/experiments.js +0 -7
  21. package/lib/firestore/api.js +15 -0
  22. package/lib/firestore/util.js +22 -1
  23. package/lib/frameworks/angular/index.js +1 -1
  24. package/lib/frameworks/flutter/index.js +1 -1
  25. package/lib/frameworks/next/index.js +1 -1
  26. package/lib/frameworks/nuxt/index.js +1 -1
  27. package/lib/frameworks/vite/index.js +5 -2
  28. package/lib/functions/projectConfig.js +5 -1
  29. package/lib/functions/secrets.js +14 -1
  30. package/lib/hosting/interactive.js +14 -19
  31. package/lib/init/features/dataconnect/index.js +21 -20
  32. package/lib/init/features/dataconnect/sdk.js +44 -21
  33. package/lib/init/features/functions/index.js +1 -0
  34. package/lib/init/features/hosting/index.js +96 -93
  35. package/lib/init/features/index.js +3 -2
  36. package/lib/init/index.js +7 -3
  37. package/lib/mcp/index.js +46 -18
  38. package/lib/mcp/prompt.js +4 -1
  39. package/lib/mcp/prompts/core/consult.js +1 -1
  40. package/lib/mcp/prompts/core/deploy.js +1 -1
  41. package/lib/mcp/prompts/core/init.js +1 -1
  42. package/lib/mcp/prompts/crashlytics/connect.js +1 -1
  43. package/lib/mcp/prompts/dataconnect/schema.js +1 -1
  44. package/lib/mcp/prompts/index.js +20 -10
  45. package/lib/mcp/resources/guides/init_backend.js +3 -26
  46. package/lib/mcp/resources/guides/init_hosting.js +15 -10
  47. package/lib/mcp/tool.js +17 -2
  48. package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
  49. package/lib/mcp/tools/apphosting/list_backends.js +1 -1
  50. package/lib/mcp/tools/auth/get_users.js +1 -1
  51. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  52. package/lib/mcp/tools/auth/update_user.js +1 -1
  53. package/lib/mcp/tools/core/create_android_sha.js +1 -1
  54. package/lib/mcp/tools/core/create_app.js +1 -1
  55. package/lib/mcp/tools/core/create_project.js +1 -1
  56. package/lib/mcp/tools/core/get_environment.js +1 -1
  57. package/lib/mcp/tools/core/get_project.js +1 -1
  58. package/lib/mcp/tools/core/get_sdk_config.js +1 -1
  59. package/lib/mcp/tools/core/get_security_rules.js +1 -1
  60. package/lib/mcp/tools/core/init.js +30 -2
  61. package/lib/mcp/tools/core/list_apps.js +1 -1
  62. package/lib/mcp/tools/core/list_projects.js +1 -1
  63. package/lib/mcp/tools/core/login.js +1 -1
  64. package/lib/mcp/tools/core/logout.js +1 -1
  65. package/lib/mcp/tools/core/read_resources.js +1 -1
  66. package/lib/mcp/tools/core/update_environment.js +1 -1
  67. package/lib/mcp/tools/core/validate_security_rules.js +15 -1
  68. package/lib/mcp/tools/crashlytics/events.js +3 -3
  69. package/lib/mcp/tools/crashlytics/issues.js +3 -3
  70. package/lib/mcp/tools/crashlytics/notes.js +4 -4
  71. package/lib/mcp/tools/crashlytics/reports.js +6 -6
  72. package/lib/mcp/tools/dataconnect/compile.js +1 -1
  73. package/lib/mcp/tools/dataconnect/execute.js +1 -1
  74. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  75. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  76. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  77. package/lib/mcp/tools/firestore/delete_document.js +1 -1
  78. package/lib/mcp/tools/firestore/get_documents.js +1 -1
  79. package/lib/mcp/tools/firestore/list_collections.js +1 -1
  80. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  81. package/lib/mcp/tools/functions/get_logs.js +1 -1
  82. package/lib/mcp/tools/index.js +14 -4
  83. package/lib/mcp/tools/messaging/send_message.js +1 -1
  84. package/lib/mcp/tools/realtime_database/get_data.js +1 -1
  85. package/lib/mcp/tools/realtime_database/set_data.js +1 -1
  86. package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
  87. package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
  88. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  89. package/lib/mcp/util/availability.js +22 -0
  90. package/lib/mcp/util/crashlytics/availability.js +81 -0
  91. package/lib/mcp/util.js +26 -6
  92. package/package.json +1 -1
  93. package/schema/firebase-config.json +3 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.doSetup = void 0;
3
+ exports.actuate = exports.askQuestions = void 0;
4
4
  const clc = require("colorette");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const path_1 = require("path");
@@ -15,13 +15,16 @@ const getDefaultHostingSite_1 = require("../../../getDefaultHostingSite");
15
15
  const utils_1 = require("../../../utils");
16
16
  const interactive_1 = require("../../../hosting/interactive");
17
17
  const templates_1 = require("../../../templates");
18
+ const error_1 = require("../../../error");
19
+ const api_1 = require("../../../hosting/api");
18
20
  const INDEX_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/index.html");
19
21
  const MISSING_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/404.html");
20
22
  const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"];
21
- async function doSetup(setup, config, options) {
22
- var _a, _b, _c, _d;
23
- var _e, _f, _g;
24
- setup.hosting = {};
23
+ async function askQuestions(setup, config, options) {
24
+ var _a, _b, _c, _d, _e, _f;
25
+ var _g, _h, _j, _k, _l;
26
+ setup.featureInfo = setup.featureInfo || {};
27
+ setup.featureInfo.hosting = {};
25
28
  if (setup.projectId) {
26
29
  let hasHostingSite = true;
27
30
  try {
@@ -33,61 +36,50 @@ async function doSetup(setup, config, options) {
33
36
  }
34
37
  hasHostingSite = false;
35
38
  }
36
- if (!hasHostingSite) {
37
- const confirmCreate = await (0, prompt_1.confirm)({
39
+ if (!hasHostingSite &&
40
+ (await (0, prompt_1.confirm)({
38
41
  message: "A Firebase Hosting site is required to deploy. Would you like to create one now?",
39
42
  default: true,
40
- });
41
- if (confirmCreate) {
42
- const createOptions = {
43
- projectId: setup.projectId,
44
- nonInteractive: options.nonInteractive,
45
- };
46
- const newSite = await (0, interactive_1.interactiveCreateHostingSite)("", "", createOptions);
47
- logger_1.logger.info();
48
- (0, utils_1.logSuccess)(`Firebase Hosting site ${(0, utils_1.last)(newSite.name.split("/"))} created!`);
49
- logger_1.logger.info();
50
- }
43
+ }))) {
44
+ const createOptions = {
45
+ projectId: setup.projectId,
46
+ nonInteractive: options.nonInteractive,
47
+ };
48
+ setup.featureInfo.hosting.newSiteId = await (0, interactive_1.pickHostingSiteName)("", createOptions);
51
49
  }
52
50
  }
53
- let discoveredFramework = experiments.isEnabled("webframeworks")
54
- ? await (0, frameworks_1.discover)(config.projectDir, false)
55
- : undefined;
56
51
  if (experiments.isEnabled("webframeworks")) {
57
- if (discoveredFramework) {
58
- const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
59
- (_a = (_e = setup.hosting).useDiscoveredFramework) !== null && _a !== void 0 ? _a : (_e.useDiscoveredFramework = await (0, prompt_1.confirm)({
60
- message: `Detected an existing ${name} codebase in the current directory, should we use this?`,
52
+ let discoveredFramework = experiments.isEnabled("webframeworks")
53
+ ? await (0, frameworks_1.discover)(config.projectDir, false)
54
+ : undefined;
55
+ if (discoveredFramework &&
56
+ (await (0, prompt_1.confirm)({
57
+ message: `Detected an existing ${frameworks_1.WebFrameworks[discoveredFramework.framework].name} codebase in the current directory, do you want to use this?`,
61
58
  default: true,
62
- }));
63
- }
64
- if (setup.hosting.useDiscoveredFramework) {
65
- setup.hosting.source = ".";
66
- setup.hosting.useWebFrameworks = true;
59
+ }))) {
60
+ setup.featureInfo.hosting.source = ".";
61
+ setup.featureInfo.hosting.useWebFrameworks = true;
62
+ setup.featureInfo.hosting.useDiscoveredFramework = true;
63
+ setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
67
64
  }
68
65
  else {
69
- setup.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
66
+ setup.featureInfo.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
70
67
  }
71
- }
72
- if (setup.hosting.useWebFrameworks) {
73
- (_b = (_f = setup.hosting).source) !== null && _b !== void 0 ? _b : (_f.source = await (0, prompt_1.input)({
74
- message: "What folder would you like to use for your web application's root directory?",
75
- default: "hosting",
76
- }));
77
- if (setup.hosting.source !== ".")
78
- delete setup.hosting.useDiscoveredFramework;
79
- discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.hosting.source));
80
- if (discoveredFramework) {
81
- const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
82
- (_c = (_g = setup.hosting).useDiscoveredFramework) !== null && _c !== void 0 ? _c : (_g.useDiscoveredFramework = await (0, prompt_1.confirm)({
83
- message: `Detected an existing ${name} codebase in ${setup.hosting.source}, should we use this?`,
84
- default: true,
68
+ if (setup.featureInfo.hosting.useWebFrameworks) {
69
+ (_a = (_g = setup.featureInfo.hosting).source) !== null && _a !== void 0 ? _a : (_g.source = await (0, prompt_1.input)({
70
+ message: "What folder would you like to use for your web application's root directory?",
71
+ default: "hosting",
85
72
  }));
86
- }
87
- if (setup.hosting.useDiscoveredFramework && discoveredFramework) {
88
- setup.hosting.webFramework = discoveredFramework.framework;
89
- }
90
- else {
73
+ discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
74
+ if (discoveredFramework) {
75
+ const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
76
+ (_b = (_h = setup.featureInfo.hosting).useDiscoveredFramework) !== null && _b !== void 0 ? _b : (_h.useDiscoveredFramework = await (0, prompt_1.confirm)({
77
+ message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
78
+ default: true,
79
+ }));
80
+ if (setup.featureInfo.hosting.useDiscoveredFramework)
81
+ setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
82
+ }
91
83
  const choices = [];
92
84
  for (const value in frameworks_1.WebFrameworks) {
93
85
  if (frameworks_1.WebFrameworks[value]) {
@@ -96,32 +88,20 @@ async function doSetup(setup, config, options) {
96
88
  choices.push({ name, value });
97
89
  }
98
90
  }
99
- const defaultChoice = (_d = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _d === void 0 ? void 0 : _d.value;
100
- setup.hosting.whichFramework =
101
- setup.hosting.whichFramework ||
91
+ const defaultChoice = (_c = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _c === void 0 ? void 0 : _c.value;
92
+ (_d = (_j = setup.featureInfo.hosting).webFramework) !== null && _d !== void 0 ? _d : (_j.webFramework = await (0, prompt_1.select)({
93
+ message: "Please choose the framework:",
94
+ default: defaultChoice,
95
+ choices,
96
+ }));
97
+ setup.featureInfo.hosting.region =
98
+ setup.featureInfo.hosting.region ||
102
99
  (await (0, prompt_1.select)({
103
- message: "Please choose the framework:",
104
- default: defaultChoice,
105
- choices,
100
+ message: "In which region would you like to host server-side content, if applicable?",
101
+ default: constants_1.DEFAULT_REGION,
102
+ choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
106
103
  }));
107
- if (discoveredFramework)
108
- (0, node_fs_1.rmSync)(setup.hosting.source, { recursive: true });
109
- await frameworks_1.WebFrameworks[setup.hosting.whichFramework].init(setup, config);
110
104
  }
111
- setup.hosting.region =
112
- setup.hosting.region ||
113
- (await (0, prompt_1.select)({
114
- message: "In which region would you like to host server-side content, if applicable?",
115
- default: constants_1.DEFAULT_REGION,
116
- choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
117
- }));
118
- setup.config.hosting = {
119
- source: setup.hosting.source,
120
- ignore: DEFAULT_IGNORES,
121
- frameworksBackend: {
122
- region: setup.hosting.region,
123
- },
124
- };
125
105
  }
126
106
  else {
127
107
  logger_1.logger.info();
@@ -129,35 +109,58 @@ async function doSetup(setup, config, options) {
129
109
  logger_1.logger.info(`will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you`);
130
110
  logger_1.logger.info("have a build process for your assets, use your build's output directory.");
131
111
  logger_1.logger.info();
132
- setup.hosting.public =
133
- setup.hosting.public ||
134
- (await (0, prompt_1.input)({
135
- message: "What do you want to use as your public directory?",
136
- default: "public",
137
- }));
138
- setup.hosting.spa =
139
- setup.hosting.spa ||
140
- (await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
112
+ (_e = (_k = setup.featureInfo.hosting).public) !== null && _e !== void 0 ? _e : (_k.public = await (0, prompt_1.input)({
113
+ message: "What do you want to use as your public directory?",
114
+ default: "public",
115
+ }));
116
+ (_f = (_l = setup.featureInfo.hosting).spa) !== null && _f !== void 0 ? _f : (_l.spa = await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
117
+ }
118
+ if (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?")) {
119
+ return (0, github_1.initGitHub)(setup);
120
+ }
121
+ }
122
+ exports.askQuestions = askQuestions;
123
+ async function actuate(setup, config, options) {
124
+ var _a;
125
+ const hostingInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.hosting;
126
+ if (!hostingInfo) {
127
+ throw new error_1.FirebaseError("Could not find hosting info in setup.featureInfo.hosting. This should not happen.", { exit: 2 });
128
+ }
129
+ if (hostingInfo.newSiteId && setup.projectId) {
130
+ await (0, api_1.createSite)(setup.projectId, hostingInfo.newSiteId);
131
+ logger_1.logger.info();
132
+ (0, utils_1.logSuccess)(`Firebase Hosting site ${hostingInfo.newSiteId} created!`);
133
+ logger_1.logger.info();
134
+ }
135
+ if (hostingInfo.webFramework) {
136
+ if (!hostingInfo.useDiscoveredFramework) {
137
+ if (hostingInfo.source && (0, node_fs_1.existsSync)(hostingInfo.source)) {
138
+ (0, node_fs_1.rmSync)(hostingInfo.source, { recursive: true });
139
+ }
140
+ await frameworks_1.WebFrameworks[hostingInfo.webFramework].init(setup, config);
141
+ }
141
142
  setup.config.hosting = {
142
- public: setup.hosting.public,
143
+ source: hostingInfo.source,
143
144
  ignore: DEFAULT_IGNORES,
145
+ frameworksBackend: {
146
+ region: hostingInfo.region,
147
+ },
144
148
  };
145
149
  }
146
- setup.hosting.github =
147
- setup.hosting.github || (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?"));
148
- if (!setup.hosting.useWebFrameworks) {
149
- if (setup.hosting.spa) {
150
+ else {
151
+ setup.config.hosting = {
152
+ public: hostingInfo.public,
153
+ ignore: DEFAULT_IGNORES,
154
+ };
155
+ if (hostingInfo.spa) {
150
156
  setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }];
151
157
  }
152
158
  else {
153
- await config.askWriteProjectFile(`${setup.hosting.public}/404.html`, MISSING_TEMPLATE);
159
+ await config.askWriteProjectFile(`${hostingInfo.public}/404.html`, MISSING_TEMPLATE, !!options.force);
154
160
  }
155
161
  const c = new apiv2_1.Client({ urlPrefix: "https://www.gstatic.com", auth: false });
156
162
  const response = await c.get("/firebasejs/releases.json");
157
- await config.askWriteProjectFile(`${setup.hosting.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version));
158
- }
159
- if (setup.hosting.github) {
160
- return (0, github_1.initGitHub)(setup);
163
+ await config.askWriteProjectFile(`${hostingInfo.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version), !!options.force);
161
164
  }
162
165
  }
163
- exports.doSetup = doSetup;
166
+ exports.actuate = actuate;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hosting = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
3
+ exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hostingActuate = exports.hostingAskQuestions = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
4
4
  var account_1 = require("./account");
5
5
  Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
6
6
  var database_1 = require("./database");
@@ -12,7 +12,8 @@ Object.defineProperty(exports, "firestoreActuate", { enumerable: true, get: func
12
12
  var functions_1 = require("./functions");
13
13
  Object.defineProperty(exports, "functions", { enumerable: true, get: function () { return functions_1.doSetup; } });
14
14
  var hosting_1 = require("./hosting");
15
- Object.defineProperty(exports, "hosting", { enumerable: true, get: function () { return hosting_1.doSetup; } });
15
+ Object.defineProperty(exports, "hostingAskQuestions", { enumerable: true, get: function () { return hosting_1.askQuestions; } });
16
+ Object.defineProperty(exports, "hostingActuate", { enumerable: true, get: function () { return hosting_1.actuate; } });
16
17
  var storage_1 = require("./storage");
17
18
  Object.defineProperty(exports, "storageAskQuestions", { enumerable: true, get: function () { return storage_1.askQuestions; } });
18
19
  Object.defineProperty(exports, "storageActuate", { enumerable: true, get: function () { return storage_1.actuate; } });
package/lib/init/index.js CHANGED
@@ -30,7 +30,11 @@ const featuresList = [
30
30
  actuate: features.dataconnectSdkActuate,
31
31
  },
32
32
  { name: "functions", doSetup: features.functions },
33
- { name: "hosting", doSetup: features.hosting },
33
+ {
34
+ name: "hosting",
35
+ askQuestions: features.hostingAskQuestions,
36
+ actuate: features.hostingActuate,
37
+ },
34
38
  {
35
39
  name: "storage",
36
40
  askQuestions: features.storageAskQuestions,
@@ -84,7 +88,7 @@ async function init(setup, config, options) {
84
88
  await f.postSetup(setup, config, options);
85
89
  }
86
90
  const duration = Math.floor((process.uptime() - start) * 1000);
87
- await (0, track_1.trackGA4)("product_init", { feature: nextFeature }, duration);
91
+ void (0, track_1.trackGA4)("product_init", { feature: nextFeature }, duration);
88
92
  return init(setup, config, options);
89
93
  }
90
94
  }
@@ -105,7 +109,7 @@ async function actuate(setup, config, options) {
105
109
  }
106
110
  }
107
111
  const duration = Math.floor((process.uptime() - start) * 1000);
108
- await (0, track_1.trackGA4)("product_init_mcp", { feature: nextFeature }, duration);
112
+ void (0, track_1.trackGA4)("product_init_mcp", { feature: nextFeature }, duration);
109
113
  return actuate(setup, config, options);
110
114
  }
111
115
  }
package/lib/mcp/index.js CHANGED
@@ -23,6 +23,7 @@ const env_1 = require("../env");
23
23
  const timeout_1 = require("../timeout");
24
24
  const resources_1 = require("./resources");
25
25
  const crossSpawn = require("cross-spawn");
26
+ const availability_1 = require("./util/availability");
26
27
  const SERVER_VERSION = "0.3.0";
27
28
  const cmd = new command_1.Command("mcp");
28
29
  const orderedLogLevels = [
@@ -50,6 +51,7 @@ class FirebaseMcpServer {
50
51
  constructor(options) {
51
52
  this._ready = false;
52
53
  this._readyPromises = [];
54
+ this._pendingMessages = [];
53
55
  this.currentLogLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined;
54
56
  this.logger = Object.fromEntries(orderedLogLevels.map((logLevel) => [
55
57
  logLevel,
@@ -92,8 +94,7 @@ class FirebaseMcpServer {
92
94
  this.currentLogLevel = params.level;
93
95
  return {};
94
96
  });
95
- this.detectProjectRoot();
96
- this.detectActiveFeatures();
97
+ void this.detectProjectSetup();
97
98
  }
98
99
  ready() {
99
100
  if (this._ready)
@@ -118,6 +119,10 @@ class FirebaseMcpServer {
118
119
  configstore_1.configstore.set(this.clientConfigKey, newConfig);
119
120
  return newConfig;
120
121
  }
122
+ async detectProjectSetup() {
123
+ await this.detectProjectRoot();
124
+ await this.detectActiveFeatures();
125
+ }
121
126
  async detectProjectRoot() {
122
127
  await (0, timeout_1.timeoutFallback)(this.ready(), null, 2000);
123
128
  if (this.cachedProjectDir)
@@ -132,10 +137,12 @@ class FirebaseMcpServer {
132
137
  if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
133
138
  return this.detectedFeatures;
134
139
  this.log("debug", "detecting active features of Firebase MCP server...");
135
- const options = await this.resolveOptions();
136
- const projectId = await this.getProjectId();
140
+ const projectId = (await this.getProjectId()) || "";
141
+ const accountEmail = await this.getAuthenticatedUser();
142
+ const ctx = this._createMcpContext(projectId, accountEmail);
137
143
  const detected = await Promise.all(types_1.SERVER_FEATURES.map(async (f) => {
138
- if (await (0, util_1.checkFeatureActive)(f, projectId, options))
144
+ const availabilityCheck = (0, availability_1.getDefaultFeatureAvailabilityCheck)(f);
145
+ if (await availabilityCheck(ctx))
139
146
  return f;
140
147
  return null;
141
148
  }));
@@ -164,19 +171,29 @@ class FirebaseMcpServer {
164
171
  const host = emulatorInfo.host.includes(":") ? `[${emulatorInfo.host}]` : emulatorInfo.host;
165
172
  return `http://${host}:${emulatorInfo.port}`;
166
173
  }
167
- get availableTools() {
174
+ async getAvailableTools() {
168
175
  var _a;
169
- return (0, index_1.availableTools)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
176
+ const features = ((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures;
177
+ const projectId = (await this.getProjectId()) || "";
178
+ const accountEmail = await this.getAuthenticatedUser();
179
+ const ctx = this._createMcpContext(projectId, accountEmail);
180
+ return (0, index_1.availableTools)(ctx, features);
170
181
  }
171
- getTool(name) {
172
- return this.availableTools.find((t) => t.mcp.name === name) || null;
182
+ async getTool(name) {
183
+ const tools = await this.getAvailableTools();
184
+ return tools.find((t) => t.mcp.name === name) || null;
173
185
  }
174
- get availablePrompts() {
186
+ async getAvailablePrompts() {
175
187
  var _a;
176
- return (0, index_2.availablePrompts)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
188
+ const features = ((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures;
189
+ const projectId = (await this.getProjectId()) || "";
190
+ const accountEmail = await this.getAuthenticatedUser();
191
+ const ctx = this._createMcpContext(projectId, accountEmail);
192
+ return (0, index_2.availablePrompts)(ctx, features);
177
193
  }
178
- getPrompt(name) {
179
- return this.availablePrompts.find((p) => p.mcp.name === name) || null;
194
+ async getPrompt(name) {
195
+ const prompts = await this.getAvailablePrompts();
196
+ return prompts.find((p) => p.mcp.name === name) || null;
180
197
  }
181
198
  setProjectRoot(newRoot) {
182
199
  this.updateStoredClientConfig({ projectRoot: newRoot });
@@ -229,8 +246,9 @@ class FirebaseMcpServer {
229
246
  await this.trackGA4("mcp_list_tools");
230
247
  const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
231
248
  this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
249
+ const availableTools = await this.getAvailableTools();
232
250
  return {
233
- tools: this.availableTools.map((t) => t.mcp),
251
+ tools: availableTools.map((t) => t.mcp),
234
252
  _meta: {
235
253
  projectRoot: this.cachedProjectDir,
236
254
  projectDetected: hasActiveProject,
@@ -245,7 +263,7 @@ class FirebaseMcpServer {
245
263
  await this.detectProjectRoot();
246
264
  const toolName = request.params.name;
247
265
  const toolArgs = request.params.arguments;
248
- const tool = this.getTool(toolName);
266
+ const tool = await this.getTool(toolName);
249
267
  if (!tool)
250
268
  throw new Error(`Tool '${toolName}' could not be found.`);
251
269
  if (!((_a = tool.mcp._meta) === null || _a === void 0 ? void 0 : _a.optionalProjectDir)) {
@@ -291,7 +309,7 @@ class FirebaseMcpServer {
291
309
  await this.trackGA4("mcp_list_prompts");
292
310
  const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
293
311
  return {
294
- prompts: this.availablePrompts.map((p) => ({
312
+ prompts: (await this.getAvailablePrompts()).map((p) => ({
295
313
  name: p.mcp.name,
296
314
  description: p.mcp.description,
297
315
  annotations: p.mcp.annotations,
@@ -310,7 +328,7 @@ class FirebaseMcpServer {
310
328
  await this.detectProjectRoot();
311
329
  const promptName = req.params.name;
312
330
  const promptArgs = req.params.arguments || {};
313
- const prompt = this.getPrompt(promptName);
331
+ const prompt = await this.getPrompt(promptName);
314
332
  if (!prompt) {
315
333
  throw new Error(`Prompt '${promptName}' could not be found.`);
316
334
  }
@@ -376,8 +394,18 @@ class FirebaseMcpServer {
376
394
  if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
377
395
  return;
378
396
  }
379
- if (this._ready)
397
+ if (this._ready) {
398
+ while (this._pendingMessages.length) {
399
+ const message = this._pendingMessages.shift();
400
+ if (!message)
401
+ continue;
402
+ this.server.sendLoggingMessage({ level: message.level, data: message.data });
403
+ }
380
404
  void this.server.sendLoggingMessage({ level, data });
405
+ }
406
+ else {
407
+ this._pendingMessages.push({ level, data });
408
+ }
381
409
  }
382
410
  }
383
411
  exports.FirebaseMcpServer = FirebaseMcpServer;
package/lib/mcp/prompt.js CHANGED
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prompt = void 0;
4
- function prompt(options, fn) {
4
+ const availability_1 = require("./util/availability");
5
+ function prompt(feature, options, fn, isAvailable) {
6
+ const isAvailableFunc = isAvailable || (0, availability_1.getDefaultFeatureAvailabilityCheck)(feature);
5
7
  return {
6
8
  mcp: options,
7
9
  fn,
10
+ isAvailable: isAvailableFunc,
8
11
  };
9
12
  }
10
13
  exports.prompt = prompt;
@@ -5,7 +5,7 @@ const appUtils_1 = require("../../../appUtils");
5
5
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
6
6
  const errors_1 = require("../../errors");
7
7
  const prompt_1 = require("../../prompt");
8
- exports.consult = (0, prompt_1.prompt)({
8
+ exports.consult = (0, prompt_1.prompt)("core", {
9
9
  name: "consult",
10
10
  description: "Use this command to consult the Firebase Assistant with access to detailed up-to-date documentation for the Firebase platform.",
11
11
  arguments: [
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deploy = void 0;
4
4
  const prompt_1 = require("../../prompt");
5
- exports.deploy = (0, prompt_1.prompt)({
5
+ exports.deploy = (0, prompt_1.prompt)("core", {
6
6
  name: "deploy",
7
7
  description: "Use this command to deploy resources to Firebase.",
8
8
  arguments: [
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.init = void 0;
4
4
  const appUtils_1 = require("../../../appUtils");
5
5
  const prompt_1 = require("../../prompt");
6
- exports.init = (0, prompt_1.prompt)({
6
+ exports.init = (0, prompt_1.prompt)("core", {
7
7
  name: "init",
8
8
  description: "Use this command to set up Firebase services, like backend and AI features.",
9
9
  annotations: {
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.connect = void 0;
4
4
  const prompt_1 = require("../../prompt");
5
- exports.connect = (0, prompt_1.prompt)({
5
+ exports.connect = (0, prompt_1.prompt)("crashlytics", {
6
6
  name: "connect",
7
7
  omitPrefix: false,
8
8
  description: "Access a Firebase application's Crashlytics data.",
@@ -18,7 +18,7 @@ ${(_a = fdcServices[0].schema.source.files) === null || _a === void 0 ? void 0 :
18
18
  function renderErrors(errors) {
19
19
  return `\n\n## Current Schema Build Errors\n\n${errors || "<NO ERRORS>"}`;
20
20
  }
21
- exports.schema = (0, prompt_1.prompt)({
21
+ exports.schema = (0, prompt_1.prompt)("core", {
22
22
  name: "schema",
23
23
  description: "Generate or update your Firebase Data Connect schema.",
24
24
  arguments: [
@@ -5,15 +5,15 @@ const core_1 = require("./core");
5
5
  const dataconnect_1 = require("./dataconnect");
6
6
  const crashlytics_1 = require("./crashlytics");
7
7
  const prompts = {
8
- core: core_1.corePrompts,
8
+ core: namespacePrompts(core_1.corePrompts, "core"),
9
9
  firestore: [],
10
10
  storage: [],
11
- dataconnect: dataconnect_1.dataconnectPrompts,
11
+ dataconnect: namespacePrompts(dataconnect_1.dataconnectPrompts, "dataconnect"),
12
12
  auth: [],
13
13
  messaging: [],
14
14
  functions: [],
15
15
  remoteconfig: [],
16
- crashlytics: crashlytics_1.crashlyticsPrompts,
16
+ crashlytics: namespacePrompts(crashlytics_1.crashlyticsPrompts, "crashlytics"),
17
17
  apphosting: [],
18
18
  database: [],
19
19
  };
@@ -33,23 +33,33 @@ function namespacePrompts(promptsToNamespace, feature) {
33
33
  return newPrompt;
34
34
  });
35
35
  }
36
- function availablePrompts(activeFeatures) {
37
- const allPrompts = [];
36
+ async function availablePrompts(ctx, activeFeatures) {
37
+ const allPrompts = getAllPrompts(activeFeatures);
38
+ const availabilities = await Promise.all(allPrompts.map((p) => {
39
+ if (p.isAvailable) {
40
+ return p.isAvailable(ctx);
41
+ }
42
+ return true;
43
+ }));
44
+ return allPrompts.filter((_, i) => availabilities[i]);
45
+ }
46
+ exports.availablePrompts = availablePrompts;
47
+ function getAllPrompts(activeFeatures) {
48
+ const promptDefs = [];
38
49
  if (!(activeFeatures === null || activeFeatures === void 0 ? void 0 : activeFeatures.length)) {
39
50
  activeFeatures = Object.keys(prompts);
40
51
  }
41
52
  if (!activeFeatures.includes("core")) {
42
- activeFeatures = ["core", ...activeFeatures];
53
+ activeFeatures.unshift("core");
43
54
  }
44
55
  for (const feature of activeFeatures) {
45
- allPrompts.push(...namespacePrompts(prompts[feature], feature));
56
+ promptDefs.push(...prompts[feature]);
46
57
  }
47
- return allPrompts;
58
+ return promptDefs;
48
59
  }
49
- exports.availablePrompts = availablePrompts;
50
60
  function markdownDocsOfPrompts() {
51
61
  var _a, _b;
52
- const allPrompts = availablePrompts();
62
+ const allPrompts = getAllPrompts();
53
63
  let doc = `
54
64
  | Prompt Name | Feature Group | Description |
55
65
  | ----------- | ------------- | ----------- |`;
@@ -7,7 +7,7 @@ exports.init_backend = (0, resource_1.resource)({
7
7
  name: "backend_init_guide",
8
8
  title: "Firebase Backend Init Guide",
9
9
  description: "guides the coding agent through configuring Firebase backend services in the current project",
10
- }, async (uri) => {
10
+ }, async (uri, ctx) => {
11
11
  return {
12
12
  contents: [
13
13
  {
@@ -24,31 +24,8 @@ The user will likely need to setup Firestore, Authentication, and Hosting. Read
24
24
  3. [Firestore Rules](firebase://guides/init/firestore_rules): read this to setup the \`firestore.rules\` file for securing your database
25
25
  4. [Hosting](firebase://guides/init/hosting): read this if the user would like to deploy to Firebase Hosting
26
26
 
27
- **firebase.json**
28
- The firebase.json file is used to deploy Firebase products with the firebase deploy command.
29
-
30
- Here is an example firebase.json file with Firebase Hosting, Firestore, and Cloud Functions. Note that you do not need entries for services that the user isn't using. Do not remove sections from the user's firebase.json unless the user gives explicit permission. For more information, refer to [firebase.json file documentation](https://firebase.google.com/docs/cli/#the_firebasejson_file)
31
- \`\`\`json
32
- {
33
- "hosting": {
34
- "public": "public",
35
- "ignore": [
36
- "firebase.json",
37
- "**/.*",
38
- "**/node_modules/**"
39
- ]
40
- },
41
- "firestore": {
42
- "rules": "firestore.rules",
43
- "indexes": "firestore.indexes.json"
44
- },
45
- "functions": {
46
- "predeploy": [
47
- "npm --prefix "$RESOURCE_DIR" run lint",
48
- "npm --prefix "$RESOURCE_DIR" run build"
49
- ]
50
- }
51
- }
27
+ Once you are done setting up, ask the user if they would like to deploy.
28
+ If they say yes, run the command '${ctx.firebaseCliCommand} deploy --non-interactive' to do so.
52
29
  \`\`\`
53
30
  `.trim(),
54
31
  },