Trello MCP Server
README.md
# Trello MCP
Connects Claude (Desktop/Cowork **and** Claude Code) to a Trello board.
The board is chosen by `board_id` in your config — nothing about a specific
board is hardcoded.
```
~/mcp-servers/trello-mcp/
trello_mcp.py the server — no credentials inside, safe to share
requirements.txt mcp==1.27.2 (pinned, see below)
.venv/ dedicated Python 3.12 environment
~/.config/trello-mcp/config.json credentials (chmod 600, never shared)
```
Registered in **both**:
- `~/Library/Application Support/Claude/claude_desktop_config.json` (Desktop/Cowork)
- `~/.claude.json` (Claude Code)
Both point at the **absolute** venv interpreter. That is load-bearing — see below.
---
## Why it used to disconnect every morning
The old config launched the server with the bare command `python3`. There are four
`python3` binaries on this Mac and **only one has the `mcp` package**:
| Interpreter | Version | `mcp`? |
|---|---|---|
| `/usr/bin/python3` | 3.9.6 | no |
| `/usr/local/bin/python3` | 3.9.6 | no |
| `/Library/Frameworks/.../3.9/bin/python3` | 3.9.6 | no |
| `/opt/anaconda3/bin/python3` | 3.12.7 | yes |
Which one `python3` means depends on the `PATH` of the process that launched the app.
Launched from **Finder or at login**, the app inherits the minimal launchd `PATH`
(`/usr/bin:/bin:/usr/sbin:/sbin`) and gets Python 3.9 → `ModuleNotFoundError: No module
named 'mcp'` → *"Trello MCP could not be connected."* Launched from a **terminal**, it
inherits the shell `PATH` where Anaconda comes first → works. Hence: broken most mornings,
fine when tested by hand.
Reproduce the old failure and the fix:
```bash
env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin python3 "$HOME/Claude/Projects/Daily Trello/trello_mcp.py" # ModuleNotFoundError
env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin ~/mcp-servers/trello-mcp/.venv/bin/python3 -c "import mcp; print('ok')" # ok
```
Naming the interpreter by absolute path means **no PATH lookup happens at all**. The app
launches one known binary, which finds one known package directory. Same result at 7 AM
as at 7 PM.
---
## What a venv actually is (and what it does *not* protect against)
Python looks for installed packages in a directory tied to the specific interpreter
running. Four interpreters = four separate package directories; `mcp` was only ever
installed into one. A venv creates a private one belonging to nothing but this server.
Technically: `python3 -m venv .venv` creates `.venv/bin/python3` and a `pyvenv.cfg`
recording the base install. On startup the interpreter reads that file and sets
`sys.prefix` to the venv, so `import` resolves to `.venv/lib/python3.12/site-packages`.
**Honest limitation:** on macOS, `venv` **symlinks** the interpreter rather than copying
it, and the standard library still loads from the base install:
```
.venv/bin/python3 -> /opt/anaconda3/bin/python3
stdlib = /opt/anaconda3/lib/python3.12
```
So this venv **still depends on Anaconda being installed.** It isolates *packages*, not
the interpreter.
| Event | Survives? |
|---|---|
| Anaconda package updates (`conda update`) | yes |
| Anaconda 3.12.x point upgrades | yes |
| macOS updates | yes |
| Any `PATH` change — the actual bug | yes |
| Shell/profile changes | yes |
| **Uninstalling Anaconda** | **no** — rebuild (below) |
| **Anaconda dropping Python 3.12** | **no** — rebuild |
| Moving/renaming this folder | no — paths are absolute; update both configs |
Rebuild after any of those, ~15 seconds:
```bash
cd ~/mcp-servers/trello-mcp && rm -rf .venv
/path/to/any/python3.10+ -m venv .venv && ./.venv/bin/pip install -r requirements.txt
```
If you ever want true independence from Anaconda, install [`uv`](https://docs.astral.sh/uv/)
and build the venv from a uv-managed standalone Python. Not done here because it adds a
new tool to the machine.
### Do not blind-upgrade `mcp`
`requirements.txt` pins **`mcp==1.27.2`**. This is deliberate: `mcp` 2.0.0 removed the
`@app.list_tools()` / `@app.call_tool()` decorator API this server is built on. Installing
latest breaks the server instantly with
`AttributeError: 'Server' object has no attribute 'list_tools'` — which looks exactly like
the connection failure this whole setup was meant to fix. Upgrade only alongside rewriting
the handlers for the new API.
---
## How the credentials file works
`~/.config/trello-mcp/config.json`:
```json
{ "api_key": "...", "token": "...", "board_id": "676f0bfc11723d27f716291d" }
```
Nothing hosted, no external service — an ordinary JSON file on this disk. Two things make
it "secret":
1. It lives **outside the code**, so copying or publishing `trello_mcp.py` carries no
credentials.
2. `chmod 600` — only this macOS user account can read it.
The server reads it at startup and holds the values in memory. To share the server, send
`trello_mcp.py`; the recipient makes their own config with their own Trello key. The value
isn't encryption — it's **separating the thing you share from the thing you don't**.
The Trello token has **no expiry** (`dateExpires: null`) and scopes Member/Board/Organization
read+write. Unlike the Google Drive server's OAuth refresh token, it does not need periodic
re-authorisation.
---
## Lists are never hardcoded
Every list is resolved by **name at call time** against the live API, so a list added in
Trello (a new class each quarter) works immediately — no code change, no restart. A
60-second cache keeps that cheap; a cache miss forces one refetch before reporting "not
found", so a list created seconds ago still resolves.
Measured on this board: a list fetch is ~307 ms. Without the cache every tool call paid it
(`get_cards` costs two round trips), so a 12-card planning session burned ~3.7 s on
refetching alone. With it, one fetch covers the burst.
The **only** list names in the code are the three workflow anchors at the top of
`trello_mcp.py`:
```python
LIST_TODO = "To Do Today"
LIST_WORKING = "Working on"
LIST_DONE = "Done Today"
```
They're named because the daily routine is defined in terms of them (`sort_todo_today`
can't be told "sort the list that means today" without a name). Rename one in Trello →
change it in that block and in the Daily Trello `CLAUDE.md`. `rename_list` warns you when
you rename an anchor.
---
## Tools (37)
**Reading** — `get_all_lists`, `get_cards` (includes attachments, labels, due, position),
`get_card` (full detail), `search_cards`, `get_board_summary`
**Cards** — `create_card` (with attachments/labels/position), `update_card`, `move_card`,
`complete_card`, `archive_card`, `unarchive_card`, `copy_card`, `set_card_position`,
`set_card_cover`, `sort_todo_today`
**Attachments** — `get_attachments`, `add_attachment` (URL **or** upload a local file),
`view_attachment` (returns images viewable — this is how Claude reads a screenshot on a
card), `delete_attachment`
**Labels** — `get_labels`, `add_label_to_card`, `remove_label_from_card`, `create_label`
**Comments** — `get_comments`, `add_comment`
**Lists** — `create_list`, `rename_list`, `archive_list`, `move_list`
**Checklists** — `get_checklists`, `create_checklist`, `add_checklist_item`,
`complete_checklist_item`, `uncheck_checklist_item`, `rename_checklist_item`,
`delete_checklist_item`, `delete_checklist`
### Known board setting: commenting is disabled
`add_comment` returns a 401 because the **board itself** has commenting turned off
(`prefs.comments: "disabled"`) — not a token or code problem. To enable: Trello → board
menu (···) → Settings → Commenting permissions → Board members. The tool detects this and
explains it rather than showing a raw 401.
### Note on `search_cards`
Trello's search index lags a few seconds. A card created moments ago may not appear yet;
use `get_cards` on its list instead.
---
## Troubleshooting
```bash
# 1. Does the interpreter still work?
~/mcp-servers/trello-mcp/.venv/bin/python3 -c "import mcp; print('ok')"
# 2. Does the server start and reach Trello? (Ctrl-C to exit; silence = healthy)
~/mcp-servers/trello-mcp/.venv/bin/python3 ~/mcp-servers/trello-mcp/trello_mcp.py
# 3. Are the configs still pointing at the venv?
grep -A3 '"trello"' ~/.claude.json "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
```
After changing either config, **fully quit and reopen** the app — MCP servers are only
launched at startup.
Older copies of this script still sit at `~/Claude/Projects/Daily Trello/trello_mcp.py`
and `~/Downloads/trello_mcp.py`. Nothing points at them; they still contain the embedded
credentials, so don't share those two.
---
## For the resume / portfolio
Kept here so a future resume session has the honest version rather than
reconstructing it.
**The story worth telling** is the failure, not the feature. This server failed
every morning for weeks and worked every time it was tested by hand — which is
the whole diagnosis: the code was fine and the *environment* was not. The config
named `python3`; an app launched at login inherits a minimal `PATH`
(`/usr/bin:/bin:/usr/sbin:/sbin`) where that resolves to a system Python with
none of the required packages, while a terminal inherits a `PATH` where it
resolves correctly. Testing from a shell was the one case guaranteed to pass.
**What I actually did:** directed the debugging. Mine were the decisions —
treating "it works when I test it" as evidence about the test rather than
reassurance about the code, and requiring a test that reproduces the real launch
conditions (stripped `PATH`) instead of the convenient ones.
**What came out of it**, now applied to every integration: absolute interpreter
paths in configs, a dedicated environment per project, every dependency pinned,
and a doctor script that checks each known failure mode and repairs what it can.
**Portfolio-length bullet:**
> Directed development of three automation integrations connecting macOS to cloud
> services, running unattended on scheduled jobs. Root-caused a recurring daily
> failure to PATH resolution differing between shell and app launch contexts, and
> established absolute-path and pinned-dependency conventions that eliminated it
> across all three.
Full case study: https://zeno-blade-creator.github.io/projects/personal-integrations.html
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues