Repository: jlowin/fastmcp

Issue:
new experimental FastMCPOpenAPI server lost headers on request when it was created with client having headers.
### Description

new experimental FastMCPOpenAPI server lost headers on request when it was created with client having headers.

situation:
create FastMCP.from_openapi(spec, client) 
to bridge existing REST server requiring authentication.

in this case, FastMCP needs to fill authentication headers from client.
but there is no such code in current sources. so the requests from MCP client fail always.

### Example Code

```Python
# follow the instructions in https://github.com/jlowin/fastmcp/blob/main/docs/integrations/openapi.mdx#authentication

import httpx
from fastmcp import FastMCP

# Bearer token authentication
api_client = httpx.AsyncClient(
    base_url="https://api.example.com",
    headers={"Authorization": "Bearer YOUR_TOKEN"}
)

# Create MCP server with authenticated client
mcp = FastMCP.from_openapi(
    openapi_spec=spec, 
    client=api_client,
)

mcp.run(...)

# then send request with MCP inspector, etc.
```

### Version Information

```Text
FastMCP version:                                       2.10.6
MCP version:                                           1.12.1
Python version:                                       3.12.11

current main branch also has the same issue, d19166d4bf35fbbf3d32e7030959cbf8b488b51d
```

### Additional Context

requires FASTMCP_EXPERIMENTAL_ENABLE_NEW_OPENAPI_PARSER=true


Discussion:

Patch:
diff --git a/src/fastmcp/experimental/server/openapi/components.py b/src/fastmcp/experimental/server/openapi/components.py
index f8a241330..f7720746b 100644
--- a/src/fastmcp/experimental/server/openapi/components.py
+++ b/src/fastmcp/experimental/server/openapi/components.py
@@ -68,6 +68,13 @@ async def run(self, arguments: dict[str, Any]) -> ToolResult:
                 else "http://localhost"
             )
 
+            # Get Headers from client
+            cli_headers = (
+                self._client.headers
+                if hasattr(self._client, "headers") and self._client.headers
+                else {}
+            )
+
             # Build the request using RequestDirector
             request = self._director.build(self._route, arguments, base_url)
 
@@ -82,6 +89,17 @@ async def run(self, arguments: dict[str, Any]) -> ToolResult:
                     for key, value in mcp_headers.items():
                         request.headers[key] = value
 
+            if cli_headers:
+                # Merge with existing headers, _client headers take precedence
+                if request.headers:
+                    request.headers.update(cli_headers)
+                else:
+                    # Create new headers from cli_headers
+                    for key, value in cli_headers.items():
+                        request.headers[key] = value
+            # print logger
+            logger.debug(f"run - sending request; headers: {request.headers}")
+
             # Execute the request
             # Note: httpx.AsyncClient.send() doesn't accept timeout parameter
             # The timeout should be configured on the client itself
@@ -210,6 +228,14 @@ async def read(self) -> str | bytes:
             headers = {}
             mcp_headers = get_http_headers()
             headers.update(mcp_headers)
+            # Get Headers from client
+            cli_headers = (
+                self._client.headers
+                if hasattr(self._client, "headers") and self._client.headers
+                else {}
+            )
+            # Merge with existing headers, _client headers take precedence
+            headers.update(cli_headers)
 
             response = await self._client.request(
                 method=self._route.method,


Test patch:
diff --git a/tests/experimental/server/openapi/test_comprehensive.py b/tests/experimental/server/openapi/test_comprehensive.py
index 187180252..c613cd0a0 100644
--- a/tests/experimental/server/openapi/test_comprehensive.py
+++ b/tests/experimental/server/openapi/test_comprehensive.py
@@ -468,6 +468,7 @@ async def test_request_building_and_execution(self, comprehensive_openapi_spec):
         # Create a mock client that tracks requests
         mock_client = Mock(spec=httpx.AsyncClient)
         mock_client.base_url = "https://api.example.com"
+        mock_client.headers = None
 
         # Mock successful response
         mock_response = Mock(spec=Response)
@@ -509,6 +510,7 @@ async def test_complex_request_with_body_and_parameters(
         """Test complex request with both parameters and body."""
         mock_client = Mock(spec=httpx.AsyncClient)
         mock_client.base_url = "https://api.example.com"
+        mock_client.headers = None
 
         mock_response = Mock(spec=Response)
         mock_response.status_code = 201
@@ -557,6 +559,7 @@ async def test_query_parameters(self, comprehensive_openapi_spec):
         """Test query parameter handling."""
         mock_client = Mock(spec=httpx.AsyncClient)
         mock_client.base_url = "https://api.example.com"
+        mock_client.headers = None
 
         mock_response = Mock(spec=Response)
         mock_response.status_code = 200
@@ -595,6 +598,7 @@ async def test_error_handling(self, comprehensive_openapi_spec):
         """Test error handling for HTTP errors."""
         mock_client = Mock(spec=httpx.AsyncClient)
         mock_client.base_url = "https://api.example.com"
+        mock_client.headers = None
 
         # Mock HTTP error response
         mock_response = Mock(spec=Response)