Metadata-Version: 2.4
Name: datagrout-conduit
Version: 0.6.0
Summary: Production-ready MCP client with mTLS, OAuth 2.1, and semantic discovery
Author-email: DataGrout <hello@datagrout.ai>
License: MIT
Project-URL: Homepage, https://github.com/DataGrout/conduit-sdk
Project-URL: Documentation, https://library.datagrout.ai/conduit-sdk
Project-URL: Repository, https://github.com/DataGrout/conduit-sdk
Project-URL: Issues, https://github.com/DataGrout/conduit-sdk/issues
Keywords: mcp,agents,ai,datagrout,llm,model-context-protocol
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp>=1.0.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: cryptography>=42.0.0
Provides-Extra: ws
Requires-Dist: websockets>=12.0; extra == "ws"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: black>=24.0.0; extra == "dev"
Requires-Dist: mypy>=1.8.0; extra == "dev"
Requires-Dist: ruff>=0.2.0; extra == "dev"
Requires-Dist: websockets>=12.0; extra == "dev"

# DataGrout Conduit — Python SDK

Production-ready MCP client with mTLS identity, OAuth 2.1, semantic discovery, and cost tracking.

## Installation

```bash
pip install datagrout-conduit==0.5.0
```

## Quick Start

```python
from datagrout.conduit import Client

async with Client("https://gateway.datagrout.ai/servers/{uuid}/mcp") as client:
    tools = await client.list_tools()
    result = await client.call_tool("salesforce@1/get_lead@1", {"id": "123"})
```

## Authentication

### Bearer Token

```python
client = Client(
    "https://gateway.datagrout.ai/servers/{uuid}/mcp",
    auth={"bearer": "your-access-token"},
)
```

### OAuth 2.1 (client_credentials)

```python
client = Client(
    "https://gateway.datagrout.ai/servers/{uuid}/mcp",
    client_id="your-client-id",
    client_secret="your-client-secret",
)
```

The SDK automatically fetches, caches, and refreshes JWTs before they expire.

### mTLS (Mutual TLS)

After bootstrapping, the client certificate handles authentication at the TLS layer — no tokens needed.

```python
from datagrout.conduit import Client, ConduitIdentity

# Auto-discover from env vars, CONDUIT_IDENTITY_DIR, or ~/.conduit/
client = Client("https://gateway.datagrout.ai/servers/{uuid}/mcp", identity_auto=True)

# Explicit identity from files
identity = ConduitIdentity.from_paths("certs/client.pem", "certs/client_key.pem")
client = Client("...", identity=identity)

# Multiple agents on one machine
client = Client("...", identity_dir="/opt/agents/agent-a/.conduit", identity_auto=True)
```

#### Identity Auto-Discovery Order

1. `identity_dir` option (if provided)
2. `CONDUIT_MTLS_CERT` + `CONDUIT_MTLS_KEY` environment variables (inline PEM)
3. `CONDUIT_IDENTITY_DIR` environment variable (directory path)
4. `~/.conduit/identity.pem` + `~/.conduit/identity_key.pem`
5. `.conduit/` relative to the current working directory

For DataGrout URLs (`*.datagrout.ai`), auto-discovery runs silently even without `identity_auto=True`.

#### Bootstrapping an mTLS Identity

First-run provisioning — generates a keypair, registers with the DataGrout CA, and saves certs locally. After this, the token is never needed again.

```python
# First run: token needed for registration
client = await Client.bootstrap_identity(
    url="https://gateway.datagrout.ai/servers/{uuid}/mcp",
    auth_token="your-access-token",
    name="my-laptop",
)

# Or bootstrap with OAuth 2.1 client_credentials
client = await Client.bootstrap_identity_oauth(
    url="https://gateway.datagrout.ai/servers/{uuid}/mcp",
    client_id="your-client-id",
    client_secret="your-client-secret",
    name="my-laptop",
)

# Subsequent runs: no token needed, mTLS auto-discovered
client = Client("https://gateway.datagrout.ai/servers/{uuid}/mcp")
```

## Semantic Discovery

When `use_intelligent_interface` is enabled, `list_tools()` returns only DataGrout's meta-tools. Agents use semantic search instead of enumerating raw integrations:

```python
client = Client("...", use_intelligent_interface=True)

# Semantic search across all connected integrations
results = await client.discover(query="find unpaid invoices", limit=5)

# Direct execution with cost tracking
result = await client.perform(
    tool="salesforce@1/get_lead@1",
    args={"id": "123"},
)
```

## Cost Tracking

Every tool call returns a receipt with credit usage:

```python
from datagrout.conduit import extract_meta

result = await client.call_tool("salesforce@1/get_lead@1", {"id": "123"})
meta = extract_meta(result)

if meta:
    print(f"Credits: {meta.receipt.net_credits}")
    print(f"Savings: {meta.receipt.savings}")
```

## Transports

```python
# MCP (default) — full MCP protocol over Streamable HTTP
client = Client(url)

# JSONRPC — lightweight, stateless, same tools and auth
client = Client(url, transport="jsonrpc")

# WebSocket — bidirectional push; requires pip install 'datagrout-conduit[ws]'
client = Client("wss://gateway.datagrout.ai/servers/{uuid}/ws", transport="websocket")
```

### WebSocket transport

The WebSocket transport uses the `datagrout-jsonrpc.v1` subprotocol over a single persistent `wss://` connection. All concurrent requests are multiplexed on that connection; responses are correlated by JSON-RPC `id` via `asyncio.Future` with no head-of-line blocking.

```python
from datagrout.conduit import Client

async with Client(
    "wss://gateway.datagrout.ai/servers/{uuid}/ws",
    auth={"bearer": "your-token"},
    transport="websocket",
) as client:
    # Subscribe to server-pushed events
    sub = await client.subscribe("agents.my-agent-id.events")

    async for event in sub:
        print(f"{event.event}: {event.data}")

    await client.unsubscribe(sub.id)
```

Supported topics:

| Topic | Fires when |
|-------|-----------|
| `agents.<agent_id>.events` | Agent lifecycle events (plan started, IC completed, grounding failed, …) |
| `tools.<tool_name>.results` | A specific tool call completes |
| `tasks.<task_id>.*` | Long-running background task transitions |
| `flows.<flow_id>.*` | `flow.into` progress and completion |
| `governor.<server_uuid>` | Governor percept events (file change, schedule, webhook) |

You can also call `await sub.recv()` for manual, one-event-at-a-time consumption:

```python
event = await sub.recv()
print(event.event, event.data)
```

**Reconnection**: after a disconnect, `send_request` raises `RuntimeError("WS transport not connected")`. Re-call `connect()` and re-subscribe — subscriptions do not survive reconnects in v0.4.

## API Reference

### Client Options

```python
Client(
    url: str,
    auth: dict = None,                    # {"bearer": "..."} or {"client_credentials": {...}}
    transport: str = "jsonrpc",           # "jsonrpc", "mcp", or "websocket"
    use_intelligent_interface: bool = False,
    identity: ConduitIdentity = None,     # explicit mTLS identity
    identity_auto: bool = False,          # auto-discover identity
    identity_dir: str = None,             # custom identity directory
    disable_mtls: bool = False,           # opt out of mTLS auto-discovery
    client_id: str = None,               # OAuth shorthand
    client_secret: str = None,           # OAuth shorthand
)
```

### Standard MCP Methods

| Method | Description |
|---|---|
| `list_tools()` | List available tools |
| `call_tool(name, args)` | Execute a tool |
| `list_resources()` | List resources |
| `read_resource(uri)` | Read a resource |
| `list_prompts()` | List prompts |
| `get_prompt(name, args)` | Get a prompt |

### DataGrout Extensions

| Method | Description |
|---|---|
| `discover(query, limit, integrations)` | Semantic tool search |
| `perform(tool, args, demux)` | Direct tool execution with tracking |
| `perform_batch(calls)` | Parallel tool execution |
| `guide(goal, policy, session_id)` | Guided multi-step workflow |
| `flow_into(plan, ...)` | Workflow orchestration |
| `prism_focus(data, lens)` | Data transformation via Prism lens |
| `estimate_cost(tool, args)` | Pre-execution credit estimate |

### Bootstrap Methods

| Method | Description |
|---|---|
| `Client.bootstrap_identity(url, auth_token, name)` | Bootstrap mTLS with access token |
| `Client.bootstrap_identity_oauth(url, client_id, client_secret, name)` | Bootstrap mTLS with OAuth 2.1 |

## Requirements

- Python 3.10+
- `httpx` (for JSONRPC transport)
- `mcp` package (optional, for MCP transport mode)

## License

MIT
