Choose your stack, preview the instructions, then copy them into your coding agent. Replace any [bracketed placeholders] with your details.
11 prompts
Add OAuth to a FastMCP server
Point an agent at your FastMCP codebase and have it wire bearer-token auth plus the discovery endpoints MCP clients require.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Add OAuth 2.1 protection to my FastMCP server so it passes claude.ai's custom connector validation.
Requirements:
1. Every /mcp request without a valid bearer token must return 401 with a WWW-Authenticate header of the form: Bearer resource_metadata="https://MY_DOMAIN/.well-known/oauth-protected-resource"
2. Serve GET /.well-known/oauth-protected-resource returning {"resource": "https://MY_DOMAIN/mcp", "authorization_servers": ["https://MY_DOMAIN"]}
3. Serve GET /.well-known/oauth-authorization-server (RFC 8414) advertising authorization_endpoint, token_endpoint, and registration_endpoint on MY_DOMAIN, with code_challenge_methods_supported including "S256" and grant_types_supported including "authorization_code" and "refresh_token".
4. Validate incoming JWTs: signature against my provider's JWKS, plus iss, exp, and aud claims. Reject failures with 401, never 403.
5. Use FastMCP's auth hooks if the installed version has them; otherwise wrap the ASGI app with middleware. Do not fork or vendor the library.
My identity provider is: [Auth0 / other — fill in]. My domain is: [fill in].
Before writing code, read the current FastMCP version in my lockfile and check what auth support it ships. Show me the endpoint map you intend to implement before touching files, then implement it and give me curl commands to verify all four discovery steps.
Put Auth0 in front of an existing MCP endpoint
Build the thin proxy that owns discovery and DCR on your domain while Auth0 handles login and tokens, accepting Claude, ChatGPT, and Grok as clients.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Build a thin OAuth proxy in front of my existing MCP server so claude.ai can connect through Auth0.
Architecture (all endpoints on MY_DOMAIN):
- /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server: static JSON served by the proxy. The authorization-server document advertises MY_DOMAIN's /authorize, /token, /register (not Auth0's) and lists S256 under code_challenge_methods_supported.
- POST /register: implement dynamic client registration locally. Store the exact redirect_uris the client sends, validate their hosts against an allowlist (claude.ai, claude.com, chatgpt.com, openai.com, grok.com, x.ai; known callbacks include https://claude.ai/api/mcp/auth_callback and https://chatgpt.com/connector_platform_oauth_redirect), and return my existing Auth0 application's client_id. At /authorize, validate the requested redirect_uri exactly against what was registered. Do not call Auth0's DCR.
- GET /authorize: 302 to https://MY_TENANT.auth0.com/authorize, forwarding redirect_uri, state, code_challenge, code_challenge_method, scope; substitute the real Auth0 client_id and append audience=https://MY_DOMAIN/mcp.
- POST /token: forward to Auth0's /token server-to-server, injecting the client secret from an environment variable; return Auth0's JSON body unchanged. Support both authorization_code and refresh_token grants.
- /mcp: reverse-proxy to my MCP server, passing the Authorization header through untouched.
The MCP server itself must validate the JWT: JWKS from https://MY_TENANT.auth0.com/.well-known/jwks.json, check iss, exp, and aud == https://MY_DOMAIN/mcp, and answer 401 with WWW-Authenticate: Bearer resource_metadata="https://MY_DOMAIN/.well-known/oauth-protected-resource" when validation fails.
My stack for the proxy is: [fill in, e.g. FastAPI behind Caddy, an Express app, a Cloudflare Worker]. Auth0 tenant: [fill in]. Secrets go in environment variables, never in code.
Implement it, then give me the four curl checks (401 probe, both metadata documents, /register) so I can verify discovery end-to-end before trying the connector UI.
Auth0 as MCP authorization server: native, no proxy
Configure a 2026 Auth0 tenant for MCP connectors entirely through the Management API: DCR flag, resource-parameter audience profile, CIMD, and a JWKS-validating resource server.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Set up my Auth0 tenant as the authorization server for my remote MCP server, using Auth0's native MCP support (no audience proxy). My tenant is MY_TENANT.us.auth0.com and my MCP endpoint is https://MY_DOMAIN/mcp [fill in both].
Tenant configuration: do all of it via the Management API (get a token from Dashboard → APIs → Auth0 Management API → API Explorer), and show me each curl:
1. PATCH /api/v2/tenants/settings with {"flags":{"enable_dynamic_client_registration":true}} so clients can self-register at /oidc/register (RFC 7591-compatible OIDC DCR).
2. PATCH resource_parameter_profile to "audience" so the RFC 8707 resource parameter MCP clients send is mapped to the audience, and set client_id_metadata_document_supported to true for ChatGPT's CIMD. Re-read the settings afterwards: patching one field can silently reset resource_parameter_profile.
3. POST /api/v2/resource-servers to create an API whose identifier is EXACTLY https://MY_DOMAIN/mcp with signing_alg RS256 and allow_offline_access true.
4. PATCH my database connection with {"is_domain_connection":true}; DCR clients are third-party and can only use domain-level connections.
Resource server behaviour (write or adapt my MCP server):
- Serve /.well-known/oauth-protected-resource with resource = https://MY_DOMAIN/mcp and authorization_servers = ["https://MY_TENANT.us.auth0.com"] (origin, no trailing slash).
- Reject unauthenticated requests with 401 (never 403) and WWW-Authenticate: Bearer realm plus resource_metadata pointing at that document.
- Validate Bearer JWTs offline against https://MY_TENANT.us.auth0.com/.well-known/jwks.json (RS256): check exp, aud = https://MY_DOMAIN/mcp, and iss = "https://MY_TENANT.us.auth0.com/"; Auth0's iss claim has a TRAILING SLASH; compare against exactly that.
Verification before I touch Claude (run these, show output):
- curl https://MY_TENANT.us.auth0.com/.well-known/oauth-authorization-server → issuer, registration_endpoint (/oidc/register), S256 all present.
- POST /oidc/register with redirect_uris ["https://claude.ai/api/mcp/auth_callback"], token_endpoint_auth_method "none", grant_types ["authorization_code","refresh_token"] → expect 201, a tpc_ client_id, and no null/"" values in the response. Note: a client_secret is returned even for public clients (unused), and there is NO RFC 7592 management URI, so probe clients must be deleted via the Management API.
- Headless end-to-end: enable the password grant on a test M2M client, set tenant default_directory to the database connection, create a test user, then POST /oauth/token with grant_type=password and audience=https://MY_DOMAIN/mcp; call the MCP endpoint with the resulting token and confirm a garbage token gets 401.
Never print secrets to logs; read them from environment variables.
Diagnose a failing claude.ai connector
A systematic run of the seven checks that isolate where an MCP OAuth connection breaks.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
My remote MCP server at https://MY_DOMAIN/mcp shows "unable to connect" when I add it as a claude.ai custom connector. Run these checks in order, report the result of each, and stop at the first failure with a diagnosis and fix:
1. DNS: dig +short MY_DOMAIN @1.1.1.1, which must return a public A/AAAA/CNAME answer. (Tailscale *.ts.net names and LAN-only DNS fail claude.ai's pre-flight even if the URL works in my browser.)
2. TLS: curl -sI https://MY_DOMAIN/mcp without -k; the certificate must chain to a public CA.
3. Unauthenticated probe: curl -si -X POST https://MY_DOMAIN/mcp | head -5; expect 401 with a WWW-Authenticate header naming the resource metadata URL (or a valid MCP response if the server is intentionally no-auth). 403, 404, redirects, or HTML mean discovery dies here.
4. Protected resource metadata: curl https://MY_DOMAIN/.well-known/oauth-protected-resource; the "resource" field must exactly equal https://MY_DOMAIN/mcp or its origin https://MY_DOMAIN (a prefix of the MCP URL); anything else fails.
5. Authorization server metadata: curl https://MY_DOMAIN/.well-known/oauth-authorization-server; must include authorization_endpoint, token_endpoint, registration_endpoint, and "S256" in code_challenge_methods_supported.
6. DCR: POST to the registration_endpoint with {"redirect_uris":["https://claude.ai/api/mcp/auth_callback"],"client_name":"probe"}; expect a JSON body containing client_id.
7. Handshake: POST an MCP initialize request to /mcp with accept: application/json, text/event-stream (use a token from the flow if auth is on); expect an initialize result, JSON or SSE-framed.
For whichever step fails, explain what claude.ai's discovery expected at that step, what my server returned instead, and the minimal change to fix it. If all seven pass, run npx @modelcontextprotocol/inspector against the endpoint and compare its handshake log with the checks above.
Stytch Connected Apps in front of an MCP server
Enable Connected Apps, dodge the project-domain metadata trap, host the authorization page, and validate Stytch JWTs — with M2M-based headless tests.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Put Stytch Connected Apps in front of my remote MCP server at https://MY_DOMAIN/mcp [fill in]. My Stytch project ID is MY_PROJECT_ID and my project domain is MY_SUBDOMAIN.customers.stytch.dev [find both under Project Settings → API keys].
Dashboard settings (Connected Apps → Settings):
- Enable dynamic client registration (this is how Claude/ChatGPT/Grok self-register) and enable CIMD for ChatGPT.
- Set the Authorization URL to https://MY_DOMAIN/authorize — a page MY app hosts where the Stytch IdentityProvider UI component (frontend SDK, public token) renders login and consent. Stytch has no hosted consent page; that page is mine to serve.
CRITICAL metadata fact — use the PROJECT DOMAIN, not the legacy API path:
- https://MY_SUBDOMAIN.customers.stytch.dev/.well-known/oauth-authorization-server exists, has a URL issuer, includes registration_endpoint (/v1/oauth2/register) and refresh_token support.
- https://test.stytch.com/v1/public/MY_PROJECT_ID/... serves ONLY openid-configuration, with a non-URL issuer (stytch.com/project-…) and NO registration_endpoint even when DCR is enabled. An MCP client pointed there will fail to register.
So my protected-resource metadata must list authorization_servers = ["https://MY_SUBDOMAIN.customers.stytch.dev"].
Resource server behaviour:
- /.well-known/oauth-protected-resource with resource = https://MY_DOMAIN/mcp and the project domain as the authorization server.
- 401 + WWW-Authenticate Bearer challenge with resource_metadata on every unauthenticated request; 401 for invalid tokens.
- Validate JWTs against https://MY_SUBDOMAIN.customers.stytch.dev/.well-known/jwks.json (RS256). Accept iss as EITHER the project domain OR the legacy "stytch.com/project-…" string (tokens carry the legacy form). Do NOT require aud to equal the MCP URL — Stytch sets aud to the project/client ID; authorize via scopes instead.
Verification (run and show output):
- POST https://MY_SUBDOMAIN.customers.stytch.dev/v1/oauth2/register with redirect_uris ["https://claude.ai/api/mcp/auth_callback"] and token_endpoint_auth_method "none" → 201, connected-app-… client_id, no null fields.
- Headless token proof: create an M2M client (POST https://test.stytch.com/v1/m2m/clients with basic auth project_id:secret), exchange client_credentials at /v1/public/MY_PROJECT_ID/oauth2/token, and call my MCP endpoint with the resulting JWT; confirm a garbage token gets 401.
Keep the project secret in environment variables and out of every log line.
OAuth for an MCP server on Cloudflare Workers
Wrap a Workers-hosted MCP server with workers-oauth-provider so discovery, DCR, and tokens all live on the Worker.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
My MCP server runs on Cloudflare Workers (stateless, createMcpHandler from the agents SDK). Add OAuth so claude.ai can connect as a custom connector.
Use Cloudflare's workers-oauth-provider library as the outer handler: it should own /.well-known/oauth-authorization-server, /authorize, /token, and /register (dynamic client registration), persisting clients and grants in a KV namespace, and pass authenticated requests through to my existing MCP handler on /mcp.
Requirements:
1. Check the currently published version of workers-oauth-provider and the agents SDK before writing code; follow their current integration pattern, not memory.
2. Also serve /.well-known/oauth-protected-resource returning {"resource": "https://MY_DOMAIN/mcp", "authorization_servers": ["https://MY_DOMAIN"]} — add it explicitly if the library version doesn't emit it.
3. Unauthenticated /mcp requests must get 401 with WWW-Authenticate: Bearer resource_metadata="https://MY_DOMAIN/.well-known/oauth-protected-resource".
4. For the /authorize consent screen, render a minimal approval page; the identity behind it is [fill in: GitHub OAuth upstream / Google / my own user table].
5. Update wrangler config with the KV binding and any required compatibility flags; keep static assets serving exactly as it is now.
6. PKCE S256 must be enforced on the code exchange.
My domain: [fill in]. After implementing, give me the curl sequence to verify the 401 probe, both metadata documents, and /register, plus an npx @modelcontextprotocol/inspector command for the full authorized handshake.
Serve an MCP endpoint behind Caddy with auto-TLS
Route /mcp and the OAuth discovery endpoints through Caddy with a Let's Encrypt certificate that renews itself.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Set up Caddy on my server to publish my MCP server at https://MY_DOMAIN with automatic Let's Encrypt TLS, so it passes claude.ai's DNS and certificate pre-flight.
Context: my MCP server listens on localhost:[PORT]. OAuth endpoints (metadata, /authorize, /token, /register) are served by [same process / a separate proxy process on localhost:PORT2 / not yet built].
Do the following:
1. Verify prerequisites and tell me if any fail: MY_DOMAIN has a public A/AAAA record pointing at this machine (dig +short MY_DOMAIN @1.1.1.1), and ports 80+443 are reachable from the internet (Let's Encrypt HTTP/TLS-ALPN challenges need them — check firewall and cloud security-group rules, and list the exact rules to add if blocked).
2. Write a Caddyfile that routes /mcp* to the MCP process and /.well-known/*, /authorize*, /token*, /register* to the OAuth process, with no route falling through to a default page that could shadow discovery paths.
3. Preserve the Authorization header and streaming responses (Streamable HTTP uses SSE frames — no response buffering).
4. Install/enable Caddy as a service on my OS so it survives reboots, using the distro package where available.
5. Verify: curl -sI https://MY_DOMAIN/mcp from outside shows a Let's Encrypt-chained certificate, and an unauthenticated POST to /mcp returns the MCP server's own response (401 with WWW-Authenticate if auth is on), not a Caddy error page.
Important: do not use a Tailscale Funnel or other *.ts.net hostname anywhere — claude.ai's connector pre-flight rejects hostnames that don't resolve on public DNS, before any HTTP request is made.
Supabase Auth OAuth Server for an MCP connector
Enable Supabase's OAuth Server beta, force the DCR toggle through the Management API, and validate the ES256 tokens in your MCP server, plus a fully headless CI proof.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Configure my Supabase project (ref MY_REF [fill in]) as the OAuth 2.1 authorization server for my remote MCP server at https://MY_DOMAIN/mcp, using the OAuth Server beta in Supabase Auth.
Enablement:
- Dashboard: Authentication → Configuration → OAuth Server (BETA) → enable. Set the Authorization Path to /authorize and the Site URL (Auth → URL Configuration) to https://MY_DOMAIN; Supabase will redirect end users to https://MY_DOMAIN/authorize, where my app renders login + consent with supabase-js and approves the authorization.
- Dynamic client registration: the dashboard's "Allow Dynamic OAuth Apps" toggle may fail to persist (beta bug). Set it through the Management API instead, with a personal access token from Account → Access Tokens:
PATCH https://api.supabase.com/v1/projects/MY_REF/config/auth with {"oauth_server_allow_dynamic_registration": true}, then GET the config back and confirm it stuck.
Discovery facts my metadata must respect:
- The issuer is https://MY_REF.supabase.co/auth/v1, under /auth/v1, NOT the project root. authorization_servers in my protected-resource metadata must be exactly that.
- Both /auth/v1/.well-known/openid-configuration and /auth/v1/.well-known/oauth-authorization-server are served; registration_endpoint is /auth/v1/oauth/clients/register; S256 is supported.
Resource server behaviour (write or adapt my MCP server):
- /.well-known/oauth-protected-resource with resource = https://MY_DOMAIN/mcp and the /auth/v1 issuer as authorization server.
- 401 + WWW-Authenticate Bearer challenge (resource_metadata pointer) when unauthenticated; 401 on invalid tokens.
- Validate JWTs against https://MY_REF.supabase.co/auth/v1/.well-known/jwks.json. Supabase signs with ES256 (P-256), so my verifier must import EC keys, not just RSA. Check iss and exp; do NOT require aud to equal the MCP URL (Supabase sets aud to "authenticated").
Verification (run and show output):
- POST /auth/v1/oauth/clients/register with redirect_uris ["https://claude.ai/api/mcp/auth_callback"], token_endpoint_auth_method "none", grant_types ["authorization_code","refresh_token"] → 201 with a uuid client_id and "registration_type": "dynamic"; no null fields. Registered apps show under Authentication → OAuth Apps; there is no RFC 7592 management URI, so clean probes up in the dashboard.
- Headless CI proof without any browser: PATCH mailer_autoconfirm true via the Management API, create a test user, POST /auth/v1/token?grant_type=password with the anon key → the returned access_token is the same ES256 JWT the OAuth server issues; call my MCP endpoint with it and confirm a garbage token gets 401.
Secrets (access token, service_role, anon key) live in environment variables only, never in code or logs.
Make my MCP server work as a ChatGPT connector
Wire the redirect URI, CIMD/DCR client identification, and discovery endpoints ChatGPT expects from a custom MCP connector.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
My remote MCP server at https://MY_DOMAIN/mcp already passes claude.ai connector validation. Make it also work as a ChatGPT custom connector (Settings → Connectors, developer mode).
Adjustments to implement, in order:
1. Redirect URIs: add https://chatgpt.com/connector_platform_oauth_redirect to the allowed redirect URIs in my OAuth layer. Better: refactor my dynamic client registration endpoint to store the exact redirect_uris each client registers and validate /authorize requests against what that client registered, with a host allowlist of claude.ai, claude.com, chatgpt.com, openai.com, grok.com, x.ai. Match the full derived redirect_uri exactly, not just the base host.
2. Client identification: ChatGPT prefers Client ID Metadata Documents (CIMD) — the client_id is a URL my authorization server fetches for client metadata. Check whether my OAuth stack supports CIMD; if yes, enable it. If not, ensure my registration_endpoint (RFC 7591 DCR) works, since ChatGPT falls back to DCR and registers once per MCP server connection, reusing the client afterwards.
3. Keep the discovery endpoints exactly as they are (/.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server with S256 PKCE) — ChatGPT probes the same documents.
4. Log every request during the first ChatGPT connection attempt (discovery, /register body, /authorize params, /token exchange) so we have the authoritative record of what it sends.
My OAuth layer is: [fill in — e.g. the Auth0 thin proxy from quickoauth.com/guides/auth0-proxy-oauth, workers-oauth-provider, my own implementation].
After implementing, give me the curl checks for the discovery endpoints plus a DCR registration using ChatGPT's redirect URI, and tell me exactly where in ChatGPT's UI to add the connector.
Add my MCP server to Grok
Prepare an OAuth-protected MCP server for Grok's Bring Your Own MCP connectors, without hardcoding callback URLs that may change.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
My remote MCP server at https://MY_DOMAIN/mcp uses OAuth 2.1 (discovery metadata, DCR, PKCE). Prepare it for Grok's custom connectors (grok.com/connectors → New Connector → Custom, which takes the MCP server URL and runs the OAuth flow inside Grok).
Do the following:
1. Refactor my dynamic client registration endpoint to the allow-what-registers pattern: store the exact redirect_uris each registering client sends, validate their hosts against an allowlist (claude.ai, claude.com, chatgpt.com, openai.com, grok.com, x.ai), and at /authorize validate the requested redirect_uri exactly against what that client_id registered. Do NOT hardcode a Grok callback URL from any blog post or forum — capture what Grok actually sends.
2. Add temporary structured logging on /register, /authorize, and /token that records (with secrets redacted) the request bodies and params, so the first Grok connection documents itself. Tell me where the log lines will appear.
3. Confirm the basics Grok's pre-flight shares with the other clients: the hostname resolves on public DNS (no tunnel-only names), the certificate chains to a public CA, the unauthenticated /mcp probe returns 401 with the WWW-Authenticate resource_metadata header, and both /.well-known documents are served with S256 in code_challenge_methods_supported.
4. Verify the MCP handshake over Streamable HTTP works with an Authorization header (accept: application/json, text/event-stream).
My OAuth layer is: [fill in]. After implementing, give me the four curl checks plus a test DCR registration, then walk me through adding the connector at grok.com/connectors and what to check in the logs if the flow stalls.
Self-host the OAuth server with Ory Hydra
Self-host MCP OAuth with Hydra, DCR, offline JWT validation, and rotating refresh tokens valid for 365 days.
Preview prompt
After copying, replace any [bracketed placeholders] with your details.
Set up a self-hosted OAuth 2.1 authorization server for my MCP server using Ory Hydra, so Claude, ChatGPT, and Grok connectors can register and authenticate without any external identity provider.
Target layout: Hydra serves https://auth.MY_DOMAIN (public API on 127.0.0.1:4444, admin API on 127.0.0.1:4445 — the admin port must NEVER be proxied publicly); my MCP server stays at https://mcp.MY_DOMAIN/mcp; my app at https://MY_APP_DOMAIN provides the login and consent pages. TLS terminates at Caddy.
Implement, in order:
1. Install Hydra (current stable) with a hydra.yml configured for: issuer https://auth.MY_DOMAIN; urls.login and urls.consent pointing at my app's /oauth/login and /oauth/consent; oidc.dynamic_client_registration.enabled: true with default_scope including my resource scopes and offline_access; strategies.access_token: jwt; oauth2.pkce.enforced_for_public_clients: true; ttl.access_token: 1h; ttl.refresh_token: 8760h (365 days); ttl.auth_code: 10m. Keep refresh-token rotation enabled. default_scope supplies defaults when registration omits scopes; enforce permissions in consent. DSN and system secrets come from environment variables (Hydra does not interpolate ${...} in YAML). SQLite on local disk is fine for a single node.
2. Build the DCR scrub proxy: a tiny HTTP service on 127.0.0.1:4480 that forwards /oauth2/register* to Hydra and recursively drops response keys whose value is null or an empty string (keep empty lists/dicts — they are valid). This is required: Hydra's registration response includes null lifespan fields and empty-string URI fields, and Claude's connector rejects the response against a strict schema before the flow starts. Use a general drop-empty-scalars rule, not a field-name list.
3. Caddy: auth.MY_DOMAIN routes /oauth2/register* to the scrub proxy and everything else to 127.0.0.1:4444; mcp.MY_DOMAIN routes to my MCP server. No route may fall through to a default page on /.well-known paths.
4. Login + consent in my app: handle Hydra's login_challenge and consent_challenge via the admin API on 127.0.0.1:4445 (accept login after authenticating the user; accept consent with the granted scopes). Preserve requested offline_access in grant_scope for EVERY consent option, including per-resource, account-only, and all-resource grants. Filter resource permissions separately so this scope cannot widen access. Do not grant offline_access if it was not requested. Clients must register grant_types containing authorization_code and refresh_token, request offline_access during authorization, and persist each rotated refresh token. Browser session duration is a separate setting; extending it does not extend MCP tokens. My app's stack is: [fill in].
5. MCP server auth: validate bearer JWTs offline against https://auth.MY_DOMAIN/.well-known/jwks.json (signature, iss, exp, audience) and answer unauthenticated /mcp requests with 401 and WWW-Authenticate: Bearer resource_metadata="https://mcp.MY_DOMAIN/.well-known/oauth-protected-resource". Serve that protected-resource document with authorization_servers: ["https://auth.MY_DOMAIN"]. Ensure the intended MCP audience is allowed in the client's registered audience before adding it to grant_access_token_audience: Hydra rejects refresh requests with an unregistered audience, even when the initial code exchange succeeds. Configure registration accordingly and test both exchanges.
6. Run Hydra, the scrub proxy, and my MCP server as systemd services (or my platform's equivalent) so they survive reboots.
Then verify: curl Hydra's /.well-known/oauth-authorization-server and jwks.json through Caddy; POST a test registration to /oauth2/register and confirm the response contains no null or "" values; run the 401 probe and protected-resource check against mcp.MY_DOMAIN; finally add the connector in claude.ai and confirm the login page is mine, not a third party's. Complete a real authorization-code exchange and assert the response includes a refresh_token and expires_in close to 3600 seconds. Exchange that token with grant_type=refresh_token without a browser login, confirm rotation, save the replacement, and use the new access token for an authenticated MCP call. Test that consent preserves offline_access only when requested and still enforces resource ownership and scopes.
For an existing deployment, install the updated Hydra config and restart Hydra as well as the consent app. Check TTL_REFRESH_TOKEN and per-client authorization_code_grant_refresh_token_lifespan / refresh_token_grant_refresh_token_lifespan overrides. Reconnect once to obtain the new grant; update or recreate clients missing refresh_token grant support. Existing tokens are not retroactively extended. Report any connector that does not request offline_access or automatically refresh: it cannot stay connected for a year just because the server allows it. Keep access tokens at one hour; long-term access comes from renewable refresh tokens.