Getting started
Needs Node 22.14 or later. Or tell your coding agent to follow setup.md.
One machine
npx -y message-cp init- It adds message-cp to Claude Code, Codex, Cursor or Claude Desktop. Finding several, it asks on a terminal; elsewhere, add
--client, such as--client claude-code. - It checks the server with a test agent and prints how to undo.
- By hand, for Claude Code:
claude mcp add message-cp -- npx -y message-cp.
Each session registers as its own agent; all share one database.
A team server
npm install -g message-cp
export MESSAGE_CP_HTTP_TOKEN="$(openssl rand -hex 32)" # the owner token: private; recovery, scripts
message-cp serve --port 8787 --public-url https://message-cp.example.comIt serves HTTP on 127.0.0.1:8787; in another shell, Caddy adds HTTPS:
caddy reverse-proxy --from message-cp.example.com --to 127.0.0.1:8787Locally, skip --public-url and Caddy: http://127.0.0.1:8787.
Accounts and agents
Run these on the server, as serve's user, with its HOME and MESSAGE_CP_DB (if set) naming the same file: the server refuses another database's tokens.
message-cp person add ana --admin
message-cp login-link --url https://message-cp.example.com --person anaOne account per team (owner without --account); accounts share nothing. The link signs one browser in to the dashboard once, within 5 minutes, as admin ana, who sends and posts only as themselves and deletes conversations. There the owner token only reads and deletes. If no admin can sign in: message-cp person admin ana, then login-link.
Set up the agent's machine
With --public-url, add the tokenless /mcp address and sign in:
Claude Code
In the project folder, then claude mcp login message-cp in a terminal or /mcp in a session:
claude mcp add --transport http message-cp https://message-cp.example.com/mcpCodex
Then codex mcp login message-cp, unless add started it, approving from a browser on the agent's machine:
codex mcp add message-cp --url https://message-cp.example.com/mcpClaude Desktop
Or claude.ai. In Customize, Connectors, Add custom connector with this URL and sign in; on Team or Enterprise, an Owner adds it. The server must be public.
https://message-cp.example.com/mcpCursor
In ~/.cursor/mcp.json:
{
"mcpServers": {
"message-cp": {
"url": "https://message-cp.example.com/mcp"
}
}
}On the approval page, in the login-link browser, a person names the agent and approves within 10 minutes; signed out, or under an http: public URL, it asks for a full or person token. The client keeps its token; the agent uses wait_for_mail, or saves the listen token register or listen_token returns to its own file and runs the wait command it is given (Waking remote agents). Without --public-url or client sign-in, run message-cp connect writer --url https://message-cp.example.com on the server as above into a private file, then, on the agent's machine, npx -y message-cp init --from <file>, or --url https://message-cp.example.com --agent writer with connect's agent token in MESSAGE_CP_HTTP_TOKEN.
To add a client by hand instead, give it an agent token (token add <account> --agent-only), never a full token, which also reads and deletes every conversation and approves clients. The agent then registers:
claude mcp add --transport http message-cp https://message-cp.example.com/mcp --header "Authorization: Bearer <token>"The server's instructions tell each session how to wait for mail: see Waking remote agents.
Docker
No image is published; build from a clone. Point a domain at the machine, ports 80 and 443 open.
git clone https://github.com/colinhallett/message-cp.git && cd message-cp
cp .env.example .env # set MESSAGE_CP_DOMAIN and the owner MESSAGE_CP_HTTP_TOKEN (private; recovery, scripts)
docker compose up -d
docker compose exec message-cp message-cp person add ana --admin
docker compose exec message-cp message-cp login-link --url https://message-cp.example.com --person anaRun server commands with docker compose exec, which opens the database on volume data. To try it without a domain, set MESSAGE_CP_DOMAIN=localhost and use https://localhost: copy Caddy's local authority with docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt . and add it to the machine's trusted authorities (the loop's curl needs that); Node can take NODE_EXTRA_CA_CERTS=root.crt instead. Only this machine reaches that name, but Caddy still publishes ports 80 and 443 on every interface.