Metadata-Version: 2.5
Name: modern-collections
Version: 0.2.0
Summary: Official client, CLI, and MCP server for the Modern Collections external REST API
Project-URL: Homepage, https://moderncollections.io
Project-URL: Documentation, https://docs.moderncollections.io
Author: Modern Collections
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: accounts-receivable,api-client,cli,collections,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: mcp<2,>=1.29
Requires-Dist: rich>=13
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# modern-collections

The official Python client, command-line tool and MCP server for the
[Modern Collections](https://moderncollections.io) API. With a Modern Collections
API key you, a script, or your AI assistant can do what you do in the creditor
dashboard: place overdue invoices, find and follow accounts, record payments,
work disputes, attach documents, and pull what has been collected.

- **AI assistants** (Claude, Cursor, anything that speaks MCP) connect to the
  hosted server at `https://api.moderncollections.io/mcp`. No install.
- **Command line**: `modern-collections`.
- **Python**: `from mc_api import MCClient`.

## Connect your AI assistant

### No install: the hosted server

Point any MCP client that can send a custom header at:

- URL: `https://api.moderncollections.io/mcp` (streamable HTTP)
- Header: `Authorization: Bearer <your API key>`

```json
{
  "mcpServers": {
    "modern-collections": {
      "url": "https://api.moderncollections.io/mcp",
      "headers": { "Authorization": "Bearer ca_..." }
    }
  }
}
```

Then ask your assistant to run `account_whoami`. It names the account the key
reaches and says what, if anything, blocks placing (verification, the
agreement, the payout account).

### One command: Claude Code, Cursor, Claude Desktop

```bash
uv tool install modern-collections      # or: pip install modern-collections
modern-collections mcp install --client claude-code      # or cursor, claude-desktop
```

It asks for your key without echoing it, checks it, and only then writes the
client's configuration:

| `--client` | What it writes |
| --- | --- |
| `claude-code` | runs `claude mcp add --transport http` against the hosted server (`--scope user` by default) |
| `cursor` | `~/.cursor/mcp.json` |
| `claude-desktop` | Claude Desktop's config file: a local `modern-collections-mcp` entry, or with `--transport remote` the hosted server through `npx mcp-remote` |

Other servers already in those files are left alone. Restart Claude Desktop
after installing.

### Local server

The same tools run as a local stdio server, with the key in its environment:

```bash
MC_API_ENV=prod MC_API_KEY=ca_... modern-collections-mcp
```

`modern-collections-mcp --http` serves the same thing over streamable HTTP on
`http://127.0.0.1:8765/mcp`; the key then comes from each request's
`Authorization` header.

## Getting a key

Dashboard → **Settings → API Key**. The key is shown once, when it is created or
rotated; Modern Collections keeps only a one-way hash, so a lost key is rotated,
not recovered. Setup snippets are under **Settings → Developer access**.

Creditor keys look like `ca_<prefix>_<secret>`. Partner keys (`pa_<prefix>_<secret>`)
act for every creditor linked to the partner; see [Partner keys](#partner-keys).

## What your assistant can do

A default set of about 25 tools covers everyday work:

| Area | Tools |
| --- | --- |
| Setup | `account_whoami`, `guide`, `partner_creditor_list` |
| Placing and following | `placement_create`, `placement_list` (search by company or invoice number), `placement_status`, `audit_query`, `placement_context` |
| Changing a placement | `placement_amend`, `placement_update_debtor`, `placement_set_posture`, `placement_recall` |
| Payments | `payment_record`, `payment_list` |
| Disputes | `dispute_list`, `dispute_get`, `dispute_acknowledge`, `dispute_resolve` |
| Documents | `document_upload`, `document_upload_link`, `placement_documents`, `document_status` |
| Reporting | `analytics_summary`, `analytics_series`, `remittance_list`, `statement_list`, `statement_download_url` |

Everything else (settlement floors, contact choices, CSV and file intake,
evidence scoring, the recovery workspace, notifications, webhooks, QuickBooks)
is in the full set. Turn it on with `?tools=all` on the hosted URL (or the
header `X-MC-Tools: all`), `MC_API_MCP_TOOLS=all` for the local server, or
`--all-tools` on `mcp install`.

`document_upload_link` is for files on your computer that a chat assistant
cannot read: it returns a link, valid for 30 minutes, that you open in a
browser to upload the file onto the placement.

### Confirmations

Tools that contact a debtor, move money, change who is contacted, or make a
legal statement are marked destructive (`placement_create`, `payment_record`,
`placement_amend`, `placement_update_debtor`, `dispute_resolve`,
`placement_recall`, intake commits, and a few more). Your assistant asks before
using them, and most MCP clients ask you too. When you want your assistant to
act without asking, tell it so, or choose "always allow" for those tools in
your MCP client.

`placement_create` starts real outreach to a real company. There is no sandbox
on this API; `placement_recall` stops a placement that should not have been
opened.

## Install

```bash
uv tool install modern-collections      # CLI + local MCP server
pip install modern-collections          # or as a library
```

Python 3.11 or later. The PyPI project named `mc-api` is unrelated.

## Configure

Pick an environment and supply a key. There is no default environment, so a
forgotten setting can never send a key to the wrong place. The key is read
from `MC_API_KEY` and is never printed or logged.

| Variable | Values |
| --- | --- |
| `MC_API_ENV` | `prod` or `demo` |
| `MC_API_BASE_URL` | an explicit API URL (overrides `MC_API_ENV`) |
| `MC_API_KEY` | `ca_...` (creditor) or `pa_...` (partner) |
| `MC_API_CREDITOR` | partner keys only: the default creditor to act for |

## Command line

```bash
export MC_API_ENV=prod MC_API_KEY=ca_...

modern-collections whoami                                  # check your setup
modern-collections placements list --q "Acme"              # find by company or invoice number
modern-collections placements create --company "Acme Freight" --amount 7500.00 --email ap@acme.co \
  --invoice-number INV-4411 --invoice-date 2026-01-01 --due-date 2026-02-01 --state OH
modern-collections placements get <placement_id>
modern-collections placements audit <placement_id>         # what has happened on the account
modern-collections payments record <placement_id> --amount 500.00 --idempotency-key <bank-ref>
modern-collections disputes list --status open
modern-collections documents upload invoice-4411.pdf --placement <placement_id> --type invoice
modern-collections documents upload-link <placement_id> --type invoice
modern-collections analytics summary
modern-collections remittances list
```

`-o json` gives machine-readable output. It is a global option, so it goes
before the subcommand (`modern-collections -o json placements list`), as do
`--env`, `--base-url`, `--api-key`, `--creditor` and `--timeout`. Exit codes:
`0` success, `1` API or runtime error, `2` configuration or usage error.

### File an invoice, read it back, correct it

```bash
P=$(modern-collections -o json placements create --company "Acme Freight" --amount 7500.00 \
      --invoice-number INV-4411 --invoice-date 2026-01-01 --due-date 2026-02-01 \
      | jq -r .placement_id)

modern-collections documents upload invoice-4411.pdf --placement $P --type invoice
modern-collections placements context $P                    # what was read off the invoice
modern-collections placements amend $P --po-number PO-8871 --reason "read off the signed BOL"
modern-collections placements amend $P --amount 7250.00 --reason "credit memo applied"
modern-collections placements debtor $P --email ap@acmefreight.com
```

Amendments apply while outreach is running: the next call or email uses the
corrected placement. Every edit is kept as an append-only, audited record. One
amendment kind per call: `--amount`, `--notes`/`--clear-notes`, or any mix of
the invoice fields (`--invoice-number`, `--invoice-date`, `--due-date`,
`--po-number`, `--service-date`, `--currency`, `--written-contract`). Status is
changed through `payments record`, `disputes resolve` or `placements recall`.

## Python

```python
from mc_api import MCClient, NotFoundError

with MCClient(env="prod", api_key="ca_...") as api:
    print(api.whoami()["blocking"])

    placement = api.create_placement(
        {
            "invoice_amount": "7500.00",
            "invoice_number": "INV-4411",
            "invoice_date": "2026-01-01",
            "due_date": "2026-02-01",
            "debtor": {"company_name": "Acme Freight", "primary_email": "ap@acme.co"},
        },
        idempotency_key="INV-4411",
    )

    for row in api.iter_placements(q="Acme", status="in_outreach"):
        print(row["id"], row["invoice_amount"])

    try:
        api.get_placement("00000000-0000-0000-0000-000000000000")
    except NotFoundError:
        pass
```

Errors are `ApiError` subclasses: `AuthError` (401/403), `NotFoundError`,
`ValidationError` (400/422, with field errors flattened to one line) and
`RateLimitedError` (429, with `retry_after`). `iter_placements`,
`iter_documents`, `iter_payments`, `iter_remittances`, `iter_statements`,
`iter_exports`, `iter_audit` and `iter_partner_creditors` walk every page lazily.
The package ships type hints (`py.typed`).

## Partner keys

A partner key reaches every creditor linked to the partner through one
connection, one creditor per call:

- MCP: run `partner_creditor_list`, then pass `creditor=` on each call (the
  creditor's id, its external reference, or its exact name).
- CLI: `modern-collections partner creditors`, then `--creditor <id or ref>`, or
  set `MC_API_CREDITOR`.
- Python: `MCClient(api_key="pa_...", creditor="<id or ref>")`.

A call without a creditor gets a `400` from the API; the client never guesses.
With a creditor key the `creditor` argument is ignored.

## Agent skills

Two skills teach an assistant how to work the account: `modern-collections`
(everyday operations) and `collections-ar-triage` (turn an AR aging export into a
reviewed batch of placements). They ship in this package:

```bash
modern-collections skills install              # into ~/.claude/skills
modern-collections skills install --dir <project>/.claude/skills
```

Over MCP the same text is available as prompts, as resources
(`modern-collections://skills/<name>`), and through the `guide` tool.

## Kept out of reach on purpose

- **Accepting the master service agreement** is a person's click in the
  dashboard. The API refuses it from an API key, and no tool or command offers it.
- **Rotating the API key** and **setting the webhook URL** need a signed-in
  dashboard session, so a leaked key cannot replace itself or redirect events.
- **Signing secrets stay out of the model.** The intake webhook secret can be
  rotated and used from the CLI and the Python client, not through MCP.

## Safety properties

- The API key lives only in the request `Authorization` header. It is kept out
  of `repr()`, exceptions and logs, and so is the creditor scope; neither is
  sent on the unauthenticated `health()` check.
- Explicit connect, read and write timeouts.
- Reads are retried on 429 (honouring `Retry-After`), 502, 503 and 504. A write is retried
  only when it carries an idempotency key, which the server replays; other
  writes are sent once, so a create or a payment is never silently duplicated.
  The MCP server derives a key for `placement_create` and `payment_record` when
  none is given, so a retried tool call replays instead of acting twice.
- Client-side checks on amendment fields and enum-like arguments, so a typo
  fails locally.
- Signed structured intake is HMAC'd over the exact bytes sent.

## License

Apache License 2.0. See `LICENSE`.
