Skip to main content
This page is for people who build an MCP client. To connect an existing client, see Connect an AI assistant. The Workchats MCP server follows the MCP authorization spec with OAuth 2.1. A client needs only the MCP URL. It finds everything else through discovery. The OAuth endpoints, clients and tokens on this page are separate from the public API’s OAuth install: an MCP token can’t call /v1, and a bot token can’t call /mcp.

Hosts

Each environment has one host. It is the OAuth issuer, the authorization server and the resource server. The examples below use Production.

Discovery

A request to /mcp without a token gets 401 and a challenge that names the resource metadata:
GET /.well-known/oauth-protected-resource/mcp:
For /mcp/readonly, resource is https://mcp.workchats.com/mcp/readonly, scopes_supported is ["workchats:read"], and the 401 challenge names that metadata URL and scope="workchats:read". GET /.well-known/oauth-authorization-server:

Register a client

A client identifies itself in one of two ways. Both are open: any client may register, and a company admin can block a client by its block key.

Client ID Metadata Document (preferred)

Use an https:// URL as your client_id. At /authorize, Workchats fetches the document at that URL. The fetch:
  • uses HTTPS only, follows no redirects, waits at most 3 seconds and reads at most 10 KB
  • refuses private and reserved addresses
  • needs the document’s client_id to equal its URL, and its redirect_uris to contain the requested redirect_uri
  • needs a URL with a path (not just the host) of at most 2048 bytes
  • needs a public client: token_endpoint_auth_method absent or none. Any other value gets invalid_client at /authorize.
A good document is cached for its Cache-Control max-age, kept between 5 minutes and 24 hours. With no max-age, or with no-store, it is cached for 5 minutes. A failed fetch is not cached. One document URL is fetched at most 5 times in 10 minutes.

Dynamic client registration

POST /register, with no authentication, as JSON or a form:
Rules:
  • client_name is 1 to 100 printable characters. It can’t contain the words Workchats, Octogle, admin, system or support.
  • redirect_uris lists 1 to 5 URIs. Each is one of:
    • https:// on any host
    • loopback http:// on 127.0.0.1, localhost or [::1], any port
    • a private-use scheme that contains a period, for example com.example.app:/callback, or one of the editor schemes cursor, vscode, vscode-insiders, windsurf and zed
  • No URI may have a fragment or a user name.
  • The URIs of one registration are all https on one host, or all on one private-use scheme, or loopback with or without https on any hosts. Any other mix is refused. VS Code’s fallback registration lists https://insiders.vscode.dev/redirect, https://vscode.dev/redirect, http://127.0.0.1/ and http://127.0.0.1:33418/ in one request. That mix is accepted. Its block key is local:, the lower-case client_name and @web, so it never shares a key or a fixed display name with an all-loopback app. Only an https-only registration gets a host key, because anyone can list an https host they do not own.
  • grant_types may hold only authorization_code and refresh_token. response_types must be ["code"].
  • token_endpoint_auth_method is none, client_secret_basic or client_secret_post. If you leave it out, it is client_secret_basic. The two secret methods get a client_secret, shown once.
  • Extra metadata (logo_uri, client_uri, tos_uri, policy_uri, contacts, scope, software_id, software_version) is stored, at most 4 KB, but never fetched or shown.
The answer is 201 with a client_id that starts with mcpc_, and the registered metadata. A registered client that never connects anyone is deleted after 30 days.

Private-use schemes

The MCP authorization spec allows only https and localhost redirect URIs. Workchats also accepts private-use schemes (RFC 8252 section 7.1), because desktop apps and editors such as Cursor and VS Code use them. The consent screen warns the employee that the code goes to an app on their computer, as it does for a loopback redirect.

Block key and display name

Every client gets a block key, the part of its identity Workchats can check. A company admin blocks a client by this key, so a block survives a new registration. Well-known keys get a fixed display name: claude.ai is “Claude”, chatgpt.com and chat.openai.com are “ChatGPT”, local:claude code is “Claude Code”, cursor is “Cursor” and vscode is “VS Code”. A local: key of a well-known host gets that host’s name. Any other client shows its client_name, cut to 40 characters. The display name is what the consent screen shows and what the “via” label of a sent message says.

Authorize

Send the employee’s browser to /authorize with PKCE:
  • code_challenge_method must be S256. plain is refused. The challenge is 43 characters of base64url.
  • redirect_uri must match a registered URI exactly. A loopback URI may differ only in its port.
  • resource (RFC 8707) is https://mcp.workchats.com/mcp or https://mcp.workchats.com/mcp/readonly. Left out, it is /mcp.
  • scope is a subset of the resource’s scopes. Left out, it is all of them.
If the client or the redirect URI is wrong, Workchats shows an error page in the browser and never redirects. Any other error redirects to your redirect_uri with error and your state: unsupported_response_type, invalid_request, invalid_target or invalid_scope. On success, the browser goes to the Workchats consent screen. The employee signs in, picks one company and approves. Workchats then redirects to:
Check that iss is the issuer you started with (RFC 9207). If the employee denies, the redirect carries error=access_denied instead of code. The request is valid for 10 minutes, and the code for 10 minutes after approval. Only employees with an active account can approve, and only in a company that has AI assistants on and has not blocked your client.

Exchange the code

POST /token, as a form or JSON:
A client with a secret authenticates with HTTP Basic or with client_id and client_secret in the body. HTTP Basic wins if both are sent.
Token responses carry Cache-Control: no-store. Outside Production the tokens contain _test_ in place of _live_. A code works once. Presenting a used code again returns invalid_grant and revokes the connection that code created, because a replayed code is more likely theft than a retry. A new connection replaces an older one of the same employee, with the same client_id and the same redirect URI. Loopback redirect URIs count as the same whatever their port. So Claude on the web and a local app that uses Claude’s client_id keep separate connections.

Call the MCP endpoint

Send the access token in the Authorization header on every request:
  • A token in the query string is refused with 400.
  • A token for /mcp also works on /mcp/readonly, where it lists only read tools. A token for /mcp/readonly works only there.
  • A token without workchats:write lists only read tools. Calling a write tool with it gets 403 with WWW-Authenticate: Bearer error="insufficient_scope", so you can ask the employee for more.
  • The server speaks MCP protocol versions 2026-07-28, 2025-11-25 and 2025-06-18 over Streamable HTTP.
  • initialize returns an Mcp-Session-Id. Send it on every later request of the session. A request without it gets 400, and an id from another connection or an ended session gets 404. A session ends on DELETE or after 10 idle minutes. A stateless 2026-07-28 request needs no session.
  • A browser request must send an Origin that is one https origin or a loopback http origin. Any other Origin gets 403.
  • A request body is at most 1 MB, and must be JSON or a form. A larger body gets 413 with {"error": "request_too_large"}.
Every request checks the connection again: the employee, the company’s AI assistants setting and the block list. A failure is 401 with error="invalid_token" in the challenge.

Scopes

There is no offline_access scope. Every grant gets a refresh token.

Tokens and lifetimes

Tokens are opaque, not JWTs. Workchats looks each one up on every request, so a revocation takes effect at once. Refresh. POST /token with grant_type=refresh_token and refresh_token, authenticated as for the code grant. The answer has a new access token and a new refresh token. The old refresh token is spent, and the old access token stops working.
  • A refresh token works only for the client it was issued to.
  • scope, if sent, can’t be wider than the grant: invalid_scope. resource, if sent, must be the grant’s: invalid_target.
  • Reuse ends the connection. A spent refresh token presented again revokes the whole connection and returns invalid_grant. There is no grace period. A client that loses a refresh response must connect again.
  • A connection ends when no refresh happens for 30 days, or when the company’s maximum session lifetime passes.

Revocation

POST /revoke (RFC 7009) with token, an access or a refresh token, authenticated as for /token, revokes the connection of that token. It answers 200 with {}, also for an unknown token, a revoked token or a token of another client. Those it leaves alone. Workchats also ends a connection when: After revocation, the next MCP request gets 401 with error="invalid_token". Show your own “reconnect” prompt. Turning AI assistants on again or unblocking a client brings no connection back.

Rate limits

OAuth endpoints, per IP address and per client: Over a limit, /token, /revoke and /register answer 429 with {"error": "slow_down"} and Retry-After. /authorize answers 429 with a plain text page. The MCP endpoint has per-employee limits, shared by all of an employee’s connections. See Limits.

Errors

OAuth endpoints answer in the RFC 6749 shape, not the public API’s error envelope:
Tool errors inside a successful MCP response are described in Tools.