MCP Tool Servers
Heartbit agents can discover and use tools from external MCP (Model Context Protocol) servers. Tools are discovered automatically at startup — no code changes needed.
Basic Configuration
Section titled “Basic Configuration”Add MCP server URLs to your agent config:
[[agents]]name = "researcher"description = "Research specialist"system_prompt = "You are a research specialist."mcp_servers = ["http://localhost:8000/mcp"]Multiple servers are supported:
mcp_servers = [ "http://localhost:8000/mcp", "http://localhost:9000/mcp"]Static Authentication
Section titled “Static Authentication”For servers requiring authentication, use the object form with an auth_header:
mcp_servers = [ { url = "http://gateway:8080/mcp", auth_header = "Bearer tok_xxx" }]You can mix simple URLs and authenticated entries:
mcp_servers = [ "http://localhost:8000/mcp", { url = "http://gateway:8080/mcp", auth_header = "Bearer tok_xxx" }]Environment Variable
Section titled “Environment Variable”For quick setup without a config file, use the HEARTBIT_MCP_SERVERS environment variable:
export HEARTBIT_MCP_SERVERS="http://localhost:8000/mcp,http://localhost:9000/mcp"Streamable HTTP Transport
Section titled “Streamable HTTP Transport”Heartbit uses Streamable HTTP as the MCP transport. The client connects to the server’s HTTP endpoint, sends JSON-RPC requests, and receives responses. The MCP protocol version is 2025-11-25.
At startup, the client:
- Sends an
initializerequest with client capabilities - Receives the server’s capabilities and tool definitions
- Registers discovered tools with the agent
Tool calls are forwarded to the MCP server as tools/call JSON-RPC requests.
Dynamic Authentication (Multi-Tenant)
Section titled “Dynamic Authentication (Multi-Tenant)”In multi-tenant deployments, each user needs their own credentials when calling MCP servers. Heartbit supports this through the AuthProvider trait:
StaticAuthProvider
Section titled “StaticAuthProvider”Returns the same auth header for all users. This is used automatically when you set auth_header in the config:
use heartbit::StaticAuthProvider;
let auth = StaticAuthProvider::new(Some("Bearer tok_xxx".into()));TokenExchangeAuthProvider
Section titled “TokenExchangeAuthProvider”Implements RFC 8693 Token Exchange to obtain per-user delegated tokens from an identity provider:
[daemon.auth]jwks_url = "https://idp.example.com/.well-known/jwks.json"
[daemon.auth.token_exchange]exchange_url = "https://idp.example.com/oauth/token"client_id = "heartbit-agent"client_secret = "secret123"agent_token = "agent-cred-token"scopes = ["crm:read", "crm:write"]How it works:
- User submits a task with a JWT
- The daemon stashes the raw JWT as the subject token
- When the agent calls an MCP tool,
TokenExchangeAuthProviderexchanges the user’s subject token for a scoped delegated token using RFC 8693 - The delegated token is injected into the MCP request’s
Authorizationheader - Tokens are cached per
(tenant_id, user_id)tuple with TTL-based expiry
This enables the agent to call MCP servers on behalf of the user with properly scoped permissions.
Configuration Fields
Section titled “Configuration Fields”| Field | Required | Description |
|---|---|---|
exchange_url | Yes | Token exchange endpoint URL |
client_id | Yes | OAuth client ID for the daemon |
client_secret | Yes | OAuth client secret for the daemon |
agent_token | Conditional | Static agent token (required if tenant_id not set) |
tenant_id | Conditional | NHI tenant ID for client_credentials grant (auto-fetches agent token) |
scopes | No | OAuth scopes for the delegated token |
Either agent_token or tenant_id must be provided. When tenant_id is set, the agent token is auto-fetched via client_credentials grant and cached.
Programmatic Usage
Section titled “Programmatic Usage”For custom MCP client setup in code:
use heartbit::{McpClient, StaticAuthProvider, TokenExchangeAuthProvider};use std::sync::Arc;
// Static authlet client = McpClient::new( "http://localhost:8000/mcp", Some(Arc::new(StaticAuthProvider::new(Some("Bearer tok".into())))),).await?;
// Token exchange authlet auth = TokenExchangeAuthProvider::new( "https://idp.example.com/oauth/token", "client-id", "client-secret", "agent-token",).with_scopes(vec!["read".into(), "write".into()]).with_user_tokens(user_tokens_map);
let client = McpClient::new( "http://gateway:8080/mcp", Some(Arc::new(auth)),).await?;
// Discovered tools are available via client.tools()