plutio-mcp
README.md
# plutio-mcp
A Model Context Protocol (MCP) server for [Plutio](https://www.plutio.com/) — the all-in-one business platform for CRM, projects, invoicing, proposals, contracts, forms, time tracking, and scheduling.
Gives Claude (and any other MCP client) structured, safe access to **every major resource in your Plutio workspace** via one concise tool per resource.
[](https://www.npmjs.com/package/@thecreativelabs/plutio-mcp)
[](https://github.com/thecreativelabs/plutio-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](#)
[](https://modelcontextprotocol.io/)
---
## What you get
- **31 resource-group tools** covering CRM, project management, time tracking, financial, documents, forms, communication, scheduling, knowledge, files, automations, and admin.
- **4 escape-hatch tools** (`plutio_api_reference`, `plutio_workspace_schema`, `plutio_rate_limit_status`, `plutio_request`) for edge cases, workspace introspection, and agent self-orientation.
- **4 analytics tools** (`plutio_mrr_snapshot`, `plutio_upcoming_renewals`, `plutio_invoice_aging`, `plutio_cashflow_forecast`) — single-call reports that replace multi-step manual aggregations.
- **1 compound lookup tool** (`plutio_client_360`) — fetches a person + their company + projects + invoices + subscriptions in one call.
- **5 MCP resources** (`plutio://people/{id}`, `plutio://companies/{id}`, `plutio://projects/{id}`, `plutio://invoices/{id}`, `plutio://tasks/{id}`) for clients that support resource browsing.
- **Read-only by default** — no accidental destructive writes until you explicitly enable them.
- **OAuth2 with auto-refresh** — client-credentials grant; tokens refresh ~1 minute before expiry.
- **Built-in rate limiting** — a token bucket capped at Plutio's 1000 req/hr default; requests queue transparently.
- **MongoDB-style filtering** — pass rich query objects directly (`$or`, `$regex`, `$gte`, etc.).
- **Bulk operations** for resources that support them.
- **Typed end-to-end** with Zod → JSON Schema on the wire.
---
## Quick start
### 1. Install
```bash
npm install -g @thecreativelabs/plutio-mcp
# or run without installing:
npx @thecreativelabs/plutio-mcp
```
### 2. Get Plutio API credentials
In your Plutio workspace go to **Settings → API Manager → Create Connection**. You'll get a **Client ID** and **Client Secret**. Copy them — you won't see the secret again.
### 3. Configure Claude Desktop / Claude Code
Add to your MCP config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, or `.mcp.json` / `.claude/settings.json` for Claude Code):
```json
{
"mcpServers": {
"plutio": {
"command": "npx",
"args": ["-y", "@thecreativelabs/plutio-mcp"],
"env": {
"PLUTIO_CLIENT_ID": "your_client_id",
"PLUTIO_CLIENT_SECRET": "your_client_secret",
"PLUTIO_BUSINESS": "your_workspace_slug",
"PLUTIO_READ_ONLY": "true"
}
}
}
}
```
> `PLUTIO_BUSINESS` is your workspace slug — the subdomain part of `<slug>.plutio.com`. If your OAuth client is tied to exactly one business, you can omit it and the server will auto-detect from the token response. When the client has access to multiple businesses, you must set it explicitly.
Restart Claude. You should now see `plutio_*` tools available.
### 4. Using ChatGPT?
`plutio-mcp` speaks HTTP natively since v0.7.0. Set `PLUTIO_MCP_HTTP=true` and the server listens on `http://<host>:<port>/mcp` instead of stdio. See [examples/10-chatgpt-setup.md](examples/10-chatgpt-setup.md) for the full walkthrough (local + ngrok, or hosted on Fly.io).
### 5. Enable writes (when you trust it)
Set `PLUTIO_READ_ONLY=false` to unlock `create`, `update`, `delete`, `archive`, `unarchive`, and bulk operations.
> **How writes actually work inside Plutio's API.** Plutio's public API only supports mutations via bulk endpoints — `PUT /{resource}/{id}` returns 403 everywhere. The MCP server handles this transparently: single-record `update`/`delete`/`archive`/`unarchive` actions route through `/bulk` internally, so from your perspective the tool interface is the usual single-record CRUD. You don't need to think about it — but it explains the version bump to 0.3.0.
---
## Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `PLUTIO_CLIENT_ID` | yes | — | OAuth2 client ID from Plutio |
| `PLUTIO_CLIENT_SECRET` | yes | — | OAuth2 client secret from Plutio |
| `PLUTIO_BUSINESS` | conditional | auto-detected from token | Workspace slug. Required when your OAuth client has access to multiple businesses |
| `PLUTIO_READ_ONLY` | no | `true` | When `true`, only `list`/`get` actions are exposed |
| `PLUTIO_API_BASE` | no | `https://api.plutio.com/v1.11` | Override for self-hosted / future API versions |
| `PLUTIO_OAUTH_URL` | no | `$PLUTIO_API_BASE/oauth/token` | Override for the token endpoint |
| `PLUTIO_MAX_REQUESTS_PER_HOUR` | no | `1000` | Raise if you have a higher-tier Plutio plan |
| `PLUTIO_LOG_LEVEL` | no | `info` | `error` / `warn` / `info` / `debug` |
| `PLUTIO_MCP_HTTP` | no | `false` | Set `true` to run as an HTTP server (needed for ChatGPT and remote deployments) |
| `PLUTIO_MCP_HTTP_PORT` | no | `8080` | HTTP port when in HTTP mode |
| `PLUTIO_MCP_HTTP_HOST` | no | `127.0.0.1` | HTTP host. Set to `0.0.0.0` for remote access (e.g. behind ngrok) |
| `PLUTIO_MCP_AUTH_TOKEN` | no | — | If set, HTTP requests must carry `Authorization: Bearer <token>`. Strongly recommended when exposing publicly |
| `PLUTIO_USER_PRESETS_DIR` | no | — | Path to a directory of your private contract/proposal preset JSON files. Loaded alongside built-ins. Use this for templates extracted from your own Plutio workspace via `plutio_template_to_preset`. See [examples/13](examples/13-template-extraction.md) |
See [`.env.example`](.env.example).
---
## Tool catalog
Every resource tool uses the same shape:
```
plutio_<resource>({ action: "list"|"get"|"create"|"update"|"delete"|"archive"|"bulk_*", ...args })
```
### CRM
- `plutio_people` — contacts, clients, leads, team members
- `plutio_companies` — organizations/accounts
- `plutio_notes` — free-form notes on any record
### Project management
- `plutio_projects`
- `plutio_tasks`
- `plutio_task_boards` · `plutio_task_groups` · `plutio_statuses`
### Time tracking
- `plutio_time_entries` — billable/non-billable logs (API path: `/time-tracks`)
- `plutio_time_categories`
### Financial
- `plutio_invoices`
- `plutio_invoice_subscriptions` — recurring invoices
- `plutio_transactions` — payments
### Documents
- `plutio_proposals`
- `plutio_contracts`
### Forms / leads / marketing
- `plutio_forms`
- `plutio_form_responses` — raw submissions before qualification
### Communication
- `plutio_conversations` · `plutio_comments` *(comments requires `entityType` + `entityId` in the query)*
### Scheduling
- `plutio_schedules` — availability windows
- `plutio_events` — calendar entries
### Knowledge
- `plutio_templates` · `plutio_canned_responses` · `plutio_wiki_pages` · `plutio_items`
### Files
- `plutio_file_folders` · `plutio_files`
### Analytics / admin
- `plutio_dashboards`
- `plutio_custom_fields`
- `plutio_automations` — Plutio's native node-based automation workflows *(list + create only; delete and single-GET are blocked by the REST API)*
- `plutio_businesses` *(workspace settings — read-only)*
### Analytics (v0.6.0)
- `plutio_mrr_snapshot` — active subscriptions aggregated into MRR, ARR, by frequency, top clients.
- `plutio_upcoming_renewals` — subs billing within the next N days (default 30), sorted by date.
- `plutio_invoice_aging` — unpaid invoices bucketed by days overdue (current / 30–60 / 60–90 / 90+).
- `plutio_cashflow_forecast` — projects expected income over N days by expanding each sub's recurrence.
### Compound (v0.6.0)
- `plutio_client_360` — one call, everything about a client: person + company + projects + invoices + subs.
### Proposal builder (v0.8.0)
- `plutio_list_proposal_presets` — see shipped presets (web-design, seo-services, consulting, creative-services, coaching).
- `plutio_proposal_from_preset` — create a fully-populated proposal (blocks + items + client link) in one call.
- `plutio_analyze_proposal` — inspect a past proposal's structure to mirror it for a new client.
### Contract builder (v0.9.0)
- `plutio_list_contract_presets` — shipped presets (`service-agreement`, `nda`).
- `plutio_contract_from_preset` — create a contract end-to-end with `{{ variable }}` substitution. **Note:** shipped presets contain TODO placeholders for legal clauses — replace before sending for signature.
### Template extractor (v0.10.0)
- `plutio_template_to_preset` — read any of your existing Plutio contract/proposal templates and emit a preset JSON ready for the builders. Run once per template, save to disk, your real prose becomes available to AI generation forever. See [examples/13](examples/13-template-extraction.md).
### Custom Pages + Custom Fields config (v0.10.2)
- `plutio_dashboard_pages` — list/get/create new pages on any of Plutio's role dashboards (Settings → Custom Pages). Update + delete blocked by REST (use UI).
- `plutio_create_dashboard_page_from_preset` — instantiate a page from a starter scaffold (`client-welcome`, `project-status`) with content blocks pre-populated. Plutio's native `{{ client.name }}` / `{{ business.name }}` tokens render at view time.
- `plutio_apply_custom_fields_bundle` — create a curated set of custom fields in one call. Shipped bundles: `lead-intake`, `client-onboarding`. Skips fields whose (entityType, title) already exists. See [examples/14](examples/14-dashboards-and-fields.md).
### Escape hatches
- `plutio_api_reference` — compact map of every tool + API path. Call first when unsure.
- `plutio_workspace_schema` — introspects custom fields. Returns `{entityType: {fieldTitle: {_id, inputType, options: {optionLabel: optionId}}}}`. Cached 5 min.
- `plutio_request` — raw API passthrough: `{ method, path, query?, body? }`.
- `plutio_rate_limit_status` — remaining requests in the current hour.
### MCP resources (v0.6.0)
Some MCP clients let you browse typed resources. The server advertises these URI templates:
- `plutio://people/{id}` · `plutio://companies/{id}` · `plutio://projects/{id}` · `plutio://invoices/{id}` · `plutio://tasks/{id}`
---
## Example prompts
Once configured, try these in Claude:
- "Use Plutio to list my 10 most recent invoices by due date."
- "Find all active projects where the client is 'Acme Corp'."
- "Show me form responses from the last 7 days that haven't been converted to people yet."
- "How much billable time did I log against project ABC in March?"
- *(with writes enabled)* "Create a follow-up task on project ABC due next Monday."
### 📖 See [`examples/`](examples/) for 6 fully worked end-to-end workflows
Each example shows the user prompt, the exact tool calls Claude will make, and a realistic answer. All verified against a live workspace.
---
## Filtering syntax
List actions accept a `query` object that supports Plutio's MongoDB-style operators:
```json
{
"action": "list",
"query": {
"status": "incomplete",
"createdAt": { "$gte": "2026-01-01" },
"$or": [
{ "tags": { "$in": ["lead", "hot"] } },
{ "companyId": "5f4..." }
]
},
"limit": 50,
"sort": "-createdAt"
}
```
---
## Custom fields
Plutio lets you define custom fields on any resource. To set them on create/update:
```json
{
"action": "update",
"id": "5f4...",
"data": {
"customFields": [
{ "_id": "<custom_field_id>", "value": "Enterprise" }
]
}
}
```
Fetch the custom-field definitions with `plutio_custom_fields` to see IDs and types.
---
## Development
```bash
# clone + install
git clone https://github.com/thecreativelabs/plutio-mcp.git
cd plutio-mcp
npm install
# dev mode (watch + reload)
npm run dev
# typecheck / build
npm run typecheck
npm run build
# run against your env
PLUTIO_CLIENT_ID=... PLUTIO_CLIENT_SECRET=... node dist/index.js
```
### Adding a new resource
All resources live in [`src/tools/registry.ts`](src/tools/registry.ts). Add a new entry and it's automatically exposed as a tool:
```ts
{
name: "my_resource",
path: "my-resource",
category: "crm",
description: "...",
archive: true,
bulk: true,
}
```
### Adding a specialized action
For endpoints that don't fit the CRUD pattern (e.g. sending an invoice, publishing a proposal, starting a timer), add a standalone tool file under `src/tools/` and register it in [`src/tools/index.ts`](src/tools/index.ts). See `escape-hatch.ts` for the pattern.
---
## Design choices
| Decision | Choice | Why |
|---|---|---|
| Tool granularity | **Resource-grouped** (one tool per resource with `action` param) | ~30 tools vs. 150+. Agents reason better over fewer, semantically meaningful tools. |
| Default write posture | **Read-only** | Public-safe by default; writes are an explicit opt-in. |
| Runtime | **TypeScript / Node 20+** | Lines up with MCP's primary ecosystem and enables `npx` distribution. |
| Rate limiting | **Client-side token bucket** | Respects Plutio's 1000/hr cap even when the agent goes wild. |
| Escape hatch | **`plutio_request` + `plutio_api_reference`** | Lets agents handle new endpoints without a server release. |
---
## Known limitations
**Lifecycle actions (Send Invoice, Publish Proposal, Sign Contract, Start/Stop Timer) are not supported by Plutio's public REST API.** Live probing confirmed:
- `PUT /invoices/bulk { status: "sent" }` returns HTTP 200 but silently drops the status field.
- `POST /invoices/{id}/send` and every variant returns 403.
- The Plutio web app handles these via Meteor methods over WebSocket/DDP, which is not part of the public API surface.
**Practical workarounds:**
1. Trigger lifecycle actions in the Plutio web UI, then use this server to read the resulting state.
2. If you've reverse-engineered a specialized endpoint yourself, invoke it via `plutio_request`.
3. For status-aware logic (e.g., "find all overdue invoices"), read the `status` and time fields — they're populated server-side once the Plutio app performs the transition.
If Plutio ever exposes these actions publicly, `plutio_request` will work without any server update.
## Roadmap
- [x] **Workspace introspection** — `plutio_workspace_schema` (v0.4.0)
- [x] **Transparent bulk routing** — single-record writes use `/bulk` internally (v0.3.0)
- [ ] Webhook support (once Plutio documents webhook endpoints)
- [ ] MCP resources (alongside tools) for read-heavy flows — expose `plutio://people/{id}` URIs
- [ ] Optional OpenAPI export of the generated schema
- [ ] SSE / streaming transport for hosted deployments
- [ ] Mocked-API integration test suite
Contributions welcome — see [Contributing](#contributing).
---
## Contributing
1. Fork the repo and create a feature branch.
2. `npm install && npm run typecheck` before you push.
3. Open a PR describing the problem you're solving and any API references.
Design principle: **don't grow the tool count blindly.** Before adding a tool, ask whether it can fit the existing resource-grouped pattern or belongs in `plutio_request`. The fewer tools, the better agents perform.
---
## License
MIT © TheCreativeLabs
## Disclaimer
This is an unofficial, community-maintained integration. Not affiliated with or endorsed by Plutio.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessUnresponsive