MCP Telegram
Back to Quickstart

OAuth for custom MCP clients

If you're building your own MCP client (CLI, SDK, IDE plugin), this page describes our OAuth implementation: standards we support, endpoints, redirect_uri rules, and how to debug common errors. Claude.ai and ChatGPT users don't need this page — use the Quickstart instead.

Standards we implement

mcp-telegram.com is fully compliant with the OAuth specifications that the MCP ecosystem relies on — no proprietary extensions, no shared secrets baked into clients.

Endpoints

Authorization server metadata (RFC 8414)
Dynamic client registration (RFC 7591)
Authorization endpoint
Token endpoint
MCP resource (Streamable HTTP)

Authorization code flow with PKCE

Standard RFC 6749 §4.1 + RFC 7636 flow. No surprises — if your OAuth library handles "authorization code + PKCE", it will work.

1

Register your client

POST /oauth/register with { redirect_uris: ["http://127.0.0.1:<port>/callback"], client_name: "your-client-name" }. The server returns client_id + client_secret (the secret is optional for public PKCE clients but harmless to receive).

2

Generate PKCE pair

Generate a random 43-128 char code_verifier, then compute code_challenge = BASE64URL(SHA256(code_verifier)). Store the verifier locally for step 4.

3

Open /oauth/authorize in a browser

Redirect the user to /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...&code_challenge=...&code_challenge_method=S256. The server displays a QR code; the user scans it in the Telegram app to authenticate.

4

Receive the authorization code

After QR scan, the server redirects to your redirect_uri with ?code=...&state=.... Verify the state parameter matches what you sent.

5

Exchange code for token

POST /oauth/token with grant_type=authorization_code, code, redirect_uri, code_verifier, client_id. Receive an access_token (10-year TTL — see Token Lifetime below) and a refresh_token. Use the access_token as Authorization: Bearer ... on /mcp requests.

redirect_uri matching rules

We follow RFC 8252 §7.3 and §8.4 strictly. The matching algorithm depends on whether the registered URI is a loopback IP literal.

Loopback (http://127.0.0.1 or http://[::1])

If you registered a loopback URI, the authorize-time redirect_uri must match scheme, host, path, and query exactly — but the port may differ. This is required by RFC 8252 §7.3 because native clients bind an OS-assigned ephemeral port that may change between runs.

HTTPS (https://your-domain.example/...)

Exact byte-for-byte match required, including path, query, and port (RFC 6749 §3.1.2). No flexibility.

localhost (NOT recommended)

We treat http://localhost as a regular hostname — exact match required, no port flexibility. RFC 8252 §8.3 recommends using IP literals (127.0.0.1 / [::1]) instead of localhost because DNS resolution is implementation-dependent.

PKCE is mandatory

code_challenge_method=S256 is required on every authorize request. Plain method is rejected. If you skip code_challenge entirely, the /token exchange will fail at code_verifier validation.

Token lifetime

access_token TTL is intentionally long (10 years) because some MCP clients do not persist refresh_token reliably. A refresh_token is still issued and rotates on every refresh (RFC 6819 §5.2.2.3). The user can revoke all tokens any time at /my/sessions or by logging out of Telegram. The user can also explicitly POST to /oauth/revoke (RFC 7009) to terminate a session.

Common errors

HTTP 400 "Invalid redirect_uri" at /oauth/authorize

The redirect_uri query parameter doesn't match any URI registered for this client_id. For loopback clients: check that scheme + host + path + query match exactly (only the port is flexible). For HTTPS clients: full byte-equality is required — watch for trailing slashes and URL-encoding differences.

HTTP 400 "Unknown client"

The client_id doesn't exist in our database. Either you skipped the /oauth/register step, the registration response was lost before you saved the client_id, or the client was revoked by the user via /my/clients.

Client surfaces "Needs Auth" repeatedly

If your client persists access_token but not refresh_token, you'll see this when the access_token expires. Our access tokens are 10-year TTL specifically to mitigate this, but if your client expires tokens client-side after some shorter window, you'll need to either persist refresh_token or call /oauth/revoke + re-auth on demand.

Tested clients

Confirmed to interoperate with our server. If you build something new and it works, send us a PR to add it here.

Multiple Telegram accounts on one OAuth connection

Since v2.32.0 a single OAuth connection can hold several Telegram identities and switch between them with one tool call — no Disconnect/Connect cycle, no new client registration.

1

List what's attached

Call telegram-accounts-list. You will see your primary (OAuth-bound) account marked with ⭐ if it's active, plus any secondaries you've added.

2

Attach another account

Call telegram-accounts-add with an optional label (e.g. "testing"). The tool returns a one-time URL (TTL 10 minutes) — open it on any device and scan the QR with the Telegram account you want to add.

3

Switch active account

Call telegram-accounts-switch with identifier=label, @username, account_id, or 'primary'. The next telegram-* tool call uses that account immediately. No reconnect.

4

Detach a secondary

Call telegram-accounts-remove identifier=… on a secondary. The Telegram account itself is NOT logged out — only the binding here. The primary cannot be removed this way; use your client's Disconnect flow to fully sign out.

Each OAuth connection is isolated: accounts you attach via Hermes are invisible to Claude.ai or ChatGPT, even if it's the same Telegram identity. To connect different accounts to different MCP clients, register each client separately and use telegram-accounts-add inside its own session.