feat: add MCP tool integration#1042
feat: add MCP tool integration#1042ajbozarth wants to merge 1 commit intogenerative-computing:mainfrom
Conversation
Adds mellea/stdlib/tools/mcp.py, a new module that bridges MCP server tools into Mellea's native tool-calling system. MCPToolSpec.as_mellea_tool() wraps each discovered tool as a MelleaTool ready to pass via ModelOption.TOOLS or to react(). Public API: - discover_mcp_tools(connection) -> list[MCPToolSpec] - MCPToolSpec.as_mellea_tool() -> MelleaTool - http_connection, sse_connection, stdio_connection helpers Each tool invocation opens a short-lived MCP session and runs on mellea's shared background event loop via _run_async_in_thread, so callers do not need to manage session lifetime. Verified end-to-end against the hosted GitHub MCP server. Adds mcp>=1.27.0 and httpx>=0.27 to the mellea[tools] extra. httpx is already transitive via smolagents and langchain-core. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
cc @akihikokuroda @psschwei @planetf1 @jakelorocco as reviewers from the original contribs PR |
planetf1
left a comment
There was a problem hiding this comment.
All the feedback from the contribs review is addressed — timeouts are now configurable with sensible defaults, all five content types are handled, and as_mellea_tool() is correctly typed. A few nits inline, nothing blocking.
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def _open_session(connection: dict[str, Any]): |
There was a problem hiding this comment.
Missing return type. Should be -> AsyncGenerator[ClientSession, None] (importable from collections.abc). mypy will flag it.
| else: | ||
| mime = item.mimeType or "unknown" | ||
| parts.append(f"[binary: {mime}]") | ||
| except Exception: |
There was a problem hiding this comment.
The exception disappears silently here. Even a logger.debug(..., exc_info=True) would help — right now you can't tell from logs whether read_resource blew up due to a network error, a permissions issue, or something else. The fallback string is fine, just don't eat the cause.
| assert s.name == "search" | ||
| assert s.description == "Search things" | ||
| assert s.input_schema == schema | ||
| assert s._connection == connection |
There was a problem hiding this comment.
Minor: testing a private attribute directly. If the internal representation changes (e.g. connection becomes a dataclass) this breaks without the contract changing. Lower-risk alternative: verify the connection is actually used — mock the transport and assert the right URL is called. Low priority.
Tool PR
Description
Adds
mellea/stdlib/tools/mcp.py, bridging MCPserver tools into Mellea's native tool-calling system. Any MCP server's tools
can now be discovered and used inside a Mellea agent.
Public API:
discover_mcp_tools(connection) -> list[MCPToolSpec]MCPToolSpec.as_mellea_tool() -> MelleaToolhttp_connection,sse_connection,stdio_connectiontransport helpersRuntime: each tool invocation opens a short-lived MCP session and runs on
mellea's shared background event loop via
_run_async_in_thread. Verifiedend-to-end against the hosted GitHub MCP server.
Deps:
mcp>=1.27.0andhttpx>=0.27added to themellea[tools]extra.httpxis already transitive viasmolagentsandlangchain-core.Supersedes
mellea-contribs#54,which will be closed rather than merged. Jake suggested during review
that this belongs in core.
Issue #1032 point 2 (generalizing the async-to-sync wrapper onto
MelleaTool.from_callable) is handled by #1041.Implementation Checklist
Protocol Compliance
MelleaToolinstances via the constructor; no backend changes required.Integration
mellea/stdlib/tools/__init__.pyor, if you are adding a library of components, from your sub-modulemellea.stdlib.tools.mcpsubmodule. Not added to__init__.pybecause the module importsmcpandhttpxat top level and should only be loaded when thetoolsextra is installed.Testing
tests/stdlib/tools/Attribution