MCP clients deprecated
This page is for people who want to connect a language-model client, such as Claude, Cursor or VS Code, to OX App Suite: what the client needs, how to get a token for it, and how the common clients are configured. How an operator enables and runs the endpoint, and what it answers in detail, is described in MCP server; the full interface is in the MCP API documentation.
What a client needs
Three things, all of them provided by whoever runs the installation:
- The endpoint URL. It is the App Suite host followed by
/mcp, for examplehttps://mail.example.com/mcp. The endpoint is off by default; if the URL answers404, it has not been enabled for your installation. - A bearer token. Every request carries one. There are two kinds, and which one to use depends on the client:
- a personal access token you mint yourself and paste into the client, for clients configured with a fixed
Authorizationheader; - a token from the installation's authorization server, obtained by the client itself through an OAuth sign-in. This works only where the operator has set that up.
- a personal access token you mint yourself and paste into the client, for clients configured with a fixed
- The scopes you want to grant. A token only opens what its scopes allow, and the client only sees the tools for those scopes:
| Scope | Tools |
|---|---|
read_mail | mail_search, mail_get, mail_attachment_get, mail_folders, vacation_get |
read_calendar | calendar_list_events, calendar_get_event, calendar_free_busy, calendar_folders, resources_search |
read_contacts | contacts_search, contact_get, users_search, contacts_folders |
read_files | files_search, file_get, files_folders |
read_tasks | tasks_search, task_get, tasks_folders |
read_reminders | reminders_list |
| any | me_get |
Everything is read-only; no tool writes, sends or deletes.
A scope opens a module, not only the tools. A token with read_mail may also read mail through the regular App Suite HTTP API, and a token with read_files may read Drive there, without the size bounds the MCP tools apply. Grant only what the client should be able to read, and treat the token like a password.
Getting a personal access token
A personal access token is minted through the App Suite HTTP API with a signed-in session. There is no settings page for it yet, so the steps below use curl. Replace the host and the login data; /appsuite/api/ is the usual path of the HTTP API.
- Sign in and keep the cookies, which the session needs:
HOST=https://mail.example.com
SESSION=$(curl -s -c cookies.txt "$HOST/appsuite/api/login?action=login" \
--data-urlencode "name=anton@example.com" --data-urlencode "password=…" | jq -r .session)
- Mint the token. Choose a label you will recognize later, the scopes, and an expiry in milliseconds since the epoch; the example expires in 90 days:
EXPIRES=$(( ($(date +%s) + 90*86400) * 1000 ))
curl -s -b cookies.txt "$HOST/appsuite/api/accesstoken?action=new&session=$SESSION&label=claude-laptop&scopes=read_mail,read_calendar,read_contacts&expires=$EXPIRES" \
| jq -r .data.secret
The answer contains the secret, oxa_…, once. Copy it into the client now; it cannot be shown again. The same answer says in mail_access whether the token can read mail (see below).
- Sign out of the helper session, which the token does not need:
curl -s -b cookies.txt "$HOST/appsuite/api/login?action=logout&session=$SESSION" >/dev/null && rm cookies.txt
To see your tokens, including which scopes you may grant at all, call accesstoken?action=all; to revoke one, call accesstoken?action=delete&id=<id>, both with a signed-in session as above. A revoked token stops working immediately. Changing your password in App Suite revokes all your tokens.
The installation limits how far ahead expires may lie and how many tokens you may hold; a request beyond either is refused with a message saying so.
When mail does not work with the token. A token reads mail only if the installation could keep a credential for it when it was minted, which mail_access reports. It is false, for example, when you signed in through single sign-on while the mail server expects passwords. The token still works for calendar, contacts, files and tasks; for mail, ask the operator, who can tell which setup applies (see Personal access tokens). If you sign in through single sign-on only, you have no password for step 1; the OAuth sign-in below is then the way in, where the operator offers it.
Signing in through OAuth
Clients that run the OAuth flow themselves need nothing but the URL. On the first request without a token the endpoint answers 401 with a WWW-Authenticate header naming its metadata document; from there the client finds the installation's authorization server, registers itself if the server allows it, and sends you to its sign-in page, where you confirm the scopes. In detail:
POST /mcpwithout a token is answered with401andWWW-Authenticate: Bearer resource_metadata="https://mail.example.com/.well-known/oauth-protected-resource/mcp", scope="…".GETon that URL, without a token, returns the resource metadata (RFC 9728):resource,authorization_serversandscopes_supported, the scopes this installation's tools require.- The client reads the authorization server's own metadata (RFC 8414 or OpenID Connect discovery), registers dynamically (RFC 7591) where the server permits it or uses a client configured in advance, and runs the authorization code flow with PKCE (
S256) and theresourceparameter set to the endpoint URL. - The resulting access token is sent as
Authorization: Bearer …; when it expires, the client refreshes it and retries.
Whether this works depends entirely on the operator: the authorization server, its client registration and the scopes it issues are set up outside App Suite. If authorization_servers is missing from the metadata document, the installation offers personal access tokens only.
Configuring clients
In every example, replace the URL and oxa_… with your own values. Keep the token out of files you share or commit.
Claude Code
claude mcp add --transport http appsuite https://mail.example.com/mcp \
--header "Authorization: Bearer oxa_…"
claude mcp list then shows the server as connected, and /mcp inside a session lists its tools.
claude.ai
Under Customize, Connectors, add a custom connector with the endpoint URL. The dialog offers two ways in:
- OAuth sign-in (Sign in now or Sign in when needed). claude.ai signs you in at the installation's authorization server and asks you to confirm the scopes. Keep claude.ai's own client settings unless the operator gave you a client id and secret for claude.ai. This works only where the operator runs an authorization server.
- A personal access token (No sign-in, then Request headers). Add the header
Authorizationwith the valueBearer oxa_…. claude.ai sends the value exactly as entered, so theBearerprefix has to be part of it. Request headers are a beta that not every account shows yet.
Use a personal access token only in a connector you add for yourself, as on the Free, Pro and Max plans. On Team and Enterprise plans the owner adds the connector and its header is shared by every member, so a personal token there would give all of them access to your data.
Cursor
In ~/.cursor/mcp.json, or .cursor/mcp.json inside a project:
{
"mcpServers": {
"appsuite": {
"url": "https://mail.example.com/mcp",
"headers": { "Authorization": "Bearer oxa_…" }
}
}
}
VS Code
In .vscode/mcp.json; the token is asked for once and kept by VS Code instead of being written into the file:
{
"inputs": [
{ "type": "promptString", "id": "appsuite-token", "description": "App Suite access token", "password": true }
],
"servers": {
"appsuite": {
"type": "http",
"url": "https://mail.example.com/mcp",
"headers": { "Authorization": "Bearer ${input:appsuite-token}" }
}
}
}
Clients without HTTP transport
A client that starts MCP servers as local processes only, such as older desktop apps, can reach the endpoint through mcp-remote, which forwards between the two:
{
"mcpServers": {
"appsuite": {
"command": "npx",
"args": ["mcp-remote", "https://mail.example.com/mcp", "--header", "Authorization:${APPSUITE_AUTH}"],
"env": { "APPSUITE_AUTH": "Bearer oxa_…" }
}
}
}
The header value is passed through the environment because some platforms split arguments at spaces.
Checking a token by hand
T=oxa_…
curl -s https://mail.example.com/mcp \
-H 'Content-Type: application/json' -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: tools/list' \
-H "Authorization: Bearer $T" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' \
| jq -r '.result.tools[].name'
The list shows exactly the tools the token's scopes allow.
When something goes wrong
| What you see | What it means |
|---|---|
404 on the URL | The endpoint is not enabled for this installation, or the URL is wrong. |
401, invalid_token | The token is unknown, mistyped, revoked or expired. Mint a new one. |
403, insufficient_scope | The token lacks the scope of the tool that was called. Mint one with that scope. |
403, "The user may not use the MCP endpoint" | The token is valid, but your account may not use the endpoint: it lacks the capability mcp, or it or its context is disabled. A new token does not help; ask the operator. |
403 for a browser-based client | The client sends an Origin the installation does not allow; the operator decides which. The endpoint sends no CORS headers, so a browser client on another origin also needs CORS set up at the operator's ingress. |
| A tool call fails with "retry in … seconds" | Too many calls in a short time, or too many at once. The call is answered with a tool error and a Retry-After header saying when to try again. |
429 | The same for a resource read, which has no tool error to carry it. |
503 | The token could not be checked right now. Keep it and retry. |
| Mail tools fail, everything else works | The token has mail_access: false; see above. |
| A tool is missing from the list | Its scope is not in the token, or the operator has not installed that part. |
Every call is logged with the client name, the tool and the outcome, but not with the arguments' values or the content that was read. What a tool returned is passed to the language model and, from there, is subject to that model provider's terms.