| import { getIsNonInteractiveSession } from '../../bootstrap/state.js' |
| import { checkHasTrustDialogAccepted } from '../../utils/config.js' |
| import { logAntError } from '../../utils/debug.js' |
| import { errorMessage } from '../../utils/errors.js' |
| import { execFileNoThrowWithCwd } from '../../utils/execFileNoThrow.js' |
| import { logError, logMCPDebug, logMCPError } from '../../utils/log.js' |
| import { jsonParse } from '../../utils/slowOperations.js' |
| import { logEvent } from '../analytics/index.js' |
| import type { |
| McpHTTPServerConfig, |
| McpSSEServerConfig, |
| McpWebSocketServerConfig, |
| ScopedMcpServerConfig, |
| } from './types.js' |
|
|
| |
| |
| |
| |
| function isMcpServerFromProjectOrLocalSettings( |
| config: ScopedMcpServerConfig, |
| ): boolean { |
| return config.scope === 'project' || config.scope === 'local' |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function getMcpHeadersFromHelper( |
| serverName: string, |
| config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig, |
| ): Promise<Record<string, string> | null> { |
| if (!config.headersHelper) { |
| return null |
| } |
|
|
| |
| |
| if ( |
| 'scope' in config && |
| isMcpServerFromProjectOrLocalSettings(config as ScopedMcpServerConfig) && |
| !getIsNonInteractiveSession() |
| ) { |
| |
| const hasTrust = checkHasTrustDialogAccepted() |
| if (!hasTrust) { |
| const error = new Error( |
| `Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, |
| ) |
| logAntError('MCP headersHelper invoked before trust check', error) |
| logEvent('tengu_mcp_headersHelper_missing_trust', {}) |
| return null |
| } |
| } |
|
|
| try { |
| logMCPDebug(serverName, 'Executing headersHelper to get dynamic headers') |
| const execResult = await execFileNoThrowWithCwd(config.headersHelper, [], { |
| shell: true, |
| timeout: 10000, |
| |
| |
| env: { |
| ...process.env, |
| CLAUDE_CODE_MCP_SERVER_NAME: serverName, |
| CLAUDE_CODE_MCP_SERVER_URL: config.url, |
| }, |
| }) |
| if (execResult.code !== 0 || !execResult.stdout) { |
| throw new Error( |
| `headersHelper for MCP server '${serverName}' did not return a valid value`, |
| ) |
| } |
| const result = execResult.stdout.trim() |
|
|
| const headers = jsonParse(result) |
| if ( |
| typeof headers !== 'object' || |
| headers === null || |
| Array.isArray(headers) |
| ) { |
| throw new Error( |
| `headersHelper for MCP server '${serverName}' must return a JSON object with string key-value pairs`, |
| ) |
| } |
|
|
| |
| for (const [key, value] of Object.entries(headers)) { |
| if (typeof value !== 'string') { |
| throw new Error( |
| `headersHelper for MCP server '${serverName}' returned non-string value for key "${key}": ${typeof value}`, |
| ) |
| } |
| } |
|
|
| logMCPDebug( |
| serverName, |
| `Successfully retrieved ${Object.keys(headers).length} headers from headersHelper`, |
| ) |
| return headers as Record<string, string> |
| } catch (error) { |
| logMCPError( |
| serverName, |
| `Error getting headers from headersHelper: ${errorMessage(error)}`, |
| ) |
| logError( |
| new Error( |
| `Error getting MCP headers from headersHelper for server '${serverName}': ${errorMessage(error)}`, |
| ), |
| ) |
| |
| return null |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function getMcpServerHeaders( |
| serverName: string, |
| config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig, |
| ): Promise<Record<string, string>> { |
| const staticHeaders = config.headers || {} |
| const dynamicHeaders = |
| (await getMcpHeadersFromHelper(serverName, config)) || {} |
|
|
| |
| return { |
| ...staticHeaders, |
| ...dynamicHeaders, |
| } |
| } |
|
|