Follow Discord
Sweep 22 Oct 2026 · 21:04Z Build v2.1.309 528 read Stable v2.1.308 Latest v2.1.309 Next v2.1.309 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-docs

Lazy authentication for MCP servers changedconnectors/building/lazy-authentication

Nearest release: v2.1.283, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 25 Sep 2026 18:00 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+73added
Lines−75removed
From line 17 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits4to this page, all time

The whole hunk

from line 17, old and new numbered
/
lines
from line 17
17173. They click **Connect** and sign in to your service in a popup.
18184. Claude retries the same tool call automatically with the new token, and the turn continues with no context lost.
1919 
20<Note>
21 If the user's organization has [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth) configured for your connector, the same refusal triggers a silent token exchange instead of the **Connect** card. Claude retries the tool call automatically and the user sees no prompt.
22</Note>
20> [!NOTE]
21>
22> If the user's organization has [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth) configured for your connector, the same refusal triggers a silent token exchange instead of the **Connect** card. Claude retries the tool call automatically and the user sees no prompt.
2323 
2424## Build lazy authentication on your server
2525 
from line 136
136136{"jsonrpc":"2.0","result":{"isError":true,"content":[{"type":"text","text":"Please sign in"}]},"id":1}
137137```
138138 
139<Warning>
140 A `200` with `isError: true` is an application-level tool failure. Claude passes the error text to the model as the tool result and moves on, and there is no auth prompt. Only a transport-level `401` causes Claude to pause the call, run the OAuth flow, and retry. A `403` triggers re-authentication only when accompanied by `WWW-Authenticate: Bearer error="insufficient_scope"` for [scope step-up](#ask-for-more-scope-with-403), and any other `403` is surfaced as a terminal error. If users are seeing "please sign in" text in the chat instead of a **Connect** button, the server is returning the wrong one.
141</Warning>
139> [!WARNING]
140>
141> A `200` with `isError: true` is an application-level tool failure. Claude passes the error text to the model as the tool result and moves on, and there is no auth prompt. Only a transport-level `401` causes Claude to pause the call, run the OAuth flow, and retry. A `403` triggers re-authentication only when accompanied by `WWW-Authenticate: Bearer error="insufficient_scope"` for [scope step-up](#ask-for-more-scope-with-403), and any other `403` is surfaced as a terminal error. If users are seeing "please sign in" text in the chat instead of a **Connect** button, the server is returning the wrong one.
142142 
143143### Serve the discovery documents
144144 
from line 203
203203}
204204```
205205 
206<Note>
207 Claude selects CIMD only when the authorization-server metadata advertises both `client_id_metadata_document_supported: true` and `"none"` in `token_endpoint_auth_methods_supported`. The second is required because Claude's CIMD client authenticates as a public client with `token_endpoint_auth_method: "none"`, so the token endpoint must accept [PKCE](https://datatracker.ietf.org/doc/html/rfc7636)-only requests without a client secret. If either property is missing, Claude falls back to looking for a `registration_endpoint`.
208</Note>
206> [!NOTE]
207>
208> Claude selects CIMD only when the authorization-server metadata advertises both `client_id_metadata_document_supported: true` and `"none"` in `token_endpoint_auth_methods_supported`. The second is required because Claude's CIMD client authenticates as a public client with `token_endpoint_auth_method: "none"`, so the token endpoint must accept [PKCE](https://datatracker.ietf.org/doc/html/rfc7636)-only requests without a client secret. If either property is missing, Claude falls back to looking for a `registration_endpoint`.
209209 
210210If you move to a real issuer, keep `client_id_metadata_document_supported: true` in that issuer's metadata if you want registration-free onboarding for Claude clients.
211211 
from line 246
246246 
247247You can confirm the public and protected paths with `curl` and Claude Code against your own server before connecting it to Claude as a custom connector, then check the sign-in prompt in a conversation. The commands below use `http://localhost:3000/mcp` as the server URL and the example's tool names; substitute your own.
248248 
249<Steps>
250 <Step title="Call a public tool without a token">
251 In a terminal, send a `tools/call` for a public tool with no `Authorization` header, with `-i` so the status line prints:
249**1. Call a public tool without a token**
252250 
253 ```bash theme={null}
254 curl -si http://localhost:3000/mcp \
255 -H 'Content-Type: application/json' \
256 -H 'Accept: application/json, text/event-stream' \
257 -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_products","arguments":{}}}'
258 ```
251In a terminal, send a `tools/call` for a public tool with no `Authorization` header, with `-i` so the status line prints:
259252 
260 The server answers `200 OK` with the JSON-RPC tool result and no `WWW-Authenticate` header.
261 </Step>
253```bash theme={null}
254curl -si http://localhost:3000/mcp \
255 -H 'Content-Type: application/json' \
256 -H 'Accept: application/json, text/event-stream' \
257 -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_products","arguments":{}}}'
258```
262259 
263 <Step title="Call a protected tool without a token">
264 Send the same request for a protected tool:
260The server answers `200 OK` with the JSON-RPC tool result and no `WWW-Authenticate` header.
265261 
266 ```bash theme={null}
267 curl -si http://localhost:3000/mcp \
268 -H 'Content-Type: application/json' \
269 -H 'Accept: application/json, text/event-stream' \
270 -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_my_orders","arguments":{}}}'
271 ```
262**2. Call a protected tool without a token**
272263 
273 The server answers `401 Unauthorized` with a `WWW-Authenticate` header carrying `resource_metadata`. If you see `200` with `isError: true` instead, your server is returning a tool error rather than failing the HTTP request.
274 </Step>
264Send the same request for a protected tool:
275265 
276 <Step title="Check both paths from Claude Code">
277 If you have [Claude Code](https://code.claude.com/docs/en/setup) installed and signed in, you can see how a Claude client treats each path before you set up a tunnel, because Claude Code connects to a `localhost` server directly. In your terminal, from your server's project folder, add the server:
266```bash theme={null}
267curl -si http://localhost:3000/mcp \
268 -H 'Content-Type: application/json' \
269 -H 'Accept: application/json, text/event-stream' \
270 -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_my_orders","arguments":{}}}'
271```
278272 
279 ```bash theme={null}
280 claude mcp add --transport http authtest http://localhost:3000/mcp
281 ```
273The server answers `401 Unauthorized` with a `WWW-Authenticate` header carrying `resource_metadata`. If you see `200` with `isError: true` instead, your server is returning a tool error rather than failing the HTTP request.
282274 
283 Then check the connection:
275**3. Check both paths from Claude Code**
284276 
285 ```bash theme={null}
286 claude mcp list
287 ```
277If you have [Claude Code](https://code.claude.com/docs/en/setup) installed and signed in, you can see how a Claude client treats each path before you set up a tunnel, because Claude Code connects to a `localhost` server directly. In your terminal, from your server's project folder, add the server:
288278 
289 The list shows `authtest: http://localhost:3000/mcp (HTTP) - ✔ Connected` with no sign-in, because connecting and listing tools never reach the gate. Next, have Claude call the public tool with [`claude -p`](https://code.claude.com/docs/en/headless), which sends one prompt and prints the answer:
279```bash theme={null}
280claude mcp add --transport http authtest http://localhost:3000/mcp
281```
290282 
291 ```bash theme={null}
292 claude -p "List the products from the authtest server." --allowedTools mcp__authtest__list_products
293 ```
283Then check the connection:
294284 
295 Claude reports the products and no sign-in prompt appears. Run the same command asking for your orders with `--allowedTools mcp__authtest__get_my_orders`. The tool result Claude receives is `MCP server "authtest" needs you to sign in again (run /mcp to re-authenticate)` rather than order data.
285```bash theme={null}
286claude mcp list
287```
296288 
297 To sign in from Claude Code, run `/mcp` in an interactive session as [Authenticate with remote MCP servers](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) describes. When you're done testing, run `claude mcp remove authtest`.
298 </Step>
289The list shows `authtest: http://localhost:3000/mcp (HTTP) - ✔ Connected` with no sign-in, because connecting and listing tools never reach the gate. Next, have Claude call the public tool with [`claude -p`](https://code.claude.com/docs/en/headless), which sends one prompt and prints the answer:
299290 
300 <Step title="Add the server to Claude as a custom connector">
301 Claude reaches custom connectors from Anthropic's infrastructure, so `localhost` isn't reachable directly. To add a local server:
291```bash theme={null}
292claude -p "List the products from the authtest server." --allowedTools mcp__authtest__list_products
293```
302294 
303 1. Expose the server over a public HTTPS tunnel, such as `cloudflared tunnel --url http://localhost:3000` or `ngrok http 3000`. Keep the tunnel up only while you test, because it exposes your local server publicly and the example's `/authorize`, `/token`, and `isTokenValid()` are stubs that treat anyone as signed in.
304 2. In Claude, go to [**Customize > Connectors**](https://claude.ai/customize/connectors) and select **Add custom connector**.
305 3. Enter the tunnel's `/mcp` URL.
295Claude reports the products and no sign-in prompt appears. Run the same command asking for your orders with `--allowedTools mcp__authtest__get_my_orders`. The tool result Claude receives is `MCP server "authtest" needs you to sign in again (run /mcp to re-authenticate)` rather than order data.
306296 
307 See [Test a local server](/docs/connectors/building/testing#test-a-local-server) for details. If your app comes from the SDK's `createMcpExpressApp()`, that section also shows the `allowedHosts` option you need before requests through the tunnel succeed.
308 </Step>
297To sign in from Claude Code, run `/mcp` in an interactive session as [Authenticate with remote MCP servers](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) describes. When you're done testing, run `claude mcp remove authtest`.
309298 
310 <Step title="Try both paths in a conversation">
311 1. Ask Claude to list products. No sign-in prompt appears.
312 2. Ask for your orders. The inline **Connect** card appears, and after you authenticate the same call completes.
313 </Step>
314</Steps>
299**4. Add the server to Claude as a custom connector**
300 
301Claude reaches custom connectors from Anthropic's infrastructure, so `localhost` isn't reachable directly. To add a local server:
302 
3031. Expose the server over a public HTTPS tunnel, such as `cloudflared tunnel --url http://localhost:3000` or `ngrok http 3000`. Keep the tunnel up only while you test, because it exposes your local server publicly and the example's `/authorize`, `/token`, and `isTokenValid()` are stubs that treat anyone as signed in.
3042. In Claude, go to [**Customize > Connectors**](https://claude.ai/customize/connectors) and select **Add custom connector**.
3053. Enter the tunnel's `/mcp` URL.
306 
307See [Test a local server](/docs/connectors/building/testing#test-a-local-server) for details. If your app comes from the SDK's `createMcpExpressApp()`, that section also shows the `allowedHosts` option you need before requests through the tunnel succeed.
308 
309**5. Try both paths in a conversation**
310 
3111. Ask Claude to list products. No sign-in prompt appears.
3122. Ask for your orders. The inline **Connect** card appears, and after you authenticate the same call completes.
315313 
316314## Next steps
317315 
Feedback