Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/mcp/client/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,17 @@ class RequestContext:
class StreamableHTTPTransport:
"""StreamableHTTP client transport implementation."""

def __init__(self, url: str) -> None:
def __init__(self, url: str, content_type: str = "application/json") -> None:
"""Initialize the StreamableHTTP transport.

Args:
url: The endpoint URL.
content_type: The Content-Type header value for POST requests.
Defaults to "application/json". Can be overridden to include
custom charsets (e.g. "application/json; charset=utf-8").
"""
self.url = url
self.content_type = content_type
self.session_id: str | None = None
self.protocol_version: str | None = None

Expand All @@ -90,7 +94,7 @@ def _prepare_headers(self) -> dict[str, str]:
"""
headers: dict[str, str] = {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"content-type": self.content_type,
}
# Add session headers if available
if self.session_id:
Expand Down Expand Up @@ -515,6 +519,7 @@ async def streamable_http_client(
*,
http_client: httpx.AsyncClient | None = None,
terminate_on_close: bool = True,
content_type: str = "application/json",
) -> AsyncGenerator[TransportStreams, None]:
"""Client transport for StreamableHTTP.

Expand All @@ -524,6 +529,9 @@ async def streamable_http_client(
client with recommended MCP timeouts will be created. To configure headers,
authentication, or other HTTP settings, create an httpx.AsyncClient and pass it here.
terminate_on_close: If True, send a DELETE request to terminate the session when the context exits.
content_type: The Content-Type header value for POST requests.
Defaults to "application/json". Can be overridden to include custom charsets
(e.g. "application/json; charset=utf-8").

Yields:
Tuple containing:
Expand All @@ -544,7 +552,7 @@ async def streamable_http_client(
# Create default client with recommended MCP timeouts
client = create_mcp_http_client()

transport = StreamableHTTPTransport(url)
transport = StreamableHTTPTransport(url, content_type=content_type)

async with anyio.create_task_group() as tg:
try:
Expand Down
35 changes: 35 additions & 0 deletions tests/client/test_http_content_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Tests for Content-Type override in streamable HTTP transport.

Verifies that the content_type parameter allows overriding the Content-Type header
to include custom charsets or other attributes.
"""

import pytest
from mcp.client.streamable_http import StreamableHTTPTransport


def test_streamable_http_transport_default_content_type() -> None:
"""Test that the default Content-Type is 'application/json'."""
transport = StreamableHTTPTransport("http://example.com/mcp")
headers = transport._prepare_headers()
assert headers["content-type"] == "application/json"


def test_streamable_http_transport_custom_content_type() -> None:
"""Test that a custom Content-Type with charset can be specified."""
transport = StreamableHTTPTransport(
"http://example.com/mcp",
content_type="application/json; charset=utf-8",
)
headers = transport._prepare_headers()
assert headers["content-type"] == "application/json; charset=utf-8"


def test_streamable_http_transport_content_type_preserved_in_headers() -> None:
"""Test that Content-Type is correctly placed in prepared headers."""
custom_type = "application/json; charset=utf-8; boundary=npm"
transport = StreamableHTTPTransport("http://example.com/mcp", content_type=custom_type)
headers = transport._prepare_headers()
# The accept header should also be present
assert "accept" in headers
assert headers["content-type"] == custom_type
Loading