-
Notifications
You must be signed in to change notification settings - Fork 61
feat: add Anthropic Claude Max/Pro OAuth support #2345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Allow users to authenticate with their Claude Max/Pro subscription instead of paying per-token via API key. Uses Anthropic's OAuth code-paste flow (PKCE + S256). The implementation mirrors the existing Codex (OpenAI ChatGPT) OAuth architecture: - New constants file with OAuth endpoints, client ID, URL builders - AnthropicOauthService: startFlow, submitCode, token refresh with mutex, auto-disconnect on invalid_grant - Provider model factory: OAuth routing decision, Bearer token injection, beta headers, tool name mcp_ prefixing/stripping - Settings UI: Connect button, code paste input, disconnect - Provider config: anthropicOauthSet flag, isConfigured override - IPC schema + router for the OAuth flow - Costs marked as included when using subscription billing Co-Authored-By: Claude Opus 4.6 <[email protected]>
Anthropic's built-in tools (web_search, code_execution, text_editor) have versioned types like "web_search_20250305" and their names must remain unchanged. Only prefix custom tools (type "custom" or no type) with mcp_ when routing through OAuth. Previously all tools were prefixed, causing the API to reject requests with: "tools.16.web_search_20250305.name: Input should be 'web_search'" Co-Authored-By: Claude Opus 4.6 <[email protected]>
Anthropic's server validates that requests made with Claude Code OAuth tokens include the Claude Code identity prefix in the system prompt. Without it, the credential is rejected with "This credential is only authorized for use with Claude Code." This matches the behavior of the opencode-anthropic-auth plugin. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Three issues causing "credential only authorized for Claude Code" rejection: 1. System prompt prefix must be a SEPARATE text block at system[0], not concatenated with existing text. The server checks that the first block is exactly the Claude Code identity string. 2. App attribution headers (http-referer, x-title) identified the request as coming from a non-Claude-Code client. Now stripped for OAuth requests. 3. Restored beta header merging so SDK betas (structured-outputs, effort) are preserved alongside the required OAuth betas. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1. Prefix tool_choice.name with mcp_ when a specific tool is forced
via tool policy. Without this, the SDK sends { type: "tool",
name: "bash" } while the tool definition says mcp_bash, causing
a validation mismatch on forced-tool requests.
2. Add carry-over buffer to streaming response prefix stripping.
Stream chunks can split a JSON token like "name":"mcp_foo" across
reads. The carry buffer holds back trailing fragments with unclosed
strings and prepends them to the next chunk before applying the
regex, preventing missed replacements.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f35d9978a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const lastNewline = replaced.lastIndexOf("\n"); | ||
| const tail = lastNewline >= 0 ? replaced.slice(lastNewline + 1) : replaced; | ||
| const quoteCount = (tail.match(/"/g) || []).length; | ||
| if (quoteCount % 2 !== 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve split tool-name fragments regardless of quote parity
In stripAnthropicOauthToolPrefix, tail carry-over is only enabled when the last SSE line has an odd quote count. If a chunk boundary lands at an even-quote point inside the pattern (for example right after "name"), the first fragment is flushed, the next fragment no longer matches /"name"\s*:\s*"mcp_..."/, and the mcp_ prefix leaks through. This makes tool-call decoding nondeterministic in streaming mode and can break tool dispatch depending on network chunking.
Useful? React with 👍 / 👎.
| if (!storedAnthropicOauth) return false; | ||
| if (!creds.isConfigured) return true; | ||
| return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Route Anthropic OAuth based on explicit API key presence
The OAuth routing decision uses creds.isConfigured, which includes environment-derived credentials. As a result, if a user connects Anthropic OAuth but has ANTHROPIC_API_KEY set in their shell environment (without setting anthropic.apiKey in config), requests are silently routed through API-key billing instead of Claude Max/Pro subscription billing. This is a billing-impacting behavior mismatch for OAuth users.
Useful? React with 👍 / 👎.
|
@jasonwbarnett IIRC this is against Anthropic's terms-of-service and there was a recent ban wave for opencode users that use that OAuth. So far OpenAI & Copilot communicated it's OK to use their subscription model externally. |
Dang. Are you guys working with Anthropic or is this a closed door at the moment? |
|
@jasonwbarnett unfortunately Anthropic is only permitting use with Claude Code which means some third-party tools that wrap the SDK may leverage it, but not us. I'm working on getting in touch with the right people there but hopes are low. OpenAI's approach is a bit different in part because they have usage-based billing with Codex. |
|
@jasonwbarnett FYI I issued you $300 in Gateway credits in appreciation for your efforts. |
Summary
Details
New files:
src/common/constants/anthropicOAuth.ts-- OAuth endpoints, client ID, URL builders, system prompt prefix constantsrc/node/utils/anthropicOauthAuth.ts-- Token parsing, expiry check (30s skew buffer)src/node/services/anthropicOauthService.ts-- OAuth service: startFlow, submitCode, cancelFlow, disconnect, getValidAuth (with AsyncMutex for thread-safe refresh)Key behaviors:
Authorization: Bearer,anthropic-beta: oauth-2025-04-20,user-agent: claude-cli/2.1.2, tool namemcp_prefixing,?beta=trueURL rewritesystem[0].textis exactly the identity string)http-referer,x-title) that would identify the request as non-Claude-Codemcp_; built-in server-side tools (web_search, etc.) are left unchangedtool_choice.namewhen tool policy forces a specific toolmcp_prefix from tool names in streaming responses (with carry-over buffer for chunk boundary safety)invalid_grantrefresh errorsUI (Settings > Anthropic):
Context
Anthropic has an undocumented OAuth API (same flow Claude Code CLI uses internally) that third-party tools like opencode already leverage via the
opencode-anthropic-authnpm plugin. This PR brings native support to mux so users with Claude Max/Pro subscriptions don't have to pay per-token when they already have a subscription.Replaces #2344 (moved from personal fork to org fork).
Test plan
make typecheckpassesmake lintpassesmake test-- 3653 pass (14 pre-existing failures in unrelated diffParser/hooks tests)Generated with Claude Code