Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165import axios from 'axios'import memoize from 'lodash-es/memoize.js'import { getOauthConfig } from 'src/constants/oauth.js'import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent,} from 'src/services/analytics/index.js'import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'import { logForDebugging } from 'src/utils/debug.js'import { isEnvDefinedFalsy } from 'src/utils/envUtils.js'import { clearMcpAuthCache } from './client.js'import { normalizeNameForMCP } from './normalization.js'import type { ScopedMcpServerConfig } from './types.js'
type ClaudeAIMcpServer = { type: 'mcp_server' id: string display_name: string url: string created_at: string}
type ClaudeAIMcpServersResponse = { data: ClaudeAIMcpServer[] has_more: boolean next_page: string | null}
const FETCH_TIMEOUT_MS = 5000const MCP_SERVERS_BETA_HEADER = 'mcp-servers-2025-12-04'
/** * Fetches MCP server configurations from Claude.ai org configs. * These servers are managed by the organization via Claude.ai. * * Results are memoized for the session lifetime (fetch once per CLI session). */export const fetchClaudeAIMcpConfigsIfEligible = memoize( async (): Promise<Record<string, ScopedMcpServerConfig>> => { try { if (isEnvDefinedFalsy(process.env.ENABLE_CLAUDEAI_MCP_SERVERS)) { logForDebugging('[claudeai-mcp] Disabled via env var') logEvent('tengu_claudeai_mcp_eligibility', { state: 'disabled_env_var' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return {} }
const tokens = getClaudeAIOAuthTokens() if (!tokens?.accessToken) { logForDebugging('[claudeai-mcp] No access token') logEvent('tengu_claudeai_mcp_eligibility', { state: 'no_oauth_token' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return {} }
// Check for user:mcp_servers scope directly instead of isClaudeAISubscriber(). // In non-interactive mode, isClaudeAISubscriber() returns false when ANTHROPIC_API_KEY // is set (even with valid OAuth tokens) because preferThirdPartyAuthentication() causes // isAnthropicAuthEnabled() to return false. Checking the scope directly allows users // with both API keys and OAuth tokens to access claude.ai MCPs in print mode. if (!tokens.scopes?.includes('user:mcp_servers')) { logForDebugging( `[claudeai-mcp] Missing user:mcp_servers scope (scopes=${tokens.scopes?.join(',') || 'none'})`, ) logEvent('tengu_claudeai_mcp_eligibility', { state: 'missing_scope' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return {} }
const baseUrl = getOauthConfig().BASE_API_URL const url = `${baseUrl}/v1/mcp_servers?limit=1000`
logForDebugging(`[claudeai-mcp] Fetching from ${url}`)
const response = await axios.get<ClaudeAIMcpServersResponse>(url, { headers: { Authorization: `Bearer ${tokens.accessToken}`, 'Content-Type': 'application/json', 'anthropic-beta': MCP_SERVERS_BETA_HEADER, 'anthropic-version': '2023-06-01', }, timeout: FETCH_TIMEOUT_MS, })
const configs: Record<string, ScopedMcpServerConfig> = {} // Track used normalized names to detect collisions and assign (2), (3), etc. suffixes. // We check the final normalized name (including suffix) to handle edge cases where // a suffixed name collides with another server's base name (e.g., "Example Server 2" // colliding with "Example Server! (2)" which both normalize to claude_ai_Example_Server_2). const usedNormalizedNames = new Set<string>()
for (const server of response.data.data) { const baseName = `claude.ai ${server.display_name}`
// Try without suffix first, then increment until we find an unused normalized name let finalName = baseName let finalNormalized = normalizeNameForMCP(finalName) let count = 1 while (usedNormalizedNames.has(finalNormalized)) { count++ finalName = `${baseName} (${count})` finalNormalized = normalizeNameForMCP(finalName) } usedNormalizedNames.add(finalNormalized)
configs[finalName] = { type: 'claudeai-proxy', url: server.url, id: server.id, scope: 'claudeai', } }
logForDebugging( `[claudeai-mcp] Fetched ${Object.keys(configs).length} servers`, ) logEvent('tengu_claudeai_mcp_eligibility', { state: 'eligible' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return configs } catch { logForDebugging(`[claudeai-mcp] Fetch failed`) return {} } },)
/** * Clears the memoized cache for fetchClaudeAIMcpConfigsIfEligible. * Call this after login so the next fetch will use the new auth tokens. */export function clearClaudeAIMcpConfigsCache(): void { fetchClaudeAIMcpConfigsIfEligible.cache.clear?.() // Also clear the auth cache so freshly-authorized servers get re-connected clearMcpAuthCache()}
/** * Record that a claude.ai connector successfully connected. Idempotent. * * Gates the "N connectors unavailable/need auth" startup notifications: a * connector that was working yesterday and is now failed is a state change * worth surfacing; an org-configured connector that's been needs-auth since * it showed up is one the user has demonstrably ignored. */export function markClaudeAiMcpConnected(name: string): void { saveGlobalConfig(current => { const seen = current.claudeAiMcpEverConnected ?? [] if (seen.includes(name)) return current return { ...current, claudeAiMcpEverConnected: [...seen, name] } })}
export function hasClaudeAiMcpEverConnected(name: string): boolean { return (getGlobalConfig().claudeAiMcpEverConnected ?? []).includes(name)}