Skip to content

Mutable request headers on the client side #1320

@hopeful0

Description

@hopeful0

Enhancement Description

Allowing flexible customization of request headers in client sessions, as proposed in #1233, would unlock notable use-case advantages. The client-side mutable request headers, accessible via the server's get_http_headers method, enable rapid sharing of temporary state information through request headers.

Currently, implementing this functionality requires users to develop additional server-side interfaces or tools to modify the state, followed by corresponding client-side calls—an approach that introduces significant unnecessary overhead.

Another similar but more refined method is the one proposed in #1315 to add metadata to each request.

Use Case

No response

Proposed Implementation

The mutable request headers feature can be implemented through the httpx_client_factory parameter of StreamableHttpTransport, as demonstrated in the example I provided in #1233.

# client -- Send Multiple Requests with Different Tokens:

import asyncio

import httpx

from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport


async def test_multiple_requests():
    httpx_client = httpx.AsyncClient()

    def httpx_client_factory(
        headers: dict[str, str],
        timeout: httpx.Timeout | None = None,
        auth: httpx.Auth | None = None,
    ):
        httpx_client.headers = headers
        if timeout:
            httpx_client.timeout = timeout
        if auth:
            httpx_client.auth = auth
        return httpx_client

    async with Client(
        transport=StreamableHttpTransport(
            "http://127.0.0.1:3000/mcp/",
            httpx_client_factory=httpx_client_factory,
        )
    ) as client:
        httpx_client.headers["x-forwarded-access-token"] = "TOKEN_1"
        # Request 1
        result1 = await client.call_tool("test_token", {})
        print(f"Request 1 result: {result1.content[0].text}")

        # the subsequent request within the same session, but the token should be updated by the reverse proxy
        httpx_client.headers["x-forwarded-access-token"] = "TOKEN_2"
        result2 = await client.call_tool("test_token", {})
        print(f"Request 2 result: {result2.content[0].text}")


asyncio.run(test_multiple_requests())

It's worth discussing how we should implement this feature in practice.

While the first implementation option would create a stateful SHTTP transport that preserves the HTTP client and exposes headers, this design would fundamentally compromise the transport's reusability across multiple clients.

Alternatively, we could maintain the HTTP client in the client session and expose mutable headers through either the Client object or ClientSession (SHTTP transport only). However, implementing this would necessitate moderate refactoring across SHTTPTransport, ClientSession, and potentially the Client class - I'm uncertain about the appropriate scope of changes that would gain consensus.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions