Skip to main content

DataGrout Conduit — Python SDK

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

Installation

pip install datagrout-conduit==0.8.1

Quick Start

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

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

OAuth 2.1 (client_credentials)

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.

OAuth 2.1 (authorization code + PKCE)

client_credentials authenticates a machine, with a secret issued out of band. To authenticate a person — and to reach https://gateway.datagrout.ai/connect, where the server binding is chosen at consent time and lives in the token rather than the URL — run the browser-consent flow once and persist the grant:

from datagrout.conduit import AuthCodeFlow, LoopbackListener

listener = await LoopbackListener.bind()          # 127.0.0.1, OS-chosen port
flow = await AuthCodeFlow.discover("https://gateway.datagrout.ai/connect")
registered = await flow.register("My App", listener.redirect_uri)

url, pending = flow.authorize_url()
print(f"Open this to sign in:\n{url}")            # the SDK never opens a browser

redirect = await listener.wait(timeout=300)
grant = await flow.exchange(pending, redirect.code, redirect.state)

# Persist BOTH: a client id without its redirect URI cannot be reused, because
# the authorization server matches redirect URIs exactly.
save_somewhere({"registered": registered.to_dict(), "grant": grant.to_dict()})

On later runs, skip straight to the grant:

client = Client(
    "https://gateway.datagrout.ai/connect",
    auth={"authorization_code": grant},
)

DataGrout rotates refresh tokens, so a grant that is refreshed and not written back leaves a consumed token on disk. Own the provider when you care:

from datagrout.conduit import AuthCodeProvider

provider = AuthCodeProvider(grant)
client = Client(url, auth={"authorization_code": provider})

# ...periodically, or once on shutdown:
rotated = provider.take_if_dirty()
if rotated is not None:
    save_somewhere({"registered": registered.to_dict(), "grant": rotated.to_dict()})

Where the grant lives is your decision — a keychain, a config file, a vault. The SDK owns its shape and its refresh, and deliberately picks no location. The shape is identical across every conduit SDK, so a grant written by the TypeScript client is readable by this one.

See examples/browser_signin.py for a complete run that registers, signs in, saves, and reuses.

mTLS (Mutual TLS)

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

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.

# 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:

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:

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

# 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.

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:

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

Client(
    url: str,
    auth: dict = None,                    # {"bearer": ...}, {"client_credentials": {...}},
                                          # or {"authorization_code": Grant | AuthCodeProvider}
    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

Metadata

Release files for datagrout-conduit 0.8.1

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

Source distribution (sdist)

Source distribution for datagrout-conduit 0.8.1
File Size Uploaded Provenance
datagrout_conduit-0.8.1.tar.gz 105.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for datagrout-conduit 0.8.1
File Interpreter ABI Platform
datagrout_conduit-0.8.1-py3-none-any.whl Python 3 none any Details

Total release size: 180.8 kB

Release files / datagrout_conduit-0.8.1.tar.gz

Download URL datagrout_conduit-0.8.1.tar.gz
Size 105.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3d65496304b86434b4011fe9f5b620653cb23e6bcc75d4d16016663182ea2e62
BLAKE2b-256 checksum
How to use checksums
68aa55005993e6cc38c0f7986a954ad6671052a37e0b5ecd2ce15cc9e3ee5ff4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Release files / datagrout_conduit-0.8.1-py3-none-any.whl

Download URL datagrout_conduit-0.8.1-py3-none-any.whl
Size 75.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bab17642bc7551813acef869b9044a47b45af1f631b841d188424000cfd82953
BLAKE2b-256 checksum
How to use checksums
158f91d4563f417d5a8919202c7e67fdf1386fc84ab8819c0377b51a350ef2eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Security for datagrout-conduit 0.8.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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