# DataGrout Conduit — Elixir SDK
Production-ready MCP client with mTLS, OAuth 2.1, and semantic discovery for Elixir/OTP.
This is a **client** library. It connects to remote MCP and JSON-RPC servers over HTTP/HTTPS, sends requests, and parses responses. It does not run any server, accept connections, or handle incoming requests.
## Installation
Add `datagrout_conduit` to your `mix.exs` dependencies:
```elixir
def deps do
[
{:datagrout_conduit, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
# Connect to a DataGrout MCP server
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: {:bearer, "your-api-key"}
)
# List available tools
{:ok, tools} = DatagroutConduit.Client.list_tools(client)
# Call a tool
{:ok, result} = DatagroutConduit.Client.call_tool(client, "get_invoices", %{status: "unpaid"})
# Extract cost metadata
meta = DatagroutConduit.extract_meta(result)
IO.inspect(meta.receipt)
```
## Authentication
### Bearer Token
```elixir
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: {:bearer, "your-token"}
)
```
### OAuth 2.1 (Client Credentials)
```elixir
{:ok, oauth} = DatagroutConduit.OAuth.start_link(
client_id: "your-client-id",
client_secret: "your-secret",
token_endpoint: "https://gateway.datagrout.ai/servers/{uuid}/oauth/token"
)
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: {:oauth, oauth}
)
```
Token endpoint can be auto-derived from the MCP URL:
```elixir
endpoint = DatagroutConduit.OAuth.derive_token_endpoint("https://gateway.datagrout.ai/servers/{uuid}/mcp")
# => "https://gateway.datagrout.ai/servers/{uuid}/oauth/token"
```
Tokens are cached and refreshed automatically 60 seconds before expiry.
### mTLS Client Certificates
```elixir
# Auto-discover identity (searches standard locations)
identity = DatagroutConduit.Identity.try_discover()
# Or from explicit paths
{:ok, identity} = DatagroutConduit.Identity.from_paths("cert.pem", "key.pem", "ca.pem")
# Or from PEM data
{:ok, identity} = DatagroutConduit.Identity.from_pem(cert_pem, key_pem, ca_pem)
# Use with client
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: {:bearer, "token"},
identity: identity
)
```
Discovery order:
1. `override_dir` option
2. `CONDUIT_MTLS_CERT` + `CONDUIT_MTLS_KEY` environment variables
3. `CONDUIT_IDENTITY_DIR` environment variable
4. `~/.conduit/identity.pem` + `identity_key.pem`
5. `.conduit/` relative to current working directory
Check certificate rotation:
```elixir
if DatagroutConduit.Identity.needs_rotation?(identity, threshold_days: 30) do
Logger.warning("mTLS certificate expires within 30 days")
end
```
For DataGrout URLs (`datagrout.ai`, `datagrout.dev`), mTLS identity is auto-discovered.
### Identity Registration & Bootstrap
Bootstrap a new mTLS identity with a one-time access token:
```elixir
{:ok, client} = DatagroutConduit.Client.bootstrap_identity(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth_token: "your-one-time-token",
name: "my-agent"
)
```
Or with OAuth client credentials:
```elixir
{:ok, client} = DatagroutConduit.Client.bootstrap_identity_oauth(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
client_id: "id",
client_secret: "secret",
name: "my-agent"
)
```
After bootstrap, subsequent runs auto-discover the saved identity — no tokens needed.
You can also use the registration module directly:
```elixir
{:ok, {private_pem, public_pem}} = DatagroutConduit.Registration.generate_keypair()
{:ok, response} = DatagroutConduit.Registration.register_identity(public_pem, auth_token: "token")
{:ok, paths} = DatagroutConduit.Registration.save_identity(response.cert_pem, private_pem, response.ca_cert_pem, "~/.conduit")
{:ok, ca_pem} = DatagroutConduit.Registration.fetch_ca_cert()
```
## Transports
### MCP (Streamable HTTP) — Default
```elixir
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://example.com/mcp",
transport: :mcp
)
```
Sends HTTP POST with JSON-RPC 2.0 bodies and MCP-specific headers. Handles both direct JSON and SSE response formats.
### JSON-RPC 2.0
```elixir
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://example.com/jsonrpc",
transport: :jsonrpc
)
```
Standard JSON-RPC 2.0 over HTTP POST.
## MCP Protocol Methods
```elixir
# Tools
{:ok, tools} = DatagroutConduit.Client.list_tools(client)
{:ok, result} = DatagroutConduit.Client.call_tool(client, "tool-name", %{arg: "val"})
# Resources
{:ok, resources} = DatagroutConduit.Client.list_resources(client)
{:ok, content} = DatagroutConduit.Client.read_resource(client, "resource://uri")
# Prompts
{:ok, prompts} = DatagroutConduit.Client.list_prompts(client)
{:ok, messages} = DatagroutConduit.Client.get_prompt(client, "prompt-name", %{})
```
## DataGrout Extensions
When connected to a DataGrout server, additional capabilities are available:
### Semantic Discovery
Find tools that match a natural-language goal:
```elixir
{:ok, results} = DatagroutConduit.Client.discover(client, goal: "find unpaid invoices", limit: 10)
# => %DiscoverResult{tools: [%DiscoveredTool{tool: %Tool{...}, score: 0.95, ...}], query: "...", total: 10}
```
### Perform (Enhanced Tool Execution)
Execute tools with demuxing, refraction, and charting:
```elixir
{:ok, result} = DatagroutConduit.Client.perform(client, "get_data", %{query: "..."}, demux: true)
```
### Guided Execution
Create and execute multi-step plans:
```elixir
{:ok, plan} = DatagroutConduit.Client.guide(client, goal: "create invoice from lead")
{:ok, result} = DatagroutConduit.Client.flow_into(client, plan)
```
Or use the interactive `GuidedSession`:
```elixir
{:ok, session} = DatagroutConduit.GuidedSession.start(client, goal: "create invoice from lead")
IO.inspect(DatagroutConduit.GuidedSession.options(session))
{:ok, session} = DatagroutConduit.GuidedSession.choose(session, 0)
{:ok, result} = DatagroutConduit.GuidedSession.complete(session)
```
### Prism Focus
Transform data through a lens:
```elixir
{:ok, result} = DatagroutConduit.Client.prism_focus(client, data: my_data, lens: "summary")
```
### Cost Estimation
```elixir
{:ok, estimate} = DatagroutConduit.Client.estimate_cost(client, "expensive-tool", %{})
# => %CreditEstimate{estimated_total: 2.5, net_total: 2.0, breakdown: %{...}}
```
## Cost Tracking
Every tool call returns metadata with credit receipts:
```elixir
{:ok, result} = DatagroutConduit.Client.call_tool(client, "analyze", %{data: "..."})
meta = DatagroutConduit.extract_meta(result)
case meta.receipt do
%{net_credits: credits, receipt_id: id} ->
Logger.info("Charged #{credits} credits (receipt: #{id})")
nil ->
:ok
end
```
## Intelligent Interface
For DataGrout URLs, the client automatically enables the _intelligent interface_:
- `list_tools/1` filters out integration tools (those containing `@` like `salesforce@1/get_lead@1`), exposing only `data-grout@1/discovery.discover@1` and `data-grout@1/discovery.perform@1`
- DG extension methods (`discover`, `perform`, `guide`, etc.) call direct JSON-RPC methods behind the scenes
Override this behavior:
```elixir
{:ok, client} = DatagroutConduit.Client.start_link(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: {:bearer, "token"},
use_intelligent_interface: false # show all tools including @-prefixed
)
```
## DG URL Detection
```elixir
DatagroutConduit.is_dg_url?("https://gateway.datagrout.ai/servers/123/mcp")
# => true
DatagroutConduit.is_dg_url?("https://example.com/mcp")
# => false
```
## Links
- [DataGrout Library](https://library.datagrout.ai) — Browse and connect to MCP servers
- [Security](https://app.datagrout.ai/security) — Security policies and audit logs
- [MCP Inspector](https://app.datagrout.ai/inspector) — Interactive MCP protocol debugger
- [JSONRPC Inspector](https://app.datagrout.ai/jsonrpc-inspector) — JSON-RPC protocol debugger
### Labs Papers
- [CTC (Cryptographic Trust Chain)](https://library.datagrout.ai/labs/ctc) — Verifiable AI execution receipts
- [Consequential Analysis](https://library.datagrout.ai/labs/consequential-analysis) — Impact assessment for AI tool calls
- [Policy](https://library.datagrout.ai/labs/policy) — Governance frameworks for AI agents
- [Semio](https://library.datagrout.ai/labs/semio) — Semantic guard rails
- [Credits](https://library.datagrout.ai/labs/credits) — Cost tracking and billing
- [AIL (AI Intermediate Language)](https://library.datagrout.ai/labs/ail) — Portable AI workflow format
## License
MIT — DataGrout <hello@datagrout.ai>