Skip to content

AI assistants (MCP)

Quiddly exposes a remote MCP server at https://$HOST/mcp. An MCP client - Claude, an IDE agent, or any assistant that speaks the protocol - signs in as a Quiddly user, the user approves the connection on a consent screen, and the assistant can then call a curated set of read-only tools against that user’s data.

Three things hold for every call:

  • It is the user’s view. Every tool applies the same tenant and client scoping as the REST API, so a client-scoped user only ever reaches its own client’s rows.
  • It is the user’s rights. No new rights exist for MCP. Each tool reuses the right that already gates the same data over REST, so granting a role MCP access is the same decision as granting it the endpoint.
  • It only reads. There are no write tools.
  • It is your provider’s copy. What the assistant reads leaves Quiddly and is processed by your model provider under your terms with them. See Where the data goes.
  • MCP enabled on your tenant. Two switches, both set by Quiddly: the Developer module, the same opt-in that gives programmatic access, and the MCP setting, which switches this surface on its own. Without either, every MCP and OAuth route answers 404. Contact your Quiddly account manager to enable MCP, or to switch it off again without affecting your other integrations.
  • A Quiddly user login. MCP signs in through the same hosted login the Operations Portal uses on your tenant. Service accounts cannot sign in here, and neither can users whose roles are managed in your own identity provider through Enterprise Connect. On a tenant where the client portal signs in through a different connection than the Operations Portal, only Operations Portal users can connect.
  • The rights the tools need, on that user’s role. See Tools for the right each tool requires.

Point the client at https://$HOST/mcp, where $HOST is the API host of the tenant you are reading this on. Everything else - discovery, registration and login - happens automatically.

Terminal window
claude mcp add --transport http quiddly https://$HOST/mcp

Then run /mcp in the session and pick quiddly to start the login.

What happens next:

  1. The client registers itself with Quiddly and opens your browser.
  2. You sign in with your Quiddly user on the hosted login page.
  3. Quiddly shows a consent screen naming the client and the address it will be sent back to. Click Approve. If you did not start this login, or you do not recognise the address, click Cancel.
  4. The client receives its tokens and lists the tools it may call. Ask your assistant a question about an invoice, a debtor or a payment and it will pick the tool.

The MCP server is a data interface. Quiddly runs no model: the assistant you connect, and the model provider behind it, are yours. Every tool result the assistant reads leaves Quiddly and is processed by that provider under your agreement with them, not under your agreement with Quiddly.

What leaves Quiddly Whatever the tools return for the signed-in user: debtor and client names, organisation and personal identification numbers, contact details, invoice and loan balances, payment references and payer names, collection case data and credit decision statuses. The withheld fields never leave.
Where it goes To the model provider your assistant uses, in the region that provider processes in. Quiddly does not see, store or control that copy.
Retention and training Governed by your provider’s terms. Quiddly cannot limit how long the provider keeps prompts and results, or whether it uses them to improve its models. Check this before connecting.
Roles under GDPR You are the controller. Quiddly processes the data on your instruction as your processor; the call an authenticated user of yours makes through the assistant is that instruction. Your model provider is your processor, not Quiddly’s sub-processor.
What stays with Quiddly Quiddly logs which tool was called, by which user and client, with which argument names, and how many rows came back. The rows themselves are not logged. Nothing is sent anywhere by Quiddly.

Before you connect an assistant to production data:

  • Use a provider you have a data processing agreement with, in a region your own data protection assessment allows.
  • Give the connecting user a role with only the rights the assistant needs. MCP inherits the user’s rights exactly; a narrower role is the simplest control.
  • Keep a person in the loop for anything the assistant drafts from this data, in particular communication to debtors.
  • Asking Quiddly to switch the MCP setting off closes MCP for the whole tenant within a minute, and leaves your other integrations untouched.

This section is for anyone building or debugging an MCP client. Quiddly follows the MCP authorization specification (2025-06-18): the MCP server is an OAuth 2.1 resource server and hosts its own authorization server, which delegates the interactive login to Quiddly’s identity provider.

Endpoint Purpose
POST /mcp The MCP server. Unauthenticated calls answer 401 with a WWW-Authenticate header that points at the protected resource metadata.
GET /.well-known/oauth-protected-resource/mcp Protected resource metadata (RFC 9728). Names the authorization server.
GET /.well-known/oauth-authorization-server Authorization server metadata (RFC 8414). Lists every endpoint below.
POST /oauth/mcp/register Dynamic client registration (RFC 7591).
GET /oauth/mcp/authorize Starts the login.
POST /oauth/mcp/consent Submitted by the consent screen; this is what issues the authorization code.
POST /oauth/mcp/token Exchanges a code, or a refresh token, for tokens.
POST /oauth/mcp/revoke Revokes a refresh token (RFC 7009).
  • Only public clients are accepted (token_endpoint_auth_method must be none).
  • redirect_uris must be https, or http on loopback, without userinfo (https://user@host/ is rejected). Private URI schemes such as vscode:// are accepted only when Quiddly has enabled them for your tenant. At most 10 URIs per registration.
  • Registration and authorization requests are rate limited per caller IP. Authorization is also limited per client_id, counting only logins that never return to the callback, with a much higher ceiling.
  • PKCE with S256 is required.
  • redirect_uri is required and must match one from the registration exactly.
  • resource (RFC 8707) is accepted and must be https://$HOST/mcp. The token is bound to that resource and is rejected anywhere else.
  • state may be at most 512 characters.
  • The authorize response sets a short-lived cookie on the user’s browser, and the callback only continues in the browser that carries it. Open the authorize URL in the user’s browser and let that browser return; do not fetch it server-side or hand the callback URL to another browser.
  • A request that fails these checks, or a callback arriving in a browser that did not start the login, is answered with an error page in the browser, not a redirect: nobody has logged in yet, so Quiddly does not send the user to a registered address it cannot vouch for. Your client learns of the failure when the user tries again.
  • After login, no code is issued until the user approves on the consent screen. Cancelling returns error=access_denied to your redirect_uri, as does a login the user finished but Quiddly cannot accept (no active user, or a user managed by an external identity provider). Failures of the login itself are reported the same way, with error, state and iss. An expired login session or consent key answers 400 directly, since the request it belonged to is gone.
  • Codes and refresh tokens are single use. A refresh rotates the refresh token and consumes the one presented. Presenting a refresh token that was already rotated out revokes the whole session, so keep exactly one live token and retry a lost refresh response by signing in again.
  • A session ends 90 days after the approval however often it rotates; the final refresh response carries no refresh_token. Refreshing also re-checks that the user is still active.
  • Revoking a refresh token revokes the session it belongs to.
  • Client, redirect_uri, PKCE and resource are validated before a code or refresh token is consumed, so a mismatched request cannot burn a credential the legitimate client is about to use.
  • MCP tokens carry token_use: "mcp" and are only valid at /mcp. They are rejected by every other API endpoint, and an ordinary Quiddly API token is rejected at /mcp. Use a service account for REST.
  • Streamable HTTP, stateless: one JSON-RPC message per POST /mcp, no session id, no server-initiated stream. GET and DELETE answer 405.
  • Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05 are negotiated on initialize. An MCP-Protocol-Version header outside that list answers 400.
  • Notifications answer 202. Batched requests are rejected.
  • Every tool result carries structuredContent next to its text block, and every tool publishes an outputSchema and read-only annotations.

The server’s initialize instructions also name the documentation for the environment it runs on: this portal’s llms.txt for API questions and the knowledge base’s llms.txt for how the Operations Portal and the Client Portal work. An assistant reads the section file a question is about instead of guessing or paging the data tools, and reads it for the release that environment is on.

Area Tool Required right
Invoices get_invoice ReadInvoice
search_invoices SearchInvoices
search_invoice_financial_events SearchInvoiceFinancialEvents
search_invoice_ledger_events SearchInvoiceLedgerEvents
Debtors get_debtor ReadDebtor
search_debtors SearchDebtors
search_debtor_financial_events SearchDebtorFinancialEvents
Collection get_collection_batch ReadCollectionBatch
search_collection_batches SearchCollectionBatches
Clients get_client ReadClient
search_clients SearchClients
search_client_accounts SearchClientAccounts
search_client_account_transactions SearchClientAccountTransactions
search_client_to_dos SearchClientToDo
Money in search_client_unplaced_payments SearchClientUnplacedPayments
search_unplaced_payments SearchUnplacedPayments
search_payment_transactions SearchPaymentTransactions
search_payment_batches SearchPaymentBatches
Money out search_payouts SearchPayouts
search_payout_batches SearchPayoutBatches
Distribution search_distributions SearchDistribution
search_distribution_events SearchDistributionEvents
Documents search_documents SearchDocument
Lending get_business_loan ReadBusinessLoan
search_business_loans SearchBusinessLoans
search_business_loan_events SearchBusinessLoanEvents
search_business_loan_ledger_events SearchBusinessLoanLedgerEvents
Credit search_debtor_factoring_decisions SearchDebtorCreditDecisionStatus
list_credit_decision_statuses ReadCreditDecisionStatus
Totals summarize the chosen register’s search right

tools/list returns every tool regardless of the user’s rights. Calling a tool the user may not use returns a normal result with isError: true and a message naming the missing right; only protocol problems (unknown tool, malformed request) produce a JSON-RPC error.

  • Amounts are exact decimals carried as strings with two decimals. Invoice and collection case amounts are in the currency named on the row; debtor balances are in the tenant’s system currency.
  • Dates and timestamps are ISO-8601.
  • Search tools take typed filters (equality, lists, text match, date ranges) and page with limit and offset, returning { <rows>, count, limit, offset, has_more }. A list filter takes at most 100 values and a text filter at most 200 characters, both declared in the tool’s input schema; offset is at most 10,000, and % or _ in a text filter match literally.
  • Free text written by a payer or a debtor (payment references and messages, names, notes) is returned as data. The server tells the model so in its instructions and in the field descriptions; an agent you build should do the same.
  • Read the structuredContent of a result; the text block is the same data serialized for models that do not support structured output.

summarize answers “how much”, “how many” and “per client, status or month” in one call, as a database aggregate, instead of the assistant paging through a search tool and adding rows up. It takes the register to aggregate, the same filters as that register’s search tool, the dimensions to group by and a group limit:

{
"register": "invoices",
"filters": { "only_booked": true, "overdue_more_than_days": 30 },
"group_by": ["client"]
}
  • Every group carries count and the register’s sums. Plain amounts are in the group’s own currency, which is always a grouping key, so they never mix; *_locale amounts are in your tenant’s system currency and can be added across groups.
  • age_bucket groups by days past due (not_overdue, 1_15, 16_30, 31_60, 61_plus) and month by calendar month, where the register has a date for it. overdue_more_than_days and overdue_at_most_days filter on the same calendar.
  • only_booked on the invoice tools is the receivables ledger as the Client Portal shows it: activated, paused, disputed, stopped and closed invoices, drafts and pending ones left out. The invoices summary requires a narrowing filter (only_booked, only_unpaid, an overdue window, a client or debtor, or statuses); an unfiltered call is rejected. For the open ledger pass only_booked together with only_unpaid or an overdue window. Debtor balances from summarize are balance_total_locale / balance_capital_locale (system currency).
  • Registers with sums: invoices, collection cases, debtors, client accounts and their transactions, unplaced payments, payments and payment batches, payouts, business loans and their events, invoice and debtor financial events. Distributions, documents, to-dos, ledger steps, payout batches and the credit decision board are counts only. The tool’s description lists each register’s dimensions and sums.
  • At most 200 groups are returned, largest first; has_more says the list was cut. Narrow the filters or group by fewer dimensions rather than paging.
  • Scoping, rights and the client-user rules are the search tool’s: a summary never shows more than the matching search would.
  • Instance-wide registers. search_payment_transactions, search_payment_batches and search_payout_batches cover data that has no client until it is placed, so they answer 403 for anyone who is not a system user.
  • Business loan events require a business_loan_id; a loan the user cannot see reads as not found.
  • Debtor financial events are closed to ERP and client handler users, as the REST endpoint is.
  • Payer identity on payments follows the REST rule: a caller holding SHOW_RESTRICTED_SENDER_INFORMATION_FOR_CLIENTS gets the payer’s identification and account number as null. The payer’s name is never restricted.
  • Documents are metadata only. No bytes, no storage key and no storage name; has_stored_file says whether a file exists, and the row carries the document id and its human-readable name. Open it in the Operations Portal or through the authenticated REST API.
  • Interest invoices are hidden from client users, as they are in the REST invoice searches.
  • get_collection_batch answers 403 to ERP and client handler users, as the REST get does.
  • search_client_unplaced_payments returns the client-facing field set: no debtor_id, source_invoice_id or matching_status. The operator tool search_unplaced_payments has them.
  • Payout destinations show the receiving bank and channel, never the account number or IBAN.
  • Not exposed: credit monitoring, payout files, debtor payout accounts, document templates, the payment matching engine, payment method configuration and reconciliation.
Symptom Cause
404 on /mcp or any /oauth/mcp/* route MCP is not enabled on the tenant: the Developer module or the MCP setting is off. Switching either off takes effect within a minute.
401 on /mcp with a token that works on REST REST tokens are not accepted at /mcp. Sign in through the client’s OAuth flow.
401 on a REST endpoint with an MCP token MCP tokens only work at /mcp. Use a service account for REST.
401 after about an hour The access token expired and the client did not refresh. Reconnect from the client.
invalid_grant saying the refresh token was already used The client presented a rotated-out token, or someone else did. The session is revoked; sign in again.
access_denied after a successful login No active Quiddly user matches the login, or the user is managed by an external identity provider.
An error page instead of a redirect when starting the login The authorization request failed validation before login: PKCE, redirect_uri, response_type, resource or state.
Tool result with isError: true naming a right The user’s role lacks that right. Ask your tenant administrator.
403 from a payment or payout batch tool Those registers are instance-wide and need a system user.
invalid_client_metadata on registration Confidential client, a non-https and non-loopback redirect_uri, or more than 10 URIs.
Consent screen names a client or address you do not recognise Someone else started the login. Click Cancel.