source dump of claude code
at main 138 lines 4.7 kB view raw
1import { getIsNonInteractiveSession } from '../../bootstrap/state.js' 2import { checkHasTrustDialogAccepted } from '../../utils/config.js' 3import { logAntError } from '../../utils/debug.js' 4import { errorMessage } from '../../utils/errors.js' 5import { execFileNoThrowWithCwd } from '../../utils/execFileNoThrow.js' 6import { logError, logMCPDebug, logMCPError } from '../../utils/log.js' 7import { jsonParse } from '../../utils/slowOperations.js' 8import { logEvent } from '../analytics/index.js' 9import type { 10 McpHTTPServerConfig, 11 McpSSEServerConfig, 12 McpWebSocketServerConfig, 13 ScopedMcpServerConfig, 14} from './types.js' 15 16/** 17 * Check if the MCP server config comes from project settings (projectSettings or localSettings) 18 * This is important for security checks 19 */ 20function isMcpServerFromProjectOrLocalSettings( 21 config: ScopedMcpServerConfig, 22): boolean { 23 return config.scope === 'project' || config.scope === 'local' 24} 25 26/** 27 * Get dynamic headers for an MCP server using the headersHelper script 28 * @param serverName The name of the MCP server 29 * @param config The MCP server configuration 30 * @returns Headers object or null if not configured or failed 31 */ 32export async function getMcpHeadersFromHelper( 33 serverName: string, 34 config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig, 35): Promise<Record<string, string> | null> { 36 if (!config.headersHelper) { 37 return null 38 } 39 40 // Security check for project/local settings 41 // Skip trust check in non-interactive mode (e.g., CI/CD, automation) 42 if ( 43 'scope' in config && 44 isMcpServerFromProjectOrLocalSettings(config as ScopedMcpServerConfig) && 45 !getIsNonInteractiveSession() 46 ) { 47 // Check if trust has been established for this project 48 const hasTrust = checkHasTrustDialogAccepted() 49 if (!hasTrust) { 50 const error = new Error( 51 `Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, 52 ) 53 logAntError('MCP headersHelper invoked before trust check', error) 54 logEvent('tengu_mcp_headersHelper_missing_trust', {}) 55 return null 56 } 57 } 58 59 try { 60 logMCPDebug(serverName, 'Executing headersHelper to get dynamic headers') 61 const execResult = await execFileNoThrowWithCwd(config.headersHelper, [], { 62 shell: true, 63 timeout: 10000, 64 // Pass server context so one helper script can serve multiple MCP servers 65 // (git credential-helper style). See deshaw/anthropic-issues#28. 66 env: { 67 ...process.env, 68 CLAUDE_CODE_MCP_SERVER_NAME: serverName, 69 CLAUDE_CODE_MCP_SERVER_URL: config.url, 70 }, 71 }) 72 if (execResult.code !== 0 || !execResult.stdout) { 73 throw new Error( 74 `headersHelper for MCP server '${serverName}' did not return a valid value`, 75 ) 76 } 77 const result = execResult.stdout.trim() 78 79 const headers = jsonParse(result) 80 if ( 81 typeof headers !== 'object' || 82 headers === null || 83 Array.isArray(headers) 84 ) { 85 throw new Error( 86 `headersHelper for MCP server '${serverName}' must return a JSON object with string key-value pairs`, 87 ) 88 } 89 90 // Validate all values are strings 91 for (const [key, value] of Object.entries(headers)) { 92 if (typeof value !== 'string') { 93 throw new Error( 94 `headersHelper for MCP server '${serverName}' returned non-string value for key "${key}": ${typeof value}`, 95 ) 96 } 97 } 98 99 logMCPDebug( 100 serverName, 101 `Successfully retrieved ${Object.keys(headers).length} headers from headersHelper`, 102 ) 103 return headers as Record<string, string> 104 } catch (error) { 105 logMCPError( 106 serverName, 107 `Error getting headers from headersHelper: ${errorMessage(error)}`, 108 ) 109 logError( 110 new Error( 111 `Error getting MCP headers from headersHelper for server '${serverName}': ${errorMessage(error)}`, 112 ), 113 ) 114 // Return null instead of throwing to avoid blocking the connection 115 return null 116 } 117} 118 119/** 120 * Get combined headers for an MCP server (static + dynamic) 121 * @param serverName The name of the MCP server 122 * @param config The MCP server configuration 123 * @returns Combined headers object 124 */ 125export async function getMcpServerHeaders( 126 serverName: string, 127 config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig, 128): Promise<Record<string, string>> { 129 const staticHeaders = config.headers || {} 130 const dynamicHeaders = 131 (await getMcpHeadersFromHelper(serverName, config)) || {} 132 133 // Dynamic headers override static headers if both are present 134 return { 135 ...staticHeaders, 136 ...dynamicHeaders, 137 } 138}