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:
|
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,
|
||||||
|
|
|
||||||
|
|
@ -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', '')}"
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue