From f835d4b41aea2f360237e5576a412d1ea0a70357 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 22 Jul 2025 10:13:17 -0400 Subject: [PATCH] Add docs for context state management (#1227) --- docs/servers/context.mdx | 46 +++++++++++++++++++++++++++++++++++++ docs/servers/middleware.mdx | 4 ++++ 2 files changed, 50 insertions(+) diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index a44165b75..91d5c2a2a 100644 --- a/docs/servers/context.mdx +++ b/docs/servers/context.mdx @@ -17,6 +17,7 @@ The `Context` object provides a clean interface to access MCP features within yo - **Resource Access**: Read data from resources registered with the server - **LLM Sampling**: Request the client's LLM to generate text based on provided messages - **User Elicitation**: Request structured input from users during tool execution +- **State Management**: Store and share data across middleware and tool calls within a request - **Request Information**: Access metadata about the current request - **Server Access**: When needed, access the underlying FastMCP server instance @@ -185,6 +186,51 @@ content = content_list[0].content **Method signature:** - **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts +### State Management + + + +Store and share data across middleware and tool calls within a request. Context objects maintain a state dictionary that's especially useful for passing information from [middleware](/servers/middleware) to your tools. + +To store a value in the context state, use `ctx.set_state(key, value)`. To retrieve a value, use `ctx.get_state(key)`. + +This simplified example shows how to use MCP middleware to store user info in the context state, and how to access that state in a tool: + +```python {7-8, 16-17} +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class UserAuthMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + + # Middleware stores user info in context state + context.fastmcp_context.set_state("user_id", "user_123") + context.fastmcp_context.set_state("permissions", ["read", "write"]) + + return await call_next() + +@mcp.tool +async def secure_operation(data: str, ctx: Context) -> str: + """Tool can access state set by middleware.""" + + user_id = ctx.get_state("user_id") # "user_123" + permissions = ctx.get_state("permissions") # ["read", "write"] + + if "write" not in permissions: + return "Access denied" + + return f"Processing {data} for user {user_id}" +``` + +**Method signatures:** +- **`ctx.set_state_value(key: str, value: Any) -> None`**: Store a value in the context state +- **`ctx.get_state_value(key: str) -> Any`**: Retrieve a value from the context state (returns None if not found) + +**State Inheritance:** +When a new context is created (nested contexts), it inherits a copy of its parent's state. This ensures that: +- State set on a child context never affects the parent context +- State set on a parent context after the child context is initialized is not propagated to the child context + +This makes state management predictable and prevents unexpected side effects between nested operations. ### Change Notifications diff --git a/docs/servers/middleware.mdx b/docs/servers/middleware.mdx index a9667269f..6ab894aae 100644 --- a/docs/servers/middleware.mdx +++ b/docs/servers/middleware.mdx @@ -244,6 +244,10 @@ You have complete control over the request flow: - **Stop the chain**: Don't call `call_next` (rarely needed) - **Handle errors**: Wrap `call_next` in try/catch blocks +#### State Management + +In addition to modifying the request and response, you can also store state data that your tools can (optionally) access later. To do so, use the FastMCP Context to either `set_state` or `get_state` as appropriate. For more information, see the [Context State Management](/servers/context#state-management) docs. + ## Creating Middleware FastMCP middleware is implemented by subclassing the `Middleware` base class and overriding the hooks you need. You only need to implement the hooks that are relevant to your use case.