Connect with MCP
Connect an MCP client to a Redis Agent Memory store to search, curate, and ingest long-term memories.
Every Redis Agent Memory store serves a Model Context Protocol (MCP) endpoint. Connect an MCP client to let an agent search and curate long-term memories and submit documents for extraction without writing API calls.
The MCP tools cover long-term memory and document ingestion. Session memory is not exposed, because the application that owns the conversation writes its events. Use the SDK or REST API for sessions.
Before you begin
Get the data-plane endpoint, store ID, and an API key with write access to the store. The MCP endpoint requires write access for every tool, including search. Save and load the IRIS_AGENT_MEMORY_* variables as shown in Save the connection values.
For the Python client, use Python 3.10 or later and install these dependencies:
Python
python -m pip install mcp==2.3.0 httpx2==2.13.1
HTTP
curl --version
jq --version
Configure the connection
Use a client that supports Streamable HTTP and custom request headers:
| Setting | Value |
|---|---|
| Transport | Streamable HTTP |
| Endpoint | <data-plane-base-url>/iris/data/v1/agent-memory/stores/<store-id>/mcp |
| Request header | Authorization: Bearer <api-key> |
The server is stateless and returns JSON responses, so clients do not need to keep an MCP session open between calls. The endpoint must be reachable from the client runtime. A hosted client needs an HTTPS endpoint reachable from its provider. Keep the API key in connection settings and out of agent instructions.
On a self-managed deployment with ownership enforcement, use a delegated or user-bound token as described in Authentication. The tools enforce the same ownership rules as the REST API.
Use the tools
Call tools/list on an initialized connection to get the input schemas. Tool arguments use snake_case names, such as owner_id and namespace_id, rather than the REST API's camelCase fields.
| Tool | Use it to |
|---|---|
search_long_term_memory |
Search by text, or filter by owner_id, namespace_id, session_id, memory_type, and topics. Text results rank by similarity; filter-only results can sort by createdAt or updatedAt. |
create_long_term_memories |
Store 1–100 new memories, each with a client-supplied id and required owner_id. An existing ID is reported in errors and left unchanged. |
edit_long_term_memory |
Change only the provided fields of one memory. Changing text re-embeds the memory. |
upsert_long_term_memory |
Create or fully replace one memory, including custom attributes. Omitted optional fields reset to their defaults. |
delete_long_term_memories |
Delete 1–100 memories by ID. |
create_document_ingestion |
Submit one base64-encoded text/plain or text/markdown document for MemoryETL extraction with an enabled extractor. |
get_document_ingestion |
Check processing progress and source cleanup for an ingestion. |
list_document_ingestion_outputs |
Page through the memory IDs an ingestion produced. |
list_memory_sources, list_memory_source_evidence, list_source_memories |
Trace a memory to its sources and evidence, or list the memories linked to a source. |
edit_long_term_memory and upsert_long_term_memory accept expected_revision to apply a change only when the memory's revision is current. The write tools accept idempotency_key so a retry replays the original result instead of writing twice.
Connect a client
This client connects, lists the tools, creates a memory, searches for it, and then deletes it.
Copy the complete program and save it as mcp_client.py or mcp_client.sh. Run python mcp_client.py or bash mcp_client.sh with the connection values you exported.
import asyncio
import json
import os
from uuid import uuid4
import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
OWNER_ID = "mcp-quickstart-user"
def read_result(result):
if result.is_error:
raise RuntimeError(f"Agent Memory tool failed: {result.content}")
if result.structured_content is not None:
return result.structured_content
return json.loads(next(block.text for block in result.content if block.type == "text"))
async def main():
url = (
f"{os.environ['IRIS_AGENT_MEMORY_URL'].rstrip('/')}"
f"/iris/data/v1/agent-memory/stores/{os.environ['IRIS_AGENT_MEMORY_STORE_ID']}/mcp"
)
memory_id = f"mcp-example-{uuid4().hex[:12]}"
async with httpx2.AsyncClient(
headers={"Authorization": f"Bearer {os.environ['IRIS_AGENT_MEMORY_API_KEY']}"}
) as http:
async with Client(streamable_http_client(url, http_client=http)) as client:
print("Tools:", [tool.name for tool in (await client.list_tools()).tools])
created = await client.call_tool(
"create_long_term_memories",
{
"idempotency_key": str(uuid4()),
"memories": [
{
"id": memory_id,
"text": "The user prefers aisle seats on long flights.",
"owner_id": OWNER_ID,
"topics": ["travel"],
}
],
},
)
print(json.dumps(read_result(created), indent=2))
found = await client.call_tool(
"search_long_term_memory",
{"text": "Which seat does the user like?", "owner_id": OWNER_ID, "limit": 3},
)
print(json.dumps(read_result(found), indent=2))
deleted = await client.call_tool(
"delete_long_term_memories", {"memory_ids": [memory_id]}
)
print(json.dumps(read_result(deleted), indent=2))
asyncio.run(main())
import asyncio
import json
import os
from uuid import uuid4
import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
OWNER_ID = "mcp-quickstart-user"
def read_result(result):
if result.is_error:
raise RuntimeError(f"Agent Memory tool failed: {result.content}")
if result.structured_content is not None:
return result.structured_content
return json.loads(next(block.text for block in result.content if block.type == "text"))
async def main():
url = (
f"{os.environ['IRIS_AGENT_MEMORY_URL'].rstrip('/')}"
f"/iris/data/v1/agent-memory/stores/{os.environ['IRIS_AGENT_MEMORY_STORE_ID']}/mcp"
)
memory_id = f"mcp-example-{uuid4().hex[:12]}"
async with httpx2.AsyncClient(
headers={"Authorization": f"Bearer {os.environ['IRIS_AGENT_MEMORY_API_KEY']}"}
) as http:
async with Client(streamable_http_client(url, http_client=http)) as client:
print("Tools:", [tool.name for tool in (await client.list_tools()).tools])
created = await client.call_tool(
"create_long_term_memories",
{
"idempotency_key": str(uuid4()),
"memories": [
{
"id": memory_id,
"text": "The user prefers aisle seats on long flights.",
"owner_id": OWNER_ID,
"topics": ["travel"],
}
],
},
)
print(json.dumps(read_result(created), indent=2))
found = await client.call_tool(
"search_long_term_memory",
{"text": "Which seat does the user like?", "owner_id": OWNER_ID, "limit": 3},
)
print(json.dumps(read_result(found), indent=2))
deleted = await client.call_tool(
"delete_long_term_memories", {"memory_ids": [memory_id]}
)
print(json.dumps(read_result(deleted), indent=2))
asyncio.run(main())
set -euo pipefail
MCP_URL="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID/mcp"
PROTOCOL_VERSION=2025-11-25
OWNER_ID=mcp-quickstart-user
MEMORY_ID="mcp-example-$(openssl rand -hex 6)"
post() {
curl --fail-with-body --silent --show-error "$MCP_URL" \
-H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "MCP-Protocol-Version: $PROTOCOL_VERSION" \
--data-binary "$1"
}
rpc() {
local request response
request=$(jq -n --arg method "$1" --argjson params "$2" --argjson id "$3" \
'{jsonrpc: "2.0", id: $id, method: $method, params: $params}') || return $?
response=$(post "$request") || return $?
jq -e '
if .error then error("MCP request failed: " + .error.message)
elif .result.isError then error("Agent Memory tool failed: " + (.result.content | tostring))
elif has("result") | not then error("MCP response has no result")
else .result end
' <<< "$response"
}
read_result() {
jq -e '.structuredContent // ([.content[] | select(.type == "text")][0].text | fromjson)'
}
INITIALIZED=$(rpc initialize '{
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {"name": "agent-memory-curl-example", "version": "1.0.0"}
}' 1)
PROTOCOL_VERSION=$(jq -er '.protocolVersion' <<< "$INITIALIZED")
post '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null
rpc tools/list '{}' 2 | jq -r '.tools[].name'
rpc tools/call "$(jq -n --arg id "$MEMORY_ID" --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
name: "create_long_term_memories",
arguments: {idempotency_key: $key, memories: [{
id: $id, text: "The user prefers aisle seats on long flights.",
owner_id: $owner, topics: ["travel"]
}]}
}')" 3 | read_result
rpc tools/call "$(jq -n --arg owner "$OWNER_ID" '{
name: "search_long_term_memory",
arguments: {text: "Which seat does the user like?", owner_id: $owner, limit: 3}
}')" 4 | read_result
rpc tools/call "$(jq -n --arg id "$MEMORY_ID" '{
name: "delete_long_term_memories", arguments: {memory_ids: [$id]}
}')" 5 | read_result
set -euo pipefail
MCP_URL="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID/mcp"
PROTOCOL_VERSION=2025-11-25
OWNER_ID=mcp-quickstart-user
MEMORY_ID="mcp-example-$(openssl rand -hex 6)"
post() {
curl --fail-with-body --silent --show-error "$MCP_URL" \
-H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "MCP-Protocol-Version: $PROTOCOL_VERSION" \
--data-binary "$1"
}
rpc() {
local request response
request=$(jq -n --arg method "$1" --argjson params "$2" --argjson id "$3" \
'{jsonrpc: "2.0", id: $id, method: $method, params: $params}') || return $?
response=$(post "$request") || return $?
jq -e '
if .error then error("MCP request failed: " + .error.message)
elif .result.isError then error("Agent Memory tool failed: " + (.result.content | tostring))
elif has("result") | not then error("MCP response has no result")
else .result end
' <<< "$response"
}
read_result() {
jq -e '.structuredContent // ([.content[] | select(.type == "text")][0].text | fromjson)'
}
INITIALIZED=$(rpc initialize '{
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {"name": "agent-memory-curl-example", "version": "1.0.0"}
}' 1)
PROTOCOL_VERSION=$(jq -er '.protocolVersion' <<< "$INITIALIZED")
post '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null
rpc tools/list '{}' 2 | jq -r '.tools[].name'
rpc tools/call "$(jq -n --arg id "$MEMORY_ID" --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
name: "create_long_term_memories",
arguments: {idempotency_key: $key, memories: [{
id: $id, text: "The user prefers aisle seats on long flights.",
owner_id: $owner, topics: ["travel"]
}]}
}')" 3 | read_result
rpc tools/call "$(jq -n --arg owner "$OWNER_ID" '{
name: "search_long_term_memory",
arguments: {text: "Which seat does the user like?", owner_id: $owner, limit: 3}
}')" 4 | read_result
rpc tools/call "$(jq -n --arg id "$MEMORY_ID" '{
name: "delete_long_term_memories", arguments: {memory_ids: [$id]}
}')" 5 | read_result
Expect the tool names, followed by the created memory ID, a search result that contains the memory, and the deleted memory ID. A request or tool error stops the program with an error.
Troubleshoot the connection
- If the connection fails, check the full endpoint, network reachability, and API key. A
403means the key lacks write access to the store. - If a tool result has
isErrorset, read its content before using the result. Connection and transport failures raise separately. - If a search returns nothing for a new memory, check
owner_idand other filters. Memories created by extraction appear only after background processing.
Keep diagnostic details without logging keys or private content.