DataGrout Conduit SDK for Ruby
Production-ready MCP client with mTLS, OAuth 2.1, and semantic discovery.
Connect to remote MCP and JSONRPC servers, invoke tools, discover capabilities with natural language, and track costs — all with enterprise-grade security.
Installation
Add to your Gemfile:
gem "datagrout-conduit", "~> 0.4.0"
Or install directly:
gem install datagrout-conduit -v 0.4.0
Quick Start
require "datagrout_conduit"
# Create a client
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: { bearer: "your-token" }
)
# Connect and initialize the MCP session
client.connect
# List available tools
tools = client.list_tools
puts "Found #{tools.size} tools"
# Call a tool
result = client.call_tool("salesforce@1/get_lead@1", { id: "123" })
puts result
# Disconnect
client.disconnect
Authentication
Bearer Token
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: { bearer: "your-token" }
)
API Key
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: { api_key: "your-api-key" }
)
OAuth 2.1 (Client Credentials)
provider = DatagroutConduit::OAuth::TokenProvider.new(
client_id: "my_client_id",
client_secret: "my_client_secret",
token_endpoint: "https://app.datagrout.ai/servers/{uuid}/oauth/token"
)
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: { oauth: provider }
)
The token endpoint is auto-derived from MCP URLs — /mcp becomes /oauth/token. Tokens are cached and refreshed 60 seconds before expiry.
mTLS (Mutual TLS)
# Auto-discover identity from standard locations
identity = DatagroutConduit::Identity.try_discover
# Or load explicitly
identity = DatagroutConduit::Identity.from_paths("cert.pem", "key.pem", ca_path: "ca.pem")
# Or from PEM strings
identity = DatagroutConduit::Identity.from_pem(cert_pem, key_pem, ca_pem: ca_pem)
# Or from environment variables
identity = DatagroutConduit::Identity.from_env
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
identity: identity
)
Identity auto-discovery order:
override_dir(if provided)CONDUIT_MTLS_CERT+CONDUIT_MTLS_KEYenv varsCONDUIT_IDENTITY_DIRenv var~/.conduit/identity.pem+identity_key.pem.conduit/relative to cwd
For DataGrout URLs, identity discovery happens automatically.
Identity Registration & Bootstrap
Bootstrap a new mTLS identity with a one-time access token:
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:
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 class directly:
private_pem, public_pem = DatagroutConduit::Registration.generate_keypair
response = DatagroutConduit::Registration.register_identity(public_pem, auth_token: "token")
paths = DatagroutConduit::Registration.save_identity(response.cert_pem, private_pem, "~/.conduit", ca_pem: response.ca_cert_pem)
ca_pem = DatagroutConduit::Registration.fetch_ca_cert
Transports
MCP (default)
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/mcp",
auth: { bearer: "token" },
transport: :mcp
)
JSONRPC
client = DatagroutConduit::Client.new(
url: "https://gateway.datagrout.ai/servers/{uuid}/jsonrpc",
auth: { bearer: "token" },
transport: :jsonrpc
)
WebSocket (datagrout-jsonrpc.v1)
client = DatagroutConduit::Client.new(
url: "wss://gateway.datagrout.ai/servers/{uuid}/ws",
auth: { bearer: "your-token" },
transport: :websocket
)
client.connect
Bidirectional push over a single wss:// connection using websocket-driver ~> 0.7 (the library underlying Rails ActionCable — no EventMachine dependency). A background Thread runs the read loop; shared state is protected by a Mutex.
Push subscriptions
# Subscribe — returns a Subscription with recv + each (Enumerable)
sub = client.subscribe("agents.my-agent-id.events")
# Block on the next event
event = sub.recv(timeout: 30)
puts "#{event.event}: #{event.data.inspect}"
# Or iterate until the subscription is closed
sub.each do |event|
puts "#{event.event}: #{event.data.inspect}"
end
# Unsubscribe when done
client.unsubscribe(sub)
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) |
Reconnection: after a disconnect, send_request and subscribe raise DatagroutConduit::NotInitializedError. Call client.connect again and re-subscribe — subscriptions do not survive reconnects in v0.4.
Standard MCP Methods
client.connect
# Tools
tools = client.list_tools
result = client.call_tool("tool-name", { arg: "value" })
# Resources
resources = client.list_resources
content = client.read_resource("resource://uri")
# Prompts
prompts = client.list_prompts
= client.get_prompt("prompt-name", { key: "value" })
client.disconnect
DataGrout Extensions
Semantic Discovery
Find tools by natural language — 10-100x more token-efficient than listing all tools.
results = client.discover(goal: "find unpaid invoices", limit: 10)
results.tools.each do |tool|
puts "#{tool.name} (score: #{tool.score})"
end
Intelligent Tool Execution
result = client.perform("salesforce@1/get_lead@1", { email: "john@example.com" }, demux: false)
Guided Workflows
session = client.guide(goal: "create invoice from lead")
puts session.status # => "in_progress"
puts session. # => available choices
session = session.choose("option_a")
result = session.complete # => final result when status == "completed"
Flow Orchestration
plan = [
{ "tool" => "get_lead", "args" => { "email" => "john@example.com" } },
{ "tool" => "create_invoice", "args" => { "lead_id" => "$prev.id" } }
]
result = client.flow_into(plan)
Prism Focus
result = client.prism_focus(data: raw_data, lens: "summary")
Cost Estimation
estimate = client.estimate_cost("salesforce@1/get_lead@1", { id: "123" })
Cost Tracking
Every tool-call result from DataGrout includes a cost receipt:
result = client.call_tool("salesforce@1/get_lead@1", { id: "123" })
= DatagroutConduit.(result)
if
puts "Credits charged: #{.receipt.net_credits}"
puts "Receipt ID: #{.receipt.receipt_id}"
puts "Balance: #{.receipt.balance_after}"
if .receipt.byok.enabled
puts "BYOK discount: #{.receipt.byok.discount_applied}"
end
end
Behaviors
- DataGrout URL detection:
DatagroutConduit.dg_url?(url)returnstruefordatagrout.aiordatagrout.devdomains - Intelligent interface: Automatically enabled for DG URLs —
list_toolsfilters to non-@tools (DG semantic tools only). Disable withuse_intelligent_interface: false - Auto mTLS: DG URLs automatically attempt identity discovery
- DG extension warnings: Non-DG URLs log a one-time warning when DG-specific methods are called
- Default transport:
:mcp
Error Handling
begin
client.list_tools
rescue DatagroutConduit::NotInitializedError
client.connect
retry
rescue DatagroutConduit::McpError => e
puts "MCP error #{e.code}: #{e.}"
rescue DatagroutConduit::RateLimitedError => e
puts "Rate limited: #{e.used}/#{e.limit}"
rescue DatagroutConduit::AuthError => e
puts "Authentication failed: #{e.}"
rescue DatagroutConduit::ConnectionError => e
puts "Connection error: #{e.}"
rescue DatagroutConduit::ConfigError => e
puts "Configuration error: #{e.}"
end
Links
- DataGrout Library — Browse and discover integrations
- Security — Security documentation
- MCP Inspector — Interactive MCP testing
- JSONRPC Inspector — Interactive JSONRPC testing
- Labs Papers — Research and whitepapers
License
MIT License. Copyright (c) DataGrout.