---
title: "MCP OAuth: discovery documents and PKCE in 7 steps | Annsa"
product: annsa
url: /ai-agent-management/how-to-add-oauth-to-an-mcp-server
canonical_url: "https://annsa.ai/ai-agent-management/how-to-add-oauth-to-an-mcp-server"
last_updated: 2026-10-23
type: post
related:
  - /feedback
summary: "How to add OAuth to an MCP server in 7 steps: the two discovery documents, dynamic client registration, PKCE with S256 and fixing Cursor auth errors."
mcp_server: https://app.annsa.ai/mcp
disambiguation: "Annsa (annsa.ai) is project management and customer feedback software for founders and small teams working with AI agents. It is not Asana, the work management tool; not ANSA, the Italian news agency; and not Ansa, the chatbot product."
---

> For agents: connect to the Annsa MCP server at https://app.annsa.ai/mcp to give every agent one shared plan. Setup guide: https://annsa.ai/docs/quick-start. Index of every page: https://annsa.ai/llms.txt.

# MCP OAuth: discovery documents and PKCE in 7 steps | Annsa

> How to add OAuth to an MCP server in 7 steps: the two discovery documents, dynamic client registration, PKCE with S256 and fixing Cursor auth errors.

## How can I add OAuth to my MCP server?

Two discovery documents, dynamic client registration, PKCE with S256, and a 401 that points the way. Plus how to fix Cursor MCP OAuth errors.

By Cat Williams-Treloar · Sep 2026 · 7 min read

- [Ask Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fannsa.ai%2Fai-agent-management%2Fhow-to-add-oauth-to-an-mcp-server%20and%20help%20me%20apply%20it%20to%20what%20I'm%20building.)
- [Ask ChatGPT](https://chatgpt.com/?q=Read%20https%3A%2F%2Fannsa.ai%2Fai-agent-management%2Fhow-to-add-oauth-to-an-mcp-server%20and%20help%20me%20apply%20it%20to%20what%20I'm%20building.)

You add OAuth to an MCP server by publishing two discovery documents, accepting dynamic client registration, running authorization code with PKCE and answering unauthenticated requests with a `401` that points at the first document. MCP clients (Claude Code, Cursor, Codex, ChatGPT, Windsurf) all walk the same chain, so once the chain is right, every client connects without a pre-shared secret.

This is the chain as Annsa implements it. It follows the MCP authorization spec, which in turn leans on 3 RFCs: 9728 for protected-resource metadata, 8414 for authorization-server metadata and 7591 for dynamic client registration.

Fig. 01: The discovery chain every MCP client walks

## Step 1: Answer the first request with a pointer

A client's first call arrives with no token. Do not return a bare `401`. Return one whose `WWW-Authenticate` header says where to look:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Annsa MCP",
  resource_metadata="https://app.annsa.ai/.well-known/oauth-protected-resource/mcp"
```

Everything the client needs from here on is derived from that URL. It never has to be told anything else.

## Step 2: Publish protected-resource metadata

At the URL you pointed to, serve RFC 9728 metadata: what the resource is, which authorization server protects it, how to send the token and what scopes exist.

```
{
  "resource": "https://app.annsa.ai/mcp",
  "authorization_servers": ["https://app.annsa.ai"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["read", "write"]
}
```

Clients derive the path from the resource URL, so serve it at both `/.well-known/oauth-protected-resource` and `/.well-known/oauth-protected-resource/mcp`.

## Step 3: Publish authorization-server metadata

At `/.well-known/oauth-authorization-server` on the origin you named, serve RFC 8414 metadata listing every endpoint:

```
{
  "issuer": "https://app.annsa.ai",
  "authorization_endpoint": "https://app.annsa.ai/mcp/auth",
  "token_endpoint": "https://app.annsa.ai/mcp/token",
  "registration_endpoint": "https://app.annsa.ai/mcp/register",
  "revocation_endpoint": "https://app.annsa.ai/mcp/revoke",
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["read", "write"]
}
```

## Step 4: Accept dynamic client registration

A coding agent on someone's laptop has no pre-issued client id. RFC 7591 lets it register itself: a `POST` to the registration endpoint with a name, redirect URIs and grant types, answered with a client id. Keep it open (no human approval, no secret) and use `token_endpoint_auth_method: "none"`, because a public client cannot keep a secret anyway. Your security comes from PKCE, not from a client secret.

## Step 5: Authorization code with PKCE, S256 only

The client sends the user to your authorization endpoint with a `code_challenge`. Require `S256` and refuse `plain`. The user signs in and approves the scopes on your consent screen. You redirect back with a code; the client exchanges it with its verifier at the token endpoint and receives an access token and a refresh token.

Annsa's access tokens last 30 days and refresh tokens 90. Choose your own, but make refresh work: an agent that has to reopen a browser every day will be abandoned.

## Step 6: Scope narrowly

Two scopes are enough for most servers. Annsa's are `read` (priorities, specs, survey results, search) and `write` (state changes: start building, mark shipped, share back). An agent that only answers questions should ask for `read` alone, and your consent screen should show the user exactly which it asked for.

## Step 7: Keep protocol errors out of the auth path

This is the mistake that costs the most time. Once a client is authenticated, a call to a method you do not implement must come back as a JSON-RPC error, code `-32601`, inside a `200`. If it comes back as a `401` (because the method fell through to an anonymous allow-list, say), every client reads it as an expired token and loops on re-authentication. We shipped that once. Auth errors are HTTP statuses; method errors are JSON-RPC bodies. Never mix them.

## How to fix Cursor MCP OAuth authentication errors

When Cursor shows the server as failing to authenticate, it is almost always one of these, in this order:

1. **The `401` has no `resource_metadata` pointer.** Cursor cannot start discovery. Check the header on an unauthenticated `POST`.
2. **Metadata is served on the wrong origin or path.** The protected-resource document must be on the resource's origin; the authorization-server document must be on the issuer named inside it.
3. **PKCE method mismatch.** The server refuses `S256` or only accepts `plain`. Only `S256` should be accepted.
4. **Redirect URI not registered.** Cursor registers its own callback at registration time; a server that ignores the registered URIs and checks a fixed one will refuse the code.
5. **A protocol error returned as `401`.** See step 7. The symptom is a server that authenticates, works for one call, then asks to authenticate again.
6. **A stale token in the client.** Re-authenticate from the client. In Cursor, remove and re-add the server; in Claude Code, `/mcp` → the server → Authenticate.

Annsa's own client-side notes for this are in [common issues](https://annsa.ai/docs/common-issues).

## What Annsa does not implement, on purpose

Two things the auth.md convention allows are not offered: identity assertions from a third-party IdP and anonymous access. Annsa declares neither, so an agent never tries a method that would fail. Publishing what you do not support is part of adding OAuth well. The full agent-facing version of this document is at [annsa.ai/auth.md](https://annsa.ai/auth.md).

## Test the chain from outside

Run these against a fresh deployment:

- Unauthenticated `POST /mcp` → `401` with `resource_metadata` in `WWW-Authenticate`
- `GET` the protected-resource URL → names your authorization server
- `GET` the authorization-server URL → lists all endpoints and `S256`
- `POST` registration with no secret → a client id
- Authorization with `code_challenge_method=plain` → refused
- Valid token, unknown method → `200` with a `-32601` body

If all six pass, Claude Code, Cursor and ChatGPT connect with the URL alone.

**See the chain working.** Annsa's server at `https://app.annsa.ai/mcp` implements every step above. Connect it from Claude Code with `claude mcp add --transport http annsa https://app.annsa.ai/mcp` and watch the browser sign-in happen with no token file.

## Keep reading

- [All posts](https://annsa.ai/blog)
- [Customer feedback tool](https://annsa.ai/feedback)
- [MCP server for product management](https://annsa.ai/ai-agent-management/mcp-server-for-product-management)
- [Does ChatGPT support MCP? Yes, here's how to connect](https://annsa.ai/ai-agent-management/does-chatgpt-support-mcp)

## Connect your agent with one URL.

Begin with one agent and the work that matters next.

- [Try Annsa](https://app.annsa.ai/signup)
- [See pricing](https://annsa.ai/pricing)

## Related pages

> For AI agents: the pages that follow this one on annsa.ai. The full index is https://annsa.ai/llms.txt.

- [Customer feedback tool: who asked, what shipped](https://annsa.ai/feedback)

If anything on this page is wrong or missing, tell us: https://annsa.ai/contact
