twin memory

Troubleshooting

What to check when twin memory's tools do not appear in an MCP client, and what each error means.

The tools do not appear

Check, in this order:

  1. The URL. It is https://api.twinmemory.dev/v1/mcp, with /v1.
  2. Both headers. Every request with a key needs Authorization: Bearer tm_... and X-Twin-Account: <end user>. A client that sends only one is refused. claude.ai and ChatGPT sign in instead: see claude.ai and ChatGPT.
  3. The transport. The server speaks Streamable HTTP: a POST per message, answered with JSON. A GET answers 405, so a client set to the older SSE transport cannot connect. With mcp-remote, pass --transport http-only.
  4. The client's own status. In Claude Code, run claude mcp list. In Cursor, open the MCP settings. In VS Code, open the MCP view and the server's output.

HTTP errors

The server checks the key and the end user as the HTTP API does, before any tool runs.

StatusMessageWhat to do
400X-Twin-Account is required: ...The client sent no X-Twin-Account header. Add it.
400X-Twin-Account is not a usable end user id: '...'Use letters, digits, ., _ and -, starting with a letter or digit. See End users.
400MCP-Protocol-Version '...' is not spoken hereServed versions are 2024-11-05 through 2025-11-25. Update the client or let it fall back.
401a bearer this service issued is requiredThe key is missing, mistyped or revoked. Check the Authorization header reads Bearer tm_....
401with WWW-Authenticate: Bearer resource_metadata=A signed-in connection's token is missing, expired or no longer valid. The client refreshes it or asks to sign in.
403with error="insufficient_scope"A Read only connection called memory_add. Connect again with Read and save.
403a request from another originA browser page on another host sent an Origin header. Call the server from your backend or a desktop client.
405The client sent a GET or a DELETE. Use Streamable HTTP.

The memory is empty, or a tool answers 404

An end user nothing was ever written for has no memory to read. Tools for that end user answer with no memory for end user '...' yet. Add something for them with /v1/memory/add first, or check that X-Twin-Account names the end user you meant.

Right after an add, the item can take a moment to land. Search finds it by its words once it has, and its dated facts appear after it has been read in the background. /v1/memory/status says where each item is.

memory_add is missing

memory_add is listed only for a read and write key, or a connection with Read and save. With a read key, or a Read only connection, the client sees the read tools alone. This is on purpose: see which key to give a client.

The key is sent as ${TWIN_MEMORY_API_KEY}

In Claude Code, a ${VAR} in .mcp.json that is not set is sent as written, and the server answers 401. claude mcp list warns about it. Set the variable in the environment Claude Code starts from.

In Windsurf (Devin Desktop), an unset ${env:VAR} becomes an empty string, with the same result.

mcp-remote splits the header on Windows

Some clients, among them Claude Desktop on Windows, do not escape spaces inside args, so "Authorization: Bearer tm_..." arrives as two arguments. Write the header without a space and keep the space in an environment variable:

claude_desktop_config.json
"args": ["mcp-remote", "https://api.twinmemory.dev/v1/mcp",
         "--header", "Authorization:${AUTH_HEADER}",
         "--header", "X-Twin-Account:user_42"],
"env": { "AUTH_HEADER": "Bearer tm_..." }

JSON-RPC errors

Once a request is admitted, a problem with the message itself comes back as a JSON-RPC error:

CodeMeaning
-32700The message is not JSON.
-32600The message is not a JSON-RPC 2.0 request.
-32601No such method. The server answers initialize, ping, tools/list and tools/call.
-32602No tool by that name, or the arguments do not fit its schema. data holds the details.
-32603The tool failed. The message names the method and the error.
An argument out of bounds (data shortened)
{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32602,
    "message": "memory_around: invalid arguments",
    "data": [
      { "type": "less_than_equal", "loc": ["width"], "msg": "Input should be less than or equal to 5" }
    ]
  }
}

On this page