@@ -3,9 +3,11 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
3
import {
4
4
CallToolRequest ,
5
5
CallToolRequestSchema ,
6
- CallToolResult ,
7
- ListToolsRequestSchema ,
8
6
ListToolsResult ,
7
+ LoggingLevel ,
8
+ SetLevelRequestSchema ,
9
+ ListToolsRequestSchema ,
10
+ CallToolResult ,
9
11
} from "@modelcontextprotocol/sdk/types.js" ;
10
12
import { checkFeatureActive , mcpError } from "./util.js" ;
11
13
import { ClientConfig , SERVER_FEATURES , ServerFeature } from "./types.js" ;
@@ -30,6 +32,17 @@ const SERVER_VERSION = "0.1.0";
30
32
31
33
const cmd = new Command ( "experimental:mcp" ) . before ( requireAuth ) ;
32
34
35
+ const orderedLogLevels = [
36
+ "debug" ,
37
+ "info" ,
38
+ "notice" ,
39
+ "warning" ,
40
+ "error" ,
41
+ "critical" ,
42
+ "alert" ,
43
+ "emergency" ,
44
+ ] as const ;
45
+
33
46
export class FirebaseMcpServer {
34
47
private _ready : boolean = false ;
35
48
private _readyPromises : { resolve : ( ) => void ; reject : ( err : unknown ) => void } [ ] = [ ] ;
@@ -41,11 +54,22 @@ export class FirebaseMcpServer {
41
54
clientInfo ?: { name ?: string ; version ?: string } ;
42
55
emulatorHubClient ?: EmulatorHubClient ;
43
56
57
+ // logging spec:
58
+ // https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/logging
59
+ currentLogLevel ?: LoggingLevel ;
60
+ // the api of logging from a consumers perspective looks like `server.logger.warn("my warning")`.
61
+ public readonly logger = Object . fromEntries (
62
+ orderedLogLevels . map ( ( logLevel ) => [
63
+ logLevel ,
64
+ ( message : unknown ) => this . log ( logLevel , message ) ,
65
+ ] ) ,
66
+ ) as Record < LoggingLevel , ( message : unknown ) => Promise < void > > ;
67
+
44
68
constructor ( options : { activeFeatures ?: ServerFeature [ ] ; projectRoot ?: string } ) {
45
69
this . activeFeatures = options . activeFeatures ;
46
70
this . startupRoot = options . projectRoot || process . env . PROJECT_ROOT ;
47
71
this . server = new Server ( { name : "firebase" , version : SERVER_VERSION } ) ;
48
- this . server . registerCapabilities ( { tools : { listChanged : true } } ) ;
72
+ this . server . registerCapabilities ( { tools : { listChanged : true } , logging : { } } ) ;
49
73
this . server . setRequestHandler ( ListToolsRequestSchema , this . mcpListTools . bind ( this ) ) ;
50
74
this . server . setRequestHandler ( CallToolRequestSchema , this . mcpCallTool . bind ( this ) ) ;
51
75
this . server . oninitialized = async ( ) => {
@@ -64,6 +88,12 @@ export class FirebaseMcpServer {
64
88
this . _readyPromises . pop ( ) ?. resolve ( ) ;
65
89
}
66
90
} ;
91
+
92
+ this . server . setRequestHandler ( SetLevelRequestSchema , async ( { params } ) => {
93
+ this . currentLogLevel = params . level ;
94
+ return { } ;
95
+ } ) ;
96
+
67
97
this . detectProjectRoot ( ) ;
68
98
this . detectActiveFeatures ( ) ;
69
99
}
@@ -275,4 +305,24 @@ export class FirebaseMcpServer {
275
305
const transport = new StdioServerTransport ( ) ;
276
306
await this . server . connect ( transport ) ;
277
307
}
308
+
309
+ private async log ( level : LoggingLevel , message : unknown ) {
310
+ let data = message ;
311
+
312
+ // mcp protocol only takes jsons or it errors; for convienence, format
313
+ // a a string into a json.
314
+ if ( typeof message === "string" ) {
315
+ data = { message } ;
316
+ }
317
+
318
+ if ( ! this . currentLogLevel ) {
319
+ return ;
320
+ }
321
+
322
+ if ( orderedLogLevels . indexOf ( this . currentLogLevel ) < orderedLogLevels . indexOf ( level ) ) {
323
+ return ;
324
+ }
325
+
326
+ await this . server . sendLoggingMessage ( { level, data } ) ;
327
+ }
278
328
}
0 commit comments