Skip to content

MCP-1: Add an MCP server layer for agentic-memory #55

Description

@Atharva-Kanherkar

MCP-1: Add an MCP server layer for agentic-memory

Summary

Add a Python MCP server that exposes agentic-memory as persistent memory infrastructure for external agents such as Claude Code, Cursor, Windsurf, and custom MCP clients.

The MCP layer should be a thin wrapper only:

  • validate tool inputs
  • call core memory services
  • return structured outputs that help the host LLM decide the next action

All memory behavior stays in the core Python modules. The MCP server must not import or call any generative LLM SDK. The only model dependency remains Gemini Embedding 2 for embeddings.

Repo Reality Check

The current repo mostly matches the intended architecture, with a few important corrections:

  • procedural memory is already implemented in code, even though the README still marks parts of it as planned
  • semantic contradiction detection and supersession resolution already exist in forgetting/contradiction.py
  • procedural outcome tracking and Wilson-score ranking already exist in stores/procedural_store.py
  • there is no cognitive_memory/ package today; the business logic lives in top-level modules such as stores/, retrieval/, forgetting/, models/, and utils/
  • PDF support exists explicitly via embed_pdf() and multimodal records
  • event emission is broader than the initial brief: the code already emits memory.accessed, memory.ranked, memory.supersession_resolved, memory.faded, and forgetting-cycle events
  • MemoryAPIService.__init__() currently mutates config.CHROMA_DB_PATH during runtime construction; that is not thread-safe and must be removed as part of the shared-runtime extraction

Why This Matters

Today the project has:

  • a core memory engine
  • a FastAPI wrapper
  • a Next.js playground

What it does not yet have is the agent-facing protocol boundary that lets any MCP-capable host reuse the memory system directly without going through the web app.

That MCP boundary is the product surface for long-lived agent memory.

Internet-Researched Constraints

1. Prefer FastMCP from the official Python SDK

The official mcp Python SDK currently recommends:

  • FastMCP for most server implementations
  • Streamable HTTP as the preferred production transport
  • stdio as the simplest local transport for IDE and CLI integrations

Implication:

  • implement the server with FastMCP
  • support both stdio and streamable-http
  • default the README examples to stdio for Claude Code / Cursor / Windsurf local setups

2. Do not rely on MCP resources for primary memory retrieval

Multiple public MCP issues show that some hosts list resources correctly but do not reliably read them when answering questions.

Implication:

  • make all primary memory operations tools, not resources
  • resources can be added later for optional media access, but recall must not depend on them

3. Keep the tool surface tight

Current host constraints matter:

  • Claude Code warns when MCP output exceeds 10,000 tokens
  • Windsurf limits available MCP tools and also caps tool calls per prompt

Implication:

  • keep the tool inventory small and high-signal
  • prefer store-specific write tools and a small number of retrieval/admin tools
  • bound result counts aggressively

4. Avoid rich binary output contracts in v1

Current MCP Python SDK and client issues show rough edges around:

  • binary/base64 content handling
  • resource encoding mismatches
  • schema generation around image output types

Implication:

  • accept media as local file paths, with base64 input as a fallback
  • return structured JSON plus durable media_ref paths
  • do not make raw binary/image tool outputs part of the critical path

5. Similar memory servers already hit config and race-condition problems

Public MCP memory-server issues include:

  • path/env handling bugs
  • write failures under concurrent usage
  • search normalization bugs

Implication:

  • validate all storage paths at startup
  • add a core-level run lock for forgetting cycles before exposing mutating admin tools
  • keep write paths single-owner and deterministic

Main Problems To Solve Before or During MCP Work

P0. The core service container is trapped inside api/app.py

MemoryAPIService currently lives inside the FastAPI module. The MCP server should not depend on FastAPI wiring.

Required change:

  • extract a shared core service container used by both the API and MCP entrypoints
  • stop mutating config.CHROMA_DB_PATH during runtime construction
  • pass chroma_path directly into each store or its PersistentClient constructor instead of routing through module-level global config
  • make embedding dimensions runtime-injectable instead of relying only on module-level config so future reindex migrations can stand up a parallel runtime cleanly

P1. Superseded semantic memories can still leak into normal retrieval

The current retriever fans out across stores but does not exclude semantic records that have been superseded.

Required change:

  • add an active-record filter in SemanticStore so the store contract itself does not surface superseded facts by default

P2. Contradiction detection failures are currently swallowed in the API

_safe_contradiction_lookup() catches broad exceptions and silently returns [].

Required change:

  • MCP write responses must expose contradiction-check status explicitly as completed, skipped, or error
  • a failure to run contradiction detection must not be indistinguishable from “no contradictions found”

P3. Forgetting-cycle execution is not concurrency-guarded

The current forgetting service has no visible single-run guard.

Required change:

  • add a core-level asyncio.Lock and return already_running for overlapping requests

P4. Manual single-record forgetting is not yet safe to expose

Low-level store delete() methods do not provide one coordinated path for:

  • record deletion
  • owned media cleanup
  • event emission
  • store-type resolution

Implication:

  • do not expose a generic forget_memory MCP tool in v1
  • expose forgetting-cycle preview/run first
  • add a core admin delete service later if needed

Proposed MCP Tool Surface

Write tools

  • remember_fact
  • remember_episode
  • remember_procedure
  • resolve_contradiction
  • record_procedure_outcome

Recall tools

  • recall_memories
  • recall_procedures
  • recall_episodes
  • get_memory

Admin and observability tools

  • get_memory_overview
  • preview_forgetting_cycle
  • run_forgetting_cycle

Notably absent in v1:

  • raw embedding tools
  • raw Chroma collection tools
  • EventBus subscription tools
  • generic delete/forget-by-id tool
  • resource-dependent primary recall flow

Tool-shape clarifications:

  • get_memory should require both memory_type and record_id rather than probing all three stores
  • recall_procedures should call ProceduralStore.get_best_procedure_matches() directly, not UnifiedRetriever.query()
  • recall_episodes should cover recent, session, and time_range modes in one tool rather than splitting temporal recall into additional tools

Child Issues

Delivery Plan

MCP-1A: Extract a shared core runtime for API and MCP

Owns:

  • new shared service-container module
  • API import cleanup so api/app.py becomes an adapter, not the owner of runtime composition
  • removal of the thread-unsafe config.CHROMA_DB_PATH mutation during runtime construction
  • moving EventRecorder into the shared runtime if it remains part of the common observability surface
  • injecting embedding dimensions through runtime construction so a migration can run 768-dim and 1536-dim runtimes side by side against different Chroma paths

Acceptance criteria:

  • FastAPI and MCP entrypoints construct the same stores, retriever, contradiction detector, forgetting service, and media store from one shared runtime factory
  • runtime construction never mutates module-level config state to swap Chroma paths
  • chroma_path is injected directly into store/client construction, so concurrent runtime creation cannot observe another runtime's database path
  • embedding dimensions can be supplied at runtime rather than only through module-level config, enabling parallel re-embedding into a fresh database path during future dimension upgrades

MCP-1B: Harden the core for MCP-safe behavior

Owns:

  • active-only semantic retrieval filtering
  • explicit contradiction-check status reporting
  • forgetting-cycle lock / single-flight guard
  • consistent exception mapping hooks
  • event emission when ProceduralStore.record_outcome() updates a procedure
  • removal of duplicate ContradictionDetector construction paths so the runtime owns one shared detector instance

Acceptance criteria:

  • a superseded semantic record is not returned by default recall
  • contradiction lookup failure is visible in the write response
  • a second forgetting-cycle request receives already_running instead of overlapping execution
  • procedural outcome recording emits an event that observability surfaces can show
  • the forgetting guard uses asyncio.Lock rather than an ad hoc sync primitive in the async MCP runtime

MCP-1C: Define MCP schemas and serializers

Owns:

  • typed request/response models for all MCP tools
  • shared success/error envelope
  • serialization of memory records, procedural matches, contradiction candidates, and forgetting reports
  • get_memory(memory_type, record_id) routing in the request model
  • explicit store-first-then-contradiction-check ordering in remember_fact
  • configurable truncation for large forgetting reports

Acceptance criteria:

  • all tools return machine-readable structured output
  • errors are stable and typed
  • responses stay below practical host token limits for common calls
  • forgetting preview/run responses cap decisions to a configurable maximum and report when output was truncated
  • remember_fact documentation makes it explicit that contradiction detection runs only after the new semantic record has been stored

MCP-1D: Implement the MCP transport layer

Owns:

  • FastMCP server
  • one runtime entrypoint that selects stdio or streamable-http
  • startup validation and health exposure

Acceptance criteria:

  • local clients can run the server over stdio
  • remote/self-hosted clients can connect over /mcp via streamable HTTP
  • the server can switch transport via a single CLI/env-controlled startup path, for example MEMORY_MCP_TRANSPORT=stdio|streamable-http

MCP-1E: Add multimodal input handling for MCP calls

Owns:

  • file-path input support
  • base64 input fallback
  • temporary-file handling for inline payloads
  • size limits and validation errors
  • explicit validation that local file paths are readable from the server filesystem, not merely meaningful to the client

Acceptance criteria:

  • screenshot, audio, and PDF-backed memories can be stored via MCP without the FastAPI upload endpoints
  • text recall can find media-backed memories
  • remote deployments fail clearly when a caller provides a local path that is not accessible from the MCP server host

MCP-1F: Document client setup and integration behavior

Owns:

  • README setup for Claude Code, Cursor, and Windsurf
  • env var reference
  • startup modes
  • deployment notes for local-path media behavior when the server is remote
  • tested mcp package version and pinning guidance
  • embedding-dimension migration guidance so users understand that changing vector size requires a re-embedding cutover, not an in-place live switch
  • troubleshooting section for common MCP client issues

Acceptance criteria:

  • a user can install dependencies, start the server, and connect one of the three supported hosts without guessing config structure

Defensive Engineering Rules

  • Use structured output for every tool. Do not rely on free-form strings.
  • Keep tool descriptions short and trigger-oriented so hosts call the right tool without orchestration glue.
  • Prefer local file paths for media input. Base64 is fallback-only.
  • Bound all list-style results with explicit defaults and max values.
  • Exclude superseded semantic memories from default recall.
  • Never silently swallow contradiction-check failures.
  • Return typed retryable metadata on dependency failures.
  • Keep the MCP layer free of business logic. If logic is missing, add it in core first.
  • reject or loudly warn on relative storage paths in non-local deployments so the server does not accidentally point at a different Chroma database because of working-directory drift
  • treat embedding-dimension changes as reindex migrations: never point an existing Chroma collection at a new vector size and hope it works

Packaging Plan

Add a real Python package boundary so users can run only the MCP server if they do not need the API or web UI.

Recommended shape:

  • add pyproject.toml
  • add a console script such as agentic-memory-mcp
  • make MCP dependencies installable as an extra
  • keep API dependencies installable separately
  • pin the tested mcp version explicitly instead of floating across point releases

Suggested dependency split:

  • core dependencies in [project.dependencies]: chromadb, google-genai, numpy, python-dotenv
  • API extra in [project.optional-dependencies].api: fastapi, uvicorn, python-multipart
  • MCP extra in [project.optional-dependencies].mcp: mcp[cli]

Recommended runtime/config additions:

  • allow EMBEDDING_DIMENSIONS to be overridden via env and runtime constructor parameters
  • keep the default at 768 for now
  • treat 1536 as a supported migration target, not a hot-swappable live toggle on existing collections

Future Migration Note: 768 to 1536

gemini-embedding-2-preview supports both 768-dimensional and 1536-dimensional embeddings. Moving from 768 to 1536 is a valid production migration, but it is a one-time re-embedding and reindexing operation because Chroma collection dimensions are fixed once data has been written.

Guidance:

  • do not change embedding size in place on an existing Chroma path
  • stand up a new runtime with the target embedding size and a fresh Chroma path
  • read records from the old collections
  • re-embed all records at the new size
  • write them into new collections
  • verify retrieval quality and record counts
  • switch traffic to the new runtime/path
  • retire the old collections after validation

When this work happens, MCP-1A should already have made the necessary runtime changes:

  • chroma_path injected directly instead of via global config mutation
  • embedding dimensions injected at runtime
  • parallel runtimes possible during migration without restart-coupled global state

Operational recommendation:

  • keep 768 as the default unless retrieval quality justifies the migration cost
  • support 1536 cleanly once the runtime/config injection refactor lands

Acceptance Surface

The MCP server is complete when:

  • an MCP host can store semantic, episodic, and procedural memories
  • contradiction candidates are returned on semantic writes and can be resolved explicitly
  • procedural recall uses similarity plus Wilson-score ranking
  • procedural outcomes can be recorded and affect future ranking
  • text recall can retrieve media-backed episodic and semantic memories
  • forgetting cycles can be previewed and run safely
  • the server runs without the FastAPI app
  • setup docs exist for Claude Code, Cursor, and Windsurf

Architecture

flowchart LR
    Host[Claude Code / Cursor / Windsurf / Custom MCP Client]
    MCP[FastMCP Server]
    Runtime[Shared Memory Runtime]
    RET[UnifiedRetriever]
    CD[ContradictionDetector]
    FGT[ForgettingService]
    SS[SemanticStore]
    ES[EpisodicStore]
    PS[ProceduralStore]
    MS[MediaStore]
    EMB[GeminiEmbedder]
    CH[(ChromaDB)]
    BUS[EventBus]

    Host --> MCP
    MCP --> Runtime
    Runtime --> RET
    Runtime --> CD
    Runtime --> FGT
    Runtime --> SS
    Runtime --> ES
    Runtime --> PS
    Runtime --> MS
    SS --> EMB
    ES --> EMB
    PS --> EMB
    SS --> CH
    ES --> CH
    PS --> CH
    SS -. emit .-> BUS
    ES -. emit .-> BUS
    PS -. emit .-> BUS
    RET -. emit .-> BUS
    FGT -. emit .-> BUS
Loading

Sources

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions