ragleap-integrations 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ # Environment
2
+ .env
3
+ *.env.local
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ venv/
10
+ .venv/
11
+
12
+ # Node
13
+ node_modules/
14
+
15
+ # Docker
16
+ docker-compose.override.yml
17
+
18
+ # IDE
19
+ .vscode/
20
+ .idea/
21
+
22
+ # OS
23
+ .DS_Store
24
+ Thumbs.db
25
+
26
+ # Logs
27
+ *.log
28
+ .env
29
+
30
+ # Package build artifacts
31
+ dist/
32
+ build/
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to `ragleap-integrations` are documented here. Format
4
+ loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [0.1.0] - 2026-10-05
7
+
8
+ ### Added
9
+
10
+ - An MCP client over Streamable HTTP that returns `ragleap_tools.Tool`
11
+ objects (`McpConfig`, `McpServerConfig`, `McpToolSpec`, `McpClient`,
12
+ `make_mcp_tools`). Owner-configured servers and an exact `server.tool`
13
+ allowlist; nothing is read from the environment; tools are discovered once
14
+ (a snapshot) or supplied by the owner as specs, in which case nothing is
15
+ sent over the network at setup.
16
+ - Modern (2026-07-28) requests with `Mcp-Method`, `Mcp-Name` and
17
+ `Mcp-Param-*` headers (Base64 sentinel encoding where required) and
18
+ `params._meta`; detection of legacy servers per the specification's
19
+ backward-compatibility rules, with a legacy `initialize` fallback for
20
+ 2025-06-18 only.
21
+ - A bounded HTTPS transport using only the standard library: public-address
22
+ check with the connection pinned to the resolved IP, TLS verified against the
23
+ hostname, no redirects, a response-size cap, constant error messages, and a
24
+ wall-clock deadline enforced by a watchdog (see
25
+ `docs/design/mcp-client.md` for the slow-drip experiment behind it).
26
+
27
+ ### Fixed
28
+
29
+ - Found by the live check, before release: a legacy server that rejects the
30
+ modern request with its own JSON-RPC error (DeepWiki answered HTTP 400 and
31
+ `-32600`) was reported as an error instead of triggering the legacy
32
+ fallback. Only `-32020`, `-32021` and `-32022` are treated as recognized
33
+ modern errors now, and a failed fallback still reports the first reply's
34
+ code.
35
+
36
+ ### Verified
37
+
38
+ - 201 tests, including the real-TLS transport tests described in the README.
39
+ - Live-checked on 2026-10-05 against DeepWiki's public MCP server (legacy path;
40
+ see the README's Verification status for exactly what that covers).
41
+
42
+ ### Not verified
43
+
44
+ - The modern 2026-07-28 path against a real server, `x-mcp-header`
45
+ mirroring, the location of a `-32022` error's supported-version list,
46
+ bearer-token authentication, plain JSON responses and pagination.
47
+
48
+ ### Known limitations
49
+
50
+ - On a legacy server every call performs a full `initialize` handshake and
51
+ never closes the session. `HeaderMismatch` is not retried. Only text
52
+ content is returned. Server-supplied text (descriptions, schemas, results)
53
+ is untrusted and not screened for prompt injection.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.5
2
+ Name: ragleap-integrations
3
+ Version: 0.1.0
4
+ Summary: MCP client that turns allowlisted tools on remote MCP servers into ragleap-tools Tool objects. Streamable HTTP only, standard library plus ragleap-tools, owner-configured allowlist, pinned and bounded network access. BYOK, no vendor lock-in.
5
+ Project-URL: Homepage, https://github.com/antonyrag/ragleap-core
6
+ Project-URL: Repository, https://github.com/antonyrag/ragleap-core
7
+ Project-URL: Documentation, https://github.com/antonyrag/ragleap-core/tree/main/packages/ragleap-integrations
8
+ Project-URL: Issues, https://github.com/antonyrag/ragleap-core/issues
9
+ Author-email: Antony <antony@ragleap.com>
10
+ License-Expression: MIT
11
+ Keywords: agents,byok,integrations,llm,mcp,model-context-protocol,self-hosted,tool-calling
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: ragleap-tools>=0.4.0
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # ragleap-integrations
27
+
28
+ An MCP (Model Context Protocol) client that turns allowlisted tools on remote
29
+ MCP servers into `ragleap_tools.Tool` objects, so MCP tools and native tools
30
+ share one shape.
31
+
32
+ ```bash
33
+ pip install ragleap-integrations
34
+ ```
35
+
36
+ ## What this is (and isn't)
37
+
38
+ v0.1.0 is an MCP client over **Streamable HTTP only**. The *owner* configures
39
+ servers and an exact `server.tool` allowlist; the model only supplies a tool's
40
+ JSON arguments, never a URL, a token or a tool outside the allowlist. It does
41
+ not own a tool-calling loop (that is `ragleap-agents`' job).
42
+
43
+ Not supported in this version: stdio (it launches a subprocess), the
44
+ deprecated HTTP+SSE transport, sampling/elicitation/roots (a server asking
45
+ for client input gets a clear "unsupported" result), resources, prompts,
46
+ subscriptions and OAuth. Connectors, code execution and HTTP fetch are
47
+ deliberately not here: each needs its own security design pass.
48
+
49
+ ## Quickstart
50
+
51
+ ```python
52
+ from ragleap_integrations import McpConfig, McpServerConfig, make_mcp_tools
53
+
54
+ config = McpConfig(
55
+ servers=[McpServerConfig("deepwiki", "https://mcp.deepwiki.com/mcp")], # token="..." if a server needs one
56
+ allowed_tools=["deepwiki.read_wiki_structure"],
57
+ )
58
+ tools = make_mcp_tools(config) # discovers the allowlisted tools once (tools/list)
59
+
60
+ openai_tools = [t.to_openai_schema() for t in tools] # or t.to_gemini_schema()
61
+ result = tools[0].call(repoName="sqlite/sqlite")
62
+ print(result.success, result.result) # True {"text": "..."}
63
+ ```
64
+
65
+ Nothing is read from the environment: your application builds the config.
66
+ Tools are exposed as `<server>__<tool>` (characters outside letters, digits,
67
+ `_` and `-` become `_`, at most 64 characters).
68
+
69
+ To keep server-controlled text out of the model's context entirely, give the
70
+ descriptions and schemas yourself; nothing is sent over the network at setup:
71
+
72
+ ```python
73
+ from ragleap_integrations import McpToolSpec
74
+
75
+ spec = McpToolSpec(
76
+ server="deepwiki", name="read_wiki_structure",
77
+ description="List the documentation topics of a GitHub repository.",
78
+ parameters={"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]},
79
+ )
80
+ tools = make_mcp_tools(config, specs=[spec])
81
+ ```
82
+
83
+ ## Trust model
84
+
85
+ - Everything a server says is untrusted text: tool descriptions, input
86
+ schemas and results flow into a model's context. This package does **not**
87
+ screen them for prompt injection. The structural limits are the allowlist,
88
+ a snapshot of the tool list taken once at setup (a server that changes a
89
+ description later has no effect), length caps, and optional owner-supplied
90
+ specs.
91
+ - Server-supplied error text is never put in a result; failures carry a
92
+ constant message plus, at most, an HTTP status or a JSON-RPC error code.
93
+ - Network access is bounded in code: https only, no credentials in the URL,
94
+ no IP-literal hosts, DNS resolved once with every address required to be
95
+ public (so DNS rebinding cannot swap in an internal address), the
96
+ connection pinned to that IP with TLS (minimum 1.2) verified against the
97
+ hostname, no redirects, no compressed responses, a response-size cap, and a
98
+ wall-clock deadline that also covers the TLS handshake and the headers.
99
+ - A bearer token, if configured, is sent only to its own server URL and is
100
+ never logged.
101
+
102
+ ## Protocol
103
+
104
+ The current revision (2026-07-28) is tried first: every request is its own
105
+ POST carrying `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers and
106
+ the version, client info and capabilities in `params._meta`; parameters marked
107
+ `x-mcp-header` in a tool's schema are mirrored into `Mcp-Param-*` headers.
108
+ A 4xx without a recognized modern error (`-32020`, `-32021`, `-32022`)
109
+ identifies a legacy server, and the client then uses the legacy `initialize`
110
+ handshake with protocol version 2025-06-18 only (an explicit allowlist, not
111
+ "whatever the server answers"). The era found is remembered per server.
112
+
113
+ ## Limits and defaults
114
+
115
+ | Setting | Default |
116
+ |---|---|
117
+ | Whole-call deadline (`total_timeout`) | 30 s |
118
+ | Per-operation socket timeout (`op_timeout`) | 10 s |
119
+ | Response size (`max_response_bytes`) | 256 KiB |
120
+ | Result text (`max_result_chars`) | 4000 characters |
121
+ | Tool description (`max_description_chars`) | 500 characters |
122
+ | Input schema (`max_schema_chars`) | 10000 characters (larger: tool skipped) |
123
+
124
+ ## Known limitations
125
+
126
+ - On a legacy server every tool call performs a full `initialize` handshake
127
+ (three POSTs) and never closes the session.
128
+ - A `HeaderMismatch` error is reported, not retried after re-reading the
129
+ tool list.
130
+ - Only text content is returned; other content types are counted and omitted.
131
+ - `ragleap_tools.Tool.call` in ragleap-tools 0.4.0 cannot take an argument
132
+ literally named `self`.
133
+ - Not safe to share one client between threads while calls are running.
134
+
135
+ ## Verification status
136
+
137
+ - 201 tests, including a real local TLS server with a throwaway certificate
138
+ for the transport (hostname verification, IP pinning, size cap, truncated
139
+ bodies, and the deadline against slow-drip bodies, slow-drip headers and a
140
+ stalled handshake).
141
+ - **Live-checked on 2026-10-05** against DeepWiki's public MCP server
142
+ (`https://mcp.deepwiki.com/mcp`, no authentication): the server answered
143
+ the modern request with HTTP 400 and a JSON-RPC `-32600` error, the client
144
+ fell back to the legacy handshake, negotiated 2025-06-18, listed tools and
145
+ read a repository's documentation structure. This verifies the legacy path,
146
+ the fallback decision, event-stream responses and the pinned TLS transport
147
+ against a real server.
148
+ - **Not live-verified:** the modern 2026-07-28 path, `x-mcp-header`
149
+ mirroring, where a `-32022` error carries its supported-version list,
150
+ bearer-token authentication, plain JSON (non-stream) responses and
151
+ pagination. They follow the public specification and are covered by tests
152
+ against fakes only; treat them as best-effort until confirmed live.
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,131 @@
1
+ # ragleap-integrations
2
+
3
+ An MCP (Model Context Protocol) client that turns allowlisted tools on remote
4
+ MCP servers into `ragleap_tools.Tool` objects, so MCP tools and native tools
5
+ share one shape.
6
+
7
+ ```bash
8
+ pip install ragleap-integrations
9
+ ```
10
+
11
+ ## What this is (and isn't)
12
+
13
+ v0.1.0 is an MCP client over **Streamable HTTP only**. The *owner* configures
14
+ servers and an exact `server.tool` allowlist; the model only supplies a tool's
15
+ JSON arguments, never a URL, a token or a tool outside the allowlist. It does
16
+ not own a tool-calling loop (that is `ragleap-agents`' job).
17
+
18
+ Not supported in this version: stdio (it launches a subprocess), the
19
+ deprecated HTTP+SSE transport, sampling/elicitation/roots (a server asking
20
+ for client input gets a clear "unsupported" result), resources, prompts,
21
+ subscriptions and OAuth. Connectors, code execution and HTTP fetch are
22
+ deliberately not here: each needs its own security design pass.
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ from ragleap_integrations import McpConfig, McpServerConfig, make_mcp_tools
28
+
29
+ config = McpConfig(
30
+ servers=[McpServerConfig("deepwiki", "https://mcp.deepwiki.com/mcp")], # token="..." if a server needs one
31
+ allowed_tools=["deepwiki.read_wiki_structure"],
32
+ )
33
+ tools = make_mcp_tools(config) # discovers the allowlisted tools once (tools/list)
34
+
35
+ openai_tools = [t.to_openai_schema() for t in tools] # or t.to_gemini_schema()
36
+ result = tools[0].call(repoName="sqlite/sqlite")
37
+ print(result.success, result.result) # True {"text": "..."}
38
+ ```
39
+
40
+ Nothing is read from the environment: your application builds the config.
41
+ Tools are exposed as `<server>__<tool>` (characters outside letters, digits,
42
+ `_` and `-` become `_`, at most 64 characters).
43
+
44
+ To keep server-controlled text out of the model's context entirely, give the
45
+ descriptions and schemas yourself; nothing is sent over the network at setup:
46
+
47
+ ```python
48
+ from ragleap_integrations import McpToolSpec
49
+
50
+ spec = McpToolSpec(
51
+ server="deepwiki", name="read_wiki_structure",
52
+ description="List the documentation topics of a GitHub repository.",
53
+ parameters={"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]},
54
+ )
55
+ tools = make_mcp_tools(config, specs=[spec])
56
+ ```
57
+
58
+ ## Trust model
59
+
60
+ - Everything a server says is untrusted text: tool descriptions, input
61
+ schemas and results flow into a model's context. This package does **not**
62
+ screen them for prompt injection. The structural limits are the allowlist,
63
+ a snapshot of the tool list taken once at setup (a server that changes a
64
+ description later has no effect), length caps, and optional owner-supplied
65
+ specs.
66
+ - Server-supplied error text is never put in a result; failures carry a
67
+ constant message plus, at most, an HTTP status or a JSON-RPC error code.
68
+ - Network access is bounded in code: https only, no credentials in the URL,
69
+ no IP-literal hosts, DNS resolved once with every address required to be
70
+ public (so DNS rebinding cannot swap in an internal address), the
71
+ connection pinned to that IP with TLS (minimum 1.2) verified against the
72
+ hostname, no redirects, no compressed responses, a response-size cap, and a
73
+ wall-clock deadline that also covers the TLS handshake and the headers.
74
+ - A bearer token, if configured, is sent only to its own server URL and is
75
+ never logged.
76
+
77
+ ## Protocol
78
+
79
+ The current revision (2026-07-28) is tried first: every request is its own
80
+ POST carrying `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers and
81
+ the version, client info and capabilities in `params._meta`; parameters marked
82
+ `x-mcp-header` in a tool's schema are mirrored into `Mcp-Param-*` headers.
83
+ A 4xx without a recognized modern error (`-32020`, `-32021`, `-32022`)
84
+ identifies a legacy server, and the client then uses the legacy `initialize`
85
+ handshake with protocol version 2025-06-18 only (an explicit allowlist, not
86
+ "whatever the server answers"). The era found is remembered per server.
87
+
88
+ ## Limits and defaults
89
+
90
+ | Setting | Default |
91
+ |---|---|
92
+ | Whole-call deadline (`total_timeout`) | 30 s |
93
+ | Per-operation socket timeout (`op_timeout`) | 10 s |
94
+ | Response size (`max_response_bytes`) | 256 KiB |
95
+ | Result text (`max_result_chars`) | 4000 characters |
96
+ | Tool description (`max_description_chars`) | 500 characters |
97
+ | Input schema (`max_schema_chars`) | 10000 characters (larger: tool skipped) |
98
+
99
+ ## Known limitations
100
+
101
+ - On a legacy server every tool call performs a full `initialize` handshake
102
+ (three POSTs) and never closes the session.
103
+ - A `HeaderMismatch` error is reported, not retried after re-reading the
104
+ tool list.
105
+ - Only text content is returned; other content types are counted and omitted.
106
+ - `ragleap_tools.Tool.call` in ragleap-tools 0.4.0 cannot take an argument
107
+ literally named `self`.
108
+ - Not safe to share one client between threads while calls are running.
109
+
110
+ ## Verification status
111
+
112
+ - 201 tests, including a real local TLS server with a throwaway certificate
113
+ for the transport (hostname verification, IP pinning, size cap, truncated
114
+ bodies, and the deadline against slow-drip bodies, slow-drip headers and a
115
+ stalled handshake).
116
+ - **Live-checked on 2026-10-05** against DeepWiki's public MCP server
117
+ (`https://mcp.deepwiki.com/mcp`, no authentication): the server answered
118
+ the modern request with HTTP 400 and a JSON-RPC `-32600` error, the client
119
+ fell back to the legacy handshake, negotiated 2025-06-18, listed tools and
120
+ read a repository's documentation structure. This verifies the legacy path,
121
+ the fallback decision, event-stream responses and the pinned TLS transport
122
+ against a real server.
123
+ - **Not live-verified:** the modern 2026-07-28 path, `x-mcp-header`
124
+ mirroring, where a `-32022` error carries its supported-version list,
125
+ bearer-token authentication, plain JSON (non-stream) responses and
126
+ pagination. They follow the public specification and are covered by tests
127
+ against fakes only; treat them as best-effort until confirmed live.
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,205 @@
1
+ # MCP client - allowlisted MCP tools as ragleap-tools Tools
2
+
3
+ ## Problem
4
+
5
+ core/mcp_client.py (in the app) already calls allowlisted tools on
6
+ remote MCP servers, but it is coupled to the app: configuration is read
7
+ from environment variables, it depends on requests and
8
+ core.action_senders, and it makes three POSTs per call (initialize,
9
+ notifications/initialized, tools/call). It never calls tools/list, so it
10
+ has no tool schema (the caller passes arguments as a JSON string), it
11
+ keeps text content only, and it pins protocol revision 2025-06-18. Its
12
+ tests use an in-process fake server, not a real one.
13
+
14
+ This package extracts and hardens that client and returns
15
+ ragleap_tools.Tool objects, so MCP tools and native tools share one shape.
16
+
17
+ ## Scope (v0.1.0)
18
+
19
+ MCP client over Streamable HTTP only. Deliberately excluded:
20
+ - stdio transport: it launches a local subprocess, i.e. code execution.
21
+ - HTTP+SSE: deprecated since 2025-03-26.
22
+ - Sampling, elicitation and roots (InputRequiredResult): v0.1.0 returns a
23
+ clear "unsupported" ToolResult error.
24
+ - Resources, prompts, subscriptions/listen, OAuth.
25
+ - Code execution, HTTP fetch, database/business-system connectors and the
26
+ agent loop: each is a separate security design pass or belongs to
27
+ ragleap-agents.
28
+
29
+ ## Design
30
+
31
+ Explicit configuration, no environment-variable fallback (the same BYOK
32
+ rule as ragleap-tools); the app builds the config from its env vars:
33
+ - McpServerConfig(name, url, token=None): static bearer token only.
34
+ - McpConfig(servers, allowed_tools): exact "server.tool" allowlist. The
35
+ model supplies only arguments; it never chooses a URL, command or token.
36
+
37
+ make_mcp_tools(config) returns one ragleap_tools.Tool per allowlisted
38
+ tool, two ways to get the schema:
39
+ 1. Owner-supplied specs (no network, safest): the owner states each
40
+ tool's description and parameters.
41
+ 2. Discovery: tools/list, restricted to allowlisted names, snapshotted
42
+ once and not refreshed per call. A server that changes a description
43
+ later has no effect. Descriptions are length-capped. Tool definitions
44
+ with invalid x-mcp-header annotations are excluded with a warning, as
45
+ the spec requires.
46
+
47
+ ## Protocol
48
+
49
+ Two code paths, chosen per the spec's Backward Compatibility section:
50
+ - Modern (2026-07-28): each request is its own POST with Accept (JSON and
51
+ event-stream), MCP-Protocol-Version, Mcp-Method, and Mcp-Name for
52
+ tools/call, plus protocolVersion, clientInfo and clientCapabilities in
53
+ _meta. No initialize, no session. x-mcp-header parameters are mirrored
54
+ into Mcp-Param-* headers. Both application/json and text/event-stream
55
+ responses are handled.
56
+ - Legacy (2025-06-18): initialize, notifications/initialized, then
57
+ tools/call, with Mcp-Session-Id when the server issues one. Ported from
58
+ core/mcp_client.py.
59
+ Try modern first, declaring the newest modern revision this package
60
+ implements. A modern server that does not support it answers with
61
+ UnsupportedProtocolVersionError listing the versions it does support (the
62
+ spec's example is 2026-07-28 and 2025-11-25); retry once with a mutually
63
+ supported version, otherwise report an error. server/discover is optional
64
+ and is not used in v0.1.0. A 4xx without a recognized modern error body
65
+ means a legacy server: fall back to legacy. The recognized modern errors are
66
+ -32020 (HeaderMismatch), -32021 (MissingRequiredClientCapability) and -32022
67
+ (UnsupportedProtocolVersion); on a 400 they are reported, never treated as
68
+ legacy (a -32022 whose supported list includes an allowlisted legacy version
69
+ continues with the legacy handshake). Any other JSON-RPC error on a 400 - a
70
+ live DeepWiki server answered -32600 - is a legacy server's own rejection and
71
+ falls back too. A fallback that then fails reports the legacy failure together
72
+ with the first reply's code, so a modern server that answered 400 and -32602
73
+ for bad arguments is not hidden. (The code numbers come from an SDK issue
74
+ summarising the spec's rule, a secondary source; the principle is in the spec.)
75
+
76
+ Legacy revisions are an explicit allowlist, not "whatever the server
77
+ answers": the app client accepts the version the server returns without
78
+ checking it, and this package will not. Which legacy revisions are
79
+ supported (2025-06-18 as in the app client, 2025-11-25, or both) is
80
+ decided after reading those revisions' transport pages.
81
+
82
+ ## Network safety
83
+
84
+ https only, public addresses only (IPv4-mapped IPv6 addresses unwrapped
85
+ before the check); resolve DNS once and connect to that IP, then wrap the
86
+ socket in TLS (minimum 1.2) verified against the hostname and hand it to
87
+ http.client, the technique used in core/page_fetch.py; no redirects;
88
+ Accept-Encoding: identity; a response-size cap; constant error messages
89
+ (no library exception text in results); the token is sent only to its
90
+ configured URL and never logged.
91
+
92
+ Wall-clock deadline. core/page_fetch.py checks its deadline only between
93
+ chunks. A test on 2026-10-03 (plain HTTP on loopback) showed that a server
94
+ sending one byte every 0.2 s kept resp.read(8192) blocked for 80 s against
95
+ a 3 s deadline, because every byte arrived inside the per-operation
96
+ timeout. Two fixes were tried: read1() bounds the body phase (stopped at
97
+ 3.0 s); a watchdog timer that shuts the socket down at the deadline bounds
98
+ both the header and the body phase (3.0 s each) but the read then looks
99
+ like a normal end of response, so the watchdog must set a flag and the
100
+ caller must raise a deadline error when it is set. This package uses the
101
+ watchdog plus read1(), started before the TLS handshake. Not yet tested
102
+ over TLS; that is a test with a local TLS server.
103
+
104
+ ## Dependencies
105
+
106
+ ragleap-tools (for Tool and ToolResult) and otherwise the standard library
107
+ (http.client, ssl, json). No requests.
108
+
109
+ ## Results and errors
110
+
111
+ Text content parts only; other content types are dropped and the result
112
+ says so. isError maps to ToolResult(success=False). Result length is
113
+ capped. Every failure (allowlist refusal, bad URL, network error, non-2xx,
114
+ malformed JSON-RPC, InputRequiredResult) is a ToolResult, never an
115
+ exception.
116
+
117
+ ## Untrusted content
118
+
119
+ Tool descriptions, input schemas and results are text controlled by the
120
+ MCP server and flow into the model's context. This is a prompt-injection
121
+ surface and is not screened. Mitigations here are structural (the
122
+ allowlist, the snapshot, description caps, optional owner-supplied
123
+ specs), not content filtering.
124
+
125
+ ## Testing
126
+
127
+ - One injectable transport function carries all HTTP, so tests exercise the
128
+ real header and body construction without a network (the ragleap-tools
129
+ convention of mocking at the lowest seam). The address check takes an
130
+ injectable resolver.
131
+ - The address check gets its own tests, because the app's tests replace it
132
+ with True: private, loopback, link-local including the cloud metadata
133
+ address, multicast, IPv4-mapped IPv6, and a host with one public and one
134
+ private address (must be refused).
135
+ - Ported behavior: allowlist parsing and refusal, no redirects, timeouts,
136
+ size cap, session handling on the legacy path, isError, text-only content.
137
+ - Deadline: a slow-drip server (one byte per interval, under the
138
+ per-operation timeout, in both the header and the body phase) must
139
+ produce a deadline error, never a truncated success.
140
+ - Modern path: MCP-Protocol-Version, Mcp-Method and Mcp-Name headers,
141
+ _meta contents, x-mcp-header mirroring including the Base64 sentinel
142
+ encoding, rejection of invalid annotations, UnsupportedProtocolVersionError
143
+ retry, and the 400-without-modern-error fallback.
144
+
145
+ ## Packaging and CI
146
+
147
+ - packages/ragleap-integrations, hatchling build, src/ragleap_integrations,
148
+ dependencies = ["ragleap-tools>=0.4.0"], the same layout as ragleap-tools.
149
+ - A ragleap-integrations-tests CI job on a Python 3.10/3.11/3.12 matrix
150
+ (ragleap-vectorstores already runs 3.10 and 3.12). ragleap-tools' own CI
151
+ runs 3.11 only although its classifiers list 3.10 to 3.12: a small
152
+ separate follow-up.
153
+ - release.yml gets one new job, one dispatch option and one tag pattern.
154
+ It is shared with other packages' jobs; nothing else in it changes.
155
+ - First-release prerequisite: the PyPI and TestPyPI projects do not exist
156
+ yet (the name was free on 2026-10-03). The account owner must add a
157
+ pending trusted publisher on each (repository antonyrag/ragleap-core,
158
+ workflow release.yml, environment pypi or testpypi).
159
+
160
+ ## Verification status
161
+
162
+ Checked against the current public specification (the 2026-07-28
163
+ Streamable HTTP page, changelog and versioning page): per-request headers
164
+ and _meta, no sessions, no negotiation handshake, UnsupportedProtocolVersionError
165
+ with a supported list, optional server/discover, x-mcp-header mirroring,
166
+ backward-compatibility detection, and InputRequiredResult for
167
+ server-to-client interactions. Not yet checked: the tools page (tools/list
168
+ and inputSchema field names, pagination), the InputRequiredResult shape,
169
+ and the 2025-06-18 and 2025-11-25 transport pages.
170
+ Live check (2026-10-05): the package's real transport and client were run against
171
+ DeepWiki's public MCP server (https://mcp.deepwiki.com/mcp, no authentication)
172
+ with every exchange traced. The server answered the modern request with HTTP
173
+ 400 and JSON-RPC -32600 (an unsupported-protocol-version message), which
174
+ exposed a bug in the first version of the detection rule (any JSON-RPC error
175
+ was treated as a modern server). After the fix the client fell back to the
176
+ legacy handshake, the server agreed to 2025-06-18, notifications/initialized
177
+ returned 202, tools/list and tools/call succeeded over event-stream responses
178
+ (CRLF framing), and read_wiki_structure returned a repository's documentation
179
+ structure. Verified live: the fallback decision, the legacy handshake,
180
+ event-stream parsing, discovery and the pinned TLS transport. Not verified
181
+ live: the modern 2026-07-28 path, x-mcp-header mirroring, where a -32022 error
182
+ carries its supported-version list, bearer-token authentication, plain JSON
183
+ responses and pagination. The existing app client had only ever run against a
184
+ fake in-process server.
185
+
186
+ ## Open decisions (resolved when v0.1.0 was built)
187
+
188
+ Resolved: (1) both paths, with legacy limited to 2025-06-18; (2) DeepWiki's
189
+ public server was used for a live check, which exercises the legacy path only;
190
+ (3) not part of v0.1.0; (4) tools are exposed as <server>__<tool>, sanitized
191
+ and cut to 64 characters, and a collision is a setup error; (5) an explicit
192
+ port in a configured URL is allowed. The list below is kept as drafted.
193
+
194
+
195
+ 1. Modern plus legacy, or modern only.
196
+ 2. A public HTTPS MCP server for a live check; otherwise the package ships
197
+ labelled unverified.
198
+ 3. Whether core/ later imports this package instead of its own client
199
+ (not part of v0.1.0).
200
+ 4. Tool naming: function-calling APIs restrict name characters, so the
201
+ "server.tool" allowlist key is not the exposed Tool name.
202
+ 5. Ports: core/page_fetch.py allows 443 only because the model chooses its
203
+ URL. Here the owner chooses the URL, so an explicit port in a configured
204
+ URL could be allowed (still pinned and public-only). Proposed default:
205
+ allow it.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ragleap-integrations"
7
+ version = "0.1.0"
8
+ description = "MCP client that turns allowlisted tools on remote MCP servers into ragleap-tools Tool objects. Streamable HTTP only, standard library plus ragleap-tools, owner-configured allowlist, pinned and bounded network access. BYOK, no vendor lock-in."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Antony", email = "antony@ragleap.com" }
14
+ ]
15
+ keywords = ["mcp", "model-context-protocol", "llm", "tool-calling", "agents", "integrations", "self-hosted", "byok"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+
27
+ dependencies = ["ragleap-tools>=0.4.0"]
28
+
29
+ [project.optional-dependencies]
30
+ test = ["pytest>=8.0.0"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/antonyrag/ragleap-core"
34
+ Repository = "https://github.com/antonyrag/ragleap-core"
35
+ Documentation = "https://github.com/antonyrag/ragleap-core/tree/main/packages/ragleap-integrations"
36
+ Issues = "https://github.com/antonyrag/ragleap-core/issues"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/ragleap_integrations"]
@@ -0,0 +1,30 @@
1
+ """
2
+ ragleap_integrations - connectors that return ragleap_tools.Tool objects.
3
+
4
+ v0.1.0: an MCP client over Streamable HTTP (see docs/design/mcp-client.md).
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ from ragleap_integrations.mcp import (
10
+ Discovery,
11
+ McpClient,
12
+ McpConfig,
13
+ McpConfigError,
14
+ McpDiscoveryError,
15
+ McpServerConfig,
16
+ McpToolSpec,
17
+ make_mcp_tools,
18
+ )
19
+
20
+ __all__ = [
21
+ "__version__",
22
+ "Discovery",
23
+ "McpClient",
24
+ "McpConfig",
25
+ "McpConfigError",
26
+ "McpDiscoveryError",
27
+ "McpServerConfig",
28
+ "McpToolSpec",
29
+ "make_mcp_tools",
30
+ ]