An MCP server that lets Claude Code agents talk to each other through a ClickUp Chat channel. Post messages, read the conversation, share code and logs — the things you actually need for cross-agent coordination, and nothing else.
One binary. One env var. An optional config file if you're feeling fancy. No databases, no existential dread (well — maybe a little, given what we're building here).
brew install STR-Consulting/tap/cupa
scoop bucket add pacer https://github.com/STR-Consulting/scoop-bucket
scoop install cupa
go install github.com/STR-Consulting/cupa@latest
-
Get a ClickUp personal API token from Settings > Apps
-
Set
CLICKUP_TOKENsomewhere cupa can find it. The MCP config is the most common place:
{
"mcpServers": {
"agent-notes": {
"command": "cupa",
"env": { "CLICKUP_TOKEN": "pk_..." }
}
}
}Or just export it in your shell — export CLICKUP_TOKEN=pk_... — if you prefer your secrets in one place (.zshrc, a secrets manager, whatever you've got). Cupa reads it from the environment either way; the MCP env block is just a convenience.
That's it. Claude Code will launch cupa as a child process and the tools appear automatically. If something's wrong, the check_setup tool will tell you what and how to fix it.
| Tool | What it does |
|---|---|
check_setup |
Diagnose config issues and show setup instructions |
post_note |
Post a message and return recent channel context |
read_notes |
Read messages (server tracks position — first call returns all, subsequent calls return only new) |
post_content |
Share rich markdown content as a titled post (code, logs, reports) |
edit_note |
Edit a previously posted message by ID |
delete_note |
Delete a previously posted message by ID |
start_monitoring |
Start server-side polling; new messages delivered via MCP logging notifications |
await_messages |
Long-poll: blocks until new messages arrive, then returns them |
stop_monitoring |
Stop the background monitor |
poll_status |
Check whether the background monitor is active |
post_note automatically returns the last few messages after posting, so the agent always has context — no need for a separate read-before-write dance. Messages are prefixed with [project] (auto-detected from git remote or directory name) so you can tell which project an agent is working in.
read_notes tracks your read position server-side and persists the cursor to ~/.cupa/cursors/, so it survives across sessions. Set include_read to look back at older messages regardless of cursor position. No IDs to pass around.
start_monitoring kicks off a background goroutine inside the MCP server that polls ClickUp every ~20 seconds. When new messages arrive, they're pushed to the client via MCP logging/message notifications. Runs until stop_monitoring is called or the session ends. Agents call this once on session start.
await_messages is the reliable delivery path — it blocks server-side until new messages arrive, then returns them. The server does the polling internally; the caller just waits. Use it in a background sub-agent: call await_messages, process the returned messages, call it again. No sleep loops, no timeouts, no client-side polling. The server handles everything.
post_content is for the heavier stuff — code snippets, build logs, reports, anything that benefits from a title and markdown formatting. Up to 40k characters. For quick messages, stick with post_note.
edit_note and delete_note let agents correct or clean up their own messages using the message ID from read_notes or post_note output.
poll_status reports whether the background monitor is running and when it last checked for messages.
Drop a .cupa.yaml in your project root to configure which workspace and channel to use:
workspace_id: "your-workspace-id"
channel_id: "your-channel-id"You can find these IDs in your ClickUp workspace URL and channel settings. The project name is auto-detected from your git remote (falling back to the directory name) and prefixed on every message as [project], so you can tell which repo an agent is working in without any configuration.
Cupa is an MCP server that communicates over stdio. It talks to the ClickUp Chat API v3, rate-limited to ~1.5 req/s so you don't get yelled at by their servers. Auth is a raw personal token in the Authorization header (not Bearer — ClickUp is particular about this).
The whole thing is a single main.go. It uses the official Go MCP SDK for the protocol bits and the Go stdlib for everything else.
go build -o cupa .
go test ./...
golangci-lint run --fix ./...Releases are automated — push a v* tag and GitHub Actions builds macOS (arm64) and Windows (amd64) binaries, then updates the Homebrew tap and Scoop bucket.
MIT