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=…, or parameters such as token and api_key) 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): an RS256-signed JWT issued for this resource, whose aud is https://mcp.dashboards.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.dashboards.*), and an API token is not valid here. 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 Magic Dashboard, 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 | 30 minutes, 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 automations |
The personal token has to be created with the MCP usage. A token created for the REST API 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-dashboards-mcp",error="invalid_token",resource_metadata="https://mcp.dashboards.kobana.com.br/.well-known/oauth-protected-resource" -
Resource metadata. The client reads the document it points at. It is public and carries no secret — only pointers:
curl https://mcp.dashboards.kobana.com.br/.well-known/oauth-protected-resource{"resource": "https://mcp.dashboards.kobana.com.br","authorization_servers": ["https://account.kobana.com.br"],"bearer_methods_supported": ["header"],"resource_documentation": "https://docs.dashboards.kobana.com.br"} -
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, refreshing it when it expires.
The token carries who you are (sub) and the organization 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 Magic Dashboard gets 403 unknown_organization.
Authorization: the permissions of whoever authorized
What a caller sees is narrower than the catalog: tools are filtered by the dashboard permissions of the person who authorized the client, in that organization, resolved on every request. The ceiling is always what that person can do in the UI.
| Situation | Result |
|---|---|
| The person is not a member of the organization | Denies every tool (key_owner_not_member). Authorize again with an account that belongs to the organization. |
| Active membership | The person's permissions and roles, applied tool by tool. owner and the * wildcard open everything; portal-users.* opens the family. |
| Membership suspended, removed, or with no role | Denies every tool (membership_inactive). |
| The permission lookup fails | Denies every tool. Fail closed. |
Permissions are read on every call, so revoking a role in the dashboard takes effect on the very next tool, 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 | 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 | Reinstating the person — no permission fixes a dead membership. |
key_owner_not_member | Authorizing again with an account that is a member of the organization. |
:::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, in the scope claim. The two checks add up: a tool only shows up if both allow it. Reading is a read tool, a resource, a read prompt and a completion; writing is everything else, destructive tools included.
Token with resources — the personal-token case. The resources define how far the token reaches, and read/write apply only within them:
| Token scope | Result |
|---|---|
dashboards/users dashboards/members read write | Reads and writes portal users and team members (with their roles) — and nothing else |
dashboards/users read | Only reads portal users |
dashboards/all read | Reads everything, writes nothing |
A resource outside the ten groups is reachable only with dashboards/all. delete, import and export on their own do not count as writing.
Token without resources — the OAuth-flow case. Only the access level applies: reading with read, write or all; writing with write or 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 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 same rule applies to the REST API, with the resource taken from the path: a token without write access to the resource gets 403 insufficient_scope on any method other than GET.
Personal token revocation
Revoking a personal token in your Kobana account does not cut access right away: Magic Dashboard verifies the token on its own, 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, suspend or remove the person's membership in the organization — 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, expired, or issued for another resource (for example, the REST API). |
| 403 | unknown_organization | The token is valid, but the organization it names does not exist in Magic Dashboard. |
| 403 | FORBIDDEN | Origin outside the allow-list (origin_not_allowed). |
| 404 | NOT_FOUND | The request arrived through a host that does not serve MCP. |
| 429 | TOO_MANY_REQUESTS | 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 denials are not HTTP errors: they come back as a tool result flagged isError, so the model can read them and react — see Errors.
Host and edge
/mcp must be reachable only through mcp.dashboards.* — never through the app host, never through the API host, whose distributions point at the same origin. Any other path answers 404: the endpoint does not advertise that it exists somewhere else.
The OAuth resource identifier — and therefore the audience the token must carry — is derived from the server configuration, never from the request Host, so a forged Host cannot steer a client to a different identifier.