Context
The mellea-mcp package was recently added to mellea-contribs in generative-computing/mellea-contribs#54. It bridges MCP server tools into Mellea's native tool-calling system so any MCP server's tools can be used inside a Mellea agent.
During review, @jakelorocco suggested this is a good candidate to live in mellea itself, and that the wrapping function should sit on MelleaTool to follow how the other tool casting works. This issue tracks that proposal.
What the package does
discover_mcp_tools(connection) -> list[MCPToolSpec] — inspect a server's tools without instantiating them
MCPToolSpec.as_mellea_tool() -> MelleaTool — create a callable MelleaTool from a spec
- Connection helpers:
http_connection, sse_connection, stdio_connection
- Session-per-call: each tool invocation opens its own short-lived MCP session
- Sync wrapper (ThreadPoolExecutor +
asyncio.run) keeps it compatible with Mellea's current event-loop model
Proposal
- Move the
mcp_tools package into mellea, folding its dependencies (mcp, httpx) into the existing mellea[tool] extra alongside the other tool-casting deps.
- Lift the async-to-sync wrapping currently in
mellea-mcp into MelleaTool itself, so any developer can register an async function as a tool — not just MCP integrations. This mirrors how other tool casting already lives on MelleaTool. The wrapper should use mellea's existing _run_async_in_thread helper (mellea/helpers/event_loop_helper.py) rather than mellea-mcp's per-call ThreadPoolExecutor + asyncio.run(), so we reuse the one shared background loop.
- As a separate follow-up, investigate native async tool-call support in mellea so the sync wrapper can eventually go away.
Why it could live in core
- MCP is rapidly becoming a default way to expose tools to agents; first-class support saves every user the extra install step.
- The implementation only uses mellea's public API (
MelleaTool constructor, ModelOption.TOOLS), so the move is mostly packaging.
- Generalizing the sync wrapper onto
MelleaTool benefits every async tool author, not just MCP users.
Dependency footprint
Adding mcp>=1.27.0 and httpx>=0.27 to mellea[tool]. httpx is already transitive via smolagents and langchain-core, so it's effectively free. mcp is pure Python and its tree is small relative to what tools already installs — net increase is a few MB of wheels. The one thing worth flagging is that mcp bundles starlette and uvicorn as runtime deps even in pure-client usage.
Event-loop handling
The async-to-sync wrapper on MelleaTool should use mellea's existing _run_async_in_thread (mellea/helpers/event_loop_helper.py) instead of the per-call ThreadPoolExecutor + asyncio.run() used today in mellea-mcp. That's the established pattern for "async underneath, sync at the edges" in mellea: one process-wide daemon thread running a persistent event loop.
Caveat to verify during the move: the session-per-call design in mellea-mcp was chosen to avoid a cross-event-loop deadlock we hit earlier with _EventLoopHandler. Migrating to the shared loop needs a check that the MCP session context manager still works cleanly.
Why this originally landed in contribs
Two reasons at the time:
- I'd been told MCP support wasn't a short-term priority for core, and this was built in spare time — contribs was the natural home for that.
- The initial plan was to build on top of
langchain's MCP adapter, which would have fit in alongside other langchain-based contribs. During development it became clear that using the mcp SDK directly produced a cleaner, smaller integration, but by then the package was already shaped as a contrib.
With the langchain dependency off the table and Jake's MelleaTool framing in mind, it's worth revisiting whether core is the better home now.
Context
The
mellea-mcppackage was recently added tomellea-contribsin generative-computing/mellea-contribs#54. It bridges MCP server tools into Mellea's native tool-calling system so any MCP server's tools can be used inside a Mellea agent.During review, @jakelorocco suggested this is a good candidate to live in mellea itself, and that the wrapping function should sit on
MelleaToolto follow how the other tool casting works. This issue tracks that proposal.What the package does
discover_mcp_tools(connection) -> list[MCPToolSpec]— inspect a server's tools without instantiating themMCPToolSpec.as_mellea_tool() -> MelleaTool— create a callableMelleaToolfrom a spechttp_connection,sse_connection,stdio_connectionasyncio.run) keeps it compatible with Mellea's current event-loop modelProposal
mcp_toolspackage into mellea, folding its dependencies (mcp,httpx) into the existingmellea[tool]extra alongside the other tool-casting deps.mellea-mcpintoMelleaToolitself, so any developer can register an async function as a tool — not just MCP integrations. This mirrors how other tool casting already lives onMelleaTool. The wrapper should use mellea's existing_run_async_in_threadhelper (mellea/helpers/event_loop_helper.py) rather thanmellea-mcp's per-callThreadPoolExecutor + asyncio.run(), so we reuse the one shared background loop.Why it could live in core
MelleaToolconstructor,ModelOption.TOOLS), so the move is mostly packaging.MelleaToolbenefits every async tool author, not just MCP users.Dependency footprint
Adding
mcp>=1.27.0andhttpx>=0.27tomellea[tool].httpxis already transitive viasmolagentsandlangchain-core, so it's effectively free.mcpis pure Python and its tree is small relative to whattoolsalready installs — net increase is a few MB of wheels. The one thing worth flagging is thatmcpbundlesstarletteanduvicornas runtime deps even in pure-client usage.Event-loop handling
The async-to-sync wrapper on
MelleaToolshould use mellea's existing_run_async_in_thread(mellea/helpers/event_loop_helper.py) instead of the per-callThreadPoolExecutor + asyncio.run()used today inmellea-mcp. That's the established pattern for "async underneath, sync at the edges" in mellea: one process-wide daemon thread running a persistent event loop.Caveat to verify during the move: the session-per-call design in
mellea-mcpwas chosen to avoid a cross-event-loop deadlock we hit earlier with_EventLoopHandler. Migrating to the shared loop needs a check that the MCP session context manager still works cleanly.Why this originally landed in contribs
Two reasons at the time:
langchain's MCP adapter, which would have fit in alongside other langchain-based contribs. During development it became clear that using themcpSDK directly produced a cleaner, smaller integration, but by then the package was already shaped as a contrib.With the langchain dependency off the table and Jake's
MelleaToolframing in mind, it's worth revisiting whether core is the better home now.