Skip to Content

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 list

Hand-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 needsValue
URLhttp://<host>:<port>/api/mcp
HeaderAuthorization: Bearer <key>
TransportStreamable 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.

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.md

It 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.

Last updated on