Authentication
The MCP server accepts a Bearer token in the Authorization header, and nothing else — no cookie, no client certificate.
:::warning Never in the URL
A token in the query string (access_token, token, bearer, bearer_token, id_token, api_key, apikey) does not authenticate, even next to a valid header: the request is answered 401 with error="invalid_request" in WWW-Authenticate. A token in the URL leaks into proxy and CDN logs, Referer and browser history — the MCP authorization spec and RFC 6750 forbid it.
:::
That token is an OAuth 2.1 access token issued by the Kobana authorization server (https://account.kobana.com.br, your Kobana account): an RS256-signed JWT issued for this resource, whose aud is https://mcp.dunning.kobana.com.br. It arrives one of two ways — the OAuth flow, run by the client itself, or a personal token you create in your Kobana account — and it is verified the same way in both cases.
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
:::info Each surface has its own audience
A token issued for the MCP server is not valid on the REST API (api.dunning.*), and an API token is refused here with 401. A token whose aud lists more than one value is refused as well. That is what stops a token handed to an assistant from being reused against the API — and the other way round.
:::
Two ways to connect
| OAuth flow | Personal token | |
|---|---|---|
| How you get it | Paste the URL into the client; it opens the Kobana sign-in and you authorize | Create it at Kobana Account → Tokens and Access → New Personal Token: app Régua de Cobrança, usage MCP, resources and access, validity |
| How the client uses it | On its own, after you authorize | You paste it into the client as Authorization: Bearer <token> |
| Validity | Short-lived, renewed by the client | 7 days to 1 year, your choice |
| Scope | Access level only — read, today | The resources and access you selected |
| Good for | Claude, ChatGPT, IDEs and any client with OAuth | Clients without OAuth support, scripts, and agents that need to write |
The personal token has to be created with the MCP usage. A token created for the REST API usage carries the API's audience and is refused here with 401 — and the reverse holds too.
The OAuth flow
You run none of these steps: the MCP client (Claude, ChatGPT, an IDE) does all of it on its own, starting from the URL. They are here for when something fails halfway.
-
Challenge. A request without a token gets
401with the challenge every OAuth 2.1 resource server must send (RFC 9728):WWW-Authenticate: Bearer realm="kobana-dunning-mcp",error="invalid_token",resource_metadata="https://mcp.dunning.kobana.com.br/.well-known/oauth-protected-resource" -
Resource metadata. The client reads the document it points at. It is public, carries no secret — only pointers — and is cacheable for five minutes (
Cache-Control: public, max-age=300):curl https://mcp.dunning.kobana.com.br/.well-known/oauth-protected-resource{"resource": "https://mcp.dunning.kobana.com.br","authorization_servers": ["https://account.kobana.com.br"],"bearer_methods_supported": ["header"],"scopes_supported": ["dunning/agreements", "dunning/audit_logs", "dunning/charges","dunning/classifications", "dunning/collection_rules", "dunning/disputes","dunning/email_layouts", "dunning/exports", "dunning/imports","dunning/interactions", "dunning/metrics", "dunning/negativations","dunning/negotiation_campaigns", "dunning/notification_channels","dunning/notification_preferences", "dunning/notifications", "dunning/people","dunning/protests", "dunning/tasks", "dunning/templates","dunning/webhook_endpoints", "read"],"resource_documentation": "https://docs.dunning.com.br/en/mcp/overview/introduction"}scopes_supportedis what the client should ask for: the 21dunning/<resource>scopes plusread. It never listsdunning/allor the verbs (write, …) — the authorization server does not grant them to a dynamically registered client, and asking for them would fail the authorization. -
Authorization server metadata (RFC 8414), at
https://account.kobana.com.br/.well-known/oauth-authorization-server. -
Dynamic client registration (RFC 7591). The client registers itself.
-
Authorization with PKCE and
resource(RFC 8707). You sign in to Kobana, pick the organization (if you have more than one) and authorize. -
Token. The client exchanges the code for a short-lived access token and sends it on every request, refreshing it when it expires.
The token carries who you are (sub) and the organization — your Kobana account — you authorized (org). org is what scopes the data — no tool takes an organization as an argument. A token whose organization does not exist in Régua de Cobrança gets 403 unknown_organization.
Authorization: the person's permissions
This is where MCP differs from the REST API. On the API, a token acts with the integration's authority. On MCP, tools are filtered by the dashboard permissions of the person who owns the token (sub) in that organization, read on every request. The ceiling is always what that person can do in the dashboard.
The person is found through the Kobana identity recorded when they sign in to the Régua de Cobrança dashboard with their Kobana account. That is why someone who has never signed in to Régua de Cobrança with their Kobana account has no tools at all: there is no membership to link the token to, and every tool is denied with key_owner_not_member. The fix is to sign in to the dashboard once with the Kobana account — or, for someone who is not on the team yet, to ask for an invitation. An invitation to another organization works too: the membership is matched by the person's e-mail.
| Situation | Result |
|---|---|
| The person is not a member of the organization (or never signed in to Régua de Cobrança with the Kobana account) | Denies every tool (key_owner_not_member). |
| Membership inactive, or with no permission or role | Denies every tool (membership_inactive). |
| The permission lookup fails | Denies every tool. Fail closed. |
| Active membership | The person's permissions and roles, applied tool by tool. dunning.dashboard.* opens everything; dunning.dashboard.charges.* opens the charges family. |
Administrative areas — members, roles, keys, settings — are never reachable by a token, and have no tool.
Permissions are read on every call, so revoking a role in the dashboard takes effect on the very next call, without waiting for the token to expire.
A tool the caller cannot run is not listed by tools/list. When tools/call refuses, the code says who fixes it:
| Code | Who fixes it |
|---|---|
insufficient_scope | Authorizing again, or using a token that reaches the resource with the access requested — for example, a personal token with write access to it. |
insufficient_permission | An organization admin, granting the permission (the error names which). |
membership_inactive | Reactivating the person — no permission fixes an inactive membership. |
key_owner_not_member | The person signing in to the Régua de Cobrança dashboard with their Kobana account, or asking for an invitation. |
:::tip Recommended posture for agents Authorize the client with an account that holds only the permissions the agent needs. A client authorized by a read-only analyst exposes only the read tools, whatever the model on the other end does. :::
Token scope
On top of permissions, the MCP server applies the scope the token carries. The two checks add up: a tool only shows up if both allow it. Reading is a read tool, a resource, a prompt and a completion; writing is everything else, destructive tools included.
The scope comes in two shapes.
Token with dunning/… resources — the personal-token case. The resources define how far the token reaches, and read/write/all apply only within them:
| Token scope | Result |
|---|---|
dunning/charges dunning/people read write | Reads and writes charges and debtors — and nothing else |
dunning/charges read | Only reads charges |
dunning/all read | Reads everything, writes nothing |
delete, import and export on their own do not count as writing.
Token without dunning/… resources — the OAuth-flow case. Only the access level applies: reading with read, write or all; writing with write or all.
A token with neither reaches nothing except dunning_me, which any valid token can call.
The resource of a tool is the first segment of its API path: /collection-rules/... is collection_rules. Debtor contact points belong to people, negotiation campaigns to negotiation_campaigns, and collection-rule steps to collection_rules. The 21 resources are the ones in scopes_supported above.
:::info Clients connected through the OAuth flow are read-only
The authorization server grants only read to dynamically registered clients — Claude, ChatGPT, the MCP Inspector, IDEs. For them, the MCP server lists only the read tools, and a tool that writes returns insufficient_scope. For an agent to write today, use a personal token with write access to the resources it needs.
:::
The scope check is repeated by the /v1 endpoint when the tool is dispatched — defense in depth. If it ever disagreed, the endpoint would answer 403 INSUFFICIENT_SCOPE.
Personal token revocation
Revoking a personal token in your Kobana account does not cut access right away: Régua de Cobrança verifies the token locally, without asking Kobana on every call, and it keeps being accepted until it expires. So:
- pick the shortest validity the use allows;
- select only the resources and access the agent needs;
- to cut an agent off immediately, deactivate the person in the organization — permissions are read on every call and take effect at once.
Failure modes
These happen before the JSON-RPC message is processed, as plain HTTP responses:
| HTTP | code | Cause |
|---|---|---|
| 400 | -32022 (JSON-RPC) | Unsupported protocol version, in the MCP-Protocol-Version header or in _meta; data.supported lists the supported ones. See Transport. |
| 400 | -32020 (JSON-RPC) | A header disagrees with the body on the 2026-07-28 era. |
| 401 | UNAUTHORIZED | Token missing, malformed, invalid, expired, issued for another resource (for example, the REST API), or sent in the URL. |
| 403 | unknown_organization | The token is valid, but the organization it names does not exist in Régua de Cobrança. |
| 403 | FORBIDDEN | origin_not_allowed: a browser Origin other than the server's own. A request with no Origin passes. |
| 404 | NOT_FOUND | The request arrived through a host that does not serve MCP. |
| 413 | -32600 (JSON-RPC) | Body over 2 MB (Request body too large) — the same cap as the API. |
| 429 | RATE_LIMITED | Rate limit reached — see Rate limit. |
| 503 | SERVICE_UNAVAILABLE | We could not reach the authorization server to verify the token. It is on our side: retry after Retry-After. |
Every 401 carries the WWW-Authenticate challenge above, which sends the client back into the OAuth flow.
Permission and scope denials are not HTTP errors: they come back as a tool result flagged isError, so the model can read them and react — see Errors.