@@ -27,10 +27,13 @@ import { Emulators } from "../emulator/types.js";
27
27
import { existsSync } from "node:fs" ;
28
28
import { ensure , check } from "../ensureApiEnabled.js" ;
29
29
import * as api from "../api.js" ;
30
+ import { LoggingStdioServerTransport } from "./logging-transport.js" ;
31
+ import { isFirebaseStudio } from "../env.js" ;
32
+ import { timeoutFallback } from "../timeout.js" ;
30
33
31
- const SERVER_VERSION = "0.1 .0" ;
34
+ const SERVER_VERSION = "0.2 .0" ;
32
35
33
- const cmd = new Command ( "experimental:mcp" ) . before ( requireAuth ) ;
36
+ const cmd = new Command ( "experimental:mcp" ) ;
34
37
35
38
const orderedLogLevels = [
36
39
"debug" ,
@@ -56,7 +59,7 @@ export class FirebaseMcpServer {
56
59
57
60
// logging spec:
58
61
// https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/logging
59
- currentLogLevel ?: LoggingLevel ;
62
+ currentLogLevel ?: LoggingLevel = process . env . FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined ;
60
63
// the api of logging from a consumers perspective looks like `server.logger.warn("my warning")`.
61
64
public readonly logger = Object . fromEntries (
62
65
orderedLogLevels . map ( ( logLevel ) => [
@@ -65,6 +68,20 @@ export class FirebaseMcpServer {
65
68
] ) ,
66
69
) as Record < LoggingLevel , ( message : unknown ) => Promise < void > > ;
67
70
71
+ /** Create a special tracking function to avoid blocking everything on initialization notification. */
72
+ private async trackGA4 (
73
+ event : Parameters < typeof trackGA4 > [ 0 ] ,
74
+ params : Parameters < typeof trackGA4 > [ 1 ] = { } ,
75
+ ) : Promise < void > {
76
+ // wait until ready or until 2s has elapsed
77
+ if ( ! this . clientInfo ) await timeoutFallback ( this . ready ( ) , null , 2000 ) ;
78
+ const clientInfoParams = {
79
+ mcp_client_name : this . clientInfo ?. name || "<unknown-client>" ,
80
+ mcp_client_version : this . clientInfo ?. version || "<unknown-version>" ,
81
+ } ;
82
+ trackGA4 ( event , { ...params , ...clientInfoParams } ) ;
83
+ }
84
+
68
85
constructor ( options : { activeFeatures ?: ServerFeature [ ] ; projectRoot ?: string } ) {
69
86
this . activeFeatures = options . activeFeatures ;
70
87
this . startupRoot = options . projectRoot || process . env . PROJECT_ROOT ;
@@ -76,10 +93,7 @@ export class FirebaseMcpServer {
76
93
const clientInfo = this . server . getClientVersion ( ) ;
77
94
this . clientInfo = clientInfo ;
78
95
if ( clientInfo ?. name ) {
79
- trackGA4 ( "mcp_client_connected" , {
80
- mcp_client_name : clientInfo . name ,
81
- mcp_client_version : clientInfo . version ,
82
- } ) ;
96
+ this . trackGA4 ( "mcp_client_connected" ) ;
83
97
}
84
98
if ( ! this . clientInfo ?. name ) this . clientInfo = { name : "<unknown-client>" } ;
85
99
@@ -106,8 +120,12 @@ export class FirebaseMcpServer {
106
120
} ) ;
107
121
}
108
122
123
+ get clientName ( ) : string {
124
+ return this . clientInfo ?. name ?? ( isFirebaseStudio ( ) ? "Firebase Studio" : "<unknown-client>" ) ;
125
+ }
126
+
109
127
private get clientConfigKey ( ) {
110
- return `mcp.clientConfigs.${ this . clientInfo ?. name || "<unknown-client>" } :${ this . startupRoot || process . cwd ( ) } ` ;
128
+ return `mcp.clientConfigs.${ this . clientName } :${ this . startupRoot || process . cwd ( ) } ` ;
111
129
}
112
130
113
131
getStoredClientConfig ( ) : ClientConfig {
@@ -122,15 +140,17 @@ export class FirebaseMcpServer {
122
140
}
123
141
124
142
async detectProjectRoot ( ) : Promise < string > {
125
- await this . ready ( ) ;
143
+ await timeoutFallback ( this . ready ( ) , null , 2000 ) ;
126
144
if ( this . cachedProjectRoot ) return this . cachedProjectRoot ;
127
145
const storedRoot = this . getStoredClientConfig ( ) . projectRoot ;
128
146
this . cachedProjectRoot = storedRoot || this . startupRoot || process . cwd ( ) ;
147
+ this . log ( "debug" , "detected and cached project root: " + this . cachedProjectRoot ) ;
129
148
return this . cachedProjectRoot ;
130
149
}
131
150
132
151
async detectActiveFeatures ( ) : Promise < ServerFeature [ ] > {
133
152
if ( this . detectedFeatures ?. length ) return this . detectedFeatures ; // memoized
153
+ this . log ( "debug" , "detecting active features of Firebase MCP server..." ) ;
134
154
const options = await this . resolveOptions ( ) ;
135
155
const projectId = await this . getProjectId ( ) ;
136
156
const detected = await Promise . all (
@@ -140,6 +160,10 @@ export class FirebaseMcpServer {
140
160
} ) ,
141
161
) ;
142
162
this . detectedFeatures = detected . filter ( ( f ) => ! ! f ) as ServerFeature [ ] ;
163
+ this . log (
164
+ "debug" ,
165
+ "detected features of Firebase MCP server: " + ( this . detectedFeatures . join ( ", " ) || "<none>" ) ,
166
+ ) ;
143
167
return this . detectedFeatures ;
144
168
}
145
169
@@ -204,28 +228,30 @@ export class FirebaseMcpServer {
204
228
return getProjectId ( await this . resolveOptions ( ) ) ;
205
229
}
206
230
207
- async getAuthenticatedUser ( ) : Promise < string | null > {
231
+ async getAuthenticatedUser ( skipAutoAuth : boolean = false ) : Promise < string | null > {
208
232
try {
209
- const email = await requireAuth ( await this . resolveOptions ( ) ) ;
210
- return email ?? "Application Default Credentials" ;
233
+ this . log ( "debug" , `calling requireAuth` ) ;
234
+ const email = await requireAuth ( await this . resolveOptions ( ) , skipAutoAuth ) ;
235
+ this . log ( "debug" , `detected authenticated account: ${ email || "<none>" } ` ) ;
236
+ return email ?? skipAutoAuth ? null : "Application Default Credentials" ;
211
237
} catch ( e ) {
238
+ this . log ( "debug" , `error in requireAuth: ${ e } ` ) ;
212
239
return null ;
213
240
}
214
241
}
215
242
216
243
async mcpListTools ( ) : Promise < ListToolsResult > {
217
244
await Promise . all ( [ this . detectActiveFeatures ( ) , this . detectProjectRoot ( ) ] ) ;
218
245
const hasActiveProject = ! ! ( await this . getProjectId ( ) ) ;
219
- await trackGA4 ( "mcp_list_tools" , {
220
- mcp_client_name : this . clientInfo ?. name ,
221
- mcp_client_version : this . clientInfo ?. version ,
222
- } ) ;
246
+ await this . trackGA4 ( "mcp_list_tools" ) ;
247
+ const skipAutoAuthForStudio = isFirebaseStudio ( ) ;
248
+ this . log ( "debug" , `skip auto-auth in studio environment: ${ skipAutoAuthForStudio } ` ) ;
223
249
return {
224
250
tools : this . availableTools . map ( ( t ) => t . mcp ) ,
225
251
_meta : {
226
252
projectRoot : this . cachedProjectRoot ,
227
253
projectDetected : hasActiveProject ,
228
- authenticatedUser : await this . getAuthenticatedUser ( ) ,
254
+ authenticatedUser : await this . getAuthenticatedUser ( skipAutoAuthForStudio ) ,
229
255
activeFeatures : this . activeFeatures ,
230
256
detectedFeatures : this . detectedFeatures ,
231
257
} ,
@@ -283,26 +309,24 @@ export class FirebaseMcpServer {
283
309
} ;
284
310
try {
285
311
const res = await tool . fn ( toolArgs , toolsCtx ) ;
286
- await trackGA4 ( "mcp_tool_call" , {
312
+ await this . trackGA4 ( "mcp_tool_call" , {
287
313
tool_name : toolName ,
288
314
error : res . isError ? 1 : 0 ,
289
- mcp_client_name : this . clientInfo ?. name ,
290
- mcp_client_version : this . clientInfo ?. version ,
291
315
} ) ;
292
316
return res ;
293
317
} catch ( err : unknown ) {
294
- await trackGA4 ( "mcp_tool_call" , {
318
+ await this . trackGA4 ( "mcp_tool_call" , {
295
319
tool_name : toolName ,
296
320
error : 1 ,
297
- mcp_client_name : this . clientInfo ?. name ,
298
- mcp_client_version : this . clientInfo ?. version ,
299
321
} ) ;
300
322
return mcpError ( err ) ;
301
323
}
302
324
}
303
325
304
326
async start ( ) : Promise < void > {
305
- const transport = new StdioServerTransport ( ) ;
327
+ const transport = process . env . FIREBASE_MCP_DEBUG_LOG
328
+ ? new LoggingStdioServerTransport ( process . env . FIREBASE_MCP_DEBUG_LOG )
329
+ : new StdioServerTransport ( ) ;
306
330
await this . server . connect ( transport ) ;
307
331
}
308
332
@@ -323,6 +347,6 @@ export class FirebaseMcpServer {
323
347
return ;
324
348
}
325
349
326
- await this . server . sendLoggingMessage ( { level, data } ) ;
350
+ if ( this . _ready ) await this . server . sendLoggingMessage ( { level, data } ) ;
327
351
}
328
352
}
0 commit comments