Metadata-Version: 2.5
Name: projonatics-mcp
Version: 0.2.0
Summary: MCP server that lets Claude Code read and update projects, tasks, milestones and financials in a Projonatics instance
Project-URL: Homepage, https://github.com/Artiselite/projonatics
Project-URL: Repository, https://github.com/Artiselite/projonatics
Project-URL: Documentation, https://github.com/Artiselite/projonatics/blob/main/mcp/README.md
Author: Artiselite
Keywords: claude,claude-code,mcp,model-context-protocol,project-management,projonatics
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.12
Description-Content-Type: text/markdown

# Projonatics MCP Server

Exposes the Projonatics API to Claude Code (or any MCP client) as tools: read clients,
projects, tasks, milestones, payment milestones, financial summaries, dashboard and alerts,
and — unless registered read-only — create and update them too.

It talks to a running Projonatics backend over HTTPS using a personal API token, so it
sees and can change exactly what that user can in the app (role-based scoping applies), and
every change is recorded in the audit log under that user.

## 1. Get an API token

In the app: open your profile menu (top right) → **Claude Code (MCP)** → **Generate token**. The key is shown once.

Or mint one on the server:

```bash
# production (docker)
docker compose -f docker-compose.prod.yml exec backend python manage.py drf_create_token you@company.com

# local
cd backend && python manage.py drf_create_token you@company.com
```

`GET /api/me/api-token/` reports whether a token exists; `DELETE` revokes it; `POST` again rotates it.

## 2. Register with Claude Code

Published on PyPI as [projonatics-mcp](https://pypi.org/project/projonatics-mcp/); `uvx` installs it on first run:

```bash
claude mcp add -s user projonatics \
  -e PROJONATICS_URL=https://<your-domain> \
  -e PROJONATICS_TOKEN=<token> \
  -- uvx projonatics-mcp
```

From a local clone (development): `-- uv run --directory /absolute/path/to/projonatics/mcp projonatics-mcp`.
To pick up a newer release: `uv cache clean projonatics-mcp` and restart Claude Code.

Check with `claude mcp get projonatics`. Re-run `claude mcp remove projonatics -s user` then `add` to change the URL or token.

### Read-only mode

Add `PROJONATICS_READ_ONLY=1` to register only the read tools (Claude cannot create, edit or delete anything):

```bash
claude mcp add -s user projonatics \
  -e PROJONATICS_URL=https://<your-domain> \
  -e PROJONATICS_TOKEN=<token> \
  -e PROJONATICS_READ_ONLY=1 \
  -- uvx projonatics-mcp
```

This is a client-side switch: it decides which tools the server offers, not what the token can do. Accepted values: `1`, `true`, `yes`, `on`.

## Tools

### Read tools

| Tool | What it returns |
|------|-----------------|
| `whoami` | User the token belongs to (id, email, role) |
| `list_clients` | All clients |
| `list_projects(client_id?)` | Projects visible to the user |
| `get_project(project_id)` | Full project detail |
| `list_milestones(project_id?)` | Work milestones |
| `list_tasks(project_id?, status?, assignee_id?, assigned_to_me?, ordering?)` | Tasks with filters |
| `get_task(task_id)` | One task |
| `get_project_financials(project_id)` | Revenue vs cost summary, payment milestones, cost breakdown (admin) |
| `list_payment_milestones(project_id?)` | Invoicing schedule |
| `get_dashboard` | Metrics, pulse, today's focus, pending payments |
| `get_alerts` | Overdue tasks, upcoming milestones, overdue payments, budget warnings, ready-to-invoice |
| `list_team_members` | Team roster |
| `estimate_cost(project_id, num_engineers, num_days)` | Cost estimate (admin) |

### Write tools

Not registered when `PROJONATICS_READ_ONLY` is set. Update tools change only the fields you pass; pass `''` to clear a nullable field. Money is passed as decimal strings, dates as `YYYY-MM-DD`.

| Tool | What it does | Role |
|------|--------------|------|
| `create_task(milestone_id, title, …)` | New task under a work milestone (assignee, collaborators, dates, status, type) | admin, user |
| `update_task(task_id, …)` | Change status/assignee/dates/blocker note, or move to another milestone | admin, user |
| `delete_task(task_id, confirm)` | Permanent delete; refuses unless `confirm=true` | admin, user |
| `create_milestone(project_id, name, …)` | New work milestone; `amount` sets its paired payment milestone | admin, user |
| `update_milestone(milestone_id, …)` | Rename, due date, sequence, amount. Status is derived from tasks and not settable | admin, user |
| `delete_milestone(milestone_id, confirm)` | Permanent delete of the milestone, **all its tasks and its payment milestone**; refuses unless `confirm=true` | admin, user |
| `create_client(name, …)` / `update_client(client_id, …)` | Client contact details | admin |
| `create_project(client_id, name, …)` | New project under a client (status, type, category, rate, budget, leads) | admin |
| `update_project(project_id, …)` | Change project fields; a `manday_rate` change is tracked in rate history | admin |
| `archive_project(project_id, archived=true)` | Archive / un-archive (projects are never deleted) | admin |
| `update_payment_milestone(payment_milestone_id, …)` | Name, amount, expected date | admin |
| `mark_payment_received(payment_milestone_id, received_date, received=true)` | Record (or reverse) a payment | admin |
| `create_team_member(email, job_title, monthly_salary, …)` | Add a team member (creates a viewer login if the email is new) | admin |
| `update_team_member(team_member_id, …)` | Display name, job title, salary | admin |
| `deactivate_team_member(team_member_id, active=false)` | Deactivate / reactivate (team members are never deleted) | admin |

The token's role is enforced by the API: a `viewer` token gets 403 on every write, a `user`
token can write tasks and milestones on its projects, admins can write everything. The error
message says which role is needed. Every tool carries MCP annotations (`readOnlyHint`,
`destructiveHint`), so clients such as Claude Code can ask for confirmation appropriately.

## Development

```bash
cd mcp
uv sync
uv run pytest
```

Tests mock the HTTP transport; no backend needed.

## Releasing

Bump `version` in `pyproject.toml`, commit, then tag and push:

```bash
git tag mcp-v0.2.0 && git push origin mcp-v0.2.0
```

`.github/workflows/publish-mcp.yml` runs the tests, builds, and publishes to PyPI via trusted publishing.
