Add custom headers support for MCP connections in backend

Co-authored-by: tjbck <25473318+tjbck@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-10 04:26:53 +00:00
parent d77b77d6d5
commit fe10a26336
3 changed files with 129 additions and 3 deletions

View file

@ -262,7 +262,12 @@ async def verify_tool_servers_config(
else: else:
try: try:
client = MCPClient() client = MCPClient()
headers = None headers = {}
# Add custom headers if provided
custom_headers = getattr(form_data, "headers", None) or {}
if isinstance(custom_headers, dict):
headers.update(custom_headers)
token = None token = None
if form_data.auth_type == "bearer": if form_data.auth_type == "bearer":
@ -279,10 +284,11 @@ async def verify_tool_servers_config(
except Exception as e: except Exception as e:
pass pass
# Authorization header from auth takes precedence over custom headers
if token: if token:
headers = {"Authorization": f"Bearer {token}"} headers["Authorization"] = f"Bearer {token}"
await client.connect(form_data.url, headers=headers) await client.connect(form_data.url, headers=headers if headers else None)
specs = await client.list_tool_specs() specs = await client.list_tool_specs()
return { return {
"status": True, "status": True,

View file

@ -1325,6 +1325,12 @@ async def process_chat_payload(request, form_data, user, metadata, model):
auth_type = mcp_server_connection.get("auth_type", "") auth_type = mcp_server_connection.get("auth_type", "")
headers = {} headers = {}
# Add custom headers if provided
custom_headers = mcp_server_connection.get("headers", {})
if isinstance(custom_headers, dict):
headers.update(custom_headers)
# Authorization header from auth takes precedence over custom headers
if auth_type == "bearer": if auth_type == "bearer":
headers["Authorization"] = ( headers["Authorization"] = (
f"Bearer {mcp_server_connection.get('key', '')}" f"Bearer {mcp_server_connection.get('key', '')}"

View file

@ -44,6 +44,7 @@
let auth_type = 'bearer'; let auth_type = 'bearer';
let key = ''; let key = '';
let headers: { key: string; value: string }[] = [];
let accessControl = {}; let accessControl = {};
@ -123,12 +124,21 @@
console.debug('Connection successful', res); console.debug('Connection successful', res);
} }
} else { } else {
// Convert headers array to object
const headersObj = {};
headers.forEach((h) => {
if (h.key && h.value) {
headersObj[h.key] = h.value;
}
});
const res = await verifyToolServerConnection(localStorage.token, { const res = await verifyToolServerConnection(localStorage.token, {
url, url,
path, path,
type, type,
auth_type, auth_type,
key, key,
headers: Object.keys(headersObj).length > 0 ? headersObj : undefined,
config: { config: {
enable: enable, enable: enable,
access_control: accessControl access_control: accessControl
@ -179,6 +189,14 @@
if (data.auth_type) auth_type = data.auth_type; if (data.auth_type) auth_type = data.auth_type;
if (data.key) key = data.key; if (data.key) key = data.key;
// Import custom headers
if (data.headers && typeof data.headers === 'object') {
headers = Object.entries(data.headers).map(([key, value]) => ({
key,
value: String(value)
}));
}
if (data.info) { if (data.info) {
id = data.info.id ?? ''; id = data.info.id ?? '';
name = data.info.name ?? ''; name = data.info.name ?? '';
@ -200,6 +218,14 @@
const exportHandler = async () => { const exportHandler = async () => {
// export current connection as json file // export current connection as json file
// Convert headers array to object
const headersObj = {};
headers.forEach((h) => {
if (h.key && h.value) {
headersObj[h.key] = h.value;
}
});
const json = JSON.stringify([ const json = JSON.stringify([
{ {
type, type,
@ -211,6 +237,7 @@
auth_type, auth_type,
key, key,
...(Object.keys(headersObj).length > 0 ? { headers: headersObj } : {}),
info: { info: {
id: id, id: id,
@ -256,6 +283,14 @@
} }
} }
// Convert headers array to object
const headersObj = {};
headers.forEach((h) => {
if (h.key && h.value) {
headersObj[h.key] = h.value;
}
});
const connection = { const connection = {
type, type,
url, url,
@ -266,6 +301,7 @@
auth_type, auth_type,
key, key,
...(Object.keys(headersObj).length > 0 ? { headers: headersObj } : {}),
config: { config: {
enable: enable, enable: enable,
access_control: accessControl access_control: accessControl
@ -293,6 +329,7 @@
key = ''; key = '';
auth_type = 'bearer'; auth_type = 'bearer';
headers = [];
id = ''; id = '';
name = ''; name = '';
@ -315,6 +352,16 @@
auth_type = connection?.auth_type ?? 'bearer'; auth_type = connection?.auth_type ?? 'bearer';
key = connection?.key ?? ''; key = connection?.key ?? '';
// Initialize custom headers from connection
if (connection?.headers && typeof connection.headers === 'object') {
headers = Object.entries(connection.headers).map(([key, value]) => ({
key,
value: String(value)
}));
} else {
headers = [];
}
id = connection.info?.id ?? ''; id = connection.info?.id ?? '';
name = connection.info?.name ?? ''; name = connection.info?.name ?? '';
description = connection.info?.description ?? ''; description = connection.info?.description ?? '';
@ -656,6 +703,73 @@
</div> </div>
</div> </div>
{#if type === 'mcp'}
<div class="flex gap-2 mt-2">
<div class="flex flex-col w-full">
<div class="flex justify-between items-center mb-0.5">
<div class="flex gap-2 items-center">
<div
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
>
{$i18n.t('Custom Headers')}
</div>
</div>
<Tooltip content={$i18n.t('Add Header')}>
<button
type="button"
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
headers = [...headers, { key: '', value: '' }];
}}
aria-label={$i18n.t('Add Header')}
>
<Plus className="size-4" />
</button>
</Tooltip>
</div>
{#if headers.length > 0}
<div class="flex flex-col gap-2">
{#each headers as header, index}
<div class="flex gap-2 items-center">
<input
class={`flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={header.key}
placeholder={$i18n.t('Header Name')}
autocomplete="off"
/>
<input
class={`flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={header.value}
placeholder={$i18n.t('Header Value')}
autocomplete="off"
/>
<button
type="button"
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
headers = headers.filter((_, i) => i !== index);
}}
aria-label={$i18n.t('Remove Header')}
>
<Minus className="size-4" />
</button>
</div>
{/each}
</div>
{:else}
<div
class={`text-xs self-center translate-y-[1px] ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
>
{$i18n.t('No custom headers configured')}
</div>
{/if}
</div>
</div>
{/if}
{#if !direct} {#if !direct}
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" /> <hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />