pip install ragleap-integrations library. To install and run ragleap-core (AI employees, channels, Docker), see docs.ragleap.com; the project home is ragleap.com.ragleap-integrations
An MCP client that turns allowlisted tools on remote MCP servers into ragleap-tools Tool objects. Streamable HTTP only, owner-configured, with bounded network access — ragleap-integrations provides the Tool objects, not the execution loop.
Install
Nothing to configure at install time: servers and the allowlist are built in your code, never read from the environment.
pip install ragleap-integrations # or, with uv uv add ragleap-integrations
What this is (and isn't)
ragleap-integrations v0.1.0 is an MCP (Model Context Protocol) client over Streamable HTTP only. The owner configures servers and an exact server.tool allowlist; the model only supplies a tool's JSON arguments, never a URL, a token or a tool outside the allowlist. It does not own a tool-calling loop — that is ragleap-agents's job.
Not supported in this version: stdio (it launches a subprocess), the deprecated HTTP+SSE transport, sampling/elicitation/roots (a server asking for client input gets a clear “unsupported” result), resources, prompts, subscriptions and OAuth. Connectors, code execution and HTTP fetch are deliberately not included: each needs its own security design pass.
Quick start
from ragleap_integrations import McpConfig, McpServerConfig, make_mcp_tools
config = McpConfig(
servers=[McpServerConfig("deepwiki", "https://mcp.deepwiki.com/mcp")], # token="..." if a server needs one
allowed_tools=["deepwiki.read_wiki_structure"],
)
tools = make_mcp_tools(config) # discovers the allowlisted tools once (tools/list)
openai_tools = [t.to_openai_schema() for t in tools] # or t.to_gemini_schema()
result = tools[0].call(repoName="sqlite/sqlite")
print(result.success, result.result) # True {"text": "..."}
Tools are exposed as <server>__<tool> (characters outside letters, digits, _ and - become _, at most 64 characters). To keep server-controlled text out of the model's context entirely, supply the descriptions and schemas yourself:
from ragleap_integrations import McpToolSpec
spec = McpToolSpec(
server="deepwiki", name="read_wiki_structure",
description="List the documentation topics of a GitHub repository.",
parameters={"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]},
)
tools = make_mcp_tools(config, specs=[spec]) # nothing is sent over the network at setup
Full public API
McpServerConfig(name, url, token=None)url must be https to a public host (checked again, with DNS, on every call); token is an optional static bearer token, sent only to this URL.McpConfig(servers, allowed_tools, ...)server.tool allowlist and limits: max_result_chars 4000, max_description_chars 500, max_schema_chars 10000, total_timeout 30 s, op_timeout 10 s, max_response_bytes 256 KiB. Nothing is read from the environment.McpToolSpec(server, name, description, parameters)make_mcp_tools(config, specs=None, client=None)ragleap_tools.Tool per allowlisted tool. Discovers once (tools/list) unless specs are given. Raises McpConfigError or McpDiscoveryError at setup; tool handlers never raise.McpClient(config, transport=None)discover() returns a Discovery (tools plus the skipped ones with a reason); call_tool(server, tool, arguments, parameters) returns a ToolResult and never raises. The transport argument is a test seam.Version history
| Version | What shipped | Real live test performed |
|---|---|---|
| v0.1.0 | Published to PyPI on 2026-10-06. An MCP client over Streamable HTTP that returns ragleap_tools.Tool objects. Owner-configured servers and an exact allowlist; tools discovered once (a snapshot) or supplied as specs. Modern (2026-07-28) requests with Mcp-Method, Mcp-Name and Mcp-Param-* headers and params._meta; legacy servers are detected per the specification's backward-compatibility rules and spoken to with the initialize handshake for 2025-06-18 only. A standard-library-only HTTPS transport: public-address check with the connection pinned to the resolved IP, TLS (minimum 1.2) verified against the hostname, no redirects, a response-size cap, constant error messages and a watchdog-enforced wall-clock deadline. | 201 tests, including a real local TLS server for the transport (hostname verification, IP pinning, size cap, truncated bodies, and the deadline against slow-drip bodies, slow-drip headers and a stalled handshake); CI green on Python 3.10, 3.11 and 3.12. After release the published wheel and sdist were checked: installed files byte-identical to the tested ones, and the sdist's tests pass against the installed wheel. Live-checked on 2026-10-05 against DeepWiki's public MCP server (no authentication): the server rejected the modern request with HTTP 400 and -32600, the client fell back to the legacy handshake, negotiated 2025-06-18, listed tools and read a repository's documentation structure. The live check found a real bug before release (a legacy server's own JSON-RPC rejection was treated as a modern server) and it was fixed. Not live-verified: the modern 2026-07-28 path, x-mcp-header mirroring, where a -32022 error carries its supported-version list, bearer-token authentication, plain JSON (non-stream) responses and pagination. Correction: the CHANGELOG shipped inside 0.1.0 is dated 2026-10-05; the files reached PyPI on 2026-10-06 (published packages cannot be edited). |
Real design decisions & findings
Each of these was decided or found before release, not retrofitted.
- ✓ shippedThe owner decides, the model only supplies arguments. Servers, tokens and an exact
server.toolallowlist come from your code; the model never picks a URL, a token or a tool outside the list. - ✓ shippedA wall-clock deadline that actually holds. A test showed a server sending one byte every 0.2 s kept a plain read blocked for 80 s against a 3 s deadline, because every byte arrived inside the per-operation timeout. A watchdog that shuts the socket down at the deadline (and sets a flag, since the read then looks like a normal end of response) fixes it, and is tested over real TLS.
- ✓ shippedNo server text in errors. Failures carry a constant message plus, at most, an HTTP status or a JSON-RPC error code, so a hostile server cannot put instructions into an error result.
- ✓ shippedThe live check found a real bug. The first detection rule treated any JSON-RPC error on a 400 as proof of a modern server; DeepWiki's own
-32600rejection broke it. Only-32020,-32021and-32022count as recognized modern errors now, and a failed fallback still shows the first reply's code. The new tests fail on the old code. - ✓ shippedA CodeQL alert was reproduced before it was fixed.
py/insecure-protocolflagged the transport because two test contexts reached it without a TLS floor in a place the analyzer follows. It was reproduced locally with the CodeQL CLI running only that query (1 result), the test helper was fixed, and the same run returned 0. No production change. - not live-verifiedThe modern path and several details. The modern 2026-07-28 request flow,
x-mcp-headermirroring, the location of a -32022 error's supported-version list, bearer-token authentication, plain JSON responses and pagination follow the public specification and are covered by tests against fakes only. Treat them as best-effort until confirmed live. - deliberately deferredstdio, code execution, HTTP fetch and connectors. Each needs its own security design pass and is not included.
Known limitations
- On a legacy server every tool call performs a full
initializehandshake (three requests) and never closes the session. - A
HeaderMismatcherror is reported, not retried after re-reading the tool list. - Only text content is returned; other content types are counted and omitted.
- Server-supplied text (descriptions, schemas, results) is untrusted and is not screened for prompt injection.
ragleap_tools.Tool.callin ragleap-tools 0.4.0 cannot take an argument literally namedself.- Not safe to share one client between threads while calls are running.