MCP server for AI assistants
The EthicsPortal MCP server lets an AI assistant work with EthicsPortal for a person who approves it. It offers the operations of the API as tools of the Model Context Protocol (MCP). The assistant acts as that person, with their current role, only in the organisations they choose. Report content is never available.
Status
Section titled “Status”The MCP server is available to every organisation that has API access. It opened on 11 November 2026.
- EthicsPortal has tested it with Claude Code. Other assistants that support remote MCP servers with OAuth are expected to work. Write to support@ethicsportal.eu if one does not.
- If API access is suspended for an organisation, the person cannot choose it on the consent screen, and its tools answer
not_found. - If EthicsPortal switches assistant connections off, the MCP endpoint and every OAuth address on this page answer
404.
The REST API and webhooks do not depend on the MCP server. Choose an API token for scripts and server integrations.
Add EthicsPortal to an assistant
Section titled “Add EthicsPortal to an assistant”A person adds the server to their assistant once. They need no EthicsPortal account first: a person with no account signs up while they connect.
| Assistant | How to add the server |
|---|---|
| Claude Code | Run claude mcp add --transport http ethicsportal https://secure.ethicsportal.eu/mcp. Then open /mcp, choose ethicsportal and Authenticate |
| Cursor | Add the server to ~/.cursor/mcp.json, as shown below. Sign in when Cursor asks |
| Claude | Settings → Connectors → Add custom connector. Enter the address https://secure.ethicsportal.eu/mcp |
| Any other assistant | Add https://secure.ethicsportal.eu/mcp as a remote MCP server or custom connector in the assistant’s settings |
{ "mcpServers": { "ethicsportal": { "url": "https://secure.ethicsportal.eu/mcp" } } }The assistant then opens a browser window. The person signs in or signs up there, reads the consent screen and presses Connect.
An assistant cannot use a server that nobody added. A coding assistant can add the server itself, because it can run the command or change its configuration. A chat assistant cannot: the person adds the connector in its settings. The setup skill gives an assistant these steps, and the steps that follow, in a form it can read.
Endpoint and transport
Section titled “Endpoint and transport”| Property | Value |
|---|---|
| Endpoint | https://secure.ethicsportal.eu/mcp |
| Transport | Streamable HTTP, POST only |
| Request | One JSON-RPC message per request. A batch is refused |
| Response | One JSON document, or no body. The server opens no stream |
| Session | None. The server issues no Mcp-Session-Id |
| Authorization | Authorization: Bearer with an OAuth access token issued for this endpoint |
| Features | Tools only. No prompts and no resources |
Send Content-Type: application/json and Accept: application/json, text/event-stream with every request.
Two lifecycles work:
- Revision
2026-07-28, without a handshake. Each request carries its protocol revision, andserver/discoverdescribes the server. - Revisions
2025-11-25and2025-06-18, with theinitializehandshake. Send theMCP-Protocol-Versionheader on the requests that follow. A client that asks for2026-07-28ininitializeis offered2025-11-25.
The server returns short instructions for the assistant in both lifecycles. They tell it to start with get_identity.
curl --fail-with-body https://secure.ethicsportal.eu/mcp \ -H "Authorization: Bearer $ETHICSPORTAL_MCP_TOKEN" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' \ -H 'Mcp-Method: tools/call' \ -H 'Mcp-Name: get_identity' \ --data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_identity","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'Most clients do not build these requests themselves. Add the endpoint address as a remote MCP server in the assistant. The assistant must support OAuth 2.1 with PKCE and Dynamic Client Registration.
Connect a client
Section titled “Connect a client”A connection is made with OAuth 2.1. The client is a public client: it has no secret. The steps are discovery, registration, the authorization request, and the token exchange.
Discovery
Section titled “Discovery”A request without a usable token answers 401. The WWW-Authenticate header names the protected resource metadata (RFC 9728):
WWW-Authenticate: Bearer scope="reports:read channel:read channel:write webhooks:read webhooks:write audit_log:read members:read members:write documents:read billing:read billing:write organizations:write organizations:create", resource_metadata="https://secure.ethicsportal.eu/.well-known/oauth-protected-resource/mcp"curl --fail-with-body https://secure.ethicsportal.eu/.well-known/oauth-protected-resource/mcp{ "resource": "https://secure.ethicsportal.eu/mcp", "authorization_servers": ["https://secure.ethicsportal.eu"], "scopes_supported": [ "reports:read", "channel:read", "channel:write", "webhooks:read", "webhooks:write", "audit_log:read", "members:read", "members:write", "documents:read", "billing:read", "billing:write", "organizations:write", "organizations:create" ], "bearer_methods_supported": ["header"]}scopes_supported lists every scope a connection can hold, here and in the authorization server metadata. The same document is also at /.well-known/oauth-protected-resource. The authorization server metadata (RFC 8414) is at the issuer:
curl --fail-with-body https://secure.ethicsportal.eu/.well-known/oauth-authorization-serverThe response includes these fields:
{ "issuer": "https://secure.ethicsportal.eu", "authorization_endpoint": "https://secure.ethicsportal.eu/oauth/authorize", "token_endpoint": "https://secure.ethicsportal.eu/oauth/token", "revocation_endpoint": "https://secure.ethicsportal.eu/oauth/revoke", "registration_endpoint": "https://secure.ethicsportal.eu/oauth/register", "scopes_supported": [ "reports:read", "channel:read", "channel:write", "webhooks:read", "webhooks:write", "audit_log:read", "members:read", "members:write", "documents:read", "billing:read", "billing:write", "organizations:write", "organizations:create" ], "response_types_supported": ["code"], "response_modes_supported": ["query"], "grant_types_supported": ["authorization_code", "refresh_token"], "token_endpoint_auth_methods_supported": ["none"], "code_challenge_methods_supported": ["S256"], "authorization_response_iss_parameter_supported": true, "resource_indicators_supported": true}There is no token introspection endpoint.
Register the client
Section titled “Register the client”Clients register themselves with Dynamic Client Registration (RFC 7591). Client ID Metadata Documents are not supported yet.
curl --fail-with-body https://secure.ethicsportal.eu/oauth/register \ -H 'Content-Type: application/json' \ --data '{"client_name":"Example assistant","redirect_uris":["https://assistant.example/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"]}'A 201 response contains the client_id. There is no client secret.
{ "client_id": "CLIENT_ID", "client_id_issued_at": 1794268800, "client_name": "Example assistant", "redirect_uris": ["https://assistant.example/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none"}| Field | Accepted |
|---|---|
redirect_uris |
Required. One to ten addresses, 4,000 characters in total |
client_name |
Optional. Up to 100 characters, without control or invisible characters. Without a name, the host of the first redirect address is used |
token_endpoint_auth_method |
Omitted, or none |
grant_types |
Omitted, or only authorization_code and refresh_token |
response_types |
Omitted, or only code |
Every other field is ignored and is not stored.
A redirect address must use https, or http on a loopback host: localhost, 127.0.0.1 or ::1. It must not contain user information or a fragment, and it must not point to an EthicsPortal host. Custom URL schemes are refused. The only exception is the exact address cursor://anysphere.cursor-mcp/oauth/callback; no other address with that scheme is accepted. A refused address answers 400 with invalid_redirect_uri. Any other refused field answers 400 with invalid_client_metadata.
A registration that has no connection is deleted when it is 30 days old. If a client ID is no longer recognised, register again.
Ask for authorization
Section titled “Ask for authorization”Send the person’s browser to the authorization endpoint:
https://secure.ethicsportal.eu/oauth/authorize ?response_type=code &client_id=CLIENT_ID &redirect_uri=https%3A%2F%2Fassistant.example%2Fcallback &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &resource=https%3A%2F%2Fsecure.ethicsportal.eu%2Fmcp &scope=reports%3Aread%20channel%3Aread &state=STATE| Parameter | Rule |
|---|---|
response_type |
code only |
redirect_uri |
A registered address. For a loopback address, the port can differ from the registered one |
code_challenge, code_challenge_method |
PKCE is required, with S256 only. plain is refused |
resource |
Required. Exactly https://secure.ethicsportal.eu/mcp |
scope |
Optional. Without it, the request asks for every scope a connection can hold. See Scopes |
response_mode |
Omitted, or query |
A request that breaks one of these rules is refused on an error page in EthicsPortal. The browser is not sent back to the client, so the client receives no error redirect.
What the person sees
Section titled “What the person sees”- The person signs in to EthicsPortal if they are not signed in, and completes a fresh-authentication challenge. A person with no account creates one at this step: they confirm their email address, accept the account terms and enter their name. Then they return to the same request.
- The consent screen shows the name of the app and the host of its redirect address. It states that the app chose both and that EthicsPortal has not verified them.
- It lists the permissions the app asks for, in plain language, and the limits of a connection.
- It lists the organisations the person can connect. These are the organisations where the person is an active member and API access is on. The person ticks at least one.
- Connect sends the browser back to the redirect address with
code,stateandiss. Cancel sends it back witherror=access_denied.
Consent is asked every time. An earlier connection never approves a new request by itself.
After each approval, the person receives one email. It names the app and its address, the time, the organisations, the permissions and the end date, and it links to Connected apps, where the person can end the connection.
Start without an organisation
Section titled “Start without an organisation”A person who has no organisation in EthicsPortal can connect an assistant, and the assistant creates their first organisation.
- The authorization request must include
organizations:create. A request that follows the metadata includes it. - The consent screen shows no list of organisations. It says that the app can create one, and that the person accepts its Data Processing Agreement afterwards.
- The connection reaches no organisation at first.
get_identityreturns an emptyorganizationslist. create_organizationcreates the organisation with the person as its owner, and adds it to the connection.- The owner accepts the organisation’s Data Processing Agreement in a browser, at
https://secure.ethicsportal.eu/organizations/{organization_id}. Until then, the organisation’s tools answeragreement_required. The assistant cannot accept it. - After that, the assistant can configure the whistleblowing channel, invite members and create a checkout link, with the scopes the person approved.
The tool results guide the assistant through steps 3 to 5. The empty get_identity result, the create_organization result and every agreement_required answer carry a next_step text: what to do next, and the address the person must open. The text is in the result’s content and in structuredContent.next_step. The REST API does not return this field.
Exchange the code
Section titled “Exchange the code”The authorization code is valid for one minute and for one use.
curl --fail-with-body https://secure.ethicsportal.eu/oauth/token \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode "code=$CODE" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode 'redirect_uri=https://assistant.example/callback' \ --data-urlencode "code_verifier=$CODE_VERIFIER" \ --data-urlencode 'resource=https://secure.ethicsportal.eu/mcp'{ "access_token": "ACCESS_TOKEN", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "REFRESH_TOKEN", "scope": "reports:read channel:read", "created_at": 1794268800}The access token works at the MCP endpoint only. The REST API refuses it with 401. A resource other than the MCP endpoint answers invalid_target.
Refresh and revoke tokens
Section titled “Refresh and revoke tokens”curl --fail-with-body https://secure.ethicsportal.eu/oauth/token \ --data-urlencode 'grant_type=refresh_token' \ --data-urlencode "refresh_token=$REFRESH_TOKEN" \ --data-urlencode "client_id=$CLIENT_ID"Each refresh returns a new access token and a new refresh token. The earlier access token and refresh token stop working at once. Store the new pair before you use it.
- A refresh cannot change the scopes. If you send
scope, it must name exactly the scopes of the connection, or the answer isinvalid_scope. - A refresh token works once. There is no period in which the earlier one is still accepted.
- Other grant types answer
unsupported_grant_type.
To end a connection from the client, revoke its current access token or refresh token (RFC 7009):
curl --fail-with-body https://secure.ethicsportal.eu/oauth/revoke \ --data-urlencode "token=$REFRESH_TOKEN" \ --data-urlencode "client_id=$CLIENT_ID"A client can revoke only its own tokens. Another client’s token answers 403.
Lifetimes
Section titled “Lifetimes”| Item | Lifetime |
|---|---|
| Authorization code | One minute, one use |
| Access token | One hour |
| Refresh token | One use. Each refresh returns a new one |
| Connection | 90 days from the approval. A refresh does not extend it |
Browser-based clients
Section titled “Browser-based clients”A client that runs in a web page on another origin can connect. The MCP endpoint, both metadata documents and the registration, token and revocation endpoints answer with Access-Control-Allow-Origin: * and answer an OPTIONS preflight with 204. They never use cookies, and they send no Access-Control-Allow-Credentials header. The MCP endpoint and the protected resource metadata expose the WWW-Authenticate and Retry-After headers to the page.
The consent screen has no such headers. Open it as a page in the person’s browser.
What ends a connection
Section titled “What ends a connection”When a connection ends, its access tokens answer 401 and its refresh tokens answer invalid_grant. The person must approve a new authorization request.
- 90 days have passed since the approval.
- The person disconnects the app under Connected apps.
- An administrator removes the connection from the last organisation it reaches.
- The person’s membership is deactivated in the last organisation it reaches.
- The client revokes its current access token or refresh token.
- A refresh token is used a second time. This includes a client that repeats a refresh request after a lost response.
- An authorization code is exchanged a second time with its PKCE verifier.
- The person approves a new request from the same app. The new connection replaces the earlier one, and the 90 days start again.
A connection stops working, without ending, while the person’s account cannot authenticate or while assistant connections are switched off.
What a connection can do
Section titled “What a connection can do”A connection acts as the person who approved it. Every tool call checks that person’s current role and case permissions again, as described in Case permissions. A connection never does more than the person can do in the web application.
- It reaches only the organisations the person ticked. Any other organisation answers
not_found, also when the person is a member of it. - An organisation that the person joins later is not added. The one exception is an organisation created with the
create_organizationtool of that connection. - When the person’s membership in an organisation is deactivated, that organisation is removed from the connection.
- The person’s account terms and the organisation’s agreement must be accepted, as for the API. Otherwise the tool answers
agreement_required.
Tool descriptions come from the API reference. Where a description says “personal token”, it applies to a connection in the same way.
Scopes
Section titled “Scopes”A connection uses the scopes of the API. The scope table describes each one. A connection can hold these:
reports:read, channel:read, channel:write, webhooks:read, webhooks:write, audit_log:read, members:read, members:write, documents:read, billing:read, billing:write, organizations:write, organizations:create
invitations:respondcannot be granted. Accepting an invitation joins an organisation that the person did not choose on the consent screen. A request that asks for it is refused.get_identityandsubmit_feedbackneed no scope.members:writelets the app send invitations with any role, in the organisations where the person is an administrator. A person who is invited as an administrator can read reports in the web application after they accept. The consent screen warns the person when an app asks for this scope, and each invitation is recorded in the audit trail as made through an AI assistant.- The metadata advertises every scope, so a client that follows it asks for all of them in the first authorization request. Most assistants make the person sign in again for each scope added later. A client can ask for fewer scopes. The consent screen always shows every scope the client asks for.
Ask for a further scope
Section titled “Ask for a further scope”A connection that asked for fewer scopes can ask for more later. When a tool needs a scope the connection does not have, nothing runs. The whole HTTP response is 403:
WWW-Authenticate: Bearer error="insufficient_scope", error_description="This tool needs the webhooks:write scope.", scope="reports:read channel:read webhooks:write", resource_metadata="https://secure.ethicsportal.eu/.well-known/oauth-protected-resource/mcp"- Read
scope. It names the scopes the connection already has and the missing one. - Send a new authorization request with exactly that
scope. - The person approves on the consent screen. The organisations of the earlier connection are ticked already.
- Exchange the new code, then call the tool again with the new access token.
The new connection replaces the earlier one. The earlier tokens stop working, the 90 days start again, and the person receives a new email.
The server offers one tool for each API operation, with the exceptions listed below. Tool definitions are generated from the OpenAPI schema, so a tool has the same fields, rules and errors as its operation. Follow the link of a tool to its page in the API reference.
- The tool name is the
operationIdof the operation in snake case:listReportsbecomeslist_reports. - The tool title and description are the summary and description of the operation.
- Tools that read are marked read-only. Tools that send
PATCHorDELETEare marked destructive and idempotent. tools/listreturns the tools in the order of the OpenAPI schema.
| Tool | API operation | Scope |
|---|---|---|
get_identity | Discover organisations | None |
get_channel | Read whistleblowing channel metadata | channel:read |
list_reports | List report metadata | reports:read |
get_report | Read report metadata | reports:read |
create_organization | Create an organisation | organizations:create |
get_channel_configuration | Read channel configuration | channel:write |
update_channel_configuration | Configure a whistleblowing channel | channel:write |
list_webhooks | List webhooks | webhooks:read |
create_webhook | Create a webhook | webhooks:write |
get_webhook | Read a webhook | webhooks:read |
update_webhook | Update a webhook | webhooks:write |
delete_webhook | Delete a webhook | webhooks:write |
rotate_webhook_secret | Rotate a webhook's signing secret | webhooks:write |
test_webhook | Send a test event | webhooks:write |
list_webhook_deliveries | List webhook deliveries | webhooks:read |
retry_webhook_delivery | Retry a failed delivery | webhooks:write |
list_audit_trail | List the audit trail | audit_log:read |
list_report_audit_trail | List a report's audit trail | audit_log:read |
list_members | List members | members:read |
list_invitations | List pending invitations | members:read |
create_invitation | Invite a member | members:write |
revoke_invitation | Revoke an invitation | members:write |
resend_invitation | Resend an invitation | members:write |
update_organization | Rename the organisation | organizations:write |
list_documents | List documents | documents:read |
get_billing_status | Read billing status | billing:read |
create_checkout_link | Create a subscription checkout link | billing:write |
create_billing_portal_link | Create a billing portal link | billing:write |
submit_feedback | Send product feedback | None |
Arguments
Section titled “Arguments”- Path parameters, such as
organization_id, are record IDs. Take them fromget_identityand from earlier results. A value that is not a record ID is refused before anything runs. - Query parameters, such as
limit,cursorandstatus, are arguments with the same names. - The request body keeps its shape.
create_webhooktakes awebhookobject, as the API does. idempotency_keyis an argument of every tool whose operation accepts theIdempotency-Keyheader.- Arguments that the tool does not declare are refused.
An Idempotency-Key header that the client sends to the MCP endpoint does not reach the operation: use the argument. Conditional requests with If-None-Match are not available through tools.
The params of a tools/call request:
{ "name": "list_reports", "arguments": { "organization_id": "gHiJkl", "status": "received", "limit": 50 }}Results and errors
Section titled “Results and errors”A successful call returns the JSON response of the API in structuredContent. The same JSON is in content as text. An operation that returns no content answers with the text Done.
When the API refuses the call, the result has isError: true, and structuredContent holds the error body of the API. The HTTP status of the MCP response is still 200, so read error.code:
{ "content": [ { "type": "text", "text": "{\"error\":{\"code\":\"validation_failed\",\"fields\":[\"url\"],\"feedback_url\":\"https://secure.ethicsportal.eu/me/feedback.json\"}}" } ], "structuredContent": { "error": { "code": "validation_failed", "fields": ["url"], "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json" } }, "isError": true}feedback_url is the address of the product feedback operation. Through MCP, use the submit_feedback tool for the same purpose.
Through a connection, get_identity returns type connection, the name of the app as name, and last_four null. Its expires_at is the end of the connection, and organizations lists only the organisations the connection reaches.
Pagination, retries and rate limits
Section titled “Pagination, retries and rate limits”- Pagination. A list returns
next_cursor. Call the tool again withcursorset to that value until it isnull. Pagination. - Safe retries. Send
idempotency_keywith every tool that accepts it, and send the same key again when you retry. The rules are those of the API: Safe retries. Keys belong to the connection. A new approval creates a new connection, so earlier keys do not carry over. - Rate limits of the endpoint. Each connection can send 120 MCP requests per minute. Requests without a usable token are limited to 120 per minute per source address. Above the limit the HTTP response is
429withRetry-After: 60. - Rate limits of the API. Tool calls also count toward the API limits: 60 per minute per connection and 300 per minute per organisation. Above these, the tool result is an error with the code
rate_limited.
Operations without a tool
Section titled “Operations without a tool”| API operation | Why it has no tool |
|---|---|
| List my invitations |
It needs invitations:respond, which no connection can hold.
|
| Accept an invitation |
It needs invitations:respond, which no connection can hold.
|
| Decline an invitation |
It needs invitations:respond, which no connection can hold.
|
| Download the compliance report |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
| Download the compliance certificate |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
| Download the whistleblowing policy |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
| Download the privacy notice |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
| Download the poster |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
| Download the case handler manual |
It returns a file, and a file does not fit a tool result. list_documents returns its download link.
|
list_documents returns the address of each document. An access token of a connection cannot download it, because the API refuses that token. A signed-in person can open the address in a browser, and a script can download it with an API token.
Billing tools return a link to a Stripe-hosted page for a person to open. They never take a payment.
Data boundary
Section titled “Data boundary”The MCP server has the same data boundary as the API. Report content, reporter identity, messages, notes and attachments are never available. No tool reads report content, and no tool submits or changes a report.
A tool returns the response of its API operation, so every field that the API excludes is also absent here. Excluded fields are omitted entirely. They are never returned as REDACTED, and there is no expansion option.
No tool returns report content. One permission can give another person access to reports in the web application: see members:write under Scopes.
Operational metadata can still identify a case inside your organisation. A tool result leaves EthicsPortal and goes to the assistant that the person connected. Administrators can see and remove these connections under Connected apps.
Manage connections
Section titled “Manage connections”- The person sees their connections in account settings under Connected apps. Each entry shows the app, its address, the organisations, the permissions, the dates and the last activity. Disconnect ends the connection in every organisation.
- An administrator sees every connection that reaches the organisation under Organisation → Integrations → Connected apps, with the member who connected it. Remove access removes that organisation from the connection. The person’s other organisations are not affected.
- The audit trail. Where the API records a request in the audit trail, it records the same tool call. The entry names the person and is marked AI assistant, with the name and address of the app. In the audit trail API, the actor’s kind is
mcp. Approving, removing and ending a connection is recorded in the audit trail of each organisation it reaches.
Errors and troubleshooting
Section titled “Errors and troubleshooting”HTTP responses of the MCP endpoint:
| Status | Meaning | Action |
|---|---|---|
| 400 | The body is not one JSON-RPC request, or the protocol revision is not supported | Send one request per POST. The error lists the supported revisions |
| 401 | There is no token, or the token cannot be used: it is unknown, expired or issued for another resource, or its connection has ended | Refresh the access token. If the refresh answers invalid_grant, start a new authorization request |
| 403 | The tool needs a scope the connection does not have | Ask for a further scope |
| 404 | Assistant connections are switched off. In the 2026-07-28 lifecycle, also a JSON-RPC method that the server does not offer, such as prompts/list |
Try again later, or write to support. Use tools only |
| 405 | The method is GET or DELETE. The response has Allow: POST |
Send POST |
| 406, 415 | The Accept or Content-Type header is not accepted |
Send the headers shown under Endpoint and transport |
| 413 | The request body is larger than 1 MB | Send a smaller request |
| 429 | The connection or the source address sent too many requests | Wait for the Retry-After interval |
A tool error arrives with HTTP 200 and isError: true. The codes are those of the API errors. The most frequent:
| Code | Meaning |
|---|---|
not_found |
The record does not exist, the person cannot access it, the organisation is not part of the connection, or API access is suspended |
forbidden |
The person’s current role does not allow the action |
agreement_required |
The person or the organisation must accept an agreement in the web application |
validation_failed |
Correct the fields named in fields |
rate_limited |
Wait one minute |
OAuth endpoints:
| Where | Answer | Meaning |
|---|---|---|
| Registration | 400 invalid_redirect_uri |
A redirect address is missing or not accepted |
| Registration | 400 invalid_client_metadata |
The client is not a public client, or a field is not accepted |
| Registration, token, revocation | 413 |
The request body is larger than 16 KB |
| Authorization | Error page | The request is not valid. The browser is not sent back to the client |
| Authorization | Page “Assistant connections are not available” | The person has no organisation with API access, and the request does not include organizations:create |
| Token | invalid_grant |
The code or refresh token is unknown, expired or already used, the PKCE verifier is wrong, or the connection has ended |
| Token | invalid_target |
resource is not the MCP endpoint |
| Token | invalid_scope |
A refresh asked for scopes other than those of the connection |
| Any | 429 |
Too many requests. Registration allows 20 per hour and authorization 30 per minute per source address. The token and revocation endpoints allow 60 per minute per client at one source address, and 600 per minute per source address |