diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 3458b97e596..c1298fbaa46 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -45,6 +45,7 @@ import { LoggingStdioServerTransport } from "./logging-transport"; import { isFirebaseStudio } from "../env"; import { timeoutFallback } from "../timeout"; import { resolveResource, resources, resourceTemplates } from "./resources"; +import * as crossSpawn from "cross-spawn"; const SERVER_VERSION = "0.3.0"; @@ -71,6 +72,7 @@ export class FirebaseMcpServer { detectedFeatures?: ServerFeature[]; clientInfo?: { name?: string; version?: string }; emulatorHubClient?: EmulatorHubClient; + private cliCommand?: string; // logging spec: // https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/logging @@ -294,9 +296,18 @@ export class FirebaseMcpServer { config: Config.load(options, true) || new Config({}, options), rc: loadRC(options), accountEmail, + firebaseCliCommand: this._getFirebaseCliCommand(), }; } + private _getFirebaseCliCommand(): string { + if (!this.cliCommand) { + const testCommand = crossSpawn.sync("firebase --version"); + this.cliCommand = testCommand.error ? "npx firebase-tools@latest" : "firebase"; + } + return this.cliCommand; + } + async mcpListTools(): Promise { await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]); const hasActiveProject = !!(await this.getProjectId()); diff --git a/src/mcp/prompts/core/deploy.ts b/src/mcp/prompts/core/deploy.ts index 937ad59b009..71d350e6b23 100644 --- a/src/mcp/prompts/core/deploy.ts +++ b/src/mcp/prompts/core/deploy.ts @@ -15,7 +15,7 @@ export const deploy = prompt( title: "Deploy to Firebase", }, }, - async ({ prompt }, { config, projectId, accountEmail }) => { + async ({ prompt }, { config, projectId, accountEmail, firebaseCliCommand }) => { return [ { role: "user" as const, @@ -41,7 +41,7 @@ ${prompt || ""} Follow the steps below taking note of any user instructions provided above. -1. If there is no active user, prompt the user to run \`firebase login\` in an interactive terminal before continuing. +1. If there is no active user, prompt the user to run \`${firebaseCliCommand} login\` in an interactive terminal before continuing. 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." 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: 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. Example (No): "No, there are no dependencies or file structures that indicate the use of a server-side rendering framework." 4. If there is no \`firebase.json\` file, manually create one based on whether the app requires SSR: 4a. If the app requires SSR, configure Firebase App Hosting: - Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": ""}}\ + Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": ""}}\ 4b. If the app does NOT require SSR, configure Firebase Hosting: Create \`firebase.json\ with a "hosting" configuration. Add a \`{"hosting": {"predeploy": ""}}\` config to build before deploying. 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. 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). 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). -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). - If it doesn't exist, create one by running the \`firebase apphosting:backends:create --backend --primary-region us-central1 --root-dir .\` shell. -7. Only after making sure Firebase has been initialized, run the \`firebase deploy\` shell command to perform the deploy. This may take a few minutes. +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). + If it doesn't exist, create one by running the \`${firebaseCliCommand} apphosting:backends:create --backend --primary-region us-central1 --root-dir .\` shell. +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. 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//apphosting\` 8. If the deploy has errors, attempt to fix them and ask the user clarifying questions as needed. -9. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running \`firebase deploy --force\`. -10. If only one specific feature is failing, use command \`firebase deploy --only \` as you debug. +9. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running \`${firebaseCliCommand} deploy --force\`. +10. If only one specific feature is failing, use command \`${firebaseCliCommand} deploy --only \` as you debug. 11. If the deploy succeeds, your job is finished. `.trim(), }, diff --git a/src/mcp/prompts/core/init.ts b/src/mcp/prompts/core/init.ts index 9e7b19ff8e0..41f3ad07737 100644 --- a/src/mcp/prompts/core/init.ts +++ b/src/mcp/prompts/core/init.ts @@ -11,7 +11,7 @@ export const init = prompt( }, }, async (_, mcp) => { - const { config, projectId, accountEmail } = mcp; + const { config, projectId, accountEmail, firebaseCliCommand } = mcp; const platform = await getPlatformFromFolder(config.projectDir); @@ -47,7 +47,7 @@ ${config.readProjectFile("firebase.json", { fallback: "" }) Follow the steps below taking note of any user instructions provided above. 1. If there is no active user, use the \`firebase_login\` tool to help them sign in. - - If you run into issues logging the user in, suggest that they run \`npx firebase-tools login --reauth\` in a separate terminal + - If you run into issues logging the user in, suggest that they run \`${firebaseCliCommand} login --reauth\` in a separate terminal 2.1 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. 1. 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. - 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. diff --git a/src/mcp/prompts/crashlytics/connect.ts b/src/mcp/prompts/crashlytics/connect.ts index bef419bcf25..066396c2fe2 100644 --- a/src/mcp/prompts/crashlytics/connect.ts +++ b/src/mcp/prompts/crashlytics/connect.ts @@ -9,7 +9,7 @@ export const connect = prompt( title: "Access Crashlytics data", }, }, - async (unused, { accountEmail }) => { + async (unused, { accountEmail, firebaseCliCommand }) => { return [ { role: "user" as const, @@ -25,7 +25,7 @@ Active user: ${accountEmail || ""} 1. **Make sure the user is logged in. No Crashlytics tools will work if the user is not logged in.** a. Use the \`firebase_get_environment\` tool to verify that the user is logged in. - b. If the Firebase 'Active user' is set to , instruct the user to run \`firebase login\` + b. If the Firebase 'Active user' is set to , instruct the user to run \`${firebaseCliCommand} login\` before continuing. Ignore other fields that are set to . We are just making sure the user is logged in. diff --git a/src/mcp/types.ts b/src/mcp/types.ts index 04381a744f8..753b6dcb443 100644 --- a/src/mcp/types.ts +++ b/src/mcp/types.ts @@ -28,4 +28,5 @@ export interface McpContext { config: Config; host: FirebaseMcpServer; rc: RC; + firebaseCliCommand: string; }