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:
try:
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
if form_data.auth_type == "bearer":
@ -279,10 +284,11 @@ async def verify_tool_servers_config(
except Exception as e:
pass
# Authorization header from auth takes precedence over custom headers
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()
return {
"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", "")
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":
headers["Authorization"] = (
f"Bearer {mcp_server_connection.get('key', '')}"

View file

@ -44,6 +44,7 @@
let auth_type = 'bearer';
let key = '';
let headers: { key: string; value: string }[] = [];
let accessControl = {};
@ -123,12 +124,21 @@
console.debug('Connection successful', res);
}
} 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, {
url,
path,
type,
auth_type,
key,
headers: Object.keys(headersObj).length > 0 ? headersObj : undefined,
config: {
enable: enable,
access_control: accessControl
@ -179,6 +189,14 @@
if (data.auth_type) auth_type = data.auth_type;
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) {
id = data.info.id ?? '';
name = data.info.name ?? '';
@ -200,6 +218,14 @@
const exportHandler = async () => {
// 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([
{
type,
@ -211,6 +237,7 @@
auth_type,
key,
...(Object.keys(headersObj).length > 0 ? { headers: headersObj } : {}),
info: {
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 = {
type,
url,
@ -266,6 +301,7 @@
auth_type,
key,
...(Object.keys(headersObj).length > 0 ? { headers: headersObj } : {}),
config: {
enable: enable,
access_control: accessControl
@ -293,6 +329,7 @@
key = '';
auth_type = 'bearer';
headers = [];
id = '';
name = '';
@ -315,6 +352,16 @@
auth_type = connection?.auth_type ?? 'bearer';
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 ?? '';
name = connection.info?.name ?? '';
description = connection.info?.description ?? '';
@ -656,6 +703,73 @@
</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}
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />