FastMCP Error Masking: How to Protect Sensitive Information in Production
FastMCP prevents sensitive data leakage by masking error details through the mask_error_details configuration flag, which intercepts generic exceptions and replaces them with a generic "Internal server error" message while allowing whitelisted error types to propagate to clients.
FastMCP, the high-level Python SDK for building Model Context Protocol (MCP) servers maintained by PrefectHQ, implements a robust error masking mechanism to safeguard internal implementation details. When deploying production MCP servers, preventing the exposure of stack traces, database connection strings, or validation errors to external clients is critical for security. Understanding how FastMCP handles error masking ensures you can balance debugging capabilities with information security requirements.
Configuration via Global Settings
The error masking behavior is controlled by the mask_error_details boolean flag defined in the global settings model. Located in fastmcp_slim/fastmcp/settings.py (lines 320–331), this setting determines whether the server should hide exact exception messages that originate from user-provided functions.
When mask_error_details=True, FastMCP conceals the original error message for all unhandled exceptions. Only a generic message is transmitted to the client. However, if the raised exception is an explicit "allowed" type—specifically ToolError, ResourceError, PromptError, or any subclass of MCPError—the original message passes through unmodified.
Server Initialization and Storage
When instantiating a FastMCP server, the constructor copies the global configuration into a private instance attribute. In fastmcp_slim/fastmcp/server/server.py (lines 402–406), the __init__ method stores the flag as _mask_error_details.
If the caller does not explicitly pass a mask_error_details value to the constructor, FastMCP falls back to the global default defined in fastmcp.settings.mask_error_details. This design allows for both global configuration and per-server overrides.
Runtime Masking in Request Handlers
The core masking logic executes within the request-handling mixins that manage tool calls, resource reads, and prompt renders. All exceptions funnel through a central checkpoint in fastmcp_slim/fastmcp/server/mixins/mcp_operations.py.
The mixin methods catch any exception that is not an MCPError subtype. If _mask_error_details is enabled, the handler replaces the exception message with "Internal server error" before converting it to the MCP wire-format error via to_mcp_error. This occurs in three specific areas:
- Tool execution (lines 1448–1651): Masks errors from
@mcp.tooldecorated functions - Resource access (lines 1525–1540): Masks errors from
@mcp.resourcehandlers - Prompt rendering (lines 1700–1720): Masks errors from
@mcp.promptfunctions
Sampling Runner Compliance
The error masking guarantee extends to the sampling subsystem. When executing tools within the sampling runner, FastMCP respects the same privacy controls. In fastmcp_slim/fastmcp/server/sampling/run.py (lines 364–368), the runner checks the masking flag before processing tool exceptions, ensuring consistent behavior across standard requests and sampling workflows.
Whitelisted Error Types
Error masking selectively allows certain exception types to bypass the filter. Developers can intentionally expose specific error messages by raising:
ToolError– For tool-specific validation failuresResourceError– For resource access issuesPromptError– For prompt rendering problems- Any
MCPErrorsubclass – For custom domain errors
These exceptions propagate to clients with their original messages intact, enabling meaningful client-side error handling while maintaining protection against accidental information leakage.
Implementation Examples
Enable error masking during server initialization to protect production endpoints:
from fastmcp import FastMCP, ToolError
# Production server with masking enabled
mcp = FastMCP("secure-server", mask_error_details=True)
@mcp.tool
def fragile_tool(x: int) -> int:
# This runtime error will be masked
raise ValueError("secret-database-password-exposed")
When a client calls fragile_tool, they receive only:
{"error": "Internal server error"}
To expose specific errors intentionally, use whitelisted exception types:
@mcp.tool
def safe_tool(x: int) -> int:
if x < 0:
# This message is visible to the client
raise ToolError("Negative numbers are not allowed")
return x * 2
For debugging scenarios, disable masking to receive full traceback details:
# Debug server - default behavior
mcp_debug = FastMCP("debug-server", mask_error_details=False)
# Now fragile_tool returns the full ValueError message and stack trace
Summary
- Global Configuration: The
mask_error_detailsflag infastmcp_slim/fastmcp/settings.pycontrols the default masking behavior for all FastMCP servers. - Server Storage: Each
FastMCPinstance stores the flag as_mask_error_detailsduring initialization inserver.py. - Selective Bypass: Only
ToolError,ResourceError,PromptError, andMCPErrorsubclasses bypass the masking filter. - Universal Coverage: The masking logic applies consistently across tools, resources, prompts, and sampling operations via
mcp_operations.pymixins. - Production Security: When enabled, all non-whitelisted exceptions return the generic message "Internal server error" to clients.
Frequently Asked Questions
What is the default value of mask_error_details in FastMCP?
By default, mask_error_details is set to False, meaning full error messages and tracebacks are included in MCP responses. This default facilitates debugging during development, but production deployments should explicitly set mask_error_details=True to prevent information leakage.
Which error types bypass the masking filter in FastMCP?
Exceptions that inherit from MCPError—including ToolError, ResourceError, and PromptError—bypass the masking mechanism. When these specific error types are raised, FastMCP transmits the original error message to the client regardless of the masking configuration.
How does FastMCP error masking affect debugging during development?
When masking is enabled, developers lose visibility into the root cause of failures from the client perspective. To debug effectively, run a local instance with mask_error_details=False or inspect server-side logs where the original exception details remain available before the masking transformation occurs.
Does error masking apply to all MCP operations including resources and prompts?
Yes, the masking logic is implemented uniformly across all request handlers. The mcp_operations.py mixins apply identical masking checks to tool calls, resource reads, and prompt renders, ensuring consistent protection regardless of which MCP primitive triggers the exception.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →