Skip to content

Add Streamable HTTP Transport to MCPServerHTTP #1905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

BrandonShar
Copy link
Contributor

@BrandonShar BrandonShar commented Jun 4, 2025

Unfortunately for #1716, it turns out the underlying library was not backwards compatible.

This re-implementation adds a param to allow the user to specify what type of server they're connecting to.

Note: I considered adding an auto option (and that still may be a good idea), but I thought i'd keep it simple for the first attempt at this.

Fixes #1632

Copy link
Contributor

hyperlint-ai bot commented Jun 4, 2025

PR Change Summary

Re-implemented Streamable HTTP Transport as an opt-in feature, allowing users to specify the server type when connecting.

  • Added support for Streamable HTTP transport in MCPServerHTTP
  • Updated documentation to reflect the new transport option
  • Modified example code to include transport parameter
  • Clarified usage instructions for defining the MCP server

Modified Files

  • docs/mcp/client.md

How can I customize these reviews?

Check out the Hyperlint AI Reviewer docs for more information on how to customize the review.

If you just want to ignore it on this PR, you can add the hyperlint-ignore label to the PR. Future changes won't trigger a Hyperlint review.

Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to add hyperlint-ignore to the PR to ignore the link check for this PR.

@@ -358,22 +357,38 @@ async def main():
For example, if `tool_prefix='foo'`, then a tool named `bar` will be registered as `foo_bar`
"""

transport: Literal['sse', 'streamable-http'] = 'sse'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These arguments match the underlying terms that FastMCP uses

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a chance, could you see if it would be easy to implement an 'auto' option that still defaults to SSE for backward compatibility, but switches to streamable-http when the path is /mcp?



@pytest.fixture
def mcp_server(request: pytest.FixtureRequest):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels complex, but I couldn't figure out a simpler way.

@@ -31,7 +36,6 @@
pytestmark = [
pytest.mark.skipif(not imports_successful(), reason='mcp and openai not installed'),
pytest.mark.anyio,
pytest.mark.vcr,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling this out, I think all VCR tests are marked individually, but something about this blocked not the initial connection but the subsequent calls within the server.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is correct. You need to use --record-mode=rewrite when running the first time for the first test.

@BrandonShar
Copy link
Contributor Author

I am confused about the test failure. Seems like a dependency issue maybe just in CI? I hate to say the tests pass on my machine, but... 😄

@@ -18,22 +18,19 @@ pip/uv-add "pydantic-ai-slim[mcp]"

PydanticAI comes with two ways to connect to MCP servers:

- [`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] which connects to an MCP server using the [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) transport
- [`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] which connects to an MCP server using the [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) or [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] which connects to an MCP server using the [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) or [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport
- [`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] which connects to an MCP server using the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) or older [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) transport

- [`MCPServerStdio`][pydantic_ai.mcp.MCPServerStdio] which runs the server as a subprocess and connects to it using the [stdio](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) transport

Examples of both are shown below; [mcp-run-python](run-python.md) is used as the MCP server in both examples.

### SSE Client
### SSE or Streamable HTTP Client
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### SSE or Streamable HTTP Client
### HTTP Client


[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] connects over HTTP using the [HTTP + Server Sent Events transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) to a server.
[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] connects over HTTP using the [HTTP + Server Sent Events transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) or the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) to a server.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] connects over HTTP using the [HTTP + Server Sent Events transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) or the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) to a server.
[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] connects to a server over HTTP using [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) or [HTTP + Server Sent Events](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse).

[Streamable HTTP](https://github.com/modelcontextprotocol/specification/pull/206) currently in development.

Before creating the SSE client, we need to run the server (docs [here](run-python.md)):
Before creating the client, we need to run the server (docs [here](run-python.md)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Before creating the client, we need to run the server (docs [here](run-python.md)):
For backwards compatibility, the default transport is SSE. If your server instead uses Streamable HTTP, you have to specify the `transport='streamable-http'` option when creating `MCPServerHTTP`.
Before creating the client, we need to run the server (docs [here](run-python.md)):

@@ -84,6 +81,10 @@ Will display as follows:

![Logfire run python code](../img/logfire-run-python-code.png)

#### Transport

`MCPServerHTTP` supports both SSE (`sse`) and Streamable HTTP (`streamable-http`) servers which can be specified via the `transport` parameter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop this section as I suggested mentioning the transport arg above.

It'd be nice if we could also have a Streamable HTTP example!

@pytest.fixture
def mcp_server(request: pytest.FixtureRequest):
proc = multiprocessing.Process(target=request.param, daemon=True)
print('Staring streamable http server process on port port')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print('Staring streamable http server process on port port')

try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('localhost', 8000))
print('MCP server started.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print('MCP server started.')



def run_streamable_http_server() -> None:
"""Run the SSE MCP Server"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Run the SSE MCP Server"""
"""Run the Streamable HTTP MCP Server"""

@@ -42,6 +46,71 @@ def agent(openai_api_key: str):
return Agent(model, mcp_servers=[server])


def run_sse_server() -> None:
"""Run the Streamable HTTP MCP server."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Run the Streamable HTTP MCP server."""
"""Run the SSE MCP server."""

while attempt < max_attempts:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('localhost', 8000))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a randomly assigned port here, we can't be sure 8000 will be available

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like FastMCP has its own host and port settings, and the run method runs the entire server. Do we need to run it in a separate process at all? Could we use a thread? It looks like FastMCP.run calls anyio.run(self.run_sse_async). We could likely do something ourselves to run the run_sse_async async method in a thread.

@DouweM
Copy link
Contributor

DouweM commented Jun 10, 2025

@BrandonShar Thanks for submitting this! Finally got around to reviewing it :)

It looks like you're trying to use a newer version of mcp, could you please update pyproject.toml as well? That may resolve the CI issue.

@@ -31,7 +36,6 @@
pytestmark = [
pytest.mark.skipif(not imports_successful(), reason='mcp and openai not installed'),
pytest.mark.anyio,
pytest.mark.vcr,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is correct. You need to use --record-mode=rewrite when running the first time for the first test.

@Kludex
Copy link
Member

Kludex commented Jun 12, 2025

I'm not sure which PR should go though.

@DouweM DouweM changed the title Add back Streamable HTTP Transport as an opt-in Add Streamable HTTP Transport to MCPServerHTTP Jun 12, 2025
@Kludex
Copy link
Member

Kludex commented Jun 13, 2025

I think we'll be adding a lot more stuff on the streaming implementation, so I think it's preferable to have those 2 classes.

@Kludex
Copy link
Member

Kludex commented Jun 13, 2025

But thanks for the PR. 🙏

@Kludex Kludex closed this Jun 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Streamable HTTP Implementaion
3 participants