Skip to content

Commit 4649e8d

Browse files
paikendclaude
andcommitted
fix: align Context logging methods with MCP spec data type
Change `message: str` to `data: Any` in `Context.log()` and convenience methods (debug, info, warning, error) to match the MCP specification which allows any JSON-serializable type for log data. Remove the `extra` parameter since callers can now pass structured data directly (e.g., `ctx.info({"key": "value"})`). Fixes #397 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fb2276b commit 4649e8d

File tree

2 files changed

+24
-40
lines changed

2 files changed

+24
-40
lines changed

src/mcp/server/mcpserver/context.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -187,28 +187,21 @@ async def elicit_url(
187187
async def log(
188188
self,
189189
level: Literal["debug", "info", "warning", "error"],
190-
message: str,
190+
data: Any,
191191
*,
192192
logger_name: str | None = None,
193-
extra: dict[str, Any] | None = None,
194193
) -> None:
195194
"""Send a log message to the client.
196195
197196
Args:
198197
level: Log level (debug, info, warning, error)
199-
message: Log message
198+
data: The data to be logged. Any JSON serializable type is allowed
199+
(string, dict, list, number, bool, etc.) per the MCP specification.
200200
logger_name: Optional logger name
201-
extra: Optional dictionary with additional structured data to include
202201
"""
203-
204-
if extra:
205-
log_data = {"message": message, **extra}
206-
else:
207-
log_data = message
208-
209202
await self.request_context.session.send_log_message(
210203
level=level,
211-
data=log_data,
204+
data=data,
212205
logger=logger_name,
213206
related_request_id=self.request_id,
214207
)
@@ -261,20 +254,18 @@ async def close_standalone_sse_stream(self) -> None:
261254
await self._request_context.close_standalone_sse_stream()
262255

263256
# Convenience methods for common log levels
264-
async def debug(self, message: str, *, logger_name: str | None = None, extra: dict[str, Any] | None = None) -> None:
257+
async def debug(self, data: Any, *, logger_name: str | None = None) -> None:
265258
"""Send a debug log message."""
266-
await self.log("debug", message, logger_name=logger_name, extra=extra)
259+
await self.log("debug", data, logger_name=logger_name)
267260

268-
async def info(self, message: str, *, logger_name: str | None = None, extra: dict[str, Any] | None = None) -> None:
261+
async def info(self, data: Any, *, logger_name: str | None = None) -> None:
269262
"""Send an info log message."""
270-
await self.log("info", message, logger_name=logger_name, extra=extra)
263+
await self.log("info", data, logger_name=logger_name)
271264

272-
async def warning(
273-
self, message: str, *, logger_name: str | None = None, extra: dict[str, Any] | None = None
274-
) -> None:
265+
async def warning(self, data: Any, *, logger_name: str | None = None) -> None:
275266
"""Send a warning log message."""
276-
await self.log("warning", message, logger_name=logger_name, extra=extra)
267+
await self.log("warning", data, logger_name=logger_name)
277268

278-
async def error(self, message: str, *, logger_name: str | None = None, extra: dict[str, Any] | None = None) -> None:
269+
async def error(self, data: Any, *, logger_name: str | None = None) -> None:
279270
"""Send an error log message."""
280-
await self.log("error", message, logger_name=logger_name, extra=extra)
271+
await self.log("error", data, logger_name=logger_name)

tests/client/test_logging_callback.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,20 @@ async def test_tool_with_log(
3636
message: str, level: Literal["debug", "info", "warning", "error"], logger: str, ctx: Context
3737
) -> bool:
3838
"""Send a log notification to the client."""
39-
await ctx.log(level=level, message=message, logger_name=logger)
39+
await ctx.log(level=level, data=message, logger_name=logger)
4040
return True
4141

42-
@server.tool("test_tool_with_log_extra")
43-
async def test_tool_with_log_extra(
44-
message: str,
42+
@server.tool("test_tool_with_log_dict")
43+
async def test_tool_with_log_dict(
4544
level: Literal["debug", "info", "warning", "error"],
4645
logger: str,
47-
extra_string: str,
48-
extra_dict: dict[str, Any],
4946
ctx: Context,
5047
) -> bool:
51-
"""Send a log notification to the client with extra fields."""
48+
"""Send a log notification with a dict payload."""
5249
await ctx.log(
5350
level=level,
54-
message=message,
51+
data={"message": "Test log message", "extra_string": "example", "extra_dict": {"a": 1, "b": 2, "c": 3}},
5552
logger_name=logger,
56-
extra={"extra_string": extra_string, "extra_dict": extra_dict},
5753
)
5854
return True
5955

@@ -84,29 +80,26 @@ async def message_handler(
8480
"logger": "test_logger",
8581
},
8682
)
87-
log_result_with_extra = await client.call_tool(
88-
"test_tool_with_log_extra",
83+
log_result_with_dict = await client.call_tool(
84+
"test_tool_with_log_dict",
8985
{
90-
"message": "Test log message",
9186
"level": "info",
9287
"logger": "test_logger",
93-
"extra_string": "example",
94-
"extra_dict": {"a": 1, "b": 2, "c": 3},
9588
},
9689
)
9790
assert log_result.is_error is False
98-
assert log_result_with_extra.is_error is False
91+
assert log_result_with_dict.is_error is False
9992
assert len(logging_collector.log_messages) == 2
10093
# Create meta object with related_request_id added dynamically
10194
log = logging_collector.log_messages[0]
10295
assert log.level == "info"
10396
assert log.logger == "test_logger"
10497
assert log.data == "Test log message"
10598

106-
log_with_extra = logging_collector.log_messages[1]
107-
assert log_with_extra.level == "info"
108-
assert log_with_extra.logger == "test_logger"
109-
assert log_with_extra.data == {
99+
log_with_dict = logging_collector.log_messages[1]
100+
assert log_with_dict.level == "info"
101+
assert log_with_dict.logger == "test_logger"
102+
assert log_with_dict.data == {
110103
"message": "Test log message",
111104
"extra_string": "example",
112105
"extra_dict": {"a": 1, "b": 2, "c": 3},

0 commit comments

Comments
 (0)