mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
feat(mcp): support dynamic HTTP request headers in MCPClient (#37938)
This commit is contained in:
parent
995ba6b00e
commit
9521c7fe7d
@ -1,10 +1,13 @@
|
||||
import logging
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from contextlib import AbstractContextManager, ExitStack
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from flask import has_request_context, request
|
||||
|
||||
from core.mcp.client.sse_client import sse_client
|
||||
from core.mcp.client.streamable_client import streamablehttp_client
|
||||
from core.mcp.error import MCPConnectionError
|
||||
@ -23,10 +26,22 @@ class MCPClient:
|
||||
sse_read_timeout: float | None = None,
|
||||
):
|
||||
self.server_url = server_url
|
||||
self.headers = headers or {}
|
||||
self.headers = headers.copy() if headers else {}
|
||||
self.timeout = timeout
|
||||
self.sse_read_timeout = sse_read_timeout
|
||||
|
||||
# Substitute placeholders with incoming request headers if in a request context
|
||||
if has_request_context() and self.headers:
|
||||
pattern = re.compile(r"\{\{\s*request\.headers?\.(.+?)\s*\}\}", re.IGNORECASE)
|
||||
for key, value in list(self.headers.items()):
|
||||
if isinstance(value, str):
|
||||
|
||||
def replace_func(match):
|
||||
header_name = match.group(1)
|
||||
return request.headers.get(header_name, "")
|
||||
|
||||
self.headers[key] = pattern.sub(replace_func, value)
|
||||
|
||||
# Initialize session and client objects
|
||||
self._session: ClientSession | None = None
|
||||
self._exit_stack = ExitStack()
|
||||
|
||||
@ -43,6 +43,57 @@ class TestMCPClient:
|
||||
assert client.timeout is None
|
||||
assert client.sse_read_timeout is None
|
||||
|
||||
def test_init_with_dynamic_request_headers(self):
|
||||
"""Test client initialization with dynamic request headers."""
|
||||
from flask import Flask
|
||||
|
||||
app = Flask("test")
|
||||
with app.test_request_context(headers={"X-Custom-Auth": "my-secret-token", "X-User-Id": "user123"}):
|
||||
client = MCPClient(
|
||||
server_url="http://test.example.com",
|
||||
headers={
|
||||
"Authorization": "Bearer {{request.headers.X-Custom-Auth}}",
|
||||
"X-Request-User": "{{request.header.X-User-Id}}",
|
||||
"X-Static-Header": "static-val",
|
||||
},
|
||||
)
|
||||
|
||||
assert client.headers == {
|
||||
"Authorization": "Bearer my-secret-token",
|
||||
"X-Request-User": "user123",
|
||||
"X-Static-Header": "static-val",
|
||||
}
|
||||
|
||||
def test_init_with_dynamic_request_headers_missing(self):
|
||||
"""Test client initialization with dynamic request headers that are missing."""
|
||||
from flask import Flask
|
||||
|
||||
app = Flask("test")
|
||||
with app.test_request_context(headers={}):
|
||||
client = MCPClient(
|
||||
server_url="http://test.example.com",
|
||||
headers={
|
||||
"Authorization": "Bearer {{request.headers.X-Custom-Auth}}",
|
||||
},
|
||||
)
|
||||
|
||||
assert client.headers == {
|
||||
"Authorization": "Bearer ",
|
||||
}
|
||||
|
||||
def test_init_with_dynamic_request_headers_no_context(self):
|
||||
"""Test client initialization with dynamic request headers but no request context."""
|
||||
client = MCPClient(
|
||||
server_url="http://test.example.com",
|
||||
headers={
|
||||
"Authorization": "Bearer {{request.headers.X-Custom-Auth}}",
|
||||
},
|
||||
)
|
||||
|
||||
assert client.headers == {
|
||||
"Authorization": "Bearer {{request.headers.X-Custom-Auth}}",
|
||||
}
|
||||
|
||||
@patch("core.mcp.mcp_client.streamablehttp_client")
|
||||
@patch("core.mcp.mcp_client.ClientSession")
|
||||
def test_initialize_with_mcp_url(self, mock_client_session, mock_streamable_client):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user