1- """Claude OAuth token handling for Agent SDK."""
1+ """Claude authentication for Agent SDK.
2+
3+ Supports three auth methods (in priority order):
4+ 1. ANTHROPIC_API_KEY env var - works everywhere (API billing)
5+ 2. System `claude` CLI - shares auth with Claude Code (local dev)
6+ 3. Mounted .credentials.json - OAuth tokens from K8s/Docker host
7+ """
28
39from __future__ import annotations
410
@@ -30,25 +36,61 @@ def _find_credentials(config_dir: str) -> str | None:
3036 return None
3137
3238
39+ def has_api_key () -> bool :
40+ """Check if ANTHROPIC_API_KEY is set."""
41+ return bool (os .environ .get ("ANTHROPIC_API_KEY" ))
42+
43+
44+ def has_system_claude () -> bool :
45+ """Check if system claude CLI is available."""
46+ return shutil .which ("claude" ) is not None
47+
48+
3349def ensure_writable_config () -> str :
3450 """Ensure a writable config dir with Claude credentials exists.
3551
36- Two deployment scenarios:
37- 1. **Local (dev)**: ~/.claude/ is writable and has .claude.json from Claude Code.
38- System `claude` CLI is available and handles auth natively.
39- 2. **Docker (mcpproxy)**: ~/.claude/ is mounted read-only with .credentials.json.
40- Bundled SDK CLI is used. We copy credentials to a writable tmpdir.
52+ Auth priority:
53+ 1. ANTHROPIC_API_KEY env var - SDK uses it directly, no config dir needed
54+ 2. System claude CLI - handles auth natively from ~/.claude
55+ 3. Mounted .credentials.json - copy to writable tmpdir for bundled CLI
4156
4257 Returns the path to the (writable) config directory.
58+ Raises RuntimeError if no auth method is available.
4359 """
60+ # If API key is set, the SDK handles auth directly
61+ if has_api_key ():
62+ logger .info ("Using ANTHROPIC_API_KEY for authentication" )
63+ # Still need a writable config dir for SDK session files
64+ tmp_dir = tempfile .mkdtemp (prefix = "mcp_scanner_claude_" )
65+ return tmp_dir
66+
4467 src_dir = get_claude_config_dir ()
4568 creds_path = _find_credentials (src_dir )
4669
70+ # System claude CLI handles its own auth (reads ~/.claude internally)
71+ if has_system_claude ():
72+ if creds_path :
73+ logger .info ("Found credentials at %s (system claude CLI will use natively)" , creds_path )
74+ else :
75+ logger .info ("System claude CLI found, will use its own auth" )
76+ return src_dir
77+
78+ # Docker/bundled CLI: need actual .credentials.json with OAuth tokens
4779 if not creds_path :
4880 raise RuntimeError (
49- f"No credentials found in { src_dir } . "
50- f"Looked for: { ', ' .join (CREDENTIAL_FILES )} . "
51- "Run 'claude login' to authenticate."
81+ "No authentication available. Options:\n "
82+ " 1. Set ANTHROPIC_API_KEY environment variable\n "
83+ " 2. Install claude CLI and run 'claude login'\n "
84+ " 3. Mount ~/.claude with .credentials.json (Docker/K8s)"
85+ )
86+
87+ # .claude.json from desktop app doesn't have raw tokens - bundled CLI can't use it
88+ if creds_path .endswith (".claude.json" ) and not has_system_claude ():
89+ raise RuntimeError (
90+ f"Found { creds_path } but bundled CLI needs .credentials.json with OAuth tokens.\n "
91+ "Options:\n "
92+ " 1. Set ANTHROPIC_API_KEY environment variable\n "
93+ " 2. Mount .credentials.json from a host with 'claude login' (K8s pattern)"
5294 )
5395
5496 logger .info ("Found credentials at %s" , creds_path )
@@ -63,7 +105,7 @@ def ensure_writable_config() -> str:
63105 except OSError :
64106 pass
65107
66- # Source is read -only (Docker mount) - copy to writable tmpdir
108+ # Read -only mount - copy to writable tmpdir
67109 tmp_dir = tempfile .mkdtemp (prefix = "mcp_scanner_claude_" )
68110
69111 for name in CREDENTIAL_FILES :
0 commit comments