Authentication
The MCP server accepts a Bearer token in the Authorization header, and nothing else — no cookie, no client certificate, no token in the URL. A request carrying ?access_token= (or ?token= and the like) is refused with 401, even alongside a valid header: in the URL the token leaks into logs, browser history and intermediaries.
That token is an OAuth 2.1 access token issued by the Kobana authorization server (https://account.kobana.com.br), an RS256-signed JWT issued for this resource: its aud is https://mcp.finance.kobana.com.br. It arrives by one of two paths — the OAuth flow, run by the client itself, or a personal token you create on 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 does not work on the REST API (api.finance.*), and an API token does not work here. That is what stops a token handed to an assistant from being reused against the API — and the other way around.
:::
Two ways to connect
| OAuth flow | Personal token | |
|---|---|---|
| How you get it | Paste the URL into the client; it opens the Kobana login and you authorize | Create it under Kobana Account → Tokens and Access → New Personal Token: app Smart Finance, usage MCP, resources and access, validity |
| How the client uses it | On its own, after authorization | You paste it into the client as Authorization: Bearer <token> |
| Validity | Short, renewed by the client | From 7 days to 1 year, chosen by you |
| Scope | Only the access level — today, read | The resources and access you checked |
| Good for | Claude, ChatGPT, IDEs and any OAuth-capable client | Clients without OAuth support, scripts, and agents that need to write |
The personal token must be created with the MCP usage. A token created for the REST API has the API's audience and is refused here with 401 — and the reverse holds too.
The workspace
The token's org names the organization; the workspace each tool acts on is decided on every call, in this order:
| Order | Where it comes from |
|---|---|
| 1 | The tool's workspace_id argument, when the call carries it |
| 2 | The connection's default workspace (below) |
| — | Neither: the tool returns the workspace_required error |
The server URL needs no workspace — https://mcp.finance.kobana.com.br is enough —, and connecting never fails for lack of one. To learn where it can act, the agent calls finance_workspaces_list, with no arguments: it lists the organization's workspaces the token's person can reach.
{ "data": [
{ "id": "2b93f876-769a-4bdc-b23e-304957f313b4", "name": "Matriz", "default": true },
{ "id": "7c1d0e52-…", "name": "Filial Sul", "default": false }
] }
Every other tool — except finance_schemas_get and finance_workspaces_list itself — takes the optional workspace_id argument (uuid):
{ "name": "finance_payables_list",
"arguments": { "workspace_id": "2b93f876-769a-4bdc-b23e-304957f313b4", "limit": 10 } }
The default workspace
It applies when the call carries no workspace_id, and it is the one marked default: true in finance_workspaces_list:
| Where | Example |
|---|---|
X-Workspace-Id header (optional) | X-Workspace-Id: 2b93f876-769a-4bdc-b23e-304957f313b4 |
| Parameter on the server URL (optional) | https://mcp.finance.kobana.com.br/?workspace_id=2b93f876-… |
| Neither | The only workspace the person reaches, if they reach exactly one |
The header and the URL parameter are still accepted, but they are now optional and only set the default: the workspace_id argument always wins over them. In every case the value is a selector, not a grant — it is checked against the token's organization and the person's access:
-
With no
workspace_idand no default — the person reaches several workspaces —, the tool returnsworkspace_required. It is a tool result withisError: true, not an HTTP error:{ "error": { "code": "workspace_required", "message": "…" } } -
A
workspace_idthe person cannot reach, or that does not exist, returns the tool errorworkspace_access_denied— the same answer either way. -
A header or URL parameter naming a workspace of another organization is still refused with HTTP
403UNKNOWN_WORKSPACE.
Resources (kobana://finance/…) and completions have no workspace argument: they use the connection's default. Without one, reading a record resource fails with workspace_required — use the tools —, and completions suggest nothing. See Resources and prompts.
The OAuth flow
You run none of these steps: the MCP client (Claude, ChatGPT, an IDE) does everything on its own from the URL. They are here for when something fails midway.
-
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-finance-mcp",error="invalid_token",resource_metadata="https://mcp.finance.kobana.com.br/.well-known/oauth-protected-resource" -
Resource metadata. The client reads the document it points to. It is public and carries no secret — only pointers:
curl https://mcp.finance.kobana.com.br/.well-known/oauth-protected-resource{"resource": "https://mcp.finance.kobana.com.br","authorization_servers": ["https://account.kobana.com.br"],"bearer_methods_supported": ["header"],"resource_documentation": "https://docs.finance.kobana.com.br/pt/mcp/overview/introduction"}There is no
scopes_supported: the Kobana Account does not publish the Smart Finance scopes yet, and a client that asked for them would be refused at 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; the consent screen warns that it is not a verified app.
-
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 from then on, renewing it when it expires.
The token carries who you are (sub) and the organization you authorized (org); the workspace comes from each call, as above. No tool takes an organization as an argument.
Two authorization axes
What a caller sees is narrower than the catalogue, through two independent filters. Neither can widen the surface.
Axis 1 — the token's scope
The client's ceiling, in the scope claim. Reading is read tools, resources, prompts and completions; writing is everything else, destructive tools included. Moving money is one step above writing.
Token with resources — the personal token's case. The resources define how far the token reaches, and read, write and all apply only within them:
| Token scope | Result |
|---|---|
finance/payables finance/people read write | Reads and writes payables and people — and nothing else |
finance/payables read | Only reads payables |
finance/all read | Reads everything, writes nothing |
finance/payables finance/payable_dispatches read write | Also sends payables to the bank |
Sending to the bank and charging require their own resource — finance/payable_dispatches and finance/receivable_charges — with write or all; finance/payables write is not enough. delete, import and export on their own do not count as writing.
Token without resources — the OAuth flow's case. Only the access level applies: reading with read, write or all; writing with write or all; moving money only with all.
:::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 lists only the read tools, and a tool that writes comes back insufficient_scope. For an agent to write today, use a personal token with write access to the resources it needs.
:::
The same rule applies on the REST API — see Scopes.
Axis 2 — the permissions of the token's person
The token's sub is the person who consented. In MCP, the tools are narrowed to what that person can do in the organization's dashboard, resolved on every request — the same roles and permissions as the Members screen.
| Situation | Result |
|---|---|
| The person never signed in to Smart Finance, or is not a member of the organization | Denies every tool. Someone who is not a member reaches nothing through MCP. |
| Active member, with access to the workspace | Their permissions, tool by tool. The owner (*) unlocks everything; payables.* unlocks the family. |
| Active member without access to the workspace | Denies every tool in that workspace. |
| Member suspended, with a pending invitation, removed, or with no permission at all | Denies every tool. A token outliving the person's access is exactly the case to close. |
| The permission lookup fails | Denies every tool. Fail closed. |
Permissions are read on every call: removing a permission in the dashboard takes effect on the very next tool, without waiting for the token to expire.
:::tip Recommended posture for agents Connect the assistant with an account that holds only the permissions the agent needs, and, on a personal token, check only the resources and access it will use. The two axes add up: a read-only analyst's token exposes only the read tools, whatever the model on the other side does. :::
How the two compose
The final set is the intersection: tools ∩ token scopes ∩ the person's permissions. A tool the caller cannot run is not listed.
When tools/call refuses, the code says who fixes it:
| Code | Who fixes it |
|---|---|
insufficient_scope | Use a token that reaches the resource with the requested access — for example, a personal token with write on that resource. |
insufficient_permission | An organization admin, granting the permission (the error names it). |
workspace_required | The agent passes workspace_id — one of the ids from finance_workspaces_list —, or the connection sets a default workspace. |
workspace_access_denied | An admin grants the person the workspace, or the agent passes another workspace_id. |
membership_inactive | Add the person to the organization or reactivate them — no permission fixes a membership that does not exist or is inactive. |
:::caution What axis 2 does not cover
Narrowing by permission applies to MCP only. An API token, called directly on api.finance.*, is still bounded only by its scopes.
:::
Security › Channels
The Security › Channels rules still apply on top of both axes — the endpoint itself enforces them. An AI agent acts through the MCP - Artificial Intelligence channel, where, by default, sending to the bank starts closed: finance_payables_send_to_bank only works once the request has been approved.
Personal token revocation
Revoking a personal token on your Kobana Account does not cut access immediately: Smart Finance verifies the token on its own, without asking Kobana on every call, and the token is still accepted until it expires. So:
- pick the shortest validity the use allows;
- check only the resources and access the agent needs;
- to cut off an agent immediately, suspend or remove the person in the organization, or take away their access to the workspace — permissions are read on every call and take effect at once.
Failure modes
| 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. |
| 401 | UNAUTHORIZED | Token missing, malformed, invalid, revoked, expired, issued for another resource (the API, for example) or sent in the URL (?access_token=). |
| 403 | UNKNOWN_ORGANIZATION / UNKNOWN_WORKSPACE | The token's org is not a customer, or the workspace in X-Workspace-Id / ?workspace_id= is not the organization's. |
| 403 | FORBIDDEN | Origin outside the allow-list (origin_not_allowed). |
| 404 | NOT_FOUND | The request arrived through a host that does not serve MCP, or asked for a path the server does not have. |
| 429 | RATE_LIMITED | Rate limit reached — see Rate limit. |
| 503 | SERVICE_UNAVAILABLE | The Kobana Account did not respond to validate the token. It is not a token problem: try again after Retry-After. |
Every 401 carries the WWW-Authenticate challenge above, which sends the client back to the OAuth flow.
Scope and permission denials are not HTTP errors: they come back as a tool result flagged isError, so the model reads them and reacts — see Errors.
Host and edge
The endpoint is reachable only through mcp.finance.* — never through the app host nor the API host, which point at the same origin. Any other path answers 404, always as JSON and for every method — { "error": { "code": "NOT_FOUND", "message": "Not found." } }, never an HTML page. The OAuth resource identifier is derived from the server configuration, never from the request's Host, so a forged Host cannot steer a client to another identifier.