Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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
  • Loading branch information
cliffhall committed Aug 19, 2025
commit 8f3e203dbcfb1f9ac78e247142334641c254afe9
8 changes: 4 additions & 4 deletions src/examples/server/jsonResponseStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,27 @@ const getServer = () => {
{
name: z.string().describe('Name to greet'),
},
async ({ name }): Promise<CallToolResult> => {
async ({ name }, extra): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

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

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

await server.sendLoggingMessage({
level: "info",
data: `Sending first greeting to ${name}`
});
}, extra.sessionId);

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

await server.sendLoggingMessage({
level: "info",
data: `Sending second greeting to ${name}`
});
}, extra.sessionId);

return {
content: [
Expand Down
6 changes: 3 additions & 3 deletions src/examples/server/simpleSseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ const getServer = () => {
interval: z.number().describe('Interval in milliseconds between notifications').default(1000),
count: z.number().describe('Number of notifications to send').default(10),
},
async ({ interval, count }): Promise<CallToolResult> => {
async ({ interval, count }, extra): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;

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

// Send periodic notifications
while (counter < count) {
Expand All @@ -47,7 +47,7 @@ const getServer = () => {
await server.sendLoggingMessage({
level: "info",
data: `Notification #${counter} at ${new Date().toISOString()}`
});
}, extra.sessionId);
}
catch (error) {
console.error("Error sending notification:", error);
Expand Down
4 changes: 2 additions & 2 deletions src/examples/server/simpleStatelessStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const getServer = () => {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(10),
},
async ({ interval, count }): Promise<CallToolResult> => {
async ({ interval, count }, extra): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;

Expand All @@ -52,7 +52,7 @@ const getServer = () => {
await server.sendLoggingMessage({
level: "info",
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
});
}, extra.sessionId);
}
catch (error) {
console.error("Error sending notification:", error);
Expand Down
21 changes: 15 additions & 6 deletions src/examples/server/simpleStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,27 @@ const getServer = () => {
readOnlyHint: true,
openWorldHint: false
},
async ({ name }): Promise<CallToolResult> => {
async ({ name }, extra): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

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

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

await server.sendLoggingMessage( { level: "info", data: `Sending first greeting to ${name}`});
await server.sendLoggingMessage({
level: "info",
data: `Sending first greeting to ${name}`
}, extra.sessionId);

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

await server.sendLoggingMessage( { level: "info", data: `Sending second greeting to ${name}`});
await server.sendLoggingMessage({
level: "info",
data: `Sending second greeting to ${name}`
}, extra.sessionId);

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

Expand All @@ -274,7 +283,7 @@ const getServer = () => {
await server.sendLoggingMessage( {
level: "info",
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
});
}, extra.sessionId);
}
catch (error) {
console.error("Error sending notification:", error);
Expand Down
4 changes: 2 additions & 2 deletions src/examples/server/sseAndStreamableHttpCompatibleServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const getServer = () => {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(50),
},
async ({ interval, count }): Promise<CallToolResult> => {
async ({ interval, count }, extra): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;

Expand All @@ -43,7 +43,7 @@ const getServer = () => {
await server.sendLoggingMessage({
level: "info",
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
});
}, extra.sessionId);
}
catch (error) {
console.error("Error sending notification:", error);
Expand Down
50 changes: 31 additions & 19 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,32 @@ export class Server<
);

if (this._capabilities.logging) {
this.setRequestHandler(SetLevelRequestSchema, async (request) => {
this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
const transportSessionId: string | undefined = extra.sessionId || extra.requestInfo?.headers['mcp-session-id'] as string || undefined;
const { level } = request.params;
this._logLevel = level;
if (transportSessionId && this._levelNames.some(l => l === level)) {
this._loggingLevels.set(transportSessionId, level);
}
return {};
})
}
}

private _logLevel: LoggingLevel = "debug";
private _msgLevels = [
{ level: "debug" },
{ level: "info" },
{ level: "notice" },
{ level: "warning" },
{ level: "error" },
{ level: "critical" },
{ level: "alert" },
{ level: "emergency" },
private _loggingLevels = new Map<string, LoggingLevel>();
private _levelNames = [
"debug",
"info",
"notice",
"warning",
"error",
"critical",
"alert",
"emergency",
];

private isMessageIgnored = (level: LoggingLevel): boolean => {
const currentLevel = this._msgLevels.findIndex((msg) => this._logLevel === msg.level);
const messageLevel = this._msgLevels.findIndex((msg) => level === msg.level);
private isMessageIgnored = (level: LoggingLevel, sessionId: string): boolean => {
const currentLevel = this._levelNames.findIndex((l) => this._loggingLevels.get(sessionId) === l);
const messageLevel = this._levelNames.findIndex((l) => level === l);
return messageLevel < currentLevel;
};

Expand Down Expand Up @@ -385,10 +388,19 @@ export class Server<
);
}

async sendLoggingMessage(params: LoggingMessageNotification["params"]) {
return (!this.isMessageIgnored(params.level))
? this.notification({ method: "notifications/message", params })
: undefined
/**
* Sends a logging message to the client, if connected.
* Note: You only need to send the parameters object, not the entire JSON RPC message
* @see LoggingMessageNotification
* @param params
* @param sessionId optional for stateless and backward compatibility
*/
async sendLoggingMessage(params: LoggingMessageNotification["params"], sessionId?: string) {
if (this._capabilities.logging) {
if (!sessionId || !this.isMessageIgnored(params.level, sessionId)) {
return this.notification({method: "notifications/message", params})
}
}
}

async sendResourceUpdated(params: ResourceUpdatedNotification["params"]) {
Expand Down
7 changes: 4 additions & 3 deletions src/server/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,12 +1050,13 @@ export class McpServer {

/**
* Sends a logging message to the client, if connected.
* Note: You only need to send the parameters object
* Note: You only need to send the parameters object, not the entire JSON RPC message
* @see LoggingMessageNotification
* @param params
* @param sessionId optional for stateless and backward compatibility
*/
async sendLoggingMessage(params: LoggingMessageNotification["params"]) {
return this.server.sendLoggingMessage(params);
async sendLoggingMessage(params: LoggingMessageNotification["params"], sessionId?: string) {
return this.server.sendLoggingMessage(params, sessionId);
}
/**
* Sends a resource list changed event to the client, if connected.
Expand Down