Skip to content

Commit 6451a6d

Browse files
authored
feat: add MCP tool integration (#1042)
* feat: add MCP tool integration 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> * fix: address review nits on MCP tool integration - Add return type annotation to `_open_session`. - Log swallowed exceptions in the `ResourceLink` fallback path at debug level instead of discarding them silently. - Drop redundant `_connection` private-attribute assertion in tests; wiring is already covered by the `_open_session` mocks used throughout. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com> --------- Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
1 parent 85b8e95 commit 6451a6d

7 files changed

Lines changed: 933 additions & 13 deletions

File tree

docs/docs/how-to/tools-and-agents.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,46 @@ gets generated (see examples above).
275275
> **Warning:** `local_code_interpreter` executes Python code in the current process.
276276
> Do not use it in production contexts without sandboxing.
277277
278+
## MCP tools
279+
280+
Mellea can consume tools from any [MCP](https://modelcontextprotocol.io/) server
281+
and drop them into an agent loop. Install with `pip install 'mellea[tools]'`.
282+
283+
The workflow is two steps: discover what the server offers, then instantiate the
284+
tools you want.
285+
286+
```python
287+
# Requires: mellea[tools]
288+
# Returns: list[MelleaTool]
289+
from mellea.stdlib.tools.mcp import discover_mcp_tools, http_connection
290+
291+
connection = http_connection("https://api.example.com/mcp/", api_key="...")
292+
293+
specs = await discover_mcp_tools(connection)
294+
tools = [s.as_mellea_tool() for s in specs if s.name in {"search", "fetch"}]
295+
```
296+
297+
`http_connection`, `sse_connection`, and `stdio_connection` build the transport
298+
config. Each tool invocation opens a short-lived session, so callers do not need
299+
to manage the connection lifetime.
300+
301+
Once built, MCP tools work like any other `MelleaTool`: pass them via
302+
`ModelOption.TOOLS` to `instruct()` or to `react()`:
303+
304+
```python
305+
# Requires: mellea[tools]
306+
# Returns: str
307+
result, _ = await react(
308+
goal="Find recent pull requests I authored.",
309+
context=ChatContext(),
310+
backend=m.backend,
311+
tools=tools,
312+
)
313+
```
314+
315+
See [`docs/examples/mcp/github_activity_summary.py`](https://github.com/generative-computing/mellea/blob/main/docs/examples/mcp/github_activity_summary.py)
316+
for a complete example against the hosted GitHub MCP server.
317+
278318
---
279319

280320
**See also:** [Tutorial 04: Making Agents Reliable](../tutorials/04-making-agents-reliable) | [Instruct, Validate, Repair](../concepts/instruct-validate-repair)

docs/examples/mcp/README.md

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
1-
# Write a poem MCP
2-
This is a simple example to show how to write a MCP tool
3-
with Mellea and instruct-validate-repair. Being able to
4-
speak the tool language allows you to integrate with
5-
Claude Desktop, Langflow, ...
1+
# MCP examples
62

7-
See code in [mcp_example.py](mcp_example.py)
3+
Two directions are covered here:
4+
5+
- **Expose Mellea as an MCP server**[`mcp_example.py`](mcp_example.py).
6+
Makes a Mellea instruct-validate-repair loop callable as an MCP tool from
7+
Claude Desktop, Langflow, or any MCP client.
8+
- **Consume MCP server tools from Mellea**
9+
[`github_activity_summary.py`](github_activity_summary.py). Discovers tools on
10+
a remote MCP server and drops them into a Mellea `react()` loop.
11+
12+
## Write a poem MCP (Mellea as server)
13+
14+
A simple example to show how to write a MCP tool with Mellea and
15+
instruct-validate-repair. Being able to speak the tool language lets you integrate
16+
with Claude Desktop, Langflow, and other MCP clients.
17+
18+
See code in [`mcp_example.py`](mcp_example.py).
19+
20+
### Running the poem server
21+
22+
Install the MCP SDK:
823

9-
## Run the example
10-
You need to install the mcp package:
1124
```bash
1225
uv pip install "mcp[cli]"
1326
```
1427

15-
and run the example in MCP debug UI:
28+
Run the example in the MCP debug UI:
29+
1630
```bash
1731
uv run mcp dev docs/examples/mcp/mcp_example.py
1832
```
1933

34+
### Use in Langflow
2035

21-
## Use in Langflow
22-
Follow this path (JSON) to use it in Langflow: [https://docs.langflow.org/mcp-client#mcp-stdio-mode](https://docs.langflow.org/mcp-client#mcp-stdio-mode)
23-
24-
The JSON to register your MCP tool is the following. Be sure to insert the absolute path to the directory containing the mcp_example.py file:
36+
Follow [this guide](https://docs.langflow.org/mcp-client#mcp-stdio-mode) to register
37+
the tool. Insert the absolute path to the directory containing `mcp_example.py`:
2538

2639
```json
2740
{
@@ -41,6 +54,36 @@ The JSON to register your MCP tool is the following. Be sure to insert the absol
4154
}
4255
```
4356

57+
## GitHub activity summary (Mellea as client)
58+
59+
Uses the hosted GitHub MCP server to summarize recent pull requests.
60+
Demonstrates the two-step workflow (discover tools, pick the ones you need,
61+
wrap as `MelleaTool`) and drives multi-turn tool use via `react()`.
62+
63+
See code in [`github_activity_summary.py`](github_activity_summary.py), and
64+
the [Tools and Agents how-to guide](../../docs/how-to/tools-and-agents) for
65+
the API overview.
66+
67+
### Running the activity summary
4468

69+
Install Mellea with tools support:
4570

71+
```bash
72+
pip install 'mellea[tools]'
73+
```
74+
75+
Set a GitHub token with `repo` and `read:user` scopes:
76+
77+
```bash
78+
export GITHUB_TOKEN=<your token>
79+
```
80+
81+
Run:
82+
83+
```bash
84+
uv run python docs/examples/mcp/github_activity_summary.py --days 14
85+
```
4686

87+
The script discovers every tool on the GitHub MCP server, filters down to
88+
`get_me` and `search_pull_requests`, then asks the model to summarize your
89+
pull-request activity over the specified window.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# pytest: skip_always
2+
"""Example: summarise recent GitHub activity using the GitHub MCP server.
3+
4+
Demonstrates the mellea MCP workflow:
5+
1. Discover all tools on the server with discover_mcp_tools()
6+
2. Pick only the ones needed by name
7+
3. Drive multi-turn tool use with mellea's react() loop
8+
9+
Prerequisites:
10+
pip install 'mellea[tools]'
11+
export GITHUB_TOKEN=<token with repo + read:user scopes>
12+
13+
Usage:
14+
uv run python docs/examples/mcp/github_activity_summary.py
15+
"""
16+
17+
import argparse
18+
import asyncio
19+
import os
20+
from datetime import UTC, datetime, timedelta
21+
22+
from mellea import start_session
23+
from mellea.backends import model_ids
24+
from mellea.core.base import AbstractMelleaTool
25+
from mellea.stdlib.context import ChatContext
26+
from mellea.stdlib.frameworks.react import react
27+
from mellea.stdlib.tools.mcp import discover_mcp_tools, http_connection
28+
29+
GITHUB_MCP_URL = "https://api.githubcopilot.com/mcp/"
30+
TOOLS_NEEDED = {"get_me", "search_pull_requests"}
31+
32+
33+
async def main(days: int) -> None:
34+
token = os.environ.get("GITHUB_TOKEN")
35+
if not token:
36+
raise SystemExit("GITHUB_TOKEN environment variable is required")
37+
38+
now = datetime.now(UTC)
39+
since = (now - timedelta(days=days)).strftime("%Y-%m-%d")
40+
today = now.strftime("%Y-%m-%d")
41+
42+
connection = http_connection(GITHUB_MCP_URL, api_key=token)
43+
m = start_session(model_id=model_ids.IBM_GRANITE_4_1_8B)
44+
45+
# --- Tool discovery ---
46+
specs = await discover_mcp_tools(connection)
47+
print(f"Discovered {len(specs)} tools on the GitHub MCP server")
48+
49+
# --- Tool selection ---
50+
relevant = [s for s in specs if s.name in TOOLS_NEEDED]
51+
print(f"Using {len(relevant)} tools: {[s.name for s in relevant]}")
52+
tools: list[AbstractMelleaTool] = [s.as_mellea_tool() for s in relevant]
53+
54+
# --- Agent loop ---
55+
result, _ = await react(
56+
goal=(
57+
f"Today is {today}. Find my GitHub username, then search for pull requests "
58+
f"I authored since {since} filtering by my username. "
59+
"List each pull request with its title, number, and repository."
60+
),
61+
context=ChatContext(),
62+
backend=m.backend,
63+
tools=tools,
64+
loop_budget=6,
65+
)
66+
67+
print("\n--- Activity Summary ---")
68+
print(result.value)
69+
70+
71+
if __name__ == "__main__":
72+
parser = argparse.ArgumentParser()
73+
parser.add_argument(
74+
"--days", type=int, default=14, help="How many days back to look"
75+
)
76+
args = parser.parse_args()
77+
asyncio.run(main(args.days))

0 commit comments

Comments
 (0)