Metadata-Version: 2.4
Name: redis-context-gateway
Version: 0.1.0a2
Summary: Redis Context Gateway REST and MCP SDK
License: MIT
License-File: LICENSE
Author: Redis AI Services Team
Requires-Python: >=3.10
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: httpcore (>=1.0.9)
Requires-Dist: httpx (>=0.28.1,<1)
Requires-Dist: mcp (>=1.30.0,<2)
Requires-Dist: pydantic (>=2.11.2)
Description-Content-Type: text/markdown

# redis-context-gateway

Python SDK for Redis Context Gateway. One package holds both clients:

- `ContextGatewayService` is the control-plane client for Gateway and Upstream administration. Speakeasy generates it from the Gateway control-plane API, the same way as the other Iris SDKs, into `redis_context_gateway.controlplane`, which also holds its `models`, `errors` and HTTP client types.
- `ContextGatewayClient` is the data-plane client in `redis_context_gateway.mcp`. It connects agents to a Gateway's MCP endpoint with the official `mcp` library.

Both are exported from `redis_context_gateway`. Each loads on first use, so a control-plane script never imports the MCP library and an agent never imports the REST client. The data plane doesn't import the control plane at all.

Alpha, on PyPI as `redis-context-gateway`, generated from `redislabsdev/iris@2584d193`. Python 3.10 or later.

## Control plane

```python
from redis_context_gateway import ContextGatewayService

with ContextGatewayService(server_url=control_plane_url, admin_token=admin_token) as service:
    gateway = service.gateways.create(display_name="Docs", tool_exposure_mode="search")
    upstream = service.upstreams.create(
        gateway_id=gateway.id,
        display_name="Docs MCP",
        namespace="docs",
        connection={"endpoint": upstream_mcp_url},
        authentication={"methods": [{"bearer": {"value": upstream_token}}]},
    )
```

Models and errors come from the control plane: `from redis_context_gateway.controlplane import errors, models`. Every product call sends the admin token. The health probes (`service.health.live()`, `service.health.ready()`) send no credential. Async variants end in `_async`. See [USAGE.md](USAGE.md) and [docs/](docs/) for every operation and model.

A `connection` is exactly one variant: `endpoint` for an MCP Upstream or `database` for a Context Surface Upstream. A connection with both raises a pydantic `ValidationError` before any request is sent.

## Agents (MCP)

```python
from redis_context_gateway import ContextGatewayClient

client = ContextGatewayClient(gateway.mcp_endpoint, agent_key)
async with client.connect() as session:
    found = await session.call_tool("search_tools", {"query": "find an issue"})
    result = await session.call_tool(
        "run_tool", {"tool": {"upstreamId": upstream.id, "originalName": "get_issue"}, "toolArgs": {"id": 42}}
    )
```

`client.connect()` opens an initialized official `mcp.ClientSession`, so Lens instrumentation applies as is. Each call opens a new session. The constructor checks the URL, so an unsafe one fails before any request. The agent key is an Identity Service key with a `context-gateway` / `gateway` / `use` grant for the Gateway. Search tools, `run_tool` and the result handle tools are ordinary tool calls. A tool failure comes back as a result with `isError: true`, not an exception.

MCP reads default to a 300-second inactivity timeout, with 30-second connect, write and connection-pool budgets. Pass `timeout=10` to use ten seconds for every HTTP timeout, or `timeout=httpx.Timeout(30, read=600)` for a separate read budget. These are HTTP inactivity limits, not a total tool-call deadline. REST timeout defaults are unchanged.

## Migrating from 0.1.0a1

0.1.0a2 renames the clients so that, following the naming adopted in the ADR-0003 amendment, `<Product>Service` is the control-plane client and `<Product>Client` is the data-plane client.

| 0.1.0a1 | 0.1.0a2 |
| --- | --- |
| `ContextGatewayClient(server_url=..., admin_token=...)` (control plane) | `ContextGatewayService(server_url=..., admin_token=...)` |
| `from redis_context_gateway.mcp import connect` | `from redis_context_gateway import ContextGatewayClient` |
| `async with connect(url, agent_key) as session:` | `async with ContextGatewayClient(url, agent_key).connect() as session:` |
| `connect(url, agent_key, timeout=..., transport=...)` | `ContextGatewayClient(url, agent_key, timeout=..., transport=...).connect()` |
| `from redis_context_gateway import models` (or `redis_context_gateway.models`) | `from redis_context_gateway.controlplane import models` |
| `from redis_context_gateway import errors` (or `redis_context_gateway.errors`) | `from redis_context_gateway.controlplane import errors` |
| Other generated names at the root, such as `HttpClient`, `AsyncHttpClient`, `RetryConfig`, `SDKConfiguration` and `USER_AGENT` | The same names from `redis_context_gateway.controlplane`. `VERSION` and `__version__` stay at the root |

The old `ContextGatewayClient` name now means the data-plane client, so code that builds it with `server_url=` fails with a `TypeError`. Rename those calls to `ContextGatewayService`. `redis_context_gateway.mcp.connect` still works in 0.1.0a2 and emits a `DeprecationWarning`. It will be removed in a later alpha. The old root modules (`models`, `errors`, `utils`, `types`, `sdk` and the rest of the generated code) are gone, so those imports fail with `ImportError` until they import from `redis_context_gateway.controlplane`. Operations, models, errors and their arguments are otherwise unchanged.

## Safety

- Neither client follows redirects, so the admin token and agent keys stay on the Gateway.
- Plain HTTP is refused unless the host is loopback (localhost, 127.0.0.0/8 or ::1).
- Each request must match its own operation's method, path and query names, so an ID like `../../v1/gateways` fails before anything is sent. A custom HTTP client that can't turn redirects off is refused with a `TypeError`.
- The SDK never reads credentials from environment variables. Pass them in explicitly. The `redis-iris` CLI and goal objects own `IRIS_CONTEXT_GATEWAY_*` settings and the `[context_gateway]` config table.
- Upstream secrets are write-only: no response model carries them.
- Generated payload debugging is disabled, even when `debug_logger` is supplied, because it would log request secrets. Application logging and custom HTTP transports are outside this guarantee. Do not log whole generated exceptions: validation diagnostics can retain supplied credentials, and response errors retain server-provided bodies.
- No call is retried automatically.

## Development

```sh
uv venv && uv pip install -e . pytest pytest-asyncio ruff
# Python 3.11+: install the local Lens package for its integration test.
uv pip install ../lens-python-sdk
.venv/bin/python -m pytest tests
```

Speakeasy generates the control plane straight into `src/redis_context_gateway/controlplane/` (`moduleName: redis_context_gateway.controlplane` in `.speakeasy/gen.yaml`). Hand-written files, protected by `.genignore`, are the package root (`src/redis_context_gateway/__init__.py` and `py.typed`), the data plane (`src/redis_context_gateway/mcp.py`), the control-plane hooks (`src/redis_context_gateway/controlplane/_hooks/registration.py`), the connection variant check (`src/redis_context_gateway/controlplane/utils/connection_variants.py`), `tests/` and this README. The patch files in `.speakeasy/patches/` add that check to the generated `MCPConnection` and `ContextSurfaceConnection` models, and Speakeasy reapplies them on every generation. Everything else is regenerated by `speakeasy run -t context-gateway-python-sdk` from the repository root, or by the `sdk_generation_context_gateway_python_sdk.yaml` workflow.

