CTX7-1764: lazy authentication on /mcp - #2958
Closed
fahreddinozcan wants to merge 2 commits into
Closed
Conversation
Adds a lazy-auth gate to /mcp so anonymous clients can connect, list tools, and call public tools without credentials, but receive an HTTP 401 + WWW-Authenticate challenge when they hit a protected tool or exhaust the anonymous call quota. - New `src/lib/auth/lazy-auth.ts`: evaluates auth state before the transport streams a 200. Gates only `tools/call`; initialize and tools/list always pass. Two configurable triggers: PROTECTED_TOOLS (by name, via CONTEXT7_PROTECTED_TOOLS env) and per-client anonymous quota (CONTEXT7_ANON_FREE_CALLS, default 5, Redis-backed, fail-open). - `src/index.ts`: handleMcpRequest takes mode "lazy"|"required"; /mcp → lazy, /mcp/oauth → required (unchanged eager behavior). buildWwwAuthenticate emits RFC 6750 header pointing at the PRM doc. - `test/lazy-auth.test.ts`: 11 unit tests covering pass-through, authenticated bypass, protected-tool gate, quota allowance + overflow, per-client isolation, quota disable (=0), and header format. - `scripts/repro-quota.mjs`: end-to-end repro script. - `scripts/docker-compose.redis.yml`: local Redis for development.
Lazy auth only helps if the calling client recognises the challenge, and the two client families disagree on what one looks like. The gate now picks the shape per caller: - HTTP 401 + `WWW-Authenticate` for spec-compliant clients (Claude, VS Code, Cursor, Cline, Zed). Unchanged, and still the default for any client we don't recognise. - HTTP 200 wrapping a failed `CallToolResult` with the same challenge in `_meta["mcp/www_authenticate"]` for ChatGPT and Codex, which do not raise their link-account UI from a bare 401. Selected by User-Agent, overridable via `CONTEXT7_TOOL_RESULT_CHALLENGE_CLIENTS`. Both carry `error_description`, which the OpenAI clients use to tell an auth failure from a tool bug. Tools now also advertise `securitySchemes` in `tools/list` (`noauth` + `oauth2` for public tools, `oauth2` alone for protected ones) so those clients know a tool is callable before an account is linked. The SDK's `registerTool` drops config keys it doesn't know, so that field is injected by wrapping the handler it installs — `test/tool-security.test.ts` asserts it on the wire so an SDK change fails the build rather than silently reaching clients. Discovery: serve the RFC 9728 path-suffixed metadata variants (`/.well-known/oauth-protected-resource/mcp`) that clients try first for an endpoint with a path, and add `resource_documentation`. Also removes the anonymous sign-in elicitation. Lazy auth drives the client's own OAuth flow, so nudging the user to run `ctx7 setup` in a terminal is a second, worse path to the same place: it interrupts the turn, needs the client to advertise `elicitation`, and leaves the caller anonymous either way. Drops `auth-prompt.ts`, the `X-Context7-Auth-Prompt` reader in api.ts and the `shouldPrompt` flag it set; the backend half is removed in upstash/context7app. `scripts/lazy-auth-probe.mjs` (was repro-quota.mjs) drives both families over raw HTTP and prints the status, header and `_meta` each one sees.
Collaborator
Author
|
Moved to CTX7-1886: #2959 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Anonymous clients keep working; the server issues an OAuth challenge only when an unauthenticated caller hits a protected tool or spends its anonymous allowance. Needs upstash/context7app#821 (removes the sign-in prompt header this replaces).
tools/callat the HTTP layer, before the transport streams a 200 —initializeandtools/listalways passCONTEXT7_PROTECTED_TOOLS(by name) and a per-client Redis quota,CONTEXT7_ANON_FREE_CALLS, default 5, fail-openWWW-Authenticateby default,_meta["mcp/www_authenticate"]on a failedCallToolResultfor ChatGPT/Codex, which ignore a bare 401securitySchemesintools/list; injected by wrapping the SDK handler, sinceregisterTooldrops unknown config keysresource_documentationX-Context7-Auth-PromptreaderTesting
scripts/lazy-auth-probe.mjsdrives both client families over raw HTTP and prints the status, header and_metaeach one sees:Ran against a local server: spec client gets
401+Bearer error="invalid_token", error_description=…, resource_metadata=…, scope="profile email"on call 4; Codex UA gets200carrying the same challenge in_meta; both PRM paths resolve. 71 unit tests pass.