mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-11 20:05:19 +00:00
Add custom headers support for MCP connections in backend
Co-authored-by: tjbck <25473318+tjbck@users.noreply.github.com>
This commit is contained in:
parent
d77b77d6d5
commit
fe10a26336
3 changed files with 129 additions and 3 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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', '')}"
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue