Content
# GraphMem: Graph-Based Memory MCP Server
GraphMem is a Ruby on Rails application implementing a Model Context Protocol (MCP) server for graph-based memory management. It enables AI assistants and other clients to create, retrieve, search, and manage knowledge entities and their relationships through a standardized interface.
[](lib/graph_mem/version.rb)
[](Gemfile)
[](Gemfile)
## Overview
GraphMem provides persistent, structured storage for knowledge entities, their relationships, and observations. It's designed as an MCP server that enables AI assistants to maintain memory across sessions, build domain-specific knowledge graphs, and effectively reference past interactions.
### Single-User Design
GraphMem is designed as a **single-user, local-network server**. There is no authentication layer -- the server trusts all incoming requests.
**Per-agent project context:** Multiple MCP clients can connect concurrently without clobbering each other's scope. Each agent identifies itself with an `X-MCP-Client` header in its MCP configuration:
```json
{
"mcpServers": {
"graph_mem": {
"url": "http://localhost:3030/mcp/sse",
"headers": { "X-MCP-Client": "Agent-1" }
}
}
}
```
The example above assumes using the container setup, which is hardcoded to port 3030. (APP_PORT is currently hardcoded in Dockerfile and docker-compose.yml)
Context set via `set_context` is stored per `client_id` in the database and survives server restarts. Agents without the header share the `"default"` client bucket (backward-compatible single-agent behavior).
Authentication and multi-tenant isolation are out of scope for now; the header is a cooperative scope key, not a security boundary.
**Key capabilities:**
- **Vector semantic search** via MariaDB 11.8 native VECTOR support + Ollama embeddings
- **Project context scoping** -- per-agent via `X-MCP-Client`, persisted across restarts
- **Entity type canonicalization** to prevent graph fragmentation
- **Auto-deduplication** on entity creation
- **Hybrid search** combining text tokenization with vector similarity (with context boosting)
- **Docker Compose** deployment with auto-start support
- **LAN-wide embedding** architecture using a centralized Ollama host
## Technology Stack
* **Ruby**: 3.4.1+
* **Rails**: 8.1.2+
* **MCP Implementation**: [fast-mcp](https://github.com/yjacquin/fast-mcp) gem
* **Database**: MariaDB 11.8+ (VECTOR support required)
* **Embeddings**: Ollama with nomic-embed-text (768 dimensions)
## Features
### Rules & Skills
GraphMem includes installable user rules and a skill inside this repo:
- rules: docs/global_and_knowledge_graph_management_rules.md
- skill: .cursor/skills/graph-mem-mcp-toolset/SKILL.md
### MCP Tools
GraphMem exposes the following MCP tools:
#### Entity Management
* `create_entity` -- Create new entities with auto-dedup check
* `get_entity` -- Retrieve entities by ID with observations and relations
* `update_entity` -- Modify entity name, type, aliases, description
* `delete_entity` -- Remove entities and all associated data
* `search_entities` -- Hybrid text + vector semantic search with relevance ranking
* `list_entities` -- Paginated listing of all entities
#### Observation Management
* `create_observation` -- Add observations to existing entities
* `delete_observation` -- Remove observations
#### Relationship Management
* `create_relation` -- Create typed relationships between entities
* `delete_relation` -- Remove relationships
* `find_relations` -- Find relationships by entity or type
* `get_subgraph_by_ids` -- Get connected subgraph of specified entities
* `search_subgraph` -- Search across entities and observations with pagination
#### Context and Workflow
* `set_context` -- Scope subsequent operations to a project (per `X-MCP-Client`)
* `get_context` -- Check the active project context
* `clear_context` -- Remove project scoping
* `bulk_update` -- Batch create entities, observations, and relations atomically
* `suggest_merges` -- Find potential duplicate entities via vector similarity
* `merge_entities` -- Merge a source entity into a target (transfers observations and relations)
* `dream_state_status` -- Report background graph compaction state (running/paused/cursor)
* `get_maintenance_reports` -- Read maintenance/compaction reports, including the `compaction_review` queue
#### Utility
* `get_version` -- Server version
* `get_current_time` -- Server time in ISO 8601
### Dream-State Compaction
A background **dream-state** job (`DreamStateCompactionJob`, scheduled via Solid Queue `recurring.yml`) periodically compacts the knowledge graph:
- **Orphan phase** -- attaches high-confidence orphan nodes to matching `Project` roots
- **Tree-walk phase** -- deduplicates identical observations and auto-merges very similar entities (cosine distance < 0.10)
- **Review queue** -- lower-confidence merges and orphan matches are written to `maintenance_reports` (`compaction_review`), readable via `get_maintenance_reports`
The job is **cooperatively pausable**: mutating MCP tools request a pause when compaction is running, so live tool traffic takes priority. Use `dream_state_status` to inspect cursor position and stats, and `get_maintenance_reports` to review and action the queued suggestions. Paused runs resume on the next scheduled trigger.
### MCP Resources
* `memory_entities` -- Query entities with filtering, sorting, and relation inclusion
* `memory_observations` -- Access observations with advanced filtering
* `memory_relations` -- Query relationships with bidirectional entity inclusion
* `memory_graph` -- Graph traversals starting from any entity
### REST API
Full REST API at `/api/v1` for direct integration. Swagger docs available at `/api-docs`.
### Graph Visualization
Interactive Cytoscape.js-based graph visualization at the server root (`/`), with contextual menus, drag-and-drop operations, and data management features.
## Quick Start with Docker
**Prerequisites:** Ollama must be running on the host with at least one embedding model pulled:
```bash
# Install Ollama (https://ollama.com) then pull the default embedding model
ollama pull nomic-embed-text
```
> The `app` container uses `network_mode: host`, so it shares the host's network
> stack. `localhost:11434` reaches Ollama with no bridge/firewall configuration needed.
> The app binds directly to host port 3030.
```bash
# Clone and enter the project
git clone https://github.com/steveoro/graph_mem.git
cd graph_mem
# Copy example config and set your master key
cp .env.example .env
# Edit .env: set RAILS_MASTER_KEY (from config/master.key) and DB_PASSWORD
# Start the stack (MariaDB 11.8 + Rails in production mode)
docker compose up -d
# Seed canonical entity types
docker compose exec app bin/rails db:seed
# Verify Ollama connectivity
docker compose exec app bin/rails embeddings:check
# Backfill embeddings
docker compose exec app bin/rails embeddings:backfill
# Update the container after a repository pull
docker compose down && docker compose up -d --build
```
The app is available at `http://localhost:3030`. Swagger API docs at `http://localhost:3030/api-docs`.
The app port (3030) is hardcoded on Dockerfile and docker-compose.yml because the service relies on host networking to access the embedding service by `ollama`. This allows a simpler container setup on different machines without resorting to iptables or firewall mangling.
This containerized app is a **single-user server** with no authentication layer -- it is
designed to run locally on a machine and/or be accessible only through a trusted LAN.
**Do not expose this service to the public internet.**
## LAN sharing
To allow a local Ubuntu server running `graph_mem` in a container with `ollama` running as a service for embedding processing, remember to allow incoming trafic if you're using `ufw` (assuming your local LAN is set on 192.168.0.0/24):
```bash
sudo ufw allow from 192.168.0.0/24 to any port 3030 proto tcp comment "GraphMem from LAN"
sudo ufw allow from 192.168.0.0/24 to any port 11434 proto tcp comment "Ollama from LAN"
```
This way, the graph_mem UI will be accessible on `http://<graph_mem_server_ip>:3030/` while the MCP server will be at `http://<graph_mem_server_ip>:3030/mcp/sse`.
## Native Development Setup
For local development, run the app natively with MariaDB on localhost.
1. **Prerequisites:**
* Ruby 3.4.1+ (via RVM or rbenv)
* MariaDB 11.8+ (for vector search)
* Ollama with an embedding model
2. **Install dependencies:**
```bash
bundle install
```
3. **Database setup:**
```bash
cp config/database.example.yml config/database.yml
# Edit config/database.yml with your MariaDB credentials
bin/rails db:prepare
bin/rails db:seed
```
4. **Pull an embedding model:**
```bash
ollama pull nomic-embed-text
```
5. **Backfill embeddings:**
```bash
bin/rails embeddings:backfill
```
6. **Start the development server:**
```bash
bin/dev
```
## Setting Up the MCP Client
### Cursor
Edit your Cursor's `mcp.json`:
**Option A -- SSE transport (recommended with Docker):**
```json
{
"mcpServers": {
"graph_mem": {
"url": "http://localhost:3030/mcp/sse"
}
}
}
```
**Option B -- stdio transport (native development / real-time changes applied):**
```json
{
"mcpServers": {
"graph_mem": {
"command": "/bin/bash",
"args": ["/absolute/path/to/graph_mem/bin/mcp_graph_mem_runner.sh"],
"env": { "RAILS_ENV": "development" }
}
}
}
```
**Option C -- stdio via Docker:**
```json
{
"mcpServers": {
"graph_mem": {
"command": "/bin/bash",
"args": ["/absolute/path/to/graph_mem/bin/docker-mcp"]
}
}
}
```
### Windsurf
Edit your Windsurf's `mcp_config.json` using the same approach as Cursor's. Usually the SSE container doesn't yield any issue.
### Antigravity
SSE doesn't work with Antigravity — Antigravity uses the newer Streamable HTTP transport (POST/DELETE directly to the serverUrl), while the current FastMCP gem (v1.6.0) only supports the older SSE dual-endpoint protocol (GET /sse + POST /messages). But stdio works fine.
Assuming `graph_mem-app-1` is the running container name, edit `mcp_servers.json`:
```json
{
"mcpServers": {
"graph_mem": {
"command": "/usr/bin/docker",
"args": [
"exec",
"-i",
"graph_mem-app-1",
"bash",
"-c",
"'bin/bundle exec ruby bin/mcp_stdio_runner.rb'"
// For development, change env below and replace the command with bash, first arg "-c" and second arg:
// "cd /home/steve/Projects/graph_mem && exec /usr/share/rvm/wrappers/ruby-3.4.1@graph_mem/bundle exec ruby bin/mcp_stdio_runner.rb"
],
"env": {
"RAILS_ENV": "production"
}
}
}
}
```
### Claude Code
Typically both stdio and SSE should work as all 3 options highlighted above should.
Edit your Claude's `mcp_servers.json` with the one of your choosing.
### LAN Access (Multiple Machines)
See [Architecture](docs/architecture.md) for details.
Mind that the current version of GraphMem is specifically designed for single-user usage only, meaning 1 AI user per installation: currently there's no session storage per conversation, so multiple AI agents connecting and using the same running GraphMem instance may overwrite each other's work (one could reset the current context of another preventing the "context valve" to work as expected, thus leading to context bloat).
But nothing prevents you to share the same generated memory graph among different workstations, provided GraphMem will be used by a single AI user at a time.
Or, more realistically, deploy GraphMem on a performant server and access it your usual workstation.
So, when running GraphMem on a local server and accessing it from other machines on the LAN:
**1. Expose Ollama on the runner host**
By default Ollama only listens on `127.0.0.1`. Create a systemd drop-in override
(survives Ollama package upgrades):
```bash
sudo mkdir -p /etc/systemd/system/ollama.service.d
echo '[Service]
Environment="OLLAMA_HOST=0.0.0.0"' | sudo tee /etc/systemd/system/ollama.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart ollama
```
Verify it's bound to all interfaces:
```bash
ss -tlnp | grep 11434
# Should show *:11434 instead of 127.0.0.1:11434
```
**2. Configure OLLAMA_URL**
On the host running GraphMem, `OLLAMA_URL=http://localhost:11434` (the default) works
because the `app` container uses host networking.
If Ollama runs on an another *different* machine, set in `.env`:
```
OLLAMA_URL=http://<ollama-host-ip>:11434
```
**3. Connect MCP clients from other LAN machines**
Point them to the workstation's SSE endpoint:
```json
{ "url": "http://<workstation-ip>:3030/mcp/sse" }
```
## Embedding Management
### Operator dashboard
Sign in at `/operator/login`, then open **Embeddings** from the home dashboard or go to `/operator/embeddings`. The page shows coverage, index status, resolved configuration (with source badges: AppSettings / ENV / Default), connection test, backfill/regenerate jobs, and add/drop ANN index actions.
Embedding service settings live under **System Settings → Embeddings** (`/operator/settings?tab=embeddings`). Configuration resolves in this order:
| Priority | Source |
|---|---|
| 1 | AppSettings (operator UI) — blank string or `0` dims defers to ENV |
| 2 | Environment variables (`OLLAMA_URL`, `EMBEDDING_MODEL`, `EMBEDDING_PROVIDER`, `EMBEDDING_DIMS`) |
| 3 | Built-in defaults (`http://localhost:11434`, `nomic-embed-text`, `ollama`, 768) |
ENV variables remain the right choice for Docker and deployment manifests; the UI overrides them when values are set. Enable **scheduled backfill** in settings to run `EmbeddingScheduledBackfillJob` daily (see `config/recurring.yml`).
See [docs/operator/embeddings.md](docs/operator/embeddings.md) for the recommended operator workflow.
### Testing Ollama Connectivity
Before backfilling or regenerating embeddings, verify that the app can reach your Ollama instance:
```bash
# Docker
docker compose exec app bin/rails embeddings:check
# Native
bin/rails embeddings:check
```
This sends a single test embedding through `EmbeddingService` using the resolved configuration (AppSettings → ENV → defaults). It reports the resolved config, response latency, and vector dimensions — the exact same code path used by `backfill` and `regenerate`.
For a lower-level check, `curl` is available inside the production container (host networking
means `localhost` reaches Ollama directly):
```bash
# Verify Ollama is reachable and list available models
docker compose exec app curl -sf http://localhost:11434/api/tags
# Test a raw embedding request
docker compose exec app curl -sf http://localhost:11434/api/embed \
-d '{"model":"nomic-embed-text","input":"hello"}'
```
### Rake Tasks
| Task | Description |
|---|---|
| `embeddings:check` | Smoke-test Ollama connectivity and config |
| `embeddings:backfill` | Generate embeddings for records missing them |
| `embeddings:regenerate` | Recompute all embeddings in-place (e.g. after switching models) |
| `embeddings:add_indexes` | Add `VECTOR INDEX` (HNSW, cosine) after all rows are populated |
| `embeddings:drop_indexes` | Remove indexes and revert columns to nullable |
### Switching Embedding Models
To change the model (e.g. from `nomic-embed-text` to a different one):
1. Pull the new model on the Ollama host: `ollama pull <model-name>`
2. Update the model (and dimensions if different) in **System Settings → Embeddings** or via `EMBEDDING_MODEL` / `EMBEDDING_DIMS` in `.env`
3. Verify connectivity: `bin/rails embeddings:check` or the operator **Test connection** button
4. Recompute all vectors: `bin/rails embeddings:regenerate` or the operator **Regenerate all** action
## Database Backup & Restore
Backups are managed through **System Settings** (`/operator/settings`, session login) and rake tasks. Dumps are timestamped, environment-scoped, and retained according to `backup_keep_max`:
```bash
# Dump current database to <backup_folder>/<YYYYMMDDHHMM>_<env>.sql.bz2
bin/rails db:dump
# List backups for the current environment
bin/rails db:list_backups
# Restore from newest backup, or a specific file
bin/rails db:restore
FILE=202601011200_production.sql.bz2 bin/rails db:restore
```
Scheduled backups run via Solid Queue (`DatabaseBackupJob`) when **Enable scheduled backups** is on in System Settings. Production schedule: 1pm and 5pm GMT (`config/recurring.yml`). Use the **Jobs** dashboard at `/operator/jobs` (same operator session) to inspect queue status.
Sign in at `/operator/login`. Default operator credentials: `operator` / `changeme` (override with `OPERATOR_USERNAME` / `OPERATOR_PASSWORD` or Rails credentials under `operator:`).
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `OLLAMA_URL` | `http://localhost:11434` | Ollama API base URL (overridden by AppSettings `embedding_url` when set) |
| `EMBEDDING_MODEL` | `nomic-embed-text` | Ollama model name for embeddings |
| `EMBEDDING_PROVIDER` | `ollama` | `ollama` or `openai_compatible` |
| `EMBEDDING_DIMS` | `768` | Vector dimensions (must match model) |
| `DB_PASSWORD` | `my_password` | MariaDB root password |
| `DB_NAME` | `graph_mem` | Database name |
| `DB_PORT` | `3307` | Host port for MariaDB (Docker) |
| `RAILS_MASTER_KEY` | -- | Rails credentials key (required for Docker) |
| `DATABASE_URL` | -- | Full database URL (overrides individual DB settings) |
| `DB_BACKUP_HOST_PATH` | `./db/backup` | full path to DB backup(s) folder (default is invalid: docker-compose won't expand special characters) |
| `OPERATOR_USERNAME` | `operator` | Operator login username for the web dashboard |
| `OPERATOR_PASSWORD` | `changeme` | Operator login password (change in production) |
## Documentation
* [Operator embeddings guide](docs/operator/embeddings.md)
* [MCP Tools Reference](docs/mcp_tools.md)
* [App Settings Reference](docs/app_settings_reference.md)
* [Architecture](docs/architecture.md)
* [Development Guide](docs/development.md)
* [Troubleshooting](docs/troubleshooting.md)
* [Memory Entity Resource](docs/memory_entity_resource.md)
* [Memory Observation Resource](docs/memory_observation_resource.md)
* [Memory Relation Resource](docs/memory_relation_resource.md)
* [Memory Graph Resource](docs/memory_graph_resource.md)
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate. Pull requests without proper test cases won't be accepted.
## License
The project is available as open source under the terms of the [LGPL-3.0 License](https://opensource.org/licenses/LGPL-3.0).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available