Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ npx -y firebase-tools login
| firebase_create_project | core | Creates a new Firebase project. |
| firebase_create_app | core | Creates a new app in your Firebase project for Web, iOS, or Android. |
| firebase_create_android_sha | core | Adds a SHA certificate hash to an existing Android app. |
| firebase_consult_assistant | core | Send a question to an AI assistant specifically enhanced to answer Firebase questions. |
| firebase_get_environment | core | Retrieves information about the current Firebase environment including current authenticated user, project directory, active project, and more. |
| firebase_update_environment | core | Updates Firebase environment config such as project directory, active project, active user account, and more. Use `firebase_get_environment` to see the currently configured environment. |
| firebase_init | core | Initializes selected Firebase features in the workspace (Firestore, Data Connect, Realtime Database). All features are optional; provide only the products you wish to set up. You can initialize new features into an existing project directory, but re-initializing an existing feature may overwrite configuration. To deploy the initialized features, run the `firebase deploy` command after `firebase_init` tool. |
Expand All @@ -66,6 +67,8 @@ npx -y firebase-tools login
| auth_set_claim | auth | Sets a custom claim on a specific user's account. Use to create trusted values associated with a user e.g. marking them as an admin. Claims are limited in size and should be succinct in name and value. Specify ONLY ONE OF `value` or `json_value` parameters. |
| auth_set_sms_region_policy | auth | Sets an SMS Region Policy for Firebase Auth to restrict the regions which can receive text messages based on an ALLOW or DENY list of country codes. This policy will override any existing policies when set. |
| dataconnect_list_services | dataconnect | List the Firebase Data Connect services available in the current project. |
| dataconnect_generate_schema | dataconnect | Generates a Firebase Data Connect Schema based on the users description of an app. |
| dataconnect_generate_operation | dataconnect | Generates a single Firebase Data Connect query or mutation based on the currently deployed schema and the provided prompt. |
| dataconnect_get_schema | dataconnect | Retrieve information about the Firebase Data Connect Schema in the project, including Cloud SQL data sources and the GraphQL Schema describing the data model. |
| dataconnect_get_connectors | dataconnect | Get the Firebase Data Connect Connectors in the project, which includes the pre-defined GraphQL queries accessible to client SDKs. |
| dataconnect_execute_graphql | dataconnect | Executes an arbitrary GraphQL against a Data Connect service or its emulator. |
Expand Down
11 changes: 10 additions & 1 deletion src/mcp/errors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types";
import { commandExistsSync, mcpError } from "./util";

export const NO_PROJECT_ERROR = 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",
);

export function mcpAuthError() {
export function mcpAuthError(): CallToolResult {

Check warning on line 9 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
const cmd = commandExistsSync("firebase") ? "firebase" : "npx -y firebase-tools";
return 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.
\`\`\`sh
Expand All @@ -14,3 +15,11 @@

[ADC]: https://cloud.google.com/docs/authentication/application-default-credentials`);
}

export function mcpGeminiError(projectId: string) {

Check warning on line 19 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment

Check warning on line 19 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
const consoleUrl = `https://firebase.corp.google.com/project/${projectId}/overview`;
return mcpError(
`This tool uses the Gemini in Firebase API. Visit Firebase Console to enable the Gemini in Firebase API ${consoleUrl} and try again.`,
"PRECONDITION_FAILED",
);
}
19 changes: 18 additions & 1 deletion src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@
import { requireAuth } from "../requireAuth.js";
import { Options } from "../options.js";
import { getProjectId } from "../projectUtils.js";
import { mcpAuthError, NO_PROJECT_ERROR } from "./errors.js";
import { mcpAuthError, NO_PROJECT_ERROR, mcpGeminiError } from "./errors.js";
import { trackGA4 } from "../track.js";
import { Config } from "../config.js";
import { loadRC } from "../rc.js";
import { EmulatorHubClient } from "../emulator/hubClient.js";
import { Emulators } from "../emulator/types.js";
import { existsSync } from "node:fs";
import { ensure, check } from "../ensureApiEnabled.js";
import * as api from "../api.js";

const SERVER_VERSION = "0.1.0";

const cmd = new Command("experimental:mcp").before(requireAuth);

export class FirebaseMcpServer {
private _ready: boolean = false;

Check warning on line 34 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
private _readyPromises: { resolve: () => void; reject: (err: unknown) => void }[] = [];
startupRoot?: string;
cachedProjectRoot?: string;
Expand All @@ -46,11 +48,11 @@
this.server.registerCapabilities({ tools: { listChanged: true } });
this.server.setRequestHandler(ListToolsRequestSchema, this.mcpListTools.bind(this));
this.server.setRequestHandler(CallToolRequestSchema, this.mcpCallTool.bind(this));
this.server.oninitialized = async () => {

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

View workflow job for this annotation

GitHub Actions / lint (20)

Promise-returning function provided to variable where a void return was expected
const clientInfo = this.server.getClientVersion();
this.clientInfo = clientInfo;
if (clientInfo?.name) {
trackGA4("mcp_client_connected", {

Check warning on line 55 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
mcp_client_name: clientInfo.name,
mcp_client_version: clientInfo.version,
});
Expand All @@ -62,19 +64,19 @@
this._readyPromises.pop()?.resolve();
}
};
this.detectProjectRoot();

Check warning on line 67 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 68 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 72 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 });
});
}

private get clientConfigKey() {

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

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
return `mcp.clientConfigs.${this.clientInfo?.name || "<unknown-client>"}:${this.startupRoot || process.cwd()}`;
}

Expand Down Expand Up @@ -207,6 +209,7 @@
const tool = this.getTool(toolName);
if (!tool) throw new Error(`Tool '${toolName}' could not be found.`);

// Check if the current project directory exists.
if (
tool.mcp.name !== "firebase_update_environment" && // allow this tool only, to fix the issue
(!this.cachedProjectRoot || !existsSync(this.cachedProjectRoot))
Expand All @@ -215,17 +218,31 @@
`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.`,
);
}

// Check if the project ID is set.
let projectId = await this.getProjectId();
if (tool.mcp._meta?.requiresProject && !projectId) {
return NO_PROJECT_ERROR;
}
projectId = projectId || "";

// Check if the user is logged in.
const accountEmail = await this.getAuthenticatedUser();
if (tool.mcp._meta?.requiresAuth && !accountEmail) {
return mcpAuthError();
}

// Check if the tool requires Gemini in Firebase API.
if (tool.mcp._meta?.requiresGemini) {
if (configstore.get("gemini")) {
await ensure(projectId, api.cloudAiCompanionOrigin(), "");
} else {
if (!(await check(projectId, api.cloudAiCompanionOrigin(), ""))) {
return mcpGeminiError(projectId);
}
}
}

const options = { projectDir: this.cachedProjectRoot, cwd: this.cachedProjectRoot };
const toolsCtx: ServerToolContext = {
projectId: projectId,
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export interface ServerTool<InputSchema extends ZodTypeAny = ZodTypeAny> {
requiresProject?: boolean;
/** Set this on a tool if it *always* requires a signed-in user to work. */
requiresAuth?: boolean;
/** Set this on a tool if it uses Gemini in Firebase API in any way. */
requiresGemini?: boolean;
/** Tools are grouped by feature. --only can configure what tools is available. */
feature?: string;
};
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tools/core/consult_assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const consult_assistant = tool(
_meta: {
requiresProject: true,
requiresAuth: true,
// TODO: Create an endpoint to check for GiF activation.
requiresGemini: true,
},
},
async ({ prompt }, { projectId }) => {
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/tools/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { init } from "./init.js";
import { get_environment } from "./get_environment.js";
import { update_environment } from "./update_environment.js";
import { list_projects } from "./list_projects.js";
// import { consult_assistant } from "./consult_assistant.js";
import { consult_assistant } from "./consult_assistant.js";

export const coreTools: ServerTool[] = [
get_project,
Expand All @@ -22,7 +22,7 @@ export const coreTools: ServerTool[] = [
create_project,
create_app,
create_android_sha,
// consult_assistant,
consult_assistant,
get_environment,
update_environment,
init,
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tools/dataconnect/generate_operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const generate_operation = tool(
_meta: {
requiresProject: true,
requiresAuth: true,
// TODO: Create an endpoint to check for GiF activiation.
requiresGemini: true,
},
},
async ({ prompt, service_id }, { projectId, config }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tools/dataconnect/generate_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const generate_schema = tool(
_meta: {
requiresProject: true,
requiresAuth: true,
// TODO: Create an endpoint to check for GiF activiation.
requiresGemini: true,
},
},
async ({ prompt }, { projectId }) => {
Expand Down
9 changes: 4 additions & 5 deletions src/mcp/tools/dataconnect/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ServerTool } from "../../tool.js";
// import { generate_operation } from "./generate_operation.js";
// import { generate_schema } from "./generate_schema.js";

import { generate_operation } from "./generate_operation.js";
import { generate_schema } from "./generate_schema.js";
import { list_services } from "./list_services.js";
import { get_schema } from "./get_schema.js";
import { get_connectors } from "./get_connector.js";
Expand All @@ -12,8 +11,8 @@ import { execute_mutation } from "./execute_mutation.js";

export const dataconnectTools: ServerTool[] = [
list_services,
// generate_schema,
// generate_operation,
generate_schema,
generate_operation,
get_schema,
get_connectors,
execute_graphql,
Expand Down
Loading