A memory your assistants keep between conversations.
- id
- 9d3a1f0e-7c2b-4e5a-9a8f-2b6c1d4e7f10
- title
- Postgres pool size for the shared PgBouncer
- content
- Keep DB_POOL_MAX at 2. The shared PgBouncer allows two backend connections for this service; raising it drops connections under load.
- type
- decision
- tags
- postgrespgbouncerdecision
- importance
- 80 / 100
- created
- 2026-09-10 14:02 UTC
- index
- 1 chunk · 1,536-dim vector
memory_search “how many db connections can we use?” → this record, score 0.81Three ways in, one URL.
Every client gets the same endpoint. The first use opens a sign-in page; after that, the assistant calls the tools on its own.
- 1Open Settings → Apps & Connectors → Advanced settings and turn on Developer mode.
- 2Back in Apps & Connectors choose Create. Name it Memory, paste the endpoint as the MCP server URL, and pick OAuth for authentication.
- 3In a chat open the + menu, then More, and switch Memory on. ChatGPT sends you to the sign-in page the first time.
- 4Say something worth keeping: “Remember that…”. ChatGPT calls
memory_store; later questions callmemory_search.
- 1Open Settings → Connectors and choose Add custom connector. On Team and Enterprise an owner adds it for everyone.
- 2Name it Memory, paste the endpoint as the remote MCP server URL, and add it.
- 3Choose Connect and finish the sign-in. In a chat, open the search-and-tools menu and turn Memory on.
- 4Ask Claude to remember or recall things. It picks the right tool and asks you before deleting anything.
claude mcp add --transport http memory \
https://mcp.datahashira.com/mcp
claude # then run /mcp and pick Memory to sign inClients that read a JSON config, such as Cursor or Windsurf, take the same URL:
{ "mcpServers": {
"memory": { "url": "https://mcp.datahashira.com/mcp" }
} }The connector publishes its OAuth metadata at /.well-known/oauth-protected-resource/mcp, so a compliant client discovers the sign-in server on its own.
Once per client, with your username and password.
Your client registers itself, sends you to the connector's own sign-in page, and receives a short-lived token. Google or per-user accounts can be added later without any client changes.
memory:read lets the assistant search and fetch. memory:write lets it store, update and delete.What your assistant can do with it.
Read tools need memory:read; write tools need memory:write. Green parameters are required; the rest show their defaults.
Find memories by meaning and by keywords, narrowed by namespace, tag, type, source or date. Returns nothing rather than a guess when confidence is low.
Hybrid ranking: vector similarity plus trigram keyword match, one result per memory.
Save a fact, decision, preference, note, snippet or document. Long content is split and indexed automatically; identical content returns the existing ID instead of a duplicate.
Up to 100 KB of text per memory. Returns the stable memory ID.
Fetch one complete memory by its stable ID, usually after a search or list has identified it.
Returns the full canonical text, metadata, tags and timestamps.
Browse the newest memories with the same filters as search, without a query and without touching the embedding model.
Newest first.
Change the title, content, type, tags, importance, source or metadata while keeping the same ID. Changed text is re-indexed automatically.
expected_updated_at refuses the write if someone else changed the memory first.
The one fetch at the start of a conversation. Returns the operating instructions, counts per namespace, and the full text of the most important, topic-related and recent memories within a character budget; with lesson it adds exactly one pending lesson and leaves the others out.
Whole memories that don't fit the budget are listed under omitted. Compact excerpts are always included as an index.
Remove a memory so it disappears from every read and search immediately. Runs only with confirm=true, which the assistant sets after you say yes.
Soft delete: an administrator can still recover the record from the database.
Turns a namespace's material into ordered, self-contained lessons using the backend planning model: weak items first, plus a few new items in each lesson. Every lesson is stored as a memory with a number and a status. From then on memory_briefing with lesson: "next" serves one lesson per conversation.
Pending lessons block a new plan unless replace_existing is set. Once every lesson is done, run it again: the finished lessons are archived and the next set starts from the updated material.
Applies a session export JSON written at the end of a chat that had no tool access: stores new memories, updates existing ones by id (a lesson by namespace and lesson_number), and deletes listed ids only with confirm_deletes=true after you say yes.
Up to 100 stores, 100 updates and 50 deletes per call; every item reports stored, duplicate, updated, deleted, not found, skipped or error.
Structured study for Japanese and Vietnamese.
Seven tools turn every answer into data: one track per language, items with their own mastery and review schedule per skill, timed sessions and the full answer history. FSRS spaces long-term reviews; a short-term queue brings every miss back three times, spaced apart, before it counts as learned. Japanese is ja with JLPT N5–N1 levels, Vietnamese is vi.
The one fetch at the start of a language session. Creates the track on first use (Japanese and Vietnamese start from preset preferences, and earlier lesson logs keep their numbers), opens or resumes today's session, and returns the teaching protocol, today's lesson, weak items, due reviews, new items and your long-form notes.
Settings passed here update the track. A session left open for 12 hours is closed as abandoned at its last answer; nothing is lost.
Records a batch of answers in order and updates mastery, the practice queue and the FSRS review schedule in one transaction. A wrong, prompted or hesitant answer puts the item back in the queue.
Retrying the same batch records nothing twice. Answers are never edited or deleted.
Creates or updates the next lesson from current mastery: weak and overdue items first, then a few new ones (fewer while many are weak). Or pass your own items.
Numbering continues from earlier lessons; completed lessons are never rewritten.
Closes the session with its actual study time and a summary, and marks the lesson complete when every item was taught.
Finishing twice changes nothing.
Adds or updates vocabulary, kana, kanji, grammar patterns and phrases with readings, meanings, JLPT levels and examples.
Keyed by <item_type>:<written form>. Optional progress seeds a starting state for what you learned before tracking; it never overwrites real history.
What to practise now: weak items that still owe spaced successes first, then items overdue on the review schedule, most overdue first.
Each skill has its own schedule: you can read a kanji well and still be due to write it.
Mastery by item type and skill, lessons, JLPT N5–N1 coverage for Japanese, and minutes, answers and accuracy for the last 7 days and 4 weeks.
JLPT targets are commonly cited estimates; JLPT publishes no official lists.
Plain requests, the right tool.
You never name a tool. Copy a prompt, paste it into a chat with Memory switched on, and the assistant picks the tool for you.
“Remember that our deploy window is Tuesdays at 10:00 IST.”
“What did we decide about the Postgres pool size?”
“Show my latest notes tagged onboarding.”
“Make that memory importance 90 and add the tag infra.”
“Forget the note about the old API key.”
“Start my Japanese session.”
“Plan my next Vietnamese lesson.”
“Give me the session JSON.”
“Import this session JSON: { … }”
“What should I review in Vietnamese right now?”
“How did my Japanese go this week? Am I on track for N5?”
“Add 日, 月 and 火 as N5 kanji with their readings.”
One session per conversation, in four prompts.
A session is fetched once at the start of a conversation and written once at the end; nothing is called in between, so a voice chat works exactly like a text chat. Vietnamese is the worked example; for Japanese, swap `vi` for `ja`.
Call learning_plan with language_code "vi" and show me the lesson's items and their roles. If it has fewer than five new items, suggest new words for my level, add them with learning_manage_items, and plan again.Start my Vietnamese session: call learning_start with language_code "vi". That is the only tool call in this conversation; follow its instructions field throughout.
Teach today's lesson, or free practice if there is none: weak items first, then due reviews, then a few new words. One word -> meaning -> pronunciation -> sentence -> drill -> recombination, Northern/Hanoi accent. Keep a private log of every answer: item, skill, grade, correct, prompted, hesitant. When every lesson item is done, say "Lesson N is complete".Give me the session JSON: one code block, version 1, with a "learning" block for language_code "vi" holding every new item, every answer in order (item_key, skill, direction, grade 1-4, is_correct, was_prompted, was_hesitant, my response when it was wrong) and "finish" with active_minutes and lesson_completed. Add a "store" of type "session_log" for today and an "update" of my baseline memory with only the big-picture changes. Nothing else inside the block.Import this session JSON with memory_import (no deletions unless I confirm), then show my progress with learning_get_progress:
<paste the block>learning_record_attempts every 20 answers or so, then learning_finish_session). Any other subject still works with memory-based lessons: memory_curriculum plans them and memory_briefing with lesson: "next" serves one per conversation.How a memory is kept.
Canonical text is stored once in PostgreSQL under your account and a namespace, so work can stay apart from personal, or one project from another.
Long entries are split by headings and paragraphs. Each piece gets a 1,536-dimension embedding in pgvector. Search combines vector similarity with trigram keyword matching, applies your filters, then collapses the results to one per memory. Anything under a confidence score of 0.35 is dropped rather than returned as a guess.
Identical content is caught by hash and returns the existing ID. Deleting is soft: the record leaves every read and search at once, but nothing is destroyed without an administrator. Vectors are derived data and can be rebuilt from the text at any time.
The service is stateless between calls, runs behind HTTPS only, and filters every query by both tenant and user. No tool accepts a user or tenant ID as an argument; both come from the verified token.
Language study lives in its own tables: one track per language with items, per-skill progress, lessons, timed sessions and an append-only answer history. The database itself keeps every row inside its track, so Japanese work never touches Vietnamese progress, and each review state stores its FSRS version so mastery can be recalculated from the raw answers.
| Text per memory | 100 KB |
| Metadata per memory | 20 KB JSON |
| Tags per memory | 50 |
| Search results | ≤ 25 |
| List page | ≤ 100 |
| Minimum search score | 0.35 |
| Requests per client address | 120 / min |
| Request body | 750 KB |
| Answers per batch | 500 |
| Items per call | 200 |
| Embedding model | text-embedding-3-small |
Endpoints and the wire contract.
A stateless MCP Streamable HTTP server. Every POST carries its own bearer token; there are no session IDs to keep.
| Route | Purpose | Auth |
|---|---|---|
POST /mcp | MCP JSON-RPC over Streamable HTTP. GET and DELETE return 405. | Bearer token |
GET /.well-known/oauth-protected-resource/mcp | Protected-resource metadata: resource URL, authorization server, scopes. | None |
GET /health/live | Process liveness. | None |
GET /health/ready | Readiness; checks the database connection. | None |
/authorize, /token, /register, /revoke | Built-in OAuth 2.1 authorization server: dynamic client registration, PKCE S256, refresh-token rotation. Metadata at /.well-known/oauth-authorization-server, keys at /.well-known/jwks.json. | Per endpoint |
GET /docs | The connector's built-in docs page. | None |
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
resource_metadata="https://mcp.datahashira.com/.well-known/oauth-protected-resource/mcp",
scope="memory:read memory:write"ES256, issued by the connector's own authorization server, with aud equal to https://mcp.datahashira.com/mcp, a stable sub, and the scopes above in scope or permissions. A call with a valid token but a missing scope returns an insufficient_scope challenge inside the tool result, so the client can ask for more access rather than fail silently.
Source, deployment notes and the full tool schemas live in the GitHub repository.
Everything the owner needs to do.
The service runs on ECS in ap-south-1 behind the shared nginx. Secrets live in SSM under /mcp-memory/; the deploy key in .env.production can read and write them.
Follow the ChatGPT or Claude steps above with https://mcp.datahashira.com/mcp. Sign in with the account stored in SSM (MCP_LOGIN_USERNAME and MCP_LOGIN_PASSWORD). Nothing else needs configuring on the client side.
Registers a throwaway client, opens the sign-in page, exchanges the code with PKCE and calls the live endpoint expecting sixteen tools.
npm run check:oauthWrite the new value, then restart the task so it is read again; about a minute of downtime.
aws ssm put-parameter --name /mcp-memory/MCP_LOGIN_PASSWORD --type SecureString --overwrite --value 'NEW-PASSWORD'
aws ecs update-service --cluster production-cluster --service mcp-memory --force-new-deploymentEvery access token stops working within an hour; clients refresh on their own.
openssl ecparam -name prime256v1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt > key.pem
aws ssm put-parameter --name /mcp-memory/OAUTH_SIGNING_KEY --type SecureString --overwrite --value "$(cat key.pem)" && rm key.pem
aws ecs update-service --cluster production-cluster --service mcp-memory --force-new-deploymentStill pending: the current one was shared in chat.
aws ssm put-parameter --name /mcp-memory/OPENAI_API_KEY --type SecureString --overwrite --value 'sk-proj-...'
aws ecs update-service --cluster production-cluster --service mcp-memory --force-new-deploymentMerge to main. GitHub Actions runs the checks, builds, pushes, runs the Prisma migrations, rolls the service and streams the CloudWatch log in the run until the rollout is stable. Redeploy without a change from the Actions tab (Run workflow). apps/connector/deploy/deploy.sh is the local fallback.
gh run watch # or open the Actions tabEdit apps/connector/prisma/schema.prisma, print the SQL, review it (qualify public.vector; write HNSW indexes and CHECK constraints by hand) and save it as prisma/migrations/<timestamp>_<name>/migration.sql. The deploy applies it; npm run db:check -w @mcp/connector reports drift.
cd apps/connector && npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --scriptTail the service log, or ask the readiness probe directly.
aws logs tail /ecs/mcp-memory --follow
curl https://mcp.datahashira.com/health/readyReplace the single-account check and the login page in apps/connector/src/modules/oauth/provider.ts; clients and the protocol layer stay as they are.
ap-south-1, cluster production-cluster, service mcp-memory, host port 4001. nginx and TLS are owned by DevOps; ask them for any change there.