Skip to main content

Authentication

The MCP server is an OAuth 2.1 Resource Server. It accepts a Bearer token in the Authorization header and nothing else — no cookies, no client certificate, and never a token in the URL: ?access_token= is ignored, and such a request is answered as unauthenticated (401 with the challenge). A URL ends up in access logs and Referer headers; a bearer token must not.

:::caution No mTLS here The REST API requires a client certificate (/api/v1 authentication); the MCP host deliberately does not. No MCP client presents a client certificate, so the transport layer is replaced by OAuth, not dropped: the audience check, the scope check and the per-person permission check all still run. :::

Two ways to connect​

The token is an OAuth 2.1 access token issued by the Kobana authorization server, an RS256 JWT issued for this resource: its aud is the MCP server URL (https://mcp.inbox.kobana.com.br). It reaches the server one of two ways, and is verified the same way in both.

OAuth flowPersonal token
How you get itPaste the URL into the client; it opens the Kobana login and you authorizeCreate it in Kobana Account → Tokens and Access → New Personal Token: app Autonomous Inbox, use MCP, resources and access, validity
How the client uses itOn its own, after authorizationYou paste it into the client as Authorization: Bearer <token>
ValidityShort, renewed by the client7 days to 1 year, your choice
ScopeThe resources you consented to, read-onlyThe resources and access you ticked
Good forClaude, ChatGPT, IDEs and any client with OAuthClients without OAuth, scripts, and agents that need to write

:::info Each surface has its own audience A token issued for the MCP server is not valid on the REST API (api.inbox.*), and an API token is not valid here — a token handed to an assistant cannot be replayed against the API. A personal token has to be created with the MCP use for the same reason. A token naming more than one audience is refused. :::

The OAuth flow​

You run none of these steps: the MCP client does everything from the URL. They are here for when something fails midway.

  1. Challenge. A request with no token is answered 401 with the challenge every OAuth 2.1 resource server has to send (RFC 9728):

    WWW-Authenticate: Bearer realm="kobana-inbox-mcp", error="invalid_token", resource_metadata="https://mcp.inbox.kobana.com.br/.well-known/oauth-protected-resource"
  2. Resource metadata. The client reads the document it points at. It is public and carries only pointers; the authorization server is named by its issuer identifier:

    curl https://mcp.inbox.kobana.com.br/.well-known/oauth-protected-resource
    {
    "resource": "https://mcp.inbox.kobana.com.br",
    "authorization_servers": ["https://account.kobana.com.br"],
    "bearer_methods_supported": ["header"],
    "scopes_supported": [
    "inbox/agent_runs", "inbox/agents", "inbox/emails", "inbox/inboxes",
    "inbox/secrets", "inbox/system_events", "inbox/usage", "inbox/webhooks",
    "inbox/workflow_runs", "inbox/workspaces", "read"
    ],
    "resource_documentation": "https://docs.inbox.kobana.com.br"
    }
  3. Authorization-server metadata (RFC 8414), at https://account.kobana.com.br/.well-known/oauth-authorization-server.

  4. Dynamic client registration (RFC 7591). The client registers itself; the consent screen says it is not a verified app.

  5. Authorization with PKCE and resource (RFC 8707). You log in to Kobana, pick the organization (if you have more than one) and authorize.

  6. Token. The client exchanges the code for a short-lived access token and sends it on every request, renewing it when it expires.

The token carries who you are (sub), the organization you authorized (org) and the scopes you consented to (scope). It is org that scopes the data — no tool takes an organization as an argument.

Two levels of authorization​

Authorization is the conjunction of two independent checks — one about the client, one about the person.

LayerAnswersDecided byEffect on MCP
Scope (inbox/X + the global write verb, or the access level)what this client may ask for on the person's behalfthe person, on the consent screen or when creating a personal tokenfilters tools/list, blocks tools/call with insufficient_scope
Permission (resource.action)what this person may do in this organizationthe organization's administrator, in the dashboardfilters tools/list, blocks tools/call with insufficient_permission

The two error codes are distinct because different people fix them: a missing scope is fixed by whoever owns the connected client, a missing permission by the organization's administrator.

Every MCP token is a person's. The authorization server only issues a resource-bound token to a person, so the MCP server always applies that person's permissions — whatever src the token carries. A person who is not a member of the organization reaches nothing (403, no_membership). Permissions are read on every call, so revoking a role in the dashboard takes effect on the next tool call, without waiting for the token to expire.

Scopes​

Scopes have two orthogonal axes: the resource, always prefixed by the app, and the verb, a global scope with no prefix. A resource without a verb means read. Writing requires both.

ScopeGrants
inbox/workspacesRead workspace tools
inbox/inboxesRead inbox tools and allowed recipients
inbox/emailsRead e-mail tools, including the .eml download
inbox/agentsRead agent tools, prompt versions and the model catalog
inbox/agent_runsRead agent-run tools
inbox/usageRead the usage and cost tools
inbox/webhooksRead webhook tools and their deliveries
inbox/system_eventsRead system-event tools
inbox/secretsRead secret tools — key, notes and metadata. The value is never returned, by any tool or endpoint
inbox/workflow_runsRead workflow-run tools, including every step of a run
inbox/allResource catch-all — reads every resource above, grants no verb
writeGlobal verb: every write tool on the resources the token carries

A read-only token is the recommended default for an agent: with inbox/all alone, the connected client physically cannot write, even if the person behind it could. A client registered through the OAuth flow gets resource scopes and no verb — read-only by construction.

The access level. Every token the authorization server issues also carries the access level the person granted — read, write, delete, import, export, all. It is honoured next to the grammar above:

  • With inbox/… scopes in the token, the resources define the reach, and read, write and all act only within them: inbox/emails read write reads and writes e-mails and nothing else; inbox/all read reads everything and writes nothing.
  • Without inbox/… scopes, the access level alone decides: reads with read, write or all; writes with write or all.

delete, import and export on their own do not count as a write.

Each tool page in Resources states the scope and the permission it requires.

Choosing the workspace​

A person-bound token does not pin a workspace — an organization may have several. Pick one per connection with:

X-Workspace-Id: <workspace-id-or-external-id>

When the organization has exactly one workspace, the header is optional. With more than one and no header, the server answers 400 workspace_required. A header naming a workspace outside the token's organization is 403 forbidden with message: unknown_workspace — indistinguishable from a nonexistent one. No tool accepts a workspace argument.

Personal token revocation​

Revoking a personal token in your Kobana account does not cut access immediately: the Autonomous Inbox verifies the token on its own, without asking Kobana on every call, and it stays valid until it expires. So:

  • pick the shortest validity the use allows;
  • tick 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​

HTTPcodeCause
400workspace_requiredOrganization has several workspaces and none was selected
400-32022 (JSON-RPC)Protocol version not supported, in MCP-Protocol-Version or in _meta; data.supported lists the supported ones. See Transport
401invalid_tokenToken missing, malformed, with an invalid signature, or issued for another resource (the REST API, say)
401invalid_issueriss is not the Kobana issuer
401token_expiredexp in the past
401token_revokedjti revoked
403forbiddenRequest did not come through the MCP edge, or the token resolves to no tenant — message says which: unknown_organization (the org claim names an account this server does not know), no_membership (the person has no live membership in that organization) or unknown_workspace (X-Workspace-Id outside the organization)
403origin_not_allowedOrigin header outside the allow-list
404not_foundRequest arrived on a host that does not serve MCP, or asked for a path that is not an MCP endpoint — always this JSON body, for every method
429rate_limitedRate limit exceeded — see Rate limiting. Rejected tokens are also throttled per client IP
503as_unavailableThe signing keys could not be fetched from the authorization server. Not a problem with your token: retry after Retry-After. Never a 401, which would send the client through the whole OAuth flow again for nothing

Every 401 carries the WWW-Authenticate challenge above, which sends the client back into the OAuth flow. Scope and permission denials are not HTTP errors: they come back as tool results flagged isError, so the model can read and react to them — see Errors.