feat(mcp): bot as MCP client over SSE — minimal stdlib client + 2 LLM tools#12
feat(mcp): bot as MCP client over SSE — minimal stdlib client + 2 LLM tools#12Smilez1985 wants to merge 1 commit into
Conversation
Lets the bot consume tools advertised by any external MCP server
that speaks the SSE transport, without dragging in the official
`mcp[cli]` Python package (it pulls `cryptography`, `pydantic-settings`,
`starlette`, `uvicorn`, `pyjwt`, `httpx-sse`, `sse-starlette`,
`python-multipart` — non-trivial RAM hit on a 512 MB Pi Zero 2W).
What's added
- src/llm/rag_mcp_client.py — hand-rolled MCP-over-SSE client,
~250 LoC, stdlib-only + `requests` (already in the venv via
litellm). Background thread reads the SSE stream and dispatches
JSON-RPC responses by id; sync `connect()` / `initialize()` /
`list_tools()` / `call_tool(name, args)` API. A module-level
`get_client()` returns a lazy singleton so multiple tool calls
share one SSE connection.
- Two new LLM tools wired into TOOL_MAP:
`mcp_list_tools()` — return advertised tool names + descriptions
`mcp_call_tool(name, arguments)` — invoke by name; arguments is
a JSON object passed as a string
(the LLM emits one).
Both gracefully no-op when the MCP path isn't configured, returning
a clear hint instead of raising.
Activation
- Env var `RAG_TRANSPORT=rest|mcp` (default `rest`). When `mcp`,
`RAG_API_URL` is interpreted as the MCP-SSE base URL (e.g.
`http://your-rag-host:8766`).
- Reuses `RAG_API_KEY` for optional Bearer auth.
- No new top-level dependencies.
Tested against rag-core's MCP-SSE endpoint
(advertised tools: rag_search, rag_persist, rag_status,
rag_list_collections, rag_recall_session, rag_session_announce,
rag_session_forget). `tools/list` returns the catalog; `tools/call`
dispatches and returns the rendered text content correctly.
Out of scope (separate follow-ups)
- Auto-registration of advertised MCP tools as first-class TOOL_MAP
entries (each with its own typed JSON schema). Today the LLM has
to look at `mcp_list_tools` then construct an `mcp_call_tool` call
itself; auto-registration would let it call them as if native.
- Multi-server support (today: single MCP server via RAG_API_URL).
- Async transport / WebSocket fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Quick context on this integration — wanted to be upfront about where it comes from. The RAG backend I built against is a project I call rag-core: a self-hosted, project-scoped retrieval memory layer with a REST API and an MCP-SSE gateway (Qdrant-backed, multi-collection, frontmatter-aware chunking, designed for AI agents). The repo is currently private while I finish a handful of pre-public-readiness items, but the plan is to open it up before long. This PR series (#10, #12, #13, #14) is intentionally written against a generic contract — any RAG backend that speaks the documented REST shape, and any MCP-SSE server with Reason for sending this upstream rather than keeping it on my fork: I use OpenClawGotchi as one of my main development drivers, and the RAG integration has become part of how I work with it day-to-day. Carrying a long-lived branch and re-rebasing on every upstream pull is a fair amount of churn — having it on No pressure on accepting; happy to iterate to whatever shape fits best, or to wait if you'd rather see the other end of the wire first. Once rag-core is public I'll ping back here — at that point you'd be welcome to clone the repo and stand up your own instance if you want to see how it pairs with the bot from the other side. (Just to be clear: this isn't an invite to my running instance — it's an invite to the source, so you can run your own.) |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
Summary
Lets openclawgotchi consume tools advertised by any external MCP server (SSE transport) — the agent can call them via two new generic LLM tools, no per-server adapters required.
The implementation is a hand-rolled minimal MCP-over-SSE client (~250 LoC, stdlib +
requests) instead of the officialmcp[cli]Python package. Rationale: the official package pulls incryptography(~4.7 MB),pydantic-settings,starlette,uvicorn,pyjwt,httpx-sse,sse-starlette,python-multipart— non-trivial RAM hit on a 512 MB Pi Zero 2W. The hand-rolled client speaks just the subset needed:initialize,tools/list,tools/call.This is a follow-up to PR #10 (RAG REST integration) — same
RAG_API_URLenv, with a newRAG_TRANSPORTswitch that picks REST or MCP.What's added
src/llm/rag_mcp_client.pyMCPSSEClientclass with.connect()/.initialize()/.list_tools()/.call_tool(name, args).threading.Eventper request).get_client()returns a connected, initialized singleton on first call; subsequent calls reuse the existing connection.extract_text_content(result)flattens an MCPtools/callresult to a printable string for the LLM.Two new LLM tools (
litellm_connector.py)mcp_list_tools()— discovery. Returns a compact listname: description (first line).mcp_call_tool(name, arguments)— invoke a tool by name.argumentsis a JSON object encoded as a string (the LLM emits one).Both no-op gracefully (informative return string, no raise) when
RAG_TRANSPORT != "mcp", whenRAG_API_URLis empty, or when the server is unreachable. Existing installs are unaffected.Config
RAG_TRANSPORT=rest|mcp(defaultrest).mcp,RAG_API_URLis interpreted as the MCP-SSE base URL (e.g.http://your-rag-host:8766).RAG_API_KEYfor optional Bearer auth.requestsalready pulled in by litellm.Test plan
RAG_TRANSPORT(or=rest):mcp_list_tools()returns "MCP transport not enabled". Bot otherwise unaffected.RAG_TRANSPORT=mcp+RAG_API_URL=<sse-base>:mcp_list_tools()lists the server's tools.mcp_call_tool("rag_search", '{"query":"hello","top_k":2}')(or whatever is advertised) returns a result.ss -tn— only one outbound connection to port 8766).Verified locally against an MCP-SSE server:
tools/listreturns the advertised catalogue,tools/callround-trips JSON-RPC and the bot renders the text content correctly.Notes for reviewers
mcp[cli]dependency is the explicit design choice. If you'd prefer using the official package for protocol-level correctness as it evolves, this client is easy to swap. The contract surface is small (4 methods, JSON in/out).mcp_list_tools→mcp_call_toolflow; auto-registration would let it call them as if they were native bot tools, with their own typed JSON schemas.RAG_API_URLslot). Multi-server support (MCP_SERVERS=alias=url,alias=url) is a roadmap item — straightforward extension once anyone needs it./modelpicker, fix: respect BOT_LANGUAGE end-to-end (prompt + display + onboarding loop) #6 BOT_LANGUAGE end-to-end, feat(update): /update command + auto_update.sh with backup & rollback #7 self-update, feat(battery): UPS HAT (C) monitoring + /battery command #8 UPS HAT battery, feat(display): auto-detect Waveshare 2.13in V4 mono vs B (3-color) variant #9 display variant detect, feat(rag): rag-core REST integration — /rag command + LLM tools + persist API #10 RAG REST integration, fix(auto_mood): don't duplicate header metrics in footer status text #11 auto_mood metric duplication.🤖 Generated with Claude Code