Skip to content

Commit 8f3e203

Browse files
committed
This commit ties logging level to session
* In src/server/index.ts - in Server class constructor when setting request handler for SetLevelRequestSchema - take the sessionId OR 'mcp-session-id' header and store the session id in the _loggingLevels map by session id. Doesn't store the level if there is no session id - change _logLevel variable to a Map, string to LoggingLevel type - rename _msgLevels array to _levelNames and just make it strings for simplicity - in isMessageIgnored function - take a sessionId argument - change _msgLevels to _levelNames - match against the level stored in the _loggingLevels map by session id - in sendLoggingLevelMessage, - add optional sessionId argument - make all action conditional upon _capabilities.logging existing - if there is no session id OR if the message is not ignored based on the level and session id, send the notification * In src/server/mcp.ts - in sendLoggingMessage - add optional sessionId argument - pass sessionId to server.sendLoggingMessage * In src/examples/server - jsonResponseStreamableHttp.ts - simpleSseServer.ts - simpleStatelessStreamableHttp.ts - simpleStreamableHttp.ts - sseAndStreamableHttpCompatibleServer.ts - update tool callbacks to get the extra param - update erver.sendLoggingMessage calls to include extra.sessionId
1 parent a0e03b2 commit 8f3e203

File tree

7 files changed

+61
-39
lines changed

7 files changed

+61
-39
lines changed

src/examples/server/jsonResponseStreamableHttp.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,27 @@ const getServer = () => {
4444
{
4545
name: z.string().describe('Name to greet'),
4646
},
47-
async ({ name }): Promise<CallToolResult> => {
47+
async ({ name }, extra): Promise<CallToolResult> => {
4848
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
4949

5050
await server.sendLoggingMessage({
5151
level: "debug",
5252
data: `Starting multi-greet for ${name}`
53-
});
53+
}, extra.sessionId);
5454

5555
await sleep(1000); // Wait 1 second before first greeting
5656

5757
await server.sendLoggingMessage({
5858
level: "info",
5959
data: `Sending first greeting to ${name}`
60-
});
60+
}, extra.sessionId);
6161

6262
await sleep(1000); // Wait another second before second greeting
6363

6464
await server.sendLoggingMessage({
6565
level: "info",
6666
data: `Sending second greeting to ${name}`
67-
});
67+
}, extra.sessionId);
6868

6969
return {
7070
content: [

src/examples/server/simpleSseServer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ const getServer = () => {
2828
interval: z.number().describe('Interval in milliseconds between notifications').default(1000),
2929
count: z.number().describe('Number of notifications to send').default(10),
3030
},
31-
async ({ interval, count }): Promise<CallToolResult> => {
31+
async ({ interval, count }, extra): Promise<CallToolResult> => {
3232
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
3333
let counter = 0;
3434

3535
// Send the initial notification
3636
await server.sendLoggingMessage({
3737
level: "info",
3838
data: `Starting notification stream with ${count} messages every ${interval}ms`
39-
});
39+
}, extra.sessionId);
4040

4141
// Send periodic notifications
4242
while (counter < count) {
@@ -47,7 +47,7 @@ const getServer = () => {
4747
await server.sendLoggingMessage({
4848
level: "info",
4949
data: `Notification #${counter} at ${new Date().toISOString()}`
50-
});
50+
}, extra.sessionId);
5151
}
5252
catch (error) {
5353
console.error("Error sending notification:", error);

src/examples/server/simpleStatelessStreamableHttp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const getServer = () => {
4242
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
4343
count: z.number().describe('Number of notifications to send (0 for 100)').default(10),
4444
},
45-
async ({ interval, count }): Promise<CallToolResult> => {
45+
async ({ interval, count }, extra): Promise<CallToolResult> => {
4646
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
4747
let counter = 0;
4848

@@ -52,7 +52,7 @@ const getServer = () => {
5252
await server.sendLoggingMessage({
5353
level: "info",
5454
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
55-
});
55+
}, extra.sessionId);
5656
}
5757
catch (error) {
5858
console.error("Error sending notification:", error);

src/examples/server/simpleStreamableHttp.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,27 @@ const getServer = () => {
5858
readOnlyHint: true,
5959
openWorldHint: false
6060
},
61-
async ({ name }): Promise<CallToolResult> => {
61+
async ({ name }, extra): Promise<CallToolResult> => {
6262
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
6363

64-
await server.sendLoggingMessage({ level: "debug", data: `Starting multi-greet for ${name}`});
64+
await server.sendLoggingMessage({
65+
level: "debug",
66+
data: `Starting multi-greet for ${name}`
67+
}, extra.sessionId);
6568

6669
await sleep(1000); // Wait 1 second before first greeting
6770

68-
await server.sendLoggingMessage( { level: "info", data: `Sending first greeting to ${name}`});
71+
await server.sendLoggingMessage({
72+
level: "info",
73+
data: `Sending first greeting to ${name}`
74+
}, extra.sessionId);
6975

7076
await sleep(1000); // Wait another second before second greeting
7177

72-
await server.sendLoggingMessage( { level: "info", data: `Sending second greeting to ${name}`});
78+
await server.sendLoggingMessage({
79+
level: "info",
80+
data: `Sending second greeting to ${name}`
81+
}, extra.sessionId);
7382

7483
return {
7584
content: [
@@ -264,7 +273,7 @@ const getServer = () => {
264273
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
265274
count: z.number().describe('Number of notifications to send (0 for 100)').default(50),
266275
},
267-
async ({ interval, count }): Promise<CallToolResult> => {
276+
async ({ interval, count }, extra): Promise<CallToolResult> => {
268277
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
269278
let counter = 0;
270279

@@ -274,7 +283,7 @@ const getServer = () => {
274283
await server.sendLoggingMessage( {
275284
level: "info",
276285
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
277-
});
286+
}, extra.sessionId);
278287
}
279288
catch (error) {
280289
console.error("Error sending notification:", error);

src/examples/server/sseAndStreamableHttpCompatibleServer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const getServer = () => {
3333
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
3434
count: z.number().describe('Number of notifications to send (0 for 100)').default(50),
3535
},
36-
async ({ interval, count }): Promise<CallToolResult> => {
36+
async ({ interval, count }, extra): Promise<CallToolResult> => {
3737
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
3838
let counter = 0;
3939

@@ -43,7 +43,7 @@ const getServer = () => {
4343
await server.sendLoggingMessage({
4444
level: "info",
4545
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
46-
});
46+
}, extra.sessionId);
4747
}
4848
catch (error) {
4949
console.error("Error sending notification:", error);

src/server/index.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,29 +111,32 @@ export class Server<
111111
);
112112

113113
if (this._capabilities.logging) {
114-
this.setRequestHandler(SetLevelRequestSchema, async (request) => {
114+
this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
115+
const transportSessionId: string | undefined = extra.sessionId || extra.requestInfo?.headers['mcp-session-id'] as string || undefined;
115116
const { level } = request.params;
116-
this._logLevel = level;
117+
if (transportSessionId && this._levelNames.some(l => l === level)) {
118+
this._loggingLevels.set(transportSessionId, level);
119+
}
117120
return {};
118121
})
119122
}
120123
}
121124

122-
private _logLevel: LoggingLevel = "debug";
123-
private _msgLevels = [
124-
{ level: "debug" },
125-
{ level: "info" },
126-
{ level: "notice" },
127-
{ level: "warning" },
128-
{ level: "error" },
129-
{ level: "critical" },
130-
{ level: "alert" },
131-
{ level: "emergency" },
125+
private _loggingLevels = new Map<string, LoggingLevel>();
126+
private _levelNames = [
127+
"debug",
128+
"info",
129+
"notice",
130+
"warning",
131+
"error",
132+
"critical",
133+
"alert",
134+
"emergency",
132135
];
133136

134-
private isMessageIgnored = (level: LoggingLevel): boolean => {
135-
const currentLevel = this._msgLevels.findIndex((msg) => this._logLevel === msg.level);
136-
const messageLevel = this._msgLevels.findIndex((msg) => level === msg.level);
137+
private isMessageIgnored = (level: LoggingLevel, sessionId: string): boolean => {
138+
const currentLevel = this._levelNames.findIndex((l) => this._loggingLevels.get(sessionId) === l);
139+
const messageLevel = this._levelNames.findIndex((l) => level === l);
137140
return messageLevel < currentLevel;
138141
};
139142

@@ -385,10 +388,19 @@ export class Server<
385388
);
386389
}
387390

388-
async sendLoggingMessage(params: LoggingMessageNotification["params"]) {
389-
return (!this.isMessageIgnored(params.level))
390-
? this.notification({ method: "notifications/message", params })
391-
: undefined
391+
/**
392+
* Sends a logging message to the client, if connected.
393+
* Note: You only need to send the parameters object, not the entire JSON RPC message
394+
* @see LoggingMessageNotification
395+
* @param params
396+
* @param sessionId optional for stateless and backward compatibility
397+
*/
398+
async sendLoggingMessage(params: LoggingMessageNotification["params"], sessionId?: string) {
399+
if (this._capabilities.logging) {
400+
if (!sessionId || !this.isMessageIgnored(params.level, sessionId)) {
401+
return this.notification({method: "notifications/message", params})
402+
}
403+
}
392404
}
393405

394406
async sendResourceUpdated(params: ResourceUpdatedNotification["params"]) {

src/server/mcp.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,12 +1050,13 @@ export class McpServer {
10501050

10511051
/**
10521052
* Sends a logging message to the client, if connected.
1053-
* Note: You only need to send the parameters object
1053+
* Note: You only need to send the parameters object, not the entire JSON RPC message
10541054
* @see LoggingMessageNotification
10551055
* @param params
1056+
* @param sessionId optional for stateless and backward compatibility
10561057
*/
1057-
async sendLoggingMessage(params: LoggingMessageNotification["params"]) {
1058-
return this.server.sendLoggingMessage(params);
1058+
async sendLoggingMessage(params: LoggingMessageNotification["params"], sessionId?: string) {
1059+
return this.server.sendLoggingMessage(params, sessionId);
10591060
}
10601061
/**
10611062
* Sends a resource list changed event to the client, if connected.

0 commit comments

Comments
 (0)