-
Notifications
You must be signed in to change notification settings - Fork 1.5k
MCP SDK: SDK internal logging & customizable logger #1034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
2042afd
a78b866
d216768
7521991
3d7aca7
df2bc7a
cb948fb
c513241
92b9c3c
9f3cdf8
f661a22
0ab3a3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * LogLevel - SysLog RFC5424 compliant log levels | ||
| * | ||
| * @see RFC5424: https://tools.ietf.org/html/rfc5424 | ||
| */ | ||
| type LogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency'; | ||
|
|
||
| /** | ||
| * Logger - SysLog RFC5424 compliant logger type | ||
| * | ||
| * @see RFC5424: https://tools.ietf.org/html/rfc5424 | ||
| */ | ||
| export type Logger = { | ||
| [Level in LogLevel]: (message: string, extra?: Record<string, unknown>) => void; | ||
| }; | ||
|
|
||
| /** | ||
| * Console logger implementation of the Logger interface, to be used by default if no custom logger is provided. | ||
| * | ||
| * @remarks | ||
| * The console logger will log to the console. | ||
| * | ||
| * The console logger will log at the following levels: | ||
| * - log (alias for console.debug) | ||
| * - info | ||
| * - error | ||
| */ | ||
| export const consoleLogger: Logger = { | ||
| debug: (message, extra) => { | ||
| console.log(message, extra); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm concerned about any implementation that uses anything but In Node, People might use it only during development and with StreamableHttp it's not an issue, but still, it's a footgun. I could imagine an implementation that always uses info: (message, extra) => {
`console.error(`[info] ${message}`, extra);
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we have another default logger for STDIO specifically? That's absolutely possible, and won't add much overhead in terms of code implementation. Not sure here... Because otherwise we limit every other transport to .error; I know for sure some systems (e.g. if it's a NextJS backend) will treat .error in a special way - so defaulting for that for info/debug/notice seems suspicious. Alternatively, we can wholeheartedly just export the default console logger, a default stdio console logger, and let the end user of the SDK specify it if they wish. Otherwise, our default would be to not log anything?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing two logger implementations, one that would work with STDIO and one that would work with other transports seems reasonable. Not sure how to name them, maybe However, you were planning to log in different places in the SDK, will they all have access to the logger that the transport has chosen? Server code that gets run before a transport is connected could still fail. What logger would it use? Maybe when creating a import { stdioLogger as logger } from '@modelcontextprotocol/sdk/shared/logger.js';
...
const server = new McpServer(
{
name: "my-server",
version: "1.0.0",
},
{
logger,
capabilities: {
tools: {},
logging: {},
},
instructions,
},
);
const transport = new StdioServerTransport();
await server.connect(transport);
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem I see is that McpServer might then use a However, it still concerns me - I'm not sure if Ultimately we might decide to not go for a default logger at all, and leave that to the implementor? Or alternatively, only have default for the HTTP transports?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking the exact same thing, re: logging to // This could return a Server or McpServer
// Any logging as a result of this call is
// happening on the server's default logger,
// which could be console.log
const {server} = createServer();
// Now we switch to a transport-appropriate logger
server.logger = new StdioTransportLogger()
const transport = new StdioServerTransport();
await server.connect(transport);
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo stdioLogger should write to a file.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Respecting log levels on console is important. |
||
| }, | ||
| info: (message, extra) => { | ||
| console.info(message, extra); | ||
| }, | ||
| notice: (message, extra) => { | ||
| console.info(message, extra); | ||
| }, | ||
| warning: (message, extra) => { | ||
| console.warn(message, extra); | ||
| }, | ||
| error: (message, extra) => { | ||
| console.error(message, extra); | ||
| }, | ||
| critical: (message, extra) => { | ||
| console.error(message, extra); | ||
| }, | ||
| alert: (message, extra) => { | ||
| console.error(message, extra); | ||
| }, | ||
| emergency: (message, extra) => { | ||
| console.error(message, extra); | ||
| } | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a problem with this implementation, since it is based on level names rather than level numbers.
I had previously thought that these stated level names were actually what
SysLog RFC5424 compliant log levelsmeant. The MCP logging spec even indicates this, saying it uses the RFC5424 log levels, and then describes them by name, not number. I now realize that is not fully correct either. It uses the level names that RFC5424 uses as examples.On closer reading of the PRI section of RFC5424, and upon inspecting the Winston logger level names, I realize the names are not the spec, but rather the numerical severity level.
RFC5424 only says
Severity values MUST be in the range of 0 to 7 inclusive.It goes on to explain how to combine Severity with Facility value to produce the Priority value. It doesn't mention anything about the specific level names being required, they are apparently only for demonstrative purposes.Winston (which you mention as being compatible with this logging feature) uses different names:
So a
logger.critical()call would have no implementation in Winston out of the box.Notice Winston's implementation is centered around the numerical values that RFC5424 specifies (though it has one less severity level). Note that Winston allows you to have your own custom log level titles, since it's not the name, but rather the severity level that is important.
I think if we're going to try and implement a custom logging feature around RFC5424, we should take the approach Winston did, which is to base it around numeric severity value, letting the name just be associated.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review!
Have updated implementation to match them.
I had previously opted in to follow rather the MCP logging spec naming (e.g.
emergencyvsemerg- the former being used in the MCP logging spec, and the latter being used in Winston).I agree to question the MCP logging documentation - that it does not define priorities, but by RFC5424 it should, as the standard revolves around priorities and not about naming.
PS. The code quote you've pasted in above from Winston's README is actually its
npmlog levels and not theirsysloglevels, probably accidentally.On another note, the goal here is not to be able to pass a winston logger and for this to work out of the box, but rather any logging library can be mapped to the
Loggerinterface, similar to how it's done forconsoleon theconsoleLoggergiven as example.Would also like some comment/viewpoint on whether we should provide a default
consoleLoggerbehavior (and log in console by default), or rather take a "not log at all if logger isn't passed by the end user" approach.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not log if not passed in :) logs can add substantial cost in observability platforms so it would be better to be opt in.