Metadata-Version: 2.4
Name: activecollab-mcp
Version: 1.11.0
Summary: MCP server for ActiveCollab — exposes projects, tasks, comments, attachments, and time tracking to MCP-compatible LLM clients.
Project-URL: Homepage, https://github.com/devjtv/active-collab-mcp
Project-URL: Issues, https://github.com/devjtv/active-collab-mcp/issues
Author: ActiveCollab MCP Contributors
License: MIT
License-File: LICENSE
Keywords: activecollab,claude,cursor,llm,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: anyio>=4.4
Requires-Dist: click>=8.1
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=1.2.0
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: pydantic>=2.7
Requires-Dist: python-dotenv>=1.0
Requires-Dist: starlette>=0.37
Requires-Dist: uvicorn>=0.30
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# activecollab-mcp

A Python MCP server that exposes an ActiveCollab workspace (projects, tasks,
comments, attachments, time tracking) to MCP-compatible LLM clients such as
Cursor and Claude Desktop. Ships as an installable package with a daemon CLI
and version management via `pipx`.

> **Heads up**: this version supersedes the previous Node.js implementation,
> which has been moved to `archive/js/` for reference.

## Features

- **One-line installer** — `pipx install activecollab-mcp && activecollab-mcp setup`, done. `setup` covers login, the background service, registration with installed AI clients (Claude Code, Codex, Grok, Pi, OMP, Gemini CLI, Cursor, opencode), and auto-updates.
- **Interactive `auth login`** — exchanges email + password for an API token via [`/issue-token`](https://developers.activecollab.com/api-documentation/v1/authentication.html) and stores it at `~/.activecollab-mcp/credentials.json` (mode 0600). No env vars required for the common case.
- **Streamable HTTP transport** on `127.0.0.1:8787/mcp` (single, modern MCP transport).
- **Daemon CLI** — `start`, `stop`, `restart`, `status`, `logs`, `config`, `version`.
- **Boot-persistent service** (macOS) — `activecollab-mcp service install` registers a `launchd` LaunchAgent with `RunAtLoad` + `KeepAlive` so the server survives reboots and auto-restarts on crash.
- **Self-update** — `activecollab-mcp update` installs the latest release from PyPI. `--auto` registers a cron entry for daily auto-updates. The running server is **automatically bounced** after a successful update so it picks up the new code.
- **Auto port fallback** — if 8787 is busy, the daemon scans the next 20 ports and binds to the first free one. The actual bound port is written to `~/.activecollab-mcp/server.port` and surfaced by `status`.
- **Read tools** — projects, users, tasks, comments, attachments, time records, name resolution.
- **Write tools** (toggle via env) — create/update/complete/reopen/delete tasks, post comments, log time, upload and attach files.
- **First-person identity** — `whoami` and `list_my_tasks` resolve "me" / "my" from the token owner so the LLM doesn't have to ask.
- **Vision-ready image attachments** — `get_image_attachment_content` returns metadata first (including `tempFilePath`) and embeds MCP `ImageContent` for images under the embed budget, so hosts that truncate large image payloads still get a usable file path fallback. Survives ActiveCollab quirks: token-auth-only download endpoint, generic `application/octet-stream` Content-Type responses, and the `{single: {...}, parent: {...}}` wrapper on `/attachments/{id}`.
- **Resilient comments + attachments** — automatic retry on transient 5xx, plus an inline fallback that pulls comments and attachments off the task payload when the dedicated endpoints return "Failed to match path". Routing-miss 500s are remembered for an hour (`endpoint_skipped: true` in responses), so broken endpoints aren't re-probed on every call.
- **Stale-token self-healing** — on a 401 the client re-reads `credentials.json` and retries once with the fresh token, so `auth login` takes effect without restarting the daemon. API keys are redacted from all error output.
- **LLM-friendly response shaping**:
  - `get_task_bundle` — task + comments + deduped attachments in one call; image metadata is returned first with temp-file fallbacks, and small image attachments are embedded as MCP image blocks (`include_image_content`, capped by `max_images`/`max_image_bytes`/`max_embedded_image_bytes`).
  - `get_project_overview` — project metadata + recent open tasks in one call.
  - `describe_workspace` — counts, top projects, top users for orientation.
  - `search_tasks` — multi-field client-side scan when the API's `search` isn't enough.
  - `format=compact` on list tools to drop heavy fields (body HTML, raw attachments) and save tokens.
  - Tasks are enriched with `project_name` and `assignee_name(s)` resolved from cached lookups — no more bare IDs in the LLM output.
  - Cached name resolution (`find_*_by_name`, `list_tasks_by_user_name`) with TTL.
  - Pagination metadata (`hasMore`, `nextPage`) returned on every list tool.

## Requirements

- Python 3.10+
- An ActiveCollab account (onboarding exchanges your email + password
  for an API token)

`pipx` will be installed automatically by the installer script if it isn't
already on your `PATH`.

## Quick install

Published on PyPI — no git access or SSH keys needed:

```bash
pipx install activecollab-mcp && activecollab-mcp setup
```

`setup` walks through onboarding interactively:

1. Runs `activecollab-mcp auth login`, prompting for your ActiveCollab URL,
   email, and password. It exchanges those for an API token via
   [`POST /issue-token`](https://developers.activecollab.com/api-documentation/v1/authentication.html)
   and stores the token at `~/.activecollab-mcp/credentials.json` (mode 0600).
   Skipped if you're already logged in.
2. Installs the boot-persistent background server (macOS LaunchAgent; ad-hoc
   daemon elsewhere) on `127.0.0.1:8787` — used by HTTP clients like Cursor.
3. Detects the AI clients installed on the machine and offers to register the
   server with each one (default yes, already-registered clients are left
   alone). Supported: **Claude Code, Codex, Grok, Pi, OMP (oh-my-pi),
   Gemini CLI, Cursor, opencode**. CLI-based clients are registered via their
   own `mcp add` command; the rest get a safe merge into their MCP config
   file (existing entries preserved, `.bak` backup written, unparseable
   configs skipped with a warning).
4. Offers to enable the daily auto-update cron (default yes).

Flags: `--all-clients` registers with every detected client without prompts;
`--skip-clients`, `--skip-service`, `--skip-auto-update` skip a step;
`--relogin` re-issues the token.

If you don't have `pipx`, run the bundled `install.sh` instead — it installs
pipx if needed, installs the package from PyPI, and runs `setup`:

```bash
/bin/bash install.sh
```

### Installer script flags

| Flag | Behavior |
| --- | --- |
| `--no-setup` | Install only; skip the interactive onboarding. |
| `--upgrade` | Force-reinstall/upgrade an existing pipx install of `activecollab-mcp`. |
| `--yes`, `-y` | Auto-confirm installing `pipx` if missing. Password prompts during `setup` are still interactive. |
| `--help`, `-h` | Print installer help. |

### Dev install (from a checkout)

```bash
pipx install .                     # production install in an isolated venv
pipx install --force .             # reinstall over an existing version
pip install -e ".[dev]"            # editable dev install in a venv
```

## Authenticate

The fastest way to get started — let the CLI exchange your email and password
for an API token (per the
[ActiveCollab `/issue-token` flow](https://developers.activecollab.com/api-documentation/v1/authentication.html)):

```bash
activecollab-mcp auth login
# ActiveCollab base URL: https://your-company.activecollab.com
# Email: you@example.com
# Password: ************
# Logged in as you@example.com.
# Token stored in /Users/you/.activecollab-mcp/credentials.json (mode 0600).
```

The token is saved to `~/.activecollab-mcp/credentials.json` with `0600`
permissions. From then on you can simply run `activecollab-mcp start` — no env
vars required.

Other auth commands:

```bash
activecollab-mcp auth show     # display stored credentials (token redacted)
activecollab-mcp auth logout   # remove stored credentials
```

Flag overrides if you want to script it (password is still prompted unless `--password` is passed):

```bash
activecollab-mcp auth login --url https://your.activecollab.com --email you@example.com
activecollab-mcp auth login --no-verify-tls         # self-signed certs
activecollab-mcp auth login --no-append-api-v1      # URL already includes /api/v1
```

## Configure (optional)

`auth login` is enough for most setups. If you'd rather manage credentials
yourself — e.g. for a headless deployment or CI — copy `.env.example` to
`.env` and set the env vars there. Env values **override** anything stored
by `auth login`.

```bash
cp .env.example .env
```

Credential precedence: process env → `.env` file → `~/.activecollab-mcp/credentials.json`.

| Variable | Purpose |
| --- | --- |
| `ACTIVECOLLAB_BASE_URL` | e.g. `https://your-company.activecollab.com` (auto-appends `/api/v1`). Optional when `auth login` has been run. |
| `ACTIVECOLLAB_API_KEY` | Personal API token. Optional when `auth login` has been run. |

Common optional:

| Variable | Default | Notes |
| --- | --- | --- |
| `ACTIVECOLLAB_AUTH_HEADER` | `X-Angie-AuthApiToken` | Override for non-standard auth setups |
| `ACTIVECOLLAB_AUTH_PREFIX` | _(empty)_ | e.g. `Bearer` for custom proxies |
| `ACTIVECOLLAB_APPEND_API_V1` | `true` | Set `false` if your base URL already includes `/api/v1` |
| `ACTIVECOLLAB_ALLOW_SELF_SIGNED` | `false` | Disable TLS verification (self-signed certs) |
| `ACTIVECOLLAB_TIMEOUT_SECONDS` | `15` | HTTP request timeout |
| `ACTIVECOLLAB_ENABLE_WRITES` | `true` | Toggle write tools off for a strictly read-only deployment |
| `ACTIVECOLLAB_NAME_CACHE_TTL` | `300` | Seconds to cache project/user lists used for fuzzy resolution |
| `ACTIVECOLLAB_MCP_HOST` | `127.0.0.1` | Bind host |
| `ACTIVECOLLAB_MCP_PORT` | `8787` | Bind port |
| `ACTIVECOLLAB_MCP_LOG_LEVEL` | `INFO` | `DEBUG`/`INFO`/`WARNING`/`ERROR` |
| `ACTIVECOLLAB_MCP_LOG_FILE` | `~/.activecollab-mcp/server.log` (daemon mode) | Optional file logging |

Legacy variable names from the JS version (`BASE_URL`, `API_TOKEN`,
`AUTH_HEADER`, `AUTH_PREFIX`) are still accepted as aliases.

## Use the CLI

```bash
activecollab-mcp --help              # list all subcommands
activecollab-mcp version             # print package version
activecollab-mcp setup               # one-shot onboarding (login + service + AI clients + auto-update)
activecollab-mcp auth login          # issue an API token from email+password
activecollab-mcp auth show           # show stored credentials (redacted)
activecollab-mcp auth logout         # remove stored credentials
activecollab-mcp config              # print effective config (api_key redacted)
activecollab-mcp run                 # run in the foreground (Ctrl-C to stop)
activecollab-mcp start               # detach and run as a background process
activecollab-mcp status              # JSON: { pid, running, bound_port, mcp_url, ... }
activecollab-mcp logs -f             # tail the server log
activecollab-mcp restart             # stop + start
activecollab-mcp stop                # graceful SIGTERM, fallback SIGKILL after 10s
activecollab-mcp update              # install the latest release from PyPI
activecollab-mcp update --check      # check only; don't install
activecollab-mcp update --auto       # also register a daily cron auto-updater
```

## Updating

`activecollab-mcp update` checks PyPI for the latest release and re-runs
`pipx install --force activecollab-mcp==<version>` when a newer one exists.
The update source is recorded in `~/.activecollab-mcp/updater.json`; legacy
pre-1.9 installs that recorded a git URL keep updating from git.

```bash
activecollab-mcp update              # install the latest PyPI release
activecollab-mcp update --check      # exit 0 if an update is available, 4 if up-to-date
activecollab-mcp update --to 1.11.0   # pin to a specific version
activecollab-mcp update --repo git@bitbucket.org:org/fork.git  # force git mode (dev)
```

### Auto-update via cron

```bash
activecollab-mcp update --auto                       # daily at 06:17
activecollab-mcp update --auto --schedule "0 4 * * 0" # weekly, Sunday 04:00
activecollab-mcp update --auto-status                 # show current schedule
activecollab-mcp update --auto-disable                # remove the cron entry
```

`--auto` writes a marker-bracketed block to your crontab so it can be removed
cleanly later. The cron line looks like:

```
# >>> activecollab-mcp auto-update >>>
17 6 * * * /Users/you/.local/bin/activecollab-mcp update --quiet >> /Users/you/.activecollab-mcp/update.log 2>&1
# <<< activecollab-mcp auto-update <<<
```

Output from auto-updates is appended to `~/.activecollab-mcp/update.log`.

### Running server is restarted automatically

After a successful `update`, the running server (if any) is bounced so it
picks up the new code in memory:

- LaunchAgent installed via `service install` → `launchctl kickstart -k`
- Manual daemon started via `activecollab-mcp start` → stop + start

Pass `--no-restart` to skip this (useful when scripting blue/green-style
rollouts or when you want to control the restart timing yourself). If no
server is running at update time, nothing happens — the new code will be
in effect the next time you start it.

The auto-update cron job also restarts the running server by default, so
clients pointed at `http://127.0.0.1:8787/mcp` will reconnect to the
upgraded server without manual intervention.

## Run on boot (macOS LaunchAgent)

`activecollab-mcp start` survives shell exit but **not** a reboot. To keep
the server running across logins and reboots, register a LaunchAgent:

```bash
activecollab-mcp service install      # writes plist + launchctl bootstrap
activecollab-mcp service status       # shows service + any manual daemon
activecollab-mcp service restart      # launchctl kickstart -k
activecollab-mcp service uninstall    # bootout + remove plist
```

What `service install` does:

- Writes `~/Library/LaunchAgents/com.efront.activecollab-mcp.plist` with
  `RunAtLoad=true` (start at login) and `KeepAlive=true` (auto-restart on
  crash).
- Stops any running `activecollab-mcp start` manual daemon first so the two
  don't fight over the port.
- `launchctl bootstrap gui/<uid> <plist>` loads + starts the service.

Service output is appended to `~/.activecollab-mcp/service.log`. After
install, manage with the `service` subcommands or `launchctl` directly.

> Linux: not yet implemented. Use an `@reboot` crontab entry pointing at
> `activecollab-mcp start`, or write a user-level systemd unit.

## Port handling

By default the server binds to `127.0.0.1:8787`. When another process
(another MCP server, a stale daemon, etc.) is already on that port:

- `activecollab-mcp run` fails fast with a clear error — foreground use
  should surface the conflict immediately. Pass `--auto-port` to opt into
  fallback behavior.
- `activecollab-mcp start` (daemon) defaults to `--auto-port`, so it scans
  the next 20 ports (`port+1` … `port+20`) and binds to the first free one.
  Disable with `--no-auto-port`.

The actually-bound port is written to `~/.activecollab-mcp/server.port`,
returned by `activecollab-mcp status` as `bound_port` / `mcp_url`, and
logged on startup. Set `ACTIVECOLLAB_MCP_AUTO_PORT=true` / `false` and
`ACTIVECOLLAB_MCP_PORT_SCAN_WINDOW=<n>` to configure via env.

State and credentials live in `~/.activecollab-mcp/`:

```
~/.activecollab-mcp/
├── credentials.json   # base_url + API token (mode 0600), written by `auth login`
├── updater.json       # source repo URL + last-check/install timestamps
├── server.pid         # PID of the running daemon (manual `start`)
├── server.port        # actual port the running server is bound to (post-fallback)
├── server.log         # rotating log file (5 MB × 5 backups)
├── service.log        # stdout/stderr captured when running under launchd
└── update.log         # auto-update log (appended by the cron entry)
```

The LaunchAgent plist (when installed via `service install`) lives at:

```
~/Library/LaunchAgents/com.efront.activecollab-mcp.plist
```

## Connect from an MCP client

### Cursor

`~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "activecollab": {
      "url": "http://127.0.0.1:8787/mcp"
    }
  }
}
```

### Claude Desktop

Claude Desktop currently uses stdio MCP servers, so for HTTP transport use a
client that supports it directly. To support stdio in the future you can run
the server inside an HTTP-to-stdio proxy.

### Health check

```bash
curl http://127.0.0.1:8787/healthz
# {"status": "ok", "name": "activecollab-mcp", "version": "0.2.0", "writes_enabled": true}
```

## Tools

### Read

| Tool | What it does |
| --- | --- |
| `health` | Calls `/info`. Use for connectivity smoke tests. |
| `whoami` | Identify who "me" / "I" refers to. Resolved from the token owner stored at `auth login`. Always call this when the user phrases a request in first person. |
| `list_my_tasks` | Convenience: whoami + list_user_tasks in one call. Use for "my tasks" / "what am I working on". |
| `describe_workspace` | Counts + top 10 projects/users for orientation. |
| `list_projects` | Project list with `format=compact` option. |
| `find_project_by_name` | Fuzzy resolve a name to a project ID (cached). |
| `get_project` | Single project by ID. |
| `get_project_overview` | Project + recent open tasks in one call. |
| `list_users` | User list, compact option. |
| `find_user_by_name` | Fuzzy resolve a name or email to a user ID (cached). |
| `get_user` | Single user by ID. |
| `list_user_tasks` | Tasks assigned to a user across projects. |
| `list_tasks` | Tasks in a project with filters. |
| `list_tasks_by_user_name` | Resolve names + return tasks in one call. |
| `search_tasks` | Client-side multi-field search within a project. |
| `get_task` | Single task. |
| `get_task_bundle` | **Task + comments + deduped attachments in one call.** Image metadata is returned first with temp-file fallbacks; small images are embedded as image content blocks. |
| `batch_get_tasks` | Parallel multi-task fetch with partial-failure reporting. |
| `list_task_comments` | Comments for a task. |
| `list_task_attachments` | Deduped attachments (inline + endpoint sources). |
| `list_task_image_attachments` | Image attachments only, with comment scan. |
| `get_attachment` | Attachment metadata, with a resolved `{normalized, raw}` summary so the LLM sees `name`, `mimeType`, `isImage`, `size`, `downloadUrl` directly. Metadata-only — use `get_image_attachment_content` to actually view image bytes. |
| `get_image_attachment_content` | Download an image and return metadata first, including `tempFilePath`; image content is embedded only when under `max_embedded_image_bytes`. Falls back through `download_url` / `preview_url` / `thumbnail_url` (with `--DOWNLOAD-TOKEN--` substitution) if the API endpoint fails. |
| `debug_attachment` | Diagnostic: report all download URL candidates and which strategy works for a given attachment, without returning bytes. Use when an image fetch fails. |
| `list_project_time_records` | Time records for a project, filterable by date. |
| `list_user_time_records` | Time records logged by a user. |

### Write (set `ACTIVECOLLAB_ENABLE_WRITES=false` to disable)

| Tool | What it does |
| --- | --- |
| `create_task` | Create a task with optional assignee, due date, labels, attachments, etc. |
| `update_task` | Partial update of task fields; can attach uploaded files. |
| `complete_task` | Mark complete. |
| `reopen_task` | Reopen a completed task. |
| `delete_task` | Move to trash (reversible via UI). |
| `post_task_comment` | Add a comment to a task; can attach uploaded files. |
| `upload_attachment` | Upload a local file, returns an upload `code` for `attach_uploaded_files`. |
| `log_time` | Create a time record on a project (and optionally a task). |

Attachment flow: `upload_attachment(file_path)` → returns `{code}` →
pass `attach_uploaded_files: ["<code>", …]` to `create_task`, `update_task`,
or `post_task_comment`. Attach codes promptly — ActiveCollab garbage-collects
unattached uploads.

## Versioning

This project follows semver. Current release is **v1.11.0**.

`activecollab-mcp update` installs the latest release published to PyPI.
Pin to a specific version with `--to`:

```bash
activecollab-mcp update --to 1.11.0      # downgrade or hold at a version
activecollab-mcp update --check         # see what's available without installing
```

When installing for the first time via pipx with a pinned version:

```bash
pipx install 'activecollab-mcp==1.11.0'
```

Releases are cut with `scripts/release.sh` (tests → build → twine upload →
git tag).

The version is queryable at runtime via `activecollab-mcp version` and the
`/healthz` endpoint.

## Development

```bash
pip install -e ".[dev]"
pytest                # unit tests (none yet — placeholders)
ruff check src
activecollab-mcp run  # runs against your real ActiveCollab via .env
```

## License

MIT
