RecallMCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@RecallMCPremember that the client prefers weekly status updates"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
RecallMCP
Persistent semantic memory as an MCP tool for AI agents.
CI badge: Once pushed to GitHub, replace the
CIbadge above with:https://github.com/<owner>/<repo>/actions/workflows/ci.yml/badge.svg
RecallMCP gives AI agents a persistent, searchable memory they can write to and query at any time — across sessions, across conversations, across namespaces. It is an MCP server that exposes five tools (remember, recall, list_memories, update_memory, forget) backed by Postgres + pgvector for semantic similarity search.
Memories are partitioned per user, isolated by row-level security, and stored as content + embedding + optional structured metadata. Deduplication happens automatically on identical content within the same namespace.
Tools
remember
Store a memory with semantic embedding.
Input:
Field | Type | Required | Description |
| string | Yes | Content to remember (1–50,000 characters) |
| string | No | Namespace grouping (default: |
| object | No | Arbitrary key/value metadata |
Output:
{ "id": "550e8400-e29b-41d4-a716-446655440000" }Returns an error with deduped: true if identical content already exists in the same namespace (or you can check the returned id — it matches the existing memory's id).
Example:
{
"content": "The user prefers Python for data analysis and prefers FastAPI over Flask for web services.",
"namespace": "preferences",
"metadata": { "source": "conversation", "confidence": 0.95 }
}Response:
{ "id": "550e8400-e29b-41d4-a716-446655440000" }Calling remember again with identical content in the same namespace returns the existing memory's id without creating a duplicate.
recall
Retrieve memories semantically similar to a query, ranked by cosine similarity.
Input:
Field | Type | Required | Description |
| string | Yes | Search query (1–10,000 characters) |
| string | No | Filter by namespace (default: |
| number | No | Max results (1–50, default: 10) |
| number | No | Minimum similarity 0.0–1.0 (default: 0.7) |
| object | No | Key/value filter on metadata (AND semantics, max 8 keys, primitive values only) |
Output:
Array of matching memories, ordered by decreasing similarity. Each result includes <memory> tag wrapping with HTML-escaped content for prompt-injection defense.
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "The user prefers Python for data analysis and prefers FastAPI over Flask for web services.",
"similarity": 0.92,
"metadata": { "source": "conversation", "confidence": 0.95 },
"namespace": "preferences",
"created_at": "2026-04-29T12:00:00.000Z"
}
]Example — recall with namespace and metadata filter:
{
"query": "What are the user's web framework preferences?",
"namespace": "preferences",
"threshold": 0.8,
"metadata_filter": { "source": "conversation" }
}Results are formatted as <memory> tags internally so the calling agent receives escaped, structured output that can't be broken by injected content (e.g., a memory containing </memory> is safely escaped).
list_memories
List stored memories for a user, with pagination and namespace filtering.
Input:
Field | Type | Required | Description |
| string | No | Filter by namespace (default: |
| number | No | Max results (1–100, default: 20) |
| number | No | Pagination offset (default: 0) |
| string | No | Sort column — |
Output:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "The user prefers Python for data analysis.",
"namespace": "preferences",
"created_at": "2026-04-29T12:00:00.000Z",
"updated_at": "2026-04-29T12:00:00.000Z"
}
]update_memory
Update a memory's content and/or metadata. Changes trigger re-embedding of new content.
Input:
Field | Type | Required | Description |
| string (UUID) | Yes | Memory ID to update |
| string | No | New content (re-embedded) |
| object | No | New metadata (replaces existing) |
At least one of content or metadata must be provided.
Output:
{ "success": true }forget
Delete memories. Supports two modes:
Mode by_id — delete a single memory by ID
{ "mode": "by_id", "id": "550e8400-e29b-41d4-a716-446655440000" }Output:
{ "success": true }Mode by_query — two-step semantic deletion
This mode requires two calls to prevent accidental bulk deletion:
Step 1 — Preview: Call forget with mode: "by_query" and a search query. Returns matching memories and a confirmation_token.
{
"mode": "by_query",
"query": "web framework preferences",
"namespace": "preferences",
"threshold": 0.85,
"limit": 10
}Response:
{
"preview": true,
"matches": [
{ "id": "550e8400-e29b-41d4-a716-446655440000", "content": "The user prefers...", "similarity": 0.91 }
],
"total_matches": 1,
"confirmation_token": "a1b2c3d4e5f6...",
"expires_at": "2026-04-29T12:05:00.000Z"
}Step 2 — Confirm: Call forget again with the same mode: "by_query" and the confirmation_token.
{
"mode": "by_query",
"confirmation_token": "a1b2c3d4e5f6..."
}Response:
{ "success": true, "deleted_count": 1 }Confirmation tokens expire after 5 minutes and are scoped to the requesting user (cross-user token reuse is rejected).
Related MCP server: Synapto
Authentication & API Keys
RecallMCP uses bearer-token authentication. API keys follow a recall_live_ prefix followed by a 32-character random suffix (URL-safe base64).
Authorization: Bearer recall_live_<32-char-suffix>Keys map to a user account and a tier that governs rate limits:
Tier | Rate Limit | Max Memories |
Free | 10 requests/min | 100 |
Starter | 60 requests/min | Unlimited |
Pro | 60 requests/min | Unlimited |
Team | 60 requests/min | Unlimited |
Rate limits are enforced per API key using a token-bucket algorithm. Paid tier quotas are currently uniform pending billing-granularity tuning.
Note: API key self-service issuance and management endpoints are in development (planned for a future release). Keys are currently provisioned manually.
Rate Limiting
Every API call is rate-limited by token-bucket per API key. The bucket is lazily refilled — idle keys don't accumulate beyond capacity. When a key exceeds its rate:
HTTP 429 with a
Retry-Afterheader (seconds until the bucket refills enough for another request)A
rate_limitedusage event is recorded withtokens_consumed: 0Denied requests never reach the tool handler
The rate limiter uses an InMemoryRateLimiter by default. The RateLimiter interface supports swapping to a distributed implementation (e.g., Redis) for multi-instance deployments.
Usage Tracking
Every tool invocation that passes authentication produces exactly one row in usage_events:
Column | Description |
| Owner of the API key that made the call |
| Specific API key used |
| Correlates with structured logs |
| Which tool was called |
| 1 for normal calls, 0 for rate-limited |
| Wall-clock time of the handler |
| Whether the tool call completed without error |
| String identifier on failure ( |
The insert is fire-and-forget — failures are logged as warnings but never surfaced to the client. Usage events are RLS-protected: users can only see their own events.
Self-Hosting Guide
A more complete deployment guide ships in a later release — this section covers the basics for local and small-scale self-hosting.
Prerequisites
Node.js 20.x (exact version required — see
.nvmrc)PostgreSQL 15+ with pgvector extension
An OpenAI API key (for embeddings; the server refuses to start without one in production)
Environment Variables
Variable | Required | Description |
| Yes | Postgres connection string with pgvector support |
| Yes | OpenAI API key for text embeddings |
| No | HTTP port (default: 8080) |
| No |
|
| No | Log level: |
| No | HMAC secret for MCPize billing webhook |
| No | Stripe secret key (webhooks disabled if absent) |
| No | Stripe webhook signing secret |
| No | JSON mapping: price IDs → tiers, e.g. |
Run Migrations
Apply migrations in order — each is a standalone SQL file under supabase/migrations/:
# Using Supabase CLI (recommended for managed Postgres):
supabase db push --db-url "$DATABASE_URL"
# Or apply manually with psql:
for f in supabase/migrations/*.sql; do
psql "$DATABASE_URL" -f "$f"
doneMigration history:
File | Description |
| Base schema: users, api_keys, memories (with pgvector), usage_events, RLS policies, update trigger |
| Forces RLS on all tables, creates the |
| Adds GIN index on |
| Adds |
| Replaces the initial usage_events table with a richer schema (request_id, tool_name, latency, tokens_consumed, error_code) |
Start the Server
# Install dependencies
npm install
# Build TypeScript
npm run build
# Start (HTTP mode on port 8080)
npm startFor development with hot reload:
npm run devThe server exposes two HTTP endpoints:
GET /health— health checkGET /ready— readiness check (DB connected)POST /mcp— MCP endpoint (Streamable HTTP transport)
Local Development
git clone https://github.com/<your-org>/recall-mcp
cd recall-mcp
npm install
# Set up local Postgres with pgvector, then:
cp .env.example .env
# Edit .env with your DATABASE_URL and OPENAI_API_KEY
# Run migrations
for f in supabase/migrations/*.sql; do
psql "$DATABASE_URL" -f "$f"
done
# Start in dev mode
npm run dev
# Run tests (requires Docker for testcontainers-based Postgres):
npm testThe test suite spins up isolated Postgres + pgvector containers via Testcontainers, applies all migrations, and runs 255 integration and unit tests covering every tool, RLS isolation, rate limiting, usage events, and cross-user security boundaries.
Architecture Overview
┌──────────────┐ POST /mcp ┌──────────────────────────────────────┐
│ MCP Client │ ──────────────────> │ Fastify Server │
│ (AI Agent) │ <────────────────── │ (Streamable HTTP Transport) │
└──────────────┘ JSON-RPC 2.0 └──────┬───────────────────────────────┘
│
▼
┌─────────────────────────┐
│ Auth Middleware │
│ (Bearer API Key → │
│ userId + tier) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Rate Limiter │
│ (token-bucket per key) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Usage Event Recorder │
│ (fire-and-forget) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Tool Dispatcher │
│ ┌───────┬──────┬────────┬──────┬────────┐ │
│ │remember│recall│list_mem│update│forget │ │
│ └───┬────┴──┬───┴───┬────┴──┬───┴───┬────┘ │
└──────┼──────┼───────┼───────┼───────┼────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────┐
│ Database Client (pg pool) │
│ with RLS user context │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────┐
│ PostgreSQL + pgvector│
│ ┌──────────────────┐ │
│ │ users │ │
│ │ api_keys │ │
│ │ memories (vec) │ │
│ │ usage_events │ │
│ └──────────────────┘ │
│ Row-Level Security │
└──────────────────────┘Key design points:
Auth is HTTP-only — API keys are sent as Bearer tokens, never exposed in MCP tool arguments
Auth context flows via AsyncLocalStorage — middleware stores
{ userId, tier, apiKeyId }in a request-scoped context; tools read it transparently without explicit parameter passingRLS is the security boundary — every database query is wrapped in
SET LOCAL app.current_user_id; Postgres enforces isolation at the row level. Even the database owner cannot bypass policies (FORCE ROW LEVEL SECURITY)Rate limiter uses a pivot-resistant interface — swap from in-memory to Redis by implementing two methods (
check()andlastDecisionMeta())Usage events are fire-and-forget — never blocks the response; failures are logged but never surfaced
Logging & Observability
RecallMCP uses pino for structured JSON logging:
Production: JSON output (pipe through
pino-prettyfor local readability withNODE_ENV=development)Request correlation: Every request has a
request_idthat appears in both structured logs and theusage_events.request_idcolumn, enabling cross-system joinabilitySensitive data redaction: Memory content, query strings, and embedding vectors are SHA-256 hashed (8-char truncated) or replaced with
[redacted]in logsTool-level wrapping: All five tools are wrapped by
handleToolWithLogging, which logs entry, exit (withelapsed_ms), and errors (witherror_codeand redacted details)
Migration History
# | File | What it does |
1 |
| Base schema: users, api_keys, memories (with |
2 |
| Forces RLS on memories and api_keys (table owner cannot bypass), creates |
3 |
| GIN index ( |
4 |
| Adds |
5 |
| Replaces the initial usage_events table with a richer schema: adds |
Status & Roadmap
Done — production hardening complete:
✅ All five MCP tools with Zod validation
✅ Row-level security with FORCE (Zero-trust multi-tenancy)
✅ Semantic search with metadata filtering
✅ Content normalization & deduplication
✅ Two-step semantic deletion (preview → confirm)
✅ Auth middleware (API key → user + tier, 1-hour LRU cache)
✅ Per-API-key token-bucket rate limiting
✅ Structured logging with request correlation and redaction
✅ Usage event tracking (foundation for billing)
✅ 255-test integration+unit suite (Testcontainers)
✅ API key self-service issuance and management endpoints (R12)
✅ Stripe webhook integration for tier sync (R13)
✅ Docker and deployment guide (R14–15)
✅ MCP Registry manifest & npm publish prep (R16)
In development:
🔄 Public user dashboard (usage stats, key management)
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
Persistent memory for AI agents — log and recall conversation context over MCP.
Persistent memory for AI agents across Claude, ChatGPT and any MCP client.
- KogniteOAuthdev.kognite
Hosted agent memory: store, search, and recall facts across sessions from any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceGives AI agents persistent memory with semantic search, automatic extraction, and memory decay, accessible via MCP protocol.7 npmMIT
- AlicenseNot gradedqualityAmaintenanceProvides persistent, searchable memory for MCP-compatible agents, enabling recall by meaning, automatic decay, trust scoring, and cross-agent handoffs.86 PyPI5MIT
- AlicenseAqualityDmaintenanceProvides persistent memory with semantic search for MCP-based AI agents, enabling them to store and recall information across sessions using vector embeddings.41MIT
- AlicenseCqualityBmaintenancePersistent semantic memory for MCP-compatible agents, enabling them to remember and recall text, audio, and documents across sessions.1069 npmMIT