Other MCP Clients
Any client that speaks MCP over Streamable HTTP can connect. The three things every client needs are
the same β the serverβs MCP URL, an Authorization: Bearer header, and some way of declaring that
the transport is Streamable HTTP. Only the third differs between them.
One expectation to set first: these clients get memory as tools β something the agent calls, not something that happens. Passive capture is an OpenClaw and Hermes feature; see Beyond MCP: first-class memory.
For both Companion Hub and Companion Memory that URL ends in /api/mcp.
<hub-url> and <memory-url> below are your two serversβ base URLs β everything before /api/mcp.
Neither has a port you can assume, so take both from the Hub dashboard if you are unsure:
Hub,
Memory.
Claude Code
claude mcp add --scope user --transport http ci-memory "<memory-url>/api/mcp" --header "Authorization: Bearer <memory-key>"
claude mcp add --scope user --transport http ci-hub "<hub-url>/api/mcp" --header "Authorization: Bearer <hub-key>"One line each, deliberately. A trailing \ continues a command in bash but is
a literal argument in PowerShell and cmd β pasted there, the first line runs
without its URL and the rest fail as unknown commands. These fit on one line,
so they run as-is in every shell.
Then confirm:
claude mcp listHand-written JSON needs "type": "http"
The CLI sets this for you. If you write the config by hand instead, the type field is
required:
{
"mcpServers": {
"ci-memory": {
"type": "http",
"url": "<memory-url>/api/mcp",
"headers": { "Authorization": "Bearer <memory-key>" }
}
}
}An entry with a url but no type is skipped, and the server simply never appears in your
session. It is not silent, though: claude mcp list reports it, naming the config file and the
fix β MCP server "β¦" has a "url" but no "type"; add "type": "http". If a server you configured by
hand is missing, run that command first.
Cursor, OpenCode, Codex, and others
The pattern is the same. Consult your clientβs documentation for where its MCP config lives and what it calls the transport field, then fill in:
| What the client needs | Value |
|---|---|
| URL | http://<host>:<port>/api/mcp |
| Header | Authorization: Bearer <key> |
| Transport | Streamable HTTP, however that client spells it |
If your client runs in a browser, it will not reach Companion Memory β
that server sends no CORS headers at all. Hub does reflect localhost
origins, so a locally served page can call it, but treat that as exposure to
be aware of rather than a supported path. Wire these up from a server-side or
CLI client.
Checking any client, without the client
When a client fails and gives you nothing to work with, take it out of the picture and test the server directly. If this works, the problem is in your client config; if it does not, the problem is the key or the address.
macOS / Linux / WSL2
BASE="http://<host>:<port>/api/mcp"
KEY="<key>"
HDRS=$(mktemp)
curl -s -D "$HDRS" -X POST "$BASE" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"verify","version":"1"}}}'
head -1 "$HDRS" # the status code you need for the next step
SID=$(grep -i '^mcp-session-id:' "$HDRS" | tr -d '\r' | awk '{print $2}')
echo "session: $SID"
curl -s -X POST "$BASE" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $KEY" \
-H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'Read the status line first: 200 means the handshake worked, and anything else is what the
Troubleshooting page is organized by. An empty session: line
alongside a 200 is not necessarily a failure β the session header is optional in the Streamable
HTTP spec, so a stateless server can answer correctly without issuing one.
Because the request accepts text/event-stream, the body may come back as SSE frames
(event: message / data: {β¦}) rather than plain JSON. Strip the framing with
sed -n 's/^data: //p' before piping to jq β or in PowerShell, run this against the tools/list
response captured above:
($res2.Content -split '\r?\n' | Where-Object { $_ -like 'data: *' }) -replace '^data: ' | ConvertFrom-Json-split takes a regular expression, so '\r?\n' splits CRLF and LF frames alike. A bare \n leaves
a carriage return on the end of every frame β which ConvertFrom-Json itself tolerates as
whitespace, but which anything comparing or printing the raw strings will not.
Let the agent do it
If your client is itself a capable agent, point it at the connect skill and let it wire itself up:
https://docs.ci.computer/connect/SKILL.mdIt is an agent-readable version of this whole section β every endpoint here plus the OpenClaw and Hermes pages, first-class memory, and the traps on Troubleshooting β written for a model rather than a person.
Pasting the URL uses it once. To keep it available for later, install it into a skills directoryΒ your agent scans; see Or let your agent do it for the difference. It cannot mint keys for you, so have both ready first.