firebase-tools 14.3.0 → 14.4.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 (146) hide show
  1. package/lib/api.js +10 -2
  2. package/lib/apphosting/backend.js +72 -18
  3. package/lib/apphosting/githubConnections.js +13 -19
  4. package/lib/apphosting/rollout.js +2 -2
  5. package/lib/apphosting/secrets/dialogs.js +4 -4
  6. package/lib/apphosting/secrets/index.js +2 -2
  7. package/lib/auth.js +22 -2
  8. package/lib/bin/cli.js +1 -33
  9. package/lib/bin/mcp.js +12 -2
  10. package/lib/checkValidTargetFilters.js +1 -0
  11. package/lib/command.js +3 -2
  12. package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
  13. package/lib/commands/deploy.js +1 -0
  14. package/lib/commands/init.js +1 -1
  15. package/lib/commands/login-use.js +2 -11
  16. package/lib/commands/use.js +9 -8
  17. package/lib/config.js +1 -0
  18. package/lib/crashlytics/listTopIssues.js +44 -0
  19. package/lib/dataconnect/cloudAICompanionClient.js +67 -0
  20. package/lib/dataconnect/dataplaneClient.js +16 -1
  21. package/lib/dataconnect/types.js +5 -1
  22. package/lib/deploy/apphosting/args.js +2 -0
  23. package/lib/deploy/apphosting/deploy.js +74 -0
  24. package/lib/deploy/apphosting/index.js +9 -0
  25. package/lib/deploy/apphosting/prepare.js +141 -0
  26. package/lib/deploy/apphosting/release.js +53 -0
  27. package/lib/deploy/apphosting/util.js +65 -0
  28. package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
  29. package/lib/deploy/functions/checkIam.js +3 -3
  30. package/lib/deploy/functions/ensure.js +2 -1
  31. package/lib/deploy/functions/prepare.js +23 -16
  32. package/lib/deploy/functions/release/fabricator.js +4 -4
  33. package/lib/deploy/functions/release/index.js +1 -1
  34. package/lib/deploy/functions/runtimes/python/index.js +3 -0
  35. package/lib/deploy/functions/runtimes/supported/types.js +17 -11
  36. package/lib/deploy/index.js +2 -0
  37. package/lib/emulator/apphosting/index.js +1 -0
  38. package/lib/emulator/apphosting/serve.js +77 -3
  39. package/lib/emulator/auth/widget_ui.js +2 -1
  40. package/lib/emulator/controller.js +18 -4
  41. package/lib/emulator/dataconnectEmulator.js +9 -2
  42. package/lib/emulator/downloadableEmulatorInfo.json +60 -0
  43. package/lib/emulator/downloadableEmulators.js +25 -61
  44. package/lib/ensureApiEnabled.js +11 -1
  45. package/lib/experiments.js +1 -1
  46. package/lib/extensions/manifest.js +2 -2
  47. package/lib/fsAsync.js +9 -2
  48. package/lib/gcp/auth.js +32 -2
  49. package/lib/gcp/cloudbilling.js +12 -1
  50. package/lib/gcp/cloudfunctions.js +1 -2
  51. package/lib/gcp/cloudfunctionsv2.js +1 -2
  52. package/lib/gcp/cloudscheduler.js +2 -2
  53. package/lib/gcp/computeEngine.js +19 -2
  54. package/lib/gcp/devConnect.js +6 -1
  55. package/lib/gcp/firestore.js +24 -1
  56. package/lib/gcp/iam.js +1 -5
  57. package/lib/gcp/storage.js +43 -1
  58. package/lib/index.js +1 -2
  59. package/lib/init/features/apphosting.js +84 -6
  60. package/lib/init/features/database.js +64 -45
  61. package/lib/init/features/dataconnect/index.js +51 -53
  62. package/lib/init/features/emulators.js +9 -5
  63. package/lib/init/features/firestore/index.js +54 -23
  64. package/lib/init/features/firestore/indexes.js +23 -23
  65. package/lib/init/features/firestore/rules.js +35 -40
  66. package/lib/init/features/functions/index.js +2 -0
  67. package/lib/init/features/functions/javascript.js +3 -2
  68. package/lib/init/features/functions/typescript.js +3 -2
  69. package/lib/init/features/genkit/index.js +2 -1
  70. package/lib/init/features/hosting/github.js +3 -2
  71. package/lib/init/features/index.js +8 -4
  72. package/lib/init/features/remoteconfig.js +3 -2
  73. package/lib/init/index.js +76 -24
  74. package/lib/logger.js +71 -7
  75. package/lib/management/projects.js +25 -2
  76. package/lib/mcp/errors.js +1 -1
  77. package/lib/mcp/index.js +134 -51
  78. package/lib/mcp/tools/auth/{disable_auth_user.js → disable_user.js} +3 -3
  79. package/lib/mcp/tools/auth/get_user.js +38 -0
  80. package/lib/mcp/tools/auth/index.js +8 -6
  81. package/lib/mcp/tools/auth/list_users.js +47 -0
  82. package/lib/mcp/tools/auth/set_claims.js +43 -0
  83. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  84. package/lib/mcp/tools/core/{consult_firebase_assistant.js → consult_assistant.js} +4 -4
  85. package/lib/mcp/tools/core/create_android_sha.js +40 -0
  86. package/lib/mcp/tools/core/create_app.js +90 -0
  87. package/lib/mcp/tools/core/create_project.js +68 -0
  88. package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
  89. package/lib/mcp/tools/core/get_environment.js +51 -0
  90. package/lib/mcp/tools/{project → core}/get_sdk_config.js +6 -3
  91. package/lib/mcp/tools/core/index.js +20 -6
  92. package/lib/mcp/tools/core/init.js +129 -0
  93. package/lib/mcp/tools/core/update_environment.js +55 -0
  94. package/lib/mcp/tools/crashlytics/index.js +5 -0
  95. package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
  96. package/lib/mcp/tools/dataconnect/converter.js +30 -1
  97. package/lib/mcp/tools/dataconnect/emulator.js +32 -0
  98. package/lib/mcp/tools/dataconnect/execute_graphql.js +48 -0
  99. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +48 -0
  100. package/lib/mcp/tools/dataconnect/execute_mutation.js +62 -0
  101. package/lib/mcp/tools/dataconnect/execute_query.js +62 -0
  102. package/lib/mcp/tools/dataconnect/{generate_dataconnect_operation.js → generate_operation.js} +9 -9
  103. package/lib/mcp/tools/dataconnect/{generate_dataconnect_schema.js → generate_schema.js} +4 -4
  104. package/lib/mcp/tools/dataconnect/{get_dataconnect_connector.js → get_connector.js} +9 -9
  105. package/lib/mcp/tools/dataconnect/{get_dataconnect_schema.js → get_schema.js} +10 -10
  106. package/lib/mcp/tools/dataconnect/index.js +14 -10
  107. package/lib/mcp/tools/dataconnect/{list_dataconnect_services.js → list_services.js} +5 -5
  108. package/lib/mcp/tools/firestore/converter.js +47 -1
  109. package/lib/mcp/tools/firestore/delete_document.js +37 -0
  110. package/lib/mcp/tools/firestore/{get_firestore_documents.js → get_documents.js} +3 -3
  111. package/lib/mcp/tools/firestore/index.js +12 -6
  112. package/lib/mcp/tools/firestore/{list_firestore_collections.js → list_collections.js} +4 -9
  113. package/lib/mcp/tools/firestore/query_collection.js +116 -0
  114. package/lib/mcp/tools/index.js +44 -8
  115. package/lib/mcp/tools/messaging/index.js +5 -0
  116. package/lib/mcp/tools/messaging/send_message.js +42 -0
  117. package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
  118. package/lib/mcp/tools/remoteconfig/index.js +7 -0
  119. package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
  120. package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
  121. package/lib/mcp/tools/rules/get_rules.js +29 -0
  122. package/lib/mcp/tools/rules/validate_rules.js +98 -0
  123. package/lib/mcp/tools/storage/get_download_url.js +34 -0
  124. package/lib/mcp/tools/storage/{get_storage_rules.js → get_rules.js} +6 -6
  125. package/lib/mcp/tools/storage/index.js +8 -2
  126. package/lib/mcp/types.js +9 -1
  127. package/lib/mcp/util.js +29 -2
  128. package/lib/messaging/interfaces.js +2 -0
  129. package/lib/messaging/sendMessage.js +48 -0
  130. package/lib/remoteconfig/publish.js +39 -0
  131. package/lib/requireAuth.js +2 -2
  132. package/lib/utils.js +2 -37
  133. package/package.json +2 -1
  134. package/schema/firebase-config.json +62 -10
  135. package/templates/init/functions/javascript/package.lint.json +1 -1
  136. package/templates/init/functions/javascript/package.nolint.json +1 -1
  137. package/templates/init/functions/typescript/package.lint.json +1 -1
  138. package/templates/init/functions/typescript/package.nolint.json +2 -2
  139. package/lib/mcp/tools/auth/get_auth_user.js +0 -29
  140. package/lib/mcp/tools/auth/set_auth_claims.js +0 -34
  141. package/lib/mcp/tools/core/get_firebase_directory.js +0 -20
  142. package/lib/mcp/tools/core/set_firebase_directory.js +0 -33
  143. package/lib/mcp/tools/firestore/get_firestore_rules.js +0 -26
  144. package/lib/mcp/tools/project/index.js +0 -7
  145. /package/lib/mcp/tools/{project → core}/get_project.js +0 -0
  146. /package/lib/mcp/tools/{project → core}/list_apps.js +0 -0
@@ -5,7 +5,6 @@ const prompt_1 = require("../../prompt");
5
5
  const fsutils = require("../../fsutils");
6
6
  const clc = require("colorette");
7
7
  async function doSetup(setup, config) {
8
- setup.config.remoteconfig = {};
9
8
  const jsonFilePath = await (0, prompt_1.input)({
10
9
  message: "What file should be used for your Remote Config template?",
11
10
  default: "remoteconfig.template.json",
@@ -20,7 +19,9 @@ async function doSetup(setup, config) {
20
19
  return;
21
20
  }
22
21
  }
23
- setup.config.remoteconfig.template = jsonFilePath;
22
+ setup.config.remoteconfig = {
23
+ template: jsonFilePath,
24
+ };
24
25
  config.writeProjectFile(setup.config.remoteconfig.template, "{}");
25
26
  }
26
27
  exports.doSetup = doSetup;
package/lib/init/index.js CHANGED
@@ -1,45 +1,97 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.init = void 0;
3
+ exports.actuate = exports.init = void 0;
4
4
  const lodash_1 = require("lodash");
5
5
  const clc = require("colorette");
6
6
  const error_1 = require("../error");
7
7
  const logger_1 = require("../logger");
8
8
  const features = require("./features");
9
- const featureFns = new Map([
10
- ["account", features.account],
11
- ["database", features.database],
12
- ["firestore", features.firestore],
13
- ["dataconnect", features.dataconnect],
14
- ["dataconnect:sdk", features.dataconnectSdk],
15
- ["functions", features.functions],
16
- ["hosting", features.hosting],
17
- ["storage", features.storage],
18
- ["emulators", features.emulators],
19
- ["extensions", features.extensions],
20
- ["project", features.project],
21
- ["remoteconfig", features.remoteconfig],
22
- ["hosting:github", features.hostingGithub],
23
- ["genkit", features.genkit],
24
- ["apphosting", features.apphosting],
25
- ]);
9
+ const featuresList = [
10
+ { name: "account", doSetup: features.account },
11
+ {
12
+ name: "database",
13
+ askQuestions: features.databaseAskQuestions,
14
+ actuate: features.databaseActuate,
15
+ },
16
+ {
17
+ name: "firestore",
18
+ askQuestions: features.firestoreAskQuestions,
19
+ actuate: features.firestoreActuate,
20
+ },
21
+ {
22
+ name: "dataconnect",
23
+ askQuestions: features.dataconnectAskQuestions,
24
+ actuate: features.dataconnectActuate,
25
+ postSetup: features.dataconnectPostSetup,
26
+ },
27
+ { name: "dataconnect:sdk", doSetup: features.dataconnectSdk },
28
+ { name: "functions", doSetup: features.functions },
29
+ { name: "hosting", doSetup: features.hosting },
30
+ { name: "storage", doSetup: features.storage },
31
+ { name: "emulators", doSetup: features.emulators },
32
+ { name: "extensions", doSetup: features.extensions },
33
+ { name: "project", doSetup: features.project },
34
+ { name: "remoteconfig", doSetup: features.remoteconfig },
35
+ { name: "hosting:github", doSetup: features.hostingGithub },
36
+ { name: "genkit", doSetup: features.genkit },
37
+ { name: "apphosting", displayName: "App Hosting", doSetup: features.apphosting },
38
+ ];
39
+ const featureMap = new Map(featuresList.map((feature) => [feature.name, feature]));
26
40
  async function init(setup, config, options) {
27
41
  var _a;
28
42
  const nextFeature = (_a = setup.features) === null || _a === void 0 ? void 0 : _a.shift();
29
43
  if (nextFeature) {
30
- if (!featureFns.has(nextFeature)) {
44
+ const f = featureMap.get(nextFeature);
45
+ if (!f) {
31
46
  const availableFeatures = Object.keys(features)
32
47
  .filter((f) => f !== "project")
33
48
  .join(", ");
34
49
  throw new error_1.FirebaseError(`${clc.bold(nextFeature)} is not a valid feature. Must be one of ${availableFeatures}`);
35
50
  }
36
- logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${(0, lodash_1.capitalize)(nextFeature)} Setup`));
37
- const fn = featureFns.get(nextFeature);
38
- if (!fn) {
39
- throw new error_1.FirebaseError(`We've lost the function to init ${nextFeature}`, { exit: 2 });
51
+ logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${f.displayName || (0, lodash_1.capitalize)(nextFeature)} Setup`));
52
+ if (f.doSetup) {
53
+ await f.doSetup(setup, config, options);
54
+ }
55
+ else {
56
+ if (f.askQuestions) {
57
+ await f.askQuestions(setup, config, options);
58
+ }
59
+ if (f.actuate) {
60
+ await f.actuate(setup, config, options);
61
+ }
62
+ }
63
+ if (f.postSetup) {
64
+ await f.postSetup(setup, config, options);
40
65
  }
41
- await fn(setup, config, options);
42
66
  return init(setup, config, options);
43
67
  }
44
68
  }
45
69
  exports.init = init;
70
+ async function actuate(setup, config, options) {
71
+ var _a;
72
+ const nextFeature = (_a = setup.features) === null || _a === void 0 ? void 0 : _a.shift();
73
+ if (nextFeature) {
74
+ const f = lookupFeature(nextFeature);
75
+ logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${(0, lodash_1.capitalize)(nextFeature)} Setup Actuation`));
76
+ if (f.doSetup) {
77
+ throw new error_1.FirebaseError(`The feature ${nextFeature} does not support actuate yet. Please run ${clc.bold("firebase init " + nextFeature)} instead.`);
78
+ }
79
+ else {
80
+ if (f.actuate) {
81
+ await f.actuate(setup, config, options);
82
+ }
83
+ }
84
+ return actuate(setup, config, options);
85
+ }
86
+ }
87
+ exports.actuate = actuate;
88
+ function lookupFeature(feature) {
89
+ const f = featureMap.get(feature);
90
+ if (!f) {
91
+ const availableFeatures = Object.keys(features)
92
+ .filter((f) => f !== "project")
93
+ .join(", ");
94
+ throw new error_1.FirebaseError(`${clc.bold(feature)} is not a valid feature. Must be one of ${availableFeatures}`);
95
+ }
96
+ return f;
97
+ }
package/lib/logger.js CHANGED
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.silenceStdout = exports.logger = exports.vsceLogEmitter = void 0;
3
+ exports.useConsoleLoggers = exports.useFileLogger = exports.logger = exports.tryStringify = exports.findAvailableLogFile = exports.vsceLogEmitter = void 0;
4
4
  const winston = require("winston");
5
- const vsCodeUtils_1 = require("./vsCodeUtils");
6
5
  const events_1 = require("events");
6
+ const path = require("path");
7
+ const fs = require("fs");
8
+ const triple_beam_1 = require("triple-beam");
9
+ const util_1 = require("util");
10
+ const vsCodeUtils_1 = require("./vsCodeUtils");
7
11
  exports.vsceLogEmitter = new events_1.EventEmitter();
8
12
  function expandErrors(logger) {
9
13
  const oldLogFunc = logger.log.bind(logger);
@@ -45,6 +49,39 @@ function maybeUseVSCodeLogger(logger) {
45
49
  logger.log = vsceLogger;
46
50
  return logger;
47
51
  }
52
+ function findAvailableLogFile() {
53
+ const candidates = ["firebase-debug.log"];
54
+ for (let i = 1; i < 10; i++) {
55
+ candidates.push(`firebase-debug.${i}.log`);
56
+ }
57
+ for (const c of candidates) {
58
+ const logFilename = path.join(process.cwd(), c);
59
+ try {
60
+ const fd = fs.openSync(logFilename, "r+");
61
+ fs.closeSync(fd);
62
+ return logFilename;
63
+ }
64
+ catch (e) {
65
+ if (e.code === "ENOENT") {
66
+ return logFilename;
67
+ }
68
+ }
69
+ }
70
+ throw new Error("Unable to obtain permissions for firebase-debug.log");
71
+ }
72
+ exports.findAvailableLogFile = findAvailableLogFile;
73
+ function tryStringify(value) {
74
+ if (typeof value === "string") {
75
+ return value;
76
+ }
77
+ try {
78
+ return JSON.stringify(value);
79
+ }
80
+ catch (_a) {
81
+ return value;
82
+ }
83
+ }
84
+ exports.tryStringify = tryStringify;
48
85
  const rawLogger = winston.createLogger();
49
86
  rawLogger.add(new winston.transports.Console({
50
87
  silent: true,
@@ -52,9 +89,36 @@ rawLogger.add(new winston.transports.Console({
52
89
  }));
53
90
  rawLogger.exitOnError = false;
54
91
  exports.logger = maybeUseVSCodeLogger(annotateDebugLines(expandErrors(rawLogger)));
55
- function silenceStdout() {
56
- exports.logger = winston.createLogger({
57
- silent: true,
58
- });
92
+ function useFileLogger(logFile) {
93
+ const logFileName = logFile !== null && logFile !== void 0 ? logFile : findAvailableLogFile();
94
+ exports.logger.add(new winston.transports.File({
95
+ level: "debug",
96
+ filename: logFileName,
97
+ format: winston.format.printf((info) => {
98
+ const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(tryStringify);
99
+ return `[${info.level}] ${(0, util_1.stripVTControlCharacters)(segments.join(" "))}`;
100
+ }),
101
+ }));
102
+ return logFileName;
103
+ }
104
+ exports.useFileLogger = useFileLogger;
105
+ function useConsoleLoggers() {
106
+ if (process.env.DEBUG) {
107
+ exports.logger.add(new winston.transports.Console({
108
+ level: "debug",
109
+ format: winston.format.printf((info) => {
110
+ const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(tryStringify);
111
+ return `${(0, util_1.stripVTControlCharacters)(segments.join(" "))}`;
112
+ }),
113
+ }));
114
+ }
115
+ else if (process.env.IS_FIREBASE_CLI) {
116
+ exports.logger.add(new winston.transports.Console({
117
+ level: "info",
118
+ format: winston.format.printf((info) => [info.message, ...(info[triple_beam_1.SPLAT] || [])]
119
+ .filter((chunk) => typeof chunk === "string")
120
+ .join(" ")),
121
+ }));
122
+ }
59
123
  }
60
- exports.silenceStdout = silenceStdout;
124
+ exports.useConsoleLoggers = useConsoleLoggers;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getProject = exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
3
+ exports.checkFirebaseEnabledForCloudProject = exports.getProject = exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
4
4
  const clc = require("colorette");
5
5
  const ora = require("ora");
6
6
  const apiv2_1 = require("../apiv2");
@@ -336,8 +336,31 @@ async function getFirebaseProject(projectId) {
336
336
  }
337
337
  exports.getFirebaseProject = getFirebaseProject;
338
338
  async function getProject(projectId) {
339
- await (0, ensureApiEnabled_1.ensure)(projectId, api.resourceManagerOrigin(), "firebase", true);
339
+ await (0, ensureApiEnabled_1.bestEffortEnsure)(projectId, api.resourceManagerOrigin(), "firebase", true);
340
340
  const response = await resourceManagerClient.get(`/projects/${projectId}`);
341
341
  return response.body;
342
342
  }
343
343
  exports.getProject = getProject;
344
+ async function checkFirebaseEnabledForCloudProject(projectId) {
345
+ try {
346
+ const res = await firebaseAPIClient.request({
347
+ method: "GET",
348
+ path: `/projects/${projectId}`,
349
+ timeout: TIMEOUT_MILLIS,
350
+ });
351
+ return res.body;
352
+ }
353
+ catch (err) {
354
+ if ((0, error_1.getErrStatus)(err) === 404) {
355
+ return undefined;
356
+ }
357
+ let message = err.message;
358
+ if (err.original) {
359
+ message += ` (original: ${err.original.message})`;
360
+ }
361
+ logger_1.logger.debug(message);
362
+ throw new error_1.FirebaseError(`Failed to check if Firebase is enabled for project ${projectId}. ` +
363
+ "Please make sure the project exists and your account has permission to access it.", { exit: 2, original: err });
364
+ }
365
+ }
366
+ exports.checkFirebaseEnabledForCloudProject = checkFirebaseEnabledForCloudProject;
package/lib/mcp/errors.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mcpAuthError = exports.NO_PROJECT_ERROR = void 0;
4
4
  const util_1 = require("./util");
5
- exports.NO_PROJECT_ERROR = (0, util_1.mcpError)('No active project was found. Use the `set_firebase_directory` tool to set the project directory to an absolute folder location containing a firebase.json config file. Alternatively, change the MCP server config to add [...,"--dir","/absolute/path/to/project/directory"] in its command-line arguments.', "PRECONDITION_FAILED");
5
+ exports.NO_PROJECT_ERROR = (0, util_1.mcpError)('No active project was found. Use the `firebase_update_environment` tool to set the project directory to an absolute folder location containing a firebase.json config file. Alternatively, change the MCP server config to add [...,"--dir","/absolute/path/to/project/directory"] in its command-line arguments.', "PRECONDITION_FAILED");
6
6
  function mcpAuthError() {
7
7
  const cmd = (0, util_1.commandExistsSync)("firebase") ? "firebase" : "npx -y firebase-tools";
8
8
  return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please instruct the user to execute this shell command to sign in or to configure [Application Default Credentials][ADC] on their machine.
package/lib/mcp/index.js CHANGED
@@ -5,110 +5,193 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
5
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
6
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
7
  const util_js_1 = require("./util.js");
8
+ const types_js_2 = require("./types.js");
8
9
  const index_js_2 = require("./tools/index.js");
9
10
  const configstore_js_1 = require("../configstore.js");
10
- const index_js_3 = require("./tools/core/index.js");
11
11
  const command_js_1 = require("../command.js");
12
12
  const requireAuth_js_1 = require("../requireAuth.js");
13
13
  const projectUtils_js_1 = require("../projectUtils.js");
14
14
  const errors_js_1 = require("./errors.js");
15
15
  const track_js_1 = require("../track.js");
16
16
  const config_js_1 = require("../config.js");
17
- const SERVER_VERSION = "0.0.1";
18
- const PROJECT_ROOT_KEY = "mcp.projectRoot";
17
+ const rc_js_1 = require("../rc.js");
18
+ const hubClient_js_1 = require("../emulator/hubClient.js");
19
+ const node_fs_1 = require("node:fs");
20
+ const SERVER_VERSION = "0.1.0";
19
21
  const cmd = new command_js_1.Command("experimental:mcp").before(requireAuth_js_1.requireAuth);
20
22
  class FirebaseMcpServer {
21
23
  constructor(options) {
22
- var _a, _b, _c;
24
+ this._ready = false;
25
+ this._readyPromises = [];
23
26
  this.activeFeatures = options.activeFeatures;
27
+ this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
24
28
  this.server = new index_js_1.Server({ name: "firebase", version: SERVER_VERSION });
25
29
  this.server.registerCapabilities({ tools: { listChanged: true } });
26
30
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, this.mcpListTools.bind(this));
27
31
  this.server.setRequestHandler(types_js_1.CallToolRequestSchema, this.mcpCallTool.bind(this));
28
- this.projectRoot =
29
- (_c = (_b = (_a = options.projectRoot) !== null && _a !== void 0 ? _a : configstore_js_1.configstore.get(PROJECT_ROOT_KEY)) !== null && _b !== void 0 ? _b : process.env.PROJECT_ROOT) !== null && _c !== void 0 ? _c : process.cwd();
30
- if (options.projectRoot)
31
- this.fixedRoot = true;
32
+ this.server.oninitialized = async () => {
33
+ var _a, _b;
34
+ const clientInfo = this.server.getClientVersion();
35
+ this.clientInfo = clientInfo;
36
+ if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.name) {
37
+ (0, track_js_1.trackGA4)("mcp_client_connected", {
38
+ mcp_client_name: clientInfo.name,
39
+ mcp_client_version: clientInfo.version,
40
+ });
41
+ }
42
+ if (!((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name))
43
+ this.clientInfo = { name: "<unknown-client>" };
44
+ this._ready = true;
45
+ while (this._readyPromises.length) {
46
+ (_b = this._readyPromises.pop()) === null || _b === void 0 ? void 0 : _b.resolve();
47
+ }
48
+ };
49
+ this.detectProjectRoot();
50
+ this.detectActiveFeatures();
32
51
  }
33
- get availableTools() {
52
+ ready() {
53
+ if (this._ready)
54
+ return Promise.resolve();
55
+ return new Promise((resolve, reject) => {
56
+ this._readyPromises.push({ resolve: resolve, reject });
57
+ });
58
+ }
59
+ get clientConfigKey() {
34
60
  var _a;
35
- const toolDefs = this.fixedRoot ? [] : [...index_js_3.coreTools];
36
- const activeFeatures = ((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length)
37
- ? this.activeFeatures
38
- : Object.keys(index_js_2.tools);
39
- for (const key of activeFeatures || []) {
40
- toolDefs.push(...index_js_2.tools[key]);
61
+ return `mcp.clientConfigs.${((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>"}:${this.startupRoot || process.cwd()}`;
62
+ }
63
+ getStoredClientConfig() {
64
+ return configstore_js_1.configstore.get(this.clientConfigKey) || {};
65
+ }
66
+ updateStoredClientConfig(update) {
67
+ const config = configstore_js_1.configstore.get(this.clientConfigKey) || {};
68
+ const newConfig = Object.assign(Object.assign({}, config), update);
69
+ configstore_js_1.configstore.set(this.clientConfigKey, newConfig);
70
+ return newConfig;
71
+ }
72
+ async detectProjectRoot() {
73
+ await this.ready();
74
+ if (this.cachedProjectRoot)
75
+ return this.cachedProjectRoot;
76
+ const storedRoot = this.getStoredClientConfig().projectRoot;
77
+ this.cachedProjectRoot = storedRoot || this.startupRoot || process.cwd();
78
+ return this.cachedProjectRoot;
79
+ }
80
+ async detectActiveFeatures() {
81
+ var _a;
82
+ if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
83
+ return this.detectedFeatures;
84
+ const options = await this.resolveOptions();
85
+ const projectId = await this.getProjectId();
86
+ const detected = await Promise.all(types_js_2.SERVER_FEATURES.map(async (f) => {
87
+ if (await (0, util_js_1.checkFeatureActive)(f, projectId, options))
88
+ return f;
89
+ return null;
90
+ }));
91
+ this.detectedFeatures = detected.filter((f) => !!f);
92
+ return this.detectedFeatures;
93
+ }
94
+ async getEmulatorHubClient() {
95
+ if (this.emulatorHubClient) {
96
+ return this.emulatorHubClient;
41
97
  }
42
- return toolDefs;
98
+ const projectId = await this.getProjectId();
99
+ if (!projectId) {
100
+ return;
101
+ }
102
+ this.emulatorHubClient = new hubClient_js_1.EmulatorHubClient(projectId);
103
+ return this.emulatorHubClient;
104
+ }
105
+ get availableTools() {
106
+ var _a;
107
+ return (0, index_js_2.availableTools)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
43
108
  }
44
109
  getTool(name) {
45
110
  return this.availableTools.find((t) => t.mcp.name === name) || null;
46
111
  }
47
- async mcpListTools() {
48
- const hasActiveProject = !!(await this.getProjectId());
49
- await (0, track_js_1.trackGA4)("mcp_list_tools", {});
50
- return {
51
- tools: this.availableTools.map((t) => t.mcp),
52
- _meta: {
53
- projectRoot: this.projectRoot,
54
- projectDetected: hasActiveProject,
55
- authenticated: await this.getAuthenticated(),
56
- activeFeatures: this.activeFeatures,
57
- },
58
- };
59
- }
60
112
  setProjectRoot(newRoot) {
61
- if (newRoot === null) {
62
- configstore_js_1.configstore.delete(PROJECT_ROOT_KEY);
63
- this.projectRoot = process.env.PROJECT_ROOT || process.cwd();
64
- void this.server.sendToolListChanged();
65
- return;
66
- }
67
- configstore_js_1.configstore.set(PROJECT_ROOT_KEY, newRoot);
68
- this.projectRoot = newRoot;
113
+ this.updateStoredClientConfig({ projectRoot: newRoot });
114
+ this.cachedProjectRoot = newRoot || undefined;
115
+ this.detectedFeatures = undefined;
69
116
  void this.server.sendToolListChanged();
70
117
  }
71
118
  async resolveOptions() {
72
- const options = { cwd: this.projectRoot };
119
+ const options = { cwd: this.cachedProjectRoot, isMCP: true };
73
120
  await cmd.prepare(options);
74
121
  return options;
75
122
  }
76
123
  async getProjectId() {
77
124
  return (0, projectUtils_js_1.getProjectId)(await this.resolveOptions());
78
125
  }
79
- async getAuthenticated() {
126
+ async getAuthenticatedUser() {
80
127
  try {
81
- await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
82
- return true;
128
+ return await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
83
129
  }
84
130
  catch (e) {
85
- return false;
131
+ return null;
86
132
  }
87
133
  }
88
- async mcpCallTool(request) {
134
+ async mcpListTools() {
89
135
  var _a, _b;
136
+ await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
137
+ const hasActiveProject = !!(await this.getProjectId());
138
+ await (0, track_js_1.trackGA4)("mcp_list_tools", {
139
+ mcp_client_name: (_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name,
140
+ mcp_client_version: (_b = this.clientInfo) === null || _b === void 0 ? void 0 : _b.version,
141
+ });
142
+ return {
143
+ tools: this.availableTools.map((t) => t.mcp),
144
+ _meta: {
145
+ projectRoot: this.cachedProjectRoot,
146
+ projectDetected: hasActiveProject,
147
+ authenticatedUser: await this.getAuthenticatedUser(),
148
+ activeFeatures: this.activeFeatures,
149
+ detectedFeatures: this.detectedFeatures,
150
+ },
151
+ };
152
+ }
153
+ async mcpCallTool(request) {
154
+ var _a, _b, _c, _d, _e, _f;
155
+ await this.detectProjectRoot();
90
156
  const toolName = request.params.name;
91
157
  const toolArgs = request.params.arguments;
92
158
  const tool = this.getTool(toolName);
93
159
  if (!tool)
94
160
  throw new Error(`Tool '${toolName}' could not be found.`);
95
161
  const projectId = await this.getProjectId();
96
- if (((_a = tool.mcp._meta) === null || _a === void 0 ? void 0 : _a.requiresAuth) && !(await this.getAuthenticated()))
162
+ const accountEmail = await this.getAuthenticatedUser();
163
+ if (tool.mcp.name !== "firebase_update_environment" &&
164
+ (!this.cachedProjectRoot || !(0, node_fs_1.existsSync)(this.cachedProjectRoot)))
165
+ return (0, util_js_1.mcpError)(`The current project directory '${this.cachedProjectRoot || "<NO PROJECT DIRECTORY FOUND>"}' does not exist. Please use the 'update_firebase_environment' tool to target a different project directory.`);
166
+ if (((_a = tool.mcp._meta) === null || _a === void 0 ? void 0 : _a.requiresAuth) && !accountEmail)
97
167
  return (0, errors_js_1.mcpAuthError)();
98
168
  if (((_b = tool.mcp._meta) === null || _b === void 0 ? void 0 : _b.requiresProject) && !projectId)
99
169
  return errors_js_1.NO_PROJECT_ERROR;
170
+ const options = { projectDir: this.cachedProjectRoot, cwd: this.cachedProjectRoot };
171
+ const toolsCtx = {
172
+ projectId: projectId,
173
+ host: this,
174
+ config: config_js_1.Config.load(options, true) || new config_js_1.Config({}, options),
175
+ rc: (0, rc_js_1.loadRC)(options),
176
+ accountEmail,
177
+ };
100
178
  try {
101
- const config = config_js_1.Config.load({ cwd: this.projectRoot });
102
- const res = await tool.fn(toolArgs, {
103
- projectId: await this.getProjectId(),
104
- host: this,
105
- config,
179
+ const res = await tool.fn(toolArgs, toolsCtx);
180
+ await (0, track_js_1.trackGA4)("mcp_tool_call", {
181
+ tool_name: toolName,
182
+ error: res.isError ? 1 : 0,
183
+ mcp_client_name: (_c = this.clientInfo) === null || _c === void 0 ? void 0 : _c.name,
184
+ mcp_client_version: (_d = this.clientInfo) === null || _d === void 0 ? void 0 : _d.version,
106
185
  });
107
- await (0, track_js_1.trackGA4)("mcp_tool_call", { tool_name: toolName, error: res.isError ? 1 : 0 });
108
186
  return res;
109
187
  }
110
188
  catch (err) {
111
- await (0, track_js_1.trackGA4)("mcp_tool_call", { tool_name: toolName, error: 1 });
189
+ await (0, track_js_1.trackGA4)("mcp_tool_call", {
190
+ tool_name: toolName,
191
+ error: 1,
192
+ mcp_client_name: (_e = this.clientInfo) === null || _e === void 0 ? void 0 : _e.name,
193
+ mcp_client_version: (_f = this.clientInfo) === null || _f === void 0 ? void 0 : _f.version,
194
+ });
112
195
  return (0, util_js_1.mcpError)(err);
113
196
  }
114
197
  }
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.disable_auth_user = void 0;
3
+ exports.disable_user = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const tool_js_1 = require("../../tool.js");
6
6
  const util_js_1 = require("../../util.js");
7
7
  const auth_js_1 = require("../../../gcp/auth.js");
8
- exports.disable_auth_user = (0, tool_js_1.tool)({
9
- name: "disable_auth_user",
8
+ exports.disable_user = (0, tool_js_1.tool)({
9
+ name: "disable_user",
10
10
  description: "Disables or enables a user based on a UID.",
11
11
  inputSchema: zod_1.z.object({
12
12
  uid: zod_1.z.string().describe("The localId or UID of the user to disable or enable"),
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get_user = void 0;
4
+ const zod_1 = require("zod");
5
+ const tool_js_1 = require("../../tool.js");
6
+ const util_js_1 = require("../../util.js");
7
+ const auth_js_1 = require("../../../gcp/auth.js");
8
+ exports.get_user = (0, tool_js_1.tool)({
9
+ name: "get_user",
10
+ description: "Retrieves a user based on an email address, phone number, or UID.",
11
+ inputSchema: zod_1.z.object({
12
+ email: zod_1.z
13
+ .string()
14
+ .optional()
15
+ .describe("The user's email address. At least one of email, phone_number, or uid must be provided."),
16
+ phone_number: zod_1.z
17
+ .string()
18
+ .optional()
19
+ .describe("The user's phone number. At least one of email, phone_number, or uid must be provided."),
20
+ uid: zod_1.z
21
+ .string()
22
+ .optional()
23
+ .describe("The user's UID. At least one of email, phone_number, or uid must be provided."),
24
+ }),
25
+ annotations: {
26
+ title: "Get Firebase Auth User",
27
+ readOnlyHint: true,
28
+ },
29
+ _meta: {
30
+ requiresAuth: true,
31
+ requiresProject: true,
32
+ },
33
+ }, async ({ email, phone_number, uid }, { projectId }) => {
34
+ if (email === undefined && phone_number === undefined && uid === undefined) {
35
+ return (0, util_js_1.mcpError)(`No user identifier supplied in auth_get_user tool`);
36
+ }
37
+ return (0, util_js_1.toContent)(await (0, auth_js_1.findUser)(projectId, email, phone_number, uid));
38
+ });
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.authTools = void 0;
4
- const get_auth_user_js_1 = require("./get_auth_user.js");
5
- const disable_auth_user_js_1 = require("./disable_auth_user.js");
6
- const set_auth_claims_js_1 = require("./set_auth_claims.js");
4
+ const get_user_js_1 = require("./get_user.js");
5
+ const disable_user_js_1 = require("./disable_user.js");
6
+ const set_claims_js_1 = require("./set_claims.js");
7
7
  const set_sms_region_policy_js_1 = require("./set_sms_region_policy.js");
8
+ const list_users_js_1 = require("./list_users.js");
8
9
  exports.authTools = [
9
- get_auth_user_js_1.get_auth_user,
10
- disable_auth_user_js_1.disable_auth_user,
11
- set_auth_claims_js_1.set_auth_claim,
10
+ get_user_js_1.get_user,
11
+ disable_user_js_1.disable_user,
12
+ list_users_js_1.list_users,
13
+ set_claims_js_1.set_claim,
12
14
  set_sms_region_policy_js_1.set_sms_region_policy,
13
15
  ];
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.list_users = void 0;
15
+ const zod_1 = require("zod");
16
+ const tool_js_1 = require("../../tool.js");
17
+ const util_js_1 = require("../../util.js");
18
+ const auth_js_1 = require("../../../gcp/auth.js");
19
+ exports.list_users = (0, tool_js_1.tool)({
20
+ name: "list_users",
21
+ description: "Retrieves all users in the project up to the specified limit.",
22
+ inputSchema: zod_1.z.object({
23
+ limit: zod_1.z
24
+ .number()
25
+ .optional()
26
+ .default(100)
27
+ .describe("The number of users to return. Defaults to 100 if not supplied."),
28
+ }),
29
+ annotations: {
30
+ title: "List Firebase Users",
31
+ readOnlyHint: true,
32
+ },
33
+ _meta: {
34
+ requiresAuth: true,
35
+ requiresProject: true,
36
+ },
37
+ }, async ({ limit } = {}, { projectId }) => {
38
+ if (!limit) {
39
+ limit = 100;
40
+ }
41
+ const users = await (0, auth_js_1.listUsers)(projectId, limit);
42
+ const usersPruned = users.map((user) => {
43
+ const { passwordHash, salt } = user, prunedUser = __rest(user, ["passwordHash", "salt"]);
44
+ return prunedUser;
45
+ });
46
+ return (0, util_js_1.toContent)(usersPruned);
47
+ });