firebase-tools 14.18.0 → 14.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/lib/appUtils.js +230 -0
- package/lib/apphosting/localbuilds.js +23 -0
- package/lib/bin/mcp.js +16 -1
- package/lib/commands/index.js +8 -0
- package/lib/commands/init.js +0 -2
- package/lib/commands/remoteconfig-experiments-delete.js +32 -0
- package/lib/commands/remoteconfig-experiments-get.js +20 -0
- package/lib/commands/remoteconfig-experiments-list.js +38 -0
- package/lib/commands/remoteconfig-rollouts-delete.js +32 -0
- package/lib/commands/remoteconfig-rollouts-get.js +20 -0
- package/lib/commands/remoteconfig-rollouts-list.js +38 -0
- package/lib/config.js +4 -2
- package/lib/deploy/apphosting/deploy.js +26 -10
- package/lib/deploy/apphosting/prepare.js +23 -1
- package/lib/deploy/apphosting/release.js +5 -0
- package/lib/deploy/apphosting/util.js +4 -3
- package/lib/deploy/functions/deploy.js +5 -4
- package/lib/deploy/functions/params.js +2 -2
- package/lib/emulator/commandUtils.js +3 -3
- package/lib/emulator/controller.js +3 -2
- package/lib/gcp/cloudsql/cloudsqladmin.js +1 -1
- package/lib/gcp/storage.js +73 -20
- package/lib/init/features/dataconnect/index.js +18 -9
- package/lib/init/features/project.js +66 -75
- package/lib/management/projects.js +16 -4
- package/lib/mcp/index.js +9 -0
- package/lib/mcp/prompts/core/deploy.js +8 -8
- package/lib/mcp/prompts/core/init.js +15 -18
- package/lib/mcp/prompts/crashlytics/connect.js +2 -2
- package/lib/mcp/prompts/index.js +30 -9
- package/lib/mcp/resources/guides/init_ai.js +8 -0
- package/lib/mcp/resources/guides/init_auth.js +4 -0
- package/lib/mcp/resources/guides/init_firestore_rules.js +2 -0
- package/lib/mcp/resources/index.js +16 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +34 -17
- package/lib/mcp/tools/core/init.js +2 -1
- package/lib/mcp/tools/core/logout.js +1 -2
- package/lib/mcp/tools/functions/get_logs.js +9 -7
- package/lib/mcp/tools/index.js +3 -2
- package/lib/remoteconfig/deleteExperiment.js +32 -0
- package/lib/remoteconfig/deleteRollout.js +33 -0
- package/lib/remoteconfig/getExperiment.js +52 -0
- package/lib/remoteconfig/getRollout.js +43 -0
- package/lib/remoteconfig/interfaces.js +3 -1
- package/lib/remoteconfig/listExperiments.js +72 -0
- package/lib/remoteconfig/listRollouts.js +72 -0
- package/lib/remoteconfig/options.js +4 -0
- package/lib/track.js +6 -4
- package/package.json +3 -1
- package/schema/firebase-config.json +6 -0
|
@@ -15,7 +15,7 @@ exports.deploy = (0, prompt_1.prompt)({
|
|
|
15
15
|
annotations: {
|
|
16
16
|
title: "Deploy to Firebase",
|
|
17
17
|
},
|
|
18
|
-
}, async ({ prompt }, { config, projectId, accountEmail }) => {
|
|
18
|
+
}, async ({ prompt }, { config, projectId, accountEmail, firebaseCliCommand }) => {
|
|
19
19
|
return [
|
|
20
20
|
{
|
|
21
21
|
role: "user",
|
|
@@ -41,7 +41,7 @@ ${prompt || "<the user didn't supply specific instructions>"}
|
|
|
41
41
|
|
|
42
42
|
Follow the steps below taking note of any user instructions provided above.
|
|
43
43
|
|
|
44
|
-
1. If there is no active user, prompt the user to run
|
|
44
|
+
1. If there is no active user, prompt the user to run \`${firebaseCliCommand} login\` in an interactive terminal before continuing.
|
|
45
45
|
2. Analyze the source code in the current working directory to determine if this is a web app. If it isn't, end this process and tell the user "The /firebase:deploy command only works with web apps."
|
|
46
46
|
3. Analyze the source code in the current working directory to determine if the app requires a server for Server-Side Rendering (SSR). This will determine whether or not to use Firebase App Hosting. Here are instructions to determine if the app needs a server:
|
|
47
47
|
Objective: Analyze the provided codebase files to determine if the web application requires a backend for Server-Side Rendering (SSR). Your final output must be a clear "Yes" or "No" followed by a brief justification.
|
|
@@ -79,19 +79,19 @@ Follow the steps below taking note of any user instructions provided above.
|
|
|
79
79
|
Example (No): "No, there are no dependencies or file structures that indicate the use of a server-side rendering framework."
|
|
80
80
|
4. If there is no \`firebase.json\` file, manually create one based on whether the app requires SSR:
|
|
81
81
|
4a. If the app requires SSR, configure Firebase App Hosting:
|
|
82
|
-
Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": "<backendId>"}}\
|
|
82
|
+
Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": "<backendId>"}}\
|
|
83
83
|
4b. If the app does NOT require SSR, configure Firebase Hosting:
|
|
84
84
|
Create \`firebase.json\ with a "hosting" configuration. Add a \`{"hosting": {"predeploy": "<build_script>"}}\` config to build before deploying.
|
|
85
85
|
5. Check if there is an active Firebase project for this environment (the \`firebase_get_environment\` tool may be helpful). If there is, provide the active project ID to the user and ask them if they want to proceed using that project. If there is not an active project, give the user two options: Provide an existing project ID or create a new project. Only use the list_projects tool on user request. Wait for their response before proceeding.
|
|
86
86
|
5a. If the user chooses to use an existing Firebase project, the \`firebase_list_projects\` tool may be helpful. Set the selected project as the active project (the \`firebase_update_environment\` tool may be helpful).
|
|
87
87
|
5b. If the user chooses to create a new project, use the \`firebase_create_project \` tool. Then set the new project as the active project (the \`firebase_update_environment\` tool may be helpful).
|
|
88
|
-
6. If firebase.json contains an "apphosting" configuration, check if a backend exists matching the provided backendId (the \`apphosting_list_backends\` tool may be helpful).
|
|
89
|
-
If it doesn't exist, create one by running the
|
|
90
|
-
7. Only after making sure Firebase has been initialized, run the
|
|
88
|
+
6. If firebase.json contains an "apphosting" configuration, check if a backend exists matching the provided backendId (the \`apphosting_list_backends\` tool may be helpful).
|
|
89
|
+
If it doesn't exist, create one by running the \`${firebaseCliCommand} apphosting:backends:create --backend <backendId> --primary-region us-central1 --root-dir .\` shell.
|
|
90
|
+
7. Only after making sure Firebase has been initialized, run the \`${firebaseCliCommand} deploy\` shell command to perform the deploy. This may take a few minutes.
|
|
91
91
|
7a. If deploying to apphosting, tell the user the deployment will take a few minutes, and they can monitor deployment progress in the Firebase console: \`https://console.firebase.google.com/project/<projectId>/apphosting\`
|
|
92
92
|
8. If the deploy has errors, attempt to fix them and ask the user clarifying questions as needed.
|
|
93
|
-
9. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running
|
|
94
|
-
10. If only one specific feature is failing, use command
|
|
93
|
+
9. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running \`${firebaseCliCommand} deploy --force\`.
|
|
94
|
+
10. If only one specific feature is failing, use command \`${firebaseCliCommand} deploy --only <feature>\` as you debug.
|
|
95
95
|
11. If the deploy succeeds, your job is finished.
|
|
96
96
|
`.trim(),
|
|
97
97
|
},
|
|
@@ -6,12 +6,12 @@ const types_1 = require("../../../dataconnect/types");
|
|
|
6
6
|
const prompt_1 = require("../../prompt");
|
|
7
7
|
exports.init = (0, prompt_1.prompt)({
|
|
8
8
|
name: "init",
|
|
9
|
-
description: "Use this command to
|
|
9
|
+
description: "Use this command to set up Firebase services, like backend and AI features.",
|
|
10
10
|
annotations: {
|
|
11
11
|
title: "Initialize Firebase",
|
|
12
12
|
},
|
|
13
13
|
}, async (_, mcp) => {
|
|
14
|
-
const { config, projectId, accountEmail } = mcp;
|
|
14
|
+
const { config, projectId, accountEmail, firebaseCliCommand } = mcp;
|
|
15
15
|
const platform = await (0, appFinder_1.getPlatformFromFolder)(config.projectDir);
|
|
16
16
|
return [
|
|
17
17
|
{
|
|
@@ -41,22 +41,17 @@ ${config.readProjectFile("firebase.json", { fallback: "<FILE DOES NOT EXIST>" })
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
## Steps
|
|
44
|
-
|
|
45
44
|
Follow the steps below taking note of any user instructions provided above.
|
|
46
45
|
|
|
47
46
|
1. If there is no active user, use the \`firebase_login\` tool to help them sign in.
|
|
48
|
-
- If you run into issues logging the user in, suggest that they run
|
|
49
|
-
2.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
2.2 Start by listing out the existing init options that are available to the user. Ask the user which set of services they would like to add to their app. Always enumerate them and list the options out explicitly for the user.
|
|
54
|
-
1. Backend Services: Backend services for the user such as user authentication, database, or cloud file hosting.
|
|
55
|
-
- IMPORTANT: The backend setup guide is for web apps only. If the user requests backend setup for a mobile app (iOS, Android, or Flutter), inform them that this is not supported and do not use the backend setup guide. You can still assist with other requests.
|
|
56
|
-
2. Firebase AI Logic: Add AI features such as chat experiences, multimodal prompts, image generation and editing (via nano banana), etc.
|
|
47
|
+
- If you run into issues logging the user in, suggest that they run \`${firebaseCliCommand} login --reauth\` in a separate terminal
|
|
48
|
+
2. Start by listing out the existing init options that are available to the user. Ask the user which set of services they would like to add to their app. Always enumerate them and list the options out explicitly for the user:
|
|
49
|
+
- Backend Services: Backend services for the app, such as setting up a database, adding a user-authentication sign up and login page, and deploying a web app to a production URL.
|
|
50
|
+
- IMPORTANT: The backend setup guide is for web apps only. If the user requests backend setup for a mobile app (iOS, Android, or Flutter), inform them that this is not supported and do not use the backend setup guide. You can still assist with other requests.
|
|
51
|
+
- Firebase AI Logic: Add AI features such as chat experiences, multimodal prompts, image generation and editing (via nano banana), etc.
|
|
57
52
|
- IMPORTANT: The Firebase AI Logic setup guide is for web, flutter, and android apps only. If the user requests firebase setup for unsupported platforms (iOS, Unity, or anything else), inform them that this is not supported and direct the user to Firebase Docs to learn how to set up AI Logic for their application (share this link with the user https://firebase.google.com/docs/ai-logic/get-started?api=dev). You can still assist with other requests.
|
|
58
|
-
3. After the user chooses an init option, create a plan based on the remaining steps in this guide, share it with the user, and give them an opportunity to accept or adjust it.
|
|
59
|
-
4. If there is no active Firebase project, ask the user if they
|
|
53
|
+
3. After the user chooses an init option, create a plan based on the remaining steps in this guide, share it with the user, and give them an opportunity to accept or adjust it.
|
|
54
|
+
4. If there is no active Firebase project, ask the user if they want to create a new project or use an existing one. If using an existing project, ask for the project ID and explain how to find it: open the Firebase Console (http://console.firebase.google.com/), locate the project ID under the project name in the projects list, or open the project and go to Project Overview → Project Settings.
|
|
60
55
|
- If they would like to create a project, use the firebase_create_project with the project ID
|
|
61
56
|
- If they would like to use an existing project, use the firebase_update_environment tool with the active_project argument.
|
|
62
57
|
- If you run into issues creating the firebase project, ask the user to go to the [Firebase Console](http://console.firebase.google.com/) and create a project. Wait for the user to report back before continuing.
|
|
@@ -65,11 +60,13 @@ Follow the steps below taking note of any user instructions provided above.
|
|
|
65
60
|
- Run the \`firebase_list_apps\` tool to list their apps, and find an app that matches their "Workspace platform"
|
|
66
61
|
- If there is no app that matches that criteria, use the \`firebase_create_app\` tool to create the app with the appropriate platform
|
|
67
62
|
- Do the following only for Flutter apps
|
|
68
|
-
-
|
|
69
|
-
|
|
63
|
+
- Execute \`firebase --version\` to check if the Firebase CLI is installed
|
|
64
|
+
- If it isn't installed, run \`npm install -g firebase-tools\` to install it. If it is installed, skip to the next step.
|
|
65
|
+
- Install the Flutterfire CLI
|
|
66
|
+
- Use the Flutterfire CLI tool to connect to the project
|
|
70
67
|
- Use the Flutterfire CLI to register the appropriate applications based on the user's input
|
|
71
68
|
- Let the developer know that you currently only support configuring web, ios, and android targets together in a bundle. Each of those targets will have appropriate apps registered in the project using the flutterfire CLI
|
|
72
|
-
- Execute flutterfire config using the following pattern: flutterfire config --yes --project=<aliasOrProjectId> --platforms=<platforms>
|
|
69
|
+
- Execute flutterfire config using the following pattern: flutterfire config --yes --project=<aliasOrProjectId> --platforms=<platforms>
|
|
73
70
|
6. Now that we have a working environment, print out 1) Active user 2) Firebase Project and 3) Firebase App & platform they are using for this process.
|
|
74
71
|
- Ask the user to confirm this is correct before continuing
|
|
75
72
|
7. Set up the web Firebase SDK. Skip straight to #8 for Flutter and Android apps
|
|
@@ -88,7 +85,7 @@ Follow the steps below taking note of any user instructions provided above.
|
|
|
88
85
|
'''
|
|
89
86
|
8. Read the guide for the appropriate services and follow the instructions. If no guides match the user's need, inform the user.
|
|
90
87
|
- Use the Firebase \`read_resources\` tool to load the instructions for the service the developer chose in step 2 of this guide
|
|
91
|
-
- [Backend Services](firebase://guides/init/backend): Read this resource to
|
|
88
|
+
- [Backend Services](firebase://guides/init/backend): Read this resource to set up backend services for the app, such as setting up a database, adding a user-authentication sign up and login page, and deploying a web app to a production URL.
|
|
92
89
|
- [Firebase AI Logic](firebase://guides/init/ai): Read this resource to add Gemini-powered AI features such as chat experiences, multimodal prompts, image generation, image editing (via nano banana), etc.
|
|
93
90
|
`.trim(),
|
|
94
91
|
},
|
|
@@ -9,7 +9,7 @@ exports.connect = (0, prompt_1.prompt)({
|
|
|
9
9
|
annotations: {
|
|
10
10
|
title: "Access Crashlytics data",
|
|
11
11
|
},
|
|
12
|
-
}, async (unused, { accountEmail }) => {
|
|
12
|
+
}, async (unused, { accountEmail, firebaseCliCommand }) => {
|
|
13
13
|
return [
|
|
14
14
|
{
|
|
15
15
|
role: "user",
|
|
@@ -25,7 +25,7 @@ Active user: ${accountEmail || "<NONE>"}
|
|
|
25
25
|
|
|
26
26
|
1. **Make sure the user is logged in. No Crashlytics tools will work if the user is not logged in.**
|
|
27
27
|
a. Use the \`firebase_get_environment\` tool to verify that the user is logged in.
|
|
28
|
-
b. If the Firebase 'Active user' is set to <NONE>, instruct the user to run
|
|
28
|
+
b. If the Firebase 'Active user' is set to <NONE>, instruct the user to run \`${firebaseCliCommand} login\`
|
|
29
29
|
before continuing. Ignore other fields that are set to <NONE>. We are just making sure the
|
|
30
30
|
user is logged in.
|
|
31
31
|
|
package/lib/mcp/prompts/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.availablePrompts = void 0;
|
|
3
|
+
exports.markdownDocsOfPrompts = exports.availablePrompts = void 0;
|
|
4
4
|
const core_1 = require("./core");
|
|
5
5
|
const dataconnect_1 = require("./dataconnect");
|
|
6
6
|
const crashlytics_1 = require("./crashlytics");
|
|
@@ -33,16 +33,37 @@ function namespacePrompts(promptsToNamespace, feature) {
|
|
|
33
33
|
return newPrompt;
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function availablePrompts(
|
|
37
|
-
const allPrompts =
|
|
38
|
-
if (!
|
|
39
|
-
|
|
36
|
+
function availablePrompts(activeFeatures) {
|
|
37
|
+
const allPrompts = [];
|
|
38
|
+
if (!(activeFeatures === null || activeFeatures === void 0 ? void 0 : activeFeatures.length)) {
|
|
39
|
+
activeFeatures = Object.keys(prompts);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
if (!activeFeatures.includes("core")) {
|
|
42
|
+
activeFeatures = ["core", ...activeFeatures];
|
|
43
|
+
}
|
|
44
|
+
for (const feature of activeFeatures) {
|
|
45
|
+
allPrompts.push(...namespacePrompts(prompts[feature], feature));
|
|
45
46
|
}
|
|
46
47
|
return allPrompts;
|
|
47
48
|
}
|
|
48
49
|
exports.availablePrompts = availablePrompts;
|
|
50
|
+
function markdownDocsOfPrompts() {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
const allPrompts = availablePrompts();
|
|
53
|
+
let doc = `
|
|
54
|
+
| Prompt Name | Feature Group | Description |
|
|
55
|
+
| ----------- | ------------- | ----------- |`;
|
|
56
|
+
for (const prompt of allPrompts) {
|
|
57
|
+
const feature = ((_a = prompt.mcp._meta) === null || _a === void 0 ? void 0 : _a.feature) || "";
|
|
58
|
+
let description = prompt.mcp.description || "";
|
|
59
|
+
if ((_b = prompt.mcp.arguments) === null || _b === void 0 ? void 0 : _b.length) {
|
|
60
|
+
const argsList = prompt.mcp.arguments.map((arg) => ` <br><${arg.name}>${arg.required ? "" : " (optional)"}: ${arg.description || ""}`);
|
|
61
|
+
description += ` <br><br>Arguments:${argsList.join("")}`;
|
|
62
|
+
}
|
|
63
|
+
description = description.replaceAll("\n", "<br>");
|
|
64
|
+
doc += `
|
|
65
|
+
| ${prompt.mcp.name} | ${feature} | ${description} |`;
|
|
66
|
+
}
|
|
67
|
+
return doc;
|
|
68
|
+
}
|
|
69
|
+
exports.markdownDocsOfPrompts = markdownDocsOfPrompts;
|
|
@@ -60,6 +60,10 @@ Take the following actions depending on the language and platform or framework t
|
|
|
60
60
|
|
|
61
61
|
- Use the firebase_init tool to set up ailogic
|
|
62
62
|
|
|
63
|
+
- For Android, the Google Services Gradle plugin is required to prevent the app from crashing. You must add it in two files:
|
|
64
|
+
- 1. In your project-level \`/build.gradle.kts\` file, add the plugin to the plugins block: id("com.google.gms.google-services") version "4.4.2" apply false
|
|
65
|
+
- 2. In your **app-level** \`/app/build.gradle.kts\` file, apply the plugin: id("com.google.gms.google-services")
|
|
66
|
+
|
|
63
67
|
### 3\. Implement AI Features
|
|
64
68
|
|
|
65
69
|
#### Gather Building Blocks for Code Generation
|
|
@@ -77,6 +81,10 @@ Take the following actions depending on the language and platform or framework t
|
|
|
77
81
|
- Kotlin Only
|
|
78
82
|
- implementation(platform("com.google.firebase:firebase-bom:34.3.0")) or a higher bom version if it is available
|
|
79
83
|
- implementation("com.google.firebase:firebase-ai")
|
|
84
|
+
- CRITICAL: When initializing the Firebase AI model in Kotlin, you must explicitly specify the Google AI backend by calling the googleAI() function. The correct syntax is GenerativeBackend.googleAI().
|
|
85
|
+
- Correct Example: val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(...)
|
|
86
|
+
- Incorrect: Do not use the invalid constant GenerativeBackend.GOOGLE_AI.
|
|
87
|
+
- The Kotlin SDK public API makes extensive use of suspend functions and coroutines. Make sure the code you generate is based on that paradigm and avoid using callbacks unless absolutely necessary in Kotlin
|
|
80
88
|
- For Flutter apps, always include the following imports. do not forget or modify them
|
|
81
89
|
- import 'package:firebase_core/firebase_core.dart';
|
|
82
90
|
- import 'package:firebase_ai/firebase_ai.dart';
|
|
@@ -27,6 +27,10 @@ exports.init_auth = (0, resource_1.resource)({
|
|
|
27
27
|
**Testing & Deployment:**
|
|
28
28
|
- Test the complete sign-up and sign-in flow to verify authentication functionality
|
|
29
29
|
- Deploy the application to production once authentication is verified and working properly
|
|
30
|
+
|
|
31
|
+
**Next Steps:**
|
|
32
|
+
- **Security Rules**: If an app uses *Cloud Firestore database*, *Cloud Storage for Firebase*, or *Firebase Realtime Database*, then please update user-based Security Rules that are structured according to the app's specific requirements.
|
|
33
|
+
- **App Deployment**: Deploy the app to production after Security Rules are verified to be working properly.
|
|
30
34
|
`.trim(),
|
|
31
35
|
},
|
|
32
36
|
],
|
|
@@ -30,6 +30,8 @@ ${config.readProjectFile("firestore.rules", { fallback: "<FILE DOES NOT EXIST>"
|
|
|
30
30
|
|
|
31
31
|
For database entities that neatly fall into the "personal" and "public categories, you can use the personalData and publicData rules. Use the following firestore.rules file, and add a comment above 'personalData' and 'publicData' to note what entities apply to each rule.
|
|
32
32
|
|
|
33
|
+
**Next Steps:**
|
|
34
|
+
- **App Deployment**: Deploy the app to production after Security Rules are verified to be working properly.
|
|
33
35
|
\`\`\`
|
|
34
36
|
rules_version = '2';
|
|
35
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveResource = exports.resourceTemplates = exports.resources = void 0;
|
|
3
|
+
exports.markdownDocsOfResources = exports.resolveResource = exports.resourceTemplates = exports.resources = void 0;
|
|
4
4
|
const docs_1 = require("./docs");
|
|
5
5
|
const init_ai_1 = require("./guides/init_ai");
|
|
6
6
|
const init_auth_1 = require("./guides/init_auth");
|
|
@@ -42,3 +42,18 @@ async function resolveResource(uri, ctx, track = true) {
|
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
exports.resolveResource = resolveResource;
|
|
45
|
+
function markdownDocsOfResources() {
|
|
46
|
+
const allResources = [...exports.resources, ...exports.resourceTemplates];
|
|
47
|
+
const headings = `
|
|
48
|
+
| Resource Name | Description |
|
|
49
|
+
| ------------- | ----------- |`;
|
|
50
|
+
const resourceRows = allResources.map((res) => {
|
|
51
|
+
let desc = res.mcp.title ? `${res.mcp.title}: ` : "";
|
|
52
|
+
desc += res.mcp.description || "";
|
|
53
|
+
desc = desc.replaceAll("\n", "<br>");
|
|
54
|
+
return `
|
|
55
|
+
| ${res.mcp.name} | ${desc} |`;
|
|
56
|
+
});
|
|
57
|
+
return headings + resourceRows.join("");
|
|
58
|
+
}
|
|
59
|
+
exports.markdownDocsOfResources = markdownDocsOfResources;
|
|
@@ -13,7 +13,7 @@ exports.list_backends = (0, tool_1.tool)({
|
|
|
13
13
|
"is the resource name of the Cloud Run service serving the App Hosting backend. The last segment of that name is the service ID. " +
|
|
14
14
|
"`domains` is the list of domains that are associated with the backend. They either have type `CUSTOM` or `DEFAULT`. " +
|
|
15
15
|
" Every backend should have a `DEFAULT` domain. " +
|
|
16
|
-
" The actual domain that a user would use to
|
|
16
|
+
" The actual domain that a user would use to connect to the backend is the last parameter of the domain resource name. " +
|
|
17
17
|
" If a custom domain is correctly set up, it will have statuses ending in `ACTIVE`.",
|
|
18
18
|
inputSchema: zod_1.z.object({
|
|
19
19
|
location: zod_1.z
|
|
@@ -8,6 +8,7 @@ const projectUtils_1 = require("../../../projectUtils");
|
|
|
8
8
|
const js_yaml_1 = require("js-yaml");
|
|
9
9
|
const auth_1 = require("../../../auth");
|
|
10
10
|
const configstore_1 = require("../../../configstore");
|
|
11
|
+
const appUtils_1 = require("../../../appUtils");
|
|
11
12
|
exports.get_environment = (0, tool_1.tool)({
|
|
12
13
|
name: "get_environment",
|
|
13
14
|
description: "Use this to retrieve the current Firebase **environment** configuration for the Firebase CLI and Firebase MCP server, including current authenticated user, project directory, active Firebase Project, and more.",
|
|
@@ -23,32 +24,48 @@ exports.get_environment = (0, tool_1.tool)({
|
|
|
23
24
|
}, async (_, { projectId, host, accountEmail, rc, config }) => {
|
|
24
25
|
const aliases = projectId ? (0, projectUtils_1.getAliases)({ rc }, projectId) : [];
|
|
25
26
|
const geminiTosAccepted = !!configstore_1.configstore.get("gemini");
|
|
27
|
+
const projectFileExists = config.projectFileExists("firebase.json");
|
|
28
|
+
const detectedApps = await (0, appUtils_1.detectApps)(process.cwd());
|
|
29
|
+
const allAccounts = (0, auth_1.getAllAccounts)().map((account) => account.user.email);
|
|
30
|
+
const hasOtherAccounts = allAccounts.filter((email) => email !== accountEmail).length > 0;
|
|
31
|
+
const projectConfigPathString = projectFileExists
|
|
32
|
+
? config.path("firebase.json")
|
|
33
|
+
: "<NO CONFIG PRESENT>";
|
|
34
|
+
const detectedAppsMap = detectedApps
|
|
35
|
+
.filter((app) => !!app.appId)
|
|
36
|
+
.reduce((map, app) => {
|
|
37
|
+
if (app.appId) {
|
|
38
|
+
map.set(app.appId, app.bundleId ? app.bundleId : "<UNKNOWN BUNDLE ID>");
|
|
39
|
+
}
|
|
40
|
+
return map;
|
|
41
|
+
}, new Map());
|
|
42
|
+
const activeProjectString = projectId
|
|
43
|
+
? `${projectId}${aliases.length ? ` (alias: ${aliases.join(",")})` : ""}`
|
|
44
|
+
: "<NONE>";
|
|
45
|
+
const acceptedGeminiTosString = geminiTosAccepted ? "Accepted" : "<NOT ACCEPTED>";
|
|
26
46
|
return (0, util_1.toContent)(`# Environment Information
|
|
27
47
|
|
|
28
48
|
Project Directory: ${host.cachedProjectDir}
|
|
29
|
-
Project Config Path: ${
|
|
30
|
-
Active Project ID: ${
|
|
49
|
+
Project Config Path: ${projectConfigPathString}
|
|
50
|
+
Active Project ID: ${activeProjectString}
|
|
51
|
+
Gemini in Firebase Terms of Service: ${acceptedGeminiTosString}
|
|
31
52
|
Authenticated User: ${accountEmail || "<NONE>"}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
${(0, js_yaml_1.dump)(rc.projects).trim()}
|
|
37
|
-
|
|
38
|
-
# Available Accounts:
|
|
39
|
-
|
|
40
|
-
${(0, js_yaml_1.dump)((0, auth_1.getAllAccounts)().map((account) => account.user.email)).trim()}
|
|
41
|
-
${config.projectFileExists("firebase.json")
|
|
42
|
-
? `
|
|
43
|
-
# firebase.json contents:
|
|
53
|
+
Detected App IDs: ${detectedAppsMap.size > 0 ? `\n\n${(0, js_yaml_1.dump)(Object.fromEntries(detectedAppsMap)).trim()}\n` : "<NONE>"}
|
|
54
|
+
Available Project Aliases (format: '[alias]: [projectId]'): ${Object.entries(rc.projects).length > 0 ? `\n\n${(0, js_yaml_1.dump)(rc.projects).trim()}\n` : "<NONE>"}${hasOtherAccounts ? `\nAvailable Accounts: \n\n${(0, js_yaml_1.dump)(allAccounts).trim()}` : ""}
|
|
55
|
+
${projectFileExists
|
|
56
|
+
? `\nfirebase.json contents:
|
|
44
57
|
|
|
45
58
|
\`\`\`json
|
|
46
59
|
${config.readProjectFile("firebase.json")}
|
|
47
60
|
\`\`\``
|
|
48
|
-
: `\
|
|
61
|
+
: `\nNo firebase.json file was found.
|
|
62
|
+
|
|
63
|
+
If this project does not use Firebase services that require a firebase.json file, no action is necessary.
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
If this project uses Firebase services that require a firebase.json file, the user will most likely want to:
|
|
51
66
|
|
|
52
67
|
a) Change the project directory using the 'firebase_update_environment' tool to select a directory with a 'firebase.json' file in it, or
|
|
53
|
-
b) Initialize a new Firebase project directory using the 'firebase_init' tool
|
|
68
|
+
b) Initialize a new Firebase project directory using the 'firebase_init' tool.
|
|
69
|
+
|
|
70
|
+
Confirm with the user before taking action.`}`);
|
|
54
71
|
});
|
|
@@ -11,6 +11,7 @@ const errors_1 = require("../../errors");
|
|
|
11
11
|
const error_1 = require("../../../error");
|
|
12
12
|
const utils_1 = require("../../../init/features/ailogic/utils");
|
|
13
13
|
const projects_1 = require("../../../management/projects");
|
|
14
|
+
const dataconnect_1 = require("../../../init/features/dataconnect");
|
|
14
15
|
exports.init = (0, tool_1.tool)({
|
|
15
16
|
name: "init",
|
|
16
17
|
description: "Use this to initialize selected Firebase services in the workspace (Cloud Firestore database, Firebase Data Connect, Firebase Realtime Database, Firebase AI Logic). All services are optional; specify only the products you want to set up. " +
|
|
@@ -70,7 +71,7 @@ exports.init = (0, tool_1.tool)({
|
|
|
70
71
|
location_id: zod_1.z
|
|
71
72
|
.string()
|
|
72
73
|
.optional()
|
|
73
|
-
.default(
|
|
74
|
+
.default(dataconnect_1.FDC_DEFAULT_REGION)
|
|
74
75
|
.describe("The GCP region ID to set up the Firebase Data Connect service."),
|
|
75
76
|
cloudsql_instance_id: zod_1.z
|
|
76
77
|
.string()
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.logout = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const clc = require("colorette");
|
|
6
5
|
const tool_1 = require("../../tool");
|
|
7
6
|
const util_1 = require("../../util");
|
|
8
7
|
const auth_1 = require("../../../auth");
|
|
@@ -44,7 +43,7 @@ exports.logout = (0, tool_1.tool)({
|
|
|
44
43
|
if (token) {
|
|
45
44
|
try {
|
|
46
45
|
await (0, auth_1.logout)(token);
|
|
47
|
-
logoutMessages.push(`Logged out from ${
|
|
46
|
+
logoutMessages.push(`Logged out from ${account.user.email}`);
|
|
48
47
|
}
|
|
49
48
|
catch (e) {
|
|
50
49
|
if (e instanceof Error) {
|
|
@@ -6,6 +6,7 @@ const tool_1 = require("../../tool");
|
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const functionslog_1 = require("../../../functions/functionslog");
|
|
8
8
|
const cloudlogging_1 = require("../../../gcp/cloudlogging");
|
|
9
|
+
const error_1 = require("../../../error");
|
|
9
10
|
const SEVERITY_LEVELS = [
|
|
10
11
|
"DEFAULT",
|
|
11
12
|
"DEBUG",
|
|
@@ -39,12 +40,13 @@ function validateTimestamp(label, value) {
|
|
|
39
40
|
}
|
|
40
41
|
exports.get_logs = (0, tool_1.tool)({
|
|
41
42
|
name: "get_logs",
|
|
42
|
-
description: "
|
|
43
|
+
description: "Use this to retrieve a page of Cloud Functions log entries using Google Cloud Logging advanced filters.",
|
|
43
44
|
inputSchema: zod_1.z.object({
|
|
44
45
|
function_names: zod_1.z
|
|
45
|
-
.
|
|
46
|
+
.array(zod_1.z.string())
|
|
47
|
+
.min(1)
|
|
46
48
|
.optional()
|
|
47
|
-
.describe("Optional list of deployed Cloud Function
|
|
49
|
+
.describe("Optional list of deployed Cloud Function IDs to filter logs (e.g. ['fnA','fnB'])."),
|
|
48
50
|
page_size: zod_1.z
|
|
49
51
|
.number()
|
|
50
52
|
.int()
|
|
@@ -84,8 +86,8 @@ exports.get_logs = (0, tool_1.tool)({
|
|
|
84
86
|
requiresProject: true,
|
|
85
87
|
},
|
|
86
88
|
}, async ({ function_names, page_size, order, page_token, min_severity, start_time, end_time, filter }, { projectId }) => {
|
|
87
|
-
const resolvedOrder = order;
|
|
88
|
-
const resolvedPageSize = page_size;
|
|
89
|
+
const resolvedOrder = (order === null || order === void 0 ? void 0 : order.toLowerCase()) === "asc" ? "asc" : "desc";
|
|
90
|
+
const resolvedPageSize = page_size !== null && page_size !== void 0 ? page_size : 50;
|
|
89
91
|
const normalizedSelectors = normalizeFunctionSelectors(function_names);
|
|
90
92
|
const filterParts = [(0, functionslog_1.getApiFilter)(normalizedSelectors)];
|
|
91
93
|
if (min_severity) {
|
|
@@ -143,7 +145,7 @@ exports.get_logs = (0, tool_1.tool)({
|
|
|
143
145
|
return (0, util_1.toContent)(response);
|
|
144
146
|
}
|
|
145
147
|
catch (err) {
|
|
146
|
-
const
|
|
147
|
-
return (0, util_1.mcpError)(
|
|
148
|
+
const errMsg = (0, error_1.getErrMsg)((err === null || err === void 0 ? void 0 : err.original) || err, "Failed to retrieve Cloud Logging entries.");
|
|
149
|
+
return (0, util_1.mcpError)(errMsg);
|
|
148
150
|
}
|
|
149
151
|
});
|
package/lib/mcp/tools/index.js
CHANGED
|
@@ -18,7 +18,7 @@ function availableTools(activeFeatures) {
|
|
|
18
18
|
activeFeatures = Object.keys(tools);
|
|
19
19
|
}
|
|
20
20
|
if (!activeFeatures.includes("core")) {
|
|
21
|
-
activeFeatures
|
|
21
|
+
activeFeatures = ["core", ...activeFeatures];
|
|
22
22
|
}
|
|
23
23
|
for (const key of activeFeatures) {
|
|
24
24
|
toolDefs.push(...tools[key]);
|
|
@@ -53,8 +53,9 @@ function markdownDocsOfTools() {
|
|
|
53
53
|
if (feature === "firebase") {
|
|
54
54
|
feature = "core";
|
|
55
55
|
}
|
|
56
|
+
const description = (((_c = tool.mcp) === null || _c === void 0 ? void 0 : _c.description) || "").replaceAll("\n", "<br>");
|
|
56
57
|
doc += `
|
|
57
|
-
| ${tool.mcp.name} | ${feature} | ${
|
|
58
|
+
| ${tool.mcp.name} | ${feature} | ${description} |`;
|
|
58
59
|
}
|
|
59
60
|
return doc;
|
|
60
61
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteExperiment = void 0;
|
|
4
|
+
const clc = require("colorette");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const apiv2_1 = require("../apiv2");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const TIMEOUT = 30000;
|
|
10
|
+
const apiClient = new apiv2_1.Client({
|
|
11
|
+
urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
|
|
12
|
+
apiVersion: "v1",
|
|
13
|
+
});
|
|
14
|
+
async function deleteExperiment(projectId, namespace, experimentId) {
|
|
15
|
+
try {
|
|
16
|
+
await apiClient.request({
|
|
17
|
+
method: "DELETE",
|
|
18
|
+
path: `projects/${projectId}/namespaces/${namespace}/experiments/${experimentId}`,
|
|
19
|
+
timeout: TIMEOUT,
|
|
20
|
+
});
|
|
21
|
+
return clc.bold(`Successfully deleted experiment ${clc.yellow(experimentId)}`);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const error = (0, error_1.getError)(err);
|
|
25
|
+
if (error.message.includes("is running and cannot be deleted")) {
|
|
26
|
+
const rcConsoleUrl = (0, utils_1.consoleUrl)(projectId, `/config/experiment/results/${experimentId}`);
|
|
27
|
+
throw new error_1.FirebaseError(`Experiment ${experimentId} is currently running and cannot be deleted. If you want to delete this experiment, stop it at ${rcConsoleUrl}`, { original: error });
|
|
28
|
+
}
|
|
29
|
+
throw new error_1.FirebaseError(`Failed to delete Remote Config experiment with ID ${experimentId} for project ${projectId}. Error: ${(0, error_1.getErrMsg)(err)}`, { original: error });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.deleteExperiment = deleteExperiment;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteRollout = void 0;
|
|
4
|
+
const api_1 = require("../api");
|
|
5
|
+
const apiv2_1 = require("../apiv2");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const clc = require("colorette");
|
|
9
|
+
const TIMEOUT = 30000;
|
|
10
|
+
const apiClient = new apiv2_1.Client({
|
|
11
|
+
urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
|
|
12
|
+
apiVersion: "v1",
|
|
13
|
+
});
|
|
14
|
+
async function deleteRollout(projectId, namespace, rolloutId) {
|
|
15
|
+
try {
|
|
16
|
+
await apiClient.request({
|
|
17
|
+
method: "DELETE",
|
|
18
|
+
path: `/projects/${projectId}/namespaces/${namespace}/rollouts/${rolloutId}`,
|
|
19
|
+
timeout: TIMEOUT,
|
|
20
|
+
});
|
|
21
|
+
return clc.bold(`Successfully deleted rollout ${clc.yellow(rolloutId)}`);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const originalError = (0, error_1.getError)(err);
|
|
25
|
+
const errorMessage = (0, error_1.getErrMsg)(err);
|
|
26
|
+
if (errorMessage.includes("is running and cannot be deleted")) {
|
|
27
|
+
const rcConsoleUrl = (0, utils_1.consoleUrl)(projectId, `/config/env/firebase/rollout/${rolloutId}`);
|
|
28
|
+
throw new error_1.FirebaseError(`Rollout '${rolloutId}' is currently running and cannot be deleted. If you want to delete this rollout, stop it at ${rcConsoleUrl}`, { original: originalError });
|
|
29
|
+
}
|
|
30
|
+
throw new error_1.FirebaseError(`Failed to delete Remote Config rollout with ID ${rolloutId} for project ${projectId}. Error: ${errorMessage}`, { original: originalError });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.deleteRollout = deleteRollout;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getExperiment = exports.parseExperiment = void 0;
|
|
4
|
+
const Table = require("cli-table3");
|
|
5
|
+
const util = require("util");
|
|
6
|
+
const api_1 = require("../api");
|
|
7
|
+
const apiv2_1 = require("../apiv2");
|
|
8
|
+
const logger_1 = require("../logger");
|
|
9
|
+
const error_1 = require("../error");
|
|
10
|
+
const TIMEOUT = 30000;
|
|
11
|
+
const TABLE_HEAD = ["Entry Name", "Value"];
|
|
12
|
+
const apiClient = new apiv2_1.Client({
|
|
13
|
+
urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
|
|
14
|
+
apiVersion: "v1",
|
|
15
|
+
});
|
|
16
|
+
const parseExperiment = (experiment) => {
|
|
17
|
+
const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
|
|
18
|
+
table.push(["Name", experiment.name]);
|
|
19
|
+
table.push(["Display Name", experiment.definition.displayName]);
|
|
20
|
+
table.push(["Service", experiment.definition.service]);
|
|
21
|
+
table.push([
|
|
22
|
+
"Objectives",
|
|
23
|
+
util.inspect(experiment.definition.objectives, { showHidden: false, depth: null }),
|
|
24
|
+
]);
|
|
25
|
+
table.push([
|
|
26
|
+
"Variants",
|
|
27
|
+
util.inspect(experiment.definition.variants, { showHidden: false, depth: null }),
|
|
28
|
+
]);
|
|
29
|
+
table.push(["State", experiment.state]);
|
|
30
|
+
table.push(["Start Time", experiment.startTime]);
|
|
31
|
+
table.push(["End Time", experiment.endTime]);
|
|
32
|
+
table.push(["Last Update Time", experiment.lastUpdateTime]);
|
|
33
|
+
table.push(["etag", experiment.etag]);
|
|
34
|
+
return table.toString();
|
|
35
|
+
};
|
|
36
|
+
exports.parseExperiment = parseExperiment;
|
|
37
|
+
async function getExperiment(projectId, namespace, experimentId) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await apiClient.request({
|
|
40
|
+
method: "GET",
|
|
41
|
+
path: `projects/${projectId}/namespaces/${namespace}/experiments/${experimentId}`,
|
|
42
|
+
timeout: TIMEOUT,
|
|
43
|
+
});
|
|
44
|
+
return res.body;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const error = (0, error_1.getError)(err);
|
|
48
|
+
logger_1.logger.debug(error.message);
|
|
49
|
+
throw new error_1.FirebaseError(`Failed to get Remote Config experiment with ID ${experimentId} for project ${projectId}. Error: ${error.message}`, { original: error });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.getExperiment = getExperiment;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRollout = exports.parseRolloutIntoTable = void 0;
|
|
4
|
+
const api_1 = require("../api");
|
|
5
|
+
const apiv2_1 = require("../apiv2");
|
|
6
|
+
const logger_1 = require("../logger");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const Table = require("cli-table3");
|
|
9
|
+
const util = require("util");
|
|
10
|
+
const TIMEOUT = 30000;
|
|
11
|
+
const TABLE_HEAD = ["Entry Name", "Value"];
|
|
12
|
+
const apiClient = new apiv2_1.Client({
|
|
13
|
+
urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
|
|
14
|
+
apiVersion: "v1",
|
|
15
|
+
});
|
|
16
|
+
const parseRolloutIntoTable = (rollout) => {
|
|
17
|
+
const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
|
|
18
|
+
table.push(["Name", rollout.name], ["Display Name", rollout.definition.displayName], ["Description", rollout.definition.description], ["State", rollout.state], ["Create Time", rollout.createTime], ["Start Time", rollout.startTime], ["End Time", rollout.endTime], ["Last Update Time", rollout.lastUpdateTime], [
|
|
19
|
+
"Control Variant",
|
|
20
|
+
util.inspect(rollout.definition.controlVariant, { showHidden: false, depth: null }),
|
|
21
|
+
], [
|
|
22
|
+
"Enabled Variant",
|
|
23
|
+
util.inspect(rollout.definition.enabledVariant, { showHidden: false, depth: null }),
|
|
24
|
+
], ["ETag", rollout.etag]);
|
|
25
|
+
return table.toString();
|
|
26
|
+
};
|
|
27
|
+
exports.parseRolloutIntoTable = parseRolloutIntoTable;
|
|
28
|
+
async function getRollout(projectId, namespace, rolloutId) {
|
|
29
|
+
try {
|
|
30
|
+
const res = await apiClient.request({
|
|
31
|
+
method: "GET",
|
|
32
|
+
path: `/projects/${projectId}/namespaces/${namespace}/rollouts/${rolloutId}`,
|
|
33
|
+
timeout: TIMEOUT,
|
|
34
|
+
});
|
|
35
|
+
return res.body;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const error = (0, error_1.getError)(err);
|
|
39
|
+
logger_1.logger.debug(error.message);
|
|
40
|
+
throw new error_1.FirebaseError(`Failed to get Remote Config Rollout with ID ${rolloutId} for project ${projectId}. Error: ${error.message}`, { original: error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.getRollout = getRollout;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TagColor = void 0;
|
|
3
|
+
exports.TagColor = exports.DEFAULT_PAGE_SIZE = exports.NAMESPACE_FIREBASE = void 0;
|
|
4
|
+
exports.NAMESPACE_FIREBASE = "firebase";
|
|
5
|
+
exports.DEFAULT_PAGE_SIZE = "10";
|
|
4
6
|
var TagColor;
|
|
5
7
|
(function (TagColor) {
|
|
6
8
|
TagColor["BLUE"] = "Blue";
|