Skip to content

Commit da6c911

Browse files
author
oleina
committed
implement mcp logging
1 parent 7d7ee95 commit da6c911

File tree

1 file changed

+53
-3
lines changed

1 file changed

+53
-3
lines changed

src/mcp/index.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
33
import {
44
CallToolRequest,
55
CallToolRequestSchema,
6-
CallToolResult,
7-
ListToolsRequestSchema,
86
ListToolsResult,
7+
LoggingLevel,
8+
SetLevelRequestSchema,
9+
ListToolsRequestSchema,
10+
CallToolResult,
911
} from "@modelcontextprotocol/sdk/types.js";
1012
import { checkFeatureActive, mcpError } from "./util.js";
1113
import { ClientConfig, SERVER_FEATURES, ServerFeature } from "./types.js";
@@ -30,6 +32,17 @@ const SERVER_VERSION = "0.1.0";
3032

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

35+
const orderedLogLevels = [
36+
"debug",
37+
"info",
38+
"notice",
39+
"warning",
40+
"error",
41+
"critical",
42+
"alert",
43+
"emergency",
44+
] as const;
45+
3346
export class FirebaseMcpServer {
3447
private _ready: boolean = false;
3548
private _readyPromises: { resolve: () => void; reject: (err: unknown) => void }[] = [];
@@ -41,11 +54,22 @@ export class FirebaseMcpServer {
4154
clientInfo?: { name?: string; version?: string };
4255
emulatorHubClient?: EmulatorHubClient;
4356

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+
4468
constructor(options: { activeFeatures?: ServerFeature[]; projectRoot?: string }) {
4569
this.activeFeatures = options.activeFeatures;
4670
this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
4771
this.server = new Server({ name: "firebase", version: SERVER_VERSION });
48-
this.server.registerCapabilities({ tools: { listChanged: true } });
72+
this.server.registerCapabilities({ tools: { listChanged: true }, logging: {} });
4973
this.server.setRequestHandler(ListToolsRequestSchema, this.mcpListTools.bind(this));
5074
this.server.setRequestHandler(CallToolRequestSchema, this.mcpCallTool.bind(this));
5175
this.server.oninitialized = async () => {
@@ -64,6 +88,12 @@ export class FirebaseMcpServer {
6488
this._readyPromises.pop()?.resolve();
6589
}
6690
};
91+
92+
this.server.setRequestHandler(SetLevelRequestSchema, async ({ params }) => {
93+
this.currentLogLevel = params.level;
94+
return {};
95+
});
96+
6797
this.detectProjectRoot();
6898
this.detectActiveFeatures();
6999
}
@@ -275,4 +305,24 @@ export class FirebaseMcpServer {
275305
const transport = new StdioServerTransport();
276306
await this.server.connect(transport);
277307
}
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+
}
278328
}

0 commit comments

Comments
 (0)