Skip to content

MCP supports Streamable HTTP.#286

Merged
jundot merged 1 commit intojundot:mainfrom
tianfeng98:feature/tf98
Mar 29, 2026
Merged

MCP supports Streamable HTTP.#286
jundot merged 1 commit intojundot:mainfrom
tianfeng98:feature/tf98

Conversation

@tianfeng98
Copy link
Copy Markdown
Contributor

For #285

Copy link
Copy Markdown
Owner

@jundot jundot left a comment

Choose a reason for hiding this comment

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

Thanks for adding streamable HTTP support! Found a few things to address before merging.

Resource leak in disconnect()

_connect_streamable_http() creates self._http_client (httpx.AsyncClient), but disconnect() never cleans it up. Needs something like:

if hasattr(self, "_http_client") and self._http_client:
    await self._http_client.__aexit__(None, None, None)
    self._http_client = None

Also, if the streamable_http or session init fails midway, already-opened resources from earlier steps won't get cleaned up either. A try/except with cleanup would help here.

Please remove uv.lock

This repo doesn't track uv.lock — looks like it got included by accident.

Minor things

  • getattr(self.config, "headers", {}) can be self.config.headers or {} since headers is now a field on MCPServerConfig
  • The raw_result type branching (isinstance(raw_result, (list, tuple))) differs from how stdio/SSE handle it (direct tuple unpacking). Matching the existing pattern would be more consistent
  • A few formatting-only changes (quote styles, blank lines) are mixed into the feature commit — separating those would make the diff cleaner
  • Could use a couple more tests: _http_client cleanup, error scenarios, headers field validation

@jundot
Copy link
Copy Markdown
Owner

jundot commented Mar 18, 2026

I left a review above. Please address those items and i'll take another look. Closing #285 for now since it's tracked by this PR.

@tianfeng98
Copy link
Copy Markdown
Contributor Author

Thanks for adding streamable HTTP support! Found a few things to address before merging.

Resource leak in disconnect()

_connect_streamable_http() creates self._http_client (httpx.AsyncClient), but disconnect() never cleans it up. Needs something like:

if hasattr(self, "_http_client") and self._http_client:
    await self._http_client.__aexit__(None, None, None)
    self._http_client = None

Also, if the streamable_http or session init fails midway, already-opened resources from earlier steps won't get cleaned up either. A try/except with cleanup would help here.

Please remove uv.lock

This repo doesn't track uv.lock — looks like it got included by accident.

Minor things

  • getattr(self.config, "headers", {}) can be self.config.headers or {} since headers is now a field on MCPServerConfig
  • The raw_result type branching (isinstance(raw_result, (list, tuple))) differs from how stdio/SSE handle it (direct tuple unpacking). Matching the existing pattern would be more consistent
  • A few formatting-only changes (quote styles, blank lines) are mixed into the feature commit — separating those would make the diff cleaner
  • Could use a couple more tests: _http_client cleanup, error scenarios, headers field validation

Thank you for your review. I will follow up on this matter and take time to address it. It will likely take about a week.

@jundot jundot force-pushed the main branch 7 times, most recently from f6faf2f to c2beead Compare March 21, 2026 05:58
@tianfeng98
Copy link
Copy Markdown
Contributor Author

Hi, @jundot . I have revised the code submission, retaining only the necessary parts and test code, and it has passed the pytest test. Please review it again.

@jundot jundot force-pushed the main branch 9 times, most recently from bef2aeb to 86720d8 Compare March 23, 2026 19:49
@rayone
Copy link
Copy Markdown
Contributor

rayone commented Mar 25, 2026

Reviewed the latest revision (63ff98a). The implementation looks solid — jundot's original feedback has been addressed. Two small things still worth fixing before merge:

1. Resource leak on partial connect failure

In _connect_streamable_http, if self._session.__aenter__() raises after _http_client and _streamable_http_client have already been entered, those resources will leak. The outer connect() catches the exception but doesn't call disconnect() to clean up. A try/except inside _connect_streamable_http would close this:

async def _connect_streamable_http(self):
    try:
        from mcp import ClientSession
        from mcp.client.streamable_http import streamable_http_client
        import httpx
    except ImportError:
        raise ImportError("MCP SDK required for MCP support. Install with: pip install mcp")

    headers = self.config.headers or {}
    self._http_client = httpx.AsyncClient(headers=headers)
    await self._http_client.__aenter__()

    try:
        self._streamable_http_client = streamable_http_client(
            url=self.config.url, http_client=self._http_client
        )
        self._read, self._write, _ = await self._streamable_http_client.__aenter__()
        self._session = ClientSession(self._read, self._write)
        await self._session.__aenter__()
    except Exception:
        # Clean up already-opened resources before re-raising
        if hasattr(self, '_streamable_http_client') and self._streamable_http_client:
            await self._streamable_http_client.__aexit__(None, None, None)
            self._streamable_http_client = None
        await self._http_client.__aexit__(None, None, None)
        self._http_client = None
        raise

2. Typo in error message

raise ValueError(f"MCP server '{self.name}': streamable-http transport requires 'url''")
#                                                                                       ^^ extra quote

Should be 'url' not 'url''.


Everything else looks good. The disconnect cleanup, headers or {}, and test coverage are all solid.

Copy link
Copy Markdown
Owner

@jundot jundot left a comment

Choose a reason for hiding this comment

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

lgtm

@jundot
Copy link
Copy Markdown
Owner

jundot commented Mar 29, 2026

Looks good, merging this now. Thanks for the contribution @tianfeng98!

There are two small things i'll fix in a follow-up commit:

  1. Extra quote in the error message ('url'''url')
  2. Resource leak on partial connect failure in _connect_streamable_http — if session init fails after _http_client is already entered, it won't get cleaned up. Same gap exists in _connect_sse and _connect_stdio too so i'll fix all three while i'm at it.

@jundot jundot merged commit 7431a49 into jundot:main Mar 29, 2026
@jundot jundot mentioned this pull request Mar 29, 2026
@tianfeng98 tianfeng98 deleted the feature/tf98 branch March 29, 2026 08:50
@jundot
Copy link
Copy Markdown
Owner

jundot commented Mar 29, 2026

@tianfeng98 v0.3.0rc1 is out with your MCP Streamable HTTP included. https://github.com/jundot/omlx/releases/tag/v0.3.0rc1 — if you get a chance, please give it a test and let me know if anything looks off. thanks!

AlexTzk pushed a commit to AlexTzk/omlx that referenced this pull request Mar 29, 2026
@tianfeng98
Copy link
Copy Markdown
Contributor Author

Reviewed the latest revision (63ff98a). The implementation looks solid — jundot's original feedback has been addressed. Two small things still worth fixing before merge:

1. Resource leak on partial connect failure

In _connect_streamable_http, if self._session.__aenter__() raises after _http_client and _streamable_http_client have already been entered, those resources will leak. The outer connect() catches the exception but doesn't call disconnect() to clean up. A try/except inside _connect_streamable_http would close this:

async def _connect_streamable_http(self):
    try:
        from mcp import ClientSession
        from mcp.client.streamable_http import streamable_http_client
        import httpx
    except ImportError:
        raise ImportError("MCP SDK required for MCP support. Install with: pip install mcp")

    headers = self.config.headers or {}
    self._http_client = httpx.AsyncClient(headers=headers)
    await self._http_client.__aenter__()

    try:
        self._streamable_http_client = streamable_http_client(
            url=self.config.url, http_client=self._http_client
        )
        self._read, self._write, _ = await self._streamable_http_client.__aenter__()
        self._session = ClientSession(self._read, self._write)
        await self._session.__aenter__()
    except Exception:
        # Clean up already-opened resources before re-raising
        if hasattr(self, '_streamable_http_client') and self._streamable_http_client:
            await self._streamable_http_client.__aexit__(None, None, None)
            self._streamable_http_client = None
        await self._http_client.__aexit__(None, None, None)
        self._http_client = None
        raise

2. Typo in error message

raise ValueError(f"MCP server '{self.name}': streamable-http transport requires 'url''")
#                                                                                       ^^ extra quote

Should be 'url' not 'url''.

Everything else looks good. The disconnect cleanup, headers or {}, and test coverage are all solid.

Thank you very much for your suggestion. These issues were indeed due to my negligence, and I will exercise caution in subsequent submissions.

@tianfeng98
Copy link
Copy Markdown
Contributor Author

@tianfeng98 v0.3.0rc1 is out with your MCP Streamable HTTP included. https://github.com/jundot/omlx/releases/tag/v0.3.0rc1 — if you get a chance, please give it a test and let me know if anything looks off. thanks!

@jundot Thank you for accepting it. It seems to be fine.
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants