headersHelper.ts
services/mcp/headersHelper.ts
139
Lines
4718
Bytes
2
Exports
9
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with integrations, mcp. It contains 139 lines, 9 detected imports, and 2 detected exports.
Important relationships
Detected exports
getMcpHeadersFromHelpergetMcpServerHeaders
Keywords
headersconfigservernameserverheadershelperutilsobjectcheckprojectdynamic
Detected imports
../../bootstrap/state.js../../utils/config.js../../utils/debug.js../../utils/errors.js../../utils/execFileNoThrow.js../../utils/log.js../../utils/slowOperations.js../analytics/index.js./types.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
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'
/**
* Check if the MCP server config comes from project settings (projectSettings or localSettings)
* This is important for security checks
*/
function isMcpServerFromProjectOrLocalSettings(
config: ScopedMcpServerConfig,
): boolean {
return config.scope === 'project' || config.scope === 'local'
}
/**
* Get dynamic headers for an MCP server using the headersHelper script
* @param serverName The name of the MCP server
* @param config The MCP server configuration
* @returns Headers object or null if not configured or failed
*/
export async function getMcpHeadersFromHelper(
serverName: string,
config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig,
): Promise<Record<string, string> | null> {
if (!config.headersHelper) {
return null
}
// Security check for project/local settings
// Skip trust check in non-interactive mode (e.g., CI/CD, automation)
if (
'scope' in config &&
isMcpServerFromProjectOrLocalSettings(config as ScopedMcpServerConfig) &&
!getIsNonInteractiveSession()
) {
// Check if trust has been established for this project
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,
// Pass server context so one helper script can serve multiple MCP servers
// (git credential-helper style). See deshaw/anthropic-issues#28.
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`,
)
}
// Validate all values are strings
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 instead of throwing to avoid blocking the connection
return null
}
}
/**
* Get combined headers for an MCP server (static + dynamic)
* @param serverName The name of the MCP server
* @param config The MCP server configuration
* @returns Combined headers object
*/
export async function getMcpServerHeaders(
serverName: string,
config: McpSSEServerConfig | McpHTTPServerConfig | McpWebSocketServerConfig,
): Promise<Record<string, string>> {
const staticHeaders = config.headers || {}
const dynamicHeaders =
(await getMcpHeadersFromHelper(serverName, config)) || {}
// Dynamic headers override static headers if both are present
return {
...staticHeaders,
...dynamicHeaders,
}
}