Skip to content

Move MCP tool integration from contribs into core mellea #1032

@ajbozarth

Description

@ajbozarth

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

  1. Move the mcp_tools package into mellea, folding its dependencies (mcp, httpx) into the existing mellea[tool] extra alongside the other tool-casting deps.
  2. 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.
  3. 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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions