diff --git a/README.md b/README.md index 68969d0e..05d60725 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,20 @@ The Model Context Protocol allows applications to provide context for LLMs in a ### Adding MCP to your python project -We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects. In a uv managed python project, add mcp to dependencies by: +We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects. -```bash -uv add "mcp[cli]" -``` +If you haven't created a uv-managed project yet, create one: + + ```bash + uv init mcp-server-demo + cd mcp-server-demo + ``` + + Then add MCP to your project dependencies: + + ```bash + uv add "mcp[cli]" + ``` Alternatively, for projects using pip for dependencies: ```bash diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index bf0ce880..a1bb7f79 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -660,7 +660,7 @@ async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContent async def log( self, level: Literal["debug", "info", "warning", "error"], - message: str, + message: Any, *, logger_name: str | None = None, ) -> None: @@ -696,18 +696,38 @@ def session(self): return self.request_context.session # Convenience methods for common log levels - async def debug(self, message: str, **extra: Any) -> None: - """Send a debug log message.""" + async def debug(self, message: Any, **extra: Any) -> None: + """Send a debug log message. + + Args: + message: The message to log. Can be any JSON-serializable type. + **extra: Additional structured data to include + """ await self.log("debug", message, **extra) - async def info(self, message: str, **extra: Any) -> None: - """Send an info log message.""" + async def info(self, message: Any, **extra: Any) -> None: + """Send an info log message. + + Args: + message: The message to log. Can be any JSON-serializable type. + **extra: Additional structured data to include + """ await self.log("info", message, **extra) - async def warning(self, message: str, **extra: Any) -> None: - """Send a warning log message.""" + async def warning(self, message: Any, **extra: Any) -> None: + """Send a warning log message. + + Args: + message: The message to log. Can be any JSON-serializable type. + **extra: Additional structured data to include + """ await self.log("warning", message, **extra) - async def error(self, message: str, **extra: Any) -> None: - """Send an error log message.""" + async def error(self, message: Any, **extra: Any) -> None: + """Send an error log message. + + Args: + message: The message to log. Can be any JSON-serializable type. + **extra: Additional structured data to include + """ await self.log("error", message, **extra) diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py index e76e59c5..21c698fa 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -554,6 +554,43 @@ async def logging_tool(msg: str, ctx: Context) -> str: level="error", data="Error message", logger=None ) + @pytest.mark.anyio + async def test_context_logging_any_type(self): + """Test that context logging methods can handle different types of messages.""" + from unittest.mock import patch + + import mcp.server.session + + mcp = FastMCP() + + async def logging_tool(ctx: Context) -> str: + # Test with different data types + await ctx.debug(42) # integer + await ctx.info(["list", "of", "items"]) # list + await ctx.warning({"key": "value"}) # dictionary + await ctx.error(3.14) # float + return "Logged messages of different types" + + mcp.add_tool(logging_tool) + + with patch("mcp.server.session.ServerSession.send_log_message") as mock_log: + async with client_session(mcp._mcp_server) as client: + result = await client.call_tool("logging_tool", {}) + assert len(result.content) == 1 + content = result.content[0] + assert isinstance(content, TextContent) + assert "Logged messages of different types" in content.text + + assert mock_log.call_count == 4 + mock_log.assert_any_call(level="debug", data=42, logger=None) + mock_log.assert_any_call( + level="info", data=["list", "of", "items"], logger=None + ) + mock_log.assert_any_call( + level="warning", data={"key": "value"}, logger=None + ) + mock_log.assert_any_call(level="error", data=3.14, logger=None) + @pytest.mark.anyio async def test_optional_context(self): """Test that context is optional."""