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_recallis 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
/mcpdifferences:agreement_acceptis 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 callPOST /v1/agreements/acceptfrom your own system. The binary tools (settings_agent_skills,agreement_certificate) write a file only over stdio; over HTTP they return the bytes ascontent_base64with a suggestedfilename.
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
Authorizationheader — excluded fromrepr(), exceptions, and logs. Same treatment for the creditor scope (creditor=/MC_API_CREDITOR): never inrepr(), 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/DELETEare 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/structuredis 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.