Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@
return {};
});

this.detectProjectRoot();

Check warning on line 149 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
this.detectActiveFeatures();

Check warning on line 150 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}

/** Wait until initialization has finished. */
ready() {

Check warning on line 154 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
if (this._ready) return Promise.resolve();
return new Promise((resolve, reject) => {
this._readyPromises.push({ resolve: resolve as () => void, reject });
Expand All @@ -162,19 +162,19 @@
return this.clientInfo?.name ?? (isFirebaseStudio() ? "Firebase Studio" : "<unknown-client>");
}

private get clientConfigKey() {

Check warning on line 165 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
return `mcp.clientConfigs.${this.clientName}:${this.startupRoot || process.cwd()}`;
}

getStoredClientConfig(): ClientConfig {
return configstore.get(this.clientConfigKey) || {};

Check warning on line 170 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
}

updateStoredClientConfig(update: Partial<ClientConfig>) {

Check warning on line 173 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
const config = configstore.get(this.clientConfigKey) || {};

Check warning on line 174 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
const newConfig = { ...config, ...update };

Check warning on line 175 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
configstore.set(this.clientConfigKey, newConfig);
return newConfig;

Check warning on line 177 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
}

async detectProjectRoot(): Promise<string> {
Expand Down Expand Up @@ -274,7 +274,7 @@
return getProjectId(await this.resolveOptions());
}

async getAuthenticatedUser(skipAutoAuth: boolean = false): Promise<string | null> {

Check warning on line 277 in src/mcp/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Type boolean trivially inferred from a boolean literal, remove type annotation
try {
this.log("debug", `calling requireAuth`);
const email = await requireAuth(await this.resolveOptions(), skipAutoAuth);
Expand All @@ -294,9 +294,19 @@
config: Config.load(options, true) || new Config({}, options),
rc: loadRC(options),
accountEmail,
firebaseCliCommand: this._getFirebaseCliCommand(),
};
}

private _getFirebaseCliCommand(): string {
// TODO(samedson) In the future, we'd like to detect how you ran the MCP
// server, and use that so that the CLI doesn't run commands that result in
// an install. The reason we can't just use `firebase` is some users may
// have run the MCP server via npx, and don't have firebase installed
// globally
return "npx firebase-tools@latest";
}

async mcpListTools(): Promise<ListToolsResult> {
await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
const hasActiveProject = !!(await this.getProjectId());
Expand Down
16 changes: 8 additions & 8 deletions src/mcp/prompts/core/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,7 +41,7 @@ ${prompt || "<the user didn't supply specific instructions>"}

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.
Expand Down Expand Up @@ -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": "<backendId>"}}\
Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": "<backendId>"}}\
4b. If the app does NOT require SSR, configure Firebase Hosting:
Create \`firebase.json\ with a "hosting" configuration. Add a \`{"hosting": {"predeploy": "<build_script>"}}\` 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 <backendId> --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 <backendId> --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/<projectId>/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 <feature>\` 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 <feature>\` as you debug.
11. If the deploy succeeds, your job is finished.
`.trim(),
},
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/prompts/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -47,7 +47,7 @@ ${config.readProjectFile("firebase.json", { fallback: "<FILE DOES NOT EXIST>" })
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.
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/prompts/crashlytics/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const connect = prompt(
title: "Access Crashlytics data",
},
},
async (unused, { accountEmail }) => {
async (unused, { accountEmail, firebaseCliCommand }) => {
return [
{
role: "user" as const,
Expand All @@ -25,7 +25,7 @@ Active user: ${accountEmail || "<NONE>"}
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 <NONE>, instruct the user to run \`firebase login\`
b. If the Firebase 'Active user' is set to <NONE>, instruct the user to run \`${firebaseCliCommand} login\`
before continuing. Ignore other fields that are set to <NONE>. We are just making sure the
user is logged in.
Expand Down
1 change: 1 addition & 0 deletions src/mcp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export interface McpContext {
config: Config;
host: FirebaseMcpServer;
rc: RC;
firebaseCliCommand: string;
}
Loading