Skip to content

feat: add MCP tool integration#1042

Open
ajbozarth wants to merge 1 commit intogenerative-computing:mainfrom
ajbozarth:feat/mcp-tools-core
Open

feat: add MCP tool integration#1042
ajbozarth wants to merge 1 commit intogenerative-computing:mainfrom
ajbozarth:feat/mcp-tools-core

Conversation

@ajbozarth
Copy link
Copy Markdown
Contributor

@ajbozarth ajbozarth commented May 8, 2026

Tool PR

Description

Adds mellea/stdlib/tools/mcp.py, bridging MCP
server 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() -> MelleaTool
  • http_connection, sse_connection, stdio_connection transport helpers

Runtime: each tool invocation opens a short-lived MCP session and runs on
mellea's shared background event loop via _run_async_in_thread. Verified
end-to-end against the hosted GitHub MCP server.

Deps: mcp>=1.27.0 and httpx>=0.27 added to the mellea[tools] extra.
httpx is already transitive via smolagents and langchain-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

  • Ensure compatibility with existing backends and providers
    • MCP tools are wrapped as standard MelleaTool instances via the constructor; no backend changes required.

Integration

  • Tool exported in mellea/stdlib/tools/__init__.py or, if you are adding a library of components, from your sub-module
    • Exported from the mellea.stdlib.tools.mcp submodule. Not added to __init__.py because the module imports mcp and httpx at top level and should only be loaded when the tools extra is installed.

Testing

  • Tests added to tests/stdlib/tools/
  • New code has 100% coverage
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

Attribution

  • AI coding assistants used

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>
@ajbozarth
Copy link
Copy Markdown
Contributor Author

cc @akihikokuroda @psschwei @planetf1 @jakelorocco as reviewers from the original contribs PR

Copy link
Copy Markdown
Contributor

@planetf1 planetf1 left a comment

Choose a reason for hiding this comment

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

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]):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move MCP tool integration from contribs into core mellea

2 participants