Skip to main content

modern-collections — Modern Collections client, CLI, and MCP server

One hardened HTTP client over the Modern Collections external REST API (the creditor-API-key surface), with two front-ends:

  • CLI — modern-collections
  • MCP server — modern-collections-mcp (stdio, or streamable HTTP on the API at /mcp)

The client depends only on the public HTTP contract, so it ships independently of the backend application.

Connect an agent

No install. Point an MCP client at the hosted server:

  • URL: https://api.moderncollections.io/mcp (streamable HTTP)
  • Header: Authorization: Bearer <creditor API key>
  • Partner keys also send X-MC-Creditor: <creditor uuid or external ref>

GET /v1/public/mcp returns that card for whatever host you are calling. To file an invoice: placement_create (confirm first — this starts real outreach), then document_upload with document_type invoice, then document_process. placement_recall stops a placement that should not have been opened.

Install

The install below is only for the local CLI and the stdio server. The hosted URL does not need it.

uv tool install modern-collections   # CLI `modern-collections` and server `modern-collections-mcp`
# or: pip install modern-collections

Requires Python 3.11 or later. The PyPI project mc-api is unrelated; this package is modern-collections.

Configure

Pick an environment (no production default — by design) and supply a creditor API key. The key is read from MC_API_KEY and is never printed or logged.

Getting a key. Dashboard → Settings → API Key. Only the bcrypt hash is stored, so an existing key can never be re-displayed — Settings shows the key id (ca_<prefix>) and a rotate action that reveals a fresh secret exactly once. A lost key is rotated, not recovered. Setup snippets for the CLI, the MCP server, and the agent skills live under Settings → Developer access.

Var Values
MC_API_ENV local | staging | demo | prod
MC_API_BASE_URL explicit base URL (overrides MC_API_ENV)
MC_API_KEY ca_<prefix>_<secret> (creditor) or pa_<prefix>_<secret> (partner)
MC_API_CREDITOR creditor UUID or external reference — only with a partner key

Environment → base URL map:

env base URL
local http://localhost:8000
staging https://web-staging-c8aa.up.railway.app (Modern Collections internal)
demo https://app.demo.moderncollections.io
prod https://api.moderncollections.io

Partner keys and the creditor scope

A partner key (pa_<prefix>_<secret>) acts on behalf of several linked creditors instead of just one, so every request has to say which creditor it is for. Pass --creditor, set MC_API_CREDITOR, or construct MCClient(creditor=...) with a creditor UUID or its external_creditor_ref; the client sends it as the X-MC-Creditor header on every request. An ordinary creditor key (ca_...) is already scoped to one creditor and ignores the header — creditor is simply a no-op for it, so day-to-day creditor scripts and skills need no changes.

export MC_API_ENV=prod MC_API_KEY=pa_... MC_API_CREDITOR=<creditor-uuid>
modern-collections placements list                          # scoped to that one creditor

# or override per invocation
modern-collections --creditor <other-creditor-uuid-or-ref> placements list

Omitting --creditor/MC_API_CREDITOR with a partner key gets a 400 from the API (X-MC-Creditor header required) — the client does not guess.

CLI

export MC_API_ENV=prod MC_API_KEY=ca_...

modern-collections health                                   # reachability
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
modern-collections placements list --status in_outreach
modern-collections placements get <placement_id>
modern-collections payments record <placement_id> --amount 500.00 --idempotency-key <bank-ref>
modern-collections -o json analytics summary
modern-collections disputes ack <placement_id> --note "spoke with AP"
modern-collections improve                                  # placements whose file needs attention
modern-collections improve <placement_id>                   # per-placement plan (missing documents)
modern-collections documents upload invoice-4411.pdf --placement <placement_id> --type invoice
modern-collections documents list --placement <placement_id>
modern-collections documents process <document_id>          # classify/extract/match now

File an invoice, read it back, then correct it

The whole loop is unified on the placement id — submit the invoice and any supporting PDFs against it, read back what was extracted, then amend:

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 documents upload bol-4411.pdf      --placement $P --type bol
modern-collections documents process <document_id>          # or let the worker pick it up

modern-collections placements context $P                    # what the platform actually read
modern-collections placements documents $P                  # everything on file

# fix what the extraction got wrong — append-only and audited
modern-collections placements amend $P --po-number PO-8871 --service-date 2026-01-20 \
      --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 are accepted mid-collection: the placement planner re-reads the row on its next tick, so a correction reaches the next call or email without any further action. Every edit writes an append-only history row plus an audit event.

One amendment kind per invocation — --amount, --notes/--clear-notes, or any combination of the contract-metadata flags (--invoice-number, --invoice-date, --due-date, --po-number, --service-date, --currency, --written-contract). A placement's status is deliberately not amendable here; use payments record, disputes resolve, or placements recall, which own the side effects.

Everything else

modern-collections intake csv-preview aging.csv             # bulk placement from an AR aging file
modern-collections intake csv-commit <preview_id> --mappings '{"amount":"Balance"}'
modern-collections evidence get <placement_id>              # how well-documented the file is
modern-collections disputes list --status open
modern-collections remittances list                         # money owed back to you
modern-collections statements list && modern-collections exports create placements
modern-collections qbo status                               # QuickBooks link
modern-collections agreements current

-o json emits machine-readable output; the default is a human table. It is a global option, so it goes before the subcommand (modern-collections -o json analytics summary, not modern-collections analytics summary -o json) — the same is true of --env, --base-url, --api-key, --creditor, and --timeout. Exit codes: 0 success, 1 API/runtime error, 2 configuration/usage error.

MCP server

Hosted, on the API, for any MCP client that can take a URL. Send the creditor key on the request. The process does not keep a key of its own, and a request without Authorization: Bearer cannot fall through to one.

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

A partner key also sends X-MC-Creditor: <creditor uuid or external ref>.

Local stdio (the same tools, key from the environment):

MC_API_ENV=local MC_API_KEY=ca_... modern-collections-mcp

modern-collections-mcp --http serves the same streamable-HTTP endpoint on http://127.0.0.1:8765/mcp for a machine that should not use the hosted URL. Set MC_API_ENV or MC_API_BASE_URL in that process. The key still comes from the client's Authorization header, not from MC_API_KEY.

The MCP server exposes the same surface as the CLI — one tool per client method, grouped as: placements (placement_create, placement_status, placement_list, placement_recall, placement_set_posture, placement_calls, placement_emails, placement_payments, placement_compliance_decision, placement_refresh_enrichment), editing (placement_amend, placement_update_debtor), documents (document_upload, document_list, document_status, document_process, document_match_decide, document_batch, placement_documents, placement_context, placement_context_rebuild), improve and evidence (placement_improve, improve_overview, evidence_*), disputes (dispute_*), money (payment_*, remittance_*, statement_*, export_*), reporting (analytics_summary, analytics_series, audit_query), settings (settings_*, notification_settings_*), integrations (qbo_*), agreements (agreement_*), and intake (intake_*).

Tools that start real outreach: placement_create, intake_csv_commit, intake_file_confirm, intake_draft_resolve (approve), intake_structured_submit. There is no sandbox on this surface. placement_recall is the stop button and is exposed as a tool for exactly that reason.

Binding or destructive: agreement_accept (legally binding), dispute_resolve, qbo_disconnect, intake_webhook_rotate_secret (invalidates the previous secret), notification_settings_replace (replaces the whole grid).

Hosted /mcp differences: agreement_accept is refused there, because the acceptance record would carry the server's loopback address instead of the signer's; accept the MSA in the dashboard or call POST /v1/agreements/accept from your own system. The binary tools (settings_agent_skills, agreement_certificate) write a file only over stdio; over HTTP they return the bytes as content_base64 with a suggested filename.

Every tool also takes a trailing creditor argument (creditor UUID or external reference) — required to reach any data at all when MC_API_KEY is a partner key (pa_...); harmless and unnecessary with an ordinary creditor key. It falls back to MC_API_CREDITOR when omitted, so a partner-scoped MCP server can be registered once per creditor without repeating it on every tool call.

The document loop closes from an agent session: document_upload with a placement_id, then document_process, then placement_context to read back the extracted fields, then placement_amend to correct anything wrong — including po_number and service_date. Amendments apply mid-collection and are audited.

The document/improve tools close the evidence loop from an agent session: improve_overview ranks the placements whose collection file needs attention, placement_improve returns one placement's plan (missing documents with where-to-look and search hints), and document_upload sends a found file — either content_base64 or file_path read from a directory explicitly allowed with MC_API_MCP_UPLOAD_ROOT. Encoding is handled client-side in the MCP server; the API itself takes the raw bytes (POST /v1/documents with Content-Type + X-Filename), so there is no base64 endpoint and no inflated payload on the wire.

mcp.example.json is the stdio config. Destructive tools set MCP destructiveHint (outreach starters, placement_recall, placement_amend, agreement_accept, dispute_resolve, qbo_disconnect, intake secret rotation, notification replace, intake address delete). For Claude Code stdio:

claude mcp add modern-collections -- modern-collections-mcp
# then set MC_API_ENV / MC_API_KEY in the server's environment
# with a partner key (pa_...), also set MC_API_CREDITOR to the linked creditor
# optional: set MC_API_MCP_UPLOAD_ROOT=/path/to/approved-documents to enable file_path uploads

Agent skills

skills/ holds Claude Code / Claude Desktop skills that drive this same API — modern-collections (core operations) and collections-ar-triage (AR aging export → reviewed placement batch). Partners download them from Settings → Developer access (GET /v1/settings/agent-skills.zip).

Client library

from mc_api import MCClient

with MCClient(env="prod", api_key="ca_...") as api:
    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"},
        }
    )
    print(api.get_placement(placement["placement_id"]))

With a partner key, add creditor= (or set MC_API_CREDITOR) to scope every call made through that client instance to one linked creditor:

with MCClient(env="prod", api_key="pa_...", creditor="<creditor uuid>") as api:
    print(api.list_placements())

Surface parity

The client, the CLI, and the MCP server cover the same creditor-API-key surface, 1:1. tests/test_parity.py holds the mapping table and fails the build if a client method gains no CLI command and no MCP tool (or vice versa), so the three front-ends cannot silently drift apart.

One endpoint is deliberately not reachable from any of them: POST /v1/settings/api-key/rotate is gated on a dashboard session and rejects ca_* bearers by design, so a leaked key cannot mint its own replacement. Rotate from Dashboard → Settings → API Key. MCClient.rotate_api_key() exists only to raise with that explanation rather than let a caller guess.

Safety properties

  • The API key lives only in the request Authorization header — excluded from repr(), exceptions, and logs. Same treatment for the creditor scope (creditor= / MC_API_CREDITOR): never in repr(), and both it and the API key are stripped from the one unauthenticated request the client makes (health()).
  • Explicit connect/read/write timeouts.
  • Only idempotent GETs are retried (429/5xx, exponential backoff); POST/PATCH/ DELETE are never auto-retried, so a create or payment can't be duplicated.
  • No production default environment.
  • Client-side validation for the amendment allow-list and enum-ish arguments, so a typo fails locally instead of burning a round-trip.
  • POST /v1/intake/structured is HMAC-signed over the exact bytes sent — the body is serialised once and posted as raw content so the signature can't desync from it.

Test

uv run --with 'mcp<2' --with typer --with pytest --with pytest-asyncio \
  pytest clients/mc_api/tests -q

Metadata

Release files for modern-collections 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modern-collections 0.1.0
File Size Uploaded Provenance Actions
modern_collections-0.1.0.tar.gz 54.6 kB Without provenance Details for modern_collections-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for modern-collections 0.1.0
File Interpreter ABI Platform Actions
modern_collections-0.1.0-py3-none-any.whl Python 3 none any Details for modern_collections-0.1.0-py3-none-any.whl

Total release size: 112.7 kB

Release files / modern_collections-0.1.0.tar.gz

Download URL modern_collections-0.1.0.tar.gz
Size 54.6 kB
Tags Source
SHA-256 checksum
How to use checksums
df7b20383fb48e3bfba1726777c562d18784372c1ba5329efd2a410d6b4909cb
BLAKE2b-256 checksum
How to use checksums
a4509b7f01651f57d1554fae87586240f6884ee549a2bf5a25158cb37b0a47d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / modern_collections-0.1.0-py3-none-any.whl

Download URL modern_collections-0.1.0-py3-none-any.whl
Size 58.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
863e4995744f8c2ce2257152d2d4635b038ef188296c002da88ef17e84aafadb
BLAKE2b-256 checksum
How to use checksums
c15593068789bc8dddf03c154bdc88cbae2c2899d31a8badcdaaff7290d3c79e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for modern-collections 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page