Miscellaneous

Miscellaneous

FastMCP tools with Shared Memory for AI Agents

FastMCP makes it straightforward to expose Python functions as MCP tools, but every tool invocation starts from a clean slate. Connecting Mem0 to FastMCP tool implementations turns a collection of stateless handlers into a memory-backed agent toolkit that accumulates user context across sessions.

What FastMCP Is and How MCP Tools Work

FastMCP is a Python framework for building servers that implement the Model Context Protocol (MCP), the open standard that lets LLMs call external tools, read resources, and receive structured prompts. Installing it takes one command: pip install fastmcp. Defining a tool takes a decorator:

from fastmcp import FastMCP

mcp = FastMCP("Agent Tools")

@mcp.tool
def get_weather(city: str) -> str:
    """Return current weather for a city."""
    return fetch_weather_api(city)

if __name__ == "__main__

from fastmcp import FastMCP

mcp = FastMCP("Agent Tools")

@mcp.tool
def get_weather(city: str) -> str:
    """Return current weather for a city."""
    return fetch_weather_api(city)

if __name__ == "__main__

from fastmcp import FastMCP

mcp = FastMCP("Agent Tools")

@mcp.tool
def get_weather(city: str) -> str:
    """Return current weather for a city."""
    return fetch_weather_api(city)

if __name__ == "__main__

FastMCP generates JSON schemas automatically from type hints, validates inputs, and populates tool descriptions from docstrings. The developer writes plain Python functions; FastMCP handles protocol negotiation, transport, and lifecycle management. The result is a clean abstraction over MCP's wire format.

The protocol itself is intentionally stateless. Each tool call is an isolated request-response exchange. The server receives arguments, executes the function, and returns a result. Nothing about the caller, the session, or prior calls is preserved between invocations.

The Statefulness Gap in MCP Tool Design

Statelessness is the right default for a protocol designed to be composable and host-agnostic. But the agents that consume MCP tools are not stateless. They serve users across multiple sessions, build up context over time, and need that context to produce useful responses.

statefulness gap emerges when the agent expects tools to behave intelligently based on what it knows about the user, but the tool has no way to access that knowledge. The tool is a black box that resets on every call.

Consider a preference-aware tool:




No matter how many times a user tells the agent their preferences, this tool returns the default. The agent may carry those preferences in its own context window during a session, but that context evaporates when the session ends. The next session starts fresh.

Why Stateless Tools Break User Experience at Scale

The cost of statelessness compounds as the user base grows. A few specific failure modes appear consistently in production:

First, repeated preference elicitation. Every new session, the agent asks users for information they have already provided. This is tolerable once; it becomes friction after the second or third time, and churn after the fifth.

Second, lost workflow context. A user running a multi-step research task across several sessions has to re-explain what was tried, what was rejected, and what constraints apply. The tool has no record of prior steps.

Third, inability to personalize at the tool layer. If personalization lives only in the agent's prompt, it can be overridden, lost to context window limits, or unavailable to tools that need to fetch data differently based on user history.

The solution is not to make MCP stateful, since the protocol design is intentional, but to attach an external memory store to the tool implementations.

The Pattern: Memory Retrieval at Tool Entry, Memory Write on State Change

The core pattern has two parts. At the start of each tool invocation, retrieve any memories relevant to the user and the current task. At the end of the invocation, if the call produced new information worth keeping, write it to the memory store.

A single Mem0 client instance is shared across all tool definitions in the server. Tool functions become stateful from the agent's perspective while the MCP protocol remains unchanged. The retrieval tools query Mem0 with a semantic query, returning the most relevant stored memories for that user. The write tools call memory.add() with a structured message, triggering Mem0's extraction pipeline to identify what is worth keeping. Four tools cover the main use cases: get preferences, set a preference, get task context for a specific task, and record a task outcome.

Stateless vs Memory-Backed FastMCP Tools

Capability

Stateless FastMCP Tools

Memory-Backed FastMCP Tools

Preference persistence across sessions

No

Yes

Task context retrieval

No

Yes

Repeated preference elicitation

Always

Never after first capture

Personalization at tool layer

No

Yes

Cross-session learning

No

Yes

Infrastructure overhead

None

Mem0 API or self-hosted

Protocol compliance

Full

Full (statelessness is in MCP layer)

Multi-user isolation

Manual (by argument)

Enforced by user_id filter

The MCP protocol itself is unchanged in both columns. The difference lives entirely inside the tool implementations.

When to Put Memory in the Tool vs in the Agent

There are two places where memory retrieval can live: inside the MCP tool, or in the agent that calls the tool. The choice depends on who needs the information and when.

Memory in the tool is appropriate when the tool needs context to produce a correct result. A recommendation tool that must know the user's domain, their past choices, and their constraints before it can return a useful answer is a good example. The tool is the only component that can make this decision; the agent cannot add user history to a tool call that does not accept it.

Memory in the agent is appropriate when the context shapes how the agent decides which tool to call, how to phrase the tool arguments, or how to interpret the result. A coding agent that knows the user prefers type-annotated Python can use that knowledge to format its tool inputs better, without burdening every tool with memory retrieval.

In practice, a well-designed system uses both: the agent holds session-level working context, and tools access persistent user memory as needed for their specific function.

Multi-User Memory Isolation Using user_id

Mem0's filters parameter enforces strict user-level isolation. Every search and every add operation scoped to a user_id operates on a separate memory namespace. One user's preferences, task history, and stored context are never visible to queries for a different user.

The user_id value can be any stable identifier: a database primary key, an auth system subject claim, or a hashed email. The key requirement is consistency: the same user must always map to the same user_id string across sessions and tool calls.

For multi-tenant MCP servers where the same deployed server handles requests for many users, this isolation model scales without architectural changes. Each user's memory is partitioned at the data layer, not the infrastructure layer.

Where Mem0 Fits

Mem0 provides the persistent memory layer that FastMCP tools lack by design. Its hosted API handles vector storage, extraction, deduplication, and retrieval without requiring a separate vector database or embedding pipeline. For self-hosted deployments, the open-source library runs the same extraction logic locally.

from fastmcp import FastMCP
from mem0 import MemoryClient
import json

mcp = FastMCP("Memory-Backed Agent Tools")
memory = MemoryClient(api_key="your-api-key")

@mcp.tool
def get_user_preferences(user_id: str) -> str:
    """Return known preferences for a user, drawn from memory."""
    results = memory.search(
        "user preferences language output format style",
        filters={"user_id": user_id}
    )
    if not results:
        return "No preferences stored yet."
    return "\\n".join([r["memory"] for r in results])

@mcp.tool
def set_user_preference(user_id: str, preference: str) -> str:
    """Store a new preference for the user."""
    memory.add(
        [{"role": "user", "content": f"My preference: {preference}"}],
        user_id=user_id
    )
    return f"Stored preference for {user_id}."

@mcp.tool
def get_task_context(user_id: str, task_description: str) -> str:
    """Return prior work and findings relevant to a task."""
    results = memory.search(
        task_description,
        filters={"user_id": user_id}
    )
    if not results:
        return "No prior context for this task."
    return json.dumps({"prior_context": [r["memory"] for r in results]}, indent=2)

@mcp.tool
def record_task_outcome(user_id: str, task: str, outcome: str) -> str:
    """Persist the outcome of a completed task step."""
    memory.add(
        [{"role": "assistant", "content": f"Task: {task}. Outcome: {outcome}"}],
        user_id=user_id
    )
    return "Task outcome recorded."

if __name__ == "__main__

from fastmcp import FastMCP
from mem0 import MemoryClient
import json

mcp = FastMCP("Memory-Backed Agent Tools")
memory = MemoryClient(api_key="your-api-key")

@mcp.tool
def get_user_preferences(user_id: str) -> str:
    """Return known preferences for a user, drawn from memory."""
    results = memory.search(
        "user preferences language output format style",
        filters={"user_id": user_id}
    )
    if not results:
        return "No preferences stored yet."
    return "\\n".join([r["memory"] for r in results])

@mcp.tool
def set_user_preference(user_id: str, preference: str) -> str:
    """Store a new preference for the user."""
    memory.add(
        [{"role": "user", "content": f"My preference: {preference}"}],
        user_id=user_id
    )
    return f"Stored preference for {user_id}."

@mcp.tool
def get_task_context(user_id: str, task_description: str) -> str:
    """Return prior work and findings relevant to a task."""
    results = memory.search(
        task_description,
        filters={"user_id": user_id}
    )
    if not results:
        return "No prior context for this task."
    return json.dumps({"prior_context": [r["memory"] for r in results]}, indent=2)

@mcp.tool
def record_task_outcome(user_id: str, task: str, outcome: str) -> str:
    """Persist the outcome of a completed task step."""
    memory.add(
        [{"role": "assistant", "content": f"Task: {task}. Outcome: {outcome}"}],
        user_id=user_id
    )
    return "Task outcome recorded."

if __name__ == "__main__

from fastmcp import FastMCP
from mem0 import MemoryClient
import json

mcp = FastMCP("Memory-Backed Agent Tools")
memory = MemoryClient(api_key="your-api-key")

@mcp.tool
def get_user_preferences(user_id: str) -> str:
    """Return known preferences for a user, drawn from memory."""
    results = memory.search(
        "user preferences language output format style",
        filters={"user_id": user_id}
    )
    if not results:
        return "No preferences stored yet."
    return "\\n".join([r["memory"] for r in results])

@mcp.tool
def set_user_preference(user_id: str, preference: str) -> str:
    """Store a new preference for the user."""
    memory.add(
        [{"role": "user", "content": f"My preference: {preference}"}],
        user_id=user_id
    )
    return f"Stored preference for {user_id}."

@mcp.tool
def get_task_context(user_id: str, task_description: str) -> str:
    """Return prior work and findings relevant to a task."""
    results = memory.search(
        task_description,
        filters={"user_id": user_id}
    )
    if not results:
        return "No prior context for this task."
    return json.dumps({"prior_context": [r["memory"] for r in results]}, indent=2)

@mcp.tool
def record_task_outcome(user_id: str, task: str, outcome: str) -> str:
    """Persist the outcome of a completed task step."""
    memory.add(
        [{"role": "assistant", "content": f"Task: {task}. Outcome: {outcome}"}],
        user_id=user_id
    )
    return "Task outcome recorded."

if __name__ == "__main__

The integration point is the tool function body. No changes to FastMCP's server setup, tool registration, or MCP protocol handling are needed. The memory layer is purely additive.

The Bottom Line

FastMCP makes building MCP tool servers fast; Mem0 makes those tools intelligent by giving them access to a persistent, per-user memory store that survives session boundaries and scales across any number of users.

Mem0 is an intelligent, open-source memory layer designed for LLMs and AI agents to provide long-term, personalized, and context-aware interactions across sessions.

Get your free API Key here: app.mem0.ai or self-host mem0 from our open source github repository

GET TLDR from:

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer