Claude Conversation Memory System
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
[](https://sonarcloud.io/summary/new_code?id=adamkwhite_claude-memory-mcp)
# Universal Memory MCP ā AI Conversation Memory
A Model Context Protocol (MCP) server that provides persistent, searchable conversation memory across multiple AI platforms. Store, search, and retrieve conversation history with fast full-text search powered by SQLite FTS5.
## Features
- š **Fast full-text search** via SQLite FTS5 with relevance ranking ā ~10x faster than a linear scan ([measured](#performance))
- š·ļø **Automatic topic extraction** ā 574+ unique topics across 2,000+ associations
- š **Weekly summaries** with insights and patterns
- šļø **Organized file storage** by date and topic
- š¤ **Multi-platform support** ā Claude, ChatGPT, Cursor AI, and custom formats
- š **MCP integration** for Claude Desktop and Claude Code
## Quick Start
### Prerequisites
- Python 3.10+ (CI runs 3.14)
- An MCP client ā Claude Code, Claude Desktop, Codex, or anything else speaking MCP over stdio
### Installation
```bash
uv tool install universal-memory-mcp # or: pipx install universal-memory-mcp
```
Not `pip install`: this is an application, and on Debian/Ubuntu and other
[PEP 668](https://peps.python.org/pep-0668/) systems installing one into the system interpreter
fails with `error: externally-managed-environment`. Inside a virtualenv you have already
activated, `pip install universal-memory-mcp` is fine.
Then point your client at the `universal-memory-mcp` console script:
```bash
claude mcp add --transport stdio universal-memory-mcp -- universal-memory-mcp
```
Or write it into the config yourself ā Claude Code and Claude Desktop:
```json
{ "mcpServers": { "universal-memory-mcp": { "command": "universal-memory-mcp" } } }
```
Codex (`~/.codex/config.toml`):
```toml
[mcp_servers.universal-memory-mcp]
command = "universal-memory-mcp"
```
The server name is yours to choose, but it sets the tool namespace your client exposes
(`mcp__<name>__*`). Conversations live in `~/claude-memory/` regardless, so renaming is safe.
Upgrading an install that points at a checkout? `scripts/switch_mcp_config.py` rewrites both
config formats in place ā dry run by default, `--apply` to write.
#### From source
```bash
git clone https://github.com/adamkwhite/universal-memory-mcp.git
cd universal-memory-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
python3 tests/validate_system.py # optional: verify the install
```
Point your client at `<checkout>/.venv/bin/python3 -m universal_memory_mcp.server_fastmcp`. The
package uses relative imports, so running the file directly cannot work ā `python3
src/universal_memory_mcp/server_fastmcp.py` fails with `attempted relative import with no known
parent package`.
### Basic Usage
#### MCP Server Mode
Your client starts the server for you; run it by hand only to debug.
```bash
universal-memory-mcp # installed from PyPI
python3 -m universal_memory_mcp.server_fastmcp # from source
```
#### Bulk Import
```bash
# Import conversations from JSON export
python3 scripts/bulk_import_enhanced.py your_conversations.json
```
## MCP Tools
### `search_conversations(query, limit=5)`
Full-text search across all stored conversations with relevance ranking. Query text is treated as literal Unicode terms, so punctuation and FTS5 operators do not change the query semantics. Results include conversation IDs for exact retrieval.
### `get_conversation(conversation_id, max_chars=12000)`
Retrieve a stored conversation by an ID returned from a search tool. Content is read from the authoritative JSON store and truncated to `max_chars` to protect the model context. `max_chars` must be between 1 and 50,000.
### `search_by_topic(topic, limit=10)`
Find conversations tagged with a specific topic.
### `add_conversation(content, title, date)`
Store a new conversation with automatic topic extraction and FTS indexing.
### `generate_weekly_summary(week_offset=0)`
Generate insights and patterns from recent conversations.
### `get_search_stats()`
View search engine statistics ā index size, topic counts, and engine status.
### `update_conversation(conversation_id, content=None, title=None, add_tags=None, remove_tags=None, set_tags=None, conversation_type=None, session_id=None, user_id=None, change_note=None, record_audit=True)`
Update fields on an existing conversation in place. Pass `conversation_id` plus any subset of fields to change; unspecified fields are left alone. By default, the first line of stored content is rewritten with a self-documenting audit line ā `[update <iso-timestamp> ā <change_note>]` ā chained across repeated updates. If `change_note` is omitted, it is derived from the changed fields.
Set `record_audit=False` only for authoritative imports whose content must remain an exact replica of the source system. Normal interactive updates should retain the default audit record.
Tag operations: `set_tags` replaces the full tag list and is mutually exclusive with `add_tags`/`remove_tags` (pass `set_tags=[]` to clear all tags); `add_tags`/`remove_tags` mutate the existing list.
Returns a status string. On success: `Status: success` plus a summary message and, when enabled, the audit line. On failure (malformed ID, conversation not found, no changes provided, conflicting tag ops, or an I/O error): `Status: error` plus a message describing the problem.
### `search_by_tag(tag, limit=10)`
Find conversations tagged with a specific tag ā a universal metadata field populated by importers or set via `update_conversation` (e.g. `starred`, `archived`, `workspace:my-project`). Exact match, case-sensitive. Requires SQLite FTS to be enabled; without it, returns an error message.
### `search_by_session_id(session_id, limit=10)`
Find all conversations sharing a `session_id`, useful for reconstructing a multi-turn session that spans several stored conversation records (e.g. a Cursor working session, a Claude thread continued across days). Results are sorted chronologically (oldest first). Requires SQLite FTS to be enabled; without it, returns an error message.
### `search_by_conversation_type(conversation_type, limit=10)`
Find conversations by `conversation_type` (e.g. `chat`, `code`, `analysis`). Exact match, most recent first. Requires SQLite FTS to be enabled; without it, returns an error message.
## Architecture
```
~/claude-memory/
āāā conversations/
ā āāā 2025/
ā ā āāā 06-june/
ā ā āāā 2025-06-01_topic-name.md
ā āāā index.json # Search index
ā āāā topics.json # Topic frequency
āāā summaries/
āāā weekly/
āāā week-2025-06-01.md
```
## Configuration
### Claude Desktop Integration
Add to your Claude Desktop MCP config:
```json
{
"mcpServers": {
"universal-memory-mcp": {
"command": "universal-memory-mcp"
}
}
}
```
Installed from source rather than PyPI? Point `command` at your virtualenv's interpreter and
run the module:
```json
{
"mcpServers": {
"universal-memory-mcp": {
"command": "/absolute/path/to/universal-memory-mcp/.venv/bin/python3",
"args": ["-m", "universal_memory_mcp.server_fastmcp"]
}
}
}
```
> **Upgrading from before the package move (#225):** configs used to name the server script
> directly (`src/server_fastmcp.py`). That no longer works in any form ā the modules moved
> under `src/universal_memory_mcp/`, and the package now uses relative imports, so running
> the file raises `attempted relative import with no known parent package`. Switch to the
> console script or the `-m` form above.
### Configuration Precedence
Settings are resolved by `src/universal_memory_mcp/config.py`'s `Config.load()`, consulted in this
order (highest wins):
1. **Environment variables** (`CLAUDE_MEMORY_*` / `CLAUDE_MCP_*`)
2. **Config file** (default `~/.claude-memory/config.json`)
3. **Platform profile** (`default`, `claude`, `chatgpt`, or `cursor` ā selects
a partial set of defaults, e.g. `log_format`)
4. **Built-in defaults**
### Environment Variables
| Variable | Purpose | Default |
|---|---|---|
| `CLAUDE_MEMORY_PATH` | Conversation storage directory | `~/claude-memory` |
| `CLAUDE_MEMORY_DISABLE_SQLITE` | Set `true` to disable SQLite FTS and fall back to JSON linear search. Inverse alias of `CLAUDE_MCP_ENABLE_SQLITE`; wins if both are set. | unset (SQLite enabled) |
| `CLAUDE_MCP_LOG_FORMAT` | Log output format: `text` or `json` | `text` |
| `CLAUDE_MCP_LOG_LEVEL` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | `INFO` |
| `CLAUDE_MCP_ENABLE_SQLITE` | Enable/disable SQLite FTS search (boolean: `true`/`false`, `1`/`0`, `yes`/`no`, `on`/`off`) | `true` |
| `CLAUDE_MCP_CONSOLE_OUTPUT` | Echo logs to stdout in addition to the log file (boolean) | `false` |
| `CLAUDE_MCP_PLATFORM_PROFILE` | Platform profile to apply: `default`, `claude`, `chatgpt`, or `cursor` | `default` |
When `CLAUDE_MEMORY_PATH` is set explicitly, the path may live outside your
home directory (e.g. a separate data drive on Windows: `D:\claude-memory`).
Paths that are *not* explicitly configured are still restricted to the home
or project directory for safety.
### Config File
As an alternative to environment variables, settings can be placed in
`~/.claude-memory/config.json`. The file is optional ā a missing file falls
back to platform-profile/built-in defaults. Example:
```json
{
"storage_path": "~/claude-memory",
"log_format": "json",
"log_level": "INFO",
"enable_sqlite": true,
"console_output": false,
"platform_profile": "default"
}
```
Unknown keys in the file raise a configuration error rather than being
silently ignored. Environment variables still override anything set here.
### Disabling SQLite
SQLite FTS5 search is enabled by default. On platforms where SQLite/FTS5 is
unavailable (e.g. some Windows Python builds), disable it to fall back to
JSON-based linear search:
```bash
export CLAUDE_MEMORY_DISABLE_SQLITE=true
```
### Logging Configuration
#### Log Format
Switch between human-readable text logs (default) and structured JSON logs for production:
```bash
# JSON format (for production log aggregation)
export CLAUDE_MCP_LOG_FORMAT=json
# Text format (default, for development)
export CLAUDE_MCP_LOG_FORMAT=text
```
**JSON Log Example:**
```json
{
"timestamp": "2025-01-15T10:30:45",
"level": "INFO",
"logger": "claude_memory_mcp",
"function": "add_conversation",
"line": 145,
"message": "Added conversation successfully",
"context": {
"type": "performance",
"duration_seconds": 0.045,
"conversation_id": "conv_abc123"
}
}
```
JSON logging is ideal for:
- Production deployments with log aggregation (Datadog, ELK, CloudWatch)
- Automated monitoring and alerting
- Structured log analysis and querying
- Performance tracking and debugging
See `docs/json-logging.md` for detailed JSON logging documentation.
## File Structure
```
universal-memory-mcp/
āāā src/
ā āāā server_fastmcp.py # Main MCP server
ā āāā conversation_memory.py # Core memory engine + SQLite FTS5
ā āāā format_detector.py # Auto-detect AI platform format
ā āāā validators.py # Input validation
ā āāā logging_config.py # Structured logging (text/JSON)
ā āāā importers/ # Platform-specific importers
ā ā āāā chatgpt_importer.py
ā ā āāā claude_importer.py
ā ā āāā cursor_importer.py
ā ā āāā generic_importer.py
ā āāā schemas/ # JSON schema validation
āāā tests/ # 435 tests, 98.68% coverage
āāā data/ # Consolidated app data
āāā scripts/ # Import and utility scripts
āāā docs/ # Documentation
```
## Performance
`scripts/benchmark_search.py` was broken (unawaited async calls, measuring
coroutine construction instead of real search time) from October 2025 until
this was found and fixed. The previous numbers below were never actually
measured and have been replaced with real ones. Reproduce with:
```bash
python scripts/generate_test_data.py --conversations 159
python scripts/benchmark_search.py --storage-path ~/claude-memory-test --iterations 5
```
Measured on a 159-conversation / 7.7MB local dataset (WSL2, Python 3.12) ā
treat as order-of-magnitude, not a precise SLA, results vary by machine:
- **Search Speed (SQLite FTS5)**: mean 15ā18ms, median 10ā13ms per query, range 0.5ā82ms across 12 query types (was claimed 0.2ā0.5ms; that figure was never measured)
- **Search vs. linear JSON scan**: SQLite FTS5 is ~10x faster (mean 14.7ms vs 154.2ms; median 10.5ms vs 152.0ms) ā the old "4.4x" claim had the right direction but was also never actually measured
- **Topic Search**: mean 3.4ms, median 2.5ms (was claimed 0.3ā0.4ms; that figure was never measured)
- **Write Speed**: mean 14ms, median 14ms per ~49KB conversation, SQLite indexing included (was claimed ~33ms; that figure was never measured)
- **Capacity**: 371 conversations in production use over 10 months
- **Test Coverage**: 98.68% (435 tests) ā 0 code smells, 0 security hotspots (SonarCloud verified)
*Last benchmarked: July 2026 | [Detailed Report](docs/PERFORMANCE_BENCHMARKS.md)*
**Note for Developers**: Performance benchmarks create a `~/claude-memory-test` directory for isolated testing. Normal MCP usage only uses `~/claude-memory/`. If you see `~/claude-memory-test`, it can be safely deleted.
## Search Examples
```python
# Technical topics
search_conversations("terraform azure")
search_conversations("mcp server setup")
search_conversations("python debugging")
# Project discussions
search_conversations("interview preparation")
search_conversations("product management")
search_conversations("architecture decisions")
# Specific problems
search_conversations("dependency issues")
search_conversations("authentication error")
search_conversations("deployment configuration")
```
## Development
### Adding New Features
1. **Topic Extraction**: Modify `_extract_topics()` in `ConversationMemoryServer`
2. **Search Algorithm**: Enhance `search_conversations()` method
3. **Summary Generation**: Improve `generate_weekly_summary()` logic
### Testing
```bash
# Run validation suite
python3 tests/validate_system.py
# Run full test suite with coverage
python3 -m pytest tests/ --cov=src --cov-report=term
# Import test data
python3 scripts/bulk_import_enhanced.py test_data.json --dry-run
```
**Test Data Storage (Developers Only)**: If you run performance benchmarks or test data generators, they create a `~/claude-memory-test` directory to isolate test data from your production `~/claude-memory` directory. **This is only for development/testing** - normal MCP usage does not create this directory.
To clean up test data after running benchmarks:
```bash
rm -rf ~/claude-memory-test
```
Or using the Makefile cleanup target:
```bash
make clean-test-data
```
## Troubleshooting
### Common Issues
**MCP Import Errors:** the `mcp` dependency comes with the package, so this normally means the
server is running under an interpreter that does not have it. Check which one your MCP config
invokes: the `universal-memory-mcp` console script from `uv tool`/`pipx`, or your virtualenv's
`python3 -m universal_memory_mcp.server_fastmcp` ā not a bare system `python3`.
**Search Returns No Results:**
- Check conversation indexing: `ls ~/claude-memory/conversations/index.json`
- Verify file permissions
- Run validation: `python3 tests/validate_system.py`
**Weekly Summary Timezone Errors:**
- Ensure all datetime objects use consistent timezone handling
- Recent fix addresses timezone-aware vs naive comparison
### System Requirements
- **Python**: 3.10+ (CI runs 3.14)
- **Disk Space**: ~10MB per 100 conversations
- **Memory**: <100MB RAM usage
- **OS**: Linux/WSL and Windows are both verified in CI on every PR (Ubuntu + `windows-latest`).
macOS is expected to work but is not covered by a CI runner.
## Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature-name`
3. Commit changes: `git commit -am 'Add feature'`
4. Push to branch: `git push origin feature-name`
5. Submit a Pull Request
**A note for fork PRs:** GitHub does not give forks access to repository secrets, so the
SonarCloud scan and the performance-results comment are **skipped** on your PR rather than run.
That is expected and is not something you can or should fix ā the test suite, linting, CodeQL and
the Windows run all still execute normally, and coverage on your changes is checked when the
branch lands on `main`. If you see those two skipped, nothing is wrong.
## Releasing
Publishing is tag-gated and uses **Trusted Publishing** (OIDC) ā there is no PyPI token stored in
this repo. `.github/workflows/publish.yml` fires only on a `vX.Y.Z` tag.
One-time setup on PyPI (publisher settings for the project, or a *pending* publisher while the
name is still unclaimed):
| field | value |
|---|---|
| Owner | `adamkwhite` |
| Repository | `universal-memory-mcp` |
| Workflow | `publish.yml` |
| Environment | `pypi` |
To cut a release:
```bash
# 1. bump `version` in pyproject.toml, commit, merge to main
# 2. tag the merged commit ā the workflow refuses a tag that disagrees with pyproject
git tag v0.1.0 && git push origin v0.1.0
```
The workflow builds, runs `twine check`, installs the wheel into a clean venv and asserts that
every module imports and that no generic top-level name leaked, then publishes. Add required
reviewers to the `pypi` environment in repo settings for a manual approval gate as well.
**Rehearse on TestPyPI before the first real upload** ā the first upload claims the name
permanently, and a version number can never be reused:
```bash
rm -rf dist && uv build
uv run --with twine --no-project twine upload --repository testpypi dist/*
# TestPyPI does not mirror mcp/jsonschema/aiofiles, so pull deps from real PyPI:
uv pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ universal-memory-mcp
```
## License
MIT License - see LICENSE file for details
## Acknowledgments
- Built with [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/python-sdk)
- Designed for [Claude Desktop](https://claude.ai/desktop) integration
- Inspired by the need for persistent conversation context
---
**Status**: Production ready ā
**Last Updated**: April 2026
**Version**: 2.0.0
TDQS
Scored across 10 tools
Most tools target distinct resources or actions: add/get/update are clear CRUD operations, and the metadata-specific searches (by_tag, by_session_id, by_conversation_type) are well-scoped. However, search_conversations and search_by_topic overlap conceptually, and the distinction between generic content search and topic search is not sharply defined.
The naming is predominantly verb_noun snake_case, with add_conversation, get_conversation, update_conversation, and generate_weekly_summary following a clear pattern. The search tools are slightly inconsistent because some use search_conversations while others use search_by_tag/search_by_session_id/search_by_conversation_type, but the style is still recognizable and predictable.
Ten tools is a reasonable size for a conversation memory system, and the CRUD core plus search variants cover most expected workflows. The count is slightly search-heavy, with six retrieval-related tools, but none feel redundant enough to remove outright.
The system covers add, get, update, search, summary, and stats, which handles the core lifecycle for stored conversations. Notable gaps include no delete/forget operation, no list-all conversations tool, and no dedicated search_by_user_id even though user_id is described as a universal metadata field.