Skip to main content
Glama
1318176013

project-memory-mcp

by 1318176013
README.md
# project-memory-mcp

A TypeScript MCP server for project memory: it stores manual project memories, retrieves them semantically, and tracks per-memory confidence. Metadata and vectors both live in PostgreSQL (via the pgvector extension), and embeddings come from an OpenAI-compatible API.

## Architecture

- MCP server exposes tools over stdio.
- PostgreSQL stores projects, memories, confidence feedback, and memory embeddings (pgvector `vector` column). Semantic search is exact cosine KNN in SQL — at this server's scale (memories are project knowledge, not a code index) a dedicated vector DB added a second store to keep consistent for no retrieval benefit.
- Embeddings are generated by a configurable OpenAI-compatible `/v1/embeddings` API.
- PostgreSQL is an external service. This server only reads its address from config, and requires the pgvector extension (the migrations run `CREATE EXTENSION IF NOT EXISTS vector`).

## Tools

These are the tools exposed over MCP (stdio and the HTTP `/mcp` endpoint). The server stores and retrieves manual project memories only; it never reads project source files. Clients contribute knowledge through `add_memory`.

Setup and health:

- `help`: start here. Returns a guide to call order, tool arguments, how `projectId` works, and the scaffold (files + contents) to wire a project into the auto-memory workflow. Takes no arguments.
- `health_check`: check PostgreSQL and embedding API connectivity.
- `register_project`: register a project and obtain a server-generated `projectId` used to scope all other tools.

Retrieval:

- `search_knowledge`: semantic search across project memories.

Memory management:

- `add_memory`: add manual knowledge and index it.
- `list_memories`: list memories with optional archived/kind/tag filters.
- `update_memory`: update a memory and refresh its embedding.
- `archive_memory`: archive a memory so it no longer appears in search results.

Confidence:

- `record_confidence_feedback`: submit trust feedback for a memory.
- `get_confidence`: read computed confidence for a memory.
- `list_confidence_feedback`: list feedback records for audit.
- `list_suspect_confidence`: list suspect or rejected memories.

## Configuration

Service config controls infrastructure:

```json
{
  "database": {
    "urlEnv": "DATABASE_URL"
  },
  "embedding": {
    "provider": "openai-compatible",
    "baseUrl": "https://api.example.com/v1",
    "apiKeyEnv": "EMBEDDING_API_KEY",
    "model": "embedding-model-name",
    "dimensions": 1536,
    "batchSize": 64
  }
}
```

Project config identifies the project:

```json
{
  "projectId": "my-project",
  "name": "My Project"
}
```

`projectId` scopes all retrieval, memory, and confidence data. `name` is optional and used only for display. An optional `root` may be supplied for informational display in the capability manifest, but the server never reads project files — retrieval isolation is keyed solely on `projectId`.

## Local Dependencies

PostgreSQL (with pgvector) is started separately. For local development only, this repo includes a commented `docker-compose.yml` that uses the `pgvector/pgvector:pg16` image:

```bash
docker compose up -d postgres
```

Then set:

```bash
cp .env.example .env
```

Fill `EMBEDDING_API_KEY` and make sure `DATABASE_URL` points at your PostgreSQL service.

## Install And Build

```bash
pnpm install
pnpm build
```

## CLI

```bash
project-memory migrate \
  --service-config ./examples/project-memory.service.json \
  --project-config ./examples/project-memory.config.json

project-memory search "where is authentication handled?" \
  --service-config ./examples/project-memory.service.json \
  --project-config ./examples/project-memory.config.json

project-memory add-memory \
  --title "Use API embeddings" \
  --content "The first version uses an OpenAI-compatible embedding API instead of self-hosted embeddings." \
  --kind decision

project-memory feedback \
  --target-id mem_xxx \
  --signal contradict \
  --rationale "This advice conflicts with the current project convention."

project-memory confidence --target-id mem_xxx
```

Other CLI commands:

```bash
project-memory list-memories [--include-archived] [--kind <k>] [--tag <t>] [--limit <n>]
project-memory update-memory --id mem_xxx [--title ...] [--content ...] [--kind ...] [--tags a,b] [--source ...]
project-memory archive-memory --id mem_xxx
project-memory list-feedback [--target-id mem_xxx] [--limit <n>]
project-memory list-suspect
```

Confidence starts at `0.7`. Feedback signals are `support`, `confirm`, `dispute`, `contradict`, `obsolete`, and `uncertain`: `support`/`confirm` raise confidence, while `dispute`/`contradict`/`obsolete`/`uncertain` lower it. Very low confidence records are marked `suspect` or `rejected`; rejected memories are excluded from retrieval, and suspect memories are heavily down-ranked.

## MCP Client Example

There are two ways to connect, depending on where the server runs.

### Local (stdio)

Use this when the server binary lives on the same machine as the MCP client, which spawns it as a child process over stdio:

```json
{
  "mcpServers": {
    "project-memory": {
      "command": "node",
      "args": ["/path/to/project-memory-mcp/dist/index.js"],
      "env": {
        "PROJECT_MEMORY_SERVICE_CONFIG": "/path/to/project-memory.service.json",
        "PROJECT_MEMORY_PROJECT_CONFIG": "/path/to/project-memory.config.json",
        "DATABASE_URL": "postgres://project_memory:project_memory@localhost:5432/project_memory",
        "EMBEDDING_API_KEY": "your-api-key"
      }
    }
  }
}
```

### Remote (HTTP)

When the server runs elsewhere — for example in a Docker container on another host — the client cannot spawn it over stdio. Connect over the Streamable HTTP `/mcp` endpoint instead. Both Claude Code and Codex support remote HTTP MCP servers natively; no stdio bridge is required.

The `/mcp` endpoint does not require the bearer token, so MCP clients connect without an `Authorization` header. The token only guards the read-only REST endpoints (see [HTTP Endpoint](#http-endpoint)).

**Claude Code** — add via the CLI (user scope is the most reliable for remote servers):

```bash
claude mcp add --transport http --scope user project-memory \
  https://your-host:8788/mcp
```

Or in `~/.claude.json`:

```json
{
  "mcpServers": {
    "project-memory": {
      "type": "http",
      "url": "https://your-host:8788/mcp"
    }
  }
}
```

**Codex** — in `~/.codex/config.toml`:

```toml
[mcp_servers.project-memory]
url = "https://your-host:8788/mcp"
```

After connecting, call `register_project` once to obtain a `projectId`, then pass that `projectId` to `search_knowledge`, `add_memory`, and the other project-scoped tools.

## HTTP Endpoint

The stdio MCP entry remains the default for local MCP clients. A network entry is also available for agents that can connect over HTTP:

```bash
pnpm start:http
```

Defaults:

```text
http://127.0.0.1:8788
```

Useful endpoints:

```text
GET  /                      Capability manifest: what this MCP is and what it can do.
GET  /help                  Usage guide: call order, tool arguments, and the auto-memory scaffold.
GET  /health                PostgreSQL and embedding API health.
GET  /status                Dependency health for the configured project.
GET  /tools                 Tool names and descriptions.
GET  /memories              List memories (query: includeArchived, kind, tag, limit, projectId).
GET  /confidence/suspect    List suspect or rejected memories.
GET  /confidence/feedback   List confidence feedback (query: targetId, limit, projectId).
POST /mcp                   MCP Streamable HTTP endpoint.
GET  /mcp                   MCP Streamable HTTP SSE stream when supported by the client.
```

Configure the bind address with:

```text
PROJECT_MEMORY_HTTP_HOST=127.0.0.1
PROJECT_MEMORY_HTTP_PORT=8788
PROJECT_MEMORY_HTTP_TOKEN=change-me
```

When `PROJECT_MEMORY_HTTP_TOKEN` is set, the read-only REST endpoints require:

```text
Authorization: Bearer <token>
```

Two endpoints are exempt from the token:

- `/health` — so external health checks work without credentials.
- `/mcp` — MCP Streamable HTTP clients (Claude Code, Codex) do not reliably forward custom headers on every request during the initialize/session handshake, so the MCP endpoint is reachable without the token. This is intended for the trusted deployment this server targets; do not expose `/mcp` to untrusted networks.

Example:

```bash
curl http://127.0.0.1:8788/ \
  -H "Authorization: Bearer $PROJECT_MEMORY_HTTP_TOKEN"
```

## Docker Deployment

The deployment image runs only the Project Memory MCP HTTP server. PostgreSQL (with pgvector) and the embedding API remain external services.

Build:

```bash
docker build -t project-memory-mcp:local .
```

Run:

```bash
docker run --rm -p 8788:8788 \
  -e PROJECT_MEMORY_HTTP_TOKEN="change-me" \
  -e DATABASE_URL="postgres://user:password@postgres-host:5432/project_memory" \
  -e EMBEDDING_BASE_URL="https://api.example.com/v1" \
  -e EMBEDDING_MODEL="text-embedding-3-small" \
  -e EMBEDDING_DIMENSIONS="1536" \
  -e EMBEDDING_BATCH_SIZE="64" \
  -e EMBEDDING_API_KEY="your-key" \
  project-memory-mcp:local
```

Or use the deployment compose file:

```bash
PROJECT_MEMORY_HTTP_TOKEN="change-me" \
DATABASE_URL="postgres://user:password@postgres-host:5432/project_memory" \
EMBEDDING_BASE_URL="https://api.example.com/v1" \
EMBEDDING_MODEL="text-embedding-3-small" \
EMBEDDING_DIMENSIONS="1536" \
EMBEDDING_API_KEY="your-key" \
docker compose -f docker-compose.deploy.yml up -d
```

Health check:

```bash
curl http://127.0.0.1:8788/health
```

Capability manifest:

```bash
curl http://127.0.0.1:8788/ \
  -H "Authorization: Bearer change-me"
```

## Operations

For production deployments, see [docs/OPERATIONS.md](docs/OPERATIONS.md). It covers:

- **Fresh deployment** — empty PostgreSQL → running server, including how schema migrations run automatically at startup.
- **Backup / restore** — all state is in PostgreSQL; a `pg_dump` is a complete backup (the pgvector `vector` column included).
- **Re-embedding** — changing `embedding.model` or `dimensions` invalidates every stored vector; the doc gives the re-embed procedure.
- **Connection pool & timeout tuning** — the PostgreSQL pool and the embedding HTTP request are both bounded so a slow dependency cannot hang requests. Defaults are conservative; override via env:

```text
DATABASE_POOL_MAX=10                      # max pooled PostgreSQL connections
DATABASE_POOL_IDLE_TIMEOUT_MS=30000       # idle client eviction
DATABASE_POOL_CONNECTION_TIMEOUT_MS=10000 # fail fast if no connection acquired
EMBEDDING_REQUEST_TIMEOUT_MS=30000        # abort a hung embedding API call
```

These may also be set under `database.pool` and `embedding.requestTimeoutMs` in the service config.

## Notes

This server is intentionally memory-centric: it stores manual project memories, retrieves them semantically, and tracks per-memory confidence from agent feedback.

Two earlier subsystems were removed once they proved to be write-only dead weight. PostgreSQL graph `nodes`/`edges` were only ever written, never read. A code/document indexing pipeline (scan → chunk → embed → store) was also removed: the server runs remotely and never has access to project source files, local agents read source directly, and `search_knowledge` only ever read memory vectors — never the chunk vectors the pipeline produced. Removing both kept the runtime lean and the responsibility singular.

Vector storage was then consolidated out of Qdrant into PostgreSQL via pgvector. Once code indexing was gone, the vector store held only memory vectors — a few hundred per project, filtered by `projectId` before any comparison — where exact cosine KNN in SQL is fast and accurate. The dedicated vector DB only added a second store to keep consistent (the dual-write orphan-handling that `add`/`update`/`archive` carried) for no retrieval benefit at that scale. If a large-scale code index ever returns, a dedicated vector DB may be worth reintroducing; at the current scope it is not.