Skip to content

Context state inheritence issue when composing servers #3794

@JFrandon

Description

@JFrandon

What happened?

When a child FastMCP server is mounted on a parent, middleware registered on
the parent can set session state via context.fastmcp_context.set_state(...).
However, that state is NOT visible to tools running on the child server: calling
ctx.get_state(...) inside a child tool returns None.

This only occurs when no shared session_state_store is provided.
Without a shared store, the parent and child maintain
separate state namespaces and the child cannot see state written by the parent
middleware.

We expected the state set by the parent middleware to be accessible to downstream tools because of the chaining of middleware.
These defaults state scoping rules are not documented.

Example Code

from __future__ import annotations

from typing import TYPE_CHECKING, Any, override

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

from fastmcp import Context, FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContext
from key_value.aio.stores.memory import MemoryStore

fix_bug = False
shared_store = MemoryStore() if fix_bug else None


class AddContextMiddleware(Middleware):
    """Middleware that adds context info to each request."""

    @override
    async def on_request(
        self,
        context: MiddlewareContext,
        call_next: Callable[..., Awaitable[Any]],
    ) -> Any:
        """Set context info state before passing to next handler.

        Returns:
            The response from the next handler.

        """
        if context.fastmcp_context:
            context.fastmcp_context.set_state("info", "I'm some context")
        return await call_next(context)


mcp_parent = FastMCP(
    "Parent",
    middleware=[AddContextMiddleware()],
    session_state_store=shared_store,
)

mcp_child = FastMCP("Child", session_state_store=shared_store)
mcp_parent.mount(mcp_child)


@mcp_child.tool
def print_context(ctx: Context) -> str:
    """Print the context info state.

    Returns:
        The info state value, or None because states are not inherited by default.

    """
    return ctx.get_state("info")


def main() -> None:
    """Run the parent MCP server."""
    mcp_parent.run()

Version Information

FastMCP version:                                                                         3.2.0
MCP version:                                                                            1.27.0
Python version:                                                                         3.14.0
Platform:                                                            Windows-11-10.0.26100-SP0
FastMCP root path: C:\Users\JeremyFrandon\projects\nexxo\bug-fastmcp\.venv\Lib\site-packages

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.potential-duplicateBot-suggested duplicate awaiting human review. Auto-closes after 3 days if unchallenged.serverRelated to FastMCP server implementation or server-side functionality.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions