From b9905347c3eae294f9e98abb927743e23563445f Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 18 Jul 2025 15:59:03 -0700 Subject: [PATCH 01/11] still debugging --- src/mcp/index.ts | 73 ++++++++++++++++++++++++------------ src/mcp/logging-transport.ts | 22 +++++++++++ src/mcp/util.ts | 18 ++++++++- src/requireAuth.ts | 7 +++- 4 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 src/mcp/logging-transport.ts diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 4a32b27588e..b46fb7f6a37 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -9,7 +9,7 @@ import { ListToolsRequestSchema, CallToolResult, } from "@modelcontextprotocol/sdk/types.js"; -import { checkFeatureActive, mcpError } from "./util.js"; +import { checkFeatureActive, mcpError, timeoutFallback } from "./util.js"; import { ClientConfig, SERVER_FEATURES, ServerFeature } from "./types.js"; import { availableTools } from "./tools/index.js"; import { ServerTool, ServerToolContext } from "./tool.js"; @@ -27,10 +27,11 @@ import { Emulators } from "../emulator/types.js"; import { existsSync } from "node:fs"; import { ensure, check } from "../ensureApiEnabled.js"; import * as api from "../api.js"; +import { LoggingStdioServerTransport } from "./logging-transport.js"; const SERVER_VERSION = "0.1.0"; -const cmd = new Command("experimental:mcp").before(requireAuth); +const cmd = new Command("experimental:mcp"); const orderedLogLevels = [ "debug", @@ -56,7 +57,7 @@ export class FirebaseMcpServer { // logging spec: // https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/logging - currentLogLevel?: LoggingLevel; + currentLogLevel?: LoggingLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined; // the api of logging from a consumers perspective looks like `server.logger.warn("my warning")`. public readonly logger = Object.fromEntries( orderedLogLevels.map((logLevel) => [ @@ -65,6 +66,20 @@ export class FirebaseMcpServer { ]), ) as Record Promise>; + /** Create a special tracking function to avoid blocking everything on initialization notification. */ + private async trackGA4( + event: Parameters[0], + params: Parameters[1] = {}, + ): Promise { + // wait until ready or until 2s has elapsed + if (!this.clientInfo) await timeoutFallback(this.ready(), null, 2000); + let clientInfoParams = { + mcp_client_name: this.clientInfo?.name || "", + mcp_client_version: this.clientInfo?.version || "", + }; + trackGA4(event, { ...params, ...clientInfoParams }); + } + constructor(options: { activeFeatures?: ServerFeature[]; projectRoot?: string }) { this.activeFeatures = options.activeFeatures; this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT; @@ -76,10 +91,7 @@ export class FirebaseMcpServer { const clientInfo = this.server.getClientVersion(); this.clientInfo = clientInfo; if (clientInfo?.name) { - trackGA4("mcp_client_connected", { - mcp_client_name: clientInfo.name, - mcp_client_version: clientInfo.version, - }); + this.trackGA4("mcp_client_connected"); } if (!this.clientInfo?.name) this.clientInfo = { name: "" }; @@ -106,8 +118,14 @@ export class FirebaseMcpServer { }); } + get clientName(): string { + return this.clientInfo?.name ?? process.env.MONOSPACE_ENV + ? "Firebase Studio" + : ""; + } + private get clientConfigKey() { - return `mcp.clientConfigs.${this.clientInfo?.name || ""}:${this.startupRoot || process.cwd()}`; + return `mcp.clientConfigs.${this.clientName}:${this.startupRoot || process.cwd()}`; } getStoredClientConfig(): ClientConfig { @@ -122,15 +140,17 @@ export class FirebaseMcpServer { } async detectProjectRoot(): Promise { - await this.ready(); + await timeoutFallback(this.ready(), null, 2000); if (this.cachedProjectRoot) return this.cachedProjectRoot; const storedRoot = this.getStoredClientConfig().projectRoot; this.cachedProjectRoot = storedRoot || this.startupRoot || process.cwd(); + this.log("debug", "detected and cached project root: " + this.cachedProjectRoot); return this.cachedProjectRoot; } async detectActiveFeatures(): Promise { if (this.detectedFeatures?.length) return this.detectedFeatures; // memoized + this.log("debug", "detecting active features of Firebase MCP server..."); const options = await this.resolveOptions(); const projectId = await this.getProjectId(); const detected = await Promise.all( @@ -140,6 +160,10 @@ export class FirebaseMcpServer { }), ); this.detectedFeatures = detected.filter((f) => !!f) as ServerFeature[]; + this.log( + "debug", + "detected features of Firebase MCP server: " + this.detectedFeatures.join(", ") || "", + ); return this.detectedFeatures; } @@ -204,11 +228,14 @@ export class FirebaseMcpServer { return getProjectId(await this.resolveOptions()); } - async getAuthenticatedUser(): Promise { + async getAuthenticatedUser(skipAutoAuth: boolean = false): Promise { try { - const email = await requireAuth(await this.resolveOptions()); - return email ?? "Application Default Credentials"; + this.log("debug", `calling requireAuth`); + const email = await requireAuth(await this.resolveOptions(), skipAutoAuth); + this.log("debug", `result of requireAuth: ${email}`); + return email ?? skipAutoAuth ? null : "Application Default Credentials"; } catch (e) { + this.log("debug", `error in requireAuth: ${e}`); return null; } } @@ -216,16 +243,14 @@ export class FirebaseMcpServer { async mcpListTools(): Promise { await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]); const hasActiveProject = !!(await this.getProjectId()); - await trackGA4("mcp_list_tools", { - mcp_client_name: this.clientInfo?.name, - mcp_client_version: this.clientInfo?.version, - }); + await this.trackGA4("mcp_list_tools"); + const skipAutoAuthForStudio = !!process.env.MONOSPACE_ENV; return { tools: this.availableTools.map((t) => t.mcp), _meta: { projectRoot: this.cachedProjectRoot, projectDetected: hasActiveProject, - authenticatedUser: await this.getAuthenticatedUser(), + authenticatedUser: await this.getAuthenticatedUser(skipAutoAuthForStudio), activeFeatures: this.activeFeatures, detectedFeatures: this.detectedFeatures, }, @@ -283,26 +308,24 @@ export class FirebaseMcpServer { }; try { const res = await tool.fn(toolArgs, toolsCtx); - await trackGA4("mcp_tool_call", { + await this.trackGA4("mcp_tool_call", { tool_name: toolName, error: res.isError ? 1 : 0, - mcp_client_name: this.clientInfo?.name, - mcp_client_version: this.clientInfo?.version, }); return res; } catch (err: unknown) { - await trackGA4("mcp_tool_call", { + await this.trackGA4("mcp_tool_call", { tool_name: toolName, error: 1, - mcp_client_name: this.clientInfo?.name, - mcp_client_version: this.clientInfo?.version, }); return mcpError(err); } } async start(): Promise { - const transport = new StdioServerTransport(); + const transport = process.env.FIREBASE_MCP_DEBUG_LOG + ? new LoggingStdioServerTransport(process.env.FIREBASE_MCP_DEBUG_LOG) + : new StdioServerTransport(); await this.server.connect(transport); } @@ -323,6 +346,6 @@ export class FirebaseMcpServer { return; } - await this.server.sendLoggingMessage({ level, data }); + if (this._ready) await this.server.sendLoggingMessage({ level, data }); } } diff --git a/src/mcp/logging-transport.ts b/src/mcp/logging-transport.ts new file mode 100644 index 00000000000..eb59fb650cf --- /dev/null +++ b/src/mcp/logging-transport.ts @@ -0,0 +1,22 @@ +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; +import { appendFileSync } from "fs"; + +export class LoggingStdioServerTransport extends StdioServerTransport { + path: string; + + constructor(path: string) { + super(); + this.path = path; + const origOnData = this._ondata; + this._ondata = (chunk: Buffer) => { + origOnData(chunk); + appendFileSync(path, chunk.toString(), { encoding: "utf8" }); + }; + } + + async send(message: JSONRPCMessage) { + await super.send(message); + appendFileSync(this.path, JSON.stringify(message) + "\n"); + } +} diff --git a/src/mcp/util.ts b/src/mcp/util.ts index 256e139a91d..4942804d9f1 100644 --- a/src/mcp/util.ts +++ b/src/mcp/util.ts @@ -104,7 +104,12 @@ export async function checkFeatureActive( if (feature in (options?.config?.data || {})) return true; // if the feature's api is active in the project, it's active try { - if (projectId) return await check(projectId, SERVER_FEATURE_APIS[feature], "", true); + if (projectId) + return await timeoutFallback( + check(projectId, SERVER_FEATURE_APIS[feature], "", true), + true, + 3000, + ); } catch (e) { // if we don't have network or something, better to default to on return true; @@ -112,6 +117,17 @@ export async function checkFeatureActive( return false; } +export async function timeoutFallback( + promise: Promise, + value: V, + timeoutMillis = 2000, +): Promise { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(value), timeoutMillis)), + ]); +} + // Helper function to process a single schema node (could be a property schema, items schema, etc.) // Returns the cleaned schema, or null if the schema becomes invalid and should be removed according to the rules. // The isRoot parameter is true only for the top-level schema object. diff --git a/src/requireAuth.ts b/src/requireAuth.ts index a4f7795ba88..c08da32a2a7 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -82,7 +82,10 @@ export async function refreshAuth(): Promise { * if the user is not authenticated * @param options CLI options. */ -export async function requireAuth(options: any): Promise { +export async function requireAuth( + options: any, + skipAutoAuth: boolean = false, +): Promise { lastOptions = options; api.setScopes([scopes.CLOUD_PLATFORM, scopes.FIREBASE_PLATFORM]); options.authScopes = api.getScopes(); @@ -104,6 +107,8 @@ export async function requireAuth(options: any): Promise { ); } else if (user && (!isExpired(tokens) || tokens?.refresh_token)) { logger.debug(`> authorizing via signed-in user (${user.email})`); + } else if (skipAutoAuth) { + return null; } else { try { return await autoAuth(options, options.authScopes); From 0a8f315147112ffcf248fec936b0cab12ee0ceae Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 18 Jul 2025 17:08:48 -0700 Subject: [PATCH 02/11] more timeouts and studio env handling --- firebase-vscode/src/core/env.ts | 7 +++---- src/env.ts | 12 ++++++++++++ src/mcp/index.ts | 14 +++++++------- src/mcp/logging-transport.ts | 1 + src/mcp/util.ts | 11 ----------- src/requireAuth.ts | 13 +++++++++++-- src/timeout.ts | 27 +++++++++++++++++++++++++++ src/track.ts | 3 ++- 8 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 src/env.ts create mode 100644 src/timeout.ts diff --git a/firebase-vscode/src/core/env.ts b/firebase-vscode/src/core/env.ts index 2d9701be7d2..ac5dd600213 100644 --- a/firebase-vscode/src/core/env.ts +++ b/firebase-vscode/src/core/env.ts @@ -2,20 +2,19 @@ import { Disposable } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { pluginLogger } from "../logger-wrapper"; import { globalSignal } from "../utils/globals"; +import { isFirebaseStudio } from "../env"; interface Environment { isMonospace: boolean; } export const env = globalSignal({ - isMonospace: Boolean(process.env.MONOSPACE_ENV), + isMonospace: isFirebaseStudio(), }); export function registerEnv(broker: ExtensionBrokerImpl): Disposable { const sub = broker.on("getInitialData", async () => { - pluginLogger.debug( - `Value of process.env.MONOSPACE_ENV: ` + `${process.env.MONOSPACE_ENV}` - ); + pluginLogger.debug(`Value of isFirebaseStudio: ` + `${isFirebaseStudio()}`); broker.send("notifyEnv", { env: env.peek(), diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 00000000000..7a99a09876d --- /dev/null +++ b/src/env.ts @@ -0,0 +1,12 @@ +import { dirExistsSync } from "./fsutils"; + +let googleIdxFolderExists: boolean | undefined; +export function isFirebaseStudio() { + if (googleIdxFolderExists === true || process.env.MONOSPACE_ENV) return true; + googleIdxFolderExists = dirExistsSync("/google/idx"); + return googleIdxFolderExists; +} + +export function isFirebaseMcp() { + return !!process.env.IS_FIREBASE_MCP; +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts index b46fb7f6a37..198a63d4146 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -28,8 +28,9 @@ import { existsSync } from "node:fs"; import { ensure, check } from "../ensureApiEnabled.js"; import * as api from "../api.js"; import { LoggingStdioServerTransport } from "./logging-transport.js"; +import { isFirebaseStudio } from "../env.js"; -const SERVER_VERSION = "0.1.0"; +const SERVER_VERSION = "0.2.0"; const cmd = new Command("experimental:mcp"); @@ -73,7 +74,7 @@ export class FirebaseMcpServer { ): Promise { // wait until ready or until 2s has elapsed if (!this.clientInfo) await timeoutFallback(this.ready(), null, 2000); - let clientInfoParams = { + const clientInfoParams = { mcp_client_name: this.clientInfo?.name || "", mcp_client_version: this.clientInfo?.version || "", }; @@ -119,9 +120,7 @@ export class FirebaseMcpServer { } get clientName(): string { - return this.clientInfo?.name ?? process.env.MONOSPACE_ENV - ? "Firebase Studio" - : ""; + return this.clientInfo?.name ?? isFirebaseStudio() ? "Firebase Studio" : ""; } private get clientConfigKey() { @@ -232,7 +231,7 @@ export class FirebaseMcpServer { try { this.log("debug", `calling requireAuth`); const email = await requireAuth(await this.resolveOptions(), skipAutoAuth); - this.log("debug", `result of requireAuth: ${email}`); + this.log("debug", `detected authenticated account: ${email || ""}`); return email ?? skipAutoAuth ? null : "Application Default Credentials"; } catch (e) { this.log("debug", `error in requireAuth: ${e}`); @@ -244,7 +243,8 @@ export class FirebaseMcpServer { await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]); const hasActiveProject = !!(await this.getProjectId()); await this.trackGA4("mcp_list_tools"); - const skipAutoAuthForStudio = !!process.env.MONOSPACE_ENV; + const skipAutoAuthForStudio = isFirebaseStudio(); + this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`); return { tools: this.availableTools.map((t) => t.mcp), _meta: { diff --git a/src/mcp/logging-transport.ts b/src/mcp/logging-transport.ts index eb59fb650cf..033ebe92f4d 100644 --- a/src/mcp/logging-transport.ts +++ b/src/mcp/logging-transport.ts @@ -8,6 +8,7 @@ export class LoggingStdioServerTransport extends StdioServerTransport { constructor(path: string) { super(); this.path = path; + appendFileSync(path, "--- new process start ---\n"); const origOnData = this._ondata; this._ondata = (chunk: Buffer) => { origOnData(chunk); diff --git a/src/mcp/util.ts b/src/mcp/util.ts index 4942804d9f1..31b3ecbd5eb 100644 --- a/src/mcp/util.ts +++ b/src/mcp/util.ts @@ -117,17 +117,6 @@ export async function checkFeatureActive( return false; } -export async function timeoutFallback( - promise: Promise, - value: V, - timeoutMillis = 2000, -): Promise { - return Promise.race([ - promise, - new Promise((resolve) => setTimeout(() => resolve(value), timeoutMillis)), - ]); -} - // Helper function to process a single schema node (could be a property schema, items schema, etc.) // Returns the cleaned schema, or null if the schema becomes invalid and should be removed according to the rules. // The isRoot parameter is true only for the top-level schema object. diff --git a/src/requireAuth.ts b/src/requireAuth.ts index c08da32a2a7..bb4effe96f9 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -10,6 +10,9 @@ import * as scopes from "./scopes"; import { Tokens, TokensWithExpiration, User } from "./types/auth"; import { setRefreshToken, setActiveAccount, setGlobalDefaultAccount, isExpired } from "./auth"; import type { Options } from "./options"; +import { isFirebaseMcp, isFirebaseStudio } from "./env"; +import { timeoutError } from "./timeout"; +import { timeouts } from "retry"; const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold( "firebase login", @@ -44,13 +47,19 @@ async function autoAuth(options: Options, authScopes: string[]): Promise( + promise: Promise, + value: V, + timeoutMillis = 2000, +): Promise { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(value), timeoutMillis)), + ]); +} + +export async function timeoutError( + promise: Promise, + error?: string | Error, + timeoutMillis = 5000, +): Promise { + if (typeof error === "string") error = new Error(error); + return Promise.race([ + promise, + new Promise((resolve, reject) => { + setTimeout(() => reject(error || new Error("Operation timed out.")), timeoutMillis); + }), + ]); +} diff --git a/src/track.ts b/src/track.ts index 7b58973e23e..55c7dbbb498 100644 --- a/src/track.ts +++ b/src/track.ts @@ -4,6 +4,7 @@ import { getGlobalDefaultAccount } from "./auth"; import { configstore } from "./configstore"; import { logger } from "./logger"; +import { isFirebaseStudio } from "./env"; const pkg = require("../package.json"); type cliEventNames = @@ -78,7 +79,7 @@ const GA4_USER_PROPS = { value: process.env.FIREPIT_VERSION || "none", }, is_firebase_studio: { - value: process.env.MONOSPACE_ENV ?? "false", + value: isFirebaseStudio().toString(), }, }; From c8e607d6e3886b187923e7783908ebc481f1f5ea Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 18 Jul 2025 17:17:48 -0700 Subject: [PATCH 03/11] small fixes --- src/mcp/index.ts | 3 ++- src/mcp/util.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 198a63d4146..c2f77aaf0fc 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -9,7 +9,7 @@ import { ListToolsRequestSchema, CallToolResult, } from "@modelcontextprotocol/sdk/types.js"; -import { checkFeatureActive, mcpError, timeoutFallback } from "./util.js"; +import { checkFeatureActive, mcpError } from "./util.js"; import { ClientConfig, SERVER_FEATURES, ServerFeature } from "./types.js"; import { availableTools } from "./tools/index.js"; import { ServerTool, ServerToolContext } from "./tool.js"; @@ -29,6 +29,7 @@ import { ensure, check } from "../ensureApiEnabled.js"; import * as api from "../api.js"; import { LoggingStdioServerTransport } from "./logging-transport.js"; import { isFirebaseStudio } from "../env.js"; +import { timeoutFallback } from "../timeout.js"; const SERVER_VERSION = "0.2.0"; diff --git a/src/mcp/util.ts b/src/mcp/util.ts index 31b3ecbd5eb..0f44cf9cddd 100644 --- a/src/mcp/util.ts +++ b/src/mcp/util.ts @@ -14,6 +14,7 @@ import { crashlyticsApiOrigin, } from "../api"; import { check } from "../ensureApiEnabled"; +import { timeoutFallback } from "../timeout"; /** * Converts data to a CallToolResult. From e2458f3117cec8b8d35d87e92b652b523de8698b Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 12:19:12 -0700 Subject: [PATCH 04/11] exclude create_project from firebase studio --- src/mcp/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mcp/index.ts b/src/mcp/index.ts index c2f77aaf0fc..bb5b2caf158 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -240,6 +240,13 @@ export class FirebaseMcpServer { } } + async filteredAvailableTools(): Promise { + const excludedNames: string[] = []; + // exclude create_project from firebase studio since there's a better way there + if (isFirebaseStudio()) excludedNames.push("firebase_create_project"); + return this.availableTools.filter((t) => !excludedNames.includes(t.mcp.name)); + } + async mcpListTools(): Promise { await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]); const hasActiveProject = !!(await this.getProjectId()); From 13a28507b828a4d61c35272b94444757c38d39f2 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 12:26:35 -0700 Subject: [PATCH 05/11] Update src/requireAuth.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/requireAuth.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/requireAuth.ts b/src/requireAuth.ts index bb4effe96f9..b22401c5ce3 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -47,13 +47,14 @@ async function autoAuth(options: Options, authScopes: string[]): Promise Date: Mon, 21 Jul 2025 12:29:57 -0700 Subject: [PATCH 06/11] touche, bot --- src/env.ts | 1 + src/mcp/index.ts | 4 ++-- src/mcp/logging-transport.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/env.ts b/src/env.ts index 7a99a09876d..637dfb7084d 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,6 +3,7 @@ import { dirExistsSync } from "./fsutils"; let googleIdxFolderExists: boolean | undefined; export function isFirebaseStudio() { if (googleIdxFolderExists === true || process.env.MONOSPACE_ENV) return true; + if (googleIdxFolderExists === false) return false; googleIdxFolderExists = dirExistsSync("/google/idx"); return googleIdxFolderExists; } diff --git a/src/mcp/index.ts b/src/mcp/index.ts index bb5b2caf158..4202c7c05b2 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -121,7 +121,7 @@ export class FirebaseMcpServer { } get clientName(): string { - return this.clientInfo?.name ?? isFirebaseStudio() ? "Firebase Studio" : ""; + return this.clientInfo?.name ?? (isFirebaseStudio() ? "Firebase Studio" : ""); } private get clientConfigKey() { @@ -162,7 +162,7 @@ export class FirebaseMcpServer { this.detectedFeatures = detected.filter((f) => !!f) as ServerFeature[]; this.log( "debug", - "detected features of Firebase MCP server: " + this.detectedFeatures.join(", ") || "", + "detected features of Firebase MCP server: " + (this.detectedFeatures.join(", ") || ""), ); return this.detectedFeatures; } diff --git a/src/mcp/logging-transport.ts b/src/mcp/logging-transport.ts index 033ebe92f4d..749a34a1a80 100644 --- a/src/mcp/logging-transport.ts +++ b/src/mcp/logging-transport.ts @@ -1,6 +1,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; import { appendFileSync } from "fs"; +import { appendFile } from "fs/promises"; export class LoggingStdioServerTransport extends StdioServerTransport { path: string; @@ -18,6 +19,6 @@ export class LoggingStdioServerTransport extends StdioServerTransport { async send(message: JSONRPCMessage) { await super.send(message); - appendFileSync(this.path, JSON.stringify(message) + "\n"); + await appendFile(this.path, JSON.stringify(message) + "\n"); } } From cac5a1eb752c1e4e062379537909294c1d604d2d Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 12:52:33 -0700 Subject: [PATCH 07/11] lint --- src/requireAuth.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/requireAuth.ts b/src/requireAuth.ts index b22401c5ce3..e212f6c2aab 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -12,7 +12,6 @@ import { setRefreshToken, setActiveAccount, setGlobalDefaultAccount, isExpired } import type { Options } from "./options"; import { isFirebaseMcp, isFirebaseStudio } from "./env"; import { timeoutError } from "./timeout"; -import { timeouts } from "retry"; const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold( "firebase login", @@ -47,14 +46,14 @@ async function autoAuth(options: Options, authScopes: string[]): Promise Date: Mon, 21 Jul 2025 13:12:57 -0700 Subject: [PATCH 08/11] vscode fix --- firebase-vscode/src/core/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-vscode/src/core/env.ts b/firebase-vscode/src/core/env.ts index ac5dd600213..90e9244d032 100644 --- a/firebase-vscode/src/core/env.ts +++ b/firebase-vscode/src/core/env.ts @@ -2,7 +2,7 @@ import { Disposable } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { pluginLogger } from "../logger-wrapper"; import { globalSignal } from "../utils/globals"; -import { isFirebaseStudio } from "../env"; +import { isFirebaseStudio } from "../../src/env"; interface Environment { isMonospace: boolean; From ba4a63e013450eba0d77867167583a0580687c71 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 13:20:10 -0700 Subject: [PATCH 09/11] revert vscode since it isnt picking up --- firebase-vscode/src/core/env.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-vscode/src/core/env.ts b/firebase-vscode/src/core/env.ts index 90e9244d032..c0e8fc5af15 100644 --- a/firebase-vscode/src/core/env.ts +++ b/firebase-vscode/src/core/env.ts @@ -2,14 +2,13 @@ import { Disposable } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { pluginLogger } from "../logger-wrapper"; import { globalSignal } from "../utils/globals"; -import { isFirebaseStudio } from "../../src/env"; interface Environment { isMonospace: boolean; } export const env = globalSignal({ - isMonospace: isFirebaseStudio(), + isMonospace: !!process.env.MONOSPACE_ENV, }); export function registerEnv(broker: ExtensionBrokerImpl): Disposable { From 758b7d0098db864a97c1619b4fb116c8e128d48e Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 13:22:48 -0700 Subject: [PATCH 10/11] fix --- firebase-vscode/src/core/env.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/firebase-vscode/src/core/env.ts b/firebase-vscode/src/core/env.ts index c0e8fc5af15..f8489d6be7a 100644 --- a/firebase-vscode/src/core/env.ts +++ b/firebase-vscode/src/core/env.ts @@ -8,12 +8,14 @@ interface Environment { } export const env = globalSignal({ - isMonospace: !!process.env.MONOSPACE_ENV, + isMonospace: Boolean(process.env.MONOSPACE_ENV), }); export function registerEnv(broker: ExtensionBrokerImpl): Disposable { const sub = broker.on("getInitialData", async () => { - pluginLogger.debug(`Value of isFirebaseStudio: ` + `${isFirebaseStudio()}`); + pluginLogger.debug( + `Value of process.env.MONOSPACE_ENV: ` + `${process.env.MONOSPACE_ENV}`, + ); broker.send("notifyEnv", { env: env.peek(), From 196bacaa91c83137be9064f34d02d65db12e12a4 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 21 Jul 2025 14:32:21 -0700 Subject: [PATCH 11/11] remove filtering --- src/mcp/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 4202c7c05b2..bfc9e09beba 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -240,13 +240,6 @@ export class FirebaseMcpServer { } } - async filteredAvailableTools(): Promise { - const excludedNames: string[] = []; - // exclude create_project from firebase studio since there's a better way there - if (isFirebaseStudio()) excludedNames.push("firebase_create_project"); - return this.availableTools.filter((t) => !excludedNames.includes(t.mcp.name)); - } - async mcpListTools(): Promise { await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]); const hasActiveProject = !!(await this.getProjectId());