Skip to Content
DocumentationConnect an AgentClaude Code, Cursor, Codex, and more

Claude Code, Cursor, Codex, and more

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.

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 prints one line per server with its health, and /mcp inside a session shows each server’s tools:

claude mcp list

To keep a key out of your shell history and out of ~/.claude.json, register the server with a ${VAR} reference instead. Claude Code expands ${VAR} in headers when it reads the config, so export the variable first (see Keep the keys out of the file) and put this in a .mcp.json at your project root:

{ "mcpServers": { "ci-memory": { "type": "http", "url": "<memory-url>/api/mcp", "headers": { "Authorization": "Bearer ${CI_MEMORY_API_KEY}" } } } }

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 does not appear 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.

Keep the keys out of the file

Every block below reads the key from an environment variable rather than embedding it, so the config file can be shared or committed without leaking a credential. Export both keys in the shell that launches the client:

export CI_MEMORY_API_KEY='<memory-key>' export CI_HUB_API_KEY='<hub-key>'

A client launched from a desktop icon does not inherit variables you exported in a terminal, so on macOS and Windows either start the client from that terminal or paste the key into the file and keep the file private.

Cursor

Add both servers to ~/.cursor/mcp.json, or to .cursor/mcp.json in a project. Cursor infers the transport from the presence of url, and it expands ${env:NAME} in url and headers:

{ "mcpServers": { "ci-memory": { "url": "<memory-url>/api/mcp", "headers": { "Authorization": "Bearer ${env:CI_MEMORY_API_KEY}" } }, "ci-hub": { "url": "<hub-url>/api/mcp", "headers": { "Authorization": "Bearer ${env:CI_HUB_API_KEY}" } } } }

Reload the window after editing, then open Cursor’s MCP settings and confirm each server shows its tools.

Codex

Codex reads ~/.codex/config.toml. Its bearer_token_env_var field names the environment variable that holds the key, and Codex sends it as Authorization: Bearer:

[mcp_servers.ci-memory] url = "<memory-url>/api/mcp" bearer_token_env_var = "CI_MEMORY_API_KEY" [mcp_servers.ci-hub] url = "<hub-url>/api/mcp" bearer_token_env_var = "CI_HUB_API_KEY"

Or add each server from the command line:

codex mcp add ci-memory --url "<memory-url>/api/mcp" --bearer-token-env-var CI_MEMORY_API_KEY codex mcp add ci-hub --url "<hub-url>/api/mcp" --bearer-token-env-var CI_HUB_API_KEY

Confirm with codex mcp list, or type /mcp in a session.

VS Code

Create .vscode/mcp.json, or run MCP: Open User Configuration for a config that follows you across workspaces. VS Code uses servers as the top-level key, not mcpServers, and can prompt for the key so it never lands in the file:

{ "inputs": [ { "type": "promptString", "id": "ci-memory-key", "description": "Companion Memory API key", "password": true } ], "servers": { "ci-memory": { "type": "http", "url": "<memory-url>/api/mcp", "headers": { "Authorization": "Bearer ${input:ci-memory-key}" } } } }

Gemini CLI

Gemini CLI uses httpUrl, not url, for a Streamable HTTP server, and expands ${VAR} in headers. Add this to ~/.gemini/settings.json, or to .gemini/settings.json in a project:

{ "mcpServers": { "ci-memory": { "httpUrl": "<memory-url>/api/mcp", "headers": { "Authorization": "Bearer ${CI_MEMORY_API_KEY}" } } } }

Or from the command line:

gemini mcp add --transport http -H "Authorization: Bearer $CI_MEMORY_API_KEY" ci-memory "<memory-url>/api/mcp"

OpenCode

Add a remote server to opencode.json. OpenCode substitutes {env:NAME}:

{ "$schema": "https://opencode.ai/config.json", "mcp": { "ci-memory": { "type": "remote", "url": "<memory-url>/api/mcp", "enabled": true, "headers": { "Authorization": "Bearer {env:CI_MEMORY_API_KEY}" } } } }

Claude Desktop

Claude Desktop’s Settings β†’ Connectors β†’ Add custom connector takes a URL and is built for servers that use OAuth or no authentication. Companion Hub and Companion Memory authenticate with a static bearer key and do not advertise an OAuth flow, so use a local bridge that turns the remote server into a stdio server instead. mcp-remote is one such bridge; it is a third-party package that Companion does not maintain or test. Put the header value in env β€” some clients do not escape a space inside args:

{ "mcpServers": { "ci-memory": { "command": "npx", "args": [ "-y", "mcp-remote", "<memory-url>/api/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer <memory-key>" } } } }

The file is ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. Restart Claude Desktop after editing. The bridge needs Node.js on the machine, and the key sits in this file, so keep the file private.

Any other client

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
URL<server-url>/api/mcp
HeaderAuthorization: Bearer <key>
TransportStreamable HTTP, however that client spells it

Confirm the tools arrived

Whichever client you use, finish with the same check: the client should list the server’s tools, not just report it as connected. A Hub key lists tools such as hub_list_installed_apps; a Memory key with MCP and Intents lists tools such as memory_recall. A server that connects with an empty list is almost always a Memory key without Intents β€” see Connected, but zero tools. If the client gives you nothing to go on, run the curl check below.

If your client runs in a browser, it almost certainly cannot reach Companion Memory β€” its CORS allowlist is the native Capacitor webview origins: capacitor://localhost, ionic://localhost, and the bare http://localhost and https://localhost. Those last two carry no port, so a page served on plain localhost (the default port, 80 or 443) is allowed as well β€” but any other origin, including localhost on a non-default port, is refused unless an operator adds it with CORS_ALLOWED_ORIGINS. Hub is narrower than that: it allows its own localhost:5002 origin, the domains you configured, and origins listed in CI_HUB_EXTRA_CORS_ORIGINS. 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