feat: mcp custom headers (#4800)
* Add support for custom HTTP headers in MCP servers Allow users to configure custom HTTP headers for SSE and streamable HTTP MCP server connections. This enables authentication and other API requirements. * Add custom headers i18n strings
This commit is contained in:
parent
c7ef2c5791
commit
1d211ee9f7
@ -7,7 +7,7 @@ import { createInMemoryMCPServer } from '@main/mcpServers/factory'
|
|||||||
import { makeSureDirExists } from '@main/utils'
|
import { makeSureDirExists } from '@main/utils'
|
||||||
import { getBinaryName, getBinaryPath } from '@main/utils/process'
|
import { getBinaryName, getBinaryPath } from '@main/utils/process'
|
||||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
import { SSEClientTransport, SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js'
|
||||||
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
||||||
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory'
|
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
@ -137,12 +137,19 @@ class McpService {
|
|||||||
transport = clientTransport
|
transport = clientTransport
|
||||||
} else if (server.baseUrl) {
|
} else if (server.baseUrl) {
|
||||||
if (server.type === 'streamableHttp') {
|
if (server.type === 'streamableHttp') {
|
||||||
transport = new StreamableHTTPClientTransport(
|
const options: StreamableHTTPClientTransportOptions = {
|
||||||
new URL(server.baseUrl!),
|
requestInit: {
|
||||||
{} as StreamableHTTPClientTransportOptions
|
headers: server.headers || {}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
transport = new StreamableHTTPClientTransport(new URL(server.baseUrl!), options)
|
||||||
} else if (server.type === 'sse') {
|
} else if (server.type === 'sse') {
|
||||||
transport = new SSEClientTransport(new URL(server.baseUrl!))
|
const options: SSEClientTransportOptions = {
|
||||||
|
requestInit: {
|
||||||
|
headers: server.headers || {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transport = new SSEClientTransport(new URL(server.baseUrl!), options)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid server type')
|
throw new Error('Invalid server type')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1072,6 +1072,8 @@
|
|||||||
"editServer": "Edit Server",
|
"editServer": "Edit Server",
|
||||||
"env": "Environment Variables",
|
"env": "Environment Variables",
|
||||||
"envTooltip": "Format: KEY=value, one per line",
|
"envTooltip": "Format: KEY=value, one per line",
|
||||||
|
"headers": "Headers",
|
||||||
|
"headersTooltip": "Custom headers for HTTP requests",
|
||||||
"findMore": "Find More MCP",
|
"findMore": "Find More MCP",
|
||||||
"searchNpx": "Search MCP",
|
"searchNpx": "Search MCP",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
|
|||||||
@ -1071,6 +1071,8 @@
|
|||||||
"editServer": "サーバーを編集",
|
"editServer": "サーバーを編集",
|
||||||
"env": "環境変数",
|
"env": "環境変数",
|
||||||
"envTooltip": "形式: KEY=value, 1行に1つ",
|
"envTooltip": "形式: KEY=value, 1行に1つ",
|
||||||
|
"headers": "ヘッダー",
|
||||||
|
"headersTooltip": "HTTP リクエストのカスタムヘッダー",
|
||||||
"findMore": "MCP を見つける",
|
"findMore": "MCP を見つける",
|
||||||
"searchNpx": "MCP を検索",
|
"searchNpx": "MCP を検索",
|
||||||
"install": "インストール",
|
"install": "インストール",
|
||||||
|
|||||||
@ -1071,6 +1071,8 @@
|
|||||||
"editServer": "Редактировать сервер",
|
"editServer": "Редактировать сервер",
|
||||||
"env": "Переменные окружения",
|
"env": "Переменные окружения",
|
||||||
"envTooltip": "Формат: KEY=value, по одной на строку",
|
"envTooltip": "Формат: KEY=value, по одной на строку",
|
||||||
|
"headers": "Заголовки",
|
||||||
|
"headersTooltip": "Пользовательские заголовки для HTTP-запросов",
|
||||||
"findMore": "Найти больше MCP",
|
"findMore": "Найти больше MCP",
|
||||||
"searchNpx": "Найти MCP",
|
"searchNpx": "Найти MCP",
|
||||||
"install": "Установить",
|
"install": "Установить",
|
||||||
|
|||||||
@ -1072,6 +1072,8 @@
|
|||||||
"editServer": "编辑服务器",
|
"editServer": "编辑服务器",
|
||||||
"env": "环境变量",
|
"env": "环境变量",
|
||||||
"envTooltip": "格式:KEY=value,每行一个",
|
"envTooltip": "格式:KEY=value,每行一个",
|
||||||
|
"headers": "请求头",
|
||||||
|
"headersTooltip": "HTTP 请求的自定义请求头",
|
||||||
"findMore": "更多 MCP",
|
"findMore": "更多 MCP",
|
||||||
"searchNpx": "搜索 MCP",
|
"searchNpx": "搜索 MCP",
|
||||||
"install": "安装",
|
"install": "安装",
|
||||||
|
|||||||
@ -1071,6 +1071,8 @@
|
|||||||
"editServer": "編輯伺服器",
|
"editServer": "編輯伺服器",
|
||||||
"env": "環境變數",
|
"env": "環境變數",
|
||||||
"envTooltip": "格式:KEY=value,每行一個",
|
"envTooltip": "格式:KEY=value,每行一個",
|
||||||
|
"headers": "請求標頭",
|
||||||
|
"headersTooltip": "HTTP 請求的自定義標頭",
|
||||||
"findMore": "更多 MCP",
|
"findMore": "更多 MCP",
|
||||||
"searchNpx": "搜索 MCP",
|
"searchNpx": "搜索 MCP",
|
||||||
"install": "安裝",
|
"install": "安裝",
|
||||||
|
|||||||
@ -27,6 +27,7 @@ interface MCPFormValues {
|
|||||||
args?: string
|
args?: string
|
||||||
env?: string
|
env?: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
headers?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Registry {
|
interface Registry {
|
||||||
@ -101,6 +102,11 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
? Object.entries(server.env)
|
? Object.entries(server.env)
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
: '',
|
||||||
|
headers: server.headers
|
||||||
|
? Object.entries(server.headers)
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
.join('\n')
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
}, [server, form])
|
}, [server, form])
|
||||||
@ -218,6 +224,20 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
mcpServer.env = env
|
mcpServer.env = env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.headers) {
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
values.headers.split('\n').forEach((line) => {
|
||||||
|
if (line.trim()) {
|
||||||
|
const [key, ...chunks] = line.split(':')
|
||||||
|
const value = chunks.join(':')
|
||||||
|
if (key && value) {
|
||||||
|
headers[key.trim()] = value.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mcpServer.headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.api.mcp.restartServer(mcpServer)
|
await window.api.mcp.restartServer(mcpServer)
|
||||||
updateMCPServer({ ...mcpServer, isActive: true })
|
updateMCPServer({ ...mcpServer, isActive: true })
|
||||||
@ -400,22 +420,40 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{serverType === 'sse' && (
|
{serverType === 'sse' && (
|
||||||
<Form.Item
|
<>
|
||||||
name="baseUrl"
|
<Form.Item
|
||||||
label={t('settings.mcp.url')}
|
name="baseUrl"
|
||||||
rules={[{ required: serverType === 'sse', message: '' }]}
|
label={t('settings.mcp.url')}
|
||||||
tooltip={t('settings.mcp.baseUrlTooltip')}>
|
rules={[{ required: serverType === 'sse', message: '' }]}
|
||||||
<Input placeholder="http://localhost:3000/sse" />
|
tooltip={t('settings.mcp.baseUrlTooltip')}>
|
||||||
</Form.Item>
|
<Input placeholder="http://localhost:3000/sse" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="headers" label={t('settings.mcp.headers')} tooltip={t('settings.mcp.headersTooltip')}>
|
||||||
|
<TextArea
|
||||||
|
rows={3}
|
||||||
|
placeholder={`Content-Type=application/json\nAuthorization=Bearer token`}
|
||||||
|
style={{ fontFamily: 'monospace' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{serverType === 'streamableHttp' && (
|
{serverType === 'streamableHttp' && (
|
||||||
<Form.Item
|
<>
|
||||||
name="baseUrl"
|
<Form.Item
|
||||||
label={t('settings.mcp.url')}
|
name="baseUrl"
|
||||||
rules={[{ required: serverType === 'streamableHttp', message: '' }]}
|
label={t('settings.mcp.url')}
|
||||||
tooltip={t('settings.mcp.baseUrlTooltip')}>
|
rules={[{ required: serverType === 'streamableHttp', message: '' }]}
|
||||||
<Input placeholder="http://localhost:3000/mcp" />
|
tooltip={t('settings.mcp.baseUrlTooltip')}>
|
||||||
</Form.Item>
|
<Input placeholder="http://localhost:3000/mcp" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="headers" label={t('settings.mcp.headers')} tooltip={t('settings.mcp.headersTooltip')}>
|
||||||
|
<TextArea
|
||||||
|
rows={3}
|
||||||
|
placeholder={`Content-Type=application/json\nAuthorization=Bearer token`}
|
||||||
|
style={{ fontFamily: 'monospace' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{serverType === 'stdio' && (
|
{serverType === 'stdio' && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -386,6 +386,7 @@ export interface MCPServer {
|
|||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
disabledTools?: string[] // List of tool names that are disabled for this server
|
disabledTools?: string[] // List of tool names that are disabled for this server
|
||||||
|
headers?: Record<string, string> // Custom headers to be sent with requests to this server
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MCPToolInputSchema {
|
export interface MCPToolInputSchema {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user