Files
upstash__context7/.changeset/lazy-auth-mcp-endpoint.md
T
Fahreddin Özcan 48da517fe5 feat(mcp)!: challenge on connect by default, keep lazy behind a flag
BREAKING CHANGE: an anonymous client on /mcp is now challenged on its first
request, including initialize. Set CONTEXT7_MCP_AUTH_MODE=lazy to restore
the previous behaviour, where anonymous callers connect and spend their free
monthly requests before being asked to sign in.

Testing the lazy gate against real clients showed the challenge lands in the
wrong place. Every client we tried runs OAuth natively at connect time:
Codex starts the flow the moment it discovers the resource metadata, without
sending a single JSON-RPC message; Claude Code exposes its authorize helpers
only for servers already flagged when the session started; Zed handles the
401 during server startup. The same challenge raised mid-conversation is
handled far worse — it fails the turn in progress, and on Claude Code the
recovery path does not appear until the next session, so the user loses
their turn and has to know to run /mcp.

Challenging on connect trades the anonymous trial for a prompt the client
knows how to show. Deployments that would rather keep the trial can set the
flag; the gate, the backend-driven quota mirror and the per-client challenge
shapes all still work in that mode and are unchanged.

The integration suite runs with the flag set, since it exercises anonymous
protocol behaviour. test/auth-mode.test.ts covers the new default against
the built binary: anonymous initialize and tools/list are refused with a
challenge carrying resource_metadata and scope, a credential gets through,
and the discovery document stays public.
2026-08-07 15:35:50 +03:00

1.9 KiB

@upstash/context7-mcp
@upstash/context7-mcp
major

The public /mcp endpoint now asks clients to authenticate when they connect.

This is a breaking change for anonymous users. A client with no credentials that previously connected and called tools now receives a 401 with a WWW-Authenticate challenge on its first request. Set CONTEXT7_MCP_AUTH_MODE=lazy to restore the previous behaviour, with anonymous callers spending their free monthly requests before being challenged.

The default is required because that is when MCP clients actually run OAuth. Codex starts the flow as soon as it discovers the resource metadata, Claude Code exposes its authorize helpers for servers flagged at session start, and Zed raises its prompt on a startup 401. The same challenge raised mid-conversation is handled far worse: it fails the turn in progress, and the recovery path often only appears in the next session. Signing in at connect time means the user gets their client's native prompt instead of a broken request.

In lazy mode the quota trigger defers to the Context7 backend, which already counts anonymous requests per client IP and reports the balance on every response (Context7-Quota-Tier, RateLimit-Remaining). The MCP server mirrors that verdict rather than counting separately, and because the balance is known one request ahead the challenge is issued before the call is proxied — so users get a sign-in prompt instead of a 429, and the refused call costs no quota.

Either mode delivers the challenge in whichever shape the calling client acts on: an HTTP 401 with WWW-Authenticate for spec-compliant clients, or a CallToolResult carrying _meta["mcp/www_authenticate"] for ChatGPT, which does not raise its link-account UI from a bare 401. Tools also advertise securitySchemes in their _meta.

Also removes the anonymous sign-in elicitation, which interrupted the turn to ask the user to run ctx7 setup in a terminal instead of driving the client's own OAuth flow.