Skip to Content

Connection troubleshooting

Organized by what you observe, because the symptom rarely names the cause. Each entry was reproduced against a live appliance or checked against the server source.

Work down from the first symptom you hit; each branch is a section on this page:

Connection refused, or the request just hangs

Nothing is listening on that port — almost always because a port was assumed rather than looked up. It hits both servers, for different reasons.

Companion Hub, over a public URL or VPN. 5002 is a real, fixed port, but it is only in the URL when you are talking to it directly — on the LAN or across a VPN such as Tailscale. Reached by its public URL or tunnel, Hub answers on 443 as https://<your-hub-domain>/api/mcp, with no port at all: that route enters through cloudflared and is served by the appliance’s Traefik on port 80. Appending :5002 to a tunnel origin is a connection error. See Find your address.

Companion Memory, on any install. Here the port is not fixed by the docs.

Hub publishes Companion Memory on the app’s own port, 8642, when that port is free at install time. If something already holds 8642, or you chose another port in the install form, Hub picks a free port in the 10000–60000 range instead. So 8642 is right on many installs and refuses the connection on others. Hub’s own 5002, by contrast, is fixed and can be relied on.

Use whichever of these matches your install:

How Memory runsAddress
Installed from the Hub app storehttp://<appliance-ip>:<host-port>/api/mcp
Over Tailscalehttps://<hub>.<tailnet>.ts.net:<host-port>/api/mcp
Public domainhttps://<your-domain>/api/mcp
Desktop app, on the same computerhttp://127.0.0.1:8642/api/mcp
Standalone, from Memory’s own composehttp://<host>:8642/api/mcp

The Access points card on the Companion Memory app page in the Hub dashboard carries the right address for each route, with a copy button. See Find your address.

A refused connection is not an auth problem — if you are getting one, the key is not yet involved.

405 Method Not Allowed, or 404 Not Found

The path is wrong. Companion Hub and Companion Memory serve MCP at /api/mcp, not /mcp.

The two servers report the same mistake differently, which is worth knowing before you conclude one of them is down:

You sentCompanion MemoryCompanion Hub
POST /mcp405404
POST /api/mcp, no key401401

Memory’s 405 is the more misleading of the two — it reads like the service is refusing your request method, when in fact its gateway routes ^/api/mcp only and the request never reached the MCP server.

401 with a key attached

Read the response body first. Companion Hub names the cause, and Companion Memory does not:

ServerBodyMeans
HubMissing Authorization headerNo Authorization header arrived — for example, you sent x-api-key
HubMalformed Authorization headerThe header was not exactly Bearer <key>
HubInvalid API keyUnknown, revoked, or missing the mcp scope
MemoryUnauthorized (no detail)Unknown, revoked, expired, or not 64 lowercase hex characters

The cause is one of these:

  • The key is not in the shape a key has. Both servers issue 64 lowercase hexadecimal characters. A trailing space, a doubled Bearer, or a truncated paste fails as an unknown key.
  • The key is unknown, revoked, or expired. Confirm it exists — cihub api-key list for Hub, Settings → API Keys for Memory, where an expiry date shows on the row.
  • The Hub key lacks the mcp scope. Hub resolves the token and its scope in a single step, so a scope failure comes back as 401 Invalid API key — the same answer as an unknown key, and not the 403 you would get from Memory. cihub api-key list shows each key’s scopes.
  • You sent it to the wrong server. Hub keys and Memory keys are not interchangeable, and the error is the same either way.
  • You used the wrong header on Hub. Hub accepts only Authorization: Bearer. Memory accepts x-api-key too, so a config that works against Memory returns 401 against Hub if it uses that header.

403 — Companion Memory only

Only Companion Memory answers 403. The key is valid, but its scopes do not cover the surface you are calling. Enforcement is deny-by-default: there are only four scopes, only the surfaces below declare one, and a key with any scope ticked is refused on every route that declares none. (Companion Hub never returns 403 for a scope problem — see the 401 entry above.)

CallingNeeds
/api/mcpMCP
the intent catalog, the pending-action routesIntents
/api/memory/*, /api/v1/intelligence/*, /api/v1/agent/jobsMemory
the /api/v1 bulk-write routes, POST /api/files/upload, PATCH /api/files/:idCapture
/api/graphql, POST /api/v1/files, /api/v1/ingest/*, graph routesno scopes ticked at all

/api/v1/* is not one scope, which is the trap in that table. Only the bulk-write routes (/api/v1/events, /communications, /contents, /things, /inventory (a deprecated alias of /things), /browser-activity, /notes, /positions, /receipts, /product-models, /observations) declare Capture; /api/v1/intelligence/* needs Memory; and /api/v1/files and /api/v1/ingest/* declare nothing at all. So a Capture-only key can POST every batch successfully and then 403 on POST /api/v1/ingest/finish — the terminal call that runs the post-load enrichment and embed backfill. The rows are stored, but because the bulk routes deliberately skip live indexing they stay out of the search index until finish runs — and finish, declaring no scope, accepts only an unscoped full-access key or a signed-in session.

A key minted for MCP connects fine and returns 403 on every memory REST call. These are separate scopes, and needing both is normal — see Get your API keys.

If a key that holds MCP + Intents + Memory still 403s, check whether the surface is one of the undeclared ones in the last row — those are reachable only by an unscoped, full-access key.

On Companion Hub the equivalent failure is a 401, not a 403 — a key that lacks the mcp scope is rejected by the MCP auth guard rather than by a separate scope check.

Connected, but zero tools

The most common problem in this whole section, and it is not a fault.

A Companion Memory key with MCP but not Intents authenticates, opens a session, and advertises nothing. Depending on your client, you see an empty tool list or tools/list answering -32601 Method not found.

Both axes move the count, but scopes do the heavier work — they decide which families are registered at all, and capability then filters whatever survived. Counted from Companion Memory v2026.10.2, which has 36 native intents and a GraphQL schema of 112 queries and 138 mutations:

Scopes on the keyCapabilityTools returned
MCP onlyanynone — -32601 on tools/list
MCP + Intentsread26
MCP + Intentswrite32
MCP + Intentsfull36
No scopes selectedfull286

A key with no scopes selected and a lower capability sees a number between the intent count and 286, because its GraphQL tools are filtered by capability too. Holding scopes fixed, full ⊇ write ⊇ read always.

Scopes do two separate things, which is why the gap between 36 and 286 is so much larger than a capability change could explain:

  • No Intents → no tools at all. Nothing is registered, so the server never advertises a tools capability and tools/list answers -32601.
  • Any scope ticked → no graph tools, ever. A key with any scope is scope-limited and is confined to the native intent tools. The much larger family generated from the GraphQL schema is withheld from it at every capability, because those tools run against resolvers a scope-limited key is denied anyway.

Treat these as illustrative rather than exact: both families grow release to release, and these are Memory’s numbers — Companion Hub advertises its own, smaller set.

Fix: mint a key with both MCP and Intents.

A tool call is refused

Capability is checked again at call time, and the refusal names the level that would have worked.

ServerMessageFix
MemoryTool "<name>" requires "write" access, but this API key is "read"Raise the key to write under Settings → API Keys
MemoryTool "<name>" reads the knowledge graph directly, which this scope-limited API key cannot accessUse a key with no scopes selected, or use the intent tools
HubTool '<name>' is destructive and this API key's capability is '<level>'…Raise the key to full under Settings → Security

A call comes back as TOOL_QUEUED

Nothing failed. The tool deletes data or sends something outside the appliance, and Settings → Autonomy is Balanced or Cautious, so Companion Memory asked its owner instead of running it. The error carries the code TOOL_QUEUED and a pendingActionId. Open Approvals in Companion Memory and approve the card; the call runs after that. A card nobody answers expires after 24 hours. A key that Hub minted for an installed agent app is asked every time, whatever the setting. See A full key still waits for Approvals.

Fewer tools than you expected

Your key’s capability is filtering the list. A read key sees only tools it could actually call; a write key sees everything except destructive ones.

This is working as intended — it stops the agent burning a turn on a tool that was always going to be refused. If you want more, see Choosing a capability.

Alternating success and empty-bodied 400

Every second request fails, with a 400 carrying no body at all:

200 400 200 400 200 400

Strict alternation, unaffected by delays between requests. This is a gateway defect, not a client problem — no config change can help.

Test for it with ten requests, not one. A single successful initialize proves nothing here, and the handshake tends to fail at the second step, making it look like notifications/initialized or tools/list is unsupported when the connection dropped underneath it.

Fixed in Companion Memory v2026.7.30 — upgrade. If you cannot upgrade immediately, retrying works — the failures observed were strictly alternating, so a second attempt succeeded every time.

tools/list returns -32601 Method not found

Not a missing method, and — on Companion Memory — almost always one cause: your key lacks Intents, so nothing was ever registered for tools/list to return. See the scope trap.

A lost session is not the explanation, even though it looks like a candidate. Neither server reports one this way: both answer 404 — Hub with a JSON-RPC -32001 Session not found body, and Memory with Session not found in its standard error body. Sessions expire after 30 minutes idle on both servers and do not survive a restart of the server, and a client that follows the MCP spec re-runs initialize on a 404. If you are getting -32601, the session is not the problem and re-running initialize does not help.

The agent connected but does not remember anything

Working as designed, and the most common surprise with this setup.

MCP provides tools. It does not provide passive turn capture or automatic context injection — those come from a memory-slot provider, which is a separate mechanism. Over MCP alone, your agent has to call a memory tool during a conversation for anything to be stored, and saying so in its system prompt is usually enough.

If you want capture to happen on its own, that provider is published for both agents: OpenClaw, Hermes. See also Beyond MCP: first-class memory.

If it is installed and still nothing accumulates, the cause is usually one of a short list — each agent’s page ends with them. The one to check first, on either agent: the key’s scopes and capability. The provider talks REST, so its key needs the Memory scope at Read & write — an MCP-scoped or Read-only key is refused on every capture, silently, while every setup check passes. On OpenClaw the other usual answer is hooks.allowConversationAccess missing from the plugin’s entry: you get the tools, and nothing is captured.

A browser-based client cannot connect

Companion Memory’s CORS allowlist contains only its own native-app origins (capacitor://localhost and the localhost forms the iOS/Android shells serve from), so a page on any other origin is refused — no client-side change fixes that. Use a server-side or CLI client. An operator can add origins with CORS_ALLOWED_ORIGINS (comma-separated); it is empty out of the box.

Companion Hub’s CORS layer is also narrow. It allows http://localhost:5002 and http://127.0.0.1:5002 (its own port), the domains you configured as DOMAIN and LOCAL_DOMAIN, and any origin an operator lists in CI_HUB_EXTRA_CORS_ORIGINS, and it refuses every other origin. Requests with no Origin header — every non-browser client — are unaffected. Treat both servers as server-side and CLI integrations.

Still stuck

Take the client out of the picture and test the server directly with curl. That single step separates “my config is wrong” from “my key is wrong” faster than any amount of reading logs.

Last updated on