MCP
The memory tools over the Model Context Protocol, at one URL, with the same key and end-user header as the HTTP routes.
POST /v1/mcp/v1/mcp speaks MCP over Streamable HTTP. Each request is one JSON-RPC 2.0 message, and each answer is plain
JSON; it never opens an SSE stream. No session is minted, so no Mcp-Session-Id is sent or needed, and a
request needs nothing from the last. GET and DELETE /v1/mcp answer 405. To connect a client such as Claude
Code or Cursor, see MCP; this page is the wire format.
The end user is fixed by the X-Twin-Account header. No tool takes an account argument, so a model using these
tools cannot reach another end user's memory.
Headers
AuthorizationstringBearer tm_…. A read key lists the read tools. A read and write key also lists memory_add.
X-Twin-AccountstringThe end user whose memory the tools read. See End users.
Content-Typestringapplication/json.
MCP-Protocol-VersionstringOne of the versions below. Absent, it is read as 2024-11-05, the oldest served.
Methods
initializemethodAnswers with the server's capabilities (tools) and the protocol version. Supported versions: 2025-11-25,
2025-06-18, 2025-03-26 and 2024-11-05; any other asked for is answered with 2025-11-25.
pingmethod{}.tools/listmethodtools/callmethod{name, arguments}. Runs one tool. The result is {content: [{type: "text", text}], isError: false}, where
text is the JSON the matching HTTP route returns. A tool that ran and could not answer, such as an end user
with no memory yet, answers isError: true with the reason as text.
A notification, a message with no id, is answered 202 with no body. A JSON array of messages (a batch) is
refused with 400: one message a request.
The server lists eight read tools for every key, and memory_add for a key that writes. memory_search can make
one model call; the others make none.
Tools
memory_searchtool{query, before?, after?, scope?}. As /memory/search.memory_greptool{pattern, scope?, limit?}. As /memory/grep.memory_readtool{path}. As /memory/read.memory_lstool{path?}. As /memory/ls.memory_aroundtool{passage_id, width?}. As /memory/around.memory_latesttool{k?, place?, after?, before?}. As /memory/latest.memory_historytool{subject, attribute?}. As /memory/history.memory_overviewtool/memory/overview.memory_addtool{items, wait?}, the same body as /memory/add. Listed only for a key that writes.The arguments and bounds of each tool are in the tools reference. memory_recall,
memory_lessons, memory_how_they_work and memory_next are not served here; the
local stdio server lists them.
Errors
400bad requestX-Twin-Account is missing or not a usable id; an MCP-Protocol-Version not served here; a body that is
not JSON (-32700); or a batch (-32600).
401unauthorizedThe bearer is not a key this service issued.
403forbiddenAn Origin header naming a host other than the one the request was sent to (-32600).
405method not allowedA GET or a DELETE.
-32700JSON-RPCThe body is not JSON. Sent with a 400.
-32600JSON-RPCThe message is not a JSON-RPC 2.0 request.
-32601JSON-RPCNo such method.
-32602JSON-RPCNo such tool, or its arguments failed validation. data holds the details.
-32603JSON-RPCThe tool failed.
curl https://api.twinmemory.dev/v1/mcp \
-H "Authorization: Bearer $TWIN_MEMORY_API_KEY" \
-H "X-Twin-Account: user_42" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": 2,
"method": "tools/call",
"params": {
"name": "memory_search",
"arguments": { "query": "Porto", "after": "2026-10-01" }
}
}'{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"query\": \"Porto\", \"found\": 1, \"hits\": [{\"path\": \"passages/memory/documents/th_a4c4bfb9e995f6bc/p_0001.md\", …}]}"
}
],
"isError": false
}
}{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": { "name": "my-agent", "version": "1.0" }
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "twin-memory", "version": "0.3.0" }
}
}