Upmind MCP Server
# Upmind MCP Server v3
Model Context Protocol server for the [Upmind](https://upmind.com) platform: bundled API documentation search plus **97 live admin tools** covering the daily operations of a hosting business — tickets, billing, services, clients, orders, provisioning, promotions, security, and reporting.
Built for and verified against a production Upmind organisation.
## Highlights
- **97 tools** across 15 toolsets (read-only / write / destructive all annotated)
- **Secure auth CLI** — session tokens stored in the **macOS Keychain**, never plaintext; `upmind-mcp token set` imports a fresh token straight from your clipboard
- **Read-only mode** (`UPMIND_READ_ONLY=true`) blocks every write tool
- **Token-efficient responses** — list tools return compact summaries (pass `full: true` for raw records)
- **Resilient API client** — retry with backoff on 429/5xx, timeouts, Upmind field-error extraction, 401 renew-and-replay
- **Refreshable docs** — `npm run update-docs` pulls the latest reference from [apidocs.upmind.com](https://apidocs.upmind.com/) (1,270+ endpoints incl. the web hosting module)
## Install
### Without cloning (recommended)
```bash
claude mcp add --transport stdio --scope user upmind -- npx -y github:spidyhost-crm/upmind-mcp-server
```
npx fetches and builds the server automatically; your config and tokens live in `~/.upmind-mcp/` + the Keychain, so updates never touch them.
### From a clone
```bash
git clone git@github.com:spidyhost-crm/upmind-mcp-server.git && cd upmind-mcp-server
npm install && npm run build
claude mcp add --transport stdio --scope user upmind -- node $(pwd)/dist/index.js
```
## Authentication
Your admin token controls the whole business — treat it accordingly. Two modes:
### Browser temp-token mode (recommended — maximum control)
Nothing long-lived is ever stored. Session tokens expire after ~1 hour.
1. Log into your Upmind admin as usual
2. DevTools → Network → any `api.upmind.io` request → copy the Bearer token
3. `npx upmind-mcp token set` — reads the clipboard, validates against the live API, stores in the **macOS Keychain**
When it expires, tools return a clear error telling you to repeat step 2–3.
### Auto-renewal mode (convenience)
`upmind-mcp login` (email + password + 2FA) stores a session **and refresh token** in the Keychain; the server renews access tokens itself and rotates the stored pair. Revoke anytime by logging that session out in Upmind or `upmind-mcp token clear`.
### CLI reference
```
upmind-mcp token set|show|clear manage the stored token (Keychain-backed)
upmind-mcp login full login with auto-renewal
upmind-mcp whoami verify credentials against the live API
upmind-mcp status show config, backend, token expiry
```
## Security model
- **Keychain storage** (macOS): tokens encrypted at rest by the OS; `UPMIND_TOKEN_BACKEND=file` falls back to `~/.upmind-mcp/tokens.json` chmod 600
- **Least privilege**: create a dedicated staff/API user in Upmind with a restricted role for this server instead of using an owner account
- **IP whitelisting**: use the built-in `list/add/remove_whitelisted_ip` tools to restrict org admin access to known IPs — a leaked token becomes useless elsewhere. Add your current IP first!
- **Read-only mode**: `UPMIND_READ_ONLY=true` hard-blocks all 48 write tools server-side
- **Destructive-tool annotations**: cancels/refunds/deletes carry `destructiveHint` so MCP clients can require confirmation
- Never paste tokens into chats or commit them — `.env`, `.tokens.json` are gitignored; the CLI clipboard flow avoids both
## Configuration
Config is layered: real env vars → project `.env` → `~/.upmind-mcp/config.env`.
| Env | Effect |
|---|---|
| `UPMIND_ORIGIN` | Your admin URL (e.g. `https://secure.spidyhost.com`) — required for OAuth flows |
| `UPMIND_API_BASE` | Default `https://api.upmind.io/api/admin` |
| `UPMIND_READ_ONLY=true` | Every write tool refuses to run |
| `UPMIND_DEFAULT_BRAND_ID` | Applied when tools accept `brand_id` and none is given |
| `UPMIND_TOOLSETS=tickets,billing` | Expose only selected toolsets (docs always on) |
| `UPMIND_TOKEN_BACKEND` | `keychain` / `file` (default auto) |
| `UPMIND_API_TOKEN` / `UPMIND_REFRESH_TOKEN` / `UPMIND_USERNAME`+`UPMIND_PASSWORD` | Env-based auth alternatives to the CLI |
| `UPMIND_TIMEOUT_MS` / `UPMIND_MAX_RETRIES` / `UPMIND_DEBUG` | Client tuning |
## Tool catalog
**docs** — `search_endpoints`, `get_endpoint_details`, `list_api_groups`, `get_group_endpoints`
**brands** — `list_brands`, `get_brand`
**catalog** — `list_categories`, `create_category`, `list_products`, `get_product`, `create_product`, `update_product`, `duplicate_product`, `copy_product_to_brand`, `reorder_products`, `list_pricelists`, `get_product_prices`, `sync_pricing`, `list_currencies`, `get_upgrade_paths`, `set_upgrade_paths`, `remove_upgrade_path`
**provisioning** — `list_provision_fields`, `get_provision_field_values`, `update_provision_field_values`, `list_provision_configs`, `list_provision_functions`
**contracts** (services) — `list_contracts`, `get_contract`, `count_active_services`, `cancel_contract`, `cancel_contract_product`, `activate_contract_product`, `set_contract_product_renewal`, `update_contract_product_dates`, `update_contract_product_price`, `change_contract_product` (upgrades, with `dry_run`), `trigger_provision`
**clients** — `list_clients`, `get_client`, `create_client`, `update_client`, `list_client_accounts`, `reset_client_password`, `resend_client_verification`
**orders** — `list_orders`, `get_order`, `create_order` (quick order → invoice), `cancel_order`, `convert_order_to_invoice`
**billing** — `list_invoices`, `get_invoice`, `credit_invoice`, `apply_client_credit`, `refund_invoice`, `create_renewal_invoice`, `refresh_invoice`, `list_payments`, `add_payment`, `refund_payment`, `list_gateways`, `get_wallet_balance`, `list_wallet_transactions`, `refund_from_wallet`
**tickets** — `list_tickets`, `get_ticket`, `get_ticket_messages`, `create_ticket`, `reply_ticket` (incl. private staff notes), `update_ticket`, `assign_ticket`, `list_ticket_departments`
**marketing** — `list_promotions`, `get_promotion`, `create_promotion`, `update_promotion`, `disable_promotion`, `list_leads`, `create_lead`, `update_lead`, `convert_lead_to_client`
**emails** — `list_email_history`, `get_email`, `resend_email`
**webhooks** — `list_webhooks`, `list_webhook_events`, `create_webhook`, `update_webhook`, `delete_webhook`
**users** — `list_users`, `get_user`
**reports** — `get_stats`, `list_reports`, `run_report`
**security** — `list_whitelisted_ips`, `add_whitelisted_ip`, `remove_whitelisted_ip`
## Upmind API knowledge baked in
Verified against production:
- **Admin prefix**: staff tokens call `https://api.upmind.io/api/admin/...`; doc URLs starting `admin/` map to the same path with the prefix stripped.
- **Pagination**: Upmind returns 10 results by default — always pass `limit`/`offset`; responses carry `total`. The client's `getAll()` auto-paginates (capped at 1,000 items).
- **Filters**: `filter[field]=value`, e.g. `filter[status.code]`, `filter[product.id]`, `filter[contract.client_id]`. ⚠️ `filter[status]=...` on invoices causes a **500 database error** — always filter status via `status.code`.
- **Status codes** are prefixed: `contract_active`, `contract_suspended`, `invoice_unpaid`, `invoice_paid`, `ticket_in_progress`, `ticket_waiting_response` (short forms like `unpaid` also match).
- **Relations** load via `with=a,b,c`; raw list rows only carry `*_id` fields (e.g. tickets have `status_id` but no status object without `with=status`).
- **OAuth**: `POST https://api.upmind.io/oauth/access_token` with `admin_password` / `admin_twofa` / `refresh_token` grants; the `Origin` header of your admin domain is required when no oauth client id is sent. Access tokens live ~1 hour; refresh tokens rotate on every use.
- **Emails**: the API exposes delivery metadata (`sent`, `bounced`, `error_message`) but not rendered bodies.
## Architecture
```
src/
├── index.ts # stdio server entry (existing MCP registrations)
├── cli.ts # upmind-mcp CLI: token/login/whoami/status + serve
├── serve.ts # shared server bootstrap
├── server.ts # assembles McpServer from the tool registry
├── registry.ts # ToolDef type + shared schema fragments
├── config.ts # layered env/.env/~/.upmind-mcp config
├── api/
│ ├── client.ts # HTTP client: retries, timeouts, error shaping
│ ├── auth.ts # TokenManager: OAuth renewal, expiry handling
│ └── token-store.ts # Keychain (macOS) / file token storage
├── docs/ # documentation cache, search, and doc tools
├── tools/ # one file per domain, exporting ToolDef[]
└── utils/compact.ts # response summarisation helpers
scripts/
├── update-docs.mjs # refresh bundled docs (npm run update-docs)
└── smoke.mjs # read-only live smoke test (npm run smoke)
data/ # bundled apidoc JSON (refreshable)
```
Adding a tool = adding one entry to a domain file. `server.ts` handles registration, read-only enforcement, annotations, and error shaping centrally.
## Development
```bash
npm run build # compile
npm run typecheck # type check only
npm run smoke # read-only smoke test against the live API
npm run update-docs # refresh bundled API docs
```
## License
MIT
TDQS
Scored across 97 tools
Most tools have clear, distinct purposes with detailed descriptions, and each targets a unique resource+action. However, the large number of similar product/contract/update tools (e.g., update_contract_product_dates vs update_contract_product_price) creates some risk of misselection, though descriptions mitigate this.
All tools follow a consistent snake_case verb_noun pattern (list_, get_, create_, update_, delete_, etc.). Minor verb variations like change, sync, and apply do not break the overall predictable naming convention.
With 97 tools, this is an extreme mismatch for an MCP server. The sheer number overwhelms an agent's ability to efficiently select the right tool, far exceeding the recommended 3-15 range and even the 25+ threshold.
The tool set covers comprehensive CRUD and lifecycle operations for clients, products, contracts, orders, invoices, payments, and tickets. Minor gaps exist (e.g., no delete_product, no create_department, no update_invoice), but agents can typically work around these omissions.