Client Logging with FastMCP
The Four Log Levels
MCP defines four severity levels for client-bound messages, and choosing the right one is mostly a matter of audience and intent.
ctx.debug() is for granular, internal details that help diagnose problems. A debug message might say Query returned 3,217 rows in 42ms or Cache miss for key user:5678. End users rarely need to see these, but they are invaluable when something goes wrong and you need to trace exactly what happened. Development-oriented clients may display them; production clients typically filter them out or store them quietly.
ctx.info() is for normal operational updates that a user or operator would find useful. Analyzing 42 breeds, Connected to database, Search complete, found 7 results — these are the messages that tell the client what is happening during a long operation. This is the level you will use most often.
ctx.warning() signals that something is not quite right, but the tool can still proceed. Perhaps the user passed a deprecated parameter, or a dataset contained some invalid entries that were skipped. The tool didn't fail, but the client should know that the result may not be exactly what was expected. Warnings are a way of saying this worked, but pay attention.
ctx.error() is for failures that the client needs to know about. An API call timed out, a required file was not found, or a computation produced an impossible result. Error-level messages are often paired with raising an exception (like ToolError), but the log message gives the client structured information about what went wrong before the exception propagates.
FastMCP provides convenience methods for the four most common levels (debug, info, warning, error), but the MCP specification actually defines eight severity levels in total: debug, info, notice, warning, error, critical, alert, and emergency. If you need one of the additional levels, you can use the generic await ctx.log("message", level="critical") method directly.
To go back to our dog age calculator example, here is what it might look like if we added some logging:
from fastmcp import FastMCP
from fastmcp import Context
from fastmcp.exceptions import ToolError
@mcp.tool
async def dog_to_human_age(
age: Annotated[int, Field(ge=0, le=30, description="The dog's age in years")],
breed: Annotated[str, Field(description="The dog's breed"Practical MCP with FastMCP & LangChain
Engineering the Agentic ExperienceEnroll now to unlock current content and receive all future updates for free. Your purchase supports the author and fuels the creation of more exciting content. Act fast, as the price will rise as the course nears completion!
