Skip to main content

ragleap-integrations

An MCP (Model Context Protocol) client that turns allowlisted tools on remote MCP servers into ragleap_tools.Tool objects, so MCP tools and native tools share one shape.

pip install ragleap-integrations

What this is (and isn't)

v0.5.0 is an MCP client over Streamable HTTP only, and (since 0.4.0) a UTCP client for http manuals (connect_utcp, below). 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' 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 here: each needs its own security design pass.

Quickstart

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": "..."}

Nothing is read from the environment: your application builds the config. 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, give the descriptions and schemas yourself; nothing is sent over the network at setup:

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])

Easy mode: connect()

from ragleap_integrations import connect

conn = connect("https://mcp.example.com/mcp", allow=["list_orders", "get_order"], token=MY_TOKEN)
print(conn.report())   # which tools are ready, which were skipped and why (with the setting to change)

provider_tools = conn.toolkit.to_openai_schema()    # also to_gemini_schema() / to_anthropic_schema()
result = conn.toolkit.call(name_from_model, arguments_from_model)   # never raises

allow is an exact list of tool names (no wildcard). name (default "mcp") prefixes the exposed names (mcp__get_order). The toolkit checks a model's arguments against the server's schema before anything is sent. For several servers, build an McpConfig and call connect_config(config).

Governance: approval flags and audit events

A server's tool annotations (readOnlyHint, destructiveHint, ...) are untrusted hints, so they can only make policy stricter. Every tool carries tool.metadata["mcp"] with requires_approval, true unless you list the tool in pre_approved, or trust a server's readOnlyHint for that server.

def approve(server, tool, arguments):
    return ask_a_person(f"Run {server}.{tool} with {arguments}?")   # must return exactly True

conn = connect(URL, allow=["list_orders", "delete_order"], approve=approve,
               pre_approved=["list_orders"], on_event=print)

Without approve the flag is advice for your own approval gate (for example in ragleap-agents). With it, a flagged call runs only if approve returns True; an exception or any other value denies the call, the server is not contacted, and the result is "MCP call was not approved". on_event receives one small dict per discovery and per call (server, tool, ok, error, approval, duration); arguments and results never appear in it. See docs/design/mcp-governance.md.

Multi-tenant use: McpHub

One owner-level McpConfig, one Connection per tenant. The tenant is a Scope: with pip install "ragleap-integrations[security]" it is ragleap-security's Scope; without it a small built-in Scope with the same fields and key format. The model never sees or sets the scope.

from ragleap_integrations import McpConfig, McpHub, McpServerConfig, RateLimit, Scope

hub = McpHub(
    McpConfig(servers=[McpServerConfig("crm", "https://mcp.example.com/mcp")],
              allowed_tools=["crm.get_order", "crm.update_order"]),
    token_provider=lambda scope, server: vault.token_for(scope.tenant, server),
    allow_for=lambda scope: ["crm.get_order"] if scope.tenant == "trial" else None,
    limit=RateLimit(60, 60),   # 60 calls a minute per tenant
    audit=audit_log,           # anything with append(event, data, scope)
)
conn = hub.connect(Scope(tenant="acme", subject="u1"))
conn.toolkit.call(name_from_model, arguments_from_model)  # same Connection as connect()
  • Each scope has its own client, tool snapshot and token; nothing mutable is shared between scopes.
  • token_provider(scope, server) is called for every request and nothing is cached by this package. None sends no token (the static token is not used then); a failing provider gives the constant text "MCP token is unavailable".
  • allow_for(scope) can only narrow the owner's allowlist. policy_for(scope) picks a per-tenant McpPolicy; its approve_scoped(scope, server, tool, arguments) wins over approve, and without a scope it denies.
  • RateLimit is per tenant (all of a tenant's scopes share it) and is checked before approval and before any network call.
  • Events carry tenant and scope; arguments and results are never in them. audit receives every event with the scope.
  • hub.connect() needs an exact Scope (a string, a dict, None or a subclass raises ScopeError). This package does not decide who the caller is.

Design and limits: docs/design/multi-tenant.md.

UTCP: connect_utcp()

from ragleap_integrations import connect_utcp

conn = connect_utcp("https://api.example.com/utcp", allow=["get_order"], token=MY_TOKEN)
print(conn.report())                                       # ready and skipped tools, with reasons
result = conn.toolkit.call(name_from_model, arguments_from_model)   # never raises

New in 0.4.0. connect_utcp() reads one UTCP (Universal Tool Calling Protocol) manual - a JSON document the provider serves - once, and returns the allowlisted http tools as ragleap_tools.Tool objects with the same governance as connect(): an exact allowlist (no wildcard), every tool flagged for approval unless pre-approved, an optional approve hook, on_event audit events (marked "protocol": "utcp", never carrying arguments or results) and constant error text. It returns the same Connection. See docs/design/utcp.md and docs/design/utcp-auth.md.

New in 0.5.0: allowed_query=[...] and one accepted auth shape (below).

  • The owner names the manual URL, the tool names and the approved origins (origins=[...]; the manual's own origin is always approved). A tool whose URL is on another origin is skipped. The origin is checked at discovery and again on every call.
  • The manual is read once. A manual that changes later has no effect.
  • Supported: the http call template with GET, POST, PUT, DELETE or PATCH. Path parameters ({name}) are filled from the arguments and URL-encoded, the argument named by body_field becomes a JSON body, and the remaining arguments go in the query string, as UTCP defines. The owner's token is sent as a bearer token to approved origins only.
  • Auth block (0.5.0): a template may declare exactly this one shape: auth_type "api_key", location "header", var_name "Authorization", api_key exactly Bearer ${NAME}, no other key. It is accepted only because it describes what this client already does; NAME is never looked up. Without a token the tool is skipped. Any other auth block, and auth_tools, are skipped.
  • Static query (0.5.0): a URL may carry a query string only if you list each parameter name in allowed_query=["format"] (at most 8 names; values are plain tokens of letters, digits, _, ., -). A call argument with the same name is refused. The default is empty: no query string, as in 0.4.0.
  • Skipped with a stated reason, never silently mis-called: any other call template type (cli, text, file, ...), header_fields, a template with any other auth block, a ${NAME} or $NAME placeholder anywhere (nothing is ever substituted and nothing is read from the environment), a template field this version does not understand (for example query_params), a non-JSON request body, a query string you did not allow, and anything that is not an https URL on a public host. A document that is not a UTCP manual (an OpenAPI document, for example) is refused.
  • Not here: multi-tenant use (a UtcpHub would follow McpHub), OAuth, basic auth, other API-key shapes, OpenAPI conversion and every transport but http.

Trust model

  • Everything a server says is untrusted text: tool descriptions, input schemas and results flow into a model's context. This package does not screen them for prompt injection. The structural limits are the allowlist, a snapshot of the tool list taken once at setup (a server that changes a description later has no effect), length caps, and optional owner-supplied specs.
  • Server-supplied error text is never put in a result; failures carry a constant message plus, at most, an HTTP status or a JSON-RPC error code.
  • Network access is bounded in code: https only, no credentials in the URL, no IP-literal hosts, DNS resolved once with every address required to be public (so DNS rebinding cannot swap in an internal address), the connection pinned to that IP with TLS (minimum 1.2) verified against the hostname, no redirects, no compressed responses, a response-size cap, and a wall-clock deadline that also covers the TLS handshake and the headers.
  • A bearer token, if configured, is sent only to its own server URL and is never logged.

Protocol

The current revision (2026-07-28) is tried first: every request is its own POST carrying MCP-Protocol-Version, Mcp-Method and Mcp-Name headers and the version, client info and capabilities in params._meta; parameters marked x-mcp-header in a tool's schema are mirrored into Mcp-Param-* headers. A 4xx without a recognized modern error (-32020, -32021, -32022) identifies a legacy server, and the client then uses the legacy initialize handshake with protocol version 2025-06-18 only (an explicit allowlist, not "whatever the server answers"). The era found is remembered per server.

Limits and defaults

Setting Default
Whole-call deadline (total_timeout) 30 s
Per-operation socket timeout (op_timeout) 10 s
Response size (max_response_bytes) 1 MiB (256 KiB in 0.1.0)
Result text (max_result_chars) 4000 characters
Tool description (max_description_chars) 500 characters
Input schema (max_schema_chars) 10000 characters (larger: tool skipped)

Known limitations

  • UTCP (0.5.0): only http call templates of UTCP manuals; no OpenAPI conversion, only one auth shape, no header_fields, no OAuth, no multi-tenant hub yet. The result is the response body text (capped); it is not parsed. The normative text of the UTCP specification was not read, only the project's documentation and reference clients (see docs/design/utcp.md).
  • This package does not decide who the caller is: tenant isolation is only as strong as the Scope you pass and the token your token_provider returns.
  • Rate windows are kept per tenant until hub.forget(tenant=...), the limit is per process, and allow_for and policy_for are read once per scope.
  • On a legacy server every tool call performs a full initialize handshake (three POSTs) and never closes the session.
  • A HeaderMismatch error is reported, not retried after re-reading the tool list.
  • Only text content is returned; other content types are counted and omitted.
  • ragleap_tools.Tool.call in ragleap-tools 0.4.0 cannot take an argument literally named self.
  • Not safe to share one client between threads while calls are running.
  • Server-supplied text is untrusted and not screened for prompt injection: tool descriptions and schemas, tool results, and the text of a tool-level error (it becomes ToolResult.error, capped at max_result_chars like a result).
  • A server whose tool list is larger than the response cap (1 MiB by default; 256 KiB in 0.1.0, which a real 15-tool server's 359 KB list exceeded) makes discovery fail with "response too large; raise max_response_bytes" until the limit is raised.

Verification status

  • 0.5.0: no tool call has been verified against a real UTCP server; discovery of a public manual was read on 2026-11-07, 08 and 09 (see the changelog). 626 tests in the development sandbox (545 earlier plus 81 new); 48 of 52 deliberate breaks of the new code caught, the other 4 equivalent. For 0.4.0 (kept for the record): 545 tests (the 331 earlier ones unchanged plus 184 for the UTCP client and 30 for the new request methods of the transport, which run against a real local TLS server; 541 passed and 4 skipped without ragleap-security); 127 of 127 deliberate breaks of the new code caught, plus one equivalent mutant.
  • 331 tests (the 275 earlier ones unchanged plus 56 new in 0.3.0: scope and token isolation, allowlists, policies, limits, events, audit, threads; 327 passed and 4 skipped without ragleap-security); 40 of 40 deliberate breaks of the new code caught.
  • Live-checked on 2026-10-26 with the repository source of the 0.3.0 branch on Python 3.10.12 against a third party's public mock MCP auth server, using scripts/tenant_live.py (repository only, not in the wheel): two real tokens were minted; each tenant's requests carried only its own token (compared by hash); a made-up token got HTTP 401 and the real tenants kept working; after one tenant's token was changed only that tenant was refused (the token is resolved per request); RateLimit(2, 60) denied the third call locally and only two tools/call requests reached the server; no event carried a token. The mock server cannot tell tenants apart, so separation is shown from the requests that were sent.
  • 275 tests (67 of them new in 0.2.0: governance, events and connect(); 53 of 53 deliberate breaks of the new code caught), including a real local TLS server with a throwaway certificate 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).
  • Live-checked on 2026-10-05 against DeepWiki's public MCP server (https://mcp.deepwiki.com/mcp, no authentication): the server answered the modern request with HTTP 400 and a JSON-RPC -32600 error, the client fell back to the legacy handshake, negotiated 2025-06-18, listed tools and read a repository's documentation structure. This verifies the legacy path, the fallback decision, event-stream responses and the pinned TLS transport against a real server.
  • Live-checked on 2026-10-12 against two public Google Cloud MCP endpoints (https://developerknowledge.googleapis.com/mcp and https://pubsub.googleapis.com/mcp) on the modern 2026-07-28 path, using the published 0.1.0 wheel in a clean virtual environment (Python 3.10.12). Read-only: tools/list only, no credentials, no tools/call. The server accepted the modern request, replied with plain application/json (non-stream), and the client discovered the tools and read their input schemas (3 tools from one server). A second server's tool list was 359,293 bytes, above the default max_response_bytes (256 KiB): discovery failed with "response too large" until the limit was raised, and two of its tools were skipped because their input schemas exceed max_schema_chars. Both servers answered an unsupported protocol version with HTTP 400 and -32600 and no supported-version list; neither implements server/discover (this client never calls it).
  • Live-checked on 2026-10-16 against https://pubsub.googleapis.com/mcp using the published 0.1.1 wheel in a clean virtual environment (Python 3.10.12), with no limit overrides. Read-only: tools/list only, no credentials, no tools/call. The 359,293-byte list of the 15-tool server, which 0.1.0 refused at its 256 KiB default, is now accepted with the default max_response_bytes (1 MiB), and the client recorded the modern era. Four of the 15 tools have an input schema above the default max_schema_chars (10,000): update_subscription (33,502 characters), create_subscription (32,989), update_topic (23,537) and create_topic (22,835); the other eleven are 2,631 or smaller. With the first three tools allowlisted, discovery registered one and skipped create_topic and update_topic, as designed. As an experiment only, max_schema_chars=40000 (an override; the default is unchanged) registered all 15 tools. Not verified: that schemas of this size are usable by a model.
  • Live-checked on 2026-10-17 against a third party's public test servers (https://mcpplaygroundonline.com/mock-mcp-servers; their own description of what each server does is not independently verified), using the repository source at commit 912d100 (0.1.1) on Python 3.10.12 and scripts/live_check_mock.py (repository only, not in the wheel): (a) a modern-path tools/call succeeded against the stateless server, both pinned with ?rev=2026-07-28 and unpinned, and the tool itself reported the modern era and revision 2026-07-28; (b) bearer-token authentication: a call without a token was refused (HTTP 401) and the same call with a freshly minted token succeeded; (c) against the same server pinned to the legacy revision (?rev=2025-11-25) the client fell back to the legacy handshake and a tool call succeeded, the tool reporting the legacy era (the reply that triggered the fallback was not recorded); (d) a tool that reports an error returned success=False with the server's message in error and nothing raised.
  • Live-checked on 2026-10-25 with the published 0.2.0 wheel on Python 3.10.12 against Google Cloud's Pub/Sub MCP endpoint (https://pubsub.googleapis.com/mcp, modern path, tools/list only, no credentials, no tools/call), using scripts/connect_live.py (repository only, not in the wheel): connect() with the default limits registered 11 tools and skipped the four whose input schemas exceed max_schema_chars. All 15 tools carry the four boolean hints. With trust_annotations=False all 11 are flagged requires_approval; with trust_annotations=True exactly six are not (the get_ and list_ tools for topics, subscriptions and snapshots, which declare readOnlyHint: true and destructiveHint: false). In both runs the flags equal an independent calculation from the raw hints, and each connect() produced one discover event and no call event.
  • Not live-verified: x-mcp-header mirroring (none of the four tools on the complex-schema server carries the annotation), pagination (none of the servers checked paginated) and this client's own handling of a -32022 reply (it never received one live; a raw legacy initialize sent to the modern-only server got HTTP 400 and -32022 with the supported versions in error.data.supported, which is where the client looks). They follow the public specification and are covered by tests against fakes only; treat them as best-effort until confirmed live.
  • Not live-verified (new in 0.2.0): the approve hook, its denial text and the per-call audit events against a real server (they need a real tools/call, and the Pub/Sub server requires credentials), and connect(token=...) against a real authorization server.
  • Not live-verified (new in 0.3.0): approve_scoped, allow_for and policy_for against a real server, a real ragleap-security AuditLog fed by a live run, concurrent tenants against a real server, a server that really separates tenants' data, and the published wheel (the live check ran on the branch source and is repeated with the wheel after release).

License

MIT

Metadata

Release files for ragleap-integrations 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ragleap-integrations 0.5.0
File Size Uploaded Provenance Actions
ragleap_integrations-0.5.0.tar.gz 103.7 kB With provenance Details for ragleap_integrations-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ragleap-integrations 0.5.0
File Interpreter ABI Platform Actions
ragleap_integrations-0.5.0-py3-none-any.whl Python 3 none any Details for ragleap_integrations-0.5.0-py3-none-any.whl

Total release size: 146.3 kB

Release files / ragleap_integrations-0.5.0.tar.gz

Download URL ragleap_integrations-0.5.0.tar.gz
Size 103.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d681e338dbe17020d6f8f6827c32521a26714fec16078081b0fc03b82d58ef46
BLAKE2b-256 checksum
How to use checksums
f83100b9dc871b1fcc943f0a2218d1bf9a82785acb3e9188939257d946be172a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Release files / ragleap_integrations-0.5.0-py3-none-any.whl

Download URL ragleap_integrations-0.5.0-py3-none-any.whl
Size 42.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
537f8cd94fa09042af3618105ef226c791e1cb57ba9bf08218178827a724a06a
BLAKE2b-256 checksum
How to use checksums
58793705af8ee2aa211ef3f005a2ac0aca54762b365ac31a041417f993a89341
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Security for ragleap-integrations 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ragleap_integrations-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page